【问题标题】:row-by-row operations and updates in data.tabledata.table 中的逐行操作和更新
【发布时间】:2013-05-31 14:55:16
【问题描述】:

我最终得到了一个大数据表,我必须对每行进行操作。 (是的......我知道这显然不是 data.table 的用途)

R) set.seed(1)
R) DT=data.table(matrix(rnorm(100),nrow=10))
R) DT[,c('a','b'):=list(1:10,2:11)]
R) DT
               V1             V2             V3             V4            V5            V6             V7              V8            V9           V10  a  b
 1: -0.6264538107  1.51178116845  0.91897737161  1.35867955153 -0.1645235963  0.3981058804  2.40161776050  0.475509528900 -0.5686687328 -0.5425200310  1  2
 2:  0.1836433242  0.38984323641  0.78213630073 -0.10278772734 -0.2533616801 -0.6120263933 -0.03924000273 -0.709946430922 -0.1351786151  1.2078678060  2  3
 3: -0.8356286124 -0.62124058054  0.07456498337  0.38767161156  0.6969633754  0.3411196914  0.68973936245  0.610726353489  1.1780869966  1.1604026157  3  4
 4:  1.5952808021 -2.21469988718 -1.98935169586 -0.05380504058  0.5566631987 -1.1293630961  0.02800215878 -0.934097631644 -1.5235668004  0.7002136495  4  5
 5:  0.3295077718  1.12493091814  0.61982574789 -1.37705955683 -0.6887556945  1.4330237017 -0.74327320888 -1.253633400239  0.5939461876  1.5868334545  5  6
 6: -0.8204683841 -0.04493360902 -0.05612873953 -0.41499456330 -0.7074951570  1.9803998985  0.18879229951  0.291446235517  0.3329503712  0.5584864256  6  7
 7:  0.4874290524 -0.01619026310 -0.15579550671 -0.39428995371  0.3645819621 -0.3672214765 -1.80495862889 -0.443291873218  1.0630998373 -1.2765922085  7  8
 8:  0.7383247051  0.94383621069 -1.47075238390 -0.05931339671  0.7685329245 -1.0441346263  1.46555486156  0.001105351632 -0.3041839236 -0.5732654142  8  9
 9:  0.5757813517  0.82122119510 -0.47815005511  1.10002537198 -0.1123462122  0.5697196274  0.15325333821  0.074341324152  0.3700188099 -1.2246126149  9 10
10: -0.3053883872  0.59390132122  0.41794156020  0.76317574846  0.8811077265 -0.1350546039  2.17261167036 -0.589520946188  0.2670987908 -0.4734006364 10 11

假设我希望在所有Vi 列中逐行使用min,我曾经在使用data.frame 时使用apply

apply(DT[,paste0('V',1:10),with=FALSE],FUN=min,MAR=1)
 [1] -0.6264538107 -0.7099464309 -0.8356286124 -2.2146998872 -1.3770595568 -0.8204683841 -1.8049586289 -1.4707523839 -1.2246126149 -0.5895209462

这样我就可以轻松更新了。

好的,现在说我想同时更新minmax(当然这是一个例子,所以我只拿了两件事,但在现实生活中会是 10……)

 f = function(x){return(c(max=max(x),min=min(x)))}
 new=apply(DT[,paste0('V',1:10),with=FALSE],FUN=f,MAR=1)
             [,1]          [,2]          [,3]         [,4]         [,5]          [,6]         [,7]         [,8]         [,9]         [,10]
max  2.4016177605  1.2078678060  1.1780869966  1.595280802  1.586833455  1.9803998985  1.063099837  1.465554862  1.100025372  2.1726116704
min -0.6264538107 -0.7099464309 -0.8356286124 -2.214699887 -1.377059557 -0.8204683841 -1.804958629 -1.470752384 -1.224612615 -0.5895209462

我想写

DT[,rownames(new):=new]

但这不起作用,所以这是我的问题

  1. 使用我的方法,如何转换new,以便我可以立即更新DT
  2. 是否有更好的方法(允许我一次更新多个列,并得到逐行计算的结果)

编辑:我找到了 1 的解决方案,但这很丑,实际上 := 不处理 matrix 很奇怪,我很确定它使用过是这样的

