从长到宽重塑数据集的有效方法

时间:2020-04-18 12:00:34

标签: r dataframe bigdata out-of-memory

我有一个看起来像这样的医学数据集:

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编码的分类变量。任何人都可以建议其他替代方法来处理此类数据。

2 个答案:

答案 0 :(得分:0)

您提出了一个有趣的问题。使用R分析该数据量将是一个真正的改变。

所以,我只能给您一般建议。首先,我认为您需要分离RAM和磁盘存储。使用Rds不能帮助您改善整形的效率,但是与csv相比,磁盘上生成的数据更少。

关于整形的效率

data.table

如果您要使用内存方法,除了使用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)。这样可以实现从widelong的转换。本着这种精神(pyspark code

df.withColumn("n", psf.lit(1)).pivot("patient_id").sum("n")

关于磁盘的大小

您使用Rds。您需要对某些格式进行更多压缩,例如fstparquet文件也得到了非常压缩,这也许是存储大量数据的最佳选择之一。您可以使用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

下面是一些使用免费的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