groupby 并规范化两个数组

groupby and normalize over two arrays

我有一个DataFrame,其中的列是MultiIndex。第一个 level 指定 'labels',第二个指定 'values'df.labels(i, j)位置的一个'label'对应df.values(i, j)位置的'value'

我想重新缩放 'values',使它们在相应的 'labels' 定义的每个组中总和为一个。

import pandas as pd
import numpy as np

np.random.seed([3,1415])
df1 = pd.DataFrame(np.random.choice(('a', 'b', 'c', 'd'),
                                    (10, 5), p=(.4, .3, .2, .1)))
df2 = pd.DataFrame((np.random.rand(10, 5) * 10).round(0))

df = pd.concat([df1, df2], axis=1, keys=['labels', 'values'])
print df

  labels             values                     
       0  1  2  3  4      0    1     2    3    4
0      b  b  b  b  b    5.0  2.0   7.0  7.0  4.0
1      a  c  c  c  c    6.0  8.0   1.0  5.0  7.0
2      d  c  c  d  c    6.0  3.0  10.0  7.0  4.0
3      a  a  a  b  a    5.0  9.0   9.0  5.0  8.0
4      a  b  a  c  c    0.0  4.0   1.0  8.0  0.0
5      c  b  a  a  b    1.0  6.0   8.0  6.0  1.0
6      c  c  c  a  c    9.0  9.0   4.0  1.0  1.0
7      d  c  a  b  c    7.0  0.0   3.0  6.0  4.0
8      b  a  b  a  a    8.0  6.0   3.0  5.0  4.0
9      c  c  c  b  c    2.0  5.0   3.0  1.0  3.0

我希望结果如下所示:

  labels                values                                        
       0  1  2  3  4         0         1         2         3         4
0      b  b  b  b  b  0.084746  0.033898  0.118644  0.118644  0.067797
1      a  c  c  c  c  0.084507  0.091954  0.011494  0.057471  0.080460
2      d  c  c  d  c  0.300000  0.034483  0.114943  0.350000  0.045977
3      a  a  a  b  a  0.070423  0.126761  0.126761  0.084746  0.112676
4      a  b  a  c  c  0.000000  0.067797  0.014085  0.091954  0.000000
5      c  b  a  a  b  0.011494  0.101695  0.112676  0.084507  0.016949
6      c  c  c  a  c  0.103448  0.103448  0.045977  0.014085  0.011494
7      d  c  a  b  c  0.350000  0.000000  0.042254  0.101695  0.045977
8      b  a  b  a  a  0.135593  0.084507  0.050847  0.070423  0.056338
9      c  c  c  b  c  0.022989  0.057471  0.034483  0.016949  0.034483

虽然 pd.DataFrame.xs 可以方便地检索一些切片:

df.xs('values', axis=1, level=0)

不幸的是,它不允许我们分配。如果我们想使用 pd.DataFrame.loc,我们需要能够指定我们想要分配给的行和列索引。

  • 使用 pd.IndexSlicepd.MultiIndex 分割成不同的层次。以下是从第一级访问 values 索引且对第二级没有限制的通用表示。

    pd.IndexSlice['values', :]
    
  • 当我们将其与 pd.DataFrame.loc 结合使用时,我们允许自己分配给 pd.DataFrame 的非常具体的片段。以下检索并允许无限制地分配给所有行,并限制为第一级等于 'values'

    的列
    df.loc[:, pd.IndexSlice['values', :]]
    
  • 为了规范化 labels 部分中的值,我将 stack() df 展开所有 'labels'到与 values 对齐的单个列中。这是这个堆叠的head()

    df.stack().head()
    
        labels    values
    0 0      b  0.084746
      1      b  0.033898
      2      b  0.118644
      3      b  0.118644
      4      b  0.067797
    
  • 此时 groupby('labels') 非常简单,除了我在最后使用 .values 以避免在我知道我时必须生成正确的索引'我们已经得到了正确顺序的值数组。


最终答案

df.loc[:, pd.IndexSlice['values', :]] = \
    df.stack().groupby('labels')['values'].apply(
        lambda x: x / x.sum()).unstack().values

要获得标准化值,您可以:

new_values = pd.DataFrame(data=np.zeros(df['values'].shape))
for v in np.unique(df['labels']):
    mask = df['values'].where(df['labels'].isin([v]))
    new_values += mask.div(mask.sum().sum()).fillna(0)
df.loc[:, 'values'] = new_values.values

还有一个有点难读的单行本:

df.loc[:, 'values'] = np.sum([df['values'].where(df['labels'].isin([v])).div(df['values'].where(df['labels'].isin([v])).sum().sum()).fillna(0).values for v in np.unique(df['labels'])], axis=0)

或者,使用 .groupby():

tmp = pd.DataFrame(np.hstack((df['labels'].values.reshape(-1, 1), df['values'].values.reshape(-1, 1))))
df.loc[:, 'values'] = tmp.groupby(0).transform(lambda x: x/x.sum()).values.reshape(df['values'].shape)

两者都导致:

  labels                values                                        
       0  1  2  3  4         0         1         2         3         4
0      b  b  b  b  b  0.084746  0.033898  0.118644  0.118644  0.067797
1      a  c  c  c  c  0.084507  0.091954  0.011494  0.057471  0.080460
2      d  c  c  d  c  0.300000  0.034483  0.114943  0.350000  0.045977
3      a  a  a  b  a  0.070423  0.126761  0.126761  0.084746  0.112676
4      a  b  a  c  c  0.000000  0.067797  0.014085  0.091954  0.000000
5      c  b  a  a  b  0.011494  0.101695  0.112676  0.084507  0.016949
6      c  c  c  a  c  0.103448  0.103448  0.045977  0.014085  0.011494
7      d  c  a  b  c  0.350000  0.000000  0.042254  0.101695  0.045977
8      b  a  b  a  a  0.135593  0.084507  0.050847  0.070423  0.056338
9      c  c  c  b  c  0.022989  0.057471  0.034483  0.016949  0.034483