groupby和规范化两个数组

时间:2016-06-07 16:41:10

标签: python numpy pandas group-by multi-index

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

我想重新缩放'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

2 个答案:

答案 0 :(得分:2)

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

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

也是一个有点难以理解的oneliner:

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

答案 1 :(得分:1)

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

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

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

  • 使用pd.IndexSlice按其不同级别对pd.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