【问题标题】:Aggregating multiple string values in Pandas pivot table在 Pandas 数据透视表中聚合多个字符串值
【发布时间】:2018-08-12 15:14:42
【问题描述】:

我正在尝试创建一个日历,用于汇总项目目录中的信息,并按时间顺序和项目类型对其进行组织。我一直在使用 Pandas,但无法正确构建基本结构。例如,给定这个数据集:

           Type      Name   Health Month  Year
0     Marketing  ProjectA       OK   Jan  2018
1       Science  ProjectB  Warning   Apr  2018
2     Marketing  ProjectC       OK   Mar  2018
3   Development  ProjectD       OK   Feb  2018
4     Marketing  ProjectE       OK   Jan  2018
5   Development  ProjectF  Warning   Feb  2018
6   Development  ProjectG  Trouble   May  2018
7     Marketing  ProjectH  Trouble   May  2018
8   Development  ProjectI  Warning   Feb  2018
9     Marketing  ProjectJ       OK   May  2018
10      Science  ProjectK  Warning   Apr  2018

使用Remove none values from dataframe 中显示的技巧,我可以创建字段来跟踪决赛桌中每个项目的排名顺序:

df['aggval'] = df['Year'].map(str) + df['Month'] + df['Type']
df['index'] = df.groupby(['aggval']).cumcount()

产生 2 个额外的列:

           Type      Name   Health Month  Year              aggval  index
0     Marketing  ProjectA       OK   Jan  2018    2018JanMarketing      0
1       Science  ProjectB  Warning   Apr  2018      2018AprScience      0
2     Marketing  ProjectC       OK   Mar  2018    2018MarMarketing      0
3   Development  ProjectD       OK   Feb  2018  2018FebDevelopment      0
4     Marketing  ProjectE       OK   Jan  2018    2018JanMarketing      1
5   Development  ProjectF  Warning   Feb  2018  2018FebDevelopment      1
6   Development  ProjectG  Trouble   May  2018  2018MayDevelopment      0
7     Marketing  ProjectH  Trouble   May  2018    2018MayMarketing      0
8   Development  ProjectI  Warning   Feb  2018  2018FebDevelopment      2
9     Marketing  ProjectJ       OK   May  2018    2018MayMarketing      1
10      Science  ProjectK  Warning   Apr  2018      2018AprScience      1

使用这些提取列,我们现在可以进行旋转以创建项目汇总表的初始版本:

pv1 = pd.pivot_table(df, values='Name', index=['Type', 'index'], columns=['Year', 'Month'], aggfunc=lambda x: "".join(x)).fillna('')
pv1 = pv1.reindex(columns = zip(12 * [2018], ['Jan', 'Feb', 'Mar', 'Apr', 'May']))

生成下面的报告。这基本上是正确的:它收集和列出项目,显示它们的名称,并按类型(泳道)和按时间顺序组织它们:

Year                 2018                                          
Month                Jan       Feb       Mar       Apr       May   
Type        index                                                  
Development 0                ProjectD                      ProjectG
            1                ProjectF                              
            2                ProjectI                              
Marketing   0      ProjectA            ProjectC            ProjectH
            1      ProjectE                                ProjectJ
Science     0                                    ProjectB          
            1                                    ProjectK          

我现在很难尝试扩展此模型以同时显示每个项目的名称和运行状况。

我可以在 Health 字段中添加第二个数据透视表值:

pv2 = pd.pivot_table(df, values=['Name', 'Health'], index=['Type', 'index'], columns=['Year', 'Month'], aggfunc={'Name':lambda x: "|".join(x), 'Health':lambda x: ":".join(x), }).fillna('')
# pv2 = pv2.reindex(columns = zip(10 * [2018], ['Jan', 'Jan', 'Feb', 'Feb', 'Mar', 'Mar', 'Apr', 'Apr', 'May', 'May'], ['Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name']))

生产:

                   Health                               Name                                          
Year                2018                                2018                                          
Month               Apr      Feb    Jan Mar   May       Apr       Feb       Jan       Mar       May   
Type        index                                                                                     
Development 0                    OK          Trouble            ProjectD                      ProjectG
            1               Warning                             ProjectF                              
            2               Warning                             ProjectI                              
