按组规范化DataFrame

时间:2014-09-25 19:06:50

标签: python pandas

假设我有一些数据生成如下:

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()

我不确定如何做的是然后在原始数据中减去每个组,每列的平均值,以便每列中的数据通过组内的平均值进行标准化。任何建议将不胜感激。

4 个答案:

答案 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