提高重复组操作的性能

时间:2016-08-20 11:44:15

标签: python pandas numpy

我有一个带有MultiIndex的DataFrame,它基本上是一个二进制矩阵:

day        day01                      day02                  
session session1 session2 session3 session1 session2 session3
0              1        0        0        0        0        0
1              0        0        1        1        1        0
2              1        1        1        0        0        1
3              1        0        0        1        0        0
4              1        0        1        0        0        0

从这个DataFrame中,我需要计算每行的每日总和:

     day01  day02
0        1      0
1        1      2
2        3      1
3        1      1
4        2      0

获得此总和中的0,1 ...(值计数)的数量:

0    2
1    5
2    2
3    1

我也需要为会话这样做。每行的会话总和:

         session1  session2  session3
0               1         0         0
1               1         1         1
2               1         1         2
3               2         0         0
4               1         0         1

获得价值计数:

0    5
1    8
2    2

作为基线,这是df.groupby(level='day', axis=1).sum().stack().value_counts()(和df.groupby(level='session', axis=1).sum().stack().value_counts())的结果。 DataFrame在模拟退火算法的每次迭代中发生变化,并重新计算这些计数。当我分析代码时,我发现在groupby操作上花费了大量时间。

我尝试在每次迭代中保存groupby对象并对这些对象进行求和,但改进大约为10%。这是创建更大的DataFrame的代码(类似于我的那个):

import numpy as np
import pandas as pd
prng = np.random.RandomState(0)
days = ['day{0:02d}'.format(i) for i in range(1, 11)]
sessions = ['session{}'.format(i) for i in range(1, 5)]
idx = pd.MultiIndex.from_product((days, sessions), names=['day', 'session'])
df = pd.DataFrame(prng.binomial(1, 0.25, (1250, 40)), columns=idx)

在我的电脑中,以下两种方法分别需要3.8s和3.38s。

def try1(df, num_repeats=1000):
    for i in range(num_repeats):
        session_counts = (df.groupby(level='session', axis=1, sort=False)
                            .sum()
                            .stack()
                            .value_counts(sort=False))
        daily_counts = (df.groupby(level='day', axis=1, sort=False)
                          .sum()
                          .stack()
                          .value_counts(sort=False))
    return session_counts, daily_counts

def try2(df, num_repeats=1000):
    session_groups = df.groupby(level='session', axis=1, sort=False)
    day_groups = df.groupby(level='day', axis=1, sort=False)
    for i in range(num_repeats):
        df.iat[0, 0] = (i + 1) % 2
        session_counts = session_groups.sum().stack().value_counts(sort=False)
        daily_counts = day_groups.sum().stack().value_counts(sort=False)
    return session_counts, daily_counts

%time try1(df)
Wall time: 3.8 s

%time try2(df)
Wall time: 3.38 s

注意:函数中的循环仅用于计时。对于第二个函数,为了获得正确的时序,我需要修改DataFrame。

我目前正在研究另一种方法,直接将DataFrame中的更改反映到计数而不重新计算组,但我还没有成功。跟踪受影响的行并更新保存的DataFrames,结果是慢一点。

有没有办法提高这些groupby操作的性能?

1 个答案:

答案 0 :(得分:2)

假设有一个常规的数据格式(相同的天数和每行的会话数),这里是一个基于NumPy的方法,使用np.unique,输出的索引按排序顺序 -

# Extract array
a,b = df.columns.levels
arr = df.values.reshape(-1,len(a),len(b))

# Get session counts
session_sums = arr.sum(1)
unq,count = np.unique(session_sums,return_counts=True)
session_counts_out = pd.Series(count,index=unq)

# Get daily count
daily_sums = arr.sum(2)
unq,count = np.unique(daily_sums,return_counts=True)
daily_counts_out = pd.Series(count,index=unq)

如果你只对没有索引的值感兴趣,这里有一个np.bincount的替代方案,基本上就是计数,就像return_counts部分用np.unique -

# Get session counts
session_sums = arr.sum(1)
count = np.bincount(session_sums.ravel())
session_counts_out = count[count>0]

# Get daily count
daily_sums = arr.sum(2)
count = np.bincount(daily_sums.ravel())
daily_counts_out = count[count>0]