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

R 将函数应用于data.table子集中的所有值,r,data.table,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 将此表转换为dat

我有一个成对的值表,我正试图找到最快的方法,将一些函数应用到这个表的各个子集。我正在试验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
将此表转换为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
现在,我如何应用一个函数,例如取这个子集中所有值的平均值(但可能是一个自定义函数)?我可以这样做,但我想知道是否有更好的方法来处理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 

dat请参阅此处发布的解决方案,了解更多通用解决方案。这也可能有助于:

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

第一部分。逐步解决办法 (1.a)将数据转换成数据表格式: 第二部分。数据表方法的一些优点 因为您似乎熟悉矩阵方法,所以我只想指出保留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]

  • 首先,我们为“sub”中标识的所有三列中的数据创建一个名为“Combined.Data”的新列
  • 在组合数据的每一行旁边,相应的参与者Id将在P.Id列中重复
  • 在第二步中,在代码的这一部分中完成:
    [,(P.Id,Normalized=normalize(Combined.Data),Combined.Data)]

  • 我们可以创建一个名为normalize的新列来存储应用函数
    normalize()
  • 此外,我们还可以包括Combined.Data列
  • 因此,通过这一行: pdist[,(Combined.Data=unlist(.SD)),.SDcols=sub,by=P.Id][order(P.Id),(P.Id,Transformed=normalize(Combined.Data),Combined.Data)]

    • 我们需要一组列
    • 跨子集折叠数据
    • 即使在折叠时,也要跟踪每个基准(P.Id)的标识符
    • 对整个折叠的数据应用转换,然后
    • 最后以数据表的形式输出一个整洁的结果,数据表有3列:(1)P.Id,(2)Transformed,&(3)Combined.data(原始值)
    • 而且,
      顺序(P.Id)
      允许输出显示有意义的顺序
    使用矩阵方法也可以做到这一点,但会更加麻烦,需要更多的代码行

    数据表允许对数据进行强大的操作和管理,特别是当您开始将操作链接在一起时

    (2.c)最后,如果您只希望将行信息保留为简单的row.number,则可以使用data.table包的.I功能: 此功能非常有用,尤其是当您没有本质上有意义的参与者或行标识符时

    第三部分。缺点:时间成本 我重新创建了上面显示的校正时间成本,数据表的解决方案确实需要更长的时间

    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 
    

    dat获得
    平均值的方法已经很好了,我认为你应该坚持使用一个矩阵:
    平均值(m[sub,sub],na.rm=TRUE)
    。我认为将其放入data.table中不会有任何好处。如果您想要提高性能,请查看
    RcppArmadillo
    RcppEigen
    中有关对矩阵子集进行操作的内容。我试图解决您看到的速度差异,并复制上面的精确代码。据我所知-矩阵版本不起作用q1完全由NAN组成。所以,这也许可以解释为什么它看起来要快得多。它实际上并没有做它应该做的事如果我遗漏了什么,请告诉我。
    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 
    
    library(data.table)
    library(magrittr) #for access to pipe operator
    pdist <- as.data.table(pdist, keep.rownames=TRUE)
    setkey(pdist, rn)
    
    # Get the list of names
    sub <- c('spB', 'spF', 'spD')
    
    #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)}
    
    # 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] 
    
    pdist[, unlist(.SD) %>% normalize(), .SDcols = sub, by=Grp]
    
    pdist[, .(Combined.Data = unlist(.SD)), .SDcols = sub, by=P.Id][order(P.Id),.(P.Id, Transformed = normalize(Combined.Data), Combined.Data)]
    
    pdist[, .(.I, normalize(unlist(.SD)), .SDcols = sub]
    
    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