読者です 読者をやめる 読者になる 読者になる

CUBE SUGAR CONTAINER

技術系のこと書きます。

Ubuntu 16.04 LTS に後から GUI (X Window System) を追加する

Ubuntu16.04LTS X Window System

Ubuntu 16.04 LTS をサーバ版でインストールした後から、やっぱり GUI が欲しいよねってときがある。 今回は、そんなときの対処法について。

使った環境は次の通り。

$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.1 LTS"
$ uname -r
4.4.0-38-generic

下準備

まずは apt-get コマンドの更新サイトを最新の状態にしておく。

$ sudo apt-get update

デスクトップ環境が欲しいとき

デスクトップ環境が欲しいときは ubuntu-desktop をインストールする。

$ sudo apt-get -y install ubuntu-desktop

これには長い時間がかかるので気長に待とう。

インストールが終わったら再起動する。

$ sudo shutdown -r now

これでデスクトップ環境が入る。

X Window System だけで良いとき

デスクトップ環境までは不要で X Window System だけあれば良いときは xserver-xorg をインストールする。

$ sudo apt-get -y install xserver-xorg

これだけで良い。

統計: ピアソンのカイ二乗検定で標本が理論分布と適合しているか調べる

Python SciPy 統計

例えば、ある六面ダイス (サイコロ) に歪みがないことを調べたいとする。 もしサイコロに歪みが無いなら、出る目の理論的な度数分布はどれも  \frac{1}{6} となるはず。 しかし、サイコロの出る目は無限母集団なので、実際にすべてのパターンを試して確認することができない。 つまり、全数調査は不可能ということ。 そのため、歪みがあるか否かは実際に何度かそのサイコロを振った有限な結果から推測する必要がある。 これはつまり、標本から母集団の分布を調べる推測統計になる。

上記のようなシチュエーションでは、今回紹介するピアソンのカイ二乗検定という方法が使える。 ピアソンのカイ二乗検定で適合度を調べると、実際に振ってみた結果から母集団が理論分布となっているか否かが判断できる。 この検定はノンパラメトリックなので、特定の分布の仕方には依存しないところが便利に使える。

ピアソンのカイ二乗検定の公式 (適合度)

まず、ピアソンのカイ二乗検定では、カイ二乗値という統計量を計算することになる。 これは、次のような公式で計算する。

 \chi^{2} = \displaystyle \sum_{i = 1}^{n} \frac{(O_i - E_i)^{2}}{E_i}

上記で  O は実際に観測した値、つまり標本を表している。 また、 E は期待値、つまり母集団を表している。 変数となっている  {}_i は分類、つまりサイコロでいえば出目に相当する。

数式の意味をざっくり説明すると、期待値と観測した値の差を取ってそれを二乗している。 ここで、二乗するので得られる値は必ず正の値になることが分かる。 そして、その値をまた期待値で割っている。 これは、期待値とのズレを二乗した値と、期待値との比率を計算していることになる。 そして、その比率をすべての分類ごとに足し合わせたのがカイ二乗値となる。

で、得られた値はどうするかというと、自由度と有意水準にもとづいてカイ二乗分布表と比べる。 そして、カイ二乗分布表の値よりも小さければ母集団が理論上の分布に沿っていると判断できる。 ただ、ここは今いっぺんに説明するよりも、あとから詳しく書いた方が分かりやすいと思う。

実際に計算してみよう

それでは、実際にサイコロのカイ二乗値を計算してみることにしよう。 ただ、本物のサイコロを用意して振るのがめんどくさかったので計算機のシミュレーションで済ませてしまうことにする。 これも、モンテカルロ法という名前のついた実験方法のひとつだ。

モンテカルロ法を実施する環境としては Python を使うことにしよう。

$ python --version
Python 3.5.2

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

$ python

REPL が起動したら、サイコロに相当する関数 dice() を定義する。

>>> import random
>>> def dice():
...     return random.randint(1, 6)
...

