我有一个包含字符串字段的数据框,例如“fish,birds,animals”等。我已将它们折叠到一个列表中,并迭代它们以便在同一数据帧中创建逻辑字段。 更新:现在使用更详细的示例更新问题。
然而,这很慢并且感觉不是最佳的。这不是我不得不做的一次操作,所以我不是那么烦恼,但想想可能有更好的方法,或许使用 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
答案 0 :(得分: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
f1()
使用strsplit()
约1/2的时间; 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
并节省重新分割所需的时间/麻烦。整齐的方法是创建表的扩展版本
tidyr::unnest(df1)
(或所谓“复制”问题中的其他方法)。这可能会导致人们重新思考逻辑列在后续操作中的作用。
答案 1 :(得分:0)
这是一步一步的解决方案; Uwe可能要快得多,但我希望这更容易理解:
Func getLife()
Local $hDLL = DllOpen("MyFirstDll.dll")
Local $result = DllCall($hDLL, "int:cdecl", "getLife")
If @error > 0 Then
MsgBox(0, "Error", "Oh ups.. dll loading fail")
Else
MsgBox(0, "Result", $result[0])
EndIf
DllClose($hDLL)
EndFunc