cucumber flesh

Rを中心としたデータ分析・統計解析らへんの話題をしていくだけ

質問お待ちしております!できればreprex使ってね 😸

最近、チームで分析プロジェクトを進行していることもあり、人のコードをみてコメントしたり、自分もみてもらう、というやりとりが増えてきました。メンバーの中で私は、どちらかといえば「R言語チョットデキル」人間で、時々発生するトラブルや質問について答えることもあります。

よくあるQ&A時の話ですが、回答に答える側になってみると質問してくる人に対してあれこれ言いたいことが出てきました。そんな気持ちを詠んだポエムです。Rの話になりますが、色々な言語で、強いてはプログラミング以外のことにでも言えるかもしれません。

Help me help you

一番に言いたいのは「 私はあなたの問題を解決したいと思っている 」ということ。個人的には、質問は自分の思いつかなかった手法や問題を提示してくれるので歓迎です(一プログラマとしての成長が望まれるので私にも見返りがある。感謝の気持ちもあります)。質問をためらわないでください(もちろん自力で解決できるに越したことはないし、落ち着いて見直したら・再度実行したら解決するもの、調べて済む問題は除く)。

問題に直面したあなたは、やや混乱しているかもしれません。ですが落ち着いて、問題がどこにあるのかを確認してみてください。文字の間違い、括弧や引用符の閉じ忘れ、などはよくある話です。そして質問してください。

あなたからの質問

注: ここでは、メールではなくてSlackのチャットやGitHub issuesでの質問を想定します。

質問はコードと一緒に

あなたからの質問が来ました。

「こんなエラーが出た」「これができない」

... お、おう。

これではちょっと困ってしまいます。あなたが何をしたいのか、何をしたら問題が発生してしまったのか、どこでつまづいているのか、詳細な情報とコードと共に示してくれると助かります。

個人的に、世の中、説明不足な質問で溢れているように感じます。説明が足りていないと、状況を把握しにくい、問題を突き詰めにくい、など悪いことが多いです。過多であることよりも不足していることが困ります。あれそれについての情報・状況を知らせてくださいと、あーだこーだ何度もやりとりを行うのはお互い良くないでしょう。最小限で問題が解決させるためには、あなたからの最初の情報提供が肝心です。

ありがちなパターン

  • 問題の途中から始めてしまう... 問題の箇所だけ、というのも困ります。ここでエラーになった、その情報は大事ですが、実は問題はもっと以前から、根本的な箇所で生じている可能性もあるのです。

コードはテキストで(ハイライトさせてくれるとさらに嬉しい)

次にあなたから実行したい処理の内容と問題の箇所を示したコードが示されました。みてみましょう。... ちょっと待って、もしかしてコンピュータの画面をスマートフォンで撮影した画像を貼り付けました? スクリーンキャプチャにしました? どうしてそんなことを!!!

コードを画像で撮影するのが好ましくないのは、画像をコピペしても問題の処理を再現することができないためです。再現、それは質問に答えるためにとても大事なことです。私が例え長年の経験と勘に優れたプログラマであったとしても、適当なコメントだけで質問に答えようとはしないでしょう。自分の考えが正しいかを確認するために、コードを自分の環境で試したいのです。しかし画像では、画像とエディタを相互に見比べながら画像に書かれている「コード」を書き起こす必要が生じてしまいます。ですので、コードは画像や文章ではなくて「コード」で示してください。ソースコードをファイルに保存してメールで添付してくれても構いません。

ソースコードですが、コピペで貼り付けました?それでも良い... い、いや、問題を説明している他の文章とは区別がつくようにしてください。お願いします。

ソースコードを記述するプログラミング言語は、数値と記号、文字列で表現されます。普通にみると見辛いです。これはあなたと一緒です。私たちはプログラミング言語を扱います。プログラミング言語自然言語と異なるのはあなたも知っていることでしょうが、SlackやGitHubではMarkdownと呼ばれるマークアップ言語(簡単な文字装飾機能と思ってください)が利用でき、その中にコードを他の文と区別させるための機能があります。

Creating and highlighting code blocks - User Documentation

加えて、シンタックスハイライトという文字に色をつける機能が用意されていて、これは多くの言語で利用できます。ソースコードの中の役割のようなものごとに異なる色が与えられ、その色を識別することで'なんとなく'読めるようになっているのです。普段シンタックスハイライトに慣れていると、装飾されていないコードを読むのは正直辛いのです。

