【问题标题】:Row-wise manipulation on large dataset大型数据集的逐行操作
【发布时间】:2013-10-03 21:47:36
【问题描述】:

我正在寻找一种更快的方法来实现以下操作。数据集包含 > 1M 行,但我提供了一个简化示例来说明该任务 --

To create the data table --

dt <- data.table(name=c("john","jill"), a1=c(1,4), a2=c(2,5), a3=c(3,6), 
      b1=c(10,40), b2=c(20,50), b3=c(30,60))

colGroups <- c("a","b")   # Columns starting in "a", and in "b"

Original Dataset
-----------------------------------
name    a1   a2   a3   b1   b2   b3
john    1    2    3    10   20   30
jill    4    5    6    40   50   60

上面的数据集经过转换,为每个唯一名称添加了 2 个新行,并且在每一行中,每组列的值独立左移(在本例中,我使用了 a 列和 b 列,但有更多)

Transformed Dataset
-----------------------------------
name    a1   a2   a3   b1   b2   b3
john    1    2    3    10   20   30  # First Row for John
john    2    3    0    20   30    0  # "a" values left shifted, "b" values left shifted
john    3    0    0    30   0     0  # Same as above, left-shifted again

jill    4    5    6    40   50   60  # Repeated for Jill
jill    5    6    0    50   60    0 
jill    6    0    0    60    0    0

等等。我的数据集非常大,这就是为什么我想看看是否有一种有效的方法来实现它。

提前致谢。

【问题讨论】:

  • 每个columnGroup 的列数是否固定?还是该值因列组而异?
  • @Ricardo Saporta - 每个列组的固定列数
  • 您的数据中是否只有唯一的名称/行?如果不是,您将如何处理重复名称..也就是说,如果您的示例数据具有“john”,而所有其他列具有不同的值..?
  • 嘿看!这是@Arun!欢迎回来芽!
  • @RicardoSaporta,在度假.. 设法潜入了一段时间.. :)

标签: r data.table


【解决方案1】:

更新: 一个(更快)的解决方案是使用以下索引(在 1e6*7 上大约需要 4 秒):

ll <- vector("list", 3)
ll[[1]] <- copy(dt[, -1])
d_idx <- seq(2, ncol(dt), by=3)
for (j in 1:2) {
    tmp <- vector("list", 2)
    for (i in seq_along(colGroups)) {
        idx <- ((i-1)*3+2):((i*3)+1)
        cols <- setdiff(idx, d_idx[i]:(d_idx[i]+j-1))
        # ..cols means "look up one level"
        tmp[[i]] <- cbind(dt[, ..cols], data.table(matrix(0, ncol=j)))
    }
    ll[[j+1]] <- do.call(cbind, tmp)
}
ans <- cbind(data.table(name=dt$name), rbindlist(ll))
setkey(ans, name)

第一次尝试(旧): 非常有趣的问题。我会使用 melt.data.tabledcast.data.table(从 1.8.11 开始)来处理它,如下所示:

require(data.table)
require(reshape2)
# melt is S3 generic, calls melt.data.table, returns a data.table (very fast)
ans <- melt(dt, id=1, measure=2:7, variable.factor=FALSE)[, 
                    grp := rep(colGroups, each=nrow(dt)*3)]
setkey(ans, name, grp)
ans <- ans[, list(variable=c(variable, variable[1:(.N-1)], 
          variable[1:(.N-2)]), value=c(value, value[-1],
     value[-(1:2)]), id2=rep.int(1:3, 3:1)), list(name, grp)]
# dcast in reshape2 is not yet a S3 generic, have to call by full name
ans <- dcast.data.table(ans, name+id2~variable, fill=0L)[, id2 := NULL]

在具有相同列数的 1e6 行上进行基准测试:

require(data.table)
require(reshape2)
set.seed(45)
N <- 1e6
dt <- cbind(data.table(name=paste("x", 1:N, sep="")), 
               matrix(sample(10, 6*N, TRUE), nrow=N))
setnames(dt, c("name", "a1", "a2", "a3", "b1", "b2", "b3"))
colGroups = c("a", "b")

