備忘ログ

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

Rで畳み込み(線形畳み込み)を行うconvolve()関数のメモなど

ちょっとしたメモ、三本立て。

Rで畳み込み(線形畳み込み)を行うconvolve()関数のメモ

Rでベクトルの畳み込みを行うconvolve()関数のメモ({stats}パッケージの関数で通常ではRの起動時にロードされている)。

線形畳み込み(普通の畳み込み)を行うときには引数のtypetype = "open"とする。既定値ではtype = "circular"で循環畳み込みになっている。

このとき、xyをそれぞれ計算するベクトルとしたとき、pythonnumpyを用いた場合のnumpy.convolve(x, y)とすると、同じような結果をRのconvolve()関数で得ようとすると2つ目のベクトルを逆順にして与える必要がある。つまり、convolve(x, rev(y), type = "open")とする必要がある。ヘルプドキュメント読むとちゃんとかいてる。

実行例

x <- 1:3
y <- 4:6
convolve(x, rev(y), type = "open")
## [1]  4 13 28 27 18

convolve()関数のヘルプドキュメントを読むと

Arguments

x, y numeric sequences of the same length to be convolved.

と書いてあるが、線形畳み込みの場合は2つのベクトルの長さが同じである必要はない。

y <- 4:7
convolve(x, rev(y), type = "open")
## [1]  4 13 28 34 32 21

循環畳み込みの場合には、当然2つのベクトルの長さが同じである必要がある。ヘルプドキュメントを読むと一瞬混乱する。

雑感

上記の通り、引数のヘルプドキュメントをみると線形畳み込みでも同じ長さのベクトルしか計算できないのか?と少し混乱する(多分だが、誰ももこのヘルプドキュメントを読んでいないんじゃないかと思う)。

また、関数名がconvolvenumpyと一緒だが、Rでは既定値でtype = "circular"となっており循環畳み込みになっていること、通常の線形畳み込みをするためには2つ目に与えるベクトルをrev()関数で順番を逆にしなければならないのが気をつけなければならない点となる。

ちゃんとヘルプドキュメントを読んでないとrev()しないとnumpyの結果と一致しないことに混乱するし、ちゃんとヘルプドキュメントを読んだら読んだで線形畳み込みでもベクトルの長さ異なったらだめなの?と混乱する謎の展開。

ja.wikipedia.org

二項演算子とパイプのメモ

二項演算子とパイプ演算子の注意のメモ。

単純な例で考える。たとえば、2つのベクトルc(1, 2)c(3, 4)の積をとってそのベクトルの和をとる場合を考える。

sum(1:2 * 3:4)
## [1] 11

これで計算できる。このとき、1:2 * 3:4を計算してから|>%>%などのパイプをつかって、sum()に計算結果を渡して和を計算しようと考えた時に、次のようなコードを書くと意図した通りには計算できない。

1:2 * 3:4 |> sum()
## [1]  7 14
library(magrittr)
1:2 * 3:4 %>% sum()
## [1]  7 14

これは、3:4が先にsum()に渡されて、1:2 * sum(3:4)が実行されてしまうため。{lobstr}パッケージのast()関数をつかって確認してみる。

lobstr::ast(1:2 * 3:4 |> sum())
## █─`*` 
## ├─█─`:` 
## │ ├─1 
## │ └─2 
## └─█─sum 
##   └─█─`:` 
##     ├─3 
##     └─4
lobstr::ast(1:2 * 3:4 %>% sum())
## █─`*` 
## ├─█─`:` 
## │ ├─1 
## │ └─2 
## └─█─`%>%` 
##   ├─█─`:` 
##   │ ├─3 
##   │ └─4 
##   └─█─sum

つまり、

`*`(1:2, 3:4 |> sum())
## [1]  7 14
`*`(1:2, 3:4 %>% sum())
## [1]  7 14

という処理になっている。関数でカッコでくくられるとわかりやすい。

一応、{lobstr}ast()で確認してみる。

lobstr::ast(`*`(1:2, 3:4 |> sum()))
## █─`*` 
## ├─█─`:` 
## │ ├─1 
## │ └─2 
## └─█─sum 
##   └─█─`:` 
##     ├─3 
##     └─4
lobstr::ast(`*`(1:2, 3:4 %>% sum()))
## █─`*` 
## ├─█─`:` 
## │ ├─1 
## │ └─2 
## └─█─`%>%` 
##   ├─█─`:` 
##   │ ├─3 
##   │ └─4 
##   └─█─sum

この場合、パイプで繋ぐのをそのものやめるか(こっちのほうがいいと思う)、パイプを使いたかったら先に1:2 * 3:4を計算すればいいのでカッコでくくってやればいい。

(1:2 * 3:4) |> sum()
## [1] 11
(1:2 * 3:4)  %>%  sum()
## [1] 11

これくらい単純だとわかりやすく踏み抜きにくいと思うが、少し複雑になると注意が必要と思う。

{naniar}パッケージはナルニア国物語

{naniar}パッケージのアップデートが降ってきたときにNEWSを読んでいると変わったときどきのリリースに変わったコードネームが書かれていることに気付いた。

たとえば、0.5.0は“The End of this Story and the Beginning of all of the Others”で、なんだこの長いコードネーム?厨二病か?と思ってコードネームでググったらナルニア国物語のChapterや主要なイベントや人名(?)だとわかった。他のコードネームもナルニア国物語関連だった。

で、{naniar}のREADMEを読んだらA note on the nameの項でナルニア国物語にもじっていることが書いてあった。おしゃれー、と思った。

naniar.njtierney.com

Rの三項演算子的関数であるifelse()関数(と{dplyr}のif_else()、case_when()関数)で出る謎のwarningメッセージについて

Rでデータ分析をする際に、三項演算子的な処理を行いたい時に{base}ifelse()関数や{dplyr}パッケージのif_else()、もっと複雑な条件分岐をするときにはcase_when()関数を使うことがある。これらの関数を使っている時に意図しない、結果にも反映されない謎のwarningメッセージがでることがある(場合によっては謎のerrorで処理がストップする)。この謎のwarningメッセージはどいうもので、どういう原因で出てくるのかを、特にifelse()の内部処理を追いかけながら見ていく。謎warningメッセージと書いたが、実質仕様なので仕組みが分かれば謎ではなくなる。

まず謎のwarningメッセージについて例を示しておく。次のような場合、ifelse()などではwarningメッセージが表示される。

y <- seq(-2, 2, by = 0.5)
ifelse(y >= 0, sqrt(y), y)
## Warning in sqrt(y): 計算結果が NaN になりました

## [1] -2.0000000 -1.5000000 -1.0000000 -0.5000000  0.0000000  0.7071068  1.0000000
## [8]  1.2247449  1.4142136

warningメッセージ(警告)では「計算結果がNaNとなりました」と表示されているが、出力されている結果にはNaNがなく一見すると謎のwarningメッセージとなっている。こういった、出力された結果からみるとなにのことを行っているのかわからないwarningメッセージを謎のwarningメッセージとする。もし、この謎のwarningメッセージが出力される原因などを知っているのであれば以下本稿はそのことしか話をしていないので読む必要はまったくない。

次から具体的な例を上げながらみていく。

ifelse()の処理を追っていく

-2から2まで、0.5刻みの値のベクトルについて、これの各値の平方根を求めようと考えてみる。

-2から2までの0.5刻みの値のベクトルと、それを平方根を求めるsqrt()関数で処理すると次のような結果になる。

y <- seq(-2, 2, by = 0.5)
y
## [1] -2.0 -1.5 -1.0 -0.5  0.0  0.5  1.0  1.5  2.0
sqrt(y)
## Warning in sqrt(y): 計算結果が NaN になりました

## [1]       NaN       NaN       NaN       NaN 0.0000000 0.7071068 1.0000000
## [8] 1.2247449 1.4142136

warningメッセージで「計算結果がNaNになりました」と表示されているが、0未満の値は実数範囲で平方根を計算できないために警告がでている。

ちなみに本稿の本旨とは異なるが、計算範囲を複素数範囲まで拡張するためには与える値を複素数範囲で与えれば計算でき、warningメッセージは表示されない。

x <- complex(real = seq(-2, 2, by = 0.5))
x
## [1] -2.0+0i -1.5+0i -1.0+0i -0.5+0i  0.0+0i  0.5+0i  1.0+0i  1.5+0i  2.0+0i
sqrt(x)
## [1] 0.0000000+1.4142136i 0.0000000+1.2247449i 0.0000000+1.0000000i
## [4] 0.0000000+0.7071068i 0.0000000+0.0000000i 0.7071068+0.0000000i
## [7] 1.0000000+0.0000000i 1.2247449+0.0000000i 1.4142136+0.0000000i

