【问题标题】:Efficiently reformat column entries in large data set in R在 R 中有效地重新格式化大型数据集中的列条目
【发布时间】:2015-12-27 04:31:17
【问题描述】:

我有一个大型(600 万行)值表,我认为需要对其进行重新格式化,然后才能将其用于与我的数据集进行比较。该表有我关心的 3 列。 第一列包含核苷酸碱基变化,形式为 C>G、A>C、A>G 等。我想将它们分成两个单独的列。 第二列有染色体和碱基位置,格式为 10:130448、2:40483、5:30821291 等。我也想把它分成两列。 第三列包含多个样本群体中的等位基因部分,格式为 .02/.03/.20。我想将第三部分提取到一个新列中。

问题是我写的代码目前非常慢。看起来它需要大约一天半的时间才能运行。我在这里缺少什么吗?任何建议,将不胜感激。

我当前的代码执行以下操作:pos、change 和 fraction 分别接收上述值的向量 split 使用 strsplit。然后我遍历整个数据库,从这三个向量中获取第 i 个值,并使用我想要的值创建新列。

数据库格式化后,我应该可以轻松地通过染色体数、碱基、参考等位基因、替代等位基因等检查大量样本。

pos <- strsplit(total.esp$NCBI.Base, ":")
change <- strsplit(total.esp$Alleles, ">")
fraction <- strsplit(total.esp$'MAFinPercent(EA/AA/All)', "/")
for (i in 1:length(pos)){
    current <- pos[[i]]
    mutation <- change[[i]]
    af <- fraction[[i]]
    total.esp$chrom[i] <- current[1]
    total.esp$base[i] <- current [2]
    total.esp$ref[i] <- mutation[1]
    total.esp$alt[i] <- mutation[2]
    total.esp$af[i] <- af[3]

}

谢谢!

