Mac に LightGBMをインストールする

手順のメモです。

公式ドキュメントにイストールガイドがありますが、実は使うのはこちらではありません。
Installation Guide

僕はpythonから使う予定なので、Githubのリポジトリの方を読んで作業します。
LightGBM Python-package

次のコマンドを順番に実行します。


brew install libomp
pip install wheel
pip install lightgbm

自分の環境では wheel はすでにインストールされているという趣旨のメッセージが出ました。


$ pip install wheel
Requirement already satisfied: wheel in ./.pyenv/versions/anaconda3-5.2.0/lib/python3.6/site-packages (0.31.1)

これでインストールできたはずなのですが、インポートしてみると次の警告がでます。


$ python
>>> import lightgbm
/Users/xxxxxx/.pyenv/versions/anaconda3-5.2.0/lib/python3.6/site-packages/lightgbm/__init__.py:46: UserWarning: Starting from version 2.2.1, the library file in distribution wheels for macOS is built by the Apple Clang (Xcode_8.3.3) compiler.
This means that in case of installing LightGBM from PyPI via the ``pip install lightgbm`` command, you don't need to install the gcc compiler anymore.
Instead of that, you need to install the OpenMP library, which is required for running LightGBM on the system with the Apple Clang compiler.
You can install the OpenMP library by the following command: ``brew install libomp``.
  "You can install the OpenMP library by the following command: ``brew install libomp``.", UserWarning)

Warningは出るものの、動作に問題はなさそうなので一旦はこのまま使うおうと思っています。
とはいえ気持ち悪いのでいつか直したいです。
brew install libomp は実行してるし、 brew list すると、libompは出てくるのですけどね。

Xcodeがどうのこうのと書かれているので、以下の記事で行なったcommand Line Toolsのインストールが影響してるような気もします。

Mac(Mojave) に pip で mecab-python3をインストールする時にはまった

複数のDataFarameを1つのExcelファイルに書き出す

pandasのDataFrameを保存したい時、to_excel()関数を使うと手軽にExcelファイルに書き出せます。
ただ、 df.to_excel(“ファイルパス”) というやり方だと、1ファイルに1個のデータフレームしか書き出せません。

複数のデータフレームをシートを分けてエクセルファイルに保存するときは、
ExcelWriter というのを使います。
ドキュメント: pandas.ExcelWriter

実際に使ってみましょう。
with を使うと便利です。 (使わない方法もあります)


import numpy as np
import pandas as pd
dataframe_list = [
    pd.DataFrame(np.random.randn(100, 10)) for _ in range(3)
]
with pd.ExcelWriter("./3sheets.xlsx") as writer:
    for i, df in enumerate(dataframe_list):
        df.to_excel(writer, sheet_name=f"シート_{i}")

sheet_name は指定しておかないと、同じシートに上書きされてしまい、
最後のDataFrameしか残らないので注意です。

フォーマット済み文字列リテラル

Pythonの3.6からの新機能で、フォーマット済み文字列リテラルというのがあることを知りました。
ドキュメント:2.4.3. フォーマット済み文字列リテラル

要は、文字列を定義する時にfかFを頭につけておくと、
その文字列の中で{}で囲んだ変数がその値に置換されるというものです。


name = "Fred"
print(f"He said his name is {name}.")
# He said his name is Fred.

これまでformat()を使うことが多かったのですが、それよりも便利そうです。
ちなみに、format()を使うなら次のように書きます。少し長いですね。


print("He said his name is {name}.".format(name=name))
# He said his name is Fred.

{, }の文字を使いたい時は{{, }}のようにそれぞれ2重に書いておけば使えます。


print(f"{{He said his name is {name}.}}")
# {He said his name is Fred.}

この他、文字列をクオーテーションで囲んだり、数値の桁数の指定など様々なオプションが使えるようです。
ほぼコピペですが、ドキュメントに乗っていたサンプルコードを一通り動かしてみました。


import decimal
from datetime import datetime

