在数据处理场景中,我们经常会遇到分组后部分组别缺少特定类型组合行的问题,比如按城市和商品品类分组统计销售额时,部分城市的特定品类没有销售记录,就会缺失对应行,影响后续的交叉分析或可视化结果准确性。

问题场景说明
假设我们有如下销售数据,包含城市、品类和销售额三个字段,其中北京缺少零食品类的记录,上海缺少数码品类的记录:
import pandas as pd
# 构造原始数据
data = {
"city": ["北京", "北京", "上海", "上海", "广州"],
"category": ["数码", "服饰", "服饰", "零食", "数码"],
"sales": [1200, 800, 900, 600, 1100]
}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
运行上述代码后,原始数据输出如下:
city category sales 0 北京 数码 1200 1 北京 服饰 800 2 上海 服饰 900 3 上海 零食 600 4 广州 数码 1100
我们的目标是为每个城市补全数码、服饰、零食三个品类的行,缺失的品类销售额填充为0。
核心实现思路
要完成这个需求,核心步骤分为三步:
- 第一步:确定所有需要补全的类型组合,也就是生成城市列表和品类列表的笛卡尔积
- 第二步:将原始数据和笛卡尔积结果进行合并,标记出缺失的行
- 第三步:对合并后的缺失行填充默认值,得到完整的数据集
具体实现方案
方案一:使用pd.MultiIndex.from_product生成全组合
这是最通用的实现方式,适合任意分组维度的情况:
# 1. 提取所有唯一城市和品类
all_cities = df["city"].unique()
all_categories = ["数码", "服饰", "零食"] # 这里可以换成df["category"].unique()获取实际所有品类
# 2. 生成城市和品类的全组合索引
full_index = pd.MultiIndex.from_product(
[all_cities, all_categories],
names=["city", "category"]
)
# 3. 将原始数据设置为多级索引,再按全组合索引重新索引,缺失值自动填充为NaN
result = df.set_index(["city", "category"]).reindex(full_index).reset_index()
# 4. 填充销售额的缺失值为0
result["sales"] = result["sales"].fillna(0).astype(int)
print("补全后的数据:")
print(result)
运行后输出的补全数据如下:
city category sales 0 北京 数码 1200 1 北京 服饰 800 2 北京 零食 0 3 上海 数码 0 4 上海 服饰 900 5 上海 零食 600 6 广州 数码 1100 7 广州 服饰 0 8 广州 零食 0
方案二:使用groupby结合complete逻辑
如果是按单个分组维度补全,也可以先分组再分别处理每个组:
# 定义单个组补全的函数
def complete_group(group, all_categories):
# 获取当前组的城市名称
city = group["city"].iloc[0]
# 生成当前城市的全品类数据
full_group = pd.DataFrame({
"city": city,
"category": all_categories
})
# 合并原始组数据和全品类数据,填充缺失值
merged = full_group.merge(group, on=["city", "category"], how="left")
merged["sales"] = merged["sales"].fillna(0).astype(int)
return merged
# 应用分组补全逻辑
all_categories = ["数码", "服饰", "零食"]
result = df.groupby("city", group_keys=False).apply(
lambda x: complete_group(x, all_categories)
)
print("补全后的数据:")
print(result)
该方案和方案一的输出结果完全一致,适合需要针对每个组做额外自定义处理的场景。
注意事项
- 如果类型组合不是固定的,需要先通过
df["category"].unique()获取所有实际存在的类型,再生成全组合,避免填充不存在的类型 - 填充的默认值可以根据实际需求调整,比如销售额可以填0,占比可以填None,后续再做对应处理
- 如果分组维度超过两个,只需要把
pd.MultiIndex.from_product的参数列表增加对应的维度即可,比如三个维度就传入[all_dim1, all_dim2, all_dim3]
以上方法可以直接复用在大部分分组数据缺失类型组合行的填充场景中,根据实际数据结构调整对应的字段名和默认值即可。