【问题标题】:What is the fastest way to find the group by max in a column in a Python Pandas dataframe AND mark it?在 Python Pandas 数据框中的列中按 max 查找分组并标记它的最快方法是什么?
【发布时间】:2020-10-09 15:51:07
【问题描述】:

UPDATE2:我实际上有 2000 次而不是 3 次。

更新:我的 df 列 A 错误。我修好了。

我在下面有一个非常大的df 版本。

data = {'A':[11111, 11111, 33333,11111], 'B':[101, 101, 102, 101],'C':[1,2,3,4],
    'draw0':[5, 6, 2, 1], 'draw1':[4,3,2,1], 'draw2':[2,3,4,6]}
df = pd.DataFrame(data)

     A     B   C  draw0   draw1   draw2
0  11111  101  1      5      4      2
1  11111  101  2      6      3      3
2  33333  102  3      2      2      4
3  11111  101  4      1      1      6

我正在尝试找出每次抽奖中哪个抽奖列获胜。以下是我目前的尝试,但速度很慢,但有效。我觉得应该有一种方法可以让应用程序更快。

draw_cols = [col for col in df if col.startswith('draw')]

for col in draw_cols:
    max_idx = df.groupby(['A', 'B'])[col].idxmax().values
    df.loc[max_idx, col] = 1
    df.loc[~df.index.isin(max_idx), col] = 0

期望的输出:

     A     B   C  draw0  draw1  draw2
0  11111  101  1      0      1      0
1  11111  101  2      1      0      0
2  33333  102  3      1      1      1
3  11111  101  4      0      0      1

我像这样生成 2000 列:

def simulateDraw(df, n=2000):
    
    #simulate n drawings from the alpha and beta values and create columns 
    return pd.concat([df,
           df.apply(lambda row: pd.Series(np.random.beta(row.C, row.C, size=n)), axis = 1).add_prefix('draw')],
          axis = 1)

【问题讨论】:

  • 你的预期输出是什么?
  • 判断哪个抽奖列获胜的逻辑是什么?
  • 我更新了 A 列。我的解决方案现在有效,但仍然很慢
  • np.where 在同一时间对每个组的所有绘图进行矢量化。因此,如果您相对于组有较大的平局,则 np.where 将是更优化的解决方案

标签: python pandas performance


【解决方案1】:
# groupby and transform the idxmax
max_idx = df.groupby(['A', 'B'])[df.columns[3:]].transform('idxmax')
# create a new column that is just your index
# this is done just in case your real data does not have a range index
max_idx['index'] = max_idx.index.values
# where the max_idx is in the index to return bool values and then update the original df
df.update(max_idx.isin(max_idx['index']).astype(int))

       A    B  C  draw0  draw1  draw2
0  11111  101  1      0      1      0
1  11111  101  2      1      0      0
2  33333  102  3      1      1      1
3  11111  101  4      0      0      1

【讨论】:

  • 此解决方案对于较大的数据帧更快。
【解决方案2】:

检查draw 列等于该列最大值的每个组

df.update(df.groupby(['A','B'])[['draw0','draw1','draw2']].apply(lambda x: x.eq(x.max(0))).astype('int'))
df

输出:

       A    B  C  draw0  draw1  draw2
0  11111  101  1      0      1      0
1  11111  101  2      1      0      0
2  33333  102  3      1      1      1
3  11111  101  4      0      0      1

微基准

simulateDraw(df, n=4) 的结果

simulateDraw(df, n=50) 的结果(更多行或列超出了我的耐心和 colab 实例上的 RAM)

用于基准测试的代码

import pandas as pd
import numpy as np
import perfplot

def simulateDraw(df, n=2000):
    return pd.concat([df,
           df.apply(lambda row: pd.Series(np.random.beta(row.C, row.C, size=n)), axis = 1).add_prefix('draw')],
          axis = 1)

def makedata(n=1):
    data = pd.DataFrame({'A':[11111, 11111, 33333,11111] * n, 'B':[101, 101, 102, 101] * n,'C':[1,2,3,4] * n})
    data = simulateDraw(data)
    return data

def forloop(df):
    draw_cols = [col for col in df if col.startswith('draw')]
    for col in draw_cols:
        max_idx = df.groupby(['A', 'B'])[col].idxmax().values
        df.loc[max_idx, col] = 1
        df.loc[~df.index.isin(max_idx), col] = 0
    return df

def applyeq(df):
    draw_cols = [col for col in df if col.startswith('draw')]
    df.update(df.groupby(['A','B'])[draw_cols].apply(lambda x: x.eq(x.max(0))).astype('int'))
    return df


def idxmax(df):
    draw_cols = [col for col in df if col.startswith('draw')]
    max_idx = df.groupby(['A', 'B'])[draw_cols].transform('idxmax')
    max_idx['index'] = max_idx.index.values
    df.update(max_idx.isin(max_idx['index']).astype(int))
    return df


perfplot.show(
    setup=makedata,
    kernels=[idxmax,applyeq,forloop],
    n_range=[2**k for k in range(5,22)],
    xlabel='len(df)'
)

【讨论】:

【解决方案3】:

这种嵌套列表理解不需要 groupby,但可以更快地更新值(它替代了 'apply lambda' 的需要,后者适用于带有np.where 的每个元素)。如果您的 dataframe 很大,它可能会更有效(虽然我没有运行任何性能指标!)

out = pd.concat(
            [
                pd.concat(
                            [
                                pd.DataFrame(
                                                np.where(
                                                            df.loc[df.B.isin([i]),['draw0','draw1','draw2']]==df.loc[df.B.isin([i]),['draw0','draw1','draw2']].max().to_numpy()[None,:],1,0
                                                        )
                                            ).reset_index(drop=True),\
                               df.loc[df.B.isin([i]),['A','B','C']].reset_index(drop=True)
                            ], axis=1, sort=False, ignore_index=True
                        ) for i in df.B.unique()
            ], axis=0, sort=False, ignore_index=True
            )


out.rename(columns = {0:'draw0',1:'draw1',2:'draw2',3:'A',4:'B',5:'C'}, inplace=True)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-24
    • 2020-05-21
    • 1970-01-01
    相关资源
    最近更新 更多