web-dev-qa-db-ja.com

dplyrのリスト列関数で変更する

ティブル内のソースベクトルと比較ベクトルの間のジャッカード類似度を計算しようとしています。

まず、names_フィールド(文字列のベクトル)を使用してtibbleを作成します。 dplyrの変異を使用して、names_vec(リスト列)を作成します。ここで、各行はベクトルになります(ベクトルの各要素は文字になります)。

次に、Jaccardの類似性を計算することになっている列jaccard_simを使用して新しいtibbleを作成します。

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のすべての値はゼロです。ただし、このようなものを実行すると、最初のエントリに対して0.2の正しいJaccard類似性が得られます。

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

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

交差はそのような関数です。この関数に、ベクトルを含む1つのリスト列と他の1つのベクトルを指定しました。これらの2つのオブジェクトには、交差がありません。

13

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

map関数が最適化されています。以下は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 
9
akrun