Pythonでメソッドチェーン!クラスとPandasでチャレンジしてみよう

Pythonでメソッドチェーンってどうやるの?
Pandasでは使えるって聞いたけど?

他の言語を使った経験がある方や勉強熱心でメソッドチェーンという用語を知った方は、Pythonでメソッドチェーンを使う方法が気になっていると思います。

こんにちは。ライターのフクロウです。Python言語を使って開発をしています。

この記事では、メソッドチェーン向けのクラスを作る方法と、Pandasライブラリによるメソッドチェーンの利用法について紹介していきます。残念ながら他の言語のように専用の構文や演算子があるわけではありませんが、Pythonでもメソッドチェーンは利用できるんです。

この記事はこんな人のために書きました。

  • Pythonでメソッドチェーンを実装してみたい方
  • Pandasでのメソッドチェーンに興味がある方

なお、Pythonの記事については、こちらにまとめています。

目次

Pythonでメソッドチェーンを実装する

Pythonにはメソッドチェーンはありません。ただ、自分で実装することは出来ます。(参考:`return self`でメソッドチェーン

この方法の肝はメソッドの返り値を必ずselfにすることです。これによってPythonのクラスのメソッドを他の言語のメソッドチェーンのようにつなげて使うことが出来ます。

class Test:
    def __init__(self, arr):
        self.arr = arr
    
    def add(self, X):
        Y = []
        for (x,y) in zip(self.arr, X):
            Y.append(x+y)
        self.arr = Y
        return self
        
    def sub(self, X):
        Y = []
        for (x,y) in zip(self.arr, X):
            Y.append(x-y)
        self.arr = Y
        return self
    
    def print(self):
        print("self.arr: ",self.arr)
        return self

これを使うと、以下のようにメソッドをつなげて利用することが出来ます。

obj = Test([1,2,3,4,5])\ # \で改行ができます。
            .print()\
            .add([10,10,10,10,10])\
            .print()\
            .sub([5,5,5,5,5])\
            .print()

# 出力
self.arr:  [1, 2, 3, 4, 5]
self.arr:  [11, 12, 13, 14, 15]
self.arr:  [6, 7, 8, 9, 10]

メソッドチェーンは普通に書くと、コードの見た目が横に長くなるので見づらくなります。(例:Test([1,2,3,4,5]).print().add([10,10,10,10,10]).print().sub([5,5,5,5,5]).print() )なのでバックスラッシュ(\)を使って改行して書くことをおすすめします。

実用性を考えると微妙なところですが、実装自体は簡単ですし、面白いですよね。

Pandasでメソッドチェーンを使ってみる

次に紹介する方法は、Pandasに制限されますが非常に簡単です。PandasのPipe機能を使ってメソッドチェーンやってみましょう!

Pandasのインストール

まだPandasをインストールしていない人はまずはPandasのインストールです。

[Pandasのインストール]

# pipを使ってインストールする場合
pip install pandas

# condaを使ってインストールする場合
conda install pandas

これでOK!次に進みます。

Pipe機能でメソッドチェーン

PandasのDataFrameのメソッドでは、DataFrame自体を返すものがあります。これらはメソッドを.でつなげてメソッドチェーンを作ることが可能です。

また、pipeというメソッドがあります。このメソッドを使えば、更に柔軟にメソッドチェーンを構築できます。

それでは試してみましょう。ここではtitanicデータセットを使ってみます。kaggleで有名なデータセットですね。

# In [1]
import pandas as pd 
import janitor # 注:pyjanitorじゃない

# In [2]
df = pd.read_csv("titanic/train.csv")
df.head()

出力結果

このデータには欠損値や文字列のデータが沢山含まれています。ここから、

  1. 文字を数字に変換
  2. 欠損値を置き換え
  3. 新しいcolumnを追加
  4. 不要なcolumnを削除

を行います。

# In [3]
def get_FamilySize(df):
    df["FamilySize"] = df["SibSp"] + df["Parch"] + 1
    return df

def replace_Embarked(df):
    df2 = df.copy()
    # 文字を数字にマッピング
    df2['Embarked'] = df2['Embarked'].map( {'S': 0, 'C': 1, 'Q': 2})
    
    # 最頻値を特定
    mode_val = df2["Embarked"].mode().iloc[0]
    
    # 欠損値を最頻値で置き換え、整数型に直す。
    df2["Embarked"] = df2["Embarked"].fillna(mode_val).astype(int)
    
    # 修正の終わったDataFrameを返す
    return df2

pipeに渡す関数を用意しておきましょう。lambda(無名関数)で済ませられるなら、pipeの中で定義してもOKです。次のメソッドに繋げられるように、返り値がDataFrameになっていることに注意してくださいね。

さて、これらの関数をメソッドチェーンに組み込むには、df.pipe(関数名)のようにします。実際のコードを見てみましょう。

# In [4]
df2 = df.fillna(value={"Age":df.Age.median()})\   # Age列の欠損値を中央値で置き換え
            .replace({"male":0, "female":1})\   # maleを0, femaleを1に置き換え
            .pipe(get_FamilySize)\   # 家族の人数の列を追加 
            .pipe(replace_Embarked)\   # Embarked列を数字に直して欠損値を最頻値で置き換え
            .drop(["Name", "SibSp", "Parch", "Ticket", "Fare", "Cabin",], axis=1)\  # 不要な列を削除

df2.head()

上の例では、メソッド同士を.で繋げて、前処理をまとめて一回に書くことが出来ています。前の章で紹介したメソッドチェーンの形と同じになっています。

pipeメソッドには、第一引数にDataFrameを受け取る関数を渡せばOKです。他の言語でメソッドチェーンに慣れている方は使ってみてはどうでしょうか。

Pandasの拡張メソッド

先程の例では、DataFrameのメソッドに無い機能をpipeを使って実行できることがわかりました。ですが、DataFrameのメソッドですべて完結するならそれに越したことはありません。

DataFrameの操作ができる関数は、Pandas標準のもの以外にもたくさんあります。例えば、Pandasの機能拡張ができるライブラリ、pyjanitor。これはR言語のパッケージ「Janitor」のPython版で、Pandasのメソッドを増やすことが出来ます。

https://github.com/ericmjl/pyjanitor

利用する場合は、まずpyjanitorライブラリをインストールしましょう。

[pyjanitorのインストール]

# pipを使ってインストールする場合
pip install pyjanitor

# condaを使ってインストールする場合
conda install pyjanitor -c conda-forge

これで、あたかもPandas固有のメソッドであるかのように拡張機能が使えます。

[pyjanitorのドキュメントより引用]

import pandas as pd
import janitor  # upon import, functions are registered as part of pandas.

df = pd.DataFrame(...)
df = df.clean_names().remove_empty()  # further method chaining possible.

また、from jaintor import 関数名 とすることで、先程紹介したパイプライン機能で利用する事もできます。

[pyjanitorのドキュメントより引用]

from janitor import clean_names, remove_empty
import pandas as pd

df = pd.DataFrame(...)
(df.pipe(clean_names)
   .pipe(remove_empty)
   .pipe(...))

まとめ

この記事ではPythonでメソッドチェーンのように、関数を繋げて書く方法について紹介しました。

他の言語のように、関数と関数の間を >> や | のような記号でつなげる機能はありませんが、コレに対応するいくつかのアプローチがあることがわかりましたね。特にPandasは、R言語の影響を受けて様々な機能拡張がサードパーティ製ツールで可能です。

コードを短く書くことだけが正義では無いですが、(わかりやすい関数名をつけるなどして)綺麗に書くことができれば、メソッドチェーンは非常に強力なツールになります。ぜひ使ってみてください!

この記事を書いた人

第一言語はPythonです。
皆さんRustやりましょう。

目次