ここで、0以上か0未満かで条件分けし0以上であれば平方根を取り、それ以外の場合は元々の値を返すようにしてみる(それ以外の場合の値をNAなどにしてもよいが、今回はちゃんと判別されて計算されていることを確認したいため元の値を返すようにしてみる)。

この場合、sapply()関数、if()else()関数を用いると次のように各値を0以上か0未満か判別して計算することができる。

sapply(y, function(y){
  if(y >= 0) sqrt(y)
  else y
})
## [1] -2.0000000 -1.5000000 -1.0000000 -0.5000000  0.0000000  0.7071068  1.0000000
## [8]  1.2247449  1.4142136

この場合は意図した通り、0未満は元々の値、0以上では各値の平方根の値が出力され、warningメッセージは出力されない。

ここで{base}ifelse()を使っても同じようなものが実現できそうに思える。ifelse()関数はエクセルのIF関数と似た引数で、ifelse(test, yes , no)testに論理値を返すオブジェクトつまり条件式など、yestestTRUEの場合に返す値、notestFALSEの場合に返す値を指定する。

今回の例をifelse()を使って単純に書くと次のようになる(冒頭と同じ)。

ifelse(y >= 0, sqrt(y), y)
## Warning in sqrt(y): 計算結果が NaN になりました

## [1] -2.0000000 -1.5000000 -1.0000000 -0.5000000  0.0000000  0.7071068  1.0000000
## [8]  1.2247449  1.4142136

これはwarningメッセージ(警告)がでるが、「計算結果がNaNになりました」と言われる一方で計算結果はちゃんと意図した通り、0未満では元々の値が帰ってきておりNaNという値は出力されていないので一見すると謎のwarningメッセージに見える。

これはifelse()がどういう処理をしているのか(ソースコードをざっと)みると判明する。今回追いかける処理の部分は次の通り。

ifelse <- function(test, yes, no){
# 中略
 ans <- test
  len <- length(ans)
  ypos <- which(test)
  npos <- which(!test)
  if (length(ypos) > 0L) 
      ans[ypos] <- rep(yes, length.out = len)[ypos]
  if (length(npos) > 0L) 
      ans[npos] <- rep(no, length.out = len)[npos]
  ans
}

今回のようなifelse(y >= 0, sqrt(y), y)のような単純な例であればここだけでifelse()関数は実行されている。

順に追っていく。

ans <- testansにはtestの結果、論理値のベクトルが格納される。今回の場合はFALSE, FALSE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TRUEが格納されている。次に、len <- lentgh(ans)で最終的に返す答えの長さを調べ、格納している(今回の場合は特に問題にならない)。

次に、ypos <- which(test)npos <- which(!test)について。which()関数は論理ベクトルのTRUEの場所インデックス、つまりベクトルの先頭を1としたときに先頭から何番目がTRUEかを示してくれる。つまり、ypos <- which(test)では5, 6, 7, 8, 9がyposに格納される。test!がついているnposはそれが逆転し、testの条件で考えると先頭を1としたときにに先頭から何番目がFALSEかを調べており、npos <- which(!test)では1, 2, 3, 4がnposに格納されている。

次の

if (length(ypos) > 0L) 
   ans[ypos] <- rep(yes, length.out = len)[ypos]
if (length(npos) > 0L) 
   ans[npos] <- rep(no, length.out = len)[npos]

では、length(ypos)length(npos)のそれぞれでyposnposの値の長さを確認し0より大きい場合にはその次に続く関数を実行することとなっている。今回はどちらも長さが0よりも大きいのでどちらも実行されている。

yposの長さが0よりも大きい場合に実行される、ans[ypos] <- rep(yes, length.out = len)[ypos]は、ansのインデックスがyposのところにyesの結果のインデックス(先頭からの位置)がyposの値で上書きされることとなる。今回、yposは5, 6, 7, 8, 9となっているので、ansの先頭から5, 6, 7, 8, 9番目の値が、yesの結果の先頭から5, 6, 7, 8, 9番目の値で上書きされる。rep()関数はyesの長さがtestの長さと異なる時に調整するもので、今回の場合yesは長さがtestと一致するため特に意味はない。

ここで、yessqrt(y)になっているので、すべてのsqrt(y)を評価しているので冒頭に示したようにwarningメッセージを出しつつ結果NaN, NaN, NaN, NaN, 0, 0.7071068, 1, 1.2247449, 1.4142136を返す。ここでコンソール上に表示される謎のwarningメッセージがでてくる。ansに格納するために使う値はyesの中でyposでインデックスがちゃんと指定されているのでNaNは取り出されない。

次にnposの長さが0よりも大きい場合に実行される、ans[npos] <- rep(no, length.out = len)[npos]では、ansのインデックスがnposのところにnoの結果のインデックス(先頭からの位置)がnposの値で上書きされることとなる。つまり、先程のans[ypos] <- rep(yes, length.out = len)[ypos]で上書きされなかった箇所がnoの値で置き換えられる。今回の例でnposは1, 2, 3, 4となっているので、ansの先頭から1, 2, 3, 4番目の値が、noの結果、今回の場合はyそのものなので-2, -1.5, -1, -0.5, 0, 0.5, 1, 1.5, 2の先頭から1, 2, 3, 4番目の値で上書きされることとなる。

そして作成されたansベクトルが最終的に結果として出力されることとなる。

上記のように処理を追うと、最終的な結果と関係しない謎のwarningがどこででているのかわかる。

その証拠に次のように意図的にwarningメッセージを2つ出すこともできる。

# 絶対値の平方根をとる
ifelse(y >= 0, sqrt(y), sqrt(-y))
## Warning in sqrt(y): 計算結果が NaN になりました

## Warning in sqrt(-y): 計算結果が NaN になりました

## [1] 1.4142136 1.2247449 1.0000000 0.7071068 0.0000000 0.7071068 1.0000000
## [8] 1.2247449 1.4142136

一見するとifelse()関数は条件に一致したものとそうでないものをを分けてから、その後の処理をしているように思えるが、実際にはyesnoのコードを評価後、条件に合わせてベクトルを作るというものになっている。yesnoを評価した時にwarningメッセージが出る場合には、出力結果に問題なくても謎のwarningメッセージが出ることがありえる。

{dplyr}ifelse()case_when()の場合を簡単に

ところで、ifelse()とほぼ同等の関数が{dplyr}であればif_else()関数としてある。

dplyr::if_else(y >= 0, sqrt(y), y)
## Warning in sqrt(y): 計算結果が NaN になりました

## [1] -2.0000000 -1.5000000 -1.0000000 -0.5000000  0.0000000  0.7071068  1.0000000
## [8]  1.2247449  1.4142136

と同じように謎warningメッセージがでる。

これも処理を簡単に追っていくと

if_else <- function(condition,
                    true,
                    false,
                    missing = NULL,
                    ...,
                    ptype = NULL,
                    size = NULL) {
# 中略
  values <- list(
    true = true,
    false = false
  )
# 後略  
                    }

truefalseをすべて評価している箇所があり、今回の場合はここですべてのyに対してsqrt(y)が実行されここでwarningメッセージが出ている。

同じく、case_when()も次のようになる。

dplyr::case_when(y >= 0 ~ sqrt(y),
                 .default = y)
## Warning in sqrt(y): 計算結果が NaN になりました

## [1] -2.0000000 -1.5000000 -1.0000000 -0.5000000  0.0000000  0.7071068  1.0000000
## [8]  1.2247449  1.4142136

これもこれまで同様に、途中処理の途中で、右辺(sqrt(y))が評価されているため、一見すると謎のwarningメッセージが表示されることとなる。

{dplyr}case_when()ではヘルプドキュメントのexamplesのところに、

case_when() evaluates all RHS expressions, and then constructs its result by extracting the selected (via the LHS expressions) parts. In particular NaNs are produced in this case:

と右辺すべて評価しているとかかれている(今回の-2から2までの平方根をとるという例はここにある)。

特に問題になる場合

今回の例のように、単純な例であれば出ているwarningメッセージはただ謎なだけで、「結果は問題ないが?」程度の疑問で終わる(warnigメッセージが出ているという気持ちの悪さは残る)がもう少し複雑な場合や、処理がerrorでストップする例がある場合には大きな問題になる。

