有没有超快的方法将数据帧行转换为列表元素?

时间:2014-06-30 09:21:17

标签: r list dataframe apply rcpp

假设数据框如下:

> n <- 3
> a <- data.frame(x=1:n,y=sample(letters,n,replace = T),stringsAsFactors = F)
> rownames(a) <- paste0("p",1:n)
> a
   x y
p1 1 a
p2 2 e
p3 3 b

我想将数据框转换为如下列表:

$p1
$p1$x
[1] 1

$p1$y
[1] "a"


$p2
$p2$x
[1] 2

$p2$y
[1] "e"


$p3
$p3$x
[1] 3

$p3$y
[1] "b"

执行此类转换的一种直观方法是使用lapply迭代所有行,但它确实很慢。如果它是一个矩阵,另一种方式是apply(a,1,as.list)。我做了一些基准测试,他们表明apply方法比lapply方法要强5倍。此外,我还测试了apply(a,1,as.vector,mode="list")方法,它比as.list方法快4倍。不幸的是,它是一个具有异构类型列的数据框。

当数据框的行数较大时,所有方法似乎都运行缓慢。有没有办法更快地做到这一点? (使用Rcpp?以及如何?)

3 个答案:

答案 0 :(得分:2)

为了记录(因为你已经提到过“Rcpp”),我在C级别添加了一种方法。加速大约是7倍;可能有更好/更快的解决方案,但是 - 与评论一致 - 可能更适合规划一种不同的方法,而不是试图尽快制作特定的部分,特别是如果很难获得显着的加速。

library(inline)

ff <- cfunction(sig = c(R_df = "data.frame"), body = '
    R_len_t nr = LENGTH(VECTOR_ELT(R_df, 0)), nc = LENGTH(R_df);

    SEXP ans;
    PROTECT(ans = allocVector(VECSXP, nr));
    for(int i = 0; i < nr; i++) {
        SET_VECTOR_ELT(ans, i, allocVector(VECSXP, nc));
        setAttrib(VECTOR_ELT(ans, i), R_NamesSymbol, 
                  getAttrib(R_df, R_NamesSymbol));
    }
    setAttrib(ans, R_NamesSymbol, getAttrib(R_df, R_RowNamesSymbol)); 

    for(int i = 0; i < nc; i++) {
        SEXP tmp;
        PROTECT(tmp = coerceVector(VECTOR_ELT(R_df, i), 
                                   TYPEOF(VECTOR_ELT(R_df, i))));
        switch(TYPEOF(tmp)) {
            case LGLSXP:
            case INTSXP: {
                R_len_t *ptmp = INTEGER(tmp);
                for(int j = 0; j < nr; j++) 
                    SET_VECTOR_ELT(VECTOR_ELT(ans, j), i, 
                                   ScalarInteger(ptmp[j]));
                break;              
            }
            case REALSXP: {
                double *ptmp = REAL(tmp);
                for(int j = 0; j < nr; j++) 
                    SET_VECTOR_ELT(VECTOR_ELT(ans, j), i, 
                                   ScalarReal(ptmp[j]));
                break;              
            }
            case STRSXP: {
                for(int j = 0; j < nr; j++) 
                    SET_VECTOR_ELT(VECTOR_ELT(ans, j), i, 
                                   ScalarString(STRING_ELT(tmp, j)));
                break;              
            }
        }
        UNPROTECT(1);
    }

    UNPROTECT(1);
    return(ans);
')

ff(a) 
#$p1
#$p1$x
#[1] 1
#
#$p1$y
#[1] "k"
#
#
#$p2
#$p2$x
#[1] 2
#
#$p2$y
#[1] "o"
#
#
#$p3
#$p3$x
#[1] 3
#
#$p3$y
#[1] "l"

与你的方法(在评论中提到)相比,证明是快速的:

identical(setNames(do.call(Map, 
                           c(function(...) 
                                "names<-"(list(...), colnames(a)), a)), 
                   row.names(a)), 
           ff(a))
#[1] TRUE 

在更大的“data.frame”上:

set.seed(101)
DF = do.call(cbind.data.frame, 
             replicate(4, cbind.data.frame(x = I(sample(letters, 1e5, T)), 
                                           y = runif(1e5), 
                                           z = sample(1e5)), simplify = F))
names(DF) = make.unique(names(DF), "")


identical(setNames(do.call(Map, 
                           c(function(...) 
                               "names<-"(list(...), colnames(DF)), DF)), 
                   row.names(DF)), 
          ff(DF))   
#[1] TRUE
library(microbenchmark)
microbenchmark(ans1 = setNames(do.call(Map, 
                                       c(function(...) 
                                           "names<-"(list(...), colnames(DF)), 
                                         DF)), 
                               row.names(DF)), 
               ff(DF), 
               times = 10)
#Unit: milliseconds
#   expr       min        lq    median       uq       max neval
#   ans1 3504.1825 3862.4333 3931.0853 4063.691 4162.9370    10
# ff(DF)  143.0398  340.6897  365.5144  404.475  498.3854    10

答案 1 :(得分:0)

看起来您希望将行拆分为列表,然后在每个行中将行拆分为包含所有元素的列表。这是一种与OP的输出相匹配的方法,但我认为@Roland更有用。 sprintf的使用是为了解决split所做的重新排序。这比apply(a, 1, as.list)解决方案的优点在于,嵌套列表的各个元素是数字和字符,而apply强制所有字符(它形成matrix)。

rows <- 1:nrow(a)
breaks <- paste0("p", sprintf(paste0("%0", nchar(max(rows)), "d"), rows))
lapply(split(a, breaks), as.list)

## $p1
## $p1$x
## [1] 1
## 
## $p1$y
## [1] "g"
## 
## 
## $p2
## $p2$x
## [1] 2
## 
## $p2$y
## [1] "c"
## 
## 
## $p3
## $p3$x
## [1] 3
## 
## $p3$y
## [1] "t"

答案 2 :(得分:-1)

根据您的评论我建议使用真实数据库或使用数据包data.table:

DT <- data.table(name=c("Ken","Ashley"),type=c("A","B"),score=c(9,8)) 
setkey(DT, name)
interests <- data.table(name=c("Ken", "Ashley"), 
               interests=list(c("reading","music"), c("dancing","swimming")))

DT[interests]
#     name type score        interests
#1:    Ken    A     9    reading,music
#2: Ashley    B     8 dancing,swimming

请注意,这是一个列表:

unclass(DT[interests])
$name
[1] "Ken"    "Ashley"

$type
[1] "A" "B"

$score
[1] 9 8

$interests
$interests[[1]]
[1] "reading" "music"  

$interests[[2]]
[1] "dancing"  "swimming"


attr(,"row.names")
[1] 1 2
attr(,".internal.selfref")
<pointer: 0x7fc7c4007978>