Markdownでは` を三回繰り返しで示し、同じく ` の三回繰り返しで囲んだ部分がコードブロックとして扱われます。コードブロックをハイライトさせるために例えばRであれば次のようにします。

```r
library(reprex)

x <- mean(c(1, NA, 3:4))
mean(x)
# [1] NA
```

pythonであればrの部分をpythonとしてください。このブログにもシンタックスハイライトは実装されていて、上記のコードを鮮やかにすることができます。

library(reprex)

x <- mean(c(1, NA, 3:4))
mean(x)
# [1] NA

色をつける。単純なことですがこれなら少し読む気になりません?

reprexパッケージを使う

さて、いよいよRならではの話に入ってきます。これまでに説明した、質問へのお願いを実現するために reprex パッケージが便利です。パッケージの機能を説明した id:yutannihilation さんの記事がすでにありますが、ちょっと内容の追記と更新をしておきます。なおreprexは rep roducible ex ample からなる造語で、Romain Francoisによって名付けられました

notchained.hatenablog.com

Prepare Reproducible Example Code for Sharing • reprex

reprexGitHub issuesやStackOverflow、Slackでの再現可能なコードの例を用意してくれるパッケージおよびその関数名です。現在CRANに登録されているバージョンは0.1.2で、ここでもそのバージョンを利用しています。

reprex()ないで対象のコードを次のいずれかの形式で与えるか、ファイルを指定するかで、一時フォルダの中でコードが実行されます。RStudioを利用しているのであれば、専用のアドインも利用可能です(個人的にはこちらがおすすめ)。

reprex(mean(rnorm(10)))

reprex(input = "mean(rnorm(10))\n")

reprex(input = "my_reprex.R")

ここで肝心なのは、reprex()を実行している環境とは別の場所でコードが再実行されることと、コピーペーストでGitHub等のサービスにシンタックスハイライト可能なMarkdown形式で貼り付け可能なテキストが用意されることかと思います。

reprex()は、質問への回答者がコピーペーストで問題を再現できるように調整されています。ですのでreprex()は、対象のコードを実行して、読み込まれていないパッケージの関数やオブジェクトがある場合、出力もエラーメッセージを含んだものになります。これは実行したコードでは示されていないパッケージの読み込みやオブジェクトの作成がコードとは別の場所(例えばコンソールで実行しただけでコードには書かれていない)で行われていたことに起因します。人間のコピペだと、うっかり問題の箇所を見落としたり、情報を不足させる危険があるので、reprex()での実行環境を変えた再実行が効果的なのです。

ここで注意なのが、csvデータ等の読み込みです。read.csv()などを伴う処理を行う場合、パスを明確に指定しないとファイルがない、とエラーになることがあります。例えば現在の作業ディレクトリ(Rprojファイルがあるディレクトリ)で次のコードをreprex()に渡すと再現が失敗します。

d <- read.csv("iris.csv")
table(d$Species)
d <- read.csv("iris.csv")
#> Warning in file(file, "rt"): cannot open file 'iris.csv': No such file or
#> directory
#> Error in file(file, "rt"): cannot open the connection
table(d$Species)
#> Error in table(d$Species): object 'd' not found

ファイルの入出力を伴う場合はパスはフルパスで示すようにしましょう。

d <- read.csv("/home/rstudio/iris.csv")
table(d$Species)
#>
#>     setosa versicolor  virginica
#>         50         50         50

問題がデータではなくて処理にある場合、データはRに最初から用意されているデータやダミーデータを作成してくれると助かります(ローカルファイルの結果を示すことはできますが、再現にはファイルそのものが必要になるためです)。reprex公式では、irismtcarsが良いとされています。またデータはすべてでなくて構わない時があります。この時はhead()sample()を使って一部のデータを示してくれるだけで良いです。sample()などランダムに結果が異なる処理では、set.seed()により乱数の固定をすることも再現性の確保に必要になります。

Session Information

ソースコードの中身を追ったところ、コードには問題がないようです。ではどうして問題が発生しているのでしょう?

問題を深追いするために、あなたの利用環境について教えてもらう必要があります。RではsessionInfo()が用意されていて、これを実行するとコンピュータのOSや読み込んでいるパッケージの情報が出力されます。