例えば、次のように、0以上であれば与えられた値を返し、0未満errorを返すf()という関数を考え、ifelse()で値が0以上であればf()で処理した値、0未満の場合にはそのままの値を返すような処理を想定すると、次のようになる。

f <- function(x){
  sapply(x, function(x){
    if(x >= 0) x
    else stop("xは0以上でなければなりません")
  })
}
x <- -2:2
ifelse(x >= 0, f(x), x)
## Error in FUN(X[[i]], ...): xは0以上でなければなりません

yesf(x)が評価されているので、そこでerrorがでるとそこで処理がストップする。なんでどうしてerrorがでるのかわからない場合には、「?」となってしまうと思う。

この場合は、やはり単純に解決するためには、sapply()などを使って一つつづの値で処理ができるようにしていくのがいいと思う。

sapply(x, function(x){
  if(x >= 0) f(x)
  else x
})
## [1] -2 -1  0  1  2
# ifelse()をこの中で使うこともできる
sapply(x, function(x) ifelse(x >= 0, f(x), x))
## [1] -2 -1  0  1  2

最後に

ifelse()if_else()case_when()関数で処理した後に文字列や任意の値にするなどの固定の値であれば、特に上記のような問題は起こらないと思う。

一方で、条件にマッチしたら値をその値を処理するなどの方法を行うと、気をつけていないと踏んでしまう問題だと思う。

上記のようにsapply()や他には{purrr}map()系関数を用いると回避できる。

