如何正确使用Pandas的reindex处理Datetime型多级索引?
问题:补全用户周度事件统计的缺失行并正确使用reindex
我有一个按用户每日统计事件次数的DataFrame,原始事件日志聚合后,用户单日事件数为0的行已缺失。需要补全这些缺失行,并按周分组,确保每个用户每周都有一条记录(事件数为0时也要保留)。
输入示例
import numpy as np import pandas as pd np.random.seed(42) df = pd.DataFrame({ "person_id": np.arange(3).repeat(5), "date": pd.date_range("2022-01-01", "2022-01-15", freq="d"), "event_count": np.random.randint(1, 7, 15), }) # 每周结束日期 # 注:2022-01-23不在原始数据中,但需要出现在结果里 desired_index = pd.to_datetime(["2022-01-02", "2022-01-09", "2022-01-16", "2022-01-23"])
原始DataFrame内容:
| person_id | date | event_count | |
|---|---|---|---|
| 0 | 0 | 2022-01-01 00:00:00 | 4 |
| 1 | 0 | 2022-01-02 00:00:00 | 5 |
| 2 | 0 | 2022-01-03 00:00:00 | 3 |
| 3 | 0 | 2022-01-04 00:00:00 | 5 |
| 4 | 0 | 2022-01-05 00:00:00 | 5 |
| 5 | 1 | 2022-01-06 00:00:00 | 2 |
| 6 | 1 | 2022-01-07 00:00:00 | 3 |
| 7 | 1 | 2022-01-08 00:00:00 | 3 |
| 8 | 1 | 2022-01-09 00:00:00 | 3 |
| 9 | 1 | 2022-01-10 00:00:00 | 5 |
| 10 | 2 | 2022-01-11 00:00:00 | 4 |
| 11 | 2 | 2022-01-12 00:00:00 | 3 |
| 12 | 2 | 2022-01-13 00:00:00 | 6 |
| 13 | 2 | 2022-01-14 00:00:00 | 5 |
| 14 | 2 | 2022-01-15 00:00:00 | 2 |
期望结果
| person_id | level_1 | event_count | |
|---|---|---|---|
| 0 | 0 | 2022-01-02 00:00:00 | 9 |
| 1 | 0 | 2022-01-09 00:00:00 | 13 |
| 2 | 0 | 2022-01-16 00:00:00 | 0 |
| 3 | 0 | 2022-01-23 00:00:00 | 0 |
| 4 | 1 | 2022-01-02 00:00:00 | 0 |
| 5 | 1 | 2022-01-09 00:00:00 | 11 |
| 6 | 1 | 2022-01-16 00:00:00 | 5 |
| 7 | 1 | 2022-01-23 00:00:00 | 0 |
| 8 | 2 | 2022-01-02 00:00:00 | 0 |
| 9 | 2 | 2022-01-09 00:00:00 | 0 |
| 10 | 2 | 2022-01-16 00:00:00 | 20 |
| 11 | 2 | 2022-01-23 00:00:00 | 0 |
当前可行但不够高效的实现
我可以通过groupby+apply的方式实现需求:
( df .groupby(["person_id", pd.Grouper(key="date", freq="w")]).sum() .groupby("person_id").apply( lambda df: ( df .reset_index(drop=True, level=0) .reindex(desired_index, fill_value=0)) ) .reset_index() )
但根据reindex文档,应该可以直接使用level=1参数,无需再次groupby,但这样操作后得到的是内连接结果,缺失了大量需要补0的行:
result = ( df .groupby(["person_id", pd.Grouper(key="date", freq="w")]).sum() .reindex(desired_index, level=1) .reset_index() )
得到的结果:
| person_id | date | event_count | |
|---|---|---|---|
| 0 | 0 | 2022-01-02 00:00:00 | 9 |
| 1 | 0 | 2022-01-09 00:00:00 | 13 |
| 2 | 1 | 2022-01-09 00:00:00 | 11 |
| 3 | 1 | 2022-01-16 00:00:00 | 5 |
| 4 | 2 | 2022-01-16 00:00:00 | 20 |
请问这是为什么?我该如何正确使用df.reindex?
注:曾找到类似问题,但其中df.unstack的方案不适用,因为期望索引与当前索引的层级并非完全重叠。
解答
为什么直接用reindex(desired_index, level=1)得到内连接?
当对多索引DataFrame使用reindex并指定level参数时,pandas只会保留原始多索引中与目标索引在指定层级有交集的组合,不会主动为每个上层索引(这里是person_id)生成所有目标层级的新组合。简单来说,它不会遍历所有用户去补全所有期望的周日期,只会保留原本就存在的用户-周组合,再补充目标索引中存在但原始数据没有的周日期对应的行,最终结果是原始索引和目标索引的内连接,而非全量笛卡尔积。
正确使用reindex的方法
要实现全量的用户-周组合补全,需要先构建完整的多索引(所有person_id × 所有desired_index),再用这个完整索引去reindex分组后的DataFrame:
# 先按用户和周分组,计算周度事件总数 weekly_df = df.groupby(["person_id", pd.Grouper(key="date", freq="w")]).sum() # 构建包含所有用户和所有期望周日期的完整多索引 full_index = pd.MultiIndex.from_product( [weekly_df.index.get_level_values("person_id").unique(), desired_index], names=["person_id", "date"] ) # 用完整索引重新对齐数据,缺失值填充为0 result = weekly_df.reindex(full_index, fill_value=0).reset_index()
这种方式无需嵌套groupby+apply,直接通过构建全量索引实现补全,效率更高,且能得到完全符合需求的结果。
内容的提问来源于stack exchange,提问作者FirefoxMetzger
相关产品推荐
相关产品推荐

