TL;DR
- 書き下しが多いPandasにおいても頻出のパターンは存在
- メソッドチェーンを使って書くことで過去の資産を再利用
- よく使う処理は関数化して、pipeやapplyで適用
背景
データフレームとして有名なPandasはデータ分析やエクセルの代わりとして使われており、書き捨てられるコードばかりですが、意外と以前書いたことがあるコードに時間を割きがちです。
この記事では再利用可能性の観点から良いコーディングパターンを紹介します。
よくあるコード
再利用のためのテクニック
メソッドチェーンを使う
メソッドチェーンを使うことで、pandasの加工を構造的に行うことができます。
df = (
pd.read_csv('2016-2017_orig.csv', sep=";") #データセット読み込み
.assign(upset_factor=lambda _df: # upset factorの計算
vect_upset_factor(_df["rating_1"], _df["rating_2"], _df["player1_is_win?"]))
.assign(num_of_set=lambda _df:_df.result.apply(lambda x: x.count("-"))) # セットカウント
.loc[lambda _df:_df["rating_1"] <= _df["rating_2"]] # 選手1が常にレート上位になるように
.loc[lambda _df: _df["age_1"]<= 25] # どちらの選手も25歳以下
.loc[lambda _df: _df["age_2"]<= 25]
.sort_values("upset_factor", ascending=False) # upsetfactorでソート
.head(10)
)
上記のように、1行ごとに動作が規定されている順に読んでいくことで、何をしたいのかわかりいです。
また、すべての行がlambda _df
で始まっており、名前がバインドされていないため、データフレームの名前が変わった場合においても問題なくコピペ等により再利用可能です。
また、メソッドチェーンを用いることによって途中の変数が必要なくなり、実行時間の高速化も期待できます。
チェーンしすぎると読むのが難しくなるので、適度に関数化や分割しておく必要はあります。
メソッドチェーンを使う際に便利な関数
関数名 | コメント |
---|---|
.loc | コラムの絞り込みや、条件絞り込み |
.assign | 新しいコラムの挿入 |
.sort_values | ソート |
.pipe | 関数の適用 |
.query | 条件絞り込み |
.drop | コラムの削除 |
よく使う処理は関数化する
ここではランキング下位の選手が上位の選手人勝ったときに高い数値になるupset factorというものを計算しており、この計算が頻発するものとします。
その場合は、下記のように関数化しておくと便利です。関数化するときは、テストしやすいように数値などを直に変数に持っておき、その後で、np.vectorize()
で関数をnumpy行列で呼び出せるようにしておくことによって、テスト効率と呼び出しの際の利便性を担保できます。
各変数にnumpy配列を入れて計算します。vect_upset_factor(df["rating_1"], df["rating_2"], df["player1_is_win?"])
import bisect
def calculate_upset_factor(rate_1, rate_2, reverse):
rate = [2**i for i in range(0,11)]
rate_1_factor = bisect.bisect_right(rate, rate_1)
rate_2_factor = bisect.bisect_right(rate, rate_2)
if reverse:
return rate_1_factor/rate_2_factor
else:
return rate_2_factor/rate_1_factor
vect_upset_factor = np.vectorize(calculate_upset_factor)
また、upsetfactorを計算して元のデータフレームに足すという動作自体が頻発するケースでは、pipe
関数を挟むとより再利用しやすくなります。
def upset_factor_pipe(_df, col1="rating_1", col2="rating_2", rev_col="player1_is_win?"):
return _df.assign(upset_factor=lambda _df:
vect_upset_factor(_df[col1], _df[col2], _df[rev_col]))
(
pd.read_csv('2016-2017_orig.csv', sep=";")
.pipe(upset_factor_pipe, "rating_1", "rating_2", "player1_is_win?")
)
まとめ
pandasであっても普段から構造化と再利用可能性を意識してコードを書くと、思ったよりも効率化できるので、ぜひ試してみてください。
今回の記事では再利用性にのみ着目していますが、高速化するためのテクニックもあるので、うまく構造化しつつパフォーマンスを意識したコードがかけるとより良いと思います。