1

我正在尝试为我相对较大的数据集实现一个 data.table,但我不知道如何在同一行中的多个列上操作一个函数。具体来说,我想创建一个新列,其中包含列子集中的特定格式的值(即直方图)。它有点像 table() 但它也包括 0 个条目并已排序 - 所以,如果你知道更好/更快的方法,我也会很感激!

简化的测试用例:

DF<-data.frame("A"=c("a","d","a"),"B"=c("b","a","a"),"C"=c("c","a","a"),"D"=c("a","b","c"),"E"=c("a","a","c"))
DT<-as.data.table(DF)
> DT
   A B C D E
1: a b c a a
2: d a a b a
3: a a a c c

我笨拙的直方图函数:

histo<-function(vec){
                     foo<-c("a"=0,"b"=0,"c"=0,"d"=0)
                     for(i in vec){foo[i]=foo[i]+1}
                     return(foo)}
>histo(unname(unlist(DF[1,])))
a b c d
3 1 1 0
>histo(unname(unlist(DF[2,])))
a b c d
3 1 0 1
>histo(unname(unlist(DF[3,])))
a b c d
3 0 2 0

所需功能和输出的伪代码

>DT[,his:=some_func_with_histo(A:E)]
>DT
   A B C D E his
1: a b c a a (3,1,1,0)
2: d a a b a (3,1,0,1)
3: a a a c c (3,0,2,0)
4

3 回答 3

1

编辑(另见下文):前提是您首先将列类转换为字符,例如,使用DT <- DT[,lapply(.SD,as.character)]...

通过使用,您可以一步factor转换和传递值 (a,b,c,d):vec

histo2 <- function(x) table(factor(x,levels=letters[1:4]))

然后你可以通过传递来遍历行by=1:nrow(DT)

DT[,as.list(histo2(.SD)),by=1:nrow(DT)]

这给...

   nrow a b c d
1:    1 3 1 1 0
2:    2 3 1 0 1
3:    3 3 0 2 0

此外,这会迭代列。这是有效的,因为.SD它是一个特殊变量,它保存与调用相关联的数据子集by。在这种情况下,该子集data.table由其中一行组成。histo2(DT[1])以同样的方式工作。

编辑(回应OP的评论):哦,对不起,我本能地用你的第一行替换了

DF<-data.frame("A"=c("a","d","a"),"B"=c("b","a","a"),"C"=c("c","a","a"),"D"=c("a","b","c"),"E"=c("a","a","c")
,stringsAsFactors=FALSE)

因为我不喜欢使用因子,除非是在制作表格时。如果您不想以这种方式将因子列转换为字符列,这将起作用:

histo3 <- function(x) table(factor(sapply(x,as.character),levels=letters[1:4]))

要将输出放入单个列中,请:=按照建议使用...

DT[,hist:=list(list(histo3(.SD))),by=1:nrow(DT)]

list(list())零件是关键;我总是通过反复试验来解决这个问题。现在 DT 看起来像这样:

   A B C D E    hist
1: a b c a a 3,1,1,0
2: d a a b a 3,1,0,1
3: a a a c c 3,0,2,0

您可能会发现直接从新列中访问信息很痛苦。例如,要访问“直方图”的“a”列,我认为最快的路线是......

DT[,hist[[1]][["a"]],by=1:nrow(DT)]

我最初的建议是创建一个仅包含计数的辅助 data.table。我认为对 data.table 中的计数做任何你想做的事情,然后再cbind返回它会更干净。如果您选择将其存储在列中,您可以稍后随时创建辅助 data.table

DT[,as.list(hist[[1]]),by=1:nrow(DT)]

你是正确的使用.SDcols. 对于你的例子,...

cols = c("A","C")
histname = paste(c("hist",cols),collapse="")
DT[,(histname):=list(list(histo3(.SD))),by=1:nrow(DT),.SDcols=cols]

这给

   A B C D E    hist  histAC
1: a b c a a 3,1,1,0 1,0,1,0
2: d a a b a 3,1,0,1 1,0,0,1
3: a a a c c 3,0,2,0 2,0,0,0
于 2013-05-30T03:04:23.817 回答
1
df <- data.table(DF)
df$hist <- unlist(apply(df, 1, function(x) {
    list(      
        sapply(letters[1:4], function(d) {
            b <- sum(!is.na(grep(d,x)))
            assign(d, b)
        }))
}), recursive=FALSE)

您的 df$hist 列是一个列表,每个值都命名为:

> df
   A B C D E    hist
1: a b c a a 3,1,2,0
2: d a a b a 3,1,1,1
3: a a a c c 3,0,3,0

> df$hist
[[1]]
a b c d 
3 1 2 0 

[[2]]
a b c d 
3 1 1 1 

[[3]]
a b c d 
3 0 3 0 
于 2013-05-30T03:05:29.693 回答
1

注意:答案已更新为 OP 的请求和 mnel 的评论

好的,您如何看待该解决方案:

library(data.table)
DT <- data.table(A=c("a","d","a"),
                 B=c("b","a","a"),
                 C=c("c","a","a"),
                 D=c("a","b","c"),
                 E=c("a","a","c"))

fun <- function(vec, char) {
  sum(vec==char)
}

DT[, Vec_Nr:= paste(Vectorize(fun, 'char')(.SD, letters[1:4]), collapse=","),
   by=1:nrow(DT),
   .SDcols=LETTERS[1:5]]
   A B C D E  Vec_Nr
1: a b c a a 3,1,1,0
2: d a a b a 3,1,0,1
3: a a a c c 3,0,2,0

我基本上将您的问题分为几个步骤:

首先,我定义了一个函数fun,它给出了一个字符的出现次数。要查看该功能如何工作,只需调用

fun(c("a", "a", "b"), "b")
[1] 1

接下来,我将此函数向量化,因为您不想只知道一个字符“b”,而是要知道很多。要将参数向量传递给函数,请使用Vectorize. 要查看它是如何工作的,只需键入

Vectorize(fun, "char")(c("a", "a", "b"), c("a", "b"))
a b 
2 1

接下来,我将结果折叠成一个字符串并将其另存为新列。请注意,我故意使用lettersandLETTERS来向您展示如何使其更具动态性。

于 2013-05-30T01:36:38.693 回答