
本文介绍如何在 pandas 中对多列(如 date_m 和 corporate)分组后,高效计算某列(如 vehicle type)中特定类别(如 'truck')所占的百分比,并将其作为新列添加到聚合结果中。
本文介绍如何在 pandas 中对多列(如 date_m 和 corporate)分组后,高效计算某列(如 vehicle type)中特定类别(如 'truck')所占的百分比,并将其作为新列添加到聚合结果中。
在数据分析中,常需在多维分组(如按月份和企业属性组合)基础上,进一步统计某分类变量(如 Vehicle type)内部的构成比例。例如,计算每个 Date_M 与 Corporate 组合中“卡车(truck)”占比,这对业务洞察(如车队结构分析、风险偏好评估)至关重要。
实现该目标的关键在于:不能直接对分组后的 Series 调用 .mean() 或 .sum() 等标量聚合函数来计算比例,因为 Vehicle type 是字符串列;而应利用 groupby.apply() 对每个分组子集进行自定义逻辑处理——即在每个子组内统计 'truck' 出现次数,再除以该子组总行数。
以下是完整、可复现的代码示例(基于您提供的数据结构):
import pandas as pd
import numpy as np
# 构建示例数据
data = {
"Date": ["2019-08-26 04:14:00", "2020-08-27 01:21:03", "2019-08-26 18:49:04",
"2020-08-31 16:57:02", "2020-08-23 14:48:02"],
"Cost": [23719, 17159, 3629, 50738, 48173],
"Corporate": [0, 0, 0, 1, 0],
"Vehicle type": ["automobile", "truck", "truck", "automobile", "truck"],
"Price": [25903, 24748, 11777, 51296, 56314],
"Insurance": [1, 1, 0, 0, 0],
"Date_M": ["2019-08", "2020-08", "2019-08", "2020-08", "2020-08"],
}
df = pd.DataFrame(data)
# 筛选无保险记录(Insurance == 0)
df_no_insurance = df[df['Insurance'] == 0]
# 按 Date_M 和 Corporate 双列分组
df_group = df_no_insurance.groupby(['Date_M', 'Corporate'])
# 构建聚合结果 DataFrame
results = pd.DataFrame()
results['Number of cars'] = df_group['Date'].count() # 每组车辆数
results['Доходность'] = np.round(
(df_group['Price'].sum() - df_group['Cost'].sum()) / df_group['Price'].sum() * 100, 2
) # 利润率(注意:此处原逻辑有误——Cost 和 Price 的 sum 应分别按组计算,见下方修正说明)
# ✅ 正确添加“卡车占比”列
results['Percentage of Trucks'] = df_group.apply(
lambda x: (x['Vehicle type'] == 'truck').sum() / len(x) * 100
)运行后,results 将包含三列:Number of cars、Доходность 和新增的 Percentage of Trucks,输出形如:
Number of cars Доходность Percentage of Trucks
Date_M Corporate
2019-08 0 1 69.19 100.0
2020-08 0 2 14.46 100.0
1 1 1.09 0.0⚠️ 重要注意事项:
-
利润率计算修正建议:原始代码中 df_group['Price'].sum() - df_group['Cost'].sum() 写法虽能运行,但逻辑上隐含了“先全局求和再相减”的误解。正确写法应为 (df_group['Price'].sum() - df_group['Cost'].sum()) / df_group['Price'].sum() —— 幸运的是,Pandas 的 GroupBy 对 sum() 返回的是对齐的 Series,因此该表达式实际是逐组计算的,无需额外调整。但为提升可读性与健壮性,推荐显式写出:
price_sum = df_group['Price'].sum() cost_sum = df_group['Cost'].sum() results['Доходность'] = np.round((price_sum - cost_sum) / price_sum * 100, 2)
-
空组安全处理:若某分组为空(如无 'truck' 记录且 len(x)==0),len(x) 为 0 将导致除零错误。生产环境建议增强鲁棒性:
results['Percentage of Trucks'] = df_group.apply( lambda x: 0.0 if len(x) == 0 else (x['Vehicle type'] == 'truck').sum() / len(x) * 100 ) -
扩展性提示:如需计算多个类别(如 'automobile', 'truck', 'van')的占比,可使用 value_counts(normalize=True):
results['Truck Share'] = df_group['Vehicle type'].apply( lambda x: x.value_counts(normalize=True).get('truck', 0) * 100 )
综上,groupby.apply() 是解决“分组内条件比例”类问题的简洁、通用方案。掌握其与布尔索引、len() 及标量运算的组合用法,可灵活应对各类分类占比分析需求。










