Pandas使用shift按月份偏移遇重复索引错误,求解决方案
解决Pandas中日期索引DataFrame添加上月/上年聚合列时的重复索引错误
问题分析
你遇到的ValueError: cannot reindex on an axis with duplicate labels,本质是原DataFrame的索引为每日级别的唯一日期,而shift(periods=1, freq='ME')会把所有日期偏移至对应月份的月末,导致偏移后的Series出现大量重复的月末索引(比如2013-11的所有日期都会被偏移到2013-12-31),无法与原DataFrame的唯一日期索引对齐。
解决方案
核心思路是先提取唯一的月度/年度聚合值,再将上月/上年的值映射回原DataFrame的每一行,避免直接对每日索引做频率偏移。
方法1:用Resample生成映射表(简洁高效)
import pandas as pd # ---------------------- 生成previous_month列 ---------------------- # 提取唯一月度数据(以月末为索引,取每个月最后一条的monthly_sum,同月份值一致) monthly_agg = tdf['monthly_sum'].resample('ME').last() # 生成上月数据,转为年月-值的字典 prev_month_map = monthly_agg.shift(1).reset_index() prev_month_map['year_month'] = prev_month_map['Date Enrolled'].dt.strftime('%Y-%m') prev_month_dict = prev_month_map.set_index('year_month')['monthly_sum'].to_dict() # 将原DataFrame日期转为年月字符串,映射上月值 tdf['previous_month'] = tdf.index.strftime('%Y-%m').map(prev_month_dict) # ---------------------- 生成previous_year列 ---------------------- # 提取唯一年度数据(以年末为索引) yearly_agg = tdf['monthly_sum'].resample('YE').last() # 生成上年数据,转为年份-值的字典 prev_year_map = yearly_agg.shift(1).reset_index() prev_year_map['year'] = prev_year_map['Date Enrolled'].dt.strftime('%Y') prev_year_dict = prev_year_map.set_index('year')['monthly_sum'].to_dict() # 映射上年值 tdf['previous_year'] = tdf.index.strftime('%Y').map(prev_year_dict)
方法2:用字符串处理年月(直观易理解)
# ---------------------- 生成previous_month列 ---------------------- # 给原DataFrame添加年月标识列 tdf['year_month'] = tdf.index.strftime('%Y-%m') # 提取每个年月的唯一月度总和 monthly_dict = tdf.groupby('year_month')['monthly_sum'].first().to_dict() # 定义函数生成上月的年月字符串 def get_prev_month(ym_str): year, month = map(int, ym_str.split('-')) return f"{year-1}-12" if month == 1 else f"{year}-{month-1:02d}" # 映射上月值 tdf['previous_month'] = tdf['year_month'].apply(get_prev_month).map(monthly_dict) # ---------------------- 生成previous_year列 ---------------------- # 添加年份标识列 tdf['year'] = tdf.index.strftime('%Y') # 提取每个年份的唯一年度总和 yearly_dict = tdf.groupby('year')['monthly_sum'].sum().to_dict() # 生成上年字符串并映射 tdf['previous_year'] = tdf['year'].astype(int).sub(1).astype(str).map(yearly_dict)
关键说明
- 两种方法都绕开了直接对每日索引的频率偏移操作,先聚合出唯一的月度/年度值,再通过年月/年份标识映射回原数据,彻底解决重复索引问题。
- 如果你的
monthly_sum在同一个月内完全一致,用groupby().first()或resample().last()提取唯一值的结果无差异。
内容的提问来源于stack exchange,提问作者Jamie Marshall
相关产品推荐
相关产品推荐

