使用Pandas进行聚合计算时排除当前行

6

如何聚合以获取组ab的平均值,同时排除当前行(目标结果在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)

数据转储:

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'])
2个回答

9
假设一个组有值 x_1, ..., x_n
整个组的平均值将是:
m = (x_1 + ... + x_n)/n

组中不包括 x_i 的总和为:
(m*n - x_i)

组内剔除 x_i 后的平均值为:
(m*n - x_i)/(n-1)

因此,您可以使用以下方法计算所需列的值:
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)

产生的结果
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'])

根据下面的评论,在原始问题的实际用例中,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)})

因此,在3百万个数据中,df['a'] 中大约有一百万个唯一值:

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

在这种情况下,上述计算(在我的机器上)需要大约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

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

以下是一个基准测试,如果我们将具有对象dtype类型的序列转换为具有固定宽度字符串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

请注意,您需要知道df['a']中字符串的最大长度才能选择适当的定宽数据类型。在上面的示例中,所有字符串的长度都为4,因此|S4适用。如果您使用|Sn,其中n是某个整数且n小于最长字符串,则这些字符串将被默默地截断而没有错误警告。这可能会导致将不应分组在一起的值进行分组。因此,责任在于您选择正确的定宽数据类型。
您可以使用
dtype = '|S{}'.format(df['a'].str.len().max())
grouped = df.groupby(df['a'].values.astype(dtype))

为确保转换使用正确的数据类型。


另外,如果只有一个情况会发生什么?我想让它成为所有其他不失败情况的平均值。 - PascalVKooten
虽然它可以处理400万行数据,但速度非常慢。我在考虑也许更好的方法是先聚合包括该行的数据,然后再减去按计数加权的数据(你的第一条评论帮助了我想到这个方法)? - PascalVKooten
你的实际使用情况有些不同,我没有看到。我已经运行了上面的代码,使用 df = pd.concat([df]*1000000) 使得 len(df) 为900万,%timeit df.groupby(['a'])['b'].transform(ave_others) 完成时间为1.18秒。 - unutbu
问题列的dtype是什么?您可以使用 df.info() 找到dtypes。 - unutbu
那是我之后检查的第一件事:两者都是 dtype=object - PascalVKooten
显示剩余7条评论

2

您可以通过逐个迭代分组来手动计算统计数据:

# Set up input
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'])
df
   a  b    c
0  1  1  0.5
1  1  1  0.5
2  1  0  1.0
3  2  1  0.5
4  2  0  1.0
5  2  1  0.5
6  3  1  0.5
7  3  0  1.0
8  3  1  0.5

# Perform grouping, excluding the current row
results = []
grouped = df.groupby(['a'])
for key, group in grouped:
    for idx, row in group.iterrows():
        # The group excluding current row
        group_other = group.drop(idx)  
        avg = group_other['b'].mean()
        results.append(row.tolist() + [avg])

# Compare our results with what is expected
results_df = pd.DataFrame(
    results, columns=['a', 'b', 'c', 'c_new']
)
results_df
   a  b    c  c_new
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

这样你就可以使用任何你想要的统计数据了。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接