1

我有一个数据框,其中包含带有字符串的字段,例如“鱼、鸟、动物”等。我已将它们折叠成一个列表,并遍历它们以在同一数据框中创建逻辑字段。 更新:这个问题现在更新了一个更详细的例子。

但是,这很慢并且感觉不是最佳的。这不是我必须多次执行的操作,所以我并没有那么烦恼,但认为可能有更好的方法,也许使用dplyr

此代码确实为字段items中 my_list 中每个元素的每个匹配项创建新字段。

no <- seq(1:3)
items <- c('fish,cat,dog', 'horse,elephant,dog', 'hamster,pig')

df <- data.frame(no, items)
df$items <- as.character(df$items)

df

将创建以下数据框:

  no              items
1  1       fish,cat,dog
2  2 horse,elephant,dog
3  3        hamster,pig

运行此代码将收集字段项并将其扩展为逻辑字段

items <- paste(df$items, collapse = ",")
item_list <- unlist(unique(strsplit(items, ",")))

for (i in 1:length(item_list)) {
    lt <- item_list[i]
    df <- df %>% rowwise() %>% mutate(!!lt := grepl(lt, items))
}

data.frame(df)

导致这个数据框:

  no              items  fish   cat   dog horse elephant hamster   pig
1  1       fish,cat,dog  TRUE  TRUE  TRUE FALSE    FALSE   FALSE FALSE
2  2 horse,elephant,dog FALSE FALSE  TRUE  TRUE     TRUE   FALSE FALSE
3  3        hamster,pig FALSE FALSE FALSE FALSE    FALSE    TRUE  TRUE
4

2 回答 2

3

这将相当快

f1 = function(df, column_name) {
    ## pre-process words
    words = strsplit(df[[column_name]], ",")
    uwords = unlist(words)
    colnames = unique(uwords)

    ## pre-allocate result matrix of 'FALSE' values
    m = matrix(FALSE, nrow(df), length(colnames), dimnames = list(NULL, colnames))

    ## update rows and columns of result matrix containing matches to TRUE
    row = rep(seq_len(nrow(df)), lengths(words))
    col = match(uwords, colnames)
    m[cbind(row, col)] = TRUE

    ## return the final result
    cbind(df, m)
}

最棘手的部分是双列矩阵的矩阵子集将双列矩阵的第一列作为行索引,将第二列作为列索引。所以你要设置的行和列TRUE

row = rep(seq_len(nrow(df)), lengths(words))
col = match(uwords, colnames)

并且矩阵更新为

m[ cbind(row, col) ] = TRUE

没有迭代(例如,sapply()),因此该match()函数被调用一次而不是nrow(df)多次。

对于 3M 行,我有

> idx = rep(1:3, 1000000)
> df1 = df[idx,]
> system.time(f1(df1, "items"))
   user  system elapsed 
 13.304   0.112  13.421 

对于 Christoph 的另一个解决方案(在撰写本文时):

f0 = function(df, column_name) {
    categories_per_row <- strsplit(df[[column_name]], split=",")
    categories <- unique(unlist(categories_per_row))
    categoryM <- t(sapply(categories_per_row, function(y) categories %in% y))
    colnames(categoryM) <- categories
    cbind(df, categoryM)
}

和 Uwe 的 data.table 解决方案(注意,引用语义会改变 dt 的值!另外,我不知道如何将列名作为函数参数传递):

library(data.table)
dt = df1
dt$no = seq_len(nrow(dt))
f2 = function(dt) {
    setDT(dt)
    dt1 = dt[, strsplit(items, ","), by = .(no, items)]
    dt1[, dcast(.SD, no + items ~ V1, function(x) length(x) > 0)] 
}

与时俱进

> system.time(res0 <- f0(df1, "items"))
   user  system elapsed 
 23.776   0.000  23.786 
> system.time(res2 <- f2(dt, "items"))
Using 'V1' as value column. Use 'value.var' to override
   user  system elapsed 
 45.668   0.072  45.593 

大约 1/2 的时间f1()strsplit(); stringr::str_split()大约快两倍,但由于用于拆分的模式是固定的(不是正则表达式),因此使用 是有意义的strsplit(fixed=TRUE),大约快 3 倍。可能一些 data.table pro 会想出一个非常快速的解决方案(但是你需要成为一个 data.table pro ......)。

做类似“将它们[项目共享的单词]折叠到列表[实际上是一个向量!]”之类的事情很诱人,但将单词留在列表中通常是明智的

> df1$items = strsplit(df1$items, ",", fixed=TRUE)
> head(df1)
  no                items
1  1       fish, cat, dog
2  2 horse, elephant, dog
3  3         hamster, pig
4  4       fish, cat, dog
5  5 horse, elephant, dog
6  6         hamster, pig

并节省重新拆分所需的时间/麻烦。tidyverse 方法是创建表的扩展版本

tidyr::unnest(df1)

(或所谓的“重复”问题中的其他方法)。这可能会导致人们重新思考逻辑列在后续操作中的作用。

于 2017-09-07T11:36:14.127 回答
0

这是一步一步的解决方案;Uwe's 可能要快得多,但我希望这更容易理解:

categories_per_row <- strsplit(df$items, split=",")
categories <- unique(unlist(strsplit(df$items, ",")))
categoryM <- t(sapply(categories_per_row, function(y) categories %in% y))
colnames(categoryM) <- categories
cbind(df, categoryM)
于 2017-09-07T10:17:10.440 回答