将函数应用于data.table子集中的所有值

时间:2017-02-01 17:03:17

标签: r data.table

我有一个成对的值表,我正在尝试找到将一些函数应用于此表的各个子集的最快方法。我正在尝试使用data.table来查看它是否符合我的需求。

例如,我从这个数据点向量开始,我将其转换为成对距离矩阵。

dat <- c(spA = 4, spB = 10, spC = 8, spD = 1, spE = 5, spF = 9)
pdist <- as.matrix(dist(dat))
pdist[upper.tri(pdist, diag = TRUE)] <- NA

看起来像这样:

> pdist
    spA spB spC spD spE spF
spA  NA  NA  NA  NA  NA  NA
spB   6  NA  NA  NA  NA  NA
spC   4   2  NA  NA  NA  NA
spD   3   9   7  NA  NA  NA
spE   1   5   3   4  NA  NA
spF   5   1   1   8   4  NA

将此表格转换为data.table

library(data.table)
pdist <- as.data.table(pdist, keep.rownames=TRUE)
setkey(pdist, rn)
> pdist
    rn spA spB spC spD spE spF
1: spA  NA  NA  NA  NA  NA  NA
2: spB   6  NA  NA  NA  NA  NA
3: spC   4   2  NA  NA  NA  NA
4: spD   3   9   7  NA  NA  NA
5: spE   1   5   3   4  NA  NA
6: spF   5   1   1   8   4  NA

如果我有一些要为其提取值的子集,

sub <- c('spB', 'spF', 'spD')

我可以执行以下操作,从而产生我感兴趣的子矩阵:

> pdist[.(sub), sub, with=FALSE]
       spB spF spD
    1:  NA  NA  NA
    2:   1  NA   8
    3:   9  NA  NA

现在,我如何应用一个函数,例如取这个子集中所有值的均值(但可能是自定义函数)?我可以这样做,但我想知道是否有更好的方法符合data.table操作。

> mean(unlist(pdist[.(sub), sub, with=FALSE]), na.rm=TRUE)
[1] 6

更新

接下来,我决定看一下矩阵与data.table方法的性能有多么不同:

dat <- runif(1000)
names(dat) <- paste0('sp', 1:1000)

spSub <- replicate(10000, sample(names(dat), 100), simplify=TRUE)

# calculate pairwise distance matrix
pdist <- as.matrix(dist(dat))
pdist[upper.tri(pdist, diag = TRUE)] <- NA

# convert to data.table
pdistDT <- as.data.table(pdist, keep.rownames='sp')
setkey(pdistDT, sp)

matMethod <- function(pdist, sub) {
    return(mean(pdist[sub, sub], na.rm=TRUE))
}

dtMethod <- function(pdistDT, sub) {
    return(mean(unlist(pdistDT[.(sub), sub, with=FALSE]), na.rm=TRUE))
}


> system.time(q1 <- lapply(spSub, function(x) matMethod(pdist, x)))
   user  system elapsed 
 18.116   0.154  18.317 

> system.time(q2 <- lapply(spSub, function(x) dtMethod(pdistDT, x)))
   user  system elapsed 
795.456  13.357 806.820 

看来,通过此处的data.table步骤会导致很大的性能成本。

1 个答案:

答案 0 :(得分:1)

请参阅此处发布的解决方案,以获取更多常规解决方案。它还可能会帮助: data.table: transforming subset of columns with a function, row by row

要应用此功能,您可以执行以下操作:

第1部分:分步解决方案

(1.a)将数据转换为Data.Table格式:

library(data.table)
library(magrittr) #for access to pipe operator
pdist <- as.data.table(pdist, keep.rownames=TRUE)
setkey(pdist, rn)

(1.b)然后,获取列名列表:

# Get the list of names
sub <- c('spB', 'spF', 'spD')

(1.c)定义要应用的功能

#Define the function you wish to apply
# Where, normalize is just a function as defined in the question:

normalize <- function(X, X.mean = mean(X, na.rm=T), X.sd = sd(X, na.rm=T)){
                          X <- (X - X.mean) / X.sd
                          return(X)}

(1.d)应用函数:

# Voila: 
pdist[, unlist(.SD, use.names = FALSE), .SDcols = sub] %>% normalize() 

#Or, you can apply the function inside the [], as below: 
pdist[, unlist(.SD, use.names = FALSE) %>% normalize(), .SDcols = sub] 