この関数を実行するとサイコロの出目が 1 から 6 の間でランダムに得られる。

>>> dice()
3
>>> dice()
5
>>> dice()
5

果たして、このサイコロは歪んでいるのか、それとも歪んでいないのか。 今回は、それをカイ二乗検定で確かめたい。

ダイスを所定の回数だけ振る

まず、カイ二乗検定を使うときは、次の条件を満たすようにすべきらしい。

 np_i \ge 10

上記の  n は標本数で  p_i は分類の理論的な出る確率を表している。

今回、理論的な確率は全ての出目で  \frac{1}{6} となることを想定している。

 p_i = \frac{1}{6}

そのため  n について解くと

 n \frac{1}{6} \ge 10

から

 n \ge 10 \times 6 = 60

となるため、標本は最低でも 60 以上ほしい。 これはつまり、カイ二乗値を計算するのに最低でも 60 回はサイコロを振る必要があるということ。

そこで、ひとまず多めに 120 回振ることにした。 Python には collections.Counter という種別ごとに登場する回数を数えるためのクラスがあるので、それを使う。

>>> from collections import Counter
>>> c = Counter([dice() for _ in range(120)])
>>> c
Counter({1: 22, 3: 22, 2: 21, 6: 21, 5: 20, 4: 14})

上記はサイコロの出目が 1 のときは 22 回あった、というように読める。 つまり 1 が出た回数が一番多くて、4 が出た回数が一番少なかったということ。

上記は出た回数が多い順にソートされていて、ちょっと見づらい。 そこで、出目ごとにソートすると、こんな感じ。

>>> sorted(c.items(), key=lambda t: t[0])
[(1, 22), (2, 21), (3, 22), (4, 14), (5, 20), (6, 21)]

カイ二乗値を計算する

標本が得られたのでカイ二乗値を計算する準備が整った。 先ほどの公式をもう一度見てみよう。

 \chi^{2} = \displaystyle \sum_{i = 1}^{n} \frac{(O_i - E_i)^{2}}{E_i}

上記で  O_i が観測値なので、これはさっきサイコロを振って得られた。

そして  O_i は期待値となっている。 これは  np_i で計算できるので、こうなる。  n が標本数で  p_i が確率だった。 確率は、すべての出目で  \frac{1}{6} となるのは前述した通り。

 E_i = np_i = 120 \times \frac{1}{6} = 20

つまり期待値として使う値はすべての出目で 20 にすれば良い。

ということで、実際に数式に値を代入してみると、こうなる。

 \chi^{2} = \displaystyle \sum_{i = 1}^{n} \frac{(O_i - E_i)^{2}}{E_i} = \frac{(22 - 20)^{2}}{20} + \frac{(21 - 20)^{2}}{20} + \frac{(22 - 20)^{2}}{20} + \frac{(14 - 20)^{2}}{20} + \frac{(20 - 20)^{2}}{20} + \frac{(21 - 20)^{2}}{20}

上記で使った値として、それぞれの出目の回数を見に戻るのが大変だろうから再掲しておく。

>>> o = [i[1] for i in sorted(c.items(), key=lambda t: t[0])]
>>> o
[22, 21, 22, 14, 20, 21]

これは、計算してみると次のようになる。

 \chi^{2} = \frac{4 + 1 + 4 + 36 + 0 + 1}{20} = \frac{46}{20} = \frac{23}{10} = 2.3

Python で計算するまでもないけど、一応確かめておこう。 すでに標本の入った変数 o はあるので、あとは期待値の入った変数 e を用意しよう。 これは、すべてに 20 が入った要素数 6 のリストになる。

>>> e = [20 for _ in range(6)]
>>> e
[20, 20, 20, 20, 20, 20]

順を追って計算していこう。 まずは標本から期待値を引いて二乗しよう。 これで公式の分母部分が計算できる。

