根据字典中单词的值检索句子分数
Retrieving sentence score based on values of words in a dictionary
已编辑 df
和 dict
我有一个包含句子的数据框:
df <- data_frame(text = c("I love pandas", "I hate monkeys", "pandas pandas pandas", "monkeys monkeys"))
以及包含单词及其相应分数的字典:
dict <- data_frame(word = c("love", "hate", "pandas", "monkeys"),
score = c(1,-1,1,-1))
我想在 df
后添加一列 "score",用于对每个句子的分数求和:
预期结果
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 3
4 monkeys monkeys -2
更新
目前的结果如下:
阿克伦的方法
建议 1
df %>% mutate(score = sapply(strsplit(text, ' '), function(x) with(dict, sum(score[word %in% x]))))
请注意,为了使此方法起作用,我必须使用 data_frame()
来创建 df
和 dict
而不是 data.frame()
否则我会得到:Error in strsplit(text, " ") : non-character argument
Source: local data frame [4 x 2]
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
这不考虑单个字符串中的多个匹配项。接近预期结果,但还差得远。
建议2
我在评论中对 akrun 的建议进行了一些微调,以将其应用于编辑后的 post
cbind(df, unnest(stri_split_fixed(df$text, ' '), group) %>%
group_by(group) %>%
summarise(score = sum(dict$score[dict$word %in% x])) %>%
ungroup() %>% select(-group) %>% data.frame())
这不考虑字符串中的多个匹配项:
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
Richard Scriven 的方法
建议 1
group_by(df, text) %>%
mutate(score = sum(dict$score[stri_detect_fixed(text, dict$word)]))
更新所有包后,这现在可以工作了(尽管它不考虑多个匹配项)
Source: local data frame [4 x 2]
Groups: text
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
建议2
total <- with(dict, {
vapply(df$text, function(X) {
sum(score[vapply(word, grepl, logical(1L), x = X, fixed = TRUE)])
}, 1)
})
cbind(df, total)
这给出了相同的结果:
text total
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
建议3
s <- strsplit(df$text, " ")
total <- vapply(s, function(x) sum(with(dict, score[match(x, word, 0L)])), 1)
cbind(df, total)
这确实有效:
text total
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 3
4 monkeys monkeys -2
Thelatemail的方法
res <- sapply(dict$word, function(x) {
sapply(gregexpr(x,df$text),function(y) length(y[y!=-1]) )
})
cbind(df, score = rowSums(res * dict$score))
请注意,我添加了 cbind()
部分。这实际上符合预期结果。
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 3
4 monkeys monkeys -2
最终答案
受 akrun 的建议启发,以下是我最终写的最 dplyr
风格的解决方案:
library(dplyr)
library(tidyr)
library(stringi)
bind_cols(df, unnest(stri_split_fixed(df$text, ' '), group) %>%
group_by(x) %>% mutate(score = sum(dict$score[dict$word %in% x])) %>%
group_by(group) %>%
summarise(score = sum(score)) %>%
select(-group))
虽然我会实施 Richard Scriven 的建议 #3,因为它是最有效的。
基准
以下是应用于更大数据集(df
的 93 个句子和 dict
的 14K 单词)的建议,使用 microbenchmark()
:
mbm = microbenchmark(
akrun = df %>% mutate(score = sapply(stri_detect_fixed(text, ' '), function(x) with(dict, sum(score[word %in% x])))),
akrun2 = cbind(df, unnest(stri_split_fixed(df$text, ' '), group) %>% group_by(group) %>% summarise(score = sum(dict$score[dict$word %in% x])) %>% ungroup() %>% select(-group) %>% data.frame()),
rscriven1 = group_by(df, text) %>% mutate(score = sum(dict$score[stri_detect_fixed(text, dict$word)])),
rscriven2 = cbind(df, score = with(dict, { vapply(df$text, function(X) { sum(score[vapply(word, grepl, logical(1L), x = X, fixed = TRUE)])}, 1)})),
rscriven3 = cbind(df, score = vapply(strsplit(df$text, " "), function(x) sum(with(dict, score[match(x, word, 0L)])), 1)),
thelatemail = cbind(df, score = rowSums(sapply(dict$word, function(x) { sapply(gregexpr(x,df$text),function(y) length(y[y!=-1]) ) }) * dict$score)),
sbeaupre = bind_cols(df, unnest(stri_split_fixed(df$text, ' '), group) %>% group_by(x) %>% mutate(score = sum(dict$score[dict$word %in% x])) %>% group_by(group) %>% summarise(score = sum(score)) %>% select(-group)),
times = 10
)
结果:
更新: 这是迄今为止我发现的最简单的 dplyr
方法。我将添加一个 stringi
函数来加快处理速度。如果df$text
中没有相同的句子,我们可以按该列分组,然后应用mutate()
注意:软件包版本为 dplyr 0.4.1 和 stringi 0.4.1
library(dplyr)
library(stringi)
group_by(df, text) %>%
mutate(score = sum(dict$score[stri_detect_fixed(text, dict$word)]))
# Source: local data frame [2 x 2]
# Groups: text
#
# text score
# 1 I love pandas 2
# 2 I hate monkeys -2
我删除了昨晚发的do()
方法,不过你可以在编辑历史中找到它。对我来说,这似乎是不必要的,因为上述方法同样有效,而且是更 dplyr
的方法。
此外,如果您对非 dplyr
答案持开放态度,这里有两个使用基本函数的答案。
total <- with(dict, {
vapply(df$text, function(X) {
sum(score[vapply(word, grepl, logical(1L), x = X, fixed = TRUE)])
}, 1)
})
cbind(df, total)
# text total
# 1 I love pandas 2
# 2 I hate monkeys -2
或者使用 strsplit()
的替代方法产生相同的结果
s <- strsplit(df$text, " ")
total <- vapply(s, function(x) sum(with(dict, score[match(x, word, 0L)])), 1)
cbind(df, total)
通过 sapply
和 gregexpr
的一些双循环:
res <- sapply(dict$word, function(x) {
sapply(gregexpr(x,df$text),function(y) length(y[y!=-1]) )
})
rowSums(res * dict$score)
#[1] 2 -2
这也说明了在单个字符串中存在多个匹配项的情况:
df <- data.frame(text = c("I love love pandas", "I hate monkeys"))
# run same code as above
#[1] 3 -2
已编辑 df
和 dict
我有一个包含句子的数据框:
df <- data_frame(text = c("I love pandas", "I hate monkeys", "pandas pandas pandas", "monkeys monkeys"))
以及包含单词及其相应分数的字典:
dict <- data_frame(word = c("love", "hate", "pandas", "monkeys"),
score = c(1,-1,1,-1))
我想在 df
后添加一列 "score",用于对每个句子的分数求和:
预期结果
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 3
4 monkeys monkeys -2
更新
目前的结果如下:
阿克伦的方法
建议 1
df %>% mutate(score = sapply(strsplit(text, ' '), function(x) with(dict, sum(score[word %in% x]))))
请注意,为了使此方法起作用,我必须使用 data_frame()
来创建 df
和 dict
而不是 data.frame()
否则我会得到:Error in strsplit(text, " ") : non-character argument
Source: local data frame [4 x 2]
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
这不考虑单个字符串中的多个匹配项。接近预期结果,但还差得远。
建议2
我在评论中对 akrun 的建议进行了一些微调,以将其应用于编辑后的 post
cbind(df, unnest(stri_split_fixed(df$text, ' '), group) %>%
group_by(group) %>%
summarise(score = sum(dict$score[dict$word %in% x])) %>%
ungroup() %>% select(-group) %>% data.frame())
这不考虑字符串中的多个匹配项:
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
Richard Scriven 的方法
建议 1
group_by(df, text) %>%
mutate(score = sum(dict$score[stri_detect_fixed(text, dict$word)]))
更新所有包后,这现在可以工作了(尽管它不考虑多个匹配项)
Source: local data frame [4 x 2]
Groups: text
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
建议2
total <- with(dict, {
vapply(df$text, function(X) {
sum(score[vapply(word, grepl, logical(1L), x = X, fixed = TRUE)])
}, 1)
})
cbind(df, total)
这给出了相同的结果:
text total
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 1
4 monkeys monkeys -1
建议3
s <- strsplit(df$text, " ")
total <- vapply(s, function(x) sum(with(dict, score[match(x, word, 0L)])), 1)
cbind(df, total)
这确实有效:
text total
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 3
4 monkeys monkeys -2
Thelatemail的方法
res <- sapply(dict$word, function(x) {
sapply(gregexpr(x,df$text),function(y) length(y[y!=-1]) )
})
cbind(df, score = rowSums(res * dict$score))
请注意,我添加了 cbind()
部分。这实际上符合预期结果。
text score
1 I love pandas 2
2 I hate monkeys -2
3 pandas pandas pandas 3
4 monkeys monkeys -2
最终答案
受 akrun 的建议启发,以下是我最终写的最 dplyr
风格的解决方案:
library(dplyr)
library(tidyr)
library(stringi)
bind_cols(df, unnest(stri_split_fixed(df$text, ' '), group) %>%
group_by(x) %>% mutate(score = sum(dict$score[dict$word %in% x])) %>%
group_by(group) %>%
summarise(score = sum(score)) %>%
select(-group))
虽然我会实施 Richard Scriven 的建议 #3,因为它是最有效的。
基准
以下是应用于更大数据集(df
的 93 个句子和 dict
的 14K 单词)的建议,使用 microbenchmark()
:
mbm = microbenchmark(
akrun = df %>% mutate(score = sapply(stri_detect_fixed(text, ' '), function(x) with(dict, sum(score[word %in% x])))),
akrun2 = cbind(df, unnest(stri_split_fixed(df$text, ' '), group) %>% group_by(group) %>% summarise(score = sum(dict$score[dict$word %in% x])) %>% ungroup() %>% select(-group) %>% data.frame()),
rscriven1 = group_by(df, text) %>% mutate(score = sum(dict$score[stri_detect_fixed(text, dict$word)])),
rscriven2 = cbind(df, score = with(dict, { vapply(df$text, function(X) { sum(score[vapply(word, grepl, logical(1L), x = X, fixed = TRUE)])}, 1)})),
rscriven3 = cbind(df, score = vapply(strsplit(df$text, " "), function(x) sum(with(dict, score[match(x, word, 0L)])), 1)),
thelatemail = cbind(df, score = rowSums(sapply(dict$word, function(x) { sapply(gregexpr(x,df$text),function(y) length(y[y!=-1]) ) }) * dict$score)),
sbeaupre = bind_cols(df, unnest(stri_split_fixed(df$text, ' '), group) %>% group_by(x) %>% mutate(score = sum(dict$score[dict$word %in% x])) %>% group_by(group) %>% summarise(score = sum(score)) %>% select(-group)),
times = 10
)
结果:
更新: 这是迄今为止我发现的最简单的 dplyr
方法。我将添加一个 stringi
函数来加快处理速度。如果df$text
中没有相同的句子,我们可以按该列分组,然后应用mutate()
注意:软件包版本为 dplyr 0.4.1 和 stringi 0.4.1
library(dplyr)
library(stringi)
group_by(df, text) %>%
mutate(score = sum(dict$score[stri_detect_fixed(text, dict$word)]))
# Source: local data frame [2 x 2]
# Groups: text
#
# text score
# 1 I love pandas 2
# 2 I hate monkeys -2
我删除了昨晚发的do()
方法,不过你可以在编辑历史中找到它。对我来说,这似乎是不必要的,因为上述方法同样有效,而且是更 dplyr
的方法。
此外,如果您对非 dplyr
答案持开放态度,这里有两个使用基本函数的答案。
total <- with(dict, {
vapply(df$text, function(X) {
sum(score[vapply(word, grepl, logical(1L), x = X, fixed = TRUE)])
}, 1)
})
cbind(df, total)
# text total
# 1 I love pandas 2
# 2 I hate monkeys -2
或者使用 strsplit()
的替代方法产生相同的结果
s <- strsplit(df$text, " ")
total <- vapply(s, function(x) sum(with(dict, score[match(x, word, 0L)])), 1)
cbind(df, total)
通过 sapply
和 gregexpr
的一些双循环:
res <- sapply(dict$word, function(x) {
sapply(gregexpr(x,df$text),function(y) length(y[y!=-1]) )
})
rowSums(res * dict$score)
#[1] 2 -2
这也说明了在单个字符串中存在多个匹配项的情况:
df <- data.frame(text = c("I love love pandas", "I hate monkeys"))
# run same code as above
#[1] 3 -2