将数据分组到 Pandas 多索引

Grouped data to Pandas Multi-Index

我在 Excel 中有一些数据已经分组(这是来自某些会计软件的现金流量表),我正在使用 Pandas 使用 read_excel() 方法。我想在前 4 列上创建一个索引,但我不知道如何维护帐户的层次结构。层次结构如下所示:

当我尝试从前 4 列创建多索引或者如果我对它们执行 ffill() 时,Pandas(有效地)会这样做...

突出显示的单元格不符合我的需要,它们应该 blank/NaN 以保持层次结构。当级别 2 的子类别 B2 开始时,不应填充级别 3 和 4 的任何类别。

这是我想要实现的目标:

最终,这些是多年的现金流量表,会计科目表略有不同,所以我希望将它们导入 Pandas DataFrames 并合并它们,以便所有账户都在整个队列中排列时间段...这只是该过程的第一步。我可以手动将其编码到字典中,但我想知道是否可以在 Pandas?

内更简单地编写它

这是一种通过颠倒列顺序和 isna 上的 cumprod 的方法:

给定 df,

df = pd.DataFrame({'Level 1':['A1']+['']*7,
                  'Level 2':['']+['B1']+['']*3+['B2']+['']*2,
                  'Level 3':['']*2+['C1']+['']*3+['C2']+[''],
                  'Level 4':['']*3+['D1','D2']+['']*2+['D3'],
                  'DataCol1':['']*3+['Value1', 'Value3']+['']*2+['Value5'],
                  'DataCol2':['']*3+['Value2', 'Value4']+['']*2+['Value6']})

输入数据帧:

  Level 1 Level 2 Level 3 Level 4 DataCol1 DataCol2
0      A1                                          
1              B1                                  
2                      C1                          
3                              D1   Value1   Value2
4                              D2   Value3   Value4
5              B2                                  
6                      C2                          
7                              D3   Value5   Value6

将''替换为np.nan:

df_nans = df.replace('', np.nan)

创建一个布尔数组,反转列,cumprod on isna;这对于所有 NaN 都为 1,直到第一个非 NaN 值之后变为 0。并再次反转以重新排序列。

mask_frame = df_nans.loc[:,::-1].isna().cumprod(axis=1).loc[:, ::-1].astype(bool)
print(mask_frame)

布尔数据框:

   Level 1  Level 2  Level 3  Level 4  DataCol1  DataCol2
0    False     True     True     True      True      True
1    False    False     True     True      True      True
2    False    False    False     True      True      True
3    False    False    False    False     False     False
4    False    False    False    False     False     False
5    False    False     True     True      True      True
6    False    False    False     True      True      True
7    False    False    False    False     False     False

转发填充 df_nans 和掩码值:

df_out = df_nans.ffill().mask(mask_frame)

输出:

  Level 1 Level 2 Level 3 Level 4 DataCol1 DataCol2
0      A1     NaN     NaN     NaN      NaN      NaN
1      A1      B1     NaN     NaN      NaN      NaN
2      A1      B1      C1     NaN      NaN      NaN
3      A1      B1      C1      D1   Value1   Value2
4      A1      B1      C1      D2   Value3   Value4
5      A1      B2     NaN     NaN      NaN      NaN
6      A1      B2      C2     NaN      NaN      NaN
7      A1      B2      C2      D3   Value5   Value6

然后,用''填充,

df_out.fillna('')

输出:

      Level 1 Level 2 Level 3 Level 4 DataCol1 DataCol2
0      A1                                          
1      A1      B1                                  
2      A1      B1      C1                          
3      A1      B1      C1      D1   Value1   Value2
4      A1      B1      C1      D2   Value3   Value4
5      A1      B2                                  
6      A1      B2      C2                          
7      A1      B2      C2      D3   Value5   Value6

我有一个简单的实用函数:

def fillright(row, cols=None, text='DUMMY'):
    """ If empty or NaN, fill given columns to the right with 'text'. """ 
    filling = False
    for c in cols:
        if row[c] is np.nan or row[c]=='':
            if filling==True:
                row[c] = text
        else:
            filling = True
    return row

levels = ['Level 1','Level 2','Level 3','Level 4']
df.apply(fillright, cols=levels, axis=1 )

中间结果:

  Level 1 Level 2 Level 3 Level 4 DataCol1 DataCol2
0      A1   DUMMY   DUMMY   DUMMY                  
1              B1   DUMMY   DUMMY                  
2                      C1   DUMMY                  
3                              D1   Value1   Value2
4                              D2   Value3   Value4
5              B2   DUMMY   DUMMY                  
6                      C2   DUMMY                  
7                              D3   Value5   Value6

现在我们进行常规填充,然后去掉虚拟文本:

df[levels] = ( df[levels]
    .replace('', np.nan)
    .fillna( method='ffill' )
    .replace('DUMMY', '')
)

这会产生所需的结果。