Python で解析 18

“Advent Calendar 2013 - Python で解析!” の十八日目。DataFrame - 13

今回は apply を取り上げる。DataFrame に任意の関数を実行するもので、 python の組み込み関数である map と思えばいいだろう。

1. データの準備

では、いつもの通りのデータの準備を。

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({
   ...:   'height': [162, 178, 167, 171, 170],
   ...:   'weight1': [51, 80, 55, 68, 71],
   ...:   'weight2': [53, 75, 51, 68, 73]
   ...: })

In [3]: df
Out[3]:
   height  weight1  weight2
0     162       51       53
1     178       80       75
2     167       55       51
3     171       68       68
4     170       71       73

2. DataFrame に apply を適用

例えば偶数の判定は次のように書ける。

In [4]: df.apply(lambda x: x % 2 == 0)
Out[4]:
  height weight1 weight2
0   True   False   False
1   True    True   False
2  False   False   False
3  False    True    True
4   True   False   False

ただ、この程度だと式で書けてしまう…。(--;

In [5]: df % 2 == 0
Out[5]:
  height weight1 weight2
0   True   False   False
1   True    True   False
2  False   False   False
3  False    True    True
4   True   False   False

3. Series に apply を適用

いつものごとく、特定の列にだけ apply を適用することもできる。

In [6]: df.height.apply(lambda x: x % 2 == 0)
Out[6]:
0     True
1     True
2    False
3    False
4     True
Name: height, dtype: bool

いい例を思いつかないので、 FizzBuzz をやってみた。

In [7]: def fizzbuzz(x):
   ...:     def fizz(x):
   ...:         return 'Fizz' if x % 3 == 0 else ''
   ...:     def buzz(x):
   ...:         return 'Buzz' if x % 5 == 0 else ''
   ...:     val = fizz(x) + buzz(x)
   ...:     return val if len(val) > 0 else x
   ...:

In [8]: df.height.apply(fizzbuzz)
Out[8]:
0    Fizz
1     178
2     167
3    Fizz
4    Buzz
Name: height, dtype: object

せっかくなので、DataFrame にも FizzBuzz を適用してみるが、DataFrame の場合は引数に Series が渡されるので、その対応をする必要がある。

In [9]: def fizzbuzz(x):
   ...:     def fizz(x):
   ...:         return 'Fizz' if x % 3 == 0 else ''
   ...:     def buzz(x):
   ...:         return 'Buzz' if x % 5 == 0 else ''
   ...:     def temp(x):
   ...:         val = fizz(x) + buzz(x)
   ...:         return val if len(val) > 0 else x
   ...:     return [it for it in x.apply(temp)]
   ...:

In [10]: df.apply(fizzbuzz)
Out[10]:
  height weight1   weight2
0   Fizz    Fizz        53
1    178    Buzz  FizzBuzz
2    167    Buzz      Fizz
3   Fizz      68        68
4   Buzz      71        73

今回はこんなところで。

Python で解析 17

“Advent Calendar 2013 - Python で解析!” の十七日目。DataFrame - 12

今回は DataFrame の演算を取り上げる。データは円単位だが表示はドルで行いたいとか、タイムゾーンを変換したい…などのシチュエーションが考えられる。

1. データの準備

では、いつもの通りのデータの準備を。

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({
   ...:   'height': [162, 178, 167, 171, 170],
   ...:   'weight1': [51, 80, 55, 68, 71],
   ...:   'weight2': [53, 75, 51, 68, 73]
   ...: })

In [3]: df
Out[3]:
   height  weight1  weight2
0     162       51       53
1     178       80       75
2     167       55       51
3     171       68       68
4     170       71       73

2. 一括演算

リストやタプルだと、ループなり、リスト内包表記なり…で、計算することになる。

In [4]: cm = [162, 178, 167, 171, 170]

In [5]: inch = [it * 0.39 for it in cm]

In [6]: inch
Out[6]: [63.18, 69.42, 65.13, 66.69, 66.3]

これでも十分に簡単な気もするが、Pandas を使う場合ともっと簡単に書ける。

In [7]: df.height * 0.39
Out[7]:
0    63.18
1    69.42
2    65.13
3    66.69
4    66.30
Name: height, dtype: float64

R などの統計処理系を使い慣れている人には当たり前のことだが、一般のアプリケーションプログラマーの中には、カルチャーショックを感じる人もいるかもしれない。

3. 集合と集合の演算

サンプルの weight1 と weight2 は、ダイエット前後の体重だとして、簡単な式でダイエットの成果を算出できてしまう。

In [8]: df.weight2 - df.weight1
Out[8]:
0    2
1   -5
2   -4
3    0
4    2
dtype: int64

4. 混在した演算

肥満かどうかの判断基準として、身長から 110 を引いた体重を目安とするというのがあるが、その基準体重と比べるとしたら、こういう式で書ける。

In [9]: df.weight2 - (df.height - 110)
Out[9]:
0     1
1     7
2    -6
3     7
4    13
dtype: int64

2 番が痩せ気味、4 番が肥満気味なサンプルデータだったが、それはともかく…。これはなかなか便利で、統計処理を使わない通常のプログラミングでも Pandas を使いたくなるかもしれない。

今回はこんなところで。

Python で解析 16

“Advent Calendar 2013 - Python で解析!” の十六日目。DataFrame - 11

今回は replace を取り上げる。間違ったデータを修正したいとか、プリフィックスを付けたいとか…そんなシチュエーションを想定している。

1. データの準備

では、いつもの通りのデータの準備を。

In [1]: import pandas as pd

In [2]: import re

In [3]: df1 = pd.DataFrame({
   ...:  u'名前': [u'山田', u'鈴木', u'佐藤', u'木村'],
   ...:  u'性別': [u'男', u'男', u'女', u'女'] ,
   ...:  u'身長': [181, 173, 159, 164],
   ...:  u'体重': [79, 71, 51, 52]
   ...: }, index=['3B005', '3B003', '3B002', '3B001'])

In [4]: df2 = pd.DataFrame({
   ...:  u'誕生日': ['1981/01/01', '1982/02/02', '1983/03/03', '1984/04/04']
   ...: }, index=['3B004','3B005','3B003', '3B002'])

In [5]: df3 = pd.DataFrame({
   ...:  u'好きなもの': [u'りんご', u'ばなな', u'いちご', u'メロン'],
   ...: }, index=['3B004','3B005','3B003', '3B001'])

In [6]: df = df1.join([df2, df3])

In [7]: df
Out[7]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04   NaN
3B001  52  木村  女  164         NaN   メロン

前回のを流用していたので join しているが、今回のテーマには関係がない。

2. 文字列の置換

メロンはウォーターメロンの間違いだったということで、'すいか' に置き換えてみる。

In [8]: df.replace(u'メロン', u'すいか')
Out[8]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04   NaN
3B001  52  木村  女  164         NaN   すいか

まとめて英語表記にしたいなら、ディクショナリーを渡す。

In [9]: df.replace({
   ...:   u'メロン': u'melon',
   ...:   u'いちご': u'strawberry',
   ...:   u'ばなな': u'banana'
   ...: })
Out[9]:
       体重  名前 性別   身長         誕生日       好きなもの
3B005  79  山田  男  181  1982/02/02      banana
3B003  71  鈴木  男  173  1983/03/03  strawberry
3B002  51  佐藤  女  159  1984/04/04         NaN
3B001  52  木村  女  164         NaN       melon

3. 正規表現で置換

正規表現も使える。試しに、プリフェックスを付けてみる。

In [10]: df.replace(re.compile('^'), 'pre_')
Out[10]:
       体重      名前     性別   身長             誕生日    好きなもの
3B005  79  pre_山田  pre_男  181  pre_1982/02/02  pre_ばなな
3B003  71  pre_鈴木  pre_男  173  pre_1983/03/03  pre_いちご
3B002  51  pre_佐藤  pre_女  159  pre_1984/04/04      NaN
3B001  52  pre_木村  pre_女  164             NaN  pre_メロン

これは影響範囲が大きいので、実際には列を絞って使うことになるだろう。

例のごとく、元の DataFrame には影響しない。

In [11]: df
Out[11]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04   NaN
3B001  52  木村  女  164         NaN   メロン

置換結果で元データを置き換えたい場合は、代入することになる。

In [12]: df = df.replace(re.compile('^'), 'pre_')

In [13]: df
Out[13]:
       体重      名前     性別   身長             誕生日    好きなもの
3B005  79  pre_山田  pre_男  181  pre_1982/02/02  pre_ばなな
3B003  71  pre_鈴木  pre_男  173  pre_1983/03/03  pre_いちご
3B002  51  pre_佐藤  pre_女  159  pre_1984/04/04      NaN
3B001  52  pre_木村  pre_女  164             NaN  pre_メロン

今回はこんなところで。

Python で解析 15

“Advent Calendar 2013 - Python で解析!” の十五日目。DataFrame - 10

1. データの準備

今回は、fillna を使ってみる。いつも綺麗なデータなら良いのだが、欠損しているデータをどうにかしなきゃならないシチュエーションだ。

In [1]: import pandas as pd

In [2]: df1 = pd.DataFrame({
   ...: u'名前': [u'山田', u'鈴木', u'佐藤', u'木村'],
   ...: u'性別': [u'男', u'男', u'女', u'女'] ,
   ...: u'身長': [181, 173, 159, 164],
   ...: u'体重': [79, 71, 51, 52]
   ...: }, index=['3B005', '3B003', '3B002', '3B001'])

In [3]: df2 = pd.DataFrame({
   ...:  u'誕生日': ['1981/01/01', '1982/02/02', '1983/03/03', '1984/04/04']
   ...: }, index=['3B004','3B005','3B003', '3B002'])

In [4]: df3 = pd.DataFrame({
   ...:  u'好きなもの': [u'りんご', u'ばなな', u'いちご', u'メロン'],
   ...: }, index=['3B004','3B005','3B003', '3B001'])

In [5]: df = df1.join([df2, df3])

In [6]: df
Out[6]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04   NaN
3B001  52  木村  女  164         NaN   メロン

今回は nan を作るために、join している。

2. nan の数も合わせて算出したい

例えば、データの個数を数えてみると nan の数が分からない。

In [7]: df[u'好きなもの'].value_counts()
Out[7]:
ばなな    1
メロン    1
いちご    1
dtype: int64

好きなものがわからない数も把握したので、nan のところに '不明' と入れてみることにする。

In [8]: df.fillna(u'不明')
Out[8]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04    不明
3B001  52  木村  女  164          不明   メロン

誕生日の欄も不明が入った。これでもかまわないのだが、カラムを限定することもできる。

In [9]: df.fillna({u'好きなもの': u'不明'})
Out[9]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04    不明
3B001  52  木村  女  164         NaN   メロン

今度は、好きなもののカラムだけが変更された。この状態で、もう一度、データの個数を数えてみる。

In [10]: df[u'好きなもの'].value_counts()
Out[10]:
ばなな    1
メロン    1
いちご    1
dtype: int64

ん? 最初と同じ結果になってしまった。

3. 元の DataFrame に反映したい

実は、次のようにすれば不明分もカウントされる。

In [11]: df[u'好きなもの'].fillna(u'不明').value_counts()
Out[11]:
ばなな    1
不明     1
メロン    1
いちご    1
dtype: int64

fillna が新しいオブジェクトを返すので、元の DataFrame に '不明' が反映されなかったのが、先ほどの失敗の原因だ。

次のようにすると、元の DataFrame に反映される。

In [12]: df[u'好きなもの'] = df[u'好きなもの'].fillna(u'不明')

In [13]: df
Out[13]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04    不明
3B001  52  木村  女  164         NaN   メロン

あるいは df を入れ替えてしまってもいい。

In [14]: df = df.fillna({u'好きなもの': u'不明'})

In [15]: df
Out[15]:
       体重  名前 性別   身長         誕生日 好きなもの
3B005  79  山田  男  181  1982/02/02   ばなな
3B003  71  鈴木  男  173  1983/03/03   いちご
3B002  51  佐藤  女  159  1984/04/04    不明
3B001  52  木村  女  164         NaN   メロン

今度は、不明分もカウントされるハズだ。

In [16]: df[u'好きなもの'].value_counts()
Out[16]:
ばなな    1
不明     1
メロン    1
いちご    1
dtype: int64

今回はこんなところで。

Python で解析 14

“Advent Calendar 2013 - Python で解析!” の十四日目。DataFrame - 9

1. データの準備

今回取り上げるのは duplicated。レコードの重複を取り除く。

誤って同じデータを取り込んでしまったとか、ログ解析でユニークユーザーに絞りたいなど…のようなシチュエーションで、必要になるデータ操作だ。

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({
   ...:   'name': [u'山田', u'鈴木', u'山田', u'鈴木'],
   ...:   'height': [166, 155, 168, 170]
   ...: })

In [3]: df
Out[3]:
   height name
0     166   山田
1     155   鈴木
2     168   山田
3     170   鈴木

2. なにはともあれ試してみる

とにもかくにも実行してみる。

In [4]: df.duplicated()
Out[4]:
0    False
1    False
2    False
3    False
dtype: bool

すべて False で重複しているレコードは存在しないことが分かる。これは、各レコードのすべての項目を比較して、一致していなければ False となるので、サンプルデータだと重複レコードは存在しないことになるわけだ。'name' カラムには重複データがあるので、'name' に限定して duplicated を実行してみる。

In [5]: df.name.duplicated()
Out[5]:
0    False
1    False
2     True
3     True
Name: name, dtype: bool

上から順に判定して行って、初めて登場する name なら False で、二回目に登場する時に True になっていることが分かる。

3. 重複データを取り除く


では、重複データを取り除いてみる。

In [6]: df[df.name.duplicated() == False]
Out[6]:
   height name
0     166   山田
1     155   鈴木

重複していないものについてフィルターをかけてみた。drop_duplicates というメソッドも用意されているので、そちらを使っても良い。

In [7]: df.drop_duplicates(['name'])
Out[7]:
   height name
0     166   山田
1     155   鈴木

同じ結果になった。

この例では、重複しているレコードのうち、最初に出現したものを抜き出しているが、最後に出現したものを抜き出すこともできる。

In [8]: df.drop_duplicates(['name'], take_last=True)
Out[8]:
   height name
2     168   山田
3     170   鈴木

drop_duplicates を使わずに、フィルターを使う方法でやる場合は、先に逆順に並べ替えてやればいいのだが、 drop_duplicates の方がお手軽か。

今回はこんなところで。

Python で解析 13

“Advent Calendar 2013 - Python で解析!” の十三日目。DataFrame - 8

1. データの準備

今回は cut を取り上げる。例えば、身長 10cm 刻みでの平均身長、平均体重を算出する…などで使うことができる。たぶん、 SQL にはない機能で、もし SQL で実現するなら case 式を使うだろう。

では、データの準備を。

In [1]: import pandas as pd

In [2]: df = pd.DataFrame({
   ...: 'height': [162, 178, 167, 171, 170],
   ...: 'weight': [51, 80, 55, 68, 71]
   ...: })

In [3]: df
Out[3]:
   height  weight
0     162      51
1     178      80
2     167      55
3     171      68
4     170      71

何はともあれ cut を使ってみると、こうなる。

In [4]: df.groupby(pd.cut(df.height, [160, 170, 180])).mean()
Out[4]:
                height  weight
height
(160, 170]  166.333333      59
(170, 180]  174.500000      74

何ができるか、なんとなく分かっただろうか?

2. cut とは

試しに cut のところだけを実行してみる。

In [5]: pd.cut(df.height, [160, 170, 180])
Out[5]:
Categorical: height
[(160, 170], (170, 180], (160, 170], (170, 180], (160, 170]]
Levels (2): Index(['(160, 170]', '(170, 180]'], dtype=object)

ちょっと分かりにくいかもしれないが、こんな風に置き換わっている。

162 -> (160, 170] # 160 以上、170 未満
178 -> (170, 180] # 170 以上、180 未満
167 -> (160, 170] # 160 以上、170 未満
171 -> (170, 180] # 170 以上、180 未満
170 -> (170, 180] # 170 以上、180 未満

cut の二番目の引数に境界値を設定するだけで、データを分類してくれるので、とっても便利。ニュースで年代別だったり、所得区分別だったり…の統計値が取り上げられるが、そーゆー時に使える。

なお、デフォルトでは (以上, 未満] だが、[より大きい、以下) という指定もできる。

In [6]: pd.cut(df.height, [160, 170, 180], right=False)
Out[6]:
Categorical: height
[[160, 170), [170, 180), [160, 170), [170, 180), [170, 180)]
Levels (2): Index(['[160, 170)', '[170, 180)'], dtype=object)

3. qcut も

cut では、境界値を設定して分類したが、 qcut は分割数に応じて、実際のデータを分割する。

In [7]: pd.qcut(df.height, 2)
Out[7]:
Categorical: height
[[162, 170], (170, 178], [162, 170], (170, 178], [162, 170]]
Levels (2): Index(['[162, 170]', '(170, 178]'], dtype=object)

二つに分割されたことが Levels の方を見ると分かる。

In [8]: pd.qcut(df.height, 4)
Out[8]:
Categorical: height
[[162, 167], (171, 178], [162, 167], (170, 171], (167, 170]]
Levels (4): Index(['[162, 167]', '(167, 170]', '(170, 171]',
                   '(171, 178]'], dtype=object)

四つに分割された。

「二十代の人の…」という時は cut で、「上位 25% の人の…」という時は qcut を使うという感じで、使い分ける。

今回はこんなところで。

Python で解析 12

“Advent Calendar 2013 - Python で解析!” の十二日目。matplotlib - 2

1. いつものごとく準備から

データ操作が続いたところで、久しぶりにチャートを。その前に、データの用意を…。

import pandas as pd
df = pd.DataFrame({
u'睦月': [18100, 22000, 6800, 14100],
u'如月': [14600, 29000, 8800, 12100],
u'弥生': [9900, 12000, 13000, 8500]
},
index = [u'山田', u'鈴木', u'佐藤', u'木村']
)

まあ、なんでもいいのだが、月ごとの出費額っぽいダミーのデータだ。

2. 棒グラフ

Notebook を使うがいいのだが、ブログの都合で、コンソールで簡単にチャートが表示されるように ipython を起動する。

$ ipython --pylab=inline

お手軽に棒グラフを書いてみる。

In [4]: df.plot(kind='bar')
Out[4]: <matplotlib.axes.AxesSubplot at 0x111e58f90>


各人の支出額が分かりやすく表示された。次は、データの行列を入れ替えて棒グラフを表示してみる。

In [5]: df.T.plot(kind='bar')
Out[5]: <matplotlib.axes.AxesSubplot at 0x113939e90>


今度は、横軸が陰暦になった。

3. 積み上げ棒グラフ

各人の総額で比べるために、積み上げてみる。

In [6]: df.plot(kind='bar', stacked=True)
Out[6]: <matplotlib.axes.AxesSubplot at 0x11160d350>


先ほどと同様に行列を入れ替えると、月ごとの比較になる。

In [7]: df.T.plot(kind='bar', stacked=True)
Out[7]: <matplotlib.axes.AxesSubplot at 0x1135c70d0>

4. 横向き

横向きにするなら kind を barh にする。

In [8]: df.plot(kind='barh', stacked=True)
Out[8]: <matplotlib.axes.AxesSubplot at 0x113619990>


In [9]: df.T.plot(kind='barh', stacked=True)
Out[9]: <matplotlib.axes.AxesSubplot at 0x113669410>

今回はこんなところで。