CUBE SUGAR CONTAINER

技術系のこと書きます。

統計: ポアソン分布を使って今後の大地震が起こる確率を求めてみる

ポアソン分布というのは、ごくまれに起こるような事象の確率分布をいう。 この説明を聞いても何のこっちゃという感じだけど、これを使うと滅多に起こらないようなことがある時間内にどれくらい起こりそうなのかが分かる。 もちろん、別に未来を予知しているわけではなく、あくまで過去の生起確率からはそのように求まるというのに過ぎない。

定義

ポアソン分布は次の数式で求められる。

 P(X = k) = \frac{\lambda^{k} e^{- \lambda}}{k!}

まず  \lambda が、その滅多に起こらないような事象の単位時間あたりの生起確率になっている。 そして  k が単位時間あたりにその事象が何回起こるかを表す変数になっている。

日本で今後一年間に震度 7 の地震が起こる確率を求めてみよう

それでは、手始めにポアソン分布を使って今後一年間に震度 7 の地震が日本の何処かで起こる確率を求めてみよう。 ちなみに、実際に政府が今後 X 年間にとある地域でマグニチュード Y 以上の地震が起こる確率を計算するときはポアソン分布を使っているらしい。

まず、ポアソン分布を使うにはその事象が単位時間あたり何回起こっているかを調べる必要がある。 今回は Wikipedia にある震度 7 の項目をベースに計算してみよう。 ここには、日本で過去に発生した震度 7 の地震の記録がある。

震度7 - Wikipedia

上記を見ると震度 7 の地震は 1995 年から 2016 年の 21 年間で 5 回発生していることが分かった。 生起確率は 1 年あたり  \frac{5}{21} ということになる。 細かくやるなら震度 7 の定義ができたタイミングから今日までで計算すべきなんだろうけど、ひとまずざっくりということで。

まずは、今後一年間に震度 7 の地震が起こらない確率を求める

単位時間あたりの生起確率が分かったところで、まずは今後一年間に震度 7 の地震が起こらない確率から求める。 この計算をする理由は後述する。

事象が一度も起こらない場合なので、回数には  k = 0 を代入する。 そして生起確率は  \lambda = \frac{5}{21} になる。 実際にポアソン分布に代入して式を整理してみよう。

 P(0) = \frac{\lambda^{k} e^{-\lambda}}{k!} = \frac{\frac{5}{21}^{0} e^{-\frac{5}{21}}}{0!} = e^{-\frac{5}{21}}

すると  P(0) = e^{-\frac{5}{21}} になることが分かった。

実際に値を計算してみる

実際に、上記の値を Python で計算してみよう。

まずは Python インタプリタを起動する。

$ python --version
Python 3.5.2
$ python

そして数学系の math パッケージをインポートしたら、先ほどの数式になるように式を入力する。

>>> import math
>>> math.e ** -(5/21)
0.7881276277453111

すると 0.78812... という値が得られた。 これはつまり、今後一年間で震度 7 の地震が起きない確率は約 78% ということだ。

何で起きない確率を計算したのか?

ポアソン分布は単位時間あたりに事象が  k 回起きる確率を求める分布だった。 つまり、単位時間あたりに少なくとも 1 回以上起きる確率を愚直に計算しようとすると、こうなってしまう。

 P(X \gt 0) = \displaystyle \sum_{k = 1}^{\infty} \frac{\lambda^{k} e^{-\lambda}}{k!}

上記は単位時間あたりに事象が  k 回起こる確率を、1 から無限まで足し合わせていく計算になっている。

試しに Python を使って、実際に k 回起こる確率を 0 から 4 までの間で見てみよう。

>>> f = lambda n: ((5 / 21) ** n) * (math.e ** -(5 / 21)) / math.factorial(n)
>>> f(0)
0.7881276277453111
>>> f(1)
0.187649435177455
>>> f(2)
0.022339218473506547
>>> f(3)
0.0017729538471036941
>>> f(4)
0.00010553296708950561

もちろん、実際にはこんな計算をしていく必要はない。 全ての事象が起こる確率は足せば 1 になるはず。

 \displaystyle \sum_{k = 0}^{\infty} \frac{\lambda^{k} e^{-\lambda}}{k!} = 1

だとすると、事象が少なくとも一回以上起こる確率は 1 から起こらない確率を引けば良いことが分かる。

 P(X \gt 0) = 1 - P(0)

このために、まずは起こらない確率を計算したというわけ。

今後一年間に震度 7 の地震が起こる確率を求める

ということで、今度は起こる確率を求めてみる。

先ほど記述した通り、少なくとも一回以上起こる確率は 1 から起こらない確率を引けば良い。

 P(X \gt 0) = 1 - P(0) = 1 - \frac{\lambda^{0} e^{-\lambda}}{0!} = 1 - e^{-\lambda} = 1 - e^{- \frac{5}{21}}

上記を元に、今度は起こる確率を計算する。

>>> 1 - math.e ** -(5/21)
0.2118723722546889

これで、今後一年間に震度 7 の地震が日本で起こる確率は約 21% と分かった。

うんうん…で?

まあ、といっても上記に関してはポアソン分布を使うまでもないでしょうという感じ。 だって、過去の単位時間あたりに起こる生起確率は分かっているんだから。

 \frac{5}{21} = 0.23809523809523808

単純に一年あたり何回起こっているかを計算した結果と大して変わりがない。

