【问题标题】:Pandas: apply operation to repetitive columns in MultiIndexPandas:将操作应用于 MultiIndex 中的重复列
【发布时间】:2016-05-27 19:08:50
【问题描述】:

我有 MultiColumns:第二层重复包含 Job OpeningsHires。我想为每个顶级列减去一个 - 但我所做的一切都会让我陷入索引错误或切片错误。如何计算?

样本数据:

>>> df.head()
Out[25]: 
           Total nonfarm              Total private               
                   Hires Job openings         Hires Job openings   
date                                                               
2001-01-01          5777         5385          5419         4887   
2002-01-01          4849         3759          4539         3381   
2003-01-01          4971         3824          4645         3424   
2004-01-01          4827         3459          4552         3153   
2005-01-01          5207         3670          4876         3358  

预期输出:

Out[25]: 
           Total nonfarm   Total private              
              difference      difference   
date                                                               
2001-01-01          1234            5678          
2002-01-01          1234            5678          
2003-01-01          1234            5678         
2004-01-01          1234            5678      
2005-01-01          1234            5678    

数字明显不正确。

特别是在 apply() 中

为了有一个普遍适用的方式,我试图设置

def apply(group):
    result = group.loc[:, pd.IndexSlice[:, 'Job openings']].div(group.loc[:, pd.IndexSlice[:, 'Hires']].values)
    result.columns = pd.MultiIndex.from_product([[group.columns.get_level_values(0)[0]], ['Ratio']])
    return result.values
foo = df.groupby(axis=1, level=0).apply(apply)

