
本教程详细介绍了如何在pandas数据框中,针对特定分组(如g1和g2)内的变量(如tpe列中的'ts'与'td')计算比率,并将结果作为新行添加回原数据框。文章通过一个实际案例,展示了如何利用`set_index`、`unstack`和向量化操作,以高效且优雅的方式处理数据重塑、比率计算以及缺失值(nan)的填充,避免了传统`groupby().apply()`方法可能存在的性能瓶颈和复杂性。
数据框中分组比率计算的挑战与解决方案
在数据分析中,我们经常需要根据数据框中的某些列进行分组,然后计算组内特定变量之间的比率。例如,在一个包含交易类型(TPE)和数量(QC)的数据框中,我们可能需要计算每组(G1, G2)中'ts'类型数量与'td'类型数量的比率(ts/td)。此外,还需要将计算出的比率作为新行添加到原始数据框中,并且要妥善处理那些不包含完整'ts'和'td'值的组。
初始数据结构
假设我们有以下Pandas DataFrame df_in:
import pandas as pd
import numpy as np
data = {
'G1': ['A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'D'],
'G2': ['S1', 'S1', 'S2', 'S2', 'S1', 'S1', 'S2', 'S2', 'S1', 'S2'],
'TPE': ['td', 'ts', 'td', 'ts', 'td', 'ts', 'td', 'ts', 'td', 'ts'],
'QC': [2, 4, 6, 3, 20, 40, 60, 30, 90, 7]
}
df_in = pd.DataFrame(data)
# 模拟原始数据中可能存在的缺失类型
df_in.loc[8, 'TPE'] = 'td' # C S1 只有 td
df_in.loc[9, 'TPE'] = 'ts' # D S2 只有 ts
df_in = df_in.drop(index=[8,9]).append(pd.DataFrame([['C', 'S1', 'td', 90], ['D', 'S2', 'ts', 7]], columns=df_in.columns), ignore_index=True)
print("原始数据框 df_in:")
print(df_in)输出 df_in 如下:
G1 G2 TPE QC 0 A S1 td 2 1 A S1 ts 4 2 A S2 td 6 3 A S2 ts 3 4 B S1 td 20 5 B S1 ts 40 6 B S2 td 60 7 B S2 ts 30 8 C S1 td 90 9 D S2 ts 7
目标输出
我们的目标是生成一个包含原始数据和计算出的比率的新数据框 df_out。对于每个 (G1, G2) 组,如果同时存在 'td' 和 'ts' 类型的 QC 值,则计算 ts_QC / td_QC 作为比率,并将新行的 TPE 列标记为 'ratio'。如果缺少 'td' 或 'ts',则比率应为空(NaN)。
期望的 df_out 结构如下:
G1 G2 TPE QC 0 A S1 td 2.0 1 A S1 ts 4.0 2 A S2 td 6.0 3 A S2 ts 3.0 4 B S1 td 20.0 5 B S1 ts 40.0 6 B S2 td 60.0 7 B S2 ts 30.0 8 C S1 td 90.0 9 D S2 ts 7.0 10 A S1 ratio 2.0 11 A S2 ratio 0.5 12 B S1 ratio 2.0 13 B S2 ratio 0.5 14 C S1 ratio NaN 15 D S2 ratio NaN
传统 groupby().apply() 方法的局限性
一种常见的思路是使用 groupby().apply() 方法,为每个组编写一个自定义函数来计算比率。
def calculate_ratio_apply(group):
td_row = group[group['TPE'] == 'td']
ts_row = group[group['TPE'] == 'ts']
if not td_row.empty and not ts_row.empty:
ratio = ts_row['QC'].values[0] / td_row['QC'].values[0]
return pd.DataFrame({'G1': [group['G1'].iloc[0]],
'G2': [group['G2'].iloc[0]],
'TPE': ['ratio'],
'QC': [ratio]})
# 如果缺少td或ts,返回一个空的DataFrame,这会导致这些组的比率行被省略
return pd.DataFrame()
# 这种方法会忽略没有完整td和ts值的组
# grouped = df_in.groupby(['G1', 'G2']).apply(calculate_ratio_apply).reset_index(drop=True)
# df_out_apply = pd.concat([df_in, grouped], ignore_index=True)
# print("\n使用 apply 方法(可能遗漏空比率):")
# print(df_out_apply)上述 apply 方法虽然能计算比率,但如果某个组没有同时包含 'td' 和 'ts' 值,它会返回一个空的DataFrame,导致这些组的比率行被完全省略,而不是填充 NaN。对于大型数据集,apply 方法也可能因为循环迭代而导致性能问题。
优化方案:利用 unstack 和向量化操作
更高效和优雅的解决方案是利用 Pandas 的数据重塑功能,将 'TPE' 列中的 'td' 和 'ts' 值转化为独立的列,然后进行向量化计算。
# 1. 重塑数据:将 'TPE' 列中的 'td' 和 'ts' 值转换为独立的列
# - set_index(['G1', 'G2', 'TPE']): 将这三列设为索引
# - unstack()['QC']: 将 TPE 索引层的数据(QC值)unstack(逆透视)成列
# 结果是一个多级索引的 DataFrame,列为 TPE 的不同值(td, ts)
tmp = df_in.set_index(['G1', 'G2', 'TPE']).unstack()['QC']
print("\n中间结果 tmp (重塑后的数据):")
print(tmp)中间结果 tmp 的结构如下,我们可以清晰地看到每个 (G1, G2) 组对应的 'td' 和 'ts' 值,以及缺失值(NaN):
TPE td ts G1 G2 A S1 2.0 4.0 S2 6.0 3.0 B S1 20.0 40.0 S2 60.0 30.0 C S1 90.0 NaN D S2 NaN 7.0
# 2. 计算比率:直接对重塑后的列进行向量化除法
# - tmp['ts'].div(tmp['td']): 计算 'ts' 列与 'td' 列的比率
# - reset_index(name='QC'): 将多级索引重置为列,并将比率结果命名为 'QC'
# - assign(TPE='ratio'): 添加一个新列 'TPE',其值为 'ratio'
ratio_df = tmp['ts'].div(tmp['td']).reset_index(name='QC').assign(TPE='ratio')
print("\n计算出的比率数据框 ratio_df:")
print(ratio_df)计算出的比率数据框 ratio_df 如下:
G1 G2 QC TPE 0 A S1 2.0 ratio 1 A S2 0.5 ratio 2 B S1 2.0 ratio 3 B S2 0.5 ratio 4 C S1 NaN ratio 5 D S2 NaN ratio
# 3. 合并数据:将原始数据框和计算出的比率数据框进行纵向合并
df_out = pd.concat([df_in, ratio_df], ignore_index=True)
print("\n最终输出数据框 df_out:")
print(df_out)最终的 df_out 完美符合我们的要求:
G1 G2 TPE QC 0 A S1 td 2.0 1 A S1 ts 4.0 2 A S2 td 6.0 3 A S2 ts 3.0 4 B S1 td 20.0 5 B S1 ts 40.0 6 B S2 td 60.0 7 B S2 ts 30.0 8 C S1 td 90.0 9 D S2 ts 7.0 10 A S1 ratio 2.0 11 A S2 ratio 0.5 12 B S1 ratio 2.0 13 B S2 ratio 0.5 14 C S1 ratio NaN 15 D S2 ratio NaN
完整代码示例
import pandas as pd
import numpy as np
# 原始数据框
data = {
'G1': ['A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'D'],
'G2': ['S1', 'S1', 'S2', 'S2', 'S1', 'S1', 'S2', 'S2', 'S1', 'S2'],
'TPE': ['td', 'ts', 'td', 'ts', 'td', 'ts', 'td', 'ts', 'td', 'ts'],
'QC': [2, 4, 6, 3, 20, 40, 60, 30, 90, 7]
}
df_in = pd.DataFrame(data)
# 模拟原始数据中可能存在的缺失类型,确保 C S1 只有 td,D S2 只有 ts
df_in = df_in.drop(index=[8,9]).append(pd.DataFrame([['C', 'S1', 'td', 90], ['D', 'S2', 'ts', 7]], columns=df_in.columns), ignore_index=True)
# 1. 重塑数据:将 'TPE' 列中的 'td' 和 'ts' 值转换为独立的列
# 通过 set_index 和 unstack,将数据从长格式转换为宽格式,便于计算
tmp = df_in.set_index(['G1', 'G2', 'TPE']).unstack()['QC']
# 2. 计算比率并格式化结果
# - tmp['ts'].div(tmp['td']): 执行向量化除法,自动处理缺失值(NaN)
# - reset_index(name='QC'): 将多级索引重置为常规列,并将比率结果列命名为 'QC'
# - assign(TPE='ratio'): 添加一个新列 'TPE',其值为 'ratio'
ratio_df = tmp['ts'].div(tmp['td']).reset_index(name='QC').assign(TPE='ratio')
# 3. 合并数据:将原始数据框和计算出的比率数据框进行纵向合并
df_out = pd.concat([df_in, ratio_df], ignore_index=True)
print("最终输出数据框 df_out:")
print(df_out)注意事项与总结
- 效率提升: 相比于 groupby().apply(),使用 set_index().unstack() 结合向量化操作(如 .div())在处理大型数据集时通常更高效,因为它利用了 Pandas 底层的优化 C 语言实现。
- 缺失值处理: unstack() 操作会自动将缺失的数据填充为 NaN。在进行除法运算时,任何包含 NaN 的运算结果仍为 NaN,这自然地满足了对“空比率”的需求。
- 可读性与简洁性: 这种方法代码更简洁,逻辑更清晰,避免了自定义函数中复杂的条件判断。
- 通用性: 这种数据重塑和向量化计算的模式适用于各种需要计算分组内变量之间比率或差值等场景。
通过本教程,您应该能够高效且优雅地在 Pandas 数据框中处理分组比率计算问题,即使面对复杂的数据结构和缺失值情况也能游刃有余。








