使用 table() 生成数据框的更好方法

Better way to produce data frame using table()

最近,我发现我一遍又一遍地使用以下模式。过程是:

在 R 中,它看起来像这样:

# Sample data
df <- data.frame(x = round(runif(100), 1), 
                 y = factor(ifelse(runif(100) > .5, 1, 0), 
                            labels = c('failure', 'success')) 
                )

# Get frequencies
dfSummary <- as.data.frame.matrix(table(df$x, df$y))
# Add column of original values from rownames
dfSummary$x <- as.numeric(rownames(dfSummary))
# Remove rownames
rownames(dfSummary) <- NULL
# Reorder columns
dfSummary <- dfSummary[, c(3, 1, 2)]

R 中有没有更优雅的东西,最好是使用基函数?我知道我可以使用 sql 在单个命令中执行此操作 - 我认为必须有可能在 R 中实现类似的行为。

sqldf解法:

library(sqldf)
dfSummary <- sqldf("select 
                     x, 
                     sum(y = 'failure') as failure,
                     sum(y = 'success') as success
                    from df group by x")

基数 R 的替代方案可以是:

aggregate(. ~ x, transform(df, success = y == "sucess", 
                               failure = y == "failure", y = NULL), sum)
#     x success failure
#1  0.0       2       4
#2  0.1       6       8
#3  0.2       1       7
#4  0.3       5       4
#5  0.4       6       6
#6  0.5       3       3
#7  0.6       4       6
#8  0.7       6       6
#9  0.8       4       5
#10 0.9       6       7
#11 1.0       1       0

base R 中的其他解决方案(到目前为止)相比,您修改为函数的代码将更加高效。如果您想要单行代码,可以使用 base R 中的 "reshape/table" 组合。

reshape(as.data.frame(table(df)), idvar='x', timevar='y',
        direction='wide')
#     x Freq.failure Freq.success
#1    0            3            2
#2  0.1            3            9
#3  0.2            5            5
#4  0.3            8            7
#5  0.4            5            3
#6  0.5            9            4
#7  0.6            3            6
#8  0.7            7            6
#9  0.8            3            1
#10 0.9            4            3
#11   1            0            4

万一你想试试data.table

library(data.table)
dcast.data.table(setDT(df), x~y)
#          x failure success
# 1: 0.0       3       2
# 2: 0.1       3       9
# 3: 0.2       5       5
# 4: 0.3       8       7
# 5: 0.4       5       3
# 6: 0.5       9       4
# 7: 0.6       3       6
# 8: 0.7       7       6
# 9: 0.8       3       1
#10: 0.9       4       3
#11: 1.0       0       4

更新

我没有注意到 as.data.frame(table( 转换为 "factor" 列(感谢@Hadley 的评论)。解决方法是:

res <- transform(reshape(as.data.frame(table(df), stringsAsFactors=FALSE),
     idvar='x', timevar='y', direction='wide'), x= as.numeric(x))

数据

set.seed(24)
df <- data.frame(x = round(runif(100), 1), 
             y = factor(ifelse(runif(100) > .5, 1, 0), 
                        labels = c('failure', 'success')) 
            )

基准

set.seed(24)
df <- data.frame(x = round(runif(1e6), 1), 
             y = factor(ifelse(runif(1e6) > .5, 1, 0), 
                        labels = c('failure', 'success')) 
            )

tomas <- function(){
   dfSummary <- as.data.frame.matrix(table(df$x, df$y))
   dfSummary$x <- as.numeric(rownames(dfSummary))
   dfSummary <- dfSummary[, c(3, 1, 2)]}


 doc <- function(){aggregate(. ~ x, transform(df,
        success = y == "success", failure = y == "failure",
                     y = NULL), sum)}

 akrun <- function(){reshape(as.data.frame(table(df)),
             idvar='x', timevar='y', direction='wide')}

library(microbenchmark)
 microbenchmark(tomas(), doc(), akrun(), unit='relative', times=20L)
 Unit: relative
 #expr       min         lq      mean    median         uq       max neval cld
 #tomas()  1.000000  1.0000000  1.000000  1.000000  1.0000000  1.000000    20  a 
 #doc()   13.451037 11.5050997 13.082074 13.043584 12.8048306 19.715535    20   b
 #akrun()  1.019977  0.9522809  1.012332  1.007569  0.9993835  1.533191    20  a 

更新为 dcast.data.table

df1 <- copy(df)
akrun2 <- function() {dcast.data.table(setDT(df1), x~y)}
microbenchmark(tomas(), akrun2(), unit='relative', times=20L)
#   Unit: relative
# expr      min       lq     mean  median       uq      max neval cld
# tomas() 6.493231 6.345752 6.410853 6.51594 6.502044 5.591753    20   b
# akrun2() 1.000000 1.000000 1.000000 1.00000 1.000000 1.000000    20  a 

这样应该效率比较高。您不能真正抑制数据框中的行名,因为它们是有效数据框的要求

X <- table(df$x,df$y)
cbind( data.frame(x=rownames(X)), unclass(X) )
      x failure success
0     0       5       3
0.1 0.1       6       1
0.2 0.2       7       8
0.3 0.3       7       3
0.4 0.4       6       6
0.5 0.5       6       4
0.6 0.6       2       5
0.7 0.7       2       7
0.8 0.8       3       7
0.9 0.9       4       6
1     1       2       0