numpyでビンを作成する

以前の記事で、pandas.cutを使ってデータをビンに区切る方法を紹介しました。
参考:pandasで数値データを区間ごとに区切って数える

これはこれで便利なのですが、似たようなことを行う関数がnumpyにも実装されていたのでその紹介です。
個人的にはこちらの方が好きです。
numpy.digitize

引数は次の3つをとります。
x : 元のデータ
bins : 区切り位置のリスト (1次元のリストで単調増加か単調減少のどちらかであることが必須)
right : 統合をどちらの端に含むか。(binsが単調増加か減少かも関係するのでドキュメントの説明を見ていただくのが確実です)

これを使うと、xの各データが、binsで区切られたなんばんめの区画に含まれるのかのリストを返してくれます。
binsは配列で渡すので等間隔でなくても使えます。

動かしてみたのがこちら。


import numpy as np
x = np.random.randint(200, size=10) - 100
print(x)
# [ 20  77  23 -50 -18 -80 -17  45  66  83]
print(np.digitize(x, bins=[-50, -10, 0, 10, 50]))
# [4 5 4 1 1 0 1 4 5 5]

bins に 5つの要素があるので、両端も含めて6つのbin(0〜5)にデータが区切られます。
例えば最初の20は、10<=20<50 なので、4番目の区画ですね。
right を省略し、左側に統合がついているので、
-50<=-50<-10 となり、-50は1番目の区画に入るということも確認できます。

Interval オブジェクトではなく、ただの数列で値を返してくれるのもありがたい。
(Intervalオブジェクトも便利なのかもしれませんがまだ慣れない。)

numpyで重み付き平均

つい最近まで、numpyやpandasには重み付き平均を求める関数は無いと勘違いしていて、
必要な時は自分で実装したのを使っていました。

データと重みが numpy の array で渡される場合だけ対応するのであればこのような関数で計算できます。
(listなどにも対応しようと思うとこれでは動きません)


def weighted_mean(data, weights):
    return np.sum(data * weights) / np.sum(weights)

しかしよくよく調べてみると、いつも使っている numpy.mean のほかにも、
numpy.averageという関数があって、これは引数にweightsを渡せるでは無いですか。
(averageの方が常に優秀というわけではなく、 meanにしか無い引数もあります。)

参考:
numpy.average
numpy.mean

numpy.average を使うと重み付き平均を手軽に計算できます。
せっかくなので適当なデータについて上の関数と結果を見比べましょう。
(ついでに計算直書きしたのも並べて確認しました。)


import numpy as np
data = np.array([1, 3, 7])
weights = np.array([5, 12, 2])
print(weighted_mean(data, weights))
# 2.8947368421052633
print(np.average(data, weights=weights))
# 2.8947368421052633
print((1*5+3*12+7*2)/(5+12+2))
# 2.8947368421052633

完全一致してますね。

Pythonでワードクラウドを作成する

テキスト中の単語の出現頻度を可視化する方法として、ワードクラウド(word cloud)というのがあります。
要は頻出の単語ほどでっかい面積を占拠できるように可視化する方法ですね。

これをPythonで作る時、その名もズバリ wordcloudというライブラリがあり、非常に手軽に使うことができます。

リポジトリ: amueller/word_cloud

インストールはpipでできます。


$ pip install wordcloud

20newsgroups のデータを使ってやってみましょう。
あまりにもごちゃごちゃすると意味がわからないので、カテゴリを一個に絞ってやってみます。(今回は sci.electronics にしました)
細かいですが、STOPWORDS があらかじめ用意されているのもありがたいですね。


from wordcloud import WordCloud
from wordcloud import STOPWORDS
from sklearn.datasets import fetch_20newsgroups
import matplotlib.pyplot as plt
remove = ('headers', 'footers', 'quotes')
categorys = [
        "sci.electronics",
    ]
twenty_news = fetch_20newsgroups(
                                subset='train',
                                remove=remove,
                                categories=categorys
                            )
raw_data = twenty_news.data
wordcloud = WordCloud(
                            stopwords=STOPWORDS, background_color="white"
                        ).generate(" ".join(raw_data))

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1, 1, 1)
ax.imshow(wordcloud, interpolation='bilinear')
ax.axis("off")
plt.show()

結果がこちらです。

itertools でリストの部分集合をリストアップする

Pythonの標準ライブラリである、itertools を使うと、リストや集合(set)の部分集合を手軽にリストアップすることができます。
ドキュメントはこちら。
itertools — 効率的なループ実行のためのイテレータ生成関数

欲しい部分集合の性質(重複の可否や、ソートの有無)に応じて、次の3種つの関数が用意されています。

itertools.permutations(iterable, r=None)
長さrのタプル列、重複なしのあらゆる並び

itertools.combinations(iterable, r)
長さrのタプル列、ソートされた順で重複なし

itertools.combinations_with_replacement(iterable, r)
長さrのタプル列、ソートされた順で重複あり

試しに要素が5個のリストを用意して、 r=3 でソートされた順番で重複無しの
部分集合をリストアップしてみましょう。
$_5\mathrm{C}_3=\frac{5\cdot4\cdot3}{3\cdot2\cdot1}=10$なので、10組みの結果が得られるはずです。


import itertools
data = list("ABCDE")

for subset in itertools.combinations(data, 3):
    print(subset)

# 以下出力
('A', 'B', 'C')
('A', 'B', 'D')
('A', 'B', 'E')
('A', 'C', 'D')
('A', 'C', 'E')
('A', 'D', 'E')
('B', 'C', 'D')
('B', 'C', 'E')
('B', 'D', 'E')
('C', 'D', 'E')

想定通りの組み合わせが出ました。
また、結果はsetやlistではなく、タプルで返されることも確認できます。

複数の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'

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)