【问题标题】:Efficient way to reshape a dataset from long to wide从长到宽重塑数据集的有效方法
【发布时间】:2020-07-31 23:09:27
【问题描述】:

我有一个如下所示的医疗数据集:

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.5GB 压缩)并且有大量的disease_ids,因此在 R 中以最有效的方式重塑这些数据需要 11.7TB 的压缩空间 /em> RDs 格式(我知道这一点是因为我将数据集划分为 100 个块,并且对单个块进行整形会产生 117 GB 的重 RDs 文件;合并其中的 100 个会产生大于 11.7TB 的东西)。

现在,我有 5 个这么大的数据集需要合并在一起,所以我觉得有点卡住了。我需要提出更有效的数据表示,但不知道如何处理需要 1-hot 编码的分类变量。任何人都可以提出任何替代方法来处理这样的数据。

【问题讨论】:

  • 你可以试试data.table, 1) setDT(df) 2) df[, n := 1] 3) dcast(unique(df), patient_id~ disease_id, value.var = "n", fill = 0) 但是有这么大的数据我怀疑它是否会有帮助。
  • 这是一些非常大的数据。我认为您需要从最终目标入手。
  • data.table 库没用。使用 dcast 会出现 free(): invalid pointer 错误。
  • 宽格式数据会有很多零吗?如果是,请考虑基本包Matrix 稀疏矩阵功能。
  • @RuiBarradas 我认为您可以通过设置 drop=TRUE 来获得稀疏的数据帧,即使散布也是如此,但这并不奏效。

标签: r dataframe bigdata out-of-memory


【解决方案1】:

考虑到您想要进行流处理的输入的大小,而 R 并不适合这种处理,所以这里我们使用一个简单的 gawk 程序来代替。 gawk 在 Windows 上的 Rtools 中可用,并且原生与大多数 UNIX/Linux 系统一起提供。

在第一遍中,gawk 程序从输入的疾病字段(即第二个字段)创建一个关联数组 disease。据推测,疾病的数量远小于文件的长度,因此这很可能适合内存。

然后在第二遍中,它读取与患者对应的每组记录,假设患者的所有记录都是连续的。对于每个患者,它会输出一个包含患者 ID 和 0 和 1 序列的单行,以便第 i 个表示不存在或存在第 i 个疾病。

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 采用第三个空格分隔的字段(它是第三个,因为两个字段之间有两个空格)并 sort 对其进行排序以获取唯一元素。

sed 1d disease.txt | cut -f 3 -d " " | sort -u > diseases-sorted.txt

排序和合并

GNU 排序实用程序可以对大于内存的文件进行排序和合并,并有一个并行选项来加快速度。另请参阅免费的 cmsort 实用程序(仅限 Windows)。

csvfix

以下是一些使用免费的csvfix 命令行实用程序的脚本。您可能需要根据您使用的命令行处理器/shell 修改引号,并且需要将每个引号放在一行或适当地转义换行符(bash 为反斜杠,Windows cmd 为抑扬符)。为了清楚起见,我们将每条流水线分布在不同的线上。

下面的第一个管道在 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

【讨论】:

    【解决方案2】:

    你提出了有趣的问题。使用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

    数据库

    您有多个选项可以在R 中使用postgreSQL。其中之一是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 包阅读它们

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-14
      • 1970-01-01
      • 1970-01-01
      • 2017-11-04
      • 2012-12-01
      • 2011-01-16
      相关资源
      最近更新 更多