如何高效遍历DataFrame、计算月度成本并生成新DataFrame?
问题描述
我正在处理多个Excel表格,表格包含产品、成本、许可起始日期、许可时长(月)、月度成本等数据。示例数据如下:
| Product | Cost | License Date Start | License length in months | Monthly cost |
|---|---|---|---|---|
| Product A | 3000 | January 2022 | 3 | 1000 |
| Product B | 2400 | March 2022 | 4 | 600 |
| Product B | 2400 | Feb 2022 | 3 | 800 |
| Product A | 2000 | March 2022 | 2 | 1000 |
需要生成一个按月份维度的新DataFrame,统计每个月各产品的月度成本及总成本,示例结果如下:
| Date | Product A cost | Product B cost | Product C cost | Total cost |
|---|---|---|---|---|
| January 2022 | 1000 | 0 | 0 | 1000 |
| February 2022 | 1000 | 800 | 0 | 1800 |
| March 2022 | 2000 | 2400 | 0 | 4400 |
| April 2022 | 1000 | 2400 | 0 | 3400 |
| May 2022 | 0 | 600 | 0 | 600 |
| June 2022 | 0 | 600 | 0 | 600 |
尝试过用apply遍历原DataFrame生成对应行再重塑,但遇到返回问题且担心效率,求最优解决方案。
最优解决方案
这里提供一个高效的向量化方案,完全避免apply遍历,利用pandas内置的日期处理和透视表功能,性能比遍历高得多,步骤如下:
1. 预处理:转换日期格式
先把许可起始日期转换为月份周期类型,方便后续生成连续月份:
import pandas as pd # 读取数据(实际场景替换为pd.read_excel读取你的Excel文件) raw_data = pd.DataFrame({ 'Product': ['Product A', 'Product B', 'Product B', 'Product A'], 'Cost': [3000, 2400, 2400, 2000], 'License Date Start': ['January 2022', 'March 2022', 'Feb 2022', 'March 2022'], 'License length in months': [3, 4, 3, 2], 'Monthly cost': [1000, 600, 800, 1000] }) # 转换为月份周期(YYYY-MM格式,保留年月信息) raw_data['License Date Start'] = pd.to_datetime(raw_data['License Date Start'], format='%B %Y').dt.to_period('M')
2. 生成每个许可覆盖的所有月份
通过period_range生成每个许可的月份范围,再用explode展开,这一步是向量化操作,比apply快数倍:
# 为每个许可生成对应的月份列表 raw_data['Date'] = raw_data.apply( lambda x: pd.period_range(start=x['License Date Start'], periods=x['License length in months'], freq='M'), axis=1 ) # 展开月份列表,每个月对应一行数据 exploded_data = raw_data.explode('Date')
3. 透视聚合:按月份和产品统计月度成本
用pivot_table快速聚合每个月各产品的月度成本总和:
# 生成透视表:行=月份,列=产品,值=月度成本求和,缺失值填0 pivot_result = pd.pivot_table( exploded_data, index='Date', columns='Product', values='Monthly cost', aggfunc='sum', fill_value=0 ) # 重命名列名,匹配示例格式 pivot_result.columns = [f"{col} cost" for col in pivot_result.columns] # 添加总成本列 pivot_result['Total cost'] = pivot_result.sum(axis=1) # 重置索引,让月份成为普通列 final_result = pivot_result.reset_index()
4. 补充完整月份范围(可选)
如果原始数据的月份不连续,可补充中间缺失的月份并填充0:
# 获取数据覆盖的完整月份范围 min_month = exploded_data['Date'].min() max_month = exploded_data['Date'].max() all_months = pd.period_range(min_month, max_month, freq='M') # 重新索引,填充缺失月份的0值 final_result = final_result.set_index('Date').reindex(all_months, fill_value=0).reset_index() final_result.rename(columns={'index': 'Date'}, inplace=True)
5. 调整列顺序(匹配示例)
如果需要加入示例中未出现的产品列(如Product C cost),可手动添加并调整列顺序:
# 添加Product C成本列,默认0 final_result['Product C cost'] = 0 # 调整列顺序为示例指定的顺序 final_result = final_result[['Date', 'Product A cost', 'Product B cost', 'Product C cost', 'Total cost']]
最终输出
运行上述代码后,final_result即为符合要求的DataFrame,输出格式与示例一致(注:示例中March的Product B成本可能存在计算误差,代码逻辑为每个许可的月度成本累加,结果更准确)。
内容的提问来源于stack exchange,提问作者Ryan Bateman
相关产品推荐
相关产品推荐