Marketing   0                        OK  OK  Trouble                      ProjectA  ProjectC  ProjectH
            1                        OK           OK                      ProjectE            ProjectJ
Science     0      Warning                            ProjectB                                        
            1      Warning                            ProjectK                  

这是正确的想法 - 每个项目的项目 Health 和 Name 都显示在正确的 Month 和正确的 Type 泳道中,但我希望它们按项目并排显示。重新索引列会在标题级别产生正确的结果,但会清除具有 Nan 值的单元格:

pv2 = pd.pivot_table(df, values=['Name', 'Health'], index=['Type', 'index'], columns=['Year', 'Month'], aggfunc={'Name':lambda x: "|".join(x), 'Health':lambda x: ":".join(x), }).fillna('')
pv2 = pv2.reindex(columns = zip(10 * [2018], ['Jan', 'Jan', 'Feb', 'Feb', 'Mar', 'Mar', 'Apr', 'Apr', 'May', 'May'], ['Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name']))

产生:

                   2018                                                      
Year               Jan         Feb         Mar         Apr         May       
Month             Health Name Health Name Health Name Health Name Health Name
Type        index                                                            
Development 0      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 
            1      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 
            2      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 
Marketing   0      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 
            1      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 
Science     0      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 
            1      NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN   NaN 

同样,结构现在是正确的,但单元格值不再显示项目特定的数据。我错过了什么?