sessionInfo()
#> R version 3.4.3 (2017-11-30)
#> Platform: x86_64-pc-linux-gnu (64-bit)
#> Running under: Debian GNU/Linux 9 (stretch)
#>
#> Matrix products: default
#> BLAS: /usr/lib/openblas-base/libblas.so.3
#> LAPACK: /usr/lib/libopenblasp-r0.2.19.so
#>
#> locale:
#>  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
#>  [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
#>  [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=C             
#>  [7] LC_PAPER=en_US.UTF-8       LC_NAME=C                 
#>  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
#> [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       
#>
#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     
#>
#> loaded via a namespace (and not attached):
#>  [1] compiler_3.4.3  backports_1.1.2 magrittr_1.5    rprojroot_1.3-2
#>  [5] tools_3.4.3     htmltools_0.3.6 yaml_2.1.16     Rcpp_0.12.15   
#>  [9] stringi_1.1.6   rmarkdown_1.8   knitr_1.19      stringr_1.2.0  
#> [13] digest_0.6.15   evaluate_0.10.1

パッケージの違いを追うのにこの情報が必要なことがあります。さらに専用のパッケージ、 sessioninfo が用意されていて、こっちを使ってもらっても良いです(こっちの方が良いかな)。sessioninfo の出力の例です。

sessioninfo::session_info()
#> ─ Session info ──────────────────────────────────────────────────────────
#>  setting  value                       
#>  version  R version 3.4.3 (2017-11-30)
#>  os       Debian GNU/Linux 9 (stretch)
#>  system   x86_64, linux-gnu           
#>  ui       X11                         
#>  language (EN)                        
#>  collate  en_US.UTF-8                 
#>  tz       UTC                         
#>  date     2018-02-28                  
#>
#> ─ Packages ──────────────────────────────────────────────────────────────
#>  package     * version    date       source                      
#>  backports     1.1.2      2017-12-13 CRAN (R 3.4.3)              
#>  clisymbols    1.2.0      2017-05-21 CRAN (R 3.4.3)              
#>  digest        0.6.15     2018-01-28 CRAN (R 3.4.3)              
#>  evaluate      0.10.1     2017-06-24 CRAN (R 3.4.3)              
#>  htmltools     0.3.6      2017-04-28 CRAN (R 3.4.3)              
#>  knitr         1.19       2018-01-29 CRAN (R 3.4.3)              
#>  magrittr      1.5        2014-11-22 CRAN (R 3.4.3)              
#>  Rcpp          0.12.15    2018-01-20 CRAN (R 3.4.3)              
#>  rmarkdown     1.8        2017-11-17 CRAN (R 3.4.3)              
#>  rprojroot     1.3-2      2018-01-03 CRAN (R 3.4.3)              
#>  sessioninfo   1.0.0      2017-06-21 CRAN (R 3.4.3)              
#>  stringi       1.1.6      2017-11-17 CRAN (R 3.4.3)              
#>  stringr       1.2.0      2017-02-18 CRAN (R 3.4.3)              
#>  withr         2.1.1.9000 2018-02-28 Github (r-lib/withr@5d05571)
#>  yaml          2.1.16     2017-12-12 CRAN (R 3.4.3)

sessionInfo()を含めないのはなぜ?

id:yutannihilation さんの記事の中で

session infoは含めない!

と書かれています。うーむ。これについて、私も最初ユタニさん同様、どうしてだろうと思いました。

ですが、sessionInfo()の情報が必要になるのは、問題がコードにない時だろうと思います。問題がコードにあるのであればsessionInfo()は余分な情報となります。

sessionInfo()はコードだけで問題解決ができない時、追加の情報として求めれば良いのだろうと考えています。

reprex 0.2.0 が良い感じ

reprex パッケージの最新版は 0.1.2であると述べましたが、おそらく次期バージョンの 0.2.0 ではさらに使いやすく、より便利になっています。問題がなければこちらのバージョンを先取りし、GitHubからインストールして使うのを勧めます。

インストールは次のコードの実行で行われます。

install.packages("githubinstall")
githubinstall::githubinstall("reprex")

0.2.0では次の画像のような出力になります。reprexを使ったこととそのバージョンも示されていて良いですね。

f:id:u_ribo:20180301073429p:plain

ちなみにパッケージは現在は tidyverseリポジトリに属するようになっています。

それでは、良い質問と良い回答を! Enjoy!

(あとでちょっと追記するかも)