# Or, if you prefer to do it without the pipe operator:
pdist[, normalize(unlist(.SD, use.names = FALSE)), .SDcols = sub] 

第2部分。Data.Table方法的一些优点

由于您似乎熟悉矩阵方法,所以我只想指出保留data.table方法的一些优点

(2.a)使用“ by =“

在组内应用功能

相对于矩阵的一个优势是,您仍然可以使用“ by =“参数在组内应用函数。

在这里的示例中,我假设您有一个名为“ Grp”的变量。

使用by=Grp行,现在归一化在组内。

pdist[, unlist(.SD) %>% normalize(), .SDcols = sub, by=Grp]

(2.b)的另一个优点是,您可以保留其他标识信息,例如,如果每一行都有您希望保留并重复的“参与者标识符” P.Id:

pdist[, .(Combined.Data = unlist(.SD)), .SDcols = sub, by=P.Id][order(P.Id),.(P.Id, Transformed = normalize(Combined.Data), Combined.Data)]

第一步,在此部分代码中完成:pdist[, .(Combined.Data = unlist(.SD)), .SDcols = sub, by=P.Id]

  1. 首先,我们为“ sub”中标识的所有三列中的数据创建一个名为 Combined.Data 的新列。
  2. 在合并数据的每一行旁边,相应的参与者ID将在 P.Id
  3. 列中重复

第二步,在此部分代码中完成:  [,.(P.Id, Normalized = normalize(Combined.Data), Combined.Data)]

  1. 我们可以创建一个名为 Normalized 的新列,以存储因应用函数normalize()
  2. 而产生的归一化值
  3. 此外,我们还可以包括Combined.Data列

因此,用以下这一行:     pdist [,。(Combined.Data = unlist(.SD)),.SDcols = sub,by = P.Id] [order(P.Id),。(P.Id,Transformed = normalize(Combined.Data),组合数据)]

  • 我们对列进行子集
  • 折叠子集中的数据
  • 即使折叠,也要保持跟踪每个数据(P.Id)的标识符
  • 对整个折叠数据应用转换,然后
  • 最终以整齐的输出形式以三列数据表的形式出现:(1)P.Id,(2)Transformed和&(3)Combined.Data(原始值)。
  • 并且order(P.Id)允许输出显示有意义的顺序。

使用矩阵方法也可以做到这一点,但麻烦得多,并且需要更多代码行。

数据表允许强大的数据处理和管理,尤其是当您开始将操作链接在一起时。

(2.c)最后,如果您只希望将行信息保留为简单的row.numbers,则可以使用data.table包的.I功能:

pdist[, .(.I, normalize(unlist(.SD)), .SDcols = sub]

此功能可能非常有用,特别是如果您没有与生俱来的参与者或行标识​​符。

第3部分。缺点:时间成本

我重新创建了上面显示的更正的时间成本,并且数据表的解决方案确实需要更长的时间

dat <- runif(1000)
names(dat) <- paste0('sp', 1:1000)

spSub <- replicate(10000, sample(names(dat), 100), simplify=TRUE)

# calculate pairwise distance matrix
pdist <- as.matrix(dist(dat))
pdist[upper.tri(pdist, diag = TRUE)] <- NA

# convert to data.table
pdistDT <- as.data.table(pdist, keep.rownames='sp')
# pdistDT$sp %<>% as.factor()
setkey(pdistDT, sp)


matMethod <- function(pdist, sub) {
  return(mean(pdist[sub, sub], na.rm=TRUE))
}


dtMethod <- function(pdistDT, sub) {
  return(pdistDT[sub, sub, with = FALSE] %>% 
           unlist(., recursive = FALSE, use.names = FALSE) %>% 
           mean(., na.rm = TRUE))
}


dtMethod1 <- function(pdistDT, sub) {
  return(pdistDT[sub, sub, with = FALSE] %>% 
           melt.data.table(., measure.vars = sub, na.rm=TRUE) %$% 
           mean(value))
}


system.time(q1 <- apply(spSub, MARGIN = 2, function(x) matMethod(pdist, x)))
# user  system elapsed 
# 2.86    0.00    3.27 

system.time(q2 <- apply(spSub, MARGIN = 2, function(x) dtMethod(pdistDT, x)))
# user  system elapsed 
# 57.20    0.02   57.23 

system.time(q3 <- apply(spSub, MARGIN = 2, function(x) dtMethod1(pdistDT, x)))
# user  system elapsed 
# 62.78    0.06   62.91