Tweet
Logo
    pandas の concat における注意点
    pandas の concat における注意点

    pandas の concat における注意点

    • はじめに
    • やりたい事
    • 解決方法
    • 1. user feature の次元を item feature に合わせる
    • 2. dummy となる key を付与して pandas の merge を使う
    • 3. iloc を使う
    • まとめ

    はじめに

    LGBM を本番に持って行く際にパフォーマンスのボトルネックになってしまった pandas の使い方とその改善についてのメモです

    やりたい事

    LGBM を api 化する際に、全ての特徴量を api の parameter として貰うのではなく、key となる id だけをもらって api 側で持っている data から LGBM の入力を作って推論を行いたいとします

    例えば user id と item id をもらって、LGBM への推論を行いたい下記のような場合を考えてみます

    DataFrame を連結して LGBM へ入力可能なデータを作る
    DataFrame を連結して LGBM へ入力可能なデータを作る

    サンプルとなるデータは以下のようなコードで作れます

    item_features = pd.DataFrame({'A': ['A1', 'A2', 'A3'],
    				                      'B': ['B1', 'B2', 'B3'],
    				                      'C': ['C1', 'C2', 'C3']})
    
    user_features = pd.DataFrame({'D': ['D2'],
                                  'E': ['E2']})

    解決方法

    1. user feature の次元を item feature に合わせる

    以下の図のように user feature の次元を item feature に合わせる事で目的の DataFrame を作る事が可能です

    user feature の次元を先に item feature に合わせる
    user feature の次元を先に item feature に合わせる

    この方法は以下のようなコードで実現できます

    user_features = pd.concat([user_features] * len(item_features), ignore_index=True)
    df = pd.concat([item_features, user_features], axis=1)

    結果の DataFrame は欲しい形になりますが、パフォーマンスが非常に悪く、item の件数に応じて使い物にならないくらいパフォーマンスが悪化してしまう可能性があります

    実際に10万件のデータで動作させてみると 7 sec 程度かかりました

    item_features = pd.DataFrame([ [f"i-{i}-1", f"i-{i}-2", f"i-{i}-3"] for i in range(100000)])
    user_features = pd.DataFrame([['u-1', 'u-2']])
    
    user_features = pd.concat([user_features] * len(item_features), ignore_index=True)
    df = pd.concat([item_features, user_features], axis=1)

    2. dummy となる key を付与して pandas の merge を使う

    図にすると以下のようになります

    dummy key を付与して merge する方法
    dummy key を付与して merge する方法

    コードでは以下のようになります

    item_features.assign(key=1).merge(user_features.assign(key=1), on='key').drop('key',axis=1)

    一見すると 1 よりも面倒な事をやっているように見えますが、パフォーマンスはこちらの方が圧倒的に良く、特に item feature の数が増えてくると顕著になります

    実際に動作確認すると、1 と同じ条件の処理が 0.06 sec 程度で終わりました

    item_features = pd.DataFrame([ [f"i-{i}-1", f"i-{i}-2", f"i-{i}-3"] for i in range(100000)])
    user_features = pd.DataFrame([['u-1', 'u-2']])
    
    item_features.assign(key=1).merge(user_features.assign(key=1), on='key').drop('key',axis=1)

    3. iloc を使う

    この記事を書いた後で更に早い方法を弊社エンジニアに教えてもらったので追記です

    概念としては 1 の方法をpandas の iloc を使うとさらに高速になりました

    user_features = user_features.iloc[np.repeat(0, len(item_features))].reset_index(drop=True)
    df = pd.concat([item_features, user_features], axis=1)
    概念 1 を iloc を使って実装する

    動作確認では、1 と同じ条件で処理が 0.01 sec 程度になりました

    item_features = pd.DataFrame([ [f"i-{i}-1", f"i-{i}-2", f"i-{i}-3"] for i in range(100000)])
    user_features = pd.DataFrame([['u-1', 'u-2']])
    
    user_features = user_features.iloc[np.repeat(0, len(item_features))].reset_index(drop=True)
    df = pd.concat([item_features, user_features], axis=1)

    まとめ

    pandas で LGBT の入力を作る場合などで覚えておきたいテクニックを紹介しました

    結果は 3 → 2 → 1 の手法の順にパフォーマンスが良く、特に 1 は非常に遅いためプロダクションでの利用などは注意が必要です

    item feature が 10 万件の際の処理時間

    Name
    結果
    1. user feature の次元を item feature に合わせる

    ~ 7 sec

    2. dummy となる key を付与して pandas の merge を使う

    ~ 0.06 sec

    3. iloc を使う

    ~ 0.01 sec

    © 2025 DROBE All rights reserved.