假设数据框如下:
> 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?以及如何?)
答案 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>