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>