52420


PandasのDataframeで、特定条件の数値を統一したいです。  
例えばこのようなdfがあります。

[code]

        id  label
    0   a     1
    1   a     2
    2   a     1
    3   b     2
    4   b     2
    5   c     2
    6   a     1
    7   b     1
    8   b     2
    
[/code]

aとbは複数のラベルを持っているため、数の多いラベルをidのラベルにしたいです。  
つまり、aのラベルを全て1に、bのラベルを全て2に統一したいです。  
この作業に対して2つの案を考えました。

1.value_countsで集計してwhere, applyで置き換える

[code]

    def change_label(old_l,new_l):
        return new_l
    
    ids = new_df['id'].unique()
    for i in trange(len(ids)):
        new_label = list(new_df[new_df['id']==ids[i]]['label'].value_counts().items())[0][0]
        new_df['label'].where(new_df['id'] != ids[i], 
                                     new_df['label'].apply(lambda x:change_label(x,new_label)), 
                                     inplace=True)
    
[/code]

2.重複データは最終的に必要がないので、value_countsで集計したあとに辞書に入れる

[code]

    ids = new_df['id'].unique()
    new_dict = {}
    for i in trange(len(ids)):
        if new_df[new_df['id']==ids[i]]['label'].nunique()>1:
            new_label = list(new_df[new_df['id']==ids[i]]['label'].value_counts().items())[0][0]
            new_dict.update({ids[i]:new_label})
        else:
            new_dict.update({ids[i]:new_df[new_df['id']==ids[i]]['label'].unique()[0]})
    
[/code]

案2は案1より高速化できたが、実際大量なデータを扱うため、さらに高速化したいです。  
他に知見がありましたら、ご教示願いたいです。


CC BY-SA 4.0
2019-01-30T05:31:53.247
52414
2019-01-30T08:07:42.093
2019-01-30T05:35:19.250
19110
31068
question
python python3 pandas
Pandasでのデータ処理を高速化したい


df.loc[df["id"] == "a", "label"] = 1


CC BY-SA 4.0
2019-01-30T07:27:59.913
52418
2019-01-30T07:27:59.913
31985
52414
answer


とりあえず、２番目の方法と同様の dict型のデータ(`new_dict`)を作成する方法ですが、  
**pandas** には最頻値を求める `Series.mode()` という関数がありますので、２番目のコードを

[code]

    new_dict = {}
    for id in new_df['id'].unique():
        new_label =  new_df[new_df['id']==id]['label'].mode()[0]
        new_dict.update({id: new_label})
    
[/code]

と書き直すことができます。（これで最初のコードに比べ数倍速くなります）  
更には ループ処理を、`GroupBy.apply()`にて書き換えることが出来ますので、

[code]

    new_dict = new_df.groupby('id')['label'].apply(lambda d: d.mode()[0])
    
[/code]

と書き直すことができ、速度も10倍近く速くなります。

> この結果のデータはSeries型となっております。もしdict型のデータを得たい場合 `new_dict.to_dict()` としてください

あとはこれを `Series.map()` を使うことでもとのDataFrameに適用できますので

[code]

    dic = new_df.groupby('id')['label'].apply(lambda d: d.mode()[0])
    new_df['label'] = new_df['id'].map(dict)
    
[/code]

で良いかと思います。

ついでに書きますと、上記のコードはさらに `GroupBy.transform()` を使って

[code]

    new_df['label'] = new_df.groupby('id')['label'].transform(lambda s:s.mode()[0])
    
[/code]

と１行でまとめることができます（速度的にはあまりメリットはありませんが）

* * *

一応、動作サンプルです。

[code]

    import pandas as pd
    import numpy as np
    from tqdm import trange
    import timeit
    
    ### 元のコード（２番）
    def org_fnc(new_df):
        ids = new_df['id'].unique()
        new_dict = {}
        for i in range(len(ids)):
            if new_df[new_df['id']==ids[i]]['label'].nunique()>1:
                new_label = list(new_df[new_df['id']==ids[i]]['label'].value_counts().items())[0][0]
                new_dict.update({ids[i]:new_label})
            else:
                new_dict.update({ids[i]:new_df[new_df['id']==ids[i]]['label'].unique()[0]})
        return new_dict
    
    ### 修正コード
    def new_fnc(new_df):
        new_dict =  new_df.groupby('id')['label'].apply(lambda d: d.mode()[0]).to_dict()
        return new_dict
    
    N = 100000
    df = pd.DataFrame({
        'id': np.random.choice(['a','b','c','d','e','f','g'], N),
        'label': np.random.randint(1,20,N)
    })
    
    ### 結果を比較
    print(org_fnc(df.copy()))
    #{'e': 16, 'a': 17, 'f': 14, 'b': 6, 'c': 3, 'g': 2, 'd': 13}
    print(new_fnc(df.copy()))
    #{'e': 16, 'a': 17, 'f': 14, 'b': 6, 'c': 3, 'g': 2, 'd': 13}
    
    ### 時間を確認
    print(timeit.timeit(lambda:org_fnc(df.copy()), number=100))
    #14.239833151672423
    print(timeit.timeit(lambda:new_fnc(df.copy()), number=100))
    #0.9063719363020688
    
[/code]


CC BY-SA 4.0
2019-01-30T07:33:10.720
52420
2019-01-30T08:07:42.093
2019-01-30T08:07:42.093
24801
24801
52414
answer