>>> [(oi - ei) ** 2 for oi, ei in zip(o, e)]
[4, 1, 4, 36, 0, 1]

それらを足すと、こうなる。

>>> sum([(oi - ei) ** 2 for oi, ei in zip(o, e)])
46

そして、それを期待値で割る。

>>> sum([(oi - ei) ** 2 for oi, ei in zip(o, e)]) / 20
2.3

ちゃんと 2.3 になった。

標本の自由度を計算する

次に、カイ二乗分布表を参照するために標本の自由度を計算しよう。 自由度というのは、標本でいくつまで種別の値が埋まると、種別全体の値が確定するかという値になっている。

今回の例なら標本数が 120 というのは、あらかじめ分かっている。 そして種別は 6 種類あって、各種別が何回登場するかは 5 種類の値が分かれば確定する。 これはつまり 1 から 5 までの出目の回数が分かってしまえば、6 が何回出たかは自然と分かるということ。 ということで、今回使う自由度  df はサイコロの面の数である 6 から 1 を引いた値で 5 になる。

 df = k = 1 = 6 - 1 = 5

ただし、これには注意点があって、使う自由度の数は母分布によって異なる。 サイコロであれば全ての種別が同じ確率で出るので一様分布になる。 一様分布であれば自由度は種別の数から 1 を引いたもので良い。 これがピアソン分布や二項分布であれば 2 を、正規分布であれば 3 を引いて使う。

カイ二乗分布表を参照する

自由度が求まったので、それにもとづいてカイ二乗分布表を参照しよう。 通常であれば、あらかじめ求めてあるものを用いる。 例えば、次のサイトなどにあるようなもの。

χ2分布表

なぜ、ここでカイ二乗分布表というものが出てくるのか疑問に思うかもしれない。 実は、先ほど計算したカイ二乗値というものは、自由度によってどのような値が出るのかが既に分かっている。 これがカイ二乗分布表というもので、ようするにどんな値がどれくらいの確率で出るか書いてある。

表の見方としては、まず先ほど求めた自由度 (df) にもとづいて 5 の行を参照する。 ひとつの行には色々な数値が並んでいるが、これは先ほど計算したのと同じカイ二乗値になっている。 それぞれのカイ二乗値は何が異なるかというと、その値になる確率が異なっている。

上にある列を見ると有意確率が書いてある。 例えば有意確率 0.99 の列で自由度が 5 の項目を見ると 0.55 となっている。 これは、自由度 5 のカイ二乗値は 99% の確率で 0.55 以上となることを表している。

有意水準を決める

カイ二乗検定というのは、仮説検定の一種となっている。 仮説検定の考え方では、ある統計量を計算したとき、その値がどれくらいの確率で出るのかを考える。

例えば、その値が出る確率が 5% 未満や 1% 未満であれば、とても起こりづらいことは明らかだ。 そんなとき、仮説検定では「珍しいことが起こった」のではなく「本来起こるべきでないことが起きた」と考える。 例えば、サイコロでいえば仮に歪みがなかったとしても偶然偏った値になるのはありえることだろう。 しかし、そんなとき仮説検定の考え方では「偶然偏った値になった」のではなく「サイコロ自体に歪みがあった」という結論にする。

もちろん、この考え方は 5% 未満や 1% 未満など、ある水準で間違った結論を導いてしまう恐れはある。 これを統計では過誤という用語で呼ぶ。 しかし、その過誤が特定の確率で起こることは折り込んだ上で、そのように考えるらしい。 この 5% や 1% といった基準のことを有意水準と呼んで、記号では  \alpha が使われることが多い。 仮説検定では、この有意水準にもとづいて統計量を考える。

有意水準は一般的に 5% (0.05) もしくは 1% (0.01) を採用することが多い。 今回は試しに 5% (0.05) を使って考えてみることにしよう。 これは 5% の確率で過誤が起こりうる、という意味でもある。

カイ二乗値を比べる