【问题讨论】:

    标签: r


    【解决方案1】:

    这是data.table 解决方案。我们将 'data.frame' 转换为 'data.table' (setDT(df1)),使用 lapply 循环 Data.table (.SD) 的子集,使用 tstrsplitsplit 通过指定列分割字符,unlistrecursive=FALSE 的输出。

    library(data.table)#v1.9.6+
    setDT(df1)[, unlist(lapply(.SD, tstrsplit,
            split='[>:/]', type.convert=TRUE), recursive=FALSE)]
    #   Alleles1 Alleles2 NCBI.Base1 NCBI.Base2 MAFinPercent1 MAFinPercent2
    #1:        C        G         10     130448          0.02          0.03
    #2:        A        C          2      40483          0.05          0.03
    #3:        A        G          5   30821291          0.02          0.04
    #   MAFinPercent3
    #1:          0.20
    #2:          0.04
    #3:          0.03
    

    注意:我假设数据集中只有 3 列。如果有更多列,并且只想对 3 列进行拆分,我们可以指定 .SDcols= 1:3 即列索引或实际列名,将输出分配 (:=) 到新列并子集仅在输出中需要。

    数据

    df1 <- data.frame(Alleles =c('C>G', 'A>C', 'A>G'), 
       NCBI.Base=c('10:130448', '2:40483', '5:30821291'), 
       MAFinPercent= c('.02/.03/.20', '.05/.03/.04', '.02/.04/.03'), 
       stringsAsFactors=FALSE)
    

    【讨论】:

      【解决方案2】:

      您可以使用tidyrdplyrseparate

      library(tidyr)
      library(dplyr)
      
      total.esp %>% separate(Alleles, c("ref", "alt"), sep=">") %>% 
                    separate(NCBI.Base, c("chrom", "base"), sep=":") %>%
                    separate(MAFinPercent.EA.AA.All., c("af1", "af2", "af3"), sep="/") %>%
                    select(-af1, -af2, af = af3)
      

      你需要小心最后一个MAFinPercent.EA.AA.All. - 你有一个可怕的列名,所以可能不得不重命名它/引用它,这取决于 r 的确切程度(这也是一个很好的理由,至少包括您问题中的一些数据,例如dput(head(total.esp)) 的输出)。

      用于检查的数据:

      total.esp <- data.frame(Alleles= rep("C>G", 50), NCBI.Base = rep("10:130448", 50), 'MAFinPercent(EA/AA/All)'= rep(".02/.03/.20", 50))
      

      因为我们现在有一个tidyr/dplyr 解决方案、一个data.table 解决方案和一个基本解决方案,让我们对它们进行基准测试。首先,来自@akrun 的数据,总共 300,000 行:

      df1 <- data.frame(Alleles =rep(c('C>G', 'A>C', 'A>G'), 100000),
                        NCBI.Base=rep(c('10:130448', '2:40483', '5:30821291'),  100000),
                        MAFinPercent= rep(c('.02/.03/.20', '.05/.03/.04', '.02/.04/.03'),  100000),
                        stringsAsFactors=FALSE)
      

      现在,基准测试:

      microbenchmark::microbenchmark(
        tidyr = {df1 %>% separate(Alleles, c("ref", "alt"), sep=">") %>% 
                         separate(NCBI.Base, c("chrom", "base"), sep=":") %>%
                         separate(MAFinPercent, c("af1", "af2", "af3"), sep="/") %>%
                         select(-af1, -af2, af = af3)},
        data.table = {setDT(df1)[, unlist(lapply(.SD, tstrsplit,
                                                 split='[>:/]', type.convert=TRUE), recursive=FALSE)]},
        base = {pos <- strsplit(df1$NCBI.Base, ":");
                change <- strsplit(df1$Alleles, ">");
                fraction <- strsplit(df1$MAFinPercent, "/");
                data.frame( chrom =sapply( pos, "[", 1), 
                            base = sapply( pos, "[", 2), 
                            ref  = sapply( change, "[", 1), 
                            alt = sapply(change, "[", 2), 
                            af  = sapply( fraction,  "[", 3)
                )}
      )
      Unit: seconds
             expr      min       lq     mean   median       uq      max neval
            tidyr 1.295970 1.398792 1.514862 1.470185 1.629978 1.889703   100
       data.table 2.140007 2.209656 2.315608 2.249883 2.481336 2.666345   100
             base 2.718375 3.079861 3.183766 3.154202 3.221133 3.791544   100
      

      tidyr 是赢家

      【讨论】:

      • 我认为在大数据集上,data.table 应该更快。 30,000 行并不是很大。
      • 我也很惊讶 - 如果您运行更大的基准测试,请随意编辑
      • 我正在尝试更大的数据集。
      • 我还有 tidyr 赢了 300,000
      • 1.验证结果在基准测试中是否相同非常重要。 Akrun 使用了type.convert=TRUE。 2. 差异的主要原因是您使用更简单的模式并为每一列重复separate()(不是真正通用的)。如果我在每列上使用 tstrsplit() 和在 300,000 行上使用 fixed=TRUE 做同样的事情,则需要 0.27 秒(tidyr = 1.1 秒)。
      【解决方案3】:

      试试这个(保留前三行代码后):

       total.esp   <- data.frame( chrom =sapply( pos, "[", 1), 
                                  base = sapply( pos, "[", 2), 
                                  ref  = sapply( change, "[", 1), 
                                  alt = sapply(change, "[", 2), 
                                  af  = sapply( af,  "[", 3)
                                 )
      

      我无法想象这需要超过几分钟的时间。 (我确实使用类似大小的 R 对象。)

      【讨论】:

      • data.table 1.9.6 有一个有效的 tstrsplit 函数,如果有帮助的话。 R 中的 for 循环通常不是一个好主意。参见例如这个:adv-r.had.co.nz/memory.html [向下滚动]。
      • 花了大约 40 秒!非常感谢。我没有意识到括号可以用作函数
      • 是的。发现[[[~$ 都是真正的“底层”函数,这也让我越来越惊讶。它们都可以写成(并且实际上被解析为)内部形式,如"["(dat, row,col)
      猜你喜欢
      • 1970-01-01
      • 2014-11-07
      • 1970-01-01
      • 1970-01-01
      • 2012-12-02
      • 1970-01-01
      • 1970-01-01
      • 2021-11-09
      相关资源
      最近更新 更多