日本で今後 10 年間に震度 7 の地震が起こる確率を求めてみよう

ポアソン分布を使うなら単位時間をもっと伸ばしたり縮めたりしないと面白みがない。 次は試しに 10 年間で起こる確率を求めてみよう。

既に分かっている通り、起こる確率は 1 から起こらない確率を引いて計算する。 ただし、今度は期間が 10 倍になっているので生起確率も 10 倍にする。

 P(X \gt 0) = 1 - P(0) = 1 - e^{-\lambda} = 1 - e^{-\frac{10 \times 5}{21}}

それでは、実際に Python で計算してみよう。

>>> 1 - math.e ** -(10 * 5/21)
0.90753752393708

ということで、今後 10 年間に震度 7 の地震が日本で起こる確率は約 90% と分かった。

今後 30 年間なら?

これを 30 年間まで伸ばすと、なんと確率は約 99.92% まで上がる。

>>> 1 - math.e ** -(30 * 5/21)
0.99920950967688

地震が起こる世界線と起こらない世界線があるとしたら、なんと起こらない世界線に到達できる可能性は  \frac{1}{1265} だ。

>>> 1 / math.e ** -(30 * 5/21)
1265.0376238043307

あくまで日本の何処か、とはいえ残りの人生の中で震度 7 の地震はほぼ間違いなく起こることが分かる。 日々の備えが必要だな…。

明日、震度 7 の地震が起こる確率は?

そして、ポアソン分布の面白いところは期間を縮めてもちゃんと計算できるところ。 明日の確率を求めたいなら、単位時間を一日にする。

 P(X \gt 0) = 1 - e^{-\frac{5}{21 \times 365}}

Python で計算してみよう。

>>> 1 - math.e ** -(5/(21*365))
0.0006521030091632962

上記から、明日震度 7 の地震が日本の何処かで起こる確率は約 0.06% と分かった。

今後一週間なら?

生起確率の単位時間を一日にして 7 倍する。

 P(X \gt 0) = 1 - e^{-\frac{7 \times 5}{21 \times 365}}

>>> 1 - math.e ** -((7*5)/(21*365))
0.004555800758262785

一週間だと確率は約 0.4% になることが分かった。

まとめ

ポアソン分布を使うと滅多に起こらない事象が今後どれくらいの確率で起こりそうなのかを計算できる。

Node.js: Mac に nvm で複数のバージョンをインストールする

Node.js には同時に複数の LTS (Long Term Support) がサポートされる期間が存在している。 また、特定のバージョンの Node.js でないと動かないようなライブラリも結構ある。 そこで、今回は複数バージョンの Node.js をインストールして管理できる nvm を使ってみることにする。

使った環境は次の通り。

$ sw_vers 
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G1108

インストール

まずは Homebrew を使って nvm をインストールする

$ brew install nvm

Homebrew がインストールされていないときは?

以下の手順でインストールする。

$ xcode-select --install
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

セットアップ

インストールできたら、次に nvm を設定する。

まずは nvm が使う作業ディレクトリを用意しておく。

$ mkdir ~/.nvm

次にシェルの設定ファイルに nvm にパスを通すための設定を追加する。 次の設定では nvm がインストールされていない環境で使ってもエラーにならないよう対処してある。 何でそんなことをするかというと、色んな環境で設定ファイルを使いまわしてるから。

$ cat << 'EOF' >> ~/.zlogin

# nvm
if [ -e $(brew --prefix nvm)/nvm.sh ]; then
  export NVM_DIR="${HOME}/.nvm"
  source $(brew --prefix nvm)/nvm.sh
fi
EOF

設定を書き込む先は、使っている設定ファイルごとに .bashrc とか .zshrc とか上手いこと変更する。

設定を書き込んだらシェルの設定ファイルを読み込み直す。

$ source ~/.zlogin

これで nvm にパスが通って使い始める準備ができた。

$ nvm --version
0.32.1

使ってみる

まずはインストールできる Node.js のバージョンは nvm ls-remote サブコマンドで確認できる。

$ nvm ls-remote | tail
         v6.4.0
         v6.5.0
         v6.6.0
         v6.7.0
         v6.8.0
         v6.8.1
         v6.9.0   (LTS: Boron)
         v6.9.1   (Latest LTS: Boron)
         v7.0.0
         v7.1.0

そして、現在インストールされているバージョンは nvm ls で確認できる。 今は何もインストールされていないため N/A となっている。 また、主要な LTS のバージョンについてはインストールされていない状態でも候補として表示されるようだ。

$ nvm ls
            N/A
