假设我有一些数据生成如下:
N = 20
m = 3
data = np.random.normal(size=(N,m)) + np.random.normal(size=(N,m))**3
然后我创建了一些分类变量:
indx = np.random.randint(0,3,size=N).astype(np.int32)
并生成一个DataFrame:
import pandas as pd
df = pd.DataFrame(np.hstack((data, indx[:,None])),
columns=['a%s' % k for k in range(m)] + [ 'indx'])
我可以得到每组的平均值:
df.groubpy('indx').mean()
我不确定如何做的是然后在原始数据中减去每个组,每列的平均值,以便每列中的数据通过组内的平均值进行标准化。任何建议将不胜感激。
答案 0 :(得分:29)
In [10]: df.groupby('indx').transform(lambda x: (x - x.mean()) / x.std())
应该这样做。
答案 1 :(得分:6)
如果数据包含多个组(数千或更多),accepted answer可能需要很长时间才能计算。
即使groupby.transform
本身很快,就像lambda函数中已经向量化的调用(.mean()
,.std()
和减法)一样,对每个函数调用纯Python函数小组创造了相当大的开销。
这可以通过使用纯矢量化Pandas / Numpy调用而不是编写任何Python方法来避免,如ErnestScribbler's answer所示。
我们可以通过利用.transform
的广播能力来解决合并和命名列的难题:
def normalize_by_group(df, by):
groups = df.groupby(by)
# computes group-wise mean/std,
# then auto broadcasts to size of group chunk
mean = groups.transform(np.mean)
std = groups.transform(np.std)
return (df[mean.columns] - mean) / std
对于基准测试,我更改了原始问题的数据生成,以允许更多组:
def gen_data(N, num_groups):
m = 3
data = np.random.normal(size=(N,m)) + np.random.normal(size=(N,m))**3
indx = np.random.randint(0,num_groups,size=N).astype(np.int32)
df = pd.DataFrame(np.hstack((data, indx[:,None])),
columns=['a%s' % k for k in range(m)] + [ 'indx'])
return df
只有两个组(因此只有两个Python函数调用),lambda版本比numpy代码慢大约1.8倍:
In: df2g = gen_data(10000, 2) # 3 cols, 10000 rows, 2 groups
In: %timeit normalize_by_group(df2g, "indx")
6.61 ms ± 72.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In: %timeit df2g.groupby('indx').transform(lambda x: (x - x.mean()) / x.std())
12.3 ms ± 130 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
将组数增加到1000,运行时问题变得明显。 lambda版本比numpy代码慢370倍:
In: df1000g = gen_data(10000, 1000) # 3 cols, 10000 rows, 1000 groups
In: %timeit normalize_by_group(df1000g, "indx")
7.5 ms ± 87.1 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In: %timeit df1000g.groupby('indx').transform(lambda x: (x - x.mean()) / x.std())
2.78 s ± 13.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
答案 2 :(得分:2)
接受的答案有效且优雅。 不幸的是,对于大型数据集,我认为使用.transform()的性能明显要慢于执行不太优雅的跟随(使用单个列“a0”说明):
means_stds = df.groupby('indx')['a0'].agg(['mean','std']).reset_index()
df = df.merge(means_stds,on='indx')
df['a0_normalized'] = (df['a0'] - df['mean']) / df['std']
要为多个列执行此操作,您必须确定合并。我的建议是在this answer中将多索引列从聚合中展平,然后分别对每列进行合并和规范化:
means_stds = df.groupby('indx')[['a0','a1']].agg(['mean','std']).reset_index()
means_stds.columns = ['%s%s' % (a, '|%s' % b if b else '') for a, b in means_stds.columns]
df = df.merge(means_stds,on='indx')
for col in ['a0','a1']:
df[col+'_normalized'] = ( df[col] - df[col+'|mean'] ) / df[col+'|std']
答案 3 :(得分:1)
虽然这不是最漂亮的解决方案,但您可以这样做:
indx = df['indx'].copy()
for indices in df.groupby('indx').groups.values():
df.loc[indices] -= df.loc[indices].mean()
df['indx'] = indx