name = "Fred"
print(f"He said his name is {name!r}.")
# "He said his name is 'Fred'."
print(f"He said his name is {repr(name)}.")  # repr() is equivalent to !r
# "He said his name is 'Fred'."
width = 10
precision = 4
value = decimal.Decimal("12.34567")
# : を使って表示桁数を指定
print(f"result: {value:{width}.{precision}}")
# 'result:      12.35'
today = datetime(year=2017, month=1, day=27)
# 日付の書式指定
print(f"{today:%B %d, %Y}")
# 'January 27, 2017'
number = 1024
# 16進法表記
print(f"{number:#0x}")
# '0x400'

pyenvで作成した環境を消す

うまくインストールできないライブラリがあり、
pipで入れたり消したり、condaで入れたり消したりとやっていたら何やら全体的に環境がおかしくなってしまいました。
(またかよという感じですが。)

しかたがないので、その環境は消して作り直すことにしました。
pyenv uninstall で環境は消せます。
参考 : pyenv uninstall


# 確認
$ pyenv versions
  system
* anaconda3-5.2.0 (set by /Users/******/.pyenv/version)

# 利用するpythonの環境をsystemのものに戻す。
$ pyenv global system
$ python --version
Python 2.7.10

# 不要になった環境を消す
$ pyenv uninstall anaconda3-5.2.0
# もう一度入れ直し
$ pyenv install anaconda3-5.2.0
$ pyenv global anaconda3-5.2.0
$ python --version
Python 3.6.5 :: Anaconda, Inc.

あとはこのブログで pip で検索して、うまくインストールできていたものを順に入れていけば元に戻るはず。
そもそも、anacondaで作った環境でcondaではなくpipを使ってるのがトラブルの原因でもあるのですが、
condaとpipの違いとか使い分けとか十分理解できてないので、どうしてもpipの方使っちゃいます。
この辺りはいつかしっかり勉強したい。

DataFrameの変化率の計算

前回の記事が差分だったので次は変化率です。
ファイナンス系のデータをはじめとして変化量よりも変化率の方が着目される例は多々あります。

pandasのDataFrameにおいては、pct_changeというメソッドで算出することができます。
pandas.DataFrame.pct_change

とりあえず使ってみましょう。


import pandas as pd
df = pd.DataFrame(
        {
            'a': [1, 2, 3, 4, 5],
            'b': [1, 4, 9, 16, 25],
            'c': [1, 8, 27, 64, 125],
        }
    )
print(df.pct_change())
'''
          a         b         c
0       NaN       NaN       NaN
1  1.000000  3.000000  7.000000
2  0.500000  1.250000  2.375000
3  0.333333  0.777778  1.370370
4  0.250000  0.562500  0.953125
'''

結果を見てわかる通り、1から4への変化は 4/1 = 4 と計算されるのではなく、 (4-1)/1 = 3 になります。
その点だけ注意です。

Dataframeの差分を取る

pandasの累積和をとったり、特定のwindowごとの和をとったりする方法はこれまで紹介してきたので、
次はDataFrameの差分を取る関数の紹介です。
元のデータが単位根過程の時に差分をとって定常化するなど、結構頻繁に使います。

さて、その方法ですが、DataFrameのdiffというメソッドを使います。

pandas.DataFrame.diff

引数には periods と axis を指定できますが、大抵はどちらもデフォルトでいけるでしょう。
periods(デフォルト:1)を指定することで、いくつ前の値との差分を取るかを指定でき、
axis(デフォルト:0) に1を指定すると隣の行との差分をれます。


import pandas as pd
df = pd.DataFrame(
        {
            'a': [1, 2, 3, 4, 5],
            'b': [1, 4, 9, 16, 25],
            'c': [1, 8, 27, 64, 125],
        }
    )
print(df.diff())
'''
     a    b     c
0  NaN  NaN   NaN
1  1.0  3.0   7.0
2  1.0  5.0  19.0
3  1.0  7.0  37.0
4  1.0  9.0  61.0
'''

