首页 > 解决方案 > 有没有比 for 循环更快的方法来更改熊猫组

问题描述

我正在使用下面的数据框:

这些是我试图按游戏分组的国际象棋游戏,然后根据该游戏中的移动数对每场游戏执行一个功能......

        game_id     move_number colour  avg_centi
0       03gDhPWr    1           white   NaN
1       03gDhPWr    2           black   37.0
2       03gDhPWr    3           white   61.0
3       03gDhPWr    4           black   -5.0
4       03gDhPWr    5           white   26.0
5       03gDhPWr    6           black   31.0
6       03gDhPWr    7           white   -2.0
... ... ... ... ...
110091  zzaiRa7s    34          black   NaN
110092  zzaiRa7s    35          white   NaN
110093  zzaiRa7s    36          black   NaN
110094  zzaiRa7s    37          white   NaN
110095  zzaiRa7s    38          black   NaN
110096  zzaiRa7s    39          white   NaN
110097  zzaiRa7s    40          black   NaN

具体来说,我正在使用pd.cut创建一个新列,game_phase列出给定的移动是否在开局、中局、残局中进行。

     game_id  move_number colour  avg_centi    phase
0   03gDhPWr            1  white        NaN  opening
1   03gDhPWr            2  black       37.0  opening
2   03gDhPWr            3  white       61.0  opening
3   03gDhPWr            4  black       -5.0  opening
4   03gDhPWr            5  white       26.0  opening
5   03gDhPWr            6  black       31.0  opening
6   03gDhPWr            7  white       -2.0  opening
..       ...          ...    ...        ...      ...
54  03gDhPWr           55  white       58.0  endgame
55  03gDhPWr           56  black       26.0  endgame
56  03gDhPWr           57  white      116.0  endgame
57  03gDhPWr           58  black     2000.0  endgame
58  03gDhPWr           59  white        0.0  endgame
59  03gDhPWr           60  black        0.0  endgame
60  03gDhPWr           61  white        NaN  endgame

我正在使用以下代码来实现这一点。请注意,每个游戏必须根据该游戏中的移动总数划分为openingmiddlegame和箱。endgame

for game_id, group in df.groupby('game_id'):
    bins = (0, round(group['move_number'].max() * 1/3), round(group['move_number'].max() * 2/3), 
            group['move_number'].max())
    phases = ["opening", "middlegame", "endgame"]
    try:
        group.loc[:, 'phase'] = pd.cut(group['move_number'], bins, labels=phases)
    except:
        group.loc[:, 'phase'] = None
    print(group)

问题在于,从数千个游戏中迭代每一个游戏需要很长时间才能找到这一点。

我认为必须有更快的方法来计算它,而不是使用for循环来遍历组并逐个执行计算。

标签: pythonpandas

解决方案


这是我使用一个简单示例提出的方法。

总结一下,3个步骤:

  1. 您可以max move number使用 groupby 找到每个游戏的
  2. 将新df合并到旧df,包括max move number
  3. 通过计算一次为所有游戏添加阶段move number/max move number

我的方法在,test1()而你的方法在test2()

import pandas
import random
import time

a = []

for group in range(25):
    for count in range(random.randint(900, 1000)):
        a.append({'group': chr(65 + group), 'count': count})


def test1(x):
    b = pandas.DataFrame(x)

    max_df = b.groupby(by='group', as_index=False)['count'].max().rename(columns={'count': 'max'})

    b = pandas.merge(b, max_df, on='group', how='left')

    b['phase'] = 'opening'
    b.loc[b['count'] > b['max'] / 3.0, 'phase'] = 'middlegame'
    b.loc[b['count'] > b['max'] / 1.5, 'phase'] = 'endgame'
    b.drop('max', axis=1, inplace=True)
    return b


def test2(x):
    df = pandas.DataFrame(x)
    df['phase'] = ''
    for game_id, group in df.groupby('group'):
        bins = (0, round(group['count'].max() * 1 / 3), round(group['count'].max() * 2 / 3),
                group['count'].max())
        phases = ["opening", "middlegame", "endgame"]
        try:
            group.loc[:, 'phase'] = pandas.cut(group['count'], bins, labels=phases)
        except:
            group.loc[:, 'phase'] = None
    return df


start_time = time.time()
out1 = test1(a)
print(time.time() - start_time)

start_time = time.time()
out2 = test2(a)
print(time.time() - start_time)

assert out1.to_dict() == out2.to_dict()

test1比 快得多test2,尽管这只是 1 次运行:

test1: 0.09799647331237793
test2: 0.769993782043457

并且test2()似乎有一些问题:它实际上并没有修改数据框,所以该phase列是空的。不确定它是否适合你。


推荐阅读