按组规范化DataFrame

Jos*_*del 15 python pandas

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

N = 20
m = 3
data = np.random.normal(size=(N,m)) + np.random.normal(size=(N,m))**3
Run Code Online (Sandbox Code Playgroud)

然后我创建一些分类变量:

indx = np.random.randint(0,3,size=N).astype(np.int32)
Run Code Online (Sandbox Code Playgroud)

并生成一个DataFrame:

import pandas as pd
df = pd.DataFrame(np.hstack((data, indx[:,None])), 
             columns=['a%s' % k for k in range(m)] + [ 'indx'])
Run Code Online (Sandbox Code Playgroud)

我可以得到每组的平均值:

df.groubpy('indx').mean()
Run Code Online (Sandbox Code Playgroud)

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

Tom*_*ger 34

In [10]: df.groupby('indx').transform(lambda x: (x - x.mean()) / x.std())
Run Code Online (Sandbox Code Playgroud)

应该这样做.


w-m*_*w-m 8

如果数据包含许多组(数千个或更多),则可接受的答案可能需要很长时间才能计算出来。

尽管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
Run Code Online (Sandbox Code Playgroud)

为了进行基准测试,我将原始问题的数据生成更改为允许更多组:

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
Run Code Online (Sandbox Code Playgroud)

只有两个组(因此只有两个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)
Run Code Online (Sandbox Code Playgroud)

将组数增加到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)
Run Code Online (Sandbox Code Playgroud)