cucumber flesh

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

何気ない関数・演算子の導入がプロジェクトを傷つけた

ちょっと前の話。私が手を加えたコードが動かないという相談を受けた。早速手元で試してみたが自分の環境では再現できている。確認のために再実行してもらうと確かに結果が違うようだ。なぜだろう。結論を書いてしまうと、問題は %<>% 演算子にあった。

%<>% 演算子、この演算子について知っている人はどのくらいいるだろうか。この演算子は、見た目も %>% と類似している。そう、 magrittr パッケージが提供するパイプ演算子の一種である。その働きは %>% 演算子と同様、パイプの左辺に与えられたオブジェクトに対し右辺で指定した関数に渡すものである。そして、その結果を左辺のオブジェクトに代入する。

library(magrittr)
x <- letters[1:3]

x %<>% toupper()
x
## [1] "A" "B" "C"

この演算子の何が良いのかというと、%>% を使った場合には次のように書く必要のあるコードを簡略化できることである。

x <- x %>% toupper()

パイプ処理の結果を元のオブジェクトに保存するのであれば、x <- x は冗長に思える。そこで %<>% を使う。一方で、それだけ、という話でもある。

%>% はtidyverseの処理の中で利用しているので知っている。しかし %<>% についてはその機能を知らない。知らない人からすると%<>%typoだと思って%>%に直してしまうかもしれない。そうすると私の意図した結果が得られないのもの納得できる。

私にとって当然であることがメンバーの常識であるとは限らない。dplyrtidyr パッケージの個々の関数について、その機能を把握するのにもある程度の時間がかかる。まずは個々の関数がどのパッケージから読み込まれているのかを理解させないといけない気もする。その場合には名前空間を指定してdplyr::mutate()のような書き方をしているが、package::function()が何を意味しているのか、名前空間とは何かがわかっていないと意味がない。これは自分も理解するのに時間が必要だった記憶がある。少なくとも本を読んだり誰かから説明を受けない限りは理解できなかっただろう。

tidyverseに含まれるパッケージやパイプ処理は大変便利であるが、それは初心者向きではない。

tidyverse is premature for you guys

(書いてない)

tidyverseの真価を知るには、Rで標準利用できる関数で手間のかかる処理をパイプ演算子と組み合わせて利用することで効果的になることを理解してもらうのが必要だ。

これは自分が感じていることであり、Hadley自身もそのようなことを言っていたのでちょっと安心した。

... チームで分析プロジェクトを進行していく際は、メンバー間で技術や知見の足並みを揃えることは大事だと考えているが、それは高い水準にするのでなく、今の状態で 共通認識として参加するメンバーが理解している範囲の水準にする方が良い気がしてきた。

パイプに加えて、先端的なパッケージや開発が盛んなパッケージの導入は便利だ。しかしそれは破滅の始まりになる危険性をはらんでいる。

#17: Dependencies.

ということを上の記事を読んで思った。ついつい忘れがちになってしまうが一度考える時間を設けたい。

質問お待ちしております!できれば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!

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

最近お気に入りのRStudio(ver.1.1.423)の機能: ファイル内検索とTODOハイライト

先日、RStudioのバージョン1.1.423がリリースされました

v1.1からの更新内容については先日開催されたrstudio::conf 2018にて、開発者の一人であるKevinが発表しているので詳細はこちらをご覧ください。国内ではぞうさんこと@kazutanが記事にしています。

RStudio v1.1 – What’s New

kazutan.github.io

そんな開発が活発なRStudioですが、最新のv1.1.423で利用可能な個人的におすすめしたいプロジェクトの運用がしやすくなる2つの機能について紹介します。

プロジェクト中のファイル内検索

どうしてこの機能に気がつかなかったのだろう。この機能を使う前と後では私のRStudioの使い方は一変してしまいました、そのくらいのお気に入りの機能。

プロジェクトやパッケージが肥大してくると、全てのRファイルや関数について把握するのは難しくなります。「このデータファイルはここで使っている」、「この図はあそこのRファイルで出力している」などなど。対象のファイルの名前やパス、オブジェクト名を変更した際、影響を及ぼす範囲を把握しきれていないと変更が追いつかず、ある日エラーになっている、という経験が皆さんにもあるでしょう。

そんな時にこの機能を使うと、Rプロジェクト中のファイルから、任意の文字列を検索し、どのファイル、どの部分で記述されているかがわかって大変便利です。特に誰かのパッケージやプロジェクトに首を突っ込む時、自分が変更した箇所が他のファイルに反映されているかを確かめるときに重宝します。

ショートカットキーが当てられていて、macOSでは「コマンド + シフト + F」で検索用の画面が起動します。正規表現の利用や対象のパス、ファイル形式について設定できて高機能です。

f:id:u_ribo:20180221071044g:plain

検索結果は、RStudioのコンソールパネルのタブとして表示されます。検索結果を見ると、検索した文字列がどこで使われているかがハイライトされます。また、特定行をクリックすることで、その部分をソースパネルで編集することができます。

全てのファイルの中身を一度に変更することはできませんが、こうして影響のある部分を書き換えていくことで、変更によるトラブルを防げるようになります。

TODOハイライト

こちらはid:niszetさんのつぶやきで知りましたが、Rファイル中で特定の文字列がハイライトされるようになっています。特定の文字列とは"TODO", "FIXME"の2つです

github.com

コード中にこうしたコメントを残しておくことで、視認性・コードの保守性が向上しますね。

f:id:u_ribo:20180221073136p:plain

todorパッケージ

github.com

この機能と組み合わせて、todorパッケージが便利です。このパッケージは、プロジェクト中の"TODO"や"FIXME"などの特定の文字列を検索します。こちらが対応しているのはRStudioのものよりも多いです。

CRANには登録されていないため、利用の際にはGitHub経由でインストールを行います。

remotes::install_github("dokato/todor")

f:id:u_ribo:20180221073343g:plain

todor::todor()およびRStudio Addinsから実行します。

RStudio v1.2に向けて

GitHubのNEWSを見ると、どうやら次のバージョン(v1.2)ではreticulateパッケージによるPythonとの連携機能の強化が行われている様子。

また、daily build版だけなのかもしれませんが、アイコンが...

Enjoy!