print(df.diff(periods=2))
'''
     a     b     c
0  NaN   NaN   NaN
1  NaN   NaN   NaN
2  2.0   8.0  26.0
3  2.0  12.0  56.0
4  2.0  16.0  98.0
'''

print(df.diff(axis=1))
'''
    a     b      c
0 NaN   0.0    0.0
1 NaN   2.0    4.0
2 NaN   6.0   18.0
3 NaN  12.0   48.0
4 NaN  20.0  100.0
'''

なお、それぞれ次のようにshiftして値をずらしたものとの差分を取るのと結果は同じです。


df - df.shift()
df - df.shift(periods=2)
df - df.shift(axis=1)

DataFrameのexpandingについて

以前の記事で、pandas DataFrameの window関数である、 rollingを紹介しました。
参考:pandasで移動平均や高値線、安値線を計算する
これは、直近n個の値に対して和や最大値、最小値、平均や分散を求めるものです。

場合によっては、直近n個ではなく最初の値から全てに対して順に行いたいことがあると思います。
演算が和であればcumsumで累積和を取るように。

どうやらpandasのversion 0.18.0からそのような関数が実装されたようです。
pandas.DataFrame.expanding

とりあえずやってみましょう。
和だったらcumsumを使えば済む話なので、
平均と標準偏差でやってみます。
(元データが乱数だとそんなに面白いものでも無いので、結果は省略。コピーして動かしてみてください。)


# データ生成
import pandas as pd
import numpy as np
df = pd.DataFrame(np.random.randn(100, 5))
print(df.shape)  # (100, 5)

# 平均
df.expanding().mean()

# 標準偏差
df.expanding().std()

# aggを使ってまとめて算出も可能。
df.expanding().agg(["mean", "std"])

pandasのDataFrameから特定のデータ型の列を抜き出す

読み込んだデータフレームが、列ごとにバラバラのデータ型を持っていて特定の型の列だけ抜き出したいという場面はよくあります。
以前は dtype 属性で調べて列名をピックアップして処理したりしていたのですが、専用のメソッドがあったので紹介します。

pandas.DataFrame.select_dtypes

これを使うと文字列型の列だけとか、数値型の列だけとか値を抽出できます。

とりあえず実験用のデータフレーム作成。


import pandas as pd
import numpy as np
df = pd.DataFrame(
        {
            'a': np.random.randint(100, size=10),
            'b': np.random.choice([True, False], size=10),
            'c': np.random.randn(10),
            'd': np.random.choice(list('abcd'), size=10),
        }
    )
print(df.dtypes)
'''
a      int64
b       bool
c    float64
d     object
dtype: object
'''

使い方は簡単で、
select_dtypesの引数に必要なデータ型を値かリストで渡すだけです。
また、excludeで逆に不要なデータ型を指定することもできます。

例えば数値だけ欲しい時(列aと、列c)次のように指定できます。
3つとも結果は同じです。


df.select_dtypes([int, float])
df.select_dtypes([np.int, np.float64])
df.select_dtypes(["int", "float"])

欲しいデータ型一種類のときは配列ではなくスカラーで渡しても大丈夫です。


df.select_dtypes(object)
df.select_dtypes(bool)

逆に不要な型を指定する例。


df.select_dtypes(exclude=object)

Page Analytics でGoogle アナリティクスの情報を可視化する

プライバシーポリシーに書いている通り、このブログにはGoogle アナリティクスを導入しています。
日々のアクセス数が見れて、僕のモチベーションに大いに貢献してくれています。

このGoogle アナリティクスの情報ですが、Chromeの拡張機能を使うと、GAのサイトに行くことなく、自分のブログ上に表示することができます。
(所有者にしか使えないので、ぜひご自身のサイトでお試しください。)

その拡張機能がこちらです。
Page Analytics (by Google)
Chromeでリンク先へ遷移し、拡張機能をインストールするとURLバーの右にオレンジのアイコンが追加されます。
自分のサイトを開いた状態でこれをONにすると、そのページのアクセス情報が観れるという優れものです。