それでは、カイ二乗分布表をもう一度見てみよう。 有意確率が 5% (0.05) で自由度が 5 の項目の値は 11.07 となっている。

カイ二乗分布表の値は、それ以上の値が出る確率がどれだけあるか、というものだった。 つまり、自由度が 5 のカイ二乗値が 11.07 以上になる確率は 5% ということだ。 ようするに計算したカイ二乗値が 11.07 以上になると、サイコロが歪んでいると判断できるわけ。

では、先ほど計算したカイ二乗値はいくつだっただろうか。 その値は 2.3 で、あきらかに 11.07 と比べると小さいことが分かる。

 \chi^{2} = 2.3 \lt 11.07

つまり、このカイ二乗値は全然ありうる値で、サイコロは歪んでいないという結論に至った。

SciPy で計算してみる

ちなみに、上記の計算は手作業が多くてなかなか面倒くさい感じだと思う。 特にカイ二乗分布表を参照することなんかは間違えやすそうだ。 そこで Python の SciPy というパッケージを使った方法も紹介しておくことにする。

SciPy はサードパーティ製のパッケージなので、まずは pip を使ってインストールしておこう。

$ pip install scipy

そして、改めて Python の REPL を起動する。

$ python

先ほどの出目と同じ状況を用意する。 変数 o が標本で変数 e が期待値となる。

>>> o = [22, 21, 22, 14, 20, 21]
>>> e = [20 for _ in range(6)]

続いてカイ二乗検定をする関数を SciPy からインポートしよう。 これには scipy.stats.chisquare() を使う。

>>> from scipy.stats import chisquare

あとは、この関数に先ほどの変数 oe を渡すだけだ。

>>> chisquare(o, e)
Power_divergenceResult(statistic=2.2999999999999998, pvalue=0.80626686988512852)

するとカイ二乗値が 2.23 で、p-値というのが 0.80 と計算されているのが分かる。

p-値というのは、それがどれくらい出やすいのかを表した値になっている。 上記であれば 0.80 なので、この値が出る確率は 80% となって全然珍しくないことが分かる。

サイコロが歪んでいるかは、有意水準よりも小さいかで判断する。 例えば有意水準が 5% であれば次のようにすれば良いというわけ。

>>> chisquare(o, e).pvalue < 0.05
False

5% の確率で間違っている恐れはあるものの、このサイコロは歪んでいないことが分かった。

コクがありそうなサイコロを使ってみる

先ほど試したのは一様乱数にもとづいたサイコロだった。 次は、あえて歪んだサイコロを振って試してみよう。

今度は、次のようにして正規乱数にもとづいたサイコロを使ってみる。 このサイコロは、小さな値ほど出やすいことになる。

>>> import random
>>> def dice():
...     return int(abs(random.normalvariate(0, 1) * 6)) % 6 + 1
...

今度は SciPy を使って一気に計算させるのでサイコロを振る数も 1200 回に増やしてみる。

>>> n = 1200
>>> c = Counter([dice() for _ in range(n)])
>>> o = [i[1] for i in sorted(c.items(), key=lambda t: t[0])]
>>> e = [n / 6 for _ in range(6)]

この結果、カイ二乗値は 69.33 が得られた。 p-値は  1.4e^{-13} というとんでもなく小さな値になっている。

>>> from scipy.stats import chisquare
>>> chisquare(o, e)
Power_divergenceResult(statistic=69.330000000000013, pvalue=1.4126539924787112e-13)

これを有意水準 5% で検定すると、このサイコロは歪んでいることが分かった。

>>> chisquare(o, e).pvalue < 0.05
True

まとめ

  • カイ二乗検定を使うと理論分布と適合しているかを調べることができる

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

Python 統計

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

定義

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

 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 で複数のバージョンをインストールする

Mac OS X JavaScript Node.js

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 Mac OS X

諸事情により 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 Mac OS X Python

以前、このブログで 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!

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