備忘ログ

チラシの裏的備忘録&メモ

CRANで公開されているRのパッケージの依存関係(ImportとSuggest)をみてみた

個人的な興味関心でCRANで公開されているRのパッケージの依存関係(ImportとSuggest)をみて、依存されている件数が多いパッケージ、つまり引用件数が多いパッケージ等をみてみたのでメモ。

とりあえず、前処理

availabel.pacages()をつかって、CARNで公開されているパッケージのリストを取り出して、Packageと、ImportsとSuggestsのデータフレームを長持ちで依存関係がないものをのぞいたもの(pkg_list)と依存関係の有無に関わらないデータを横持ち(df)で作っておく。

library(plyr)
library(tidyverse)
## -- Attaching packages --------------------------------------- tidyverse 1.3.1 --

## v ggplot2 3.3.5     v purrr   0.3.4
## v tibble  3.1.6     v dplyr   1.0.7
## v tidyr   1.1.4     v stringr 1.4.0
## v readr   2.1.1     v forcats 0.5.1

## -- Conflicts ------------------------------------------ tidyverse_conflicts() --
## x dplyr::arrange()   masks plyr::arrange()
## x purrr::compact()   masks plyr::compact()
## x dplyr::count()     masks plyr::count()
## x dplyr::failwith()  masks plyr::failwith()
## x dplyr::filter()    masks stats::filter()
## x dplyr::id()        masks plyr::id()
## x dplyr::lag()       masks stats::lag()
## x dplyr::mutate()    masks plyr::mutate()
## x dplyr::rename()    masks plyr::rename()
## x dplyr::summarise() masks plyr::summarise()
## x dplyr::summarize() masks plyr::summarize()
df <- available.packages(repos = "https://cloud.r-project.org/")
df <- df %>% 
  as_tibble()
df <- df %>% 
  select(Package, Imports, Suggests)

import_list <- str_split(df$Imports, pattern = ",")
suggest_list <- str_split(df$Suggests, pattern = ",")

import_list <- ldply(import_list, rbind)
suggest_list <- ldply(suggest_list, rbind)

colnames(import_list) <- str_c("Imports", 1:ncol(import_list))
colnames(suggest_list) <- str_c("Suggests", 1:ncol(suggest_list))

df <- bind_cols(select(.data = df, Package), import_list, suggest_list)

df <- df %>% 
  map_df(.f = function(x){
  x %>% 
    str_remove_all(pattern = "\\(.+\\)") %>% 
    str_remove_all(pattern = " ") %>% 
    str_remove_all(pattern = "\n") %>% 
    str_remove_all(pattern = "\\(.+\\)") %>% 
    str_remove_all(pattern = " ") %>% 
    str_remove_all(pattern = "\n")
})

pkg_list <- pivot_longer(df, cols = !Package)

pkg_list <- pkg_list %>% 
  mutate(name = str_remove(name, pattern = "[:digit:]+"),
         value = if_else(value == "", NA_character_, value)) 
pkg_list <- pkg_list %>% 
  na.omit()

冗長な気もするが、データが取れたのでこれを見ていく。

取り出せたCARNに登録されている現在のパッケージ数は、

nrow(df)
## [1] 18694

となる。微妙(30くらい?)にウェブページ上で示されるパッケージ数と異なるが、リポジトリに反映されるタイミングとかの差かなと思っている。

コードを実行するたび(日毎)に取り出せるパッケージ数が変わってくるのでよくわからない。

ImportsもSuggestsもないパッケージは……

ImportsもSuggestsもないパッケージ数を見てみる。つまり全く関数に依存していないデータセットのパッケージか、{base}以外には依存していないパッケージ群になる。

df %>% 
  filter(is.na(Imports1)) %>% 
  filter(is.na(Suggests1)) %>% 
  nrow()
## [1] 3173

全体比でいうと、

(df %>% 
  filter(is.na(Imports1)) %>% 
  filter(is.na(Suggests1)) %>% 
  nrow()) / nrow(df)
## [1] 0.1697336

とおよそ17%程度のパッケージが依存関係がないパッケージになっている。

依存関係がもっとも多いパッケージは……

Importsが最も多いパッケージトップ5は

pkg_list %>% 
  filter(name == "Imports") %>% 
  select(Package) %>% 
  table() %>% 
  as_tibble() %>% 
  arrange(-n) %>% 
  head(5)
## # A tibble: 5 x 2
##   .                  n
##   <chr>          <int>
## 1 Seurat            47
## 2 autostats         42
## 3 pguIMP            41
## 4 epitweetr         37
## 5 MetaIntegrator    36

となっている。

個人的には47個って多い印象。素の状態でインストールを始めたらトイレに行って帰ってこられそう。

ちなみにImportsがあるパッケージのImportsの平均数は

Import_n <- pkg_list %>% 
  filter(name == "Imports") %>% 
  select(Package) %>% 
  table() %>% 
  as_tibble()

mean(Import_n$n)
## [1] 5.743405

となっている。

Suggestsが最も多いパッケージトップ5は

pkg_list %>% 
  filter(name == "Suggests") %>% 
  select(Package) %>% 
  table() %>% 
  as_tibble() %>% 
  arrange(-n) %>% 
  head(5)
