展平分隔的复合柱

时间:2014-02-12 21:00:52

标签: r

我在R中获得了一个数据框,其中一个字段是复合的(分隔的)。这是我得到的一个例子:

users=c(1,2,3)
items=c("23 77 49", "10 18 28", "20 31 84")
df = data.frame(users,items)

(我不构建它;这仅用于说明目的。)

  users    items
  1        23 77 49
  2        10 18 28
  3        20 31 84

我想展平第二列,以便获得(非唯一的)用户ID列表和每行单独的项目。所以我想最终:

user   item
1        23
1        77
1        49
2        10
2        18
2        28
3        20
3        31
3        84

我试过了:

data.frame(user = df$users, item = unlist(strsplit(as.character(df$items), " "))) 

但我得到“论据意味着不同的行数”。我理解为什么,但找不到解决办法给我想要的结果。有什么想法吗?

另外,当我获得超过2000万行时,最有效的方法是什么?

3 个答案:

答案 0 :(得分:2)

items <- strsplit(df$items, " ")
data.frame(user = rep(df$users, sapply(items, length)), item = unlist(items))

##   user item                                                                                                                                                                                                                                
## 1    1   23                                                                                                                                                                                                                                
## 2    1   77                                                                                                                                                                                                                                
## 3    1   49                                                                                                                                                                                                                                
## 4    2   10                                                                                                                                                                                                                                
## 5    2   18                                                                                                                                                                                                                                
## 6    2   28                                                                                                                                                                                                                                
## 7    3   20                                                                                                                                                                                                                                
## 8    3   31                                                                                                                                                                                                                                
## 9    3   84  

library(data.table)

DT <- data.table(df)    
DT[, list(item = unlist(strsplit(items, " "))), by = users]

##    users item                                                                                                                                                                                                                              
## 1:     1   23                                                                                                                                                                                                                              
## 2:     1   77                                                                                                                                                                                                                              
## 3:     1   49                                                                                                                                                                                                                              
## 4:     2   10                                                                                                                                                                                                                              
## 5:     2   18                                                                                                                                                                                                                              
## 6:     2   28                                                                                                                                                                                                                              
## 7:     3   20                                                                                                                                                                                                                              
## 8:     3   31                                                                                                                                                                                                                              
## 9:     3   84 

答案 1 :(得分:1)

这是dplyr解决方案

users=c(1,2,3)
items=c("23 77 49", "10 18 28", "20 31 84")
df = data.frame(users,items,stringsAsFactors=FALSE)
rbind_all(do(df %.% group_by(users), 
          .f = function(d) data.frame(d[,1,drop=FALSE], 
              items = unlist(strsplit(d[['items']],' ')), 
           stringsAsFactors=FALSE)))

拥有expand函数会非常好,即与summarise相反

例如。如果以下可行。

df %.% group_by(users) %.% expand(unlist(strsplit(items,' ')))

答案 2 :(得分:1)

如果您愿意安装我的“SOfun”软件包或加载我的concat.split.DT function,并且如果每个“item”字符串中的项目数相同(在您的示例中,有3个),则以下可能是一个选项:

library(reshape2)
library(data.table)

melt(concat.split.DT(indf, "items", " "), id.vars="users")

这是一个例子。

示例数据:3行,3000行和3,000,000行

我添加了一个“id”列,以便您可以比较两个选项的输出。

## your sample data.frame
df <- data.frame(users=c(1,2,3),
                 items=c("23 77 49", "10 18 28", "20 31 84"))

## extended to 3000 rows
df1k <- df[rep(rownames(df), 1000), ]
df1k$id <- sequence(nrow(df1k))

## extended to 3 million rows
df1m <- df1M <- df[rep(rownames(df), 1000000), ]
df1m$id <- sequence(nrow(df1m))

加载所需的包

  • {SOfun'(仅限GitHub)concat.split.DT使用“data.table”中的fread来分割连接值。
  • {li>“reshape2”代表melt
  • “data.table”因为它的精彩,至少是版本1.8.11

# library(devtools)
# install_github("SOfun", "mrdwab")
library(SOfun)
library(data.table)
library(reshape2)
packageVersion("data.table")
# [1] ‘1.8.11’

以下是测试Jake答案速度的一些功能。稍后我会尝试用“dplyr”更新。

fun1 <- function(indf) {
  DT <- melt(concat.split.DT(indf, "items", " "), 
             id.vars=c("id", "users"))
  setkeyv(DT, c("id", "users"))
  DT
}

fun2 <- function(indf) {
  DT <- data.table(indf)    
  DT[, list(item = unlist(strsplit(as.character(items), " "))), 
     by = list(id, users)]
}

在3,000行进行测试

microbenchmark(fun1(df1k), fun2(df1k))
# Unit: milliseconds
#        expr       min        lq    median        uq      max neval
#  fun1(df1k)  17.64675  18.21658  18.79859  21.21943  71.7737   100
#  fun2(df1k) 152.97974 158.44148 163.12707 199.77297 345.7508   100

在3,000,000行上测试(仅一次)

时间会在几秒钟内......

system.time(fun1(df1m))
#    user  system elapsed 
#    7.71    0.94    8.69 
system.time(fun2(df1m))
#    user  system elapsed 
#  177.80    0.50  178.97 

更新

@Jake在评论中提出了一个很好的观点,即添加“id”会在时间上产生很大的不同。我添加它只是为了可以轻松比较两个data.table方法的输出,看看结果是一样的。

删除“id”列并删除fun1fun2中对“id”的引用,为我们提供以下内容:

microbenchmark(fun1a(df1M), fun2a(df1M), fun3(df1M), times = 5)
# Unit: seconds
#         expr       min        lq    median        uq       max neval
#  fun1a(df1M)  2.307313  2.420845  2.630284  2.822011  3.074464     5
#  fun2a(df1M) 12.480502 12.491783 12.761392 13.069169 13.733686     5
#   fun3(df1M) 13.976329 14.281856 14.471252 15.041450 15.089593     5

上面的基准测试是fun3,这是@ mnel的“dplyr”方法。

fun3 <- function(indf) {
  rbind_all(do(indf %.% group_by(users), 
               .f = function(d) data.frame(
                 d[,1,drop=FALSE], 
                 items = unlist(strsplit(as.character(d[['items']]),' ')), 
                 stringsAsFactors=FALSE)))
}

非常好的表现所有答案!