6.2 参照透過性のある書き方

Pandas の使い方を論じた本はいくつもありますが (ただし私は全てを把握していません), 私が重視しているのはデータ分析作業にありがちな, 頻繁に何度もプログラムを書き換えなければならない状況での使いやすさです. 詳しくは, 『R ユーザーへの pandas 実践ガイド』『R ユーザーのための Pandas 実践ガイド II』(この2つは私が個人的に書いたものです), 『Python/pandasのデータ処理で再帰代入撲滅委員会』という記事も参考にしてください. また, 公式ドキュメントの “Enhancing performance” というパフォーマンス向上のヒントを集めた項目も参考になります.

何度も書き換えが発生する作業ではなるべく修正箇所が少ない書き方のほうが早く, ミスも少なくて済みます. また, ちょっとした簡単なクエリのような処理, たとえば最初の数行にどういう値が入っているかとか, 特定の条件で抜き出したらどうなるのかとかを咄嗟に出力したい, ということがとてもとてもよくあります. そのような時に, たとえばシェルスクリプトのようにワンライナーで操作できれば便利だと私は考えています. このため, pandas の特徴の1つであるメソッドチェーンを重視した構文を活用すると良いでしょう.

例えば, df というデータフレームがあったとして, そのうち x 列がゼロより大きなものに限定して, 中身をちょっと覗き見たいとします. すると,

df_cond = df.query('x > 0')
df_cond.head()

という書き方ができます. しかしここでは df_cond という1度しか出番のないオブジェクトを作ってしまっています. pandas.DataFrame の多くのメソッドは, それ自身がデータフレームを返すため, メソッドをいくつもつなげることができます. 上記も以下のようにワンライナーに書き換えられます.

df.query('x > 0').head()

さらに, 私は参照透過性のある書き方を重視しています.

たとえば, 参考記事から取り出したものですが,

  1. 各列を列colでグループ別合計する
  2. x の値が正の行だけを取り出す
  3. y の数値を2乗した z を新たに作成する

という処理が必要な場合,

df = df.groupby('col').sum()
df = df.loc[df['x'] > 0]
df['z'] = df['y'] ** 2

と書けます. しかし df が何度も出てきて冗長です. メソッドチェーンにより

df.groupby('col').sum().loc[df.groupby('col').sum()['x'] > 0].assign(
    z=df.groupby('col').sum().loc[
      df.groupby('col').sum()['x'] > 0
    ]['y'] ** 2)

と書くことができます. df の数は2つ減りましたが, やや複雑に見えます. ここで,

  • df ではなく, 他のデータフレームにも適用したい
  • グループ集計の列を col から別の列に変更したい

といった修正をする場合, 修正すべき箇所が複数あります. そしてデータ分析作業では, このようなことは1日に幾度となくあります. 特に変数はデータの特徴を見るために色々試す必要があり, 一度に数十, 数百通りを試す必要があるかもしれません. しかし以下のように書いた場合, 変更すべきオブジェクトや列名は1箇所だけで済みます13.

df.groupby('col').sum().loc[lambda d: d['x'] > 0].assign(
  z=lambda d: d['y']**2)

具体的な使い方は既に挙げた参考ページを読んでください.


  1. .assign() を使う場合, データフレームのコピーが発生するためサイズの大きなデータではオーバーヘッドが問題となります. .eval() はこの問題を克服し, かつある程度の透過性のある書き方ができますが, 一方で例えば関数を呼び出せないなど, 使用できる演算子に大きな制約があります. 使用できる構文の詳細は公式の “Expression evaluation via eval()” を参考にしてください. 加えて, これは公式マニュアルにも記載のないことですが, 列名が pandas 内部で使用しているモジュールなどと競合するとエラーの原因となることもあります. 行の条件抽出に関しても, .assign() に対して .eval() があるように .loc[] の代わりに .query() を使えば, lambda 演算子を使わずにすむため簡潔になりますが, 一方で複雑な条件式には不向きです.↩︎