## # A tibble: 5 x 2
##   .              n
##   <chr>      <int>
## 1 mlr          111
## 2 parameters    98
## 3 insight       88
## 4 broom         83
## 5 fscaret       67

となっている。

111個ってめちゃめちゃ多い印象。素の状態でSuggestsも含めてインストールしたらお風呂に入れそう。

こちらのSuggestsがあるパッケージのSuggestsの平均値は

Suggest_n <- pkg_list %>% 
  filter(name == "Suggests") %>% 
  select(Package) %>% 
  table() %>% 
  as_tibble()

mean(Suggest_n$n)
## [1] 4.350905

となっている。

多く引用されているパッケージは……

Importsとして多く引用されているパッケージトップ10は次の通り。

pkg_list %>% 
  filter(name == "Imports") %>% 
  select(value) %>% 
  table() %>% 
  as_tibble() %>% 
  rename("Package" = '.') %>% 
  arrange(-n) %>% 
  head(10)
## # A tibble: 10 x 2
##    Package       n
##    <chr>     <int>
##  1 stats      4481
##  2 utils      2896
##  3 methods    2701
##  4 dplyr      2423
##  5 ggplot2    2372
##  6 Rcpp       2221
##  7 graphics   1998
##  8 magrittr   1589
##  9 rlang      1431
## 10 grDevices  1326

R-Core Team謹製の{stats}{utils}{methods}の続いては{dplyr}{ggplot2}が多くのパッケージでImportsとして引用されていることがわかる。

Hadley Wickham氏強い。

ただ、これはDescriptionにかかれているImportsを読んだだけなので、実際に引用数というふうに考えると孫引き等を考慮するともう少し数字は変わりそうな印象はある。

トップ20で可視化してみる

pkg_list %>% 
  filter(name == "Imports") %>% 
  select(value) %>% 
  table() %>% 
  as_tibble() %>% 
  rename("Package" = '.') %>% 
  arrange(-n) %>% 
  head(20) %>% 
  mutate(Package = factor(Package, levels = Package)) %>% 
  ggplot(aes(x = Package, y = n)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

f:id:indenkun:20220114145954p:plain

可視化してみると{stats}が圧倒的に多いが、ある意味{stats}で用意されている関数群はもはやRの{base}といっても差し支えないのじゃないかと思うので(妄言)、実質{dplyr}はトップ3に入っていると言っても過言じゃないんじゃないか(妄言2)と思う。いや、{utils}{methods}{base}みたいなもんじゃないか(妄言3)、ということは被引用件数トップは{dplyr}といっても過言じゃないんじゃないか(妄言4)。

でも本当に、基本的なパッケージである{stats}{utils}などは普通に使っている分には勝手にロードされているのでRの{base}と大差なくつかっている関数群になると思うので、意識して依存関係として使われているパッケージとしては{dplyr}がトップといっても差し支えない気もする。

それはそうと、トップ20までみてみると、{tidyvese}系のパッケージがなんと多いこと。

ちなみにSuggestsをみてみると、

pkg_list %>% 
  filter(name == "Suggests") %>% 
  select(value) %>% 
  table() %>% 
  as_tibble() %>% 
  rename("Package" = '.') %>% 
  arrange(-n) %>% 
  head(10)
## # A tibble: 10 x 2
##    Package       n
##    <chr>     <int>
##  1 knitr      6413
##  2 testthat   6402
##  3 rmarkdown  5896
##  4 covr       1811
##  5 ggplot2    1080
##  6 dplyr       620
##  7 MASS        577
##  8 spelling    517
##  9 markdown    367
## 10 roxygen2    323

となっている。

{testthat}って、テストを{testthat}で書くと勝手に追加されたものってことでしょ?(多分)

ということはCARNに登録されているパッケージ全体の約1/3が、{testthat}をつかってテストを書いているということ?で、やはりここでもHadley Wickham強い。

こちらも可視化してみる。

pkg_list %>% 
  filter(name == "Suggests") %>% 
  select(value) %>% 
  table() %>% 
  as_tibble() %>% 
  rename("Package" = '.') %>% 
  arrange(-n) %>% 
  head(20) %>% 
  mutate(Package = factor(Package, levels = Package)) %>% 
  ggplot(aes(x = Package, y = n)) +
  geom_bar(stat = "identity") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))

f:id:indenkun:20220114150007p:plain

{kintr}{testthat}{rmarkdown}が圧倒的に多い印象。

相互依存関係にあるパッケージは……

Importsにお互いのパッケージが記載されている、相互依存的なパッケージ、つまり片方のパッケージをいれるともう一方もインストールされる同士のパッケージは次の通りとなる。

pkg_import_list <- pkg_list %>% 
  filter(name == "Imports")
ans <- map2_df(pkg_import_list$Package, pkg_import_list$value, function(x, y){
    pkg_import_list %>% 
      filter(Package == y) %>% 
      filter(value == x)
  })

ans
## # A tibble: 0 x 3
## # ... with 3 variables: Package <chr>, name <chr>, value <chr>

で(コードの誤りがなければ)なかった。

おまけ:空欄という依存関係

Discriptionを機械的に読み込んでいると、依存関係のパッケージに1つ空欄が出てくることがある。

これは必要ないのに、依存関係のパッケージの最後に,をつけてもう一個パッケージがあるように見せかけられているから。

Importsにこのような記載があるパッケージが100以上あった。