第111回R勉強会@東京(#TokyoR)に参加(聴講)した

第111回R勉強会@東京(#TokyoR)に参加(初心者セッション・応用セッション・LT聴講した)した。

 

tokyor.connpass.com

 

初参加だったが、全体的な感想としては、「Rのことを日本語で喋っているのを聞くのは楽しいな」と思った。とても楽しい会だった。運営の方々のご尽力に感謝。

またオンライン開催なのは、現地参加が難しい状況ではとても嬉しい。

 

それぞれセッションの感想を思い出しながら雑記していく。

 

初心者セッション

初心者セッションは@y__mattu氏のR入門〜データハンドリング基礎と、@kilometer00氏の分散分析の基礎だった。

想定される初心者(対象)はRを初めて触るけどちょっと難しいなと感じている層(みたいなことを冒頭喋っていた気がする)のよう。

明確に話しはされていなかったと思うが、おそらく、想定対象者層としては大学等で全体講義としてRに触れる機会があったが、各講座(ゼミ)で実際にRでなにか統計処理しようと思った時に「はて、困ったな」というくらいのレベルなのかなと思った。

どういう情報をどういう粒度で限られた時間内で伝えるのか、そういうところは難しそうだなと思った。また、膨大な情報を伝える時に対面式じゃないので、聴講者の反応がわからないのでどこを重点的に説明したら良いのか、ここは少し説明を省いても良いのかが難しそうだと思った。

 

応用セッション

@paithiov909氏のRを使って短歌の「詩的度」を測るは、興味深いアプローチだなと思った。極めて質的なものを測るというところ、それをRを使って行っているのは面白いと感じた。

あと、日本語の文節区切りを{stringi}パッケージの関数で実行するというものが途中で紹介されており、{stringi}すごすぎじゃないか?と思った。tokenizer積んでるんだろうか?{stringi}パッケージは文字列処理でできることがかなり多くみんな使いこなせてるの、すごいなといつもびっくりする。

ところで全然関係ないが、{stringi}パッケージのアップデートが降ってくる時に、ソースからコンパイルするとかなり時間がかかるのは自環境が貧弱だからだろうか?

 

@Atsushi776氏のNeovimでR言語をやってこうとしてる話はRのエディタをNeovimとしてやっていこういう話だった。いろいろなエディタを試して旅をしている人を見るとすごいなと思うし、そういう人の話を聞いたり記事を読むのは楽しい。「なんたってかっこいい。」たしかに……。

自分はRを書くときにはRStudioを使っているが、「ここがもう少し◯◯だったら」と思うこともままあるが、つるしの状態でかなりRに最適化されているRStudioは離れがたい。ユーザー数も多く、自分がハマったことは大抵誰かもハマっていて調べると解決策が出てきたりする確率が高いのもいいと思ってる。

そんなこと思ったら、昔、Linuxディストリビューションの旅をしていた時に、いろいろなディストリビューションを試した結果、最終的につるしの状態でいい感じで比較的ユーザー数も多いUbuntuに落ち着いていたことを思い出した(環境壊して再インストールをしばしばしていたというのもある)。Gentooとか使えればかっこいいなと思うが、使い続けることやら考えたら自分はなんとなくメジャーどころに落ち着いてしまうんだなぁ、と自分を振り返った。

 

@yutakanzawa氏のggplot2のアノテーション技法はggplot2にどうアノテーションをつけるか、注意点などの話だった。アノテーションのつけすぎに注意という話があった。たしかにアノテーション・修飾に凝り始めると細かいところの調整に時間を取られ、グラフそのものはできているのに本質的じゃない作業に没頭しがちだと思った。一旦出力してプレゼンテーションソフトでアノテーション付けたらいいんじゃないかという話もあった。

確かに、どこまでグラフをggplot2やR内でこだわるのかと言うのは作業量を変える問題になると思う。

ただ個人的には時間が許す限りにおいては、できる範囲でggplot2内で完結したくて、そのひとつはggplot2で作成したグラフを出力したものにあとからプレゼンテーションソフトなどでいろいろつけると少し解像度が違ったりして若干違和感を感じることがあることが理由としてはある。もう一つの理由としては、後で同じようなグラフを書く時に、アノテーション系も同じように出力するためにはggplot2で完結していないと後で同じ様なグラフを作る時に、コードを再利用しても最後のところで手作業が発生するのが少し嫌だなと思うから。

 

LT

@KunisatoY氏のpsyinfrでらくらく研究室運営は、RとGitHubをつかって研究フローの研究室内でのお約束をみんな同じようにできるようにしようという取り組みだった。良い取り組みだなと思ったけど、実運用的には前年度はよかったが今年度はいまいちだったみたいな話だった。

複数人のフローの標準化って難しいよねって思った。

 

@florets1氏の請求と支払を照合する技術は2つのデータフレームに別れた請求と支払いのデータを{dplyr}パッケージのjoin_*()関数でガッチャンコする話だった。join_*()関数便利だよね、と思った。

 

@u_ribo氏のmandaRa: R言語の悟りのための修行の道場は新しいRの日本語の情報をまとめたウェブページを作ろうという話だった。r-wakalangは登録しているユーザーしか中の情報にアクセスできない上に、一定期間で情報が消えてしまう問題をかかえており、RjpWikiもやや情報として古い傾向にあるので新しいRの情報をまとめたウェブページを作ろうという話のよう。

確かに散逸しがちな情報が一箇所にまとまっているとアクセスしやすいかと思う。また、インターネット上でオープンに情報が見られる状態というのはいいと思う。検索エンジンで日本語で検索してわかることが増えるのはいいことだと思う。ヘルプドキュメントや公式ドキュメント、Stack Overflowを見ればいいとかあるが、やはり日本語で情報が読めるというのは大事だと思う。ただ、wiki的にみんなで情報集めて(記事書いて)作っていいきましょうというといふうだけどもどういう粒度のどういう情報を集めていくのかなーと思った。とてもいい試みで応援したいが、特に手持ちもない、というか詳しい人が記事を書いているスタイル?なので特に書けそうなこともないしとりあえず見守っていこうかと思った。

 

@bob3bob3氏の『データ可視化学入門』をPythonからRに翻訳した話は、Pythonでのグラフ作成コードをRに翻訳したとう話で、AIに翻訳を協力してもらったというところが、現代の潮流に乗ってるなと思った。また、AIの出したコードを確認して、Rっぽさを入れていく作業があったというのは興味深かった。

 

その他

オンライン懇親会がどういう感じなのかわからなくて、特に喋れることもないしなー懇親会怖っとなって懇親会には参加しなかった。

 

あの記事を書いている人の声ってこんな感じなんだ、というか、当たり前だけど実在する人間なんだなという、謎の感想を抱いた。

周りにRについて話をする人がいないので、Rについて話をしている人の話を聞くだけで楽しかった。

 

事情があってスマホからの参加で、スマホのZoomを使いこなせなかったのもあるが、自分が悪いのだがチャットに貼られた資料や最後のアンケートのURLを拾えなかった。

この大SNS時代にX(旧Twitter)をやっていないので、X検索も少し手間取るので、資料をCompassから参照できるようにしてくれるか、URL等をCompass上に記載してくれると嬉しさ倍増だと思った。

CRANリポジトリからパッケージが多くremoveされる日を調べてみた

RのパッケージはしばしばCRANリポジトリからremoveされArchivesとなる(CRANのArchivesには、removeされたパッケージだけではなくパッケージがアップデートされた時に以前のパッケージもある)。

CRANリポジトリからremoveされるのは、多くの場合はCRANが定期的に行っているパッケージのR CMD checkの結果ERRORとなり、更新がなされない場合にCRANからremoveされArchiveとなる。特にR本体のマイナーバージョン以上のアップデートでしばしば起こるとのことで、コードそのものよりもRのPolicyや細かい挙動の変更に伴って弾かれてしまっている例があると考えている。

ところでどういうタイミングでパッケージがremoveされているのかというのが気になった。パッケージがremoveされることが多い日があるのか、など見てみたいと思った。

CRANリポジトリftpサーバでも提供されており、

ここの中身をみるとArchivesとなっているパッケージのリストや、更新日がわかる。現在CRANリポジトリで利用できないパッケージのArchivesの更新日はCRANリポジトリからパッケージがremoveされた日になっている。この情報と、現在利用可能なパッケージのリストをつきあわせると、CRANリポジトリからremoveされたパッケージのリストを抽出できる。

ということでまずは使うデータセットを作っていく。

# {tiryvese}をいろいろ使って読み込んでおく(全部は使わない)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# Archivesにあるパッケージのリストのデータセットを作る
pkg_arc <- read.table(curl::curl("ftp://cran.r-project.org/pub/R/src/contrib/Archive/"), stringsAsFactors = FALSE)
pkg_arc <- pkg_arc |> 
  # 236はパッケージではないREDEMEディレクトリ
  filter(V5 != 236) |>
  select(Package = V9, M = V6, D = V7, Y = V8) |>
  mutate(Y = case_when(!str_detect(Y, ":") ~ as.numeric(Y),
                       M %in% month.abb[1:month(Sys.Date())] ~ year(Sys.Date()),
                       .default = year(Sys.Date()) - 1)) |> 
  mutate(Date = as_date(mapply(paste0, Y, M, D)))
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `Y = case_when(...)`.
## Caused by warning:
## !  強制変換により NA が生成されました
# 現在CRANで利用可能なすべてのパッケージのリストのデータセットを作る
# available.package()は現在の環境(RバージョンとOS)で利用可能なものとなるのですべてのパッケージではない
pkg_db <- tools::CRAN_package_db()

ところで、{tools}CRAN_package_db()では一部パッケージ(名)の重複がある。具体的には次のパッケージ(名)が重複して抽出されている。

pkg_db$Package[duplicated(pkg_db$Package)]
##  [1] "boot"       "class"      "cluster"    "codetools"  "foreign"   
##  [6] "KernSmooth" "lattice"    "MASS"       "Matrix"     "mgcv"      
## [11] "nlme"       "nnet"       "rpart"      "spatial"    "survival"

重複しているものはひとつを除外すればいい(最初に出てくる方だけ残す)。

pkg_db <- pkg_db[!duplicated(pkg_db$Package), ]

Archivesにあるパッケージ数、現在CRANから利用可能なパッケージ数、archivesにのみあるパッケージ(つまりCRANからremoveされたパッケージ)を数、現在利用可能なパッケージでarchivesにないパッケージ(初版のみのパッケージ)数を確認してみる。

# archivesにあるパッケージ数
n_arc <- length(pkg_arc$Package)
n_arc
## [1] 22408
# 現在利用可能なパッケージ数
n_db <- length(pkg_db$Package)
n_db
## [1] 20443
# archivesにのみあるパッケージ(CRANからremoveされたパッケージ)数
n_only_arc <- length(pkg_arc$Package[! pkg_arc$Package %in% pkg_db$Package])
n_only_arc
## [1] 6326
# 現在利用可能なパッケージでarchivesにないパッケージ(初版のみのパッケージ)数
n_only_db <- length(pkg_db$Package[! pkg_db$Package %in% pkg_arc$Package])
n_only_db
## [1] 4361

ところで、removeされた後にCRANに再登録されたパッケージ等はここではCRANで利用可能なパッケージとしてカウントされている。6326ものパッケージがCRANからremoveされていることがわかる。

一応検算しておく。

# 検算
# 現在利用可能なパッケージ数 = archivesにあるパッケージ数 - archivesにのみあるパッケージ数 + まだarchivesされていないパッケージ数
n_db
## [1] 20443
n_arc - n_only_arc + n_only_db
## [1] 20443

ここからいつパッケージがremoveされたのかをみていく。CRANからremoveされたパッケージだけのデータセットを作る。

# removeされたパッケージだけのデータセットを作る
pkg_remove <- pkg_arc[! pkg_arc$Package %in% pkg_db$Package, ]

年ごとにどのくらいパッケージがremoveされているかを見てみる。

# 年ごと
pkg_remove_year <- pkg_remove |> 
  summarise(n = n(), .by = Y) |> 
  complete(Y = full_seq(Y, 1)) |> 
  replace_na(list(n = 0))
# 年ごと可視化
ggplot(data = pkg_remove_year, aes(x = Y, y = n)) +
  geom_line()

2022年に最も多くのパッケージがremoveされていることがわかる。

日付ごとに見てみる。

# 日付ごと
pkg_remove_day <- pkg_remove |> 
  summarise(n = n(), .by = Date) |> 
  complete(Date = full_seq(Date, 1)) |> 
  replace_na(list(n = 0))
# 日ごと可視化
ggplot(data = pkg_remove_day, aes(x = `Date`, y = n)) +
  geom_line()

コンスタントにremoveされているのではなくある程度波がある様子。おそらく、CRAN Policyの変更、R CMD checkで使われるライブラリやRのバージョンの変更のタイミングなどに合わせてremoveの嵐が吹き荒れるのかと思う。ただ、これらがいつ変更になったのかのログを見つけることができなかったのでこれらが一致しているのかは精査はできていない。

CRANからremoveされたパッケージ数が多い日トップ10をみてみる。head(11)としているのはArchivesの日付の最も古い日付けが2008年2月16日となっており、この日にまとめられているだけではないかと思うのでトップ10からは除外されるべきと思う。

# removeされたパッケージの多い日トップ10
arrange(pkg_remove_day, -n) |> 
  head(11)
## # A tibble: 11 × 2
##    Date           n
##    <date>     <int>
##  1 2022-05-09   204
##  2 2022-06-20   162
##  3 2023-08-18   115
##  4 2008-02-16    84
##  5 2020-02-19    65
##  6 2023-10-16    64
##  7 2022-04-27    61
##  8 2022-06-10    61
##  9 2023-08-19    54
## 10 2018-06-17    51
## 11 2022-06-13    47

となっている。1日に200以上のパッケージがremoveされるなんて結構な大嵐だと思う。15年以上の歴史でこの2022年5月9日だけでremoveされArchivesのみとなったパッケージ全体の3.2%がremoveされている。

ちなみに、removeされたパッケージとして2008年2月16日にArchivesに登録されたパッケージは次の通り。

pkg_arc$Package[pkg_arc$Date == min(pkg_arc$Date)]
##  [1] "AnalyzeIO"     "CoCoAn"        "DBI.RODBC"     "DBI.RPgSQL"   
##  [5] "Dopt"          "FLCore"        "FLEDA"         "FortranCallsR"
##  [9] "GLMMGibbs"     "GammaTest"     "GeneSOM"       "HTML"         
## [13] "InfNet"        "Java"          "Malmig"        "RGtk2.10"     
## [17] "RPgSQL"        "Rcmdr.HH"      "Rdbi"          "Rdbi.PgSQL"   
## [21] "RmSQL"         "Rnotes"        "Rstreams"      "SAGx"         
## [25] "bats"          "baymvb"        "climate.plot"  "colSums"      
## [29] "ctest"         "cxx"           "dseplus"       "ensemble"     
## [33] "event.chart"   "exactDistr"    "funfits"       "gRcox"        
## [37] "grasper"       "grid"          "haplo.score"   "hpower"       
## [41] "integrate"     "limma"         "lme"           "mclust1998"   
## [45] "meanscore"     "micEcdat"      "mscalib"       "msgcop"       
## [49] "multilm"       "multiv"        "netCDF"        "newallelic"   
## [53] "nlrq"          "nls"           "normix"        "npConfRatio"  
## [57] "nprq"          "phyloarray"    "pls.pcr"       "polymars"     
## [61] "ppr"           "ptproc"        "ratetables"    "rcompletion"  
## [65] "regexp"        "roblm"         "runStat"       "seao"         
## [69] "seao.gui"      "serialize"     "splines"       "spsarlm"      
## [73] "sptests"       "spweights"     "stataread"     "survival4"    
## [77] "survival5"     "syskern"       "tapiR"         "tcltk"        
## [81] "timeslab"      "twostage"      "vtcl"          "write.snns"   
## [85] "zmatrix"

一番パッケージがremoveされた2022-05-09は次の通り。

# 一番多い日にremoveされたパッケージ
pkg_arc$Package[pkg_arc$Date == head(arrange(pkg_remove_day, -n), 1)$Date]
##   [1] "AMAP.Seq"                    "AMOEBA"                     
##   [3] "ASPBay"                      "AllPossibleSpellings"       
##   [5] "BAEssd"                      "BCellMA"                    
##   [7] "BayHaz"                      "BaySIC"                     
##   [9] "CARS"                        "CISE"                       
##  [11] "CNOGpro"                     "COBRA"                      
##  [13] "CRAC"                        "CVTuningCov"                
##  [15] "CityPlot"                    "ConNEcT"                    
##  [17] "DBGSA"                       "DCGL"                       
##  [19] "DIME"                        "DSviaDRM"                   
##  [21] "Demerelate"                  "DescToolsAddIns"            
##  [23] "DunnettTests"                "EBASS"                      
##  [25] "ES"                          "FAMT"                       
##  [27] "FBN"                         "FactMixtAnalysis"           
##  [29] "Familias"                    "FitAR"                      
##  [31] "FlickrAPI"                   "GESTr"                      
##  [33] "GExMap"                      "GWG"                        
##  [35] "GeneF"                       "GeoDE"                      
##  [37] "HAPim"                       "HDMD"                       
##  [39] "HIest"                       "HarmonicRegression"         
##  [41] "HiLMM"                       "HyPhy"                      
##  [43] "IBDhaploRtools"              "ICE"                        
##  [45] "InfDim"                      "InteractiveIGraph"          
##  [47] "KANT"                        "LEAPFrOG"                   
##  [49] "Lambda4"                     "LocFDRPois"                 
##  [51] "MATTOOLS"                    "MC2toPath"                  
##  [53] "MConjoint"                   "MImix"                      
##  [55] "MInt"                        "Methplot"                   
##  [57] "MiST"                        "MuFiCokriging"              
##  [59] "NPHMC"                       "OOmisc"                     
##  [61] "PAGI"                        "PharmPow"                   
##  [63] "PlotRegionHighlighter"       "PriorCD"                    
##  [65] "R2DGC"                       "RAD"                        
##  [67] "RBitmoji"                    "REQS"                       
##  [69] "RIFS"                        "RSwissMaps"                 
##  [71] "RWebLogo"                    "RelValAnalysis"             
##  [73] "Rothermel"                   "Runiversal"                 
##  [75] "SEMrushR"                    "SQDA"                       
##  [77] "SUE"                         "ShinyImage"                 
##  [79] "SocialNetworks"              "SolveRationalMatrixEquation"
##  [81] "SpatialBall"                 "SportsAnalytics"            
##  [83] "TERAplusB"                   "TiddlyWikiR"                
##  [85] "UBCRM"                       "UScensus2000cdp"            
##  [87] "VBTree"                      "VIF"                        
##  [89] "VarBundle"                   "VarSwapPrice"               
##  [91] "Voss"                        "YaleToolkit"                
##  [93] "YieldCurve"                  "ZipRadius"                  
##  [95] "aGE"                         "akmeans"                    
##  [97] "asVPC"                       "audiolyzR"                  
##  [99] "barcode"                     "bbo"                        
## [101] "beadarrayFilter"             "benchden"                   
## [103] "betaBayes"                   "bolasso"                    
## [105] "bootspecdens"                "bvenn"                      
## [107] "cartools"                    "catspec"                    
## [109] "ccChooser"                   "cheb"                       
## [111] "citbcmst"                    "citccmst"                   
## [113] "ciuupi"                      "cleanerR"                   
## [115] "clinPK"                      "clusterGenomics"            
## [117] "coexist"                     "collectArgs"                
## [119] "compareODM"                  "coreTDT"                    
## [121] "cosmoFns"                    "countyfloods"               
## [123] "crn"                         "crrp"                       
## [125] "customsteps"                 "cytoDiv"                    
## [127] "datadigest"                  "deseasonalize"              
## [129] "dfexplore"                   "dielectric"                 
## [131] "displayHTS"                  "documair"                   
## [133] "dotdot"                      "dragracer"                  
## [135] "dupiR"                       "edcc"                       
## [137] "emme2"                       "epade"                      
## [139] "errorizer"                   "expose"                     
## [141] "exsic"                       "extfunnel"                  
## [143] "eyetracking"                 "fakeR"                      
## [145] "fam2r"                       "fcd"                        
## [147] "forward"                     "fts"                        
## [149] "gRapfa"                      "gendata"                    
## [151] "geneListPie"                 "genotypeR"                  
## [153] "glmdm"                       "hbm"                        
## [155] "hiPOD"                       "highD2pop"                  
## [157] "hornpa"                      "hqmisc"                     
## [159] "hsicCCA"                     "hsmm"                       
## [161] "imputeMDR"                   "infra"                      
## [163] "infutil"                     "interferenceCI"             
## [165] "intubate"                    "jointPm"                    
## [167] "knnIndep"                    "labelrank"                  
## [169] "loop"                        "lpme"                       
## [171] "lsa"                         "majesticR"                  
## [173] "marl"                        "mazeinda"                   
## [175] "mcll"                        "metaplotr"                  
## [177] "mgpd"                        "miRada"                     
## [179] "minxent"                     "mmm2"                       
## [181] "modiscloud"                  "msda"                       
## [183] "muma"                        "mut"                        
## [185] "myepisodes"                  "mztwinreg"                  
## [187] "ncg"                         "networksis"                 
## [189] "nlmeODE"                     "notifyR"                    
## [191] "optAUC"                      "orderbook"                  
## [193] "p2distance"                  "pacbpred"                   
## [195] "paleoMAS"                    "parboost"                   
## [197] "parcoords"                   "pathlibr"                   
## [199] "pcmabc"                      "phenability"                
## [201] "phyloland"                   "planar"                     
## [203] "plpoisson"                   "plsr"                       
## [205] "pmcgd"                       "pnmtrem"                    
## [207] "popKorn"                     "probFDA"                    
## [209] "probemod"                    "proteomicdesign"            
## [211] "pvsR"                        "qLearn"                     
## [213] "r511"                        "rateratio.test"             
## [215] "rcane"                       "relMix"                     
## [217] "reweight"                    "rfigshare"                  
## [219] "rgabriel"                    "ri"                         
## [221] "rpcdsearch"                  "rsurfer"                    
## [223] "rvTDT"                       "sBF"                        
## [225] "scar"                        "scenario"                   
## [227] "semdiag"                     "sensitivityPStrat"          
## [229] "sigmaNet"                    "skda"                       
## [231] "smcure"                      "snpStatsWriter"             
## [233] "spatialnbda"                 "sprsmdl"                    
## [235] "ssh.utils"                   "ssvd"                       
## [237] "subtype"                     "svSocket"                   
## [239] "symbols"                     "tiger"                      
## [241] "tmle.npvi"                   "truncgof"                   
## [243] "vrmlgen"                     "weco"                       
## [245] "wmlf"                        "xtune"

グラフが格納された、リストの入れ子を解消したい

入れ子forループでグラフを複数出力したものを、{gridExtra}パッケージのgrid.arrange()か何かで並べたい。解決策案の一つ、入れ子状態のlist形式のオブジェクトの入れ子状態を解消し実現する。

これを見て

qiita.com

すでに回答済みなので必要ないと思うが、自分のブログ記事が引用されているので別解を書いてみる(この質問のそのものを最近見つけた、質問そのものはクローズ済)。

解決策1.「forループの段階で入れ子にならないようにする」はすでに回答済みなので、解決策案2.として書かれている「後からリストの入れ子を解消する」という方法について書いていく。こちらは、{purrr}パッケージのlist_flatten()関数を用いると実現できる。

具体的に実行していく。まずはQittaの記事の例にならってplotlistにグラフを格納していく。

記事中に明示されていないが、{tidyvrese}パッケージを読んでいることが前提になっているようなので冒頭に呼び出しておく。

# Qittaの記事の通り
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.4.4     ✔ tibble    3.2.1
## ✔ lubridate 1.9.3     ✔ tidyr     1.3.1
## ✔ purrr     1.0.2     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
production.area <- rep(c("a", "b", "c"), times = 50 )
iris <- cbind(iris, production.area)
plotlist <- NULL
for(i in unique(iris$Species)){
  for(j in unique(iris$production.area)){
    (plotlist[[i]][[j]] <- iris %>%
       filter(Species == i) %>%
       filter(production.area == j) %>%
       ggplot(aes(x = Sepal.Length, y = Sepal.Width))+
       geom_point()+
       labs(title=paste0(i, "*", j))
     )
  }
}

これで、plotlistができる。plotlist入れ子状態のlist形式になっているので{gridExtra}パッケージのgrid.arrange()を使ってまとめてグラフを表示しようとすると、{ggplot2}で作成した図をリスト形式で受け取るgrobs引数にplotlistを渡してもリストが深いのでうまく受け取ってくれない。

library(gridExtra)
## 
##  次のパッケージを付け加えます: 'gridExtra'

##  以下のオブジェクトは 'package:dplyr' からマスクされています:
## 
##     combine
grid.arrange(grobs = plotlist)
## Error in gList(...):  "gList" 中には 'grobs' のみが許されます

どうしたらいいかというと、単純にlistを浅くすればいいので、list_flatten()関数でplotlist入れ子状態を解消するといい。{purrr}パッケージのlist_flatten()関数を使うと一つリストが浅くなってくれる。

# {purrr}は{tidyverse}を読み込んだ時に読み込み済み
grid.arrange(grobs = list_flatten(plotlist))

R本体やパッケージの引用情報を参照するcitation()関数について

R本体のRのパッケージの引用情報を参照する時に使うcitation()の役に立つかもしれないTIPS的なのものをまとめていく。だいたいヘルプドキュメントに書いてある。

引用情報を呼び出す

Rの本体の引用情報を一番簡単に取り出すには次のようにするとできる。

citation()
## To cite R in publications use:
## 
##   R Core Team (2023). _R: A Language and Environment for Statistical
##   Computing_. R Foundation for Statistical Computing, Vienna, Austria.
##   <https://www.R-project.org/>.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Manual{,
##     title = {R: A Language and Environment for Statistical Computing},
##     author = {{R Core Team}},
##     organization = {R Foundation for Statistical Computing},
##     address = {Vienna, Austria},
##     year = {2023},
##     url = {https://www.R-project.org/},
##   }
## 
## We have invested a lot of time and effort in creating R, please cite it
## when using it for data analysis. See also 'citation("pkgname")' for
## citing R packages.

これは既定値でcitation(package = "base")が実行されるため。

パッケージの引用情報を呼び出す場合は、パッケージ名を入力する。例えば{ggplot2}パッケージの場合は次のようにする。

citation("ggplot2")
## To cite ggplot2 in publications, please use
## 
##   H. Wickham. ggplot2: Elegant Graphics for Data Analysis.
##   Springer-Verlag New York, 2016.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Book{,
##     author = {Hadley Wickham},
##     title = {ggplot2: Elegant Graphics for Data Analysis},
##     publisher = {Springer-Verlag New York},
##     year = {2016},
##     isbn = {978-3-319-24277-4},
##     url = {https://ggplot2.tidyverse.org},
##   }

このとき、パッケージ内にCITATIONファイルがある場合には上記のようにCITATIONファイルの情報が呼び出させる({ggplot2}パッケージはCITAITONファイルがある)。CITATIONファイルがない場合はDESCRIPTIONファイルから所定の情報が呼び出される。例えば、CITATIONファイルのないパッケージの例として{zipangu}パッケージの例は次のとおりになる。

citation("zipangu")
## 'zipangu' パッケージの引用には以下を用いてください:
## 
##   Uryu S (2022). _zipangu: Japanese Utility Functions and Data_. R
##   package version 0.3.2, <https://CRAN.R-project.org/package=zipangu>.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Manual{,
##     title = {zipangu: Japanese Utility Functions and Data},
##     author = {Shinya Uryu},
##     year = {2022},
##     note = {R package version 0.3.2},
##     url = {https://CRAN.R-project.org/package=zipangu},
##   }

ここで、CITAITONファイルがあるが、DESCRIPTIONファイルから呼び出した情報の引用情報がほしい場合(例えばバージョン情報などが必要な場合)には、auto = TRUEとするとDESCRIPTIONファイルの内容からの情報が出力される。

citation("ggplot2", auto = TRUE)
## 'ggplot2' パッケージの引用には以下を用いてください:
## 
##   Wickham H, Chang W, Henry L, Pedersen T, Takahashi K, Wilke C, Woo K,
##   Yutani H, Dunnington D (2023). _ggplot2: Create Elegant Data
##   Visualisations Using the Grammar of Graphics_. R package version
##   3.4.4, <https://CRAN.R-project.org/package=ggplot2>.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Manual{,
##     title = {ggplot2: Create Elegant Data Visualisations Using the Grammar of Graphics},
##     author = {Hadley Wickham and Winston Chang and Lionel Henry and Thomas Lin Pedersen and Kohske Takahashi and Claus Wilke and Kara Woo and Hiroaki Yutani and Dewey Dunnington},
##     year = {2023},
##     note = {R package version 3.4.4},
##     url = {https://CRAN.R-project.org/package=ggplot2},
##   }

なお、参照できる引用情報はインストールされているパッケージに限る。インストールしていないパッケージの引用情報はcitation()関数では参照できない。

citation("MISS")
## Error in citation("MISS"):  'MISS' という名前のパッケージはありません

ちなみに、CITATIONファイルがパッケージにあるかどうかは、system.file()で簡単にわかる。

# CITAITONファイルがある場合(CITAITONファイルのパスが返ってくる)
system.file("CITATION", package = "ggplot2")
## [1] "C:/Users/User_name/AppData/Local/R/win-library/4.3/ggplot2/CITATION"
# CITATIONファイルがない場合(””が返ってくる)
system.file("CITATION", package = "zipangu")
## [1] ""

引用情報の表示スタイルを調整する

citation()関数のヘルプドキュメントには詳細な記載はないが、bibenty()関数のヘルプドキュメントに記載がある通り、print()関数またはformat()関数のstyle引数を指定することで表示するスタイルを調整することができる。表示できるバリエーションはtext, Bibtex, citation, html, latex, textVersion, Rとなっている。

それぞれ、{ggplot2}パッケージ(規定値でCITAITONファイルを読み込んだ場合)のprint()関数での表示は次のとおりとなっている。

# citaitonをggplot2_citaitonとオブジェクトにいれておく
# print(ciatiton("ggplot2"), style = "text")などでも実行できる
ggplot2_citation <- citation("ggplot2")
print(ggplot2_citation, style = "text")
## Wickham H (2016). _ggplot2: Elegant Graphics for Data Analysis_.
## Springer-Verlag New York. ISBN 978-3-319-24277-4,
## <https://ggplot2.tidyverse.org>.
print(ggplot2_citation, style = "Bibtex")
## @Book{,
##   author = {Hadley Wickham},
##   title = {ggplot2: Elegant Graphics for Data Analysis},
##   publisher = {Springer-Verlag New York},
##   year = {2016},
##   isbn = {978-3-319-24277-4},
##   url = {https://ggplot2.tidyverse.org},
## }
print(ggplot2_citation, style = "citation")
## To cite ggplot2 in publications, please use
## 
##   H. Wickham. ggplot2: Elegant Graphics for Data Analysis.
##   Springer-Verlag New York, 2016.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Book{,
##     author = {Hadley Wickham},
##     title = {ggplot2: Elegant Graphics for Data Analysis},
##     publisher = {Springer-Verlag New York},
##     year = {2016},
##     isbn = {978-3-319-24277-4},
##     url = {https://ggplot2.tidyverse.org},
##   }
print(ggplot2_citation, style = "html")
## <p>Wickham H (2016).
## <em>ggplot2: Elegant Graphics for Data Analysis</em>.
## Springer-Verlag New York.
## ISBN 978-3-319-24277-4, <a href="https://ggplot2.tidyverse.org">https://ggplot2.tidyverse.org</a>. 
## </p>
print(ggplot2_citation, style = "latex")
## Wickham H (2016).
## \emph{ggplot2: Elegant Graphics for Data Analysis}.
## Springer-Verlag New York.
## ISBN 978-3-319-24277-4, \url{https://ggplot2.tidyverse.org}.
print(ggplot2_citation, style = "textVersion")
## H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016.
print(ggplot2_citation, style = "R")
## bibentry(bibtype = "Book",
##          textVersion = "H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016.",
##          header = "To cite ggplot2 in publications, please use",
##          author = person(given = "Hadley",
##                          family = "Wickham"),
##          title = "ggplot2: Elegant Graphics for Data Analysis",
##          publisher = "Springer-Verlag New York",
##          year = "2016",
##          isbn = "978-3-319-24277-4",
##          url = "https://ggplot2.tidyverse.org")

citation()で作られるオブジェクトは既定値ではstyle = "citation"で実行されている。使いたい表示形式のものを指定して表示すればば良い。

format()関数の場合は次のようになる。

format(ggplot2_citation, style = "text")
## [1] "Wickham H (2016). _ggplot2: Elegant Graphics for Data Analysis_.\nSpringer-Verlag New York. ISBN 978-3-319-24277-4,\n<https://ggplot2.tidyverse.org>."
format(ggplot2_citation, style = "Bibtex")
## [1] "@Book{,\n  author = {Hadley Wickham},\n  title = {ggplot2: Elegant Graphics for Data Analysis},\n  publisher = {Springer-Verlag New York},\n  year = {2016},\n  isbn = {978-3-319-24277-4},\n  url = {https://ggplot2.tidyverse.org},\n}"
format(ggplot2_citation, style = "citation")
## [1] ""                                                                                                                                                                                                                                                                                                                                                                                                                                                      
## [2] "To cite ggplot2 in publications, please use\n\n  H. Wickham. ggplot2: Elegant Graphics for Data Analysis.\n  Springer-Verlag New York, 2016.\n\n LaTeX ユーザのための BibTeX エントリーは \n\n  @Book{,\n    author = {Hadley Wickham},\n    title = {ggplot2: Elegant Graphics for Data Analysis},\n    publisher = {Springer-Verlag New York},\n    year = {2016},\n    isbn = {978-3-319-24277-4},\n    url = {https://ggplot2.tidyverse.org},\n  }"
## [3] ""
format(ggplot2_citation, style = "html")
## [1] "<p>Wickham H (2016).\n<em>ggplot2: Elegant Graphics for Data Analysis</em>.\nSpringer-Verlag New York.\nISBN 978-3-319-24277-4, <a href=\"https://ggplot2.tidyverse.org\">https://ggplot2.tidyverse.org</a>. \n</p>"
format(ggplot2_citation, style = "latex")
## [1] "Wickham H (2016).\n\\emph{ggplot2: Elegant Graphics for Data Analysis}.\nSpringer-Verlag New York.\nISBN 978-3-319-24277-4, \\url{https://ggplot2.tidyverse.org}."
format(ggplot2_citation, style = "textVersion")
## [1] "H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016."
format(ggplot2_citation, style = "R")
## [1] "bibentry(bibtype = \"Book\",\n         textVersion = \"H. Wickham. ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York, 2016.\",\n         header = \"To cite ggplot2 in publications, please use\",\n         author = person(given = \"Hadley\",\n                         family = \"Wickham\"),\n         title = \"ggplot2: Elegant Graphics for Data Analysis\",\n         publisher = \"Springer-Verlag New York\",\n         year = \"2016\",\n         isbn = \"978-3-319-24277-4\",\n         url = \"https://ggplot2.tidyverse.org\")"

改行情報が\nで表記される。これはいろいろな用途が考えられるが例えば、ファイルに書き込んだりする時にwriteLines()などを使うときやcat()関数やmessage()関数など\nを改行として解釈する関数に渡すときなどに使える。

また、print()関数では出力スタイルが変わっているだけでオブジェクトそのものはリスト形式のままだが、format()関数だと任意のスタイルの文字列に変えることができる。

x_print <- print(ggplot2_citation, style = "text")
## Wickham H (2016). _ggplot2: Elegant Graphics for Data Analysis_.
## Springer-Verlag New York. ISBN 978-3-319-24277-4,
## <https://ggplot2.tidyverse.org>.
x_print
## To cite ggplot2 in publications, please use
## 
##   H. Wickham. ggplot2: Elegant Graphics for Data Analysis.
##   Springer-Verlag New York, 2016.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Book{,
##     author = {Hadley Wickham},
##     title = {ggplot2: Elegant Graphics for Data Analysis},
##     publisher = {Springer-Verlag New York},
##     year = {2016},
##     isbn = {978-3-319-24277-4},
##     url = {https://ggplot2.tidyverse.org},
##   }
x_format <- format(ggplot2_citation, style = "text")
x_format
## [1] "Wickham H (2016). _ggplot2: Elegant Graphics for Data Analysis_.\nSpringer-Verlag New York. ISBN 978-3-319-24277-4,\n<https://ggplot2.tidyverse.org>."

必要な情報だけを取り出す

引用情報のうち一部だけが必要となることがある。citaiton()で作られるオブジェクトはリスト形式なので、必要な中身を$などで取り出すことができる。

citation("ggplot2")$title
## [1] "ggplot2: Elegant Graphics for Data Analysis"
# オブジェクトでもっている場合
ggplot2_citation <- citation("ggplot2")
ggplot2_citation$title
## [1] "ggplot2: Elegant Graphics for Data Analysis"

複数の引用情報を取り出す

citation()はそのままでは複数のパッケージを指定し、引用情報を取り出すことができない。

citation(c("zipangu", "ggplot2"))
## Error in system.file(package = package, lib.loc = lib.loc):  'package' は長さ 1 でなければなりません

そこで、{purrr}パッケージのmap()関数や、lapply()などの関数を用いることで複数の文献の引用情報をまとめて出力することができる。

# {purrr}のmap()を使う
purrr::map(c("zipangu", "ggplot2"), citation)
## [[1]]
## 'zipangu' パッケージの引用には以下を用いてください:
## 
##   Uryu S (2022). _zipangu: Japanese Utility Functions and Data_. R
##   package version 0.3.2, <https://CRAN.R-project.org/package=zipangu>.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Manual{,
##     title = {zipangu: Japanese Utility Functions and Data},
##     author = {Shinya Uryu},
##     year = {2022},
##     note = {R package version 0.3.2},
##     url = {https://CRAN.R-project.org/package=zipangu},
##   }
## 
## [[2]]
## To cite ggplot2 in publications, please use
## 
##   H. Wickham. ggplot2: Elegant Graphics for Data Analysis.
##   Springer-Verlag New York, 2016.
## 
##  LaTeX ユーザのための BibTeX エントリーは 
## 
##   @Book{,
##     author = {Hadley Wickham},
##     title = {ggplot2: Elegant Graphics for Data Analysis},
##     publisher = {Springer-Verlag New York},
##     year = {2016},
##     isbn = {978-3-319-24277-4},
##     url = {https://ggplot2.tidyverse.org},
##   }

ここで、text形式でlist形式ではなくベクトル形式で引用情報を持っておきたいと持ったら、例えば次のようにするとできる。format()で取り出すと、改行が\nとなるのでこれをスペースで置換しておく。改行情報を持っておきたかったらそのままにしておけばいい。

purrr::map_chr(c("zipangu", "ggplot2"), ~ citation(.x) 
               |> format(style = "text") 
               |> gsub(pattern = "\\n", replacement = " ", x = _))
## [1] "Uryu S (2022). _zipangu: Japanese Utility Functions and Data_. R package version 0.3.2, <https://CRAN.R-project.org/package=zipangu>."              
## [2] "Wickham H (2016). _ggplot2: Elegant Graphics for Data Analysis_. Springer-Verlag New York. ISBN 978-3-319-24277-4, <https://ggplot2.tidyverse.org>."

RのパッケージのREADMEを読む方法を考える(READMEをvignetteにするなど)

Rでパッケージを使っていると、関数の使い方やパッケージの目的などを調べたくなる時がある。そんな時に関数のヘルプドキュメントは重要な情報源となるが、パッケージのREAEMEも大いに役に立つ。

READMEとはパッケージの概要や簡単な使い方などを説明したドキュメントのことで、GitHubで公開されているパッケージならリポジトリのトップに表示されている。CRANでもREAME.mdがあるパッケージではパッケージのページにREADMEの項目があり、参照することができる。

大いに役に立つREADMEながら、READMEを参照するためにはパッケージのウェブページ(CRANやGitHubなどの)にアクセスする必要があり、通常はウェブ検索等でパッケージ名検索しREADMEの項目にアクセスする必要がある。

しかし、ブラウザを立ち上げて検索かけるという手間があり、やや不便である。また、"パッケージ名 R package"と検索しても上位に当該パッケージのウェブページが表示されないため、少し探すのに手間取ることがある。

そこで、パッケージのREADMEを簡便に読みたいと思い、方法を考えた。

方法としては

  • READMEがある(ありそうな)ウェブページにアクセスする関数を作ってREADMEにウェブブラウザをつかってアクセスする。

  • パッケージをインストールする時にREAME.mdの内容をvignetteにコピーする処理を行い、vignette()関数を用いてREAMEを参照できるようにする。

というものを考えた。後者のパッケージをインストール中にREAEME.mdをvignetteにする処理を行う方は現在のところGitHubで公開されているパッケージでしか実行できない(が、CRANからソースからのインストールであれば似たような処理で仕組み上は工夫すればできそうだと思う)。

(追記)CRANリポジトリに公開されているパッケージでもパッケージをインストールする時にREAME.mdの内容をvignetteにコピーする処理を行い、vignette()関数を用いてREAMEを参照できるようにする処理をする関数を追加した。後述。(追記終了)

READMEがある(ありそうな)ウェブページにアクセスする関数を作ってREADMEにウェブブラウザをつかってアクセスする

CRANからインストールされたパッケージであれば、CRANのパッケージのウェブページ上にREADMEがあるはず(なければない)。GitHubからインストールされたパッケージであれば、パッケージをインストールしたリポジトリのページにREADMEがあるはず(なければない)。

ということで、パッケージ名を入力するとインストールされているパッケージのDESCRIPTIONを参照し、CRANからインストールされたのかGitHubからインストールされたのかを判別して、それぞれのページにブラウザでアクセスする関数を作った。

readme <- function(package){
  if(length(package) != 1) stop("Only one package name can be specified.")
  if(!is.character(package)) stop("The package name must be specified as a string.")
  repository_info <- utils::packageDescription(package)[c("Repository", "GithubRepo", "GithubUsername")]
  if(!is.null(repository_info$Repository) && repository_info$Repository == "CRAN")
    utils::browseURL(paste0("https://cran.r-project.org/web/packages/",
                     package,
                     "/readme/README.html"))
  else if(!is.null(repository_info$GithubRepo))
    utils::browseURL(paste0("https://github.com/",
                     repository_info$GithubUsername,
                     "/",
                     repository_info$GithubRepo))
  else stop("Only packages installed from CRAN or GitHub are supported.")
}

この関数はGitHubで公開している{infun}パッケージに追加した。

github.com

これで、例えば{zipangu}パッケージをインストールしている場合、

readme("zipangu")

{zipangu}をCRANからインストールしている場合はCRAN上のREADMEのウェブページ、開発版等をGitHubからインストールしている場合はGitHubリポジトリのウェブページがブラウザで開かれる。

もしREADMEがない場合は表示できない。

パッケージをインストールする時にREAME.mdの内容をvignetteにコピーする処理を行い、vignette()関数を用いてREAMEを参照できるようにする

パッケージをインストールする時に、REAME.mdの内容をvignetteにREADMEとしてコピーする処理を行って、パッケージをインストールするようにする方法を考えた。

この方法はGitHub上で公開している{readme2vignette}install_github_with_readme()を使ってGitHub上で公開されているパッケージをインストールすると実現できる。

(追記)CRANリポジトリにあるパッケージも同様の処理でREAMEをvignetteにコピーするinstall_cran_with_readme(){readme2vignette}に追加した。(追記終了)

github.com

この方法では、README.mdの内容がvignetteとなるので、vignette("README", "パッケージ名")でREADMEをローカル環境で参照することができる。

install.packages("remotes")
remotes::install_github("indenkun/readme2vignette")
# 例えば、MissMechをインストールしてみる
readme2vignette::install_github_with_readme("indenkun/MissMech")

これで、

vignette("README", package = "MissMech")

でREADMEを参照することができる。

{readme2vignette}install_github_with_readme(){remotes}install_github()の処理の途中にREADME.mdの内容をvignette化する処理を挟んでいるだけ。

(追記){readme2vignette}install_cran_with_readme(){remotes}install_cran()の処理の途中にREADME.mdの内容をvignette化する処理を挟んでいる。インストールするパッケージは、途中で処理を挟むために、必ずソースパッケージからインストールする必要があり、バイナリパッケージからのインストールはできない。依存関係でインストールされるパッケージはその限りではない。(追記終了)

ただ、この方法だとパッケージのバージョンアップなどでinstall_github_with_readme()以外でパッケージの再インストールを行うと元々のパッケージがインストールされるのでREADMEのvignetteは消えてしまう。

{readme2vignette}install_github_with_readme()について

{remotes}install_github()は、GitHub上のパッケージをダウンロードしてインストールする関数で、その際にREADME.mdをvignetteにコピーしてくれるようにしたのが{readme2vignette}install_github_with_readme()である。

具体的な処理の流れは以下の通り。

  • README.mdがあるかどうか、vignetteディレクトリがないかどうかを確認する
  • DESCRIPTIONファイルに{knitr}{rmarkdown}パッケージをSuggestsに追加する
  • DESCRIPTIONファイルに{knitr}をVignetteBuilderとして追加する
  • vignetteディレクトリを作成し、README.Rmdを作成する
  • README.Rmdの中身はchild = "../README.md"とする
  • README.Rmd内で、man/figuresディレクトリがあれば、その中身をコピーする
  • install_github()build_vignettes = TRUEとしてvignetteをBuildする

これで、README.mdがない場合やvignetteがすでにある場合は、README.mdのvignette化をスキップし、それ以外のときにはREADME.mdの内容をそのままvignetteにREADMEとして反映させることができる。

ただし、この方法ではman/figuresディレクトリ以外にあるイメージファイルは参照されないので、イメージが欠落してしまう。

また、REAMDE.RmdではなくREADME.mdを参照しているためvignette中のRのコードはsourceで確認できないというデメリットがある。しかし、README.RmdではときどきパッケージのDESCRIPTIONに依存関係として記述されていないパッケージを用いているコードが用いられていたり、環境依存情報を用いている例があり、README.Rmdをレンダリングする時にパッケージ不足でうまく行かない可能性やもともとのREADME.mdとやや異なる結果になる可能性があることから、README.mdを参照することとしている。

雑記

Rのパッケージには、パッケージの目的や使い方を説明するドキュメントとしてREADMEとvignetteの二種類がある。READMEは、パッケージの概要や使い方などを説明したドキュメントで、ウェブページで見ることができる。vignetteは、パッケージの詳細な解説や実例などを含んだHTMLファイルまたはpdfファイルで、ローカル環境で見ることができます。

READMEとvignetteの関係については、パッケージ開発者やユーザーの間で意見が分かれており、READMEとvignetteの違いや役割について、概観しただけでも以下のような議論がある*1*2*3

  • READMEとvignetteのどちらが重要か
  • READMEとvignetteのどちらが優先されるべきか
  • READMEとvignetteのどちらが更新されやすいか
  • READMEとvignetteのどちらが読みやすいか
  • READMEとvignetteのどちらが分かりやすいか
  • READMEとvignetteのどちらが効率的か

これらの議論には一概に正解があるとは言えず、パッケージの目的や内容、開発者のスタイルや意図、ユーザーのニーズや環境などによると思う。

ところで実際のパッケージとしては、vignetteを備えずにREADMEしかないパッケージが多くあるように思っている。README.mdはソースパッケージに含まれて入るので、インストールするときもそのまま取り込んでくれればR本体でローカル環境でもREADMEを読めそうだと思うのだが……。

また、古いISSUEながら{usethis}にパッケージ開発時にREADMEの内容をvignetteにする関数が提案されたこともあったが、採用あいならずという感じのこともあった。

github.com

READMEの内容をヘルプドキュメントに入れている例もある({callr}パッケージなど)。

callr/R/callr-package.R at main · r-lib/callr · GitHub

もちろんちゃんとvignetteを書くべきという議論はありえるが、現状としてREADMEはあるがvignetteはないパッケージが多くあると思うので、なんとかしてREADMEが参照できるといいのではないかと思っている。