各リンクのクリック率をページ上に可視化したり、クリック率によって着色したりできて楽しいでおすすめです。

圧縮行格納方式(CRS)の疎行列のデータ構造

今回も疎行列のお話です。
前回の記事で登場したcrsとcscについて、具体的にどのようなデータ構造なのかを紹介します。
ちなみにcrsとcscはそれぞれ、
圧縮行格納方式 (Compressed Sparse Row) と、
圧縮列格納方式 (Compressed Sparse Column) の略です。
ほぼ同じ処理を行方向に行うか列方向の違いしかなく、転置を取るとそれぞれ入れ替わるので、 CSRの方を紹介します。

ちなみに、 wikipediaの説明で理解したので、それをみながら記事を書いています。
例として取り上げる行列はこれ。(wikipediaの例と同じ。)
$$
\left(
\begin{matrix}
1 & 2 & 3 & 0 \\
0 & 0 & 0 & 1 \\
2 & 0 & 0 & 2 \\
0 & 0 & 0 & 1 \\
\end{matrix}
\right)
$$

まず、csr形式のデータで作りましょう。
今回はarrayで作って変換するのではなく、お作法にしたがい、lil形式で生成してから変換します。


from scipy import sparse
# 成分が全て0の 4 * 4 の lil形式の疎行列を作成。
M_lil = sparse.lil_matrix((4, 4))
# 各成分を代入。
M_lil[0, 0] = 1
M_lil[0, 1] = 2
M_lil[0, 2] = 3
M_lil[1, 3] = 1
M_lil[2, 0] = 2
M_lil[2, 3] = 2
M_lil[3, 3] = 1
# M_csr形式に変換
M_csr = sparse.csr_matrix(M_lil)
# 確認
print(M_csr)
# 出力
'''
  (0, 0)    1.0
  (0, 1)    2.0
  (0, 2)    3.0
  (1, 3)    1.0
  (2, 0)    2.0
  (2, 3)    2.0
  (3, 3)    1.0
'''

これで、csr形式の変数、M_csrに例の行列が格納されました。
printすると整形されて表示されるのですが、実際のデータ構造はこうはなっていません。
wikipediaの説明と、ドキュメントをみながら確認します。
まず、 実際のデータは、次の3つの属性に格納されています。

data ・・・ CSR format data array of the matrix
indices ・・・ CSR format index array of the matrix
indptr ・・・ CSR format index pointer array of the matrix

具体例を見てから説明します。


print(M_csr.data)
# [1. 2. 3. 1. 2. 2. 1.]
print(M_csr.indices)
# [0 1 2 3 0 3 3]
print(M_csr.indptr)
# [0 3 4 6 7]

まず、data が 疎行列の0では無い要素の値を、左上から行方向(右側へ)に順番に並べたものです。
(csrのrが対応。 cscの場合はここで列方向(下向き)に並べたものになります。)
そして、indices が、それぞれの要素が、何列目の要素なのかを示す配列です。

明らかにわかるように、data と indices の要素の数はその疎行列の0では無い成分の個数です。
あとは、dataの各要素が何行目なのかがわかれば、行列を復元できますが、
それを担っているのが、indptr です。
これだけ、wikipediaの説明と異なっていて非常にわかりにくいですが、次のように解釈できます。


# 行列の最初の行のデータは、indptrの最初の2個のデータで作ったスライスの値
print(M_csr.data[0: 3])
# [1. 2. 3.]
# 次の行のデータは、indptrの一つずらした2個のデータで作ったスライスの値
print(M_csr.data[3: 4])
# [1.]
# 以下繰り返し
print(M_csr.data[4: 6])
# [2. 2.]
print(M_csr.data[6: 7])
# [1.]

明らかにわかる通り、 indptr の要素の個数は行の数より1つ大きくなります。

これで、csr_matrixの中のデータの構造がわかりました。
また、data属性の中に行単位でデータが固まって存在してて、
行単位の取り出しや演算が得意なことにも納得できると思います。