你遇到的错误:
```
TypeError: Passing PeriodDtype data is invalid. Use `data.to_timestamp()` instead
```
说明:你的 `monthly_stats` 的索引虽然是时间格式,但它是 **Period Index(周期索引)**,比如 `'2024-10'` 是一个 `Period` 类型(频率为月),而不是 `Timestamp`。
而 `pd.to_datetime()` 不能直接处理 `Period` 类型的数据,所以报错。
---
### ✅ 错误原因
当你执行:
```python
monthly_stats_reset = monthly_stats.reset_index()
monthly_stats_reset['date'] = pd.to_datetime(monthly_stats_reset['date'])
```
这里的 `monthly_stats_reset['date']` 是 `Period` 类型(如 `<Period: 2024-10, freq=M>`),不能被 `pd.to_datetime()` 正确解析。
---
## ✅ 正确解决方案
你应该使用 `.to_timestamp()` 方法将 `Period` 转换为 `Timestamp`,这是专门用于 Period → Datetime 的方法。
---
### ✅ 修改后的完整修复代码(解决 Period 报错)
```python
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# 设置全局字体为 Times New Roman
plt.rcParams['font.family'] = 'Times New Roman'
# -----------------------------
# 1. 处理 Period Index 并转换为 Timestamp
# -----------------------------
# 假设 monthly_stats 的索引是 PeriodIndex (e.g., '2024-10', freq='M')
monthly_stats_reset = monthly_stats.reset_index()
# 直接使用 to_timestamp() 将 Period 转为 Timestamp
monthly_stats_reset['date'] = monthly_stats_reset['date'].dt.to_timestamp()
# 提取 Year 和 Month
monthly_stats_reset['Year'] = monthly_stats_reset['date'].dt.year
monthly_stats_reset['Month'] = monthly_stats_reset['date'].dt.month
# 创建 YearMonth 字符串用于显示和排序
monthly_stats_reset['YearMonth'] = monthly_stats_reset['Year'].astype(str) + '-' + \
monthly_stats_reset['Month'].astype(str).str.zfill(2)
# 按日期排序(虽然应该已经是有序的)
monthly_stats_reset = monthly_stats_reset.sort_values(by='date').reset_index(drop=True)
# -----------------------------
# 2. 绘图设置
# -----------------------------
plt.figure(figsize=(16, 7))
ind = np.arange(len(monthly_stats_reset))
width = 0.35
# 绘制三组柱状图
accuracy_bar = plt.bar(ind - width, monthly_stats_reset['Level Accuracy Rate'], width,
color='green', label='Level Accuracy Rate')
underestimate_bar = plt.bar(ind, monthly_stats_reset['Level Underestimate Rate'], width,
color='red', label='Level Underestimate Rate')
overestimate_bar = plt.bar(ind + width, monthly_stats_reset['Level Overestimate Rate'], width,
color='blue', label='Level Overestimate Rate')
# 添加百分比标签(仅对非零值)
for bars in [accuracy_bar, underestimate_bar, overestimate_bar]:
for bar in bars:
height = bar.get_height()
if height > 0:
plt.text(bar.get_x() + bar.get_width() / 2.0, height + 0.005,
f'{height:.2%}', ha='center', va='bottom', fontsize=9)
# 折线图:准确率趋势
plt.plot(ind - width, monthly_stats_reset['Level Accuracy Rate'],
marker='o', color='green', linewidth=2, markersize=5,
label='Accuracy Trend')
# 图表设置
plt.title('AQI Level Forecast Accuracy Evaluation', fontsize=16, fontweight='bold')
plt.xlabel('Month', fontsize=12)
plt.ylabel('Rate', fontsize=12)
plt.xticks(ind, monthly_stats_reset['YearMonth'], rotation=45, ha='right')
# 图例放在右侧外部
plt.legend(bbox_to_anchor=(1.02, 1), loc='upper left', fontsize=10, frameon=True)
# y轴范围
max_rate = monthly_stats_reset[['Level Accuracy Rate', 'Level Overestimate Rate', 'Level Underestimate Rate']].max().max()
plt.ylim(0, max_rate * 1.15)
# 自动调整布局,避免裁剪
plt.tight_layout(rect=[0, 0, 0.85, 1])
# 显示图形
plt.show()
```
---
## 🔍 关键修改点总结
| 问题 | 解决方式 |
|------|---------|
| `PeriodDtype` 不可转 `datetime` | 使用 `.dt.to_timestamp()` 而非 `pd.to_datetime()` |
| 索引类型识别错误 | 明确知道 `monthly_stats.index` 是 `PeriodIndex` |
| 时间排序 | 使用 `.sort_values(by='date')` 确保顺序正确 |
---
### 🧪 如何判断你的索引是不是 Period?
你可以运行以下代码检查:
```python
print(type(monthly_stats.index))
print(monthly_stats.index.dtype)
```
输出可能是:
```
<class 'pandas.core.indexes.period.PeriodIndex'>
period[M]
```
→ 这就确认了它是按月的 `PeriodIndex`,必须用 `to_timestamp()` 转换!
---
### 💡 补充建议:如果你希望保留字符串形式的 '2024-10',也可以这样做
```python
# 更简单的方式:直接用原索引生成 YearMonth
monthly_stats_reset['YearMonth'] = monthly_stats_reset['date'].astype(str) # 直接转成 '2024-10'
```
因为 `Period` 转 `str` 后就是 `'YYYY-MM'` 格式,无需额外处理。
---