DT[,c('a1','a2'):=data.table(matrix(apply(DT[,paste0('V',1:10),with=FALSE],FUN=f,MAR=1),byrow=T,nrow=10))]
R) DT
               V1             V2             V3             V4            V5            V6             V7              V8            V9           V10  a  b
 1: -0.6264538107  1.51178116845  0.91897737161  1.35867955153 -0.1645235963  0.3981058804  2.40161776050  0.475509528900 -0.5686687328 -0.5425200310  1  2
 2:  0.1836433242  0.38984323641  0.78213630073 -0.10278772734 -0.2533616801 -0.6120263933 -0.03924000273 -0.709946430922 -0.1351786151  1.2078678060  2  3
 3: -0.8356286124 -0.62124058054  0.07456498337  0.38767161156  0.6969633754  0.3411196914  0.68973936245  0.610726353489  1.1780869966  1.1604026157  3  4
 4:  1.5952808021 -2.21469988718 -1.98935169586 -0.05380504058  0.5566631987 -1.1293630961  0.02800215878 -0.934097631644 -1.5235668004  0.7002136495  4  5
 5:  0.3295077718  1.12493091814  0.61982574789 -1.37705955683 -0.6887556945  1.4330237017 -0.74327320888 -1.253633400239  0.5939461876  1.5868334545  5  6
 6: -0.8204683841 -0.04493360902 -0.05612873953 -0.41499456330 -0.7074951570  1.9803998985  0.18879229951  0.291446235517  0.3329503712  0.5584864256  6  7
 7:  0.4874290524 -0.01619026310 -0.15579550671 -0.39428995371  0.3645819621 -0.3672214765 -1.80495862889 -0.443291873218  1.0630998373 -1.2765922085  7  8
 8:  0.7383247051  0.94383621069 -1.47075238390 -0.05931339671  0.7685329245 -1.0441346263  1.46555486156  0.001105351632 -0.3041839236 -0.5732654142  8  9
 9:  0.5757813517  0.82122119510 -0.47815005511  1.10002537198 -0.1123462122  0.5697196274  0.15325333821  0.074341324152  0.3700188099 -1.2246126149  9 10
10: -0.3053883872  0.59390132122  0.41794156020  0.76317574846  0.8811077265 -0.1350546039  2.17261167036 -0.589520946188  0.2670987908 -0.4734006364 10 11
             a1            a2
 1: 2.401617761 -0.6264538107
 2: 1.207867806 -0.7099464309
 3: 1.178086997 -0.8356286124
 4: 1.595280802 -2.2146998872
 5: 1.586833455 -1.3770595568
 6: 1.980399899 -0.8204683841
 7: 1.063099837 -1.8049586289
 8: 1.465554862 -1.4707523839
 9: 1.100025372 -1.2246126149
10: 2.172611670 -0.5895209462

EDIT2:从我的数据上看,使用DT[, (newColnames):=f(.DT), by=IDX, .SDcols=someIdx] 比应用方式慢得多,这是预期的吗?

