将非平凡函数应用于有序的data.table子集

时间:2014-01-16 08:32:50

标签: r data-structures out-of-memory data.table

问题

我正在尝试使用我新发现的data.table power(for good)来计算一堆数据的频率内容,如下所示:

|  Sample|  Channel|  Trial|     Voltage|Class  |  Subject|
|-------:|--------:|------:|-----------:|:------|--------:|
|       1|        1|      1|  -196.82253|1      |        1|
|       1|        2|      1|   488.15166|1      |        1|
|       1|        3|      1|  -311.92386|1      |        1|
|       1|        4|      1|  -297.06078|1      |        1|
|       1|        5|      1|  -244.95824|1      |        1|
|       1|        6|      1|  -265.96525|1      |        1|
|       1|        7|      1|  -258.93263|1      |        1|
|       1|        8|      1|  -224.07819|1      |        1|
|       1|        9|      1|   -87.06051|1      |        1|
|       1|       10|      1|  -183.72961|1      |        1|

大约有5700万行 - 每个变量都是除电压之外的整数。 Sample是一个索引,从1:350开始,Channel从1:118开始。有280次试验。

示例数据

Martín的示例数据是有效的,我相信(分类变量的数量与错误无关):

big.table <- data.table(Sample = 1:350, Channel = 1:118, Trial = letters,
             Voltage = rnorm(10e5, -150, 100), Class = LETTERS, Subject = 1:20)

过程

我要做的第一件事就是将关键设置为Sample,因为我希望我对单个数据系列所做的任何事情都按照合理的顺序进行:

setkey(big.table,Sample)

然后,我对电压信号进行一些滤波以去除高频。 (过滤函数返回与第二个参数长度相同的向量):

require(signal)
high.pass <- cheby1(cheb1ord(Wp = 0.14, Ws = 0.0156, Rp = 0.5, Rs = 10))
big.table[,Voltage:=filtfilt(high.pass,Voltage),by=Subject]

初始错误

我想看看它是否正确处理(即按主题划分,试验试验,按频道划分频道,按样本顺序),所以我添加了一个包含电压列频谱内容的列:

get.spectrum <- function(x) {
    spec.obj <- spectrum(x,method="ar",plot=FALSE)
    outlist <- list()
    outlist$spec <- 20*log10(spec.obj$spec)
    outlist$freq <- spec.obj$freq
    return(outlist)
  }
big.table[,c("Spectrum","Frequency"):=get.spectrum(Voltage),by=Subject]

Error: cannot allocate vector of size 6.1 Gb

我认为问题是get.spectrum()试图立即吃掉整个列,考虑到整个表只有1.7GB左右。是这样吗?我有什么选择?

你有什么尝试?

增加分组的粒度

如果我打电话给get.spectrum,包括我想要分组的所有列,我会得到一个更有希望的错误:

big.table[,c("Spectrum","Frequency"):=get.spectrum(Voltage),
        by=c("Subject","Trial","Channel","Sample")]

