初めに
PANDASは、データサイエンティスト向けの最高のデータ処理ライブラリですが、数百万行のデータを取り扱う際にパフォーマンスの落とし穴を回避するように注意する必要があります。今回は仕事の中で学んできたいくつのテクニックを紹介したいと思います。
DataFrame
PANDASは列志向のデータ構造なので、列ごとの処理は得意です。DataFrame
の作成には「1レコード1行」形式で、1レコードに対してすべての測定可能の値(湿度、値段、座標など)はカラムごとに行うことを推奨します。
しかし、膨大なデータ加工において行ごとのforループ処理したらパフォーマンスを格段に落とす。本記事はパフォーマンスを考えて頭よくデータ加味を行う方法を紹介したいと思います。
まずはサンプル用のテーブルを作ります。
data = {'Team': ['Tiger', 'Tiger', 'Rabbit', 'Rabbit', 'Cat',
'Cat', 'Cat', 'Cat', 'Tiger', 'Human', 'Human', 'Tiger'],
'Rank': [1, 2, 2, 3, 3,4 ,1 ,1,2 , 4,1,2],
'Year': [2014,2015,2014,2015,2014,2015,2016,2017,2016,2014,2015,2017],
'Points':[876,789,863,673,741,812,756,788,694,701,804,690]}
df= pd.DataFrame(data)
print(team_dashboard)
'''
Team Rank Year Points
0 Tiger 1 2014 876
1 Tiger 2 2015 789
2 Rabbit 2 2014 863
3 Rabbit 3 2015 673
4 Cat 3 2014 741
5 Cat 4 2015 812
6 Cat 1 2016 756
7 Cat 1 2017 788
8 Tiger 2 2016 694
9 Human 4 2014 701
10 Human 1 2015 804
11 Tiger 2 2017 690
'''
print(df.columns) # 列ラベルの取得
'''
Index(['Team', 'Rank', 'Year', 'Points'], dtype='object')
'''
print(df.index) # row indexの取得
'''
RangeIndex(start=0, stop=12, step=1)
'''
DataFrameにはindexと呼ばれる特殊なリストがある。上の例では['Team', 'Rank', 'Year', 'Points']
のように各列を表す要素をラベルと、左側の0, 1, 2, 3, ...のように各行を表すrow indexがある。
groupby()
groupby
は、同じ値を持つデータをまとめて、それぞれのグループに対して共通の操作を行いたい時に使います。データ加工において定番になっているが、groupbyは多様な使い方があるので最初はなかなか難しいです。
では、さっそぐ例を見ていきましょう。groupby()
メソッドにカラム名を入れたらGroupBy
オブェクトを返す。
# 1個レベル
df_g = df.groupby(by=['Team'])
print(df_g.groups)
'''
{'Cat': Int64Index([4, 5, 6, 7], dtype='int64'),
'Human': Int64Index([9, 10], dtype='int64'),
'Rabbit': Int64Index([2, 3], dtype='int64'),
'Tiger': Int64Index([0, 1, 8, 11], dtype='int64')}
'''
# 2個(複数)ラベル
df_g = df.groupby(by=['Team', 'Year'])
print(df_g.groups)
'''
{('Cat', 2014): Int64Index([4], dtype='int64'),
('Cat', 2015): Int64Index([5], dtype='int64'),
('Cat', 2016): Int64Index([6], dtype='int64'),
('Cat', 2017): Int64Index([7], dtype='int64'),
('Human', 2014): Int64Index([9], dtype='int64'),
('Human', 2015): Int64Index([10], dtype='int64'),
('Rabbit', 2014): Int64Index([2], dtype='int64'),
('Rabbit', 2015): Int64Index([3], dtype='int64'),
('Tiger', 2014): Int64Index([0], dtype='int64'),
('Tiger', 2015): Int64Index([1], dtype='int64'),
('Tiger', 2016): Int64Index([8], dtype='int64'),
('Tiger', 2017): Int64Index([11], dtype='int64')}
'''
こうして{列ラベル: [行ラベル, 行ラベル, ...]}
のような形で、どのグループにどの列が入ったか分かる。各ラベルに対して同じラベルの持っているデータのrow indexリストが収納されている。
ちなみにグループ内のデータを取得には get_group()
にグループキーを渡す。
df_oneGroup = df_g.get_group('Rabbit')
print(df_oneGroup)
'''
Team Rank Year Points
2 Rabbit 2 2014 863
3 Rabbit 3 2015 673
'''
df_oneGroup = df_g.get_group(('Cat', 2014))
print(df_oneGroup)
'''
Team Rank Year Points
4 Cat 3 2014 741
'''
まあ、実際get_group()
はデバッグ以外にあまり使わないですが、GroupBy
オブジェクトでグループごとにさまざま演算ができる。例えば各チームに年ごとのRankとPointsの平均をとるにはmean()メソッドを呼び出す。ほかには sum()
、mode()
などたくさんのメソッドが提供されてる。ちなみに、as_index=False
するとデフォルトのグループラベルをリセットされ、[0, 1, 2, ..., n]になる
df_mean = team_dashboard.groupby(by=['Team', 'Year']).mean()
print(df_mean)
'''
Rank Points
Team Year
Cat 2014 3 741
2015 4 812
2016 1 756
2017 1 788
Human 2014 4 701
2015 1 804
Rabbit 2014 2 863
2015 3 673
Tiger 2014 1 876
2015 2 789
2016 2 694
2017 2 690
'''
df_mean = team_dashboard.groupby(by=['Team', 'Year'], as_index=False).mean()
print(df_mean)
'''
Team Year Rank Points
0 Cat 2014 3 741
1 Cat 2015 4 812
2 Cat 2016 1 756
3 Cat 2017 1 788
4 Human 2014 4 701
5 Human 2015 1 804
6 Rabbit 2014 2 863
7 Rabbit 2015 3 673
8 Tiger 2014 1 876
9 Tiger 2015 2 789
10 Tiger 2016 2 694
11 Tiger 2017 2 690
'''
agg()
を使ってグループごとに複数行を返す
先ほどのGroupBy.mean()
のように、グループごとに数値を求められますが、もしグループに別々に数値を求めようとするときはagg()
(Aggregration)を使う。Aggregationに使う関数がstring、numpyメソッド、自作関数、ラムダ式で呼び出せる。agg()
を使うにはdict()
に定義して以下のように渡すと実装できる。
'''
location col1 col2 col3 col4
0 a True 2 1 4
1 a False 6 2 6
2 b True 7 6 3
3 b True 3 3 4
4 b False 8 4 6
5 c True 9 57 8
6 d False 1 74 9
'''
func_dict = {'col1': lambda x: x.any(), # 欠損状態の確認
'col2': np.mean, # 平均
'col3': np.sum, # 合計
'col4': lambda S: S.mode()[0]} # 最頻値
df_agg = df.groupby('location').agg(func_dict).reset_index()
print(df_agg)
'''
location col1 col2 col3 col4
0 a True 4 3 4
1 b True 6 13 3
2 c True 9 57 8
3 d False 1 74 9
'''
cut()
を使って任意の境界値でカテゴリ変数を数値に変換する
データをカテゴリ化する際に、指定した境界値でカテゴリ化を行うcut()
を紹介します。例えば、「深夜、午前、正午、午後、夜」と五つのカテゴリでデータ全体を等分するときは容易に行えます。
prods = pd.DataFrame({'hour':range(0, 24)})
b = [0, 6, 11, 12, 17, 24]
l = ['深夜', '午前','正午', '午後', '夜']
prods['period'] = pd.cut(prods['hour'], bins=b, labels=l, include_lowest=True)
print(prods)
'''
hour period
0 0 深夜
1 1 深夜
2 2 深夜
3 3 深夜
4 4 深夜
5 5 深夜
6 6 深夜
7 7 午前
8 8 午前
9 9 午前
10 10 午前
11 11 午前
12 12 正午
13 13 午後
14 14 午後
15 15 午後
16 16 午後
17 17 午後
18 18 夜
19 19 夜
20 20 夜
21 21 夜
22 22 夜
23 23 夜
'''
resample()
を使って時系列データをグループ化して加工する
今回は「1時間ごとの累積件数を計算する」とします。ここはpd.cumsum()
を使ってみようかなと思うので、予めでnum_ride_1h
カラムを作っておいて「1」を与える。そうしたらresample()
でtimestampカラムで1時間ごとにグループ化した後、各グループにはcumsum()
メソッドを呼び出すことで完成できる。
df_raw= make_classification(n_samples, n_features+1)
df_raw['timestamp'] = random_datetimes_or_dates(start, end, n=n_samples)
df_raw['num_ride_1h'] = 1
print(df_raw)
'''
var_0 var_1 var_2 class timestamp num_ride_1h
0 1.062513 -0.056001 0.761312 0 2020-09-21 00:01:57 1
1 -2.272391 1.307474 -1.276716 0 2020-09-21 00:14:49 1
2 -1.470793 1.245910 -0.708071 2 2020-09-21 00:17:46 1
3 -1.827838 1.365970 -0.933938 0 2020-09-21 00:25:13 1
4 -1.115794 -0.045542 -0.830372 0 2020-09-21 00:31:45 1
.. ... ... ... ... ... ...
95 0.247010 0.903812 0.448323 0 2020-09-21 23:29:25 1
96 -0.665399 1.861112 0.063642 1 2020-09-21 23:32:51 1
97 0.875282 0.028704 0.649306 2 2020-09-21 23:36:21 1
98 2.159065 -1.155290 1.238786 0 2020-09-21 23:37:23 1
99 1.739777 -1.775147 0.748372 2 2020-09-21 23:56:04 1
'''
df_raw['num_ride_1h'] = df_raw.resample('1H', on='timestamp')['num_ride_1h'].cumsum()
'''
var_0 var_1 var_2 class timestamp num_ride_1h
0 -1.331170 -0.274703 0.809738 1 2020-10-11 00:10:54 1
1 -1.373495 -1.067991 1.738302 1 2020-10-11 00:14:24 2
2 -1.471448 0.216404 0.296618 0 2020-10-11 00:43:29 3
3 -2.282394 -1.528916 2.605747 1 2020-10-11 00:48:52 4
4 0.162427 0.524188 -0.663437 2 2020-10-11 00:51:23 5
.. ... ... ... ... ... ...
95 1.197076 0.274294 -0.759543 1 2020-10-11 22:23:50 3
96 -0.459688 0.646523 -0.573518 0 2020-10-11 23:00:20 1
97 0.212496 0.773962 -0.969428 2 2020-10-11 23:11:43 2
98 1.578519 0.496655 -1.156869 1 2020-10-11 23:14:31 3
99 1.318311 -0.324909 -0.114971 0 2020-10-11 23:46:46 4
'''
pd.Grouper()
を使うことも可能。どららも同じ結果を出せる。
df_raw['num_ride_1h'] = df_raw.groupby(pd.Grouper(key='timestamp', freq='1h'))['num_ride_1h'].cumsum()
最後に
ほかにいくつ紹介したい例がありますが、今回はここで終わりたいと思います。次の機会があれば時系列データを中心にしたデータ加工についてまとめて紹介します。
【株式会社クアンド】
私が勤めしている株式会社クアンドは地方産業アップグレードに活動しています。
ぜひご覧になってください。
http://quando.jp/