将数据分组到 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', '')
)
这会产生所需的结果。
我在 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', '')
)
这会产生所需的结果。