system.time({
ans <- melt(dt, id=1, measure=2:7, variable.factor=FALSE)[, 
                    grp := rep(colGroups, each=nrow(dt)*3)]
setkey(ans, name, grp)
ans <- ans[, list(variable=c(variable, variable[1:(.N-1)], 
          variable[1:(.N-2)]), value=c(value, value[-1],
     value[-(1:2)]), id2=rep.int(1:3, 3:1)), list(name, grp)]
ans <- dcast.data.table(ans, name+id2~variable, fill=0L)[, id2 := NULL]

})

#   user  system elapsed 
# 45.627   2.197  52.051 

【讨论】:

  • 在这里工作正常,刚刚试过......你加载了 dt 和 colGroups
  • @Arun - 谢谢,确实非常快。我的实际表有大约 40 个组和每组 9 列,将尝试相同。
  • 我在 1.8.10 上进行测试,所以我得到 ans 作为 d.f 而不是 d.t
  • 啊我c。只是为了添加更多上下文(以免这看起来像一个奇怪的练习),需要上述操作来准备一个带有滞后预测变量的时间序列表,以使用 ML 算法测量季节性变化(colGroups 是时间间隔)。感谢您的帮助。
  • @Arun - 是的,它要快得多。 1e6 上的性能约为 2 秒。 1e6*7 是 ~ 26 秒。尽管如此,与第一次尝试中的代码相比,改进了大约 90% 以上。非常好。
【解决方案2】:

您可以追加行,然后按组向上移动列。 由于每组的总列数是固定的,因此您迭代每个组号。

## Add in the extra rows
dt <- dt[, rbindlist(rep(list(.SD), 3)), by=name]


### ASSUMING A FIXED NUMBER PER COLGROUP
N <- 3

colsShifting <- as.vector(sapply(colGroups, paste0, 2:N))

for (i in (2:N)-1 ) {
  current <- colsShifting[ (i) +  ( (N-1) * (seq_along(colGroups)-1) )]
  dt[, c(current) := {
              .NN <- .N; 
              .CROP <- .SD[1:(.NN-i)]  ## These lines are only for clean code. You can put it all into the `rbindlist` line
              rbindlist(list(.CROP, as.data.table(replicate(ncol(.SD), rep(0, i),simplify=FALSE ))))
            } 
      , .SDcols=current
      , by=name]
  }

给出:

dt
#     name a1 a2 a3 b1 b2 b3
#  1: john  1  2  3 10 20 30
#  2: john  1  2  0 10 20  0
#  3: john  1  0  0 10  0  0
#  4: jill  4  5  6 40 50 60
#  5: jill  4  5  0 40 50  0
#  6: jill  4  0  0 40  0  0

【讨论】:

    【解决方案3】:

    只需编辑所选答案的@Arun (s) 代码。在这里提供,因为我不能在 cmets 部分发布。

    #Parameterized version of @Arun (author) code (in the selected answer)
    
    #Shifting Columns in R
    #--------------------------------------------
    N = 5  # SET - Number of unique names
    set.seed(5)
    colGroups <- c("a","b") # ... (i) # SET colGroups
    totalColsPerGroup <- 10 # SET Cols Per Group
    numColsToLeftShift <- 8 # SET Cols to Shift
    
    lenColGroups <- length(colGroups) # ... (ii)
    
    # From (i) and (ii)
    totalCols = lenColGroups * totalColsPerGroup
    
    
    dt <- cbind(data.table(name=paste("x", 1:N, sep="")), 
                matrix(sample(5, totalCols*N, TRUE), nrow=N)) # Change 5 if needed
    
    ll <- vector("list", numColsToLeftShift)
    ll[[1]] <- copy(dt[, -1, with=FALSE])
    d_idx <- seq(2, ncol(dt), by=totalColsPerGroup)
    for (j in 1:(numColsToLeftShift)) {
      tmp <- vector("list", 2)
      for (i in seq_along(colGroups)) {
        idx <- ((i-1)*totalColsPerGroup+2):((i*totalColsPerGroup)+1) #OK
        tmp[[i]] <- cbind(dt[, setdiff(idx, d_idx[i]:(d_idx[i]+j-1)), 
                             with=FALSE], data.table(matrix(0, ncol=j)))
    
      }      
      ll[[j+1]] <- do.call(cbind, tmp)
    
    }
    ans <- cbind(data.table(name=dt$name), rbindlist(ll))
    setkey(ans, name)
    

    --

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-07-01
      • 2013-10-12
      • 1970-01-01
      • 2018-08-11
      • 2014-12-19
      • 1970-01-01
      • 2014-10-05
      相关资源
      最近更新 更多