使用dplyr中的列表列函数进行变异

16

我正在尝试计算一个数据框中源向量和比较向量之间的Jaccard相似度。

首先,创建一个带有names_字段(字符串向量)的数据框。使用dplyr的mutate函数,创建names_vec,这是一个列表列,其中每行现在是一个向量(向量中的每个元素都是一个字母)。

然后,创建一个新的数据框,其中包含列jaccard_sim,该列应计算Jaccard相似度。

source_vec <- c('a', 'b', 'c')

df_comp <- tibble(names_ = c("b d f", "u k g", "m o c"),
              names_vec = strsplit(names_, ' '))

df_comp_jaccard <- df_comp %>%
   dplyr::mutate(jaccard_sim = length(intersect(names_vec, source_vec))/length(union(names_vec, source_vec)))

jaccard_sim 中的所有值都为零。然而,如果我们运行类似于下面这样的内容,我们将得到第一个条目正确的 Jaccard 相似度 0.2:

a <- length(intersect(source_vec, df_comp[[1,2]]))
b <- length(union(source_vec, df_comp[[1,2]]))
a/b

{btsdaf} - matsuo_basho
2个回答

18
你可以简单地添加rowwise
df_comp_jaccard <- df_comp %>%
  rowwise() %>%
  dplyr::mutate(jaccard_sim = length(intersect(names_vec, source_vec))/
                              length(union(names_vec, source_vec)))

# A tibble: 3 x 3
  names_ names_vec jaccard_sim
   <chr>    <list>       <dbl>
1  b d f <chr [3]>         0.2
2  u k g <chr [3]>         0.0
3  m o c <chr [3]>         0.2

使用rowwise,您可以获得一些人在使用mutate时期望的直观行为:“对每一行执行此操作”。

不使用rowwise意味着您利用向量化函数,这要快得多,这就是默认设置,但如果您不小心可能会产生意外结果。

mutate(或其他dplyr函数)工作逐行的印象是一种幻觉,这是由于您正在使用向量化函数,实际上您总是在操作整个列。

我将用几个例子说明:

有时候,使用像paste这样的向量化函数会得到相同的结果:

tibble(a=1:10,b=10:1) %>% mutate(X = paste(a,b,sep="_"))
tibble(a=1:10,b=10:1) %>% rowwise %>% mutate(X = paste(a,b,sep="_"))
# # A tibble: 5 x 3
#       a     b     X
#   <int> <int> <chr>
# 1     1     5   1_5
# 2     2     4   2_4
# 3     3     3   3_3
# 4     4     2   4_2
# 5     5     1   5_1

有时可能会有不同的情况,例如使用非向量化的函数,比如max

tibble(a=1:5,b=5:1) %>% mutate(max(a,b))
# # A tibble: 5 x 3
#       a     b `max(a, b)`
#   <int> <int>       <int>
# 1     1     5           5
# 2     2     4           5
# 3     3     3           5
# 4     4     2           5
# 5     5     1           5

tibble(a=1:5,b=5:1) %>% rowwise %>% mutate(max(a,b))
# # A tibble: 5 x 3
#       a     b `max(a, b)`
#   <int> <int>       <int>
# 1     1     5           5
# 2     2     4           4
# 3     3     3           3
# 4     4     2           4
# 5     5     1           5

请注意,在实际情况下,您不应使用rowwise,而应使用为此目的矢量化的pmax

tibble(a=1:5,b=5:1) %>% mutate(pmax(a,b))
# # A tibble: 5 x 3
#       a     b `pmax(a, b)`
#   <int> <int>        <int>
# 1     1     5            5
# 2     2     4            4
# 3     3     3            3
# 4     4     2            4
# 5     5     1            5

Intersect是一个函数,它需要输入两个参数:一个包含向量的列表列和另一个向量,这两个对象没有交集。


{btsdaf} - matsuo_basho
rowwise并不是必须的,你可以像@akrun在他的解决方案中所做的那样使用mutatemap,或者在其他情况下使用pmap,这实际上是“官方推荐的解决方案”。有时候,rowwise更易读(嵌入代码较少),尽管它更“神奇”(很少有人真正理解它对对象的影响)并且速度较慢。抱歉回答晚了! - moodymudskipper

12

我们可以使用map函数来遍历list

library(tidyverse)
df_comp %>% 
     mutate(jaccard_sim = map_dbl(names_vec, ~length(intersect(.x, 
                 source_vec))/length(union(.x, source_vec))))
# A tibble: 3 x 3
#   names_ names_vec jaccard_sim
#    <chr>    <list>       <dbl>
#1  b d f <chr [3]>         0.2
#2  u k g <chr [3]>         0.0
#3  m o c <chr [3]>         0.2

映射函数已经优化。以下是稍大数据集的system.time

df_comp1 <- df_comp[rep(1:nrow(df_comp), 1e5),]
system.time({

 df_comp1 %>%
      rowwise() %>%
      dplyr::mutate(jaccard_sim = length(intersect(names_vec, source_vec))/length(union(names_vec, source_vec)))
    })
 #user  system elapsed 
 # 25.59    0.05   25.96 

system.time({
  df_comp1 %>% 
     mutate(jaccard_sim = map_dbl(names_vec, ~length(intersect(.x, 
                 source_vec))/length(union(.x, source_vec))))
   })
#user  system elapsed 
#  13.22    0.00   13.22 

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接