Error in ar.yw.default(x, aic = aic, order.max = order.max, na.action = na.action,  : 
  'order.max' must be >= 1

这意味着我正在调用的spectrum()函数正在获取错误形状的数据。

切入点,尝试不同的'where'条件

根据罗兰的建议,我将点数减少到大约2000万,并尝试以下方法:

big.table[,"Spectrum":=get.spectrum(Voltage),
        by=c("Subject","Trial","Channel")]

Error in `[.data.table`(big.table, , `:=`("Spectrum", get.spectrum(Voltage)),  :
  All items in j=list(...) should be atomic vectors or lists. If you are trying something like
  j=list(.SD,newcol=mean(colA)) then use := by group instead (much quicker), or cbind or merge 
  afterwards.

我的想法是我不应该按Sample分组,因为我想将此函数应用于上述by向量给出的每组350个样本。

通过data.table FAQ的第2.16节中收集的一些内容改进了这一点,我添加了ORDER BY的SQL等价物。我知道Sample列需要从1:350获得每个输入到spectrum()函数:

> big.table[Sample==c(1:350),c("Spectrum","Frequency"):=as.list(get.spectrum(Voltage)),
+             by=c("Subject","Trial","Channel")]
Error in ar.yw.default(x, aic = aic, order.max = order.max, na.action = na.action,  : 
  'order.max' must be >= 1

同样,我遇到了非独特输入问题。

2 个答案:

答案 0 :(得分:2)

也许这可以开始解决问题:

I believe the error data.table gives is because get.spectrum returns a list with:
spec and freq.

Using this example dataset:
big.table <- data.table(Sample = 1:350, Channel = 1:118, Trial = letters,
                 Voltage = rnorm(10e5, -150, 100), Class = LETTERS, Subject = 1:20)

str(big.table)
setkey(big.table,Sample)

get.spectrum <- function(x) {
  spec.obj <- spectrum(x,method="ar",plot=FALSE)
  outlist <- list()
  outlist$spec <- 20*log10(spec.obj$spec)
  outlist$freq <- spec.obj$freq
  return(outlist)
}

VT <- get.spectrum(big.table$Voltage)
str(VT)

# Then you should decide which value you would like to inset in big.table
get.spectrum(big.table$Voltage)$spec
# or
get.spectrum(big.table$Voltage)$freq

这应该有效。您也可以使用set()

big.table[, Spectrum:= get.spectrum(Voltage)$spec, by=Subject]
big.table[, Frequency:= get.spectrum(Voltage)$freq, by=Subject]

修改 正如评论中提到的,我试图使用set()提供答案,但我不知道如何“分组”主题:这是我尝试过的,不确定它是否是预期的答案。

cols = c("spec", "freq")
for(inx in cols){
  set(big.table, i=NULL, j=j ,value = get.spectrum(big.table[["Voltage"]])[inx])
}

<强> EDIT2 每个列的两个函数。使用变量分组的不同组合。

spec_fun <- function(x) {
  spec.obj <- spectrum(x,method="ar",plot=FALSE)
  spec <- 20*log10(spec.obj$spec)
  spec
}

freq_fun <- function(x) {
  freq <- spectrum(x,method="ar",plot=FALSE)$freq
  freq
}

big.table[, Spectrum:= spec_fun(Voltage), by=c("Subject","Trial","Channel")]
big.table[, Frequency:= freq_fun(Voltage), by=c("Subject","Trial","Channel")]

# It gives some warnings(), probaby because of the made up data.

答案 1 :(得分:1)

在一些有耐心听我捶打的extended discussion with Martín Bel之后,我能够解决一些出错的问题。

初始错误

一个主要问题是spectrum(),即在data.table的每个时间序列组件上调用的函数,需要一个表示多变量时间序列的2D结构(在本例中为channels x samples) 。所以这个电话

big.table[,c("Spectrum","Frequency"):=get.spectrum(Voltage),by=Subject]

Error: cannot allocate vector of size 6.1 Gb

非常糟糕。

brute'for'ce

使用(通常是无用的)并行化这是一种缓慢的方法。 get.spectrum()被修改为返回一个简单的向量,该向量与来自j的返回类型的第三个错误相关:

get.spectrum <- function(x) {
    spec.obj <- spectrum(x,method="ar",plot=FALSE)
    outlist <- list()
    outlist <- 20*log10(spec.obj$spec)
    # outlist$freq <- spec.obj$freq # don't return me
    return(outlist)
}

require(parallel)
require(foreach)
freq.bins <- 500
spectra <- foreach(s.ind = unique(big.table$Subject), .combine=rbind) %:% {
              foreach(t.ind = unique(big.table$Trial), .combine=rbind) %dopar% {

                cbind((sampling.rate * (seq_len(freq.bins)-1) / sampling.rate),
                  rep(c.ind,freq.bins),
                  rep(t.ind,freq.bins),
                  get.spectrum((subset(big.table, 
                   subset=(Subject==s.ind & 
                             Trial==t.ind),
                   select=Voltage))$Voltage),
                  rep(s.ind,freq.bins))

              }
            }

这给出了正确的结果,因为get.spectrum()的每个输入都是主题和试验被修复的子集,使Channel和Sample变化。但是,它非常慢,并且在我在这台机器上的4个核心中的1个中花费了超过80%的计算负荷。

data.table方法

我回到了讨论中出现的一些玩具箱,并再次尝试了这个:

spec.dt <- big.table[,get.spectrum(Voltage),by=c("Subject","Trial")]

这很接近!它返回一个几乎正确结构的data.table。

> str(spec.dt)
Classes ‘data.table’ and 'data.frame':  140000 obs. of  3 variables:
 $ Subject: int  1 1 1 1 1 1 1 1 1 1 ...
 $ Trial  : int  1 1 1 1 1 1 1 1 1 1 ...
 $ V1     : num  110.7 109 105.4 101.6 98.2 ...

但是,缺少Channel变量。轻松修复:

> spec.dt <- erp.table[,get.spectrum(Voltage),by=c("Subject","Trial","Channel")]
> str(spec.dt)
Classes ‘data.table’ and 'data.frame':  16520000 obs. of  4 variables:
 $ Subject: int  1 1 1 1 1 1 1 1 1 1 ...
 $ Trial  : int  1 1 1 1 1 1 1 1 1 1 ...
 $ Channel: int  1 1 1 1 1 1 1 1 1 1 ...
 $ V1     : num  78.6 78.6 78.6 78.5 78.5 ...
 - attr(*, ".internal.selfref")=<externalptr>

这是对的吗?好吧,很容易检查它是否是正确的形状。我们知道默认spectrum()电话中有500个频率分档,我说数据有118个频道。

> nrow(spec.dt)
[1] 16520000
> nrow(spec.dt)/500
[1] 33040
> nrow(spec.dt)/500/118
[1] 280

我在最初的问题中没有提到它,但确实有280次试验。

备注

这里一个明显的规则是,在by参数中,您需要省略与从属数据相对应的自变量。如果不这样做,则会出现另一个错误。

> spectra.table <- big.table[,get.spectrum(Voltage),by=c("Sample","Subject","Channel")]
Error in ar.yw.default(x, aic = aic, order.max = order.max, na.action = na.action,  : 
  'order.max' must be >= 1

此处电压是Sample的函数(因为样本是索引) - 对于每个Channel和每个Subject,它会反复重复。

我不知道究竟是什么问题。

基准

> system.time(spec.dt <- erp.table[,get.spectrum(Voltage),by=c("Subject","Trial","Channel")])
   user  system elapsed 
 86.669   3.452  87.414

system.time(
  spectra <- foreach(s.ind = unique(erp.table$Subject), .combine=rbind) %:% 
              foreach(t.ind = unique(erp.table$Trial), .combine=rbind) %dopar% {

                cbind((sampling.rate * (seq_len(freq.bins)-1) / sampling.rate),
                  rep(c.ind,freq.bins),
                  rep(t.ind,freq.bins),
                  get.spectrum((subset(erp.table, 
                   subset=(Subject==s.ind & 
                             Trial==t.ind),
                   select=Voltage))$Voltage),
                  rep(s.ind,freq.bins))

              })
   user  system elapsed 
114.259  17.937 131.873 

第二个基准是乐观的;我第二次运行它而没有清理环境或删除变量。