- NumPyの関数を直接適用させる
- 自作の関数を適用する
- apply関数を使って列ごとや行ごとに関数を適用する
- DataFrame,Seriesの各要素にはapplymap, map関数を使う
- groupby関数との併用にはapplyを使う
- まとめ
- 参考
PandasのDataFrameやSeriesにPandas以外の関数を適用させる方法はいくつか存在します。
1つは適用させたい関数の引数として直接DataFrame(Series)を指定する方法で、
もう1つはapply
関数やapplymap
関数を使用する方法です。
NumPyの関数を適用させたい場合は直接指定する方法でほとんど問題ありませんが、他のモジュールの関数や自作の関数ですとapply
やapplymap
を使わないと思うような動作をしないことがあるのでこちらの関数を使って計算することをオススメします。
本記事では以下の方法を一つ一つ解説していきます。
- 関数を直接適用
-
apply
でDataFrameやSeriesの列や行ごと -
applymap
でDataFrameの各要素ごと -
map
でSeriesの各要素ごと -
groupby
メソッドと併用できるのはapply
NumPyの関数を直接適用させる
DataFrameやSeriesの中身のデータを配列と見立ててNumPyの関数を適用させることができます。 使える引数の種類がPandas用に変換されており(例えばkeepdims引数が無効になっているなど)、NumPyの配列を対象にした時ほど自由度は高くありませんが、実用の範囲内ならほぼ問題ないでしょう。
PandasとNumPyの親和性は非常に高く、手軽に適用ができます。
NumPyの関数のタイプでも
-
np.sqrt
関数のように各要素ごとに処理を実行する関数(ufuncと呼びます) -
np.sum
関数のように列全体や行全体を対象とする関数
の2種類に大きく分けることができます。
各要素に処理を実行する関数はDataFrameやSeriesの形状に関係なく1つ1つ処理を実行するだけなのであまり混乱はありませんが、 列や行全体(もしくはデータ全体)を処理対象とする関数についてはNumPyのndarrayを対象とするときと若干挙動が異なります。
ufuncは要素ごと
In [1]: import pandas as pd
In [2]: import numpy as np
In [3]: df = pd.DataFrame({'A': np.random.rand(10),
...: 'B': np.random.rand(10)})
...:
In [4]: df
Out[4]:
A B
0 0.006706 0.686428
1 0.748844 0.642340
2 0.903863 0.631214
3 0.399030 0.075003
4 0.769379 0.206487
5 0.269700 0.034937
6 0.779630 0.979609
7 0.168005 0.434582
8 0.156968 0.874474
9 0.597533 0.508777
上のDataFrameを考えます。これに対してそれぞれ指数関数の値を計算するnp.exp
関数を適用させてみます。
In [5]: np.exp(df)
Out[5]:
A B
0 1.006728 1.986607
1 2.114553 1.900924
2 2.469122 1.879892
3 1.490379 1.077887
4 2.158426 1.229352
5 1.309572 1.035554
6 2.180664 2.663414
7 1.182943 1.544318
8 1.169959 2.397615
9 1.817630 1.663256
デフォルトでは列ごとに処理を行う
デフォルトではSeriesに対してならデータ全体、DataFrameに対してなら列全体が処理の対象となっており、DataFrameの構造に沿った関数の適用のされかたをしてくれます。
先ほどのDataFrameに対してnp.sum
関数を適用させてみましょう。
In [6]: np.sum(df) # 各列ごとの合計を表示する
Out[6]:
A 4.799659
B 5.073851
dtype: float64
In [7]: np.sum(df['A']) # 列全体
Out[7]: 4.7996585197314054
このようにデフォルトでは列ごとの合計を表示します。基本的に処理の実行する方向は行方向(縦方向)に進んで行くのがデフォルトです。
要素の足し合わせを行うcumsum
関数も使ってみます。
In [8]: np.cumsum(df)
Out[8]:
A B
0 0.006706 0.686428
1 0.755549 1.328768
2 1.659412 1.959982
3 2.058442 2.034985
4 2.827822 2.241473
5 3.097522 2.276409
6 3.877152 3.256018
7 4.045157 3.690600
8 4.202125 4.565074
9 4.799659 5.073851
In [9]: np.cumsum(df['B']) # Seriesは全体になる
Out[9]:
0 0.686428
1 1.328768
2 1.959982
3 2.034985
4 2.241473
5 2.276409
6 3.256018
7 3.690600
8 4.565074
9 5.073851
Name: B, dtype: float64
これもA
列とB
列とで別々に処理を行なっています。
行ごとに処理を行いたい場合はaxis=1, ‘columns’
列方向に処理を行いたい場合は引数としてaxis=1
を指定すれば良いです。axis='columns'
とすることも可能です。先ほどの2つの関数に対してaxis
引数を変更します。
In [10]: np.sum(df, axis=1) # axis=1
Out[10]:
0 0.693134
1 1.391183
2 1.535077
3 0.474033
4 0.975867
5 0.304637
6 1.759238
7 0.602587
8 1.031443
9 1.106310
dtype: float64
In [11]: np.cumsum(df, axis=1)
Out[11]:
A B
0 0.006706 0.693134
1 0.748844 1.391183
2 0.903863 1.535077
3 0.399030 0.474033
4 0.769379 0.975867
5 0.269700 0.304637
6 0.779630 1.759238
7 0.168005 0.602587
8 0.156968 1.031443
9 0.597533 1.106310
欠損値があっても対応可能
欠損値があっても関数の適用は可能です。
In [20]: df.iloc[4, :] = np.nan # 欠損値
In [21]: np.sum(df)
Out[21]:
A 4.030279
B 4.867364
dtype: float64
関数全体に適用させることはできないようです。
自作の関数を適用する
自作の関数を適用する場合、要素ごとの処理ならば関数の引数としてDataFrameを指定しても問題ありません。
In [30]: def func(x):
...: return x * 2
...:
In [31]: func(df)
Out[31]:
A B
0 0.013412 1.372857
1 1.497687 1.284680
2 1.807725 1.262428
3 0.798060 0.150006
4 NaN NaN
5 0.539401 0.069874
6 1.559259 1.959217
7 0.336010 0.869164
8 0.313937 1.748949
9 1.195067 1.017554
In [32]: def func2(x):
...: return x**2 + 4*x - 1
...:
In [33]: func2(df)
Out[33]:
A B
0 -0.973132 2.216897
1 2.556141 1.981960
2 3.432419 1.923288
3 0.755346 -0.694363
4 NaN NaN
5 0.151540 -0.859032
6 2.726340 3.878067
7 -0.299754 0.927190
8 -0.347488 3.262603
9 1.747180 1.293962
文字列データの操作はSeries.str.(関数)
でPython組み込みの文字列関数を呼び出すことができるので。これを使って処理するとよいでしょう。
In [34]: df_str = pd.DataFrame({'A': ["I don't have a cake.", "I'm hungry."],
...: 'B': ["He doesn't have a cake.", "He is hungry."
...: ]})
In [46]: df_str['A'].str.replace("'", " B")
Out[46]:
0 I don Bt have a cake.
1 I Bm hungry.
Name: A, dtype: object
apply関数を使って列ごとや行ごとに関数を適用する
次はPandasの関数であるapply
関数を使って関数を適用させていきます。無名関数も適用可能です。
apply
関数によって指定された関数に渡される値は各々の列のSeriesとなっており、axis='columns'
もしくはaxis=1
と指定された時は行ごとのSeriesが渡されることになっています。
In [46]: df
Out[46]:
A B
0 0.006706 0.686428
1 0.748844 0.642340
2 0.903863 0.631214
3 0.399030 0.075003
4 NaN NaN
5 0.269700 0.034937
6 0.779630 0.979609
7 0.168005 0.434582
8 0.156968 0.874474
9 0.597533 0.508777
In [47]: f = lambda x: (x - x.min())/(x.max() - x.min())
In [48]: df.apply(f)
Out[48]:
A B
0 0.000000 0.689649
1 0.827211 0.642978
2 1.000000 0.631200
3 0.437297 0.042413
4 NaN NaN
5 0.293142 0.000000
6 0.861526 1.000000
7 0.179789 0.423052
8 0.167487 0.888708
9 0.658555 0.501592
In [49]: def function(dataframe):
...: return dataframe.max() - dataframe.min()
...:
In [50]: df.apply(function)
Out[50]:
A 0.897157
B 0.944672
dtype: float64
行ごとに処理させる場合はaxis=1,’columns’を指定
行ごとに処理を実行させたい場合はaxis=1
またはaxis='columns'
と指定します。
In [52]: df.apply(function, axis='columns')
Out[52]:
0 0.679723
1 0.106504
2 0.272649
3 0.324027
4 NaN
5 0.234764
6 0.199979
7 0.266577
8 0.717506
9 0.088756
dtype: float64
DataFrame,Seriesの各要素にはapplymap, map関数を使う
DataFrameの各要素に対してはapplymap
関数を、Seriesの各要素に対してはmap
関数を用います。
In [54]: df_2 = pd.DataFrame({'data1' : np.random.randn(10),
...: 'data2' : np.random.randn(10)})
...:
In [55]: f = lambda x: "大" if x > 0 else "小"
In [56]: df_2.applymap(f)
Out[56]:
data1 data2
0 小 小
1 小 小
2 大 大
3 小 大
4 大 小
5 大 大
6 小 大
7 小 大
8 小 大
9 大 小
In [57]: df_2['data1'].map(f) # Seriesに対してはmap関数
Out[57]:
0 小
1 小
2 大
3 小
4 大
5 大
6 小
7 小
8 小
9 大
Name: data1, dtype: object
groupby関数との併用にはapplyを使う
SeriesやDataFrameをgroupby
関数を使ってグループ分けしたのち、グループごとに関数を適用させたい時はapply
関数との相性が良いです。
groupby
関数の詳しい使い方については別途解説します。
ここではKaggleのタイタニックのデータを使います。会員登録が必要ですが、以下の公式サイトからダウンロード可能です。
ここのtrain.csv
という訓練データを使います。
In [58]: train = pd.read_csv("train.csv")
In [59]: train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId 891 non-null int64
Survived 891 non-null int64
Pclass 891 non-null int64
Name 891 non-null object
Sex 891 non-null object
Age 714 non-null float64
SibSp 891 non-null int64
Parch 891 non-null int64
Ticket 891 non-null object
Fare 891 non-null float64
Cabin 204 non-null object
Embarked 889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
In [60]: def stats(s): # グループごとの概要を取得する。
...: return { "mean" : s.mean(), "max" : s.max(), "min" : s.min(), "count" : s.count()}
...:
このように、辞書形式で関数を指定するとSeriesが返されるのでデータの見通しがよくなります。
性別(Sex
)と経済的階級(Pclass
)でグループ分けをし、この関数を特定の列(今回は同乗した親族の数SibSp
)に適用させてみます。
In [62]: train.groupby(["Sex","Pclass"])['SibSp'].apply(stats)
Out[62]:
Sex Pclass
female 1 count 94.000000
max 3.000000
mean 0.553191
min 0.000000
2 count 76.000000
max 3.000000
mean 0.486842
min 0.000000
3 count 144.000000
max 8.000000
mean 0.895833
min 0.000000
male 1 count 122.000000
max 3.000000
mean 0.311475
min 0.000000
2 count 108.000000
max 2.000000
mean 0.342593
min 0.000000
3 count 347.000000
max 8.000000
mean 0.498559
min 0.000000
Name: SibSp, dtype: float64
みやすくするためにunstack
関数をつけます。すると、一番内側のIndexが列のラベルに移動します。
In [63]: train.groupby(["Sex","Pclass"])['SibSp'].apply(stats).unstack()
Out[63]:
count max mean min
Sex Pclass
female 1 94.0 3.0 0.553191 0.0
2 76.0 3.0 0.486842 0.0
3 144.0 8.0 0.895833 0.0
male 1 122.0 3.0 0.311475 0.0
2 108.0 2.0 0.342593 0.0
3 347.0 8.0 0.498559 0.0
無名関数でも適用できます。
In [69]: f = lambda x: x.std()
In [70]: train.groupby(["Sex","Pclass"])['SibSp'].apply(f)
Out[70]:
Sex Pclass
female 1 0.665865
2 0.642774
3 1.531573
male 1 0.546695
2 0.566380
3 1.288846
Name: SibSp, dtype: float64
まとめ
今回はPandasのDataFrameやSeriesに関数を適用させる方法をまとめました。
apply
やapplymap
、map
関数と、似たような関数名が並び混乱するかもしれませんがそれぞれの関数で扱う関数の種類が異なってくるのでこの辺りの違いはざっくりとでいいので押さえておくと処理の際にエラーにならず、スムーズに実装できるかと思います。
また、groupby
メソッドにおいてapply
関数は真価を発揮すると言ってもよく、是非色々と使ってみてください。
参考
- Python for Data Analysis 2nd edition –Wes McKinney(書籍)
- pandas.DataFrame.apply — pandas 0.23.4 documentation
- pandas.DataFrame.applymap — pandas 0.23.4 documentation
- pandas.Series.map — pandas 0.23.4 documentation