4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

再利用性可能なPandasコードの書き方

Posted at

TL;DR

  • 書き下しが多いPandasにおいても頻出のパターンは存在
  • メソッドチェーンを使って書くことで過去の資産を再利用
  • よく使う処理は関数化して、pipeやapplyで適用

背景

データフレームとして有名なPandasはデータ分析やエクセルの代わりとして使われており、書き捨てられるコードばかりですが、意外と以前書いたことがあるコードに時間を割きがちです。  
この記事では再利用可能性の観点から良いコーディングパターンを紹介します。

よくあるコード

スクリーンショット 2023-04-21 22.47.46.png
これを下記のようにするのがこの記事の目標
スクリーンショット 2023-04-21 22.54.24.png

再利用のためのテクニック

メソッドチェーンを使う

メソッドチェーンを使うことで、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であっても普段から構造化と再利用可能性を意識してコードを書くと、思ったよりも効率化できるので、ぜひ試してみてください。
今回の記事では再利用性にのみ着目していますが、高速化するためのテクニックもあるので、うまく構造化しつつパフォーマンスを意識したコードがかけるとより良いと思います。

4
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?