【问题讨论】:

    标签: pandas pivot pivot-table


    【解决方案1】:

    pv2 开始时按此顺序排列列:

    In [35]: pv2.columns.tolist()
    Out[35]: 
    [('Health', 2018, 'Apr'),
     ('Health', 2018, 'Feb'),
     ('Health', 2018, 'Jan'),
     ('Health', 2018, 'Mar'),
     ('Health', 2018, 'May'),
     ('Name', 2018, 'Apr'),
     ('Name', 2018, 'Feb'),
     ('Name', 2018, 'Jan'),
     ('Name', 2018, 'Mar'),
     ('Name', 2018, 'May')]
    

    我们想要重新排列列以使其具有此顺序:

    In [36]: list(zip(10 * [2018], ['Jan', 'Jan', 'Feb', 'Feb', 'Mar', 'Mar', 'Apr', 'Apr', 'May', 'May'], ['Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name']))
    Out[36]: 
    [(2018, 'Jan', 'Health'),
     (2018, 'Jan', 'Name'),
     (2018, 'Feb', 'Health'),
     (2018, 'Feb', 'Name'),
     (2018, 'Mar', 'Health'),
     (2018, 'Mar', 'Name'),
     (2018, 'Apr', 'Health'),
     (2018, 'Apr', 'Name'),
     (2018, 'May', 'Health'),
     (2018, 'May', 'Name')]
    

    每列由一个 3 元组表示。 reindex 可以重新排序列列表,但不能更改 3 元组中项目的内部顺序。为此,请使用reorder_levels

    In [37]: pv2 = pv2.reorder_levels(['Year','Month',0], axis=1)
    In [38]: pv2.columns.tolist()
    Out[38]: 
    [(2018, 'Apr', 'Health'),
     (2018, 'Feb', 'Health'),
     (2018, 'Jan', 'Health'),
     (2018, 'Mar', 'Health'),
     (2018, 'May', 'Health'),
     (2018, 'Apr', 'Name'),
     (2018, 'Feb', 'Name'),
     (2018, 'Jan', 'Name'),
     (2018, 'Mar', 'Name'),
     (2018, 'May', 'Name')]
    

    按所需顺序获得级别后,您可以致电reindex 重新排序列(以按顺序排列月份)。


    import sys
    import pandas as pd
    pd.options.display.width = sys.maxsize
    
    df = pd.DataFrame({'Health': ['OK', 'Warning', 'OK', 'OK', 'OK', 'Warning', 'Trouble', 'Trouble', 'Warning', 'OK', 'Warning'], 'Month': ['Jan', 'Apr', 'Mar', 'Feb', 'Jan', 'Feb', 'May', 'May', 'Feb', 'May', 'Apr'], 'Name': ['ProjectA', 'ProjectB', 'ProjectC', 'ProjectD', 'ProjectE', 'ProjectF', 'ProjectG', 'ProjectH', 'ProjectI', 'ProjectJ', 'ProjectK'], 'Type': ['Marketing', 'Science', 'Marketing', 'Development', 'Marketing', 'Development', 'Development', 'Marketing', 'Development', 'Marketing', 'Science'], 'Year': [2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018]})
    
    df['index'] = df.groupby(['Year','Month','Type']).cumcount()
    
    pv2 = pd.pivot_table(df, values=['Name', 'Health'], index=['Type', 'index'], 
                         columns=['Year', 'Month'], 
                         aggfunc={'Name':lambda x: "|".join(x), 
                                  'Health':lambda x: ":".join(x), }).fillna('')
    pv2 = pv2.reorder_levels(['Year','Month',0], axis=1)
    pv2 = pv2.reindex(columns = zip(10 * [2018], ['Jan', 'Jan', 'Feb', 'Feb', 'Mar', 'Mar', 'Apr', 'Apr', 'May', 'May'], ['Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name', 'Health', 'Name']))
    
    print(pv2)
    

    产量

    Year                2018                                                                                    
    Month                Jan                Feb              Mar                Apr                May          
                      Health      Name   Health      Name Health      Name   Health      Name   Health      Name
    Type        index                                                                                           
    Development 0                            OK  ProjectD                                      Trouble  ProjectG
                1                       Warning  ProjectF                                                       
                2                       Warning  ProjectI                                                       
    Marketing   0         OK  ProjectA                        OK  ProjectC                     Trouble  ProjectH
                1         OK  ProjectE                                                              OK  ProjectJ
    Science     0                                                           Warning  ProjectB                   
                1                                                           Warning  ProjectK                   
    

    虽然有时您可能需要手动指定所需的顺序 列,这不是(必然)这些情况之一。你想要的顺序是 自然日期顺序。因此,解析YearMonth 标记为实际日期(dtype datetime64[ns])。这解锁了 Pandas 的智能日期时间处理行为。

    • 例如,pivot_table 将自动为我们对日期进行排序,如果我们以日期列为中心(即 dtype 为 datetime64[ns] 的列)。

    • 此外,我们可以方便地按顺序生成所有日历月,而无需手动输入日期:

      dates = pd.date_range('2018-01-01', '2018-12-31', freq='MS')
      
    • 我们也可以轻松地将 DatetimeIndex 转换为 2 级 MultiIndex 年/月格式(用于演示目的):

      pv2.index = pd.Index(pv2.index.strftime('%Y-%b')).str.split('-', expand=True)
      

    例如,

    import sys
    import pandas as pd
    pd.options.display.width = sys.maxsize
    
    df = pd.DataFrame({'Health': ['OK', 'Warning', 'OK', 'OK', 'OK', 'Warning', 'Trouble', 'Trouble', 'Warning', 'OK', 'Warning'], 'Month': ['Jan', 'Apr', 'Mar', 'Feb', 'Jan', 'Feb', 'May', 'May', 'Feb', 'May', 'Apr'], 'Name': ['ProjectA', 'ProjectB', 'ProjectC', 'ProjectD', 'ProjectE', 'ProjectF', 'ProjectG', 'ProjectH', 'ProjectI', 'ProjectJ', 'ProjectK'], 'Type': ['Marketing', 'Science', 'Marketing', 'Development', 'Marketing', 'Development', 'Development', 'Marketing', 'Development', 'Marketing', 'Science'], 'Year': [2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018, 2018]})
    
    df['Date'] = pd.to_datetime(df['Year'].astype('str')+df['Month'], format='%Y%b')
    df['index'] = df.groupby(['Date','Type']).cumcount()
    
    pv2 = pd.pivot_table(df, values=['Name', 'Health'], columns=['Type', 'index'], 
                         index=['Date'], 
                         aggfunc={'Name':lambda x: "|".join(x), 
                                  'Health':lambda x: ":".join(x), }).fillna('')
    
    dates = pd.date_range('2018-01-01', '2018-12-31', freq='MS')
    pv2 = pv2.reindex(dates, fill_value='')
    pv2.index = pd.Index(pv2.index.strftime('%Y-%b')).str.split('-', expand=True)
    pv2 = pv2.stack(0)
    pv2 = pv2.T
    print(pv2)
    

    产量

                        2018                                                                                     ...                                                             
                         Jan                Feb              Mar                Apr                May           ...     Aug         Sep         Oct         Nov         Dec     
                      Health      Name   Health      Name Health      Name   Health      Name   Health      Name ...  Health Name Health Name Health Name Health Name Health Name
    Type        index                                                                                            ...                                                             
    Development 0                            OK  ProjectD                                      Trouble  ProjectG ...                                                             
                1                       Warning  ProjectF                                                        ...                                                             
                2                       Warning  ProjectI                                                        ...                                                             
    Marketing   0         OK  ProjectA                        OK  ProjectC                     Trouble  ProjectH ...                                                             
                1         OK  ProjectE                                                              OK  ProjectJ ...                                                             
    Science     0                                                           Warning  ProjectB                    ...                                                             
                1                                                           Warning  ProjectK                    ...                                                             
    

    【讨论】:

    • 感谢@unutbu,这很接近,但它仍然将所有“健康”列和所有“名称”列放在一起。我希望在每个月下看到一个健康和名称列,因此项目名称和状态显示在彼此旁边。我想这需要每个月的标题跨越 2 列(一列用于名称,一列用于类型)
    • 很抱歉;我爱上了MultiIndex.from_product,忘记了你真正要求的顺序。上面的代码现在使用您想要的顺序。
    • 谢谢@unutbu 这现在更有意义了——效果很好
    • 关于您对列排序的第二个解释,您的方法是否仍能保证列的存在?也就是说,即使那里没有值,也要确保有 Jan、Feb、Mar 等列?回想一下,目标是创建一种日历视图,所以我认为手动指定列是确保表示每个月的最佳方式,而与实际记录内容无关
    • 在这种情况下,我想我会将YearMonth 列转换为单个日期时间列first。对实际日期时间进行所有计算,然后转换回Year/Month 格式,仅在最后用于演示目的。我已经编辑了帖子以说明我的意思。
    【解决方案2】:

    IIUC,你只需要swaplevelsort_index

    #pv2 = pd.pivot_table(df, values=['Name', 'Health'], index=['Type', 'index'], columns=['Year', 'Month'], aggfunc={'Name':lambda x: "|".join(x), 'Health':lambda x: ":".join(x), }).fillna('')
    
    pv2.swaplevel(0,1,axis=1).swaplevel(1,2,axis=1).sort_index(axis=1)
    
    Out[220]: 
    Year                  2018                                                \
    Month                  Apr                Feb              Jan             
                        Health      Name   Health      Name Health      Name   
    Type        index                                                          
    Development 0                              OK  ProjectD                    
                1                         Warning  ProjectF                    
                2                         Warning  ProjectI                    
    Marketing   0                                               OK  ProjectA   
                1                                               OK  ProjectE   
    Science     0      Warning  ProjectB                                       
                1      Warning  ProjectK                                       
    Year                                                   
    Month                Mar                May            
                      Health      Name   Health      Name  
    Type        index                                      
    Development 0                       Trouble  ProjectG  
                1                                          
                2                                          
    Marketing   0         OK  ProjectC  Trouble  ProjectH  
                1                            OK  ProjectJ  
    Science     0                                          
                1                                          
    
    #pv2.swaplevel(0,1,axis=1).swaplevel(1,2,axis=1).sort_index(axis=1).to_excel('aaaaaa.xlsx')
    

    【讨论】:

    • 谢谢@Wen。您的输出是我正在寻找的,但我无法仅使用您生成的swaplevel 代码来重现它。您能否编辑您的答案以包括上游步骤,包括创建数据透视表以及介于两者之间的任何内容?
    • 谢谢你帮我修好了。 sort_index 最后做了什么?可以用来对列重新排序(例如,将其设为 Name,Health 而不是 Health,Name)?
    • 另外,如果您导出到 Excel,您能否确认您看到相同的格式?奇怪的是,当我调用 to_excel 时,我会在生成的 Excel 文件中看到 Health 和 Name 列再次取消分组
    • @Ramon排序索引,是对索引进行排序,不是对索引进行重新排序,它会在每一级索引内重新排列顺序
    • @Ramon 我写入excel后会更新图片
    猜你喜欢
    • 2020-08-17
    • 1970-01-01
    • 2020-06-05
    • 1970-01-01
    • 2019-06-12
    • 2018-07-13
    • 1970-01-01
    • 2020-08-15
    • 1970-01-01
    相关资源
    最近更新 更多