【问题讨论】:

  • 感谢您的评论,我会继续申请,因为它看起来是迄今为止最快的,也感谢您的回答,但正如我在帖子中所说的那样,最小值和最大值只是简单的示例,applyallow更短更灵活的代码。最后我更尴尬的是data.table(matrix(...部分

标签: r data.table


【解决方案1】:

在每一行上创建.SD 可能是一项非常昂贵的操作,尤其是当您的data.table 包含rows >> columns 时。我建议使用pminpmax 跨列循环使用。我将用更大的数据(沿行)来说明这一点。

数据:

set.seed(1)
require(data.table)
DT1 <- data.table(matrix(rnorm(1e6),ncol=10))
DT1[, a := 1:1e5]
DT2 <- copy(DT1)
DT3 <- copy(DT1)

功能:

arun <- function(DT) {
    # assign first column (dummy)
    DT[, `:=`(min = DT[, V1], max = DT[, V1])]
    # get all other column names and use pmin and pmax 
    # and replace min and max columns
    cols <- names(DT)[2:10]
    for (i in cols) {
        DT[, `:=`(min = pmin(min, DT[[i]]), max = pmax(max, DT[[i]]))]
    }
    DT
}

eddi <- function(DT) {
    DT[, `:=`(min = min(.SD), max = max(.SD)), by = a, .SDcols = paste0("V", 1:10)]
}

frank <- function(DT) {
    cols    <- names(DT)[grepl('^V[[:digit:]]+$',names(DT))]
    newcols <- c("min","max")
    myfun   <- range
    DT[,(newcols):=as.list(myfun(.SD)),.SDcols=cols,by=1:nrow(DT)]
}

基准测试:

require(microbenchmark)
microbenchmark(o1 <- arun(DT1), o2 <- eddi(DT2), o3 <- frank(DT3), times=2)

Unit: milliseconds
             expr        min         lq     median          uq         max neval
  o1 <- arun(DT1)   204.4417   204.4417   250.5205    296.5992    296.5992     2
  o2 <- eddi(DT2) 92343.5321 92343.5321 96706.1622 101068.7923 101068.7923     2
 o3 <- frank(DT3) 49083.7000 49083.7000 49521.9296  49960.1592  49960.1592     2

identical(o1, o2) # TRUE
identical(o1, o3) # TRUE

--

正如@Frank 在 cmets 下指出的那样,您可以将 for 循环替换为 do.call 为:

DT[, c("min", "max") := { z <- dt[, 1:10]; 
             list(do.call(pmin, z), do.call(pmax, z))}]

【讨论】:

  • 很酷的东西。这在我的计算机上执行大致相同并且缺少循环:colrange = (1:ncol(DT))[grepl('^V[[:digit:]]+$',names(DT))]; DT[,c("min","max"):={z &lt;- "[.listof"(DT,colrange); list(do.call(pmin,z),do.call(pmax,z))}]。我必须查找 methods("[") 才能弄清楚。
  • 这样更好,但您不需要z &lt;- "[.listof"...do.call 期望 listdata.table 在内部是 list。所以,你可以这样做:DT[,c("min","max"):= list(do.call(pmin,DT[, 1:10, with=FALSE),do.call(pmax,DT[, 1:10, with=FALSE))]
  • 是的,但我只需要包含相关的列(不是“a”)。
  • (在 R 公共聊天中回复。)
【解决方案2】:

由于您已经将行号作为您的data.table* 中的一列,您可以这样做:

DT[, `:=`(a1 = max(.SD), a2 = min(.SD)), by = a, .SDcols = paste0("V", 1:10)]

setkey(DT, a)
DT[J(a), `:=`(a1 = max(.SD), a2 = min(.SD)), .SDcols = paste0("V", 1:10)]

第二个选项使用无声by-without-by

*当然你也可以只使用row.names1:nrow(DT)

【讨论】:

  • 我尝试了by=row.names(DT) 并得到了一个错误。为了澄清,它说“by=eval(row.names(DT)) 应该可以工作。”
  • @Frank 使用 by=list(row.names(DT))
  • @statquant - 对不起,我没有得到你想要的?只需添加您需要的任何其他计算
  • 我打算发布一个和你一样的答案,但拼写出来:cols &lt;- names(DT)[grepl('^V[[:digit:]]$',names(DT))];newcols &lt;- c("min","max");myfun &lt;- range;DT[,(newcols):=as.list(myfun(.SD)),.SDcols=cols,by=1:nrow(DT)]
  • @Frank - 我更喜欢1:nrow(DT),我要偷它并添加到上面:)
【解决方案3】:

如果您想使用不同的功能,这说明了步骤:

cols    <- names(DT)[grepl('^V[[:digit:]]+$',names(DT))]
newcols <- c("min","max")
myfun   <- range
DT[,(newcols):=as.list(myfun(.SD)),.SDcols=cols,by=1:nrow(DT)]

【讨论】:

  • 你需要使用[[:digit:]]+$ 我想否则,它会跳过所有数字> 9的列(例如V10)。
  • 当然。不幸的是,这在有很多行的data.table 上会非常慢。
  • 这可能是 OP 尚未接受答案的原因。如果不将数据存储在矩阵而不是data.table 中,我不知道如何加快速度。我很想看看它是如何做到的。
【解决方案4】:

我是否遗漏了什么,这不是给出跨行的最小值

set.seed(1)
DT=data.table(matrix(rnorm(100),nrow=10))
DT[,c('a','b'):=list(1:10,2:11)]
DT
cols<-c("V1", "V2", "V3", "V4", "V5", "V6", "V7", "V8", "V9", "V10")

方法一

DT[,Min_Vi:=do.call(pmin, c(.SD, na.rm=TRUE)), .SDcols=cols]

方法二

transform(DT,Min_Vi=pmin(get(cols)))

【讨论】:

    猜你喜欢
    • 2014-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-14
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    相关资源
    最近更新 更多