这有两个问题:

  • 我需要用.values 作弊,才能正确地分开
  • foo 不是正确的数据框:

    住宿和餐饮服务 [[0.76], [0.480349344978], [0.501388888889], [... 艺术、娱乐和休闲 [[0.558139534884],[0.46017699115],[0.2483221... 建设 [[0.35], [0.274881516588], [0.267260579065], [...

我首先尝试返回result,而不是result.values,但这只会导致一个充满NaN的数据框

特别是使用列名

我不喜欢最高投票的答案是它需要 .diff().div() - hacks,这使得代码难以阅读,并且当有超过两列时难以实现子级。

【问题讨论】:

    标签: python pandas dataframe


    【解决方案1】:

    设置

    import pandas as pd
    
    df = pd.DataFrame(
        [
            [5777, 5385, 5419, 4887],
            [4849, 3759, 4539, 3381],
            [4971, 3824, 4645, 3424],
            [4827, 3459, 4552, 3153],
            [5207, 3670, 4876, 3358],
        ],
        index=pd.to_datetime(['2001-01-01',
                              '2002-01-01',
                              '2003-01-01',
                              '2004-01-01',
                              '2005-01-01']),
        columns=pd.MultiIndex.from_tuples(
            [('Total nonfarm', 'Hires'), ('Total nonfarm', 'Job Openings'),
             ('Total private', 'Hires'), ('Total private', 'Job Openings')]
        )
    )
    
    print df
    
               Total nonfarm              Total private             
                       Hires Job Openings         Hires Job Openings
    2001-01-01          5777         5385          5419         4887
    2002-01-01          4849         3759          4539         3381
    2003-01-01          4971         3824          4645         3424
    2004-01-01          4827         3459          4552         3153
    2005-01-01          5207         3670          4876         3358
    

    试试:

    df.T.groupby(level=0).diff(-1).dropna().T
    
               Total nonfarm Total private
                       Hires         Hires
    2001-01-01         392.0         532.0
    2002-01-01        1090.0        1158.0
    2003-01-01        1147.0        1221.0
    2004-01-01        1368.0        1399.0
    2005-01-01        1537.0        1518.0
    

    要应用其他变换,比如比率,您可以这样做:

    print df.T.groupby(level=0).apply(lambda x: np.exp(np.log(x).diff(-1))).dropna().T
    
               Total nonfarm Total private
                       Hires         Hires
    2001-01-01      1.072795      1.108860
    2002-01-01      1.289971      1.342502
    2003-01-01      1.299948      1.356600
    2004-01-01      1.395490      1.443704
    2005-01-01      1.418801      1.452055
    

    或者:

    print df.T.groupby(level=0).apply(lambda x: x.div(x.shift(-1))).dropna().T
    
               Total nonfarm Total private
                       Hires         Hires
    2001-01-01      1.072795      1.108860
    2002-01-01      1.289971      1.342502
    2003-01-01      1.299948      1.356600
    2004-01-01      1.395490      1.443704
    2005-01-01      1.418801      1.452055
    

    要重命名列并与原始数据框组合,您可以:

    df2 = df.T.groupby(level=0).diff(-1).dropna().T
    df2.columns = pd.MultiIndex.from_tuples(
        [('Total nonfarm', 'difference'),
         ('Total private', 'difference')])
    pd.concat([df, df2], axis=1).sort_index(axis=1)
    

    看起来像:

               Total nonfarm                         Total private               \
                       Hires Job Openings difference         Hires Job Openings   
    2001-01-01          5777         5385      392.0          5419         4887   
    2002-01-01          4849         3759     1090.0          4539         3381   
    2003-01-01          4971         3824     1147.0          4645         3424   
    2004-01-01          4827         3459     1368.0          4552         3153   
    2005-01-01          5207         3670     1537.0          4876         3358   
    
               difference  
    2001-01-01      532.0  
    2002-01-01     1158.0  
    2003-01-01     1221.0  
    2004-01-01     1399.0  
    2005-01-01     1518.0  
    

    【讨论】:

    • 非常聪明——但这会是不同操作的通用解决方案吗?例如,如果我想计算比率怎么办?
    • 让我们看看。首先,处理示例数据需要时间,因为您没有提供代码来生成数据框。所以,我让你知道什么可能有效。从那时起,我已经构建了您的数据框,并且可以进行更多实验。
    • 哦,以后你可以直接复制df.head()后面的输出,用pd.read_clipboard()读进去。
    • 敬畏,这行得通吧?不知道为什么我没有尝试。
    • @piRSquared 我找到了更好的解决方案替代方案,请参阅我的答案。
    【解决方案2】:

    我觉得你可以用IndexSlice:

    idx = pd.IndexSlice
    df[('Total private','difference')] = (df.loc[:, idx[('Total nonfarm', 'Hires')]] - 
                                          df.loc[:, idx[('Total private', 'Hires')]])
    print (df)
               Total nonfarm              Total private                        
    date               Hires Job openings         Hires Job openings difference
    2001-01-01          5777         5385          5419         4887        358
    2002-01-01          4849         3759          4539         3381        310
    2003-01-01          4971         3824          4645         3424        326
    2004-01-01          4827         3459          4552         3153        275
    2005-01-01          5207         3670          4876         3358        331
    

    如果你想要多列,你可以使用修改后的piRSquared's answer - 你可以去掉转置:

    print (df.groupby(level=0,axis=1).diff(-1).dropna(1))
               Total nonfarm Total private             
    date               Hires         Hires Job openings
    2001-01-01         392.0         532.0       4495.0
    2002-01-01        1090.0        1158.0       2291.0
    2003-01-01        1147.0        1221.0       2277.0
    2004-01-01        1368.0        1399.0       1785.0
    2005-01-01        1537.0        1518.0       1821.0
    

    【讨论】:

    • 我喜欢你的第一个答案,特别是因为它没有使用div()diff() hacks。如果有两个以上的子列,我特别想划分Hires/Job openings,而忽略第三列怎么办?这里的许多建议感觉它们对场景非常特殊,并不普遍适用。我觉得如果在 10 天内我遇到了类似的问题,除非这正是我今天遇到的情况,否则我没有学到太多可以帮助我解决该问题的知识
    • 感谢您的支持。 “超过两个子列”是什么意思?你可以添加样品吗?现在我没有时间陪你,但稍后我会尽力帮助你。
    • 第 1 层的另一列,例如通过df.loc[:, idx['Total nonfarm', 'foo']] = 15 生成
    【解决方案3】:

    让我们保持简单。

    In [19]: df['Total nonfarm'] - df['Total private']
    Out[19]: 
                Hires  Job Openings
    2001-01-01    358           498
    2002-01-01    310           378
    2003-01-01    326           400
    2004-01-01    275           306
    2005-01-01    331           312
    

    【讨论】:

      【解决方案4】:

      解决此问题的另一种方法是交换列级别并使用列访问器。

      设置

      import pandas as pd
      
      df = pd.DataFrame(
          [
              [5777, 5385, 5419, 4887],
              [4849, 3759, 4539, 3381],
              [4971, 3824, 4645, 3424],
              [4827, 3459, 4552, 3153],
              [5207, 3670, 4876, 3358],
          ],
          index=pd.to_datetime(['2001-01-01',
                                '2002-01-01',
                                '2003-01-01',
                                '2004-01-01',
                                '2005-01-01']),
          columns=pd.MultiIndex.from_tuples(
              [('Total nonfarm', 'Hires'), ('Total nonfarm', 'Job Openings'),
               ('Total private', 'Hires'), ('Total private', 'Job Openings')]
          )
      )
      
      print df
                 Total nonfarm              Total private             
                         Hires Job Openings         Hires Job Openings
      2001-01-01          5777         5385          5419         4887
      2002-01-01          4849         3759          4539         3381
      2003-01-01          4971         3824          4645         3424
      2004-01-01          4827         3459          4552         3153
      2005-01-01          5207         3670          4876         3358
      

      如果我们交换级别然后排序,它看起来像:

      print df.swaplevel(0, 1, 1).sort_index(axis=1)
      
                         Hires                Job Openings              
                 Total nonfarm Total private Total nonfarm Total private
      2001-01-01          5777          5419          5385          4887
      2002-01-01          4849          4539          3759          3381
      2003-01-01          4971          4645          3824          3424
      2004-01-01          4827          4552          3459          3153
      2005-01-01          5207          4876          3670          3358
      

      有了这个,我们可以通过.Hires['Hires'] 访问Hires。将此与您的减法相结合:

      print df.swaplevel(0, 1, 1)['Hires']
      
                  Total nonfarm  Total private
      2001-01-01           5777           5419
      2002-01-01           4849           4539
      2003-01-01           4971           4645
      2004-01-01           4827           4552
      2005-01-01           5207           4876
      
      print df.swaplevel(0, 1, 1)['Hires'] - df.swaplevel(0, 1, 1)['Job Openings']
      
                  Total nonfarm  Total private
      2001-01-01            392            532
      2002-01-01           1090           1158
      2003-01-01           1147           1221
      2004-01-01           1368           1399
      2005-01-01           1537           1518
      

      解决方案

      加上一点额外的东西,我做到了:

      df_ = df.swaplevel(0, 1, 1)
      
      _df = pd.concat([
              df_,
              pd.concat([df_['Hires'] - df_['Job Openings'], df_['Hires'] / df_['Job Openings']],
                       axis=1, keys=['Difference', 'Ratio'])
          ], axis=1)
      
      df = _df.swaplevel(0, 1, 1).sort_index(axis=1)
      
      print df
      
                 Total nonfarm                              Total private        \
                    Difference Hires Job Openings     Ratio    Difference Hires   
      2001-01-01           392  5777         5385  1.072795           532  5419   
      2002-01-01          1090  4849         3759  1.289971          1158  4539   
      2003-01-01          1147  4971         3824  1.299948          1221  4645   
      2004-01-01          1368  4827         3459  1.395490          1399  4552   
      2005-01-01          1537  5207         3670  1.418801          1518  4876   
      
      
                 Job Openings     Ratio  
      2001-01-01         4887  1.108860  
      2002-01-01         3381  1.342502  
      2003-01-01         3424  1.356600  
      2004-01-01         3153  1.443704  
      2005-01-01         3358  1.452055 
      

      您也可以使用xs 来抓取横截面。

      kw = dict(axis=1, level=1)
      
      df.xs('Hires', **kw) - df.xs('Job Openings', **kw)
      
                  Total nonfarm  Total private
      2001-01-01            392            532
      2002-01-01           1090           1158
      2003-01-01           1147           1221
      2004-01-01           1368           1399
      2005-01-01           1537           1518
      

      【讨论】:

      • 是否可以使用 xs 方法在多索引数据框中创建列,例如 example
      • IIUC,是的。 xs 有一个 drop_level=True 参数,可以设置为 False 从而保留多索引。
      • 我的偏好是使用你的groupbyapply 但是当我尝试使用四个级别时它失败了。为什么会这样?
      【解决方案5】:

      使用groupbyapply

      设置

      import pandas as pd
      
      df = pd.DataFrame(
          [
              [5777, 5385, 5419, 4887],
              [4849, 3759, 4539, 3381],
              [4971, 3824, 4645, 3424],
              [4827, 3459, 4552, 3153],
              [5207, 3670, 4876, 3358],
          ],
          index=pd.to_datetime(['2001-01-01',
                                '2002-01-01',
                                '2003-01-01',
                                '2004-01-01',
                                '2005-01-01']),
          columns=pd.MultiIndex.from_tuples(
              [('Total nonfarm', 'Hires'), ('Total nonfarm', 'Job Openings'),
               ('Total private', 'Hires'), ('Total private', 'Job Openings')]
          )
      )
      
      print df
      

      解决方案

      def diff(group):
          g = group.shift().sub(group).dropna()
          g.index = ['Difference']
          return g
      
      def ratio(group):
          g = group.shift().div(group).dropna()
          g.index = ['Ratio']
          return g
      
      def do_nothing(group):
          return group
      
      pd.concat(
          [df.T.groupby(level=0).apply(f).T for f in [diff, ratio, do_nothing]],
          axis=1
      ).sort_index(axis=1)
      
                 Total nonfarm                          Total private        \
                    Difference Hires Job Openings Ratio    Difference Hires   
      2001-01-01         392.0  5777         5385  1.07         532.0  5419   
      2002-01-01        1090.0  4849         3759  1.29        1158.0  4539   
      2003-01-01        1147.0  4971         3824  1.30        1221.0  4645   
      2004-01-01        1368.0  4827         3459  1.40        1399.0  4552   
      2005-01-01        1537.0  5207         3670  1.42        1518.0  4876   
      
      
                 Job Openings Ratio  
      2001-01-01         4887  1.11  
      2002-01-01         3381  1.34  
      2003-01-01         3424  1.36  
      2004-01-01         3153  1.44  
      2005-01-01         3358  1.45  
      

      【讨论】:

      • 我尝试同化上述here,但找不到正确的语法。你能提供任何意见吗?
      猜你喜欢
      • 2021-02-12
      • 1970-01-01
      • 2013-02-04
      • 2020-11-30
      • 1970-01-01
      • 2017-05-04
      • 2020-06-15
      • 2017-03-02
      相关资源
      最近更新 更多