【问题标题】:Applying non-trivial functions to ordered subsets of data.table将非平凡函数应用于 data.table 的有序子集
【发布时间】:2014-02-05 01:34:53
【问题描述】:

问题

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

|  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() 函数正在获取错误形状的数据。

切入点,尝试不同的“位置”条件

按照罗兰的建议,我将点数减少到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.

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

通过从 data.table 常见问题解答的第 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

再次,我遇到了非唯一输入的问题。

【问题讨论】:

  • 在巨大的 data.table 中使用它之前,您应该使用数据的子集进行测试。此外,如果您在 R 中处理这么多数据并且没有至少 16 GB 的 RAM,我的建议是投资更多的内存。现在没那么贵了。
  • 你是对的,当然;我删掉了大部分让我在 618MB 上获得 2300 万分的主题。
  • @TrevorAlexander 如果你模拟一些数据会更容易帮助你。
  • 我现在对 data.table 有足够的信心做到这一点。
  • @TrevorAlexander 我的意思是模拟一些与您正在工作的数据具有相似特征的数据,因此我们可以尝试您的代码并回答问题。

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


【解决方案1】:

或许这样可以开始解决问题:

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]

编辑 如 cmets 中所述,我尝试使用 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.

【讨论】:

  • 你打败了我的答案一个不错的编辑;我想对一个主题和一个通道的行进行少量随机抽样,但我在试图弄清楚如何使用 data.table 时遇到了困难。 set() 方法执行了两次计算,对吧?
  • 我试图想出一个固定的答案,但它不能正常工作。我不确定它是否可以处理 by=variable。也许另一种方法是编写两个函数:一个用于频率,另一个用于规范。如果您打算对许多列执行此操作,请使用lapply(.SD, NEW_FUN)。如果只是两个人,我想这没关系。
  • 技术上我可以在外面计算 freq (seq_len(500)-1) / 500 其中 500 是返回的点数,但最好了解如何在一个时间。我在其他一些问题中看到c("Spectrum","Frequency"):= 应该可以工作,但我没有管理它。
  • VT &lt;- get.spectrum(big.table$Voltage) 如何知道如何按主题/通道/试验分别按顺序传递电压? setkey() 能保证吗?
  • 使用dualchan.table &lt;- data.table(Sample = 1:350, Channel = 1:2, Trial = 1,Voltage = rnorm(350*2), Class = 1, Subject = 1) 和单通道表进行测试似乎表明某种by 分组是有序的,但我不清楚如何。
【解决方案2】:

extended discussion with Martín Belextended discussion with Martín Bel 有足够的耐心听我敲打,我才能够找出一些问题所在。

初始错误

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

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

Error: cannot allocate vector of size 6.1 Gb

很糟糕。

蛮力'力'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() 的每个输入都是一个子集,其中 Subject 和 Trial 是固定的,而 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

这里的电压是样本的函数(因为样本是一个索引)——它对每个通道和每个主题一遍又一遍地重复。

不过,我不知道到底是什么问题。

基准

> 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 

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

【讨论】:

  • 从来不知道 data.table 可以接受从函数返回的列表,这太神奇了。谢谢!
猜你喜欢
  • 1970-01-01
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-17
  • 1970-01-01
  • 2017-09-29
相关资源
最近更新 更多