node -> stable (-> N/A) (default)
iojs -> N/A (default)
lts/* -> lts/boron (-> N/A)
lts/argon -> v4.6.2 (-> N/A)
lts/boron -> v6.9.1 (-> N/A)

試しに v4.x 系の LTS をインストールしてみる。 --lts オプションをつけると LTS 版しか入らないようになるみたい?

$ nvm install lts/argon --lts
VERSION_PATH=''
######################################################################## 100.0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v4.6.2 (npm v2.15.11)
nvm_ensure_default_set: a version is required

インストールすると nvm ls コマンドにも有効になっているのが v4.6.2 という表示が出た。

$ nvm ls
->       v4.6.2
node -> stable (-> v4.6.2) (default)
stable -> 4.6 (-> v4.6.2) (default)
iojs -> N/A (default)
lts/* -> lts/boron (-> N/A)
lts/argon -> v4.6.2
lts/boron -> v6.9.1 (-> N/A)

また、node コマンドも使えるようになっている。

$ node --version
v4.6.2

次に、別のバージョンもインストールしてみよう。 今度は v6.x 系の LTS をインストールする。

$ nvm install lts/boron --lts
VERSION_PATH=''
######################################################################## 100.0%
Computing checksum with shasum -a 256
Checksums matched!
Now using node v6.9.1 (npm v3.10.8)
nvm_ensure_default_set: a version is required

すると、インストールしただけでそのバージョンが有効になった。

$ nvm ls
         v4.6.2
->       v6.9.1
node -> stable (-> v6.9.1) (default)
stable -> 6.9 (-> v6.9.1) (default)
iojs -> N/A (default)
lts/* -> lts/boron (-> v6.9.1)
lts/argon -> v4.6.2
lts/boron -> v6.9.1

node コマンドで確認してもバージョンが v6.9.1 に切り替わっている。

$ node --version
v6.9.1

もし、使うバージョンを切り替えたいときは nvm use サブコマンドを使えば良い。

$ nvm use 4.6.2
Now using node v4.6.2 (npm v2.15.11)

これでお目当てのバージョンに切り替わった。

$ node --version
v4.6.2

めでたしめでたし。

統計: 条件付き確率をベン図で理解する

条件付き確率というのは、具体的には次のような式で表される。

 P(B | A) = \frac{P(A \cap B)}{P(A)}

これは、ある事象  A が起こる条件の下で事象  B が発生する確率を求める式になっている。 とはいえ、これを見ていても一体どんな状況なのかさっぱり分からなかった。 具体的には  A B が同時に起こる確率  P(A \cap B) と何が違うのか理解できなかった。

ただ、ある問題をベン図と一緒に考えていたら、やっと分かるようになった。 今回は条件付き確率の自分なりの理解について書いておく。

問題

100 円玉 5 枚、10円玉 7 枚、1 円玉 3 枚の入った小銭入れから、同時に 3 枚の硬貨を取り出す。 いずれの硬貨を取り出すのも同様に確からしいとする。 取り出した 3 枚の金額の合計が 150 円以上であるという条件のもとで、その 3 枚の中に 1 円玉が含まれる条件付き確率はいくらか。

(統計検定 2 級公式問題集 2015 年 6 月問題、問 8 より)

上記の問題をベン図で考えると、次のようになる。

f:id:momijiame:20161110003800p:plain

上記のベン図に「組み合わせ」とあるように、この問題を解くには組み合わせの公式が必要になる。 具体的には、高校の数学 A の範囲で習うコレ。

 {}_n \mathrm{ C }_r = \frac{n!}{r! (n - r)!}

全ての組み合わせ

まず始めに、全ての組み合わせが何通りあるかを考えよう。

コインは全部で 15 枚あって、そこから 3 枚を取り出すことを考えれば良い。

 {}_{15} \mathrm{ C }_{3} = \frac{15!}{3!(15 - 3)!} = \frac{15 \times 14 \times 13}{3 \times 2} = \frac{2730}{6} = 455

上記から、全ての組み合わせは 455 通りあることが分かった。 つまり、各個別の組み合わせは  \frac{1}{455} の確率で発生する。

150 円以上になる組み合わせ

次に、合計が 150 円以上になる組み合わせについて考える。 これは、先ほどのベン図でいうと赤い集合に相当する。

f:id:momijiame:20161110003800p:plain

3 枚のコインで 150 円以上にするには、最低でも 100 円玉が 2 枚以上ないといけない。

100 円玉が 3 枚になる組み合わせ

まずは 3 枚のコインが全て 100 円玉だったときのパターンから。

これはつまり、5 枚あるコインの中から 3 枚を取り出すときの組み合わせを考えれば良い。

 {}_5 \mathrm{ C }_{3} = \frac{5!}{3!(5 - 3)!} = \frac{5 \times 4 \times 3}{3 \times 2} = \frac{60}{6} = 10

このパターンは 10 通りあることが分かった。

100 円玉が 2 枚になる組み合わせ

次に 3 枚のコインのうち 2 枚が 100 円玉で、残りの 1 枚がそれ以外のパターン。 これは、5 枚あるコインの中から 2 枚を取り出す組み合わせと、それ以外の 10 枚から 1 枚を取り出す組み合わせの積になる。

 {}_5 \mathrm{ C}_{2} \times {}_{10} \mathrm{ C }_1 = \frac{5!}{2!(5 - 2)!} \times \frac{10!}{1!(10 - 1)!} = \frac{5 \times 4}{2} \times \frac{10}{1} = 10 \times 10 = 100

このパターンは 100 通りあることが分かった。

確率を計算する

上記から 150 円以上になる組み合わせは 2 つのパターンを合計して 110 通りあることが分かった。

確率は、150 円以上になる組み合わせと全ての組み合わせの比率になる。 つまり、後ほど前提条件となる確率  P(A) はこう。

 P(A) = \frac{110}{455} = \frac{22}{91}

150 円以上で 1 円玉が含まれる組み合わせ

次に 150 円以上で、なおかつその中に 1 円玉が含まれる組み合わせを考えてみよう。 事象  A が 150 円以上だとすると 1 円玉が含まれるのが事象  B になる。 両者をどちらも満たす状況というのは  A \cap B だ。

前述した通り 150 円以上とするには、必ず 100 円玉が 2 枚以上は必要となる。 そして、その中に 1 円玉が含まれるという状況は 100 円玉が 2 枚で 1 円玉が 1 枚のときだけ。

これはつまり 5 枚の 100 円玉から 2 枚を取り出す組み合わせと、3 枚の 1 円玉から 1 枚を取り出す組み合わせの積になる。

 {}_5 \mathrm{ C}_{2} \times {}_{3} \mathrm{ C }_1 = \frac{5!}{2!(5 - 2)!} \times \frac{3!}{1!(3 - 1)!} = 10 \times 3 = 30

上記から、これは 30 通りあることが分かった。

つまり、上記の条件は確率でいうと次のようになる。

 P(A \cap B ) = \frac{30}{455} = \frac{6}{91}

条件付き確率

さて、これで条件付き確率を計算するのに必要なものは揃った。

条件付き確率の式に値を代入してみよう。

 P(B | A) = \frac{P(A \cap B)}{P(A)} = \frac{ \frac{6}{91} }{ \frac{22}{91} } = \frac{6}{91} \times \frac{91}{22} = \frac{6}{22}

結果は  \frac{6}{22} となった。 つまり、最初に出てきた問題の答えは  \frac{6}{22} ということになる。

計算の意味

とはいえ、肝心なのは上記の計算に一体どんな意味があるかだと思う。

もう一度、最初に示したベン図を見てもらいたい。

f:id:momijiame:20161110003800p:plain

先ほどの条件付き確率の式と一緒に見ると、赤い集合と小豆色の集合の比率を計算していることが分かる。

 P(B | A) = \frac{P(A \cap B)}{P(A)}

条件付き確率とは

つまり、条件付き確率というのは、事象  A が前提条件となるため  P(A) を分母とした確率になる。 その状況下で、さらに事象  B が起こるということは、両事象が一緒に起こる確率  P(A \cap B) が分子になる。 これで、事象  A が起こってから事象  B が起こる確率が計算できるというわけらしい。

先ほどの問題を例にして説明しよう。 コインを 3 枚取り出したときに合計が 150 円以上だったという事象  A は、既に起こっているとする。 既に起こっているとした上で、取り出したコインの中に 1 円玉が含まれる確率が知りたい。 これの計算が、条件付き確率の意味だった。

以上、ベン図と一緒に式を見ると「フーンなるほど」とならない…かな?

Mac: Ruby の開発環境を整えてみる

諸事情により Ruby を書くことになるかもしれないので、環境を整えるためにやったことをメモしておく。 rbenv を使って複数バージョンの Ruby をインストールして、Bundler で vendoring できるようにするところまで。

今回使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G1108

Homebrew をインストールする

まずは rbenv をインストールするために Homebrew をインストールしておく。 Homebrew はコマンドラインで色々なツールをインストールするためのソフトウェア。

$ xcode-select --install
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

rbenv をインストールする

rbenv を使うことで任意のバージョンの Ruby をインストールできる。

まずは Homebrew の状態を最新にしておく。

$ brew update && brew doctor && brew upgrade

その上で rbenv をインストールする。

$ brew install rbenv

rbenv をセットアップする

まずは rbenv init を実行するものらしいので、やってみる。

$ rbenv init
# Load rbenv automatically by appending
# the following to ~/.zshrc:

eval "$(rbenv init -)"

なるほど、上記を行を使っているシェルの設定ファイルに追加せよということね。

ただ、今の環境は Oh-my-zsh を使っている関係で .zlogin に個人的な設定を入れているので、そちらを使うことにしよう。

どうせなら、上記をそのままシェルの設定ファイルにリダイレクトしてもよさそう。

$ rbenv init >> ~/.zlogin

ただ、設定ファイルは rbenv を入れていない環境でも使いまわしているので、次のようにした。 rbenv コマンドの有無を確かめた上で rbenv init を実行する。

$ cat << 'EOF' >> ~/.zlogin
# rbenv
which rbenv  > /dev/null 2 > /dev/null
if [ $? -eq 0 ]; then
  eval "$(rbenv init -)"
fi
EOF

書き終わったら、シェルの設定ファイルを読み込み直すのをお忘れなく。 これで rbenv をインストールしたディレクトリにパスが通る。

$ source ~/.zlogin

rbenv を使ってみる

まずは、インストールできる Ruby のバージョンを確認してみる。

$ rbenv install -l

どうやら、今 (2016/11 現在) インストールできる最新のものは 2.3.1 らしい。

$ rbenv install 2.3.1

新しいバージョンをインストールしたときは rbenv rehash コマンドを実行する。

$ rbenv rehash

使うバージョンを切り替える

今は macOS がデフォルトでインストールしているバージョンを使っている状態になっている。

$ ruby --version
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15]

普段使いのバージョンを切り替える

これを、先ほど rbenv でインストールしたバージョンに切り替えるには rbenv global サブコマンドを使う。

$ rbenv global 2.3.1

これで、普段使いのバージョンが切り替わった。

$ ruby --version
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

元に戻したいときは rbenv global system とすれば、元々システムが使っていた処理系に切り替わる。

$ rbenv global system
$ ruby --version
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15]

特定のプロジェクトで使うバージョンを切り替える

特定のディレクトリ (プロジェクト) で使うバージョンを指定したいなら rbenv local を指定する。

$ rbenv local 2.3.1

すると、そのディレクトリに .ruby-version という使うバージョンを指定するファイルができる。

$ cat .ruby-version 
2.3.1

rbenv はこれを読み取って使うバージョンを選んでくれる。

$ ruby --version     
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

rbenv local --unset コマンドを実行すれば元のバージョンに戻る。

$ rbenv local --unset
$ ruby --version  
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15]

あるいは、単にファイルを削除してしまっても構わない。

$ rm .ruby-version

シェルで使うバージョンを一時的に切り替える

シェルで使うバージョンを一時的に指定したいときは rbenv shell コマンドを使う。

$ rbenv shell 2.3.1
$ ruby --version
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]

これは環境変数 RBENV_VERSION に使うバージョンが指定されることで動作している。

$ env | grep RBENV
RBENV_SHELL=zsh
RBENV_VERSION=2.3.1

これも rbenv local コマンドと同じように、元に戻したいときは --unset オプションを使えばもとに戻る。

$ rbenv shell --unset
$ ruby --version     
ruby 2.0.0p648 (2015-12-16 revision 53162) [universal.x86_64-darwin15]

あるいは単に環境変数 RBENV_VERSION を消してしまっても構わない。

$ unset RBENV_VERSION

Bundler をインストールする

Ruby のパッケージ管理には Bundler を使うのがデファクトスタンダードになっているみたい。 これを使うとプロジェクトごとに使うパッケージを切り替えることができて vendoring が実現できる。

まずは gem install コマンドで Bundler をインストールする。

$ gem install bundler

インストールすると bundle コマンドが使えるようになる。

$ bundle --version
Bundler version 1.13.6

Bundler を使ってみる

それでは、適当な名前で作業ディレクトリを用意しよう。 これが Ruby で書かれた何らかのプロジェクトを模している。

$ mkdir -p sample
$ cd sample

bundle init コマンドで新しいプロジェクトを用意する。

$ bundle init

すると Gemfile というファイルができる。 これにプロジェクトで使うパッケージを羅列していくみたい。

$ cat Gemfile 
# frozen_string_literal: true
source "https://rubygems.org"

# gem "rails"

初期状態では rails がコメントアウトされた状態で記述されている。

試しに rails のコメントアウトを外して使ってみたい。 そこで GNU sed が使いたいので Homebrew でインストールしてエイリアスを貼っておく。

$ brew install gnu-sed
$ alias sed=gsed

sed コマンドを使って先頭のコメントアウト記号 # を外す。

$ sed -i -e "s:^# gem:gem:" Gemfile
$ cat Gemfile
# frozen_string_literal: true
source "https://rubygems.org"

gem "rails"

bundle install コマンドを使うと Gemfile の内容を読み取って gem パッケージをインストールしてくれる。 このとき --path を指定するのをお忘れなく。 もし、これを忘れるとシステムにパッケージがインストールされてしまう。

$ bundle install --path=vendor/bundle
...(省略)...
Bundle complete! 1 Gemfile dependency, 38 gems now installed.
Bundled gems are installed into ./vendor/bundle.

上記で、インストールされたパッケージのコマンド群は vendor ディレクトリ以下にある。 そのため、デフォルトではパスが通っていない。 もし、それらを実行したいときは bundle exec コマンドを経由しよう。

$ bundle exec rails --version
Rails 5.0.0.1

rbenv-binstubs を使って横着する

ただ、上記の bundle exec コマンドを使うのは正直めんどくさいところもある。 そんなときは rbenv-binstubs というツールを使うと楽ができるかもしれない。

まずは、先ほど gem パッケージをインストールしたディレクトリを削除しておく。

$ rm -rf vendor

Homebrew で rbenv-binstubs をインストールする。

$ brew install rbenv-binstubs

次に、また bundle install で gem パッケージをインストールする。 ただし、今度は --binstubs というオプションをつけるところが異なる。

$ bundle install --path=vendor/bundle --binstubs=vendor/bin

インストールできたら rbenv rehash コマンドを実行しておこう。

$ rbenv rehash

すると bundle exec コマンドを使わずとも直接 vendor ディレクトリ以下のコマンドが使えるようになる。

$ rails --version
Rails 5.0.0.1

bundle install コマンドのオプションは何だか長くて忘れそうだから、エイリアスを貼っておくのが良いかな。

$ cat << 'EOF' >> ~/.zlogin
# bundle
which bundle > /dev/null 2 > /dev/null
if [ $? -eq 0 ]; then
  alias bi="bundle install --path=vendor/bundle --binstubs=vendor/bin"
fi
EOF
$ source ~/.zlogin

これで、ひとまず複数バージョンの Ruby をインストールして vendoring できるところまでできた。

Python: Fabric を組み込みで使うときの注意点

以前、このブログで Fabric をスクリプトに組み込んで使う方法について書いた。

blog.amedama.jp

ただ、このやり方はちょっとした注意点があるので追記しておく。

今回使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G1108
$ python --version
Python 3.5.2

インストール

何はともあれ、まずは Fabric をインストールしておく。 Python 3 に対応したものは fabric3 という名前でインストールできる。

$ pip install fabric3

Python 2 なら数字なし。

$ pip install fabric

下準備

前回のエントリと同じように Fabric で操作する対象は Vagrant で作った仮想マシンにする。 そのため、まずは Homebrew Cask を使って Vagrant と VirtualBox をインストールしておく。

$ xcode-select --install
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew tap caskroom/cask
$ brew cask install vagrant virtualbox

次に、適当な作業ディレクトリに Vagrantfile を用意する。 これが操作する対象の仮想マシンの設定ファイルになる。

$ cat << 'EOF' > Vagrantfile
# -*- mode: ruby -*-
Vagrant.configure("2") do |config|
  config.vm.box = "bento/ubuntu-16.04"
  config.vm.provider "virtualbox" do |vb|
    vb.cpus = "2"
    vb.memory = "1024"
  end
end
EOF

用意ができたら、仮想マシンを起動しよう。

$ vagrant up

ssh-config サブコマンドで OpenSSH の設定を確認しておこう。 主に使うポート番号をチェックする。 今回は 2200 がアサインされた。 つまり、ポートフォワーディング経由で 127.0.0.1:2200 に SSH すると仮想マシンに接続できる。

$ vagrant ssh-config
Host default
  HostName 127.0.0.1
  User vagrant
  Port 2200
  UserKnownHostsFile /dev/null
  StrictHostKeyChecking no
  PasswordAuthentication no
  IdentityFile /Users/amedama/Documents/vagrant/fabric/.vagrant/machines/default/virtualbox/private_key
  IdentitiesOnly yes
  LogLevel FATAL

リモートホストでコマンドを実行する

以前のエントリと同じように Fabric を組み込んだスクリプトを用意する。 ここでは exit コマンドに引数 0 を指定して実行している。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fabric.api import run
from fabric.api import execute
from fabric.api import env

def task():
    run('exit 0')


def main():
    env.user = 'vagrant'
    env.password = 'vagrant'

    execute(task, hosts=['vagrant@127.0.0.1:2200'])
    print('Done!')


if __name__ == '__main__':
    main()

Fabric のタスク実行が終わった後に Done! を出力しているところがポイント。

上記を fabfile.py というファイルで保存して、実行してみよう。

$ python fabfile.py
[vagrant@127.0.0.1:2200] Executing task 'task'
[vagrant@127.0.0.1:2200] run: exit 0
Done!

ちゃんと Done! の出力までされている。

問題があるパターン

次は同じソースコードでも、実行する exit コマンドの引数に 1 を渡してみよう。 こうすると exit コマンドの返り値が 1 になる。 返り値が非ゼロということは、エラー扱いになるということだ。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fabric.api import run
from fabric.api import execute
from fabric.api import env

def task():
    run('exit 1')


def main():
    env.user = 'vagrant'
    env.password = 'vagrant'

    execute(task, hosts=['vagrant@127.0.0.1:2200'])
    print('Done!')


if __name__ == '__main__':
    main()

さて、それでは上記を実行してみよう。

$ python fabfile.py
[vagrant@127.0.0.1:2200] Executing task 'task'
[vagrant@127.0.0.1:2200] run: exit 1

Fatal error: run() received nonzero return code 1 while executing!

Requested: exit 1
Executed: /bin/bash -l -c "exit 1"

Aborting.

なんと、今度は Done! の出力が見当たらない。

何が起こったのか?

実は Fabric の run() や sudo() といった関数は、実行したコマンドの返り値が非ゼロのときにプロセスを終了してしまう。 そのため、さきほどのサンプルコードでは非ゼロが帰った時点で Python のプロセスが終了してしまった。 これによって Done! を出力する行まで到達しなかったわけだ。

対処方法

この問題を回避するには、コマンドを実行する関数の warn_only という引数に True を指定すれば良い。

今度は、先ほどと同じソースコードで run() 関数の引数に warn_only=True を指定してみよう。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fabric.api import run
from fabric.api import execute
from fabric.api import env

def task():
    run('exit 1', warn_only=True)


def main():
    env.user = 'vagrant'
    env.password = 'vagrant'

    execute(task, hosts=['vagrant@127.0.0.1:2200'])
    print('Done!')


if __name__ == '__main__':
    main()

上記を実行してみよう。 今度は返り値が非ゼロであることについて警告は出るものの、そのまま処理が継続している。 そのため Done! が出力されていることも分かる。

$ python fabfile.py
[vagrant@127.0.0.1:2200] Executing task 'task'
[vagrant@127.0.0.1:2200] run: exit 1

Warning: run() received nonzero return code 1 while executing 'exit 1'!

Done!

より細かいエラー処理

先ほどの例では、エラーがあっても突っ走ってしまうことを意味している。 このままだと、むしろ扱いにくいことになるだろう。 そこで、次はコマンドの実行結果が成功か失敗かを判断できるようにしよう。

コマンドの実行結果が成功か失敗か判断するには、コマンドを実行する関数の返り値を受け取るようにすれば良い。 次のようにして返り値の succeeded アトリビュートを確認する。 今回のサンプルコードではコマンドの実行が失敗したときには RuntimeError 例外を上げるようにしている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from fabric.api import run
from fabric.api import execute
from fabric.api import env

def task():
    res = run('exit 1', warn_only=True, quiet=True)
    if not res.succeeded:
        raise RuntimeError('Oops!')


def main():
    env.user = 'vagrant'
    env.password = 'vagrant'

    try:
        execute(task, hosts=['vagrant@127.0.0.1:2200'])
    except RuntimeError:
        # ほんとはこんなエラーメッセージだしちゃダメダヨ
        print('Something wrong!')
    print('Done!')


if __name__ == '__main__':
    main()

では、上記を実行してみよう。 今度はエラーをハンドリングしつつも Done! の表示が得られている。

$ python fabfile.py
[vagrant@127.0.0.1:2200] Executing task 'task'
Something wrong!
Done!

かんぺきだね。 めでたしめでたし。

Python: inspect.signature() で関数のシグネチャを調べる

Python は inspect モジュールを使うことでオブジェクトから多くの情報を取得できる。 そして、関数のシグネチャを調べる方法としては、これまで getargspec() 関数が使われることが多かった。 ただ、この関数は Python 3 系では非推奨 (Deprecated) となっている。 そのため、今後は使わない方が良い。

getargspec() 関数が非推奨となった代わりに、今は signature() 関数を使うことが推奨されているようだ。 今回は、この関数の使い方について扱う。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G1004
$ python --version
Python 3.5.2

インストール

Python 3.3 以降であれば signature() 関数は標準ライブラリの inspect モジュールに用意されている。 そのため、特に何も意識することなく使うことができる。

しかし、もし使っているのが Python 3.2 未満であれば PyPI からバックポート版をダウンロードして使うことができる。

$ pip install funcsigs

使ってみる

今回は Python の REPL を使って動作を確認していこう。

$ python

早速 inspect モジュールの signature() 関数をインポートしよう。

>>> from inspect import signature

もし、Python 2 系でバックポート版を使っているのであれば、次のようにしてインポートできる。

>>> from funcsigs import signature

まず手始めに、次のような関数 f() を定義しておく。 この関数は foo と bar というふたつのパラメータを受け取る。 bar に関してはデフォルト値が設定されている。

>>> def f(foo, bar='Hello'):
...     pass
...

それでは、signature() 関数に先ほど定義した関数 f() を渡してみよう。 すると Signature というオブジェクトが得られることが分かる。

>>> signature(f)
<Signature (foo, bar='Hello')>

このオブジェクトには parameters という名前で受け取るパラメータの情報が格納されている。 このメンバは次のように順序関係を持った辞書型のオブジェクトとして得られる。

>>> sig = signature(f)
>>> sig.parameters
mappingproxy(OrderedDict([('foo', <Parameter "foo">), ('bar', <Parameter "bar='Hello'">)]))

例えばパラメータの foo について知りたいときは、次のように名前をキーとしてアクセスすれば良い。 得られるのは Parameter というオブジェクトになっている。

>>> sig.parameters['foo']
<Parameter "foo">

このオブジェクトには名前はもちろん、デフォルト値などの情報も入っている。 もしデフォルト値が設定されていないときは inspect.Signature.empty で得られるオブジェクトになる。

>>> sig.parameters['foo'].name
'foo'
>>> sig.parameters['foo'].default
<class 'inspect._empty'>
>>> sig.parameters['bar'].default
'Hello'

その他、パラメータがどのような使われ方をしているかも得られる。 例えば、通常であれば次のように POSITIONAL_OR_KEYWORD になる。

>>> sig.parameters['foo'].kind
<_ParameterKind.POSITIONAL_OR_KEYWORD: 1>

それに対し *args, **kwargs といった可変長引数のときは VAR_POSITIONAL や VAR_KEYWORD になる。

>>> def f(*args, **kwargs):
...     pass
...
>>> signature(f)
<Signature (*args, **kwargs)>
>>> sig = signature(f)
>>> sig.parameters['args'].kind
<_ParameterKind.VAR_POSITIONAL: 2>
>>> sig.parameters['kwargs'].kind
<_ParameterKind.VAR_KEYWORD: 4>

また、Python 3 の機能ということもあってアノテーションにも対応している。 次のように引数や返り値にアノテーションをつけた関数を用意して signature() にかけてみる。

>>> def f(foo: str) -> int:
...     return -1
...
>>> signature(f)
<Signature (foo:str) -> int>
>>> sig = signature(f)

すると Parameter からは annotation というメンバでアノテーションに指定した型が得られる。

>>> sig.parameters['foo'].annotation
<class 'str'>

また Signature からも return_annotation というメンバで返り値のアノテーションが取得できる。

>>> sig.return_annotation
<class 'int'>

それ以外にも便利な機能がある。 例えば、その関数を特定の引数で実行したときに、パラメータが引数に対してどのようにバインドされるかを確かめることもできる。 これには Signature#bind() メソッドを使う。

>>> def f(foo, bar='Hello', baz=None, *args, **kwargs):
...     pass
...
>>> sig = signature(f)
>>> sig.bind('foo', baz=1, bar='Bye', hoge=True)
<BoundArguments (foo='foo', bar='Bye', baz=1, kwargs={'hoge': True})>

上記で得られるのは BoundArguments というオブジェクトになっている。 このオブジェクトからは arguments というメンバでバインドした結果が得られる。

>>> bound_args = sig.bind('foo', baz=1, bar='Bye', hoge=True)
>>> bound_args.arguments
OrderedDict([('foo', 'foo'), ('bar', 'Bye'), ('baz', 1), ('kwargs', {'hoge': True})])

上記を応用すると、関数の呼び出しをトレースするようなデコレータを書きやすいなと思った。 次のサンプルコードでは @recording というデコレータを定義している。 このデコレータは修飾した関数をラップして、呼び出し内容と結果をログに自動で残す機能を持っている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

try:
    from inspect import signature
except ImportError:
    from funcsigs import signature

import logging
import functools


LOG = logging.getLogger(__name__)


def recording(f):
    """関数 (メソッド) の呼び出しを記録するデコレータです。

    受け取ったパラメータと返り値をログに出力します。"""
    @functools.wraps(f)
    def _recording(*args, **kwargs):
        # 表面上は元々の関数 (メソッド) がそのまま実行されたように振る舞う
        result = f(*args, **kwargs)
        # デコレーションする関数のシグネチャを取得する
        sig = signature(f)
        # 受け取ったパラメータをシグネチャにバインドする
        bound_args = sig.bind(*args, **kwargs)
        # 関数名やバインドしたパラメータの対応関係を取得する
        func_name = f.__name__
        func_args = ','.join('{k}={v}'.format(k=k, v=v)
                             for k, v in bound_args.arguments.items())
        # ログに残す
        fmt = '{func_name}({func_args}) -> {result}'
        msg = fmt.format(func_name=func_name,
                         func_args=func_args,
                         result=result)
        LOG.debug(msg)
        # 結果を返す
        return result
    return _recording


# デコレーションすると、その関数の呼び出しをログに記録する
@recording
def add(a, b):
    return a + b


def main():
    # デバッグレベルのログも出力する
    logging.basicConfig(level=logging.DEBUG)
    # デコレーションされた関数を呼び出すと自動でログが出力される
    print(add(1, 2))


if __name__ == '__main__':
    main()

上記を実行すると、次のような出力が得られる。

$ python recording.py
DEBUG:__main__:add(a=1,b=2) -> 3
3

見事に関数の呼び出しと、その返り値までが自動でログに記録された。

めでたしめでたし。

Mac OS X に Homebrew で R をインストールする

R は統計の世界でよく使われているプログラミング言語とその実行環境。 Mac OS X なら Homebrew を使うと割りとサクッとインストールできる。

使った環境は次の通り。

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.11.6
BuildVersion:   15G1004

下準備

ここでは Homebrew が既にインストールされている状態を仮定する。 もし、まだインストールしていないときは公式サイトの記載にもとづいて入れる。

brew.sh

まずは Homebrew を最新の状態にしておく。 Homebrew では、何をするにしても最初に brew doctor コマンドを打って問題がないことを確認しておくとハマることが少なくなる。

$ brew update && brew doctor && brew upgrade

インストール

(2019-06-19 追記) 現在、R は標準リポジトリに存在するため science リポジトリをタップする必要はないと教えていただきました。 ありがとうございます。

R はデフォルトでは参照されない science リポジトリにあるので、まずは tap しておく。

$ brew tap homebrew/science

あとはパッケージ名に r を指定してインストールするだけ。

$ brew install r

使い方

インストールすると r というコマンドで起動できる。

$ r

R version 3.3.1 (2016-06-21) -- "Bug in Your Hair"
Copyright (C) 2016 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin15.6.0 (64-bit)

R は、自由なソフトウェアであり、「完全に無保証」です。
一定の条件に従えば、自由にこれを再配布することができます。
配布条件の詳細に関しては、'license()' あるいは 'licence()' と入力してください。

R は多くの貢献者による共同プロジェクトです。
詳しくは 'contributors()' と入力してください。
また、R や R のパッケージを出版物で引用する際の形式については
'citation()' と入力してください。

'demo()' と入力すればデモをみることができます。
'help()' とすればオンラインヘルプが出ます。
'help.start()' で HTML ブラウザによるヘルプがみられます。
'q()' と入力すれば R を終了します。

>

zsh を使っているときの注意点

シェルに zsh を使っていると、上記の手順で r が起動できない問題にハマる。

$ echo $SHELL
/bin/zsh

具体的には、インストールするときに使ったコマンドがなぜか実行されてしまう。

$ r
brew install r
Warning: homebrew/science/r-3.3.1_3 already installed

実は zsh では r がシェルの組み込みコマンドになっている。 これを打ち込むと、前回のコマンドを繰り返し実行してしまう。

$ which r
r: shell built-in command

本来の r を起動したいときは、シェルの組み込みコマンドの r を無効にしよう。

$ disable r

しかる後に r と打ち込もう。

$ r

R version 3.3.1 (2016-06-21) -- "Bug in Your Hair"
Copyright (C) 2016 The R Foundation for Statistical Computing
Platform: x86_64-apple-darwin15.6.0 (64-bit)

R は、自由なソフトウェアであり、「完全に無保証」です。
一定の条件に従えば、自由にこれを再配布することができます。
配布条件の詳細に関しては、'license()' あるいは 'licence()' と入力してください。

R は多くの貢献者による共同プロジェクトです。
詳しくは 'contributors()' と入力してください。
また、R や R のパッケージを出版物で引用する際の形式については
'citation()' と入力してください。

'demo()' と入力すればデモをみることができます。
'help()' とすればオンラインヘルプが出ます。
'help.start()' で HTML ブラウザによるヘルプがみられます。
'q()' と入力すれば R を終了します。

>

RStudio

お好みに応じて RStudio も入れたりしよう。 こちらは cask リポジトリにある。

$ brew tap caskroom/cask
$ brew cask install rstudio
$ open /Applications/RStudio.app

めでたしめでたし。