我有一个看起来像这样的医学数据集:
patient_id disease_id
1111111111 DISEASE:1
1111111111 DISEASE:2
1111111111 DISEASE:3
1111111111 DISEASE:4
1111111111 DISEASE:5
1111111111 DISEASE:6
1111111111 DISEASE:6
1111111112 DISEASE:1
1111111112 DISEASE:2
1111111112 DISEASE:4
1111111113 DISEASE:1
1111111113 DISEASE:5
我需要将其输入到神经网络/随机森林模型中。因此,唯一想到的自然数据表示形式就是:
patient_id DISEASE:1 DISEASE:2 DISEASE:3 DISEASE:4 DISEASE:5 DISEASE:6 ...
11111111111 1 1 1 1 1 1 ...
11111111112 1 1 0 1 0 0 ...
11111111113 1 0 0 0 1 0 ...
但是我的数据集非常大(〜50GB,压缩后的1.5 GB),并且有disease_id
吨,因此在中以最有效的方式重塑数据可能需要R中有11.7 TB的空间。 (以RDs格式压缩(我知道这是因为我将数据集分成100个块,并且对单个数据集进行重塑会产生117 GB的沉重RDs文件;将其中的100个合并将产生大于11.7TB的文件)。
现在,我有5个需要合并在一起的大数据集,所以我感觉有些卡住。我需要提出一个更有效的数据表示形式,但不知道如何处理需要1-hot编码的分类变量。任何人都可以建议其他替代方法来处理此类数据。
答案 0 :(得分:0)
您提出了一个有趣的问题。使用R
分析该数据量将是一个真正的改变。
所以,我只能给您一般建议。首先,我认为您需要分离RAM和磁盘存储。使用Rds
不能帮助您改善整形的效率,但是与csv
相比,磁盘上生成的数据更少。
如果您要使用内存方法,除了使用data.table::dcast
之外,我没有其他可能性。在这种情况下,请遵循@Ronak Shah的建议:
library(data.table)
setDT(df)
df[, n := 1]
dcast(unique(df), patient_id~ disease_id, value.var = "n", fill = 0)
?data.table::dcast
:
本着data.table的精神,它非常快速且内存高效,使其非常适合处理RAM中的大型数据集。更重要的是,它能够在内存使用方面非常有效地处理非常大的数据。
对于海量数据,我认为内存并不是最合适的方法。您可能会研究数据库方法(尤其是postgreSQL
)或Spark
。
您可以在postgreSQL
中使用多个选项来使用R
。其中之一是dbplyr:如果您知道tidyverse
语法,就会发现熟悉的动词。与标准R
数据框相比,数据透视操作对数据库而言有些棘手,但您可能会发现some ways to do that。您可以找到比我更多的数据库专家,这会给您带来很多有趣的窍门。
Spark
Spark
可能是执行整形的很好人选。如果您使用的是个人计算机(独立模式),您仍然可以并行执行内核之间的任务,但是不要忘记更改会话的spark.memory.fraction
参数,否则我认为您可能会遇到out of memory
问题。我比pyspark
更习惯sparkR
,但我认为逻辑是相同的。
自Spark 1.6
起,您可以透视数据(ex: pyspark doc)。这样可以实现从wide
到long
的转换。本着这种精神(pyspark code
)
df.withColumn("n", psf.lit(1)).pivot("patient_id").sum("n")
您使用Rds
。您需要对某些格式进行更多压缩,例如fst
。 parquet
文件也得到了非常压缩,这也许是存储大量数据的最佳选择之一。您可以使用SparkR
或使用arrow
软件包阅读它们
答案 1 :(得分:0)
鉴于您要进行流处理的输入大小,R不适合此类处理,因此这里我们使用一个简单的gawk程序。 gawk在Windows上的Rtools中可用,并且在大多数UNIX / Linux系统中都自带。
在第一遍中,gawk程序从输入的疾病字段(即第二字段)创建关联数组disease
。推测疾病的数量比文件的长度小得多,因此很可能适合内存。
然后在第二遍中,假定患者的所有记录都是连续的,它将读取与患者相对应的每组记录。对于每位患者,它会输出一行带有患者ID的行以及0和1的序列,以便ith表示不存在或存在ith疾病。
FNR == 1 { next } # skip header on both passes
# first pass - create disease array
FNR == NR {
disease[$2] = 0;
next;
}
# second pass - create and output flattened records
{
if ($1 != prevkey && FNR > 2) {
printf("%s ", prevkey);
for(d in disease) printf("%d ", disease[d]);
printf("\n");
for(d in disease) disease[d] = 0;
}
disease[$2] = 1;
prevkey = $1;
}
END {
if (FNR == NR) for(d in disease) {
print d;
} else {
printf("%s ", prevkey);
for(d in disease) printf("%d ", disease[d]);
printf("\n");
}
}
如果将上面的gawk代码放入model_mat.awk中,则可以这样运行它-请注意,文件必须指定两次-对于两次遍历中的每一次都必须指定一次:
gawk -f model_mat.awk disease.txt disease.txt
以下是输出,我们假设希望每种疾病都以1表示(如果存在)或0表示(如果不存在)。
1111111111 1 1 1 1 1 1
1111111112 1 1 0 1 0 0
1111111113 1 0 0 0 1 0
如果仅使用一个disease.txt参数运行它,则它将仅运行第一遍,然后在末尾列出没有重复的疾病:
gawk -f model_mat.awk disease.txt
给予:
DISEASE:1
DISEASE:2
DISEASE:3
DISEASE:4
DISEASE:5
DISEASE:6
列出疾病的另一种方法是此UNIX管道,该管道列出没有重复的疾病并对其进行排序。 sed删除标题,cut截取第三个空格分隔的字段(由于两个字段之间有两个空格,所以使用第三个空格),并对其进行排序,并采用唯一元素。
sed 1d disease.txt | cut -f 3 -d " " | sort -u > diseases-sorted.txt
GNU排序实用程序可以对大于内存的文件进行排序和合并,并具有并行选项以加快速度。另请参见免费的cmsort实用程序(仅Windows)。
下面是一些使用免费的csvfix命令行实用程序的脚本。您可能需要根据使用的命令行处理器/ shell修改引号,并且需要将引号放在单行或适当地换行(对于bash为反斜杠,对于Windows cmd为circumflex)。为了清楚起见,我们已经显示了每个管道分布在单独的行上。
下面的第一个管道在Disease-list.txt中创建了一个疾病单栏列表。其中的第一个csvfix命令删除标题,第二个csvfix命令提取第二个字段(即删除患者ID),最后一个csvfix命令将其减少为唯一的疾病。
下面的第二个管道创建一个文件,每个病人一行一行,带有病人ID,后跟该病人的疾病。其中的第一个csvfix命令将删除标头,第二个将其转换为csv格式,最后一个csvfix命令将其展平。
csvfix remove -if "$line == 1" -smq disease.txt |
csvfix read_dsv -s " " -cm -f 2 |
csvfix uniq -smq > disease-list.txt
csvfix remove -if "$line == 1" -smq disease.txt |
csvfix read_dsv -s " " -cm -f 1,2 |
csvfix flatten -smq > flat.txt