熊猫聚合平均值,同时排除当前行

Pas*_*ten 5 python aggregate pandas

如何聚合以获得b组的平均值a,同时排除当前行(目标结果是c)?

a b   c

1 1   0.5   # (avg of 0 & 1, excluding 1)
1 1   0.5   # (avg of 0 & 1, excluding 1)
1 0   1     # (avg of 1 & 1, excluding 0)

2 1   0.5   # (avg of 0 & 1, excluding 1)
2 0   1     # (avg of 1 & 1, excluding 0)
2 1   0.5   # (avg of 0 & 1, excluding 1)

3 1   0.5   # (avg of 0 & 1, excluding 1)
3 0   1     # (avg of 1 & 1, excluding 0)
3 1   0.5   # (avg of 0 & 1, excluding 1)
Run Code Online (Sandbox Code Playgroud)

数据转储:

import pandas as pd
data = pd.DataFrame([[1, 1, 0.5], [1, 1, 0.5], [1, 0, 1], [2, 1, 0.5], [2, 0, 1], 
                     [2, 1, 0.5], [3, 1, 0.5], [3, 0, 1], [3, 1, 0.5]],
                     columns=['a', 'b', 'c'])
Run Code Online (Sandbox Code Playgroud)

unu*_*tbu 6

假设一个组有值x_1, ..., x_n

整个小组的平均值为

m = (x_1 + ... + x_n)/n
Run Code Online (Sandbox Code Playgroud)

该组没有总和x_i将是

(m*n - x_i)
Run Code Online (Sandbox Code Playgroud)

该组的不平均x_i

(m*n - x_i)/(n-1)
Run Code Online (Sandbox Code Playgroud)

因此,您可以使用

import pandas as pd
df = pd.DataFrame([[1, 1, 0.5], [1, 1, 0.5], [1, 0, 1], [2, 1, 0.5], [2, 0, 1], 
                     [2, 1, 0.5], [3, 1, 0.5], [3, 0, 1], [3, 1, 0.5]],
                     columns=['a', 'b', 'c'])

grouped = df.groupby(['a'])
n = grouped['b'].transform('count')
mean = grouped['b'].transform('mean')
df['result'] = (mean*n - df['b'])/(n-1)
Run Code Online (Sandbox Code Playgroud)

产生

In [32]: df
Out[32]: 
   a  b    c  result
0  1  1  0.5     0.5
1  1  1  0.5     0.5
2  1  0  1.0     1.0
3  2  1  0.5     0.5
4  2  0  1.0     1.0
5  2  1  0.5     0.5
6  3  1  0.5     0.5
7  3  0  1.0     1.0
8  3  1  0.5     0.5

In [33]: assert df['result'].equals(df['c'])
Run Code Online (Sandbox Code Playgroud)

根据以下注释,在OP的实际使用案例中,DataFrame的a列包含字符串:

def make_random_str_array(letters, strlen, size):
    return (np.random.choice(list(letters), size*strlen)
            .view('|S{}'.format(strlen)))

N = 3*10**6
df = pd.DataFrame({'a':make_random_str_array(letters='ABCD', strlen=10, size=N),
                   'b':np.random.randint(10, size=N)})
Run Code Online (Sandbox Code Playgroud)

因此,在df['a']300万个总值中,大约有100万个唯一值:

In [87]: uniq, key = np.unique(df['a'], return_inverse=True)
In [88]: len(uniq)
Out[88]: 988337

In [89]: len(df)
Out[89]: 3000000
Run Code Online (Sandbox Code Playgroud)

在这种情况下,上述计算(在我的机器上)大约需要11秒

In [86]: %%timeit
   ....: grouped = df.groupby(['a'])
n = grouped['b'].transform('count')
mean = grouped['b'].transform('mean')
df['result'] = (mean*n - df['b'])/(n-1)
   ....:    ....:    ....:    ....: 
1 loops, best of 3: 10.5 s per loop
Run Code Online (Sandbox Code Playgroud)

Pandas将所有字符串值列转换为object dtype。但是我们可以将DataFrame列转换为具有固定宽度dtype的NumPy数组,并根据这些值进行分组。

这是一个基准测试,显示如果将具有对象dtype的Series转换为具有固定宽度的字符串dtype的NumPy数组,则计算所需的时间少于2秒

In [97]: %%timeit
   ....: grouped = df.groupby(df['a'].values.astype('|S4'))
n = grouped['b'].transform('count')
mean = grouped['b'].transform('mean')
df['result'] = (mean*n - df['b'])/(n-1)
   ....:    ....:    ....:    ....: 
1 loops, best of 3: 1.39 s per loop
Run Code Online (Sandbox Code Playgroud)

请注意,您需要知道字符串的最大长度df['a']才能选择适当的固定宽度dtype。在上面的示例中,所有字符串的长度均为4,因此|S4可以正常工作。如果您使用|Sn某个整数n并且n小于最长字符串,则这些字符串将被无提示地截断,而不会出现错误警告。这可能会导致不应分组的值分组。因此,您有责任选择正确的固定宽度dtype。

你可以用

dtype = '|S{}'.format(df['a'].str.len().max())
grouped = df.groupby(df['a'].values.astype(dtype))
Run Code Online (Sandbox Code Playgroud)

确保转换使用正确的dtype。