#AI夏令营 #Datawhale #夏令营
在上次的机器学习GBDT之后,我又接触到了一个比较新的概念——时间序列挖掘。对于EDA,我们并不陌生,但是要怎么将数据分析好并进行可视化的展示,才是我们需要考虑的关键。由于本次的EDA我并没有能够想出比教程里更好的点,这里总结一下思路。
一、划分测试集
导入合适的包
import pandas as pd
import seaborn as sns
import matplotlib.pylab as plt
from pathlib import Path
import warnings
warnings.filterwarnings('ignore')
plt.style.use('ggplot')
plt.rcParams['font.sans-serif'] = ['WenQuanYi Micro Hei',"SimHei"]
plt.rcParams['axes.unicode_minus'] = False
base_path=Path("data")
对数据的处理首先需要把测试集合划分清楚
# 创建测试集掩码,标记出所有价格为 NaN 的数据行
test_mask = electricity_price["price"].isna()
# 创建训练集掩码,标记出所有价格不为 NaN 的数据行,其中~代表布尔取反,即True和False互换。 `[True,False]`和 `~[False,True]`一致
train_mask = ~test_mask
# 打印训练集的范围和总长度
# 训练集范围从训练集中最早的日期到最新的日期
print(f"训练集范围:{electricity_price[train_mask].index.min()} --> {electricity_price[train_mask].index.max()}\t总长度{len(electricity_price[train_mask])}")
# 打印测试集的范围和总长度
# 测试集范围从测试集中最早的日期到最新的日期
print(f"测试集范围:{electricity_price[test_mask].index.min()} --> {electricity_price[test_mask].index.max()}\t总长度{len(electricity_price[test_mask])}")
观察到数据中的出清价格在一小时内都是相同的,但负荷会在一小时中变动
我们进一步验证
def check_repeated(data, repeat_count=4):
"""
检查给定数据序列中是否存在元素不断重复的情况。
参数:
data (list): 要检查的序列数据。
repeat_count (int): 每个元素应重复的次数。默认值为4。
返回:
None
"""
# 以步长 repeat_count 遍历 data 的索引
for i in range(0, len(data), repeat_count):
# 从索引 i 开始,取长度为 repeat_count 的子序列
subsequence = data[i:i + repeat_count]
# 如果子序列的独特元素数量不等于 1,则表示不是同一元素重复
if len(set(subsequence)) != 1:
print(f"序列数据不是元素不断重复 {repeat_count} 次")
return # 发现不满足条件的情况后,直接返回
# 如果遍历完所有子序列,未发现不满足条件的情况,则输出满足条件的信息
print(f"序列数据是元素不断重复 {repeat_count} 次")
# 调用函数并传入特定的电价数据
check_repeated(electricity_price[train_mask]["price"])
可视化的图更加直观
# 使用loc方法选择指定日期的数据,绘制价格图表
electricity_price.loc["2023-01-03"].plot(y="price", figsize=(18, 5), marker='o')
# 设置图表的标题
plt.title("2023年1月3日出清价格走势")
二、从不同维度观察数据特征
以下会从五个维度观察数据,验证数据特征并做出可视化展示
1、统计指标分析
统计指标可以从以下两方面分析
- 中心趋势:均值、中位数 、最大值 、最小值 、众数
- 变异程度:标准差 、极差 、四分位数、变异系数 、偏度和峰度 、变化率
2、总负荷和数值分布
总负荷数值上接近正态分布
# 使用 Seaborn 库绘制总负荷数值的分布图
# sns.displot(...) 函数用于绘制数据的分布图
ax = sns.displot(
electricity_price, # 输入数据,包含绘制所需的列
x="demand", # 指定绘图的列,这里是 "demand" 列
aspect=1.5, # 图形的宽高比,1.5 表示宽度是高度的 1.5 倍
height=5, # 图形的高度设置为 5 英寸
kde=True # 启用核密度估计(KDE),用于绘制数据的平滑概率密度曲线
)
# 设置图形的标题
ax.set(title="总负荷数值分布")
3、出清价格数值分布
ax=sns.displot(electricity_price,x="price",aspect=1.5,height=5,kde=True);
ax.set(title="出清价格数值分布")
4、分时统计特征
# 从 DataFrame 的索引中提取时间信息,并添加为新的列
# 假设索引为 DatetimeIndex 类型
# 提取小时信息,并创建一个新列 "hour"
electricity_price["hour"] = electricity_price.index.hour
# 提取月份信息,并创建一个新列 "month"
electricity_price["month"] = electricity_price.index.month
# 提取日期信息,并创建一个新列 "day"
electricity_price["day"] = electricity_price.index.day
# 提取星期几的信息(0 = 周一, 6 = 周日),并创建一个新列 "weekday"
electricity_price["weekday"] = electricity_price.index.weekday
# 提取年份信息,并创建一个新列 "year"
electricity_price["year"] = electricity_price.index.year
绘制图表
# 创建一个包含两个子图的绘图区域,图形大小为 20x9 英寸,并启用约束布局
fig, ax = plt.subplots(
1, # 子图的行数为 1
2, # 子图的列数为 2
figsize=(20, 9), # 设置整个图形的大小为 20x9 英寸
constrained_layout=True # 启用约束布局以自动调整子图位置和大小
)
# 绘制不同年份的分时电价线图
sns.lineplot(
electricity_price.groupby(["hour", "year"])["price"].mean().reset_index(), # 按小时和年份分组,计算每组的平均电价,并重置索引
x="hour", # x 轴数据为小时
y="price", # y 轴数据为平均电价
ax=ax[0], # 将图绘制到第一个子图 (ax[0])
marker="o", # 数据点标记为圆圈
hue="year", # 根据年份不同设置不同的颜色
palette="Set1" # 使用 "Set1" 调色板
)
ax[0].set_title("不同年份的分时电价") # 设置第一个子图的标题
ax[0].set_xticks(range(24)) # 设置 x 轴刻度为 0 到 23(小时范围)
# 绘制不同年份的分时总负荷线图
sns.lineplot(
electricity_price.groupby(["hour", "year"])["demand"].mean().reset_index(), # 按小时和年份分组,计算每组的平均总负荷,并重置索引
x="hour", # x 轴数据为小时
y="demand", # y 轴数据为平均总负荷
ax=ax[1], # 将图绘制到第二个子图 (ax[1])
marker="o", # 数据点标记为圆圈
hue="year", # 根据年份不同设置不同的颜色
palette="Set1" # 使用 "Set1" 调色板
)
ax[1].set_title("不同年份的分时总负荷") # 设置第二个子图的标题
ax[1].set_xticks(range(24)); # 设置 x 轴刻度为 0 到 23(小时范围)
# 创建一个图形,大小为 17x10 英寸
plt.figure(figsize=(17, 10))
# 绘制热图,显示不同月份和时间下的电价
sns.heatmap(
pivot, # 透视表数据
cmap="coolwarm", # 使用 "coolwarm" 调色板,显示热图的颜色
linewidths=0.5, # 设置单元格之间的分隔线宽度为 0.5
annot=True, # 启用单元格值的注释
fmt=".0f", # 注释的格式为整数
annot_kws={"size": 12, # 注释文本的大小设置为 12
"weight": "bold", # 注释文本的字体加粗
"color": "black"} # 注释文本的颜色为黑色
)
# 设置图形的标题
plt.title("不同月份和时间下的电价")
5、负电价
minus_mask = electricity_price["price"] < 0
plt.figure(figsize=(12, 7))
ax = sns.countplot(electricity_price[minus_mask], x="hour")
ax.set(title="负电量频数出现的小时分布")
(
# 选择不满足 minus_mask 条件的数据
electricity_price[minus_mask]
# 按月份和日期分组,计算每个组合的记录数量
.groupby(["month", "day"])["price"]
.size() # 计算每个分组的大小(即每个分组的记录数)
.reset_index() # 重置索引,使 groupby 结果成为 DataFrame,并保留分组字段为列
.sort_values("price", # 按 "price" 列排序
ascending=False) # 降序排序
.head(15) # 选择排序后的前 15 行
)
6、高电价
# 计算电价的上限阈值,使用 3 标准差原则来检测异常值
upper_threshold = (
electricity_price["price"].mean() + # 电价的均值
3 * electricity_price["price"].std() # 加上 3 倍的电价标准差
)
# 创建一个布尔型掩码,用于标识电价高于上限阈值的异常值
high_abnormal_mask = (
electricity_price["price"] > upper_threshold # 判断电价是否大于计算得到的上限阈值
)
# 从电价数据中筛选出高于上限阈值的异常值
(
electricity_price[high_abnormal_mask]
# 按月份和日期分组,并计算每个组合的异常值记录数量
.groupby(["month", "day"])["price"]
.size() # 计算每个分组的大小(即每个分组的记录数)
.reset_index() # 重置索引,将分组字段转换为 DataFrame 的列
.sort_values("price", # 按 "price" 列排序
ascending=False) # 降序排序
.head(15) # 选择排序后的前 15 行
plt.figure(figsize=(20, 8))
ax = sns.lineplot(electricity_price.loc["2022-08-01":"2022-08-08"]["demand"], color="black")
plt.setp(ax.get_xticklabels(), rotation=45, ha='right')
plt.axvspan("2022-08-03", "2022-08-06", color="yellow", alpha=0.2)
plt.title("2022年8月1日 - 2022年8月8日 总需求")
7、双变量分析
electricity_price[["demand","price"]].corr()
# 创建一个图形,大小为 8x10 英寸
plt.figure(figsize=(8, 10))
# 绘制回归图(散点图及拟合线)
sns.regplot(
data=electricity_price.loc["2022"], # 选择2022年的数据
x="demand", # x 轴的变量为 "demand"
y="price", # y 轴的变量为 "price"
scatter_kws={ # 设置散点图的样式
"s": 0.5, # 散点的大小设置为 0.5
"alpha": 0.6, # 散点的透明度设置为 0.6
"color": "black" # 散点的颜色设置为黑色
},
color="red", # 拟合线的颜色设置为红色
lowess=True # 启用局部加权回归(Lowess)以拟合数据
)
三、小结
1.气象状况对出清价格有较大影响
2.节假日对出清价格有较大影响,易于出现负值
3.总负荷与出清价格线性关系很高,但总体呈现分段线性的特征
4.不同月份/小时下的出清价格受市场竞争影响较大
5.碳中和不断发展,火电价格有总体下降的趋势
以上为Datawhale2024夏令营课程笔记,主要以记录为主,笔者通过学习其代码逻辑及其可视化方法,将会思考如何进行进一步更加深入的探索性数据分析。
未完待续。。。。。。。。。。。。。。。。。。。。。。。。。。。。。