一. baseline手册
二. 赛题
比赛(见链接)提供某市场的出清价格以及市场参与主体相关参数信息,选手使用Agent-based model,根据历史市场需求、外部环境以及主体基本信息对主体博弈行为进行刻画,预测未来情景下的市场出清价格。
三. 代码及思路
1.导入包
import numpy as np
import pandas as pd
from pathlib import Path
from sklearn.linear_model import LinearRegression
2.数据预处理
base_path = Path("data")
# 读取市场数据
electricity_price = pd.read_csv(base_path / "electricity price.csv")
# 读取市场主体(各发电机组)数据
unit = pd.read_csv(base_path / "unit.csv")
# electricity_price["clearing price (CNY/MWh)"].isna()找到出清价格为缺失值的行,即要预测的目标
# 通过 `drop(columns="demand")` 从筛选出的行中去掉 `demand` 列。这样做的目的是为了符合最终的提交格式。
sample_submit = electricity_price[electricity_price["clearing price (CNY/MWh)"].isna()].drop(columns="demand")
# 将 `sample_submit` 数据框保存为一个 CSV 文件
sample_submit.to_csv(base_path / "sample_submit.csv", index=False)
# 将day和time列合并成timestamp列,便于提取时间戳特征
# `day`列:该列包含日期信息,格式为 `YYYY-MM-DD`。
# `time`列:该列包含时间信息,格式为 `HH:MM`。
# 注意到这里有一个特殊处理,即将 `24:00:00` 替换为 `00:00`。详解(1)
electricity_price["timestamp"] = pd.to_datetime(
electricity_price["day"] + " " + electricity_price["time"].str.replace("24:00:00", "00:00"))
# 处理24:00:00的情况,即表示第二天的00:00:00。详解(2)
mask = electricity_price['timestamp'].dt.time == pd.Timestamp('00:00:00').time()
# 将 electricity_price 数据框中满足 mask 条件的行的 timestamp 列的值增加一天。详解(3)
electricity_price.loc[mask, 'timestamp'] += pd.Timedelta(days=1)
# 设置列的顺序,同时去除day和time列
electricity_price = electricity_price[["timestamp", "demand", "clearing price (CNY/MWh)"]]
部分代码详解:
(1)pd.to_datetime( electricity_price["day"] + " " + electricity_price["time"].str.replace("24:00:00", "00:00"))
electricity_price["day"] + " " + electricity_price["time"].str.replace("24:00:00", "00:00")
这一部分代码将 day 列和处理过的 time 列拼接成一个字符串,形成完整的日期时间表示。例如,如果 day 是 2023-07-28,time 是 23:59:59,那么拼接后的字符串就是 2023-07-28 23:59:59。如果 time 是 24:00:00,则会被替换为 00:00,形成 2023-07-28 00:00。
pd.to_datetime()
将拼接后的字符串转换为 datetime 对象。pd.to_datetime 是 Pandas 中的一个函数,用于将字符串转换为 datetime 对象,便于后续的时间戳特征提取和时间序列分析。
(2)mask = electricity_price['timestamp'].dt.time == pd.Timestamp('00:00:00').time()
electricity_price['timestamp'].dt.time
这是在提取 electricity_price 数据框中 timestamp 列的时间部分。dt 访问器用于处理 datetime 类型的数据,使我们能够提取时间、日期等组件。
pd.Timestamp('00:00:00').time()
这是创建一个 Timestamp 对象,并提取其时间部分。pd.Timestamp('00:00:00') 创建一个时间戳对象表示时间 "00:00:00",然后 .time() 方法提取其时间部分。
electricity_price['timestamp'].dt.time == pd.Timestamp('00:00:00').time()
检查 timestamp 列的时间部分是否等于 "00:00:00"
(3)electricity_price.loc[mask, 'timestamp'] += pd.Timedelta(days=1)
.loc[] 是 pandas 中用于基于标签访问数据的方法。
mask是一个布尔数组,用于选择或过滤数据。
mask == True: 表示选中或保留那些mask中对应位置为True的数据。 这些位置的数据将被包含在结果中或被操作。
mask == False: 表示排除或忽略那些mask中对应位置为False的数据。 这些位置的数据将不会被包含在结果中或不被操作。
例子:
假设我们有一个DataFrame df:
A B
0 1 5
1 2 6
2 3 7
3 4 8
和一个mask:
[True, False, True, False]
那么df[mask] 或 df.loc[mask] 会选择第0行和第2行,因为这些位置在mask中是True。
df[~mask] 会选择第1行和第3行,因为这些位置在原始mask中是False。
pd.Timedelta(days=1) 创建了一个表示一天时间长度的 Timedelta 对象。
———————————————————————————————————————————
代码运行部分结果:
- 使用ABM估计市场出清价格
ABM(Agent-Based Model,基于主体的模型)是一种用于估计市场出清价格的有效方法。以下是使用ABM估计市场出清价格的基本思路:
(1) 定义主体(Agent):
- 买方主体:定义买方的特征,如预算、需求量、价格敏感度等。
- 卖方主体:定义卖方的特征,如成本、供给量、定价策略等。
(2) 设定交易规则:
- 确定交易机制,如双边谈判、拍卖等。
- 定义主体之间的互动方式。
(3) 初始化市场环境:
- 设定初始价格、供需数量等市场参数。
- 生成符合设定特征的买方和卖方主体群体。
(4) 模拟交易过程:
- 让买卖双方按照设定的规则进行交易。
- 记录每次交易的价格和数量。
(5) 动态调整:
- 根据交易结果,买卖双方可能会调整自己的策略。
- 市场参数也可能随之变化。
(6) 迭代模拟:
- 重复步骤4和5,进行多轮交易模拟。
(7) 数据分析:
- 收集模拟过程中的交易数据。
- 分析价格变化趋势,找出稳定的价格水平。
(8) 确定市场出清价格:
- 根据分析结果,估计最终的市场出清价格。
- 可能需要多次运行模型,取平均值或中位数。
(9) 敏感性分析:
- 改变部分参数,观察对结果的影响。
- 验证模型的稳健性。
(10) 结果解释与应用:
- 解释估计结果的经济含义。
- 将结果应用于实际决策或进一步研究。
使用ABM估计市场出清价格的优势在于,它可以模拟复杂的市场动态,考虑个体行为的异质性,并且能够捕捉到宏观层面难以观察到的微观互动过程。这种方法特别适用于那些难以用传统分析方法建模的复杂市场系统。
然而,ABM也面临一些挑战,如参数设置的复杂性、计算资源的需求,以及结果的不确定性。因此,在使用ABM时,需要谨慎设计模型,并结合其他方法来验证结果的可靠性。
至于市场出清价格,就是需求与供给两条曲线相交的点的市场价格。 例图如下,交点为市场出清价格:
(《经济学原理 微观经济学分册》北京大学出版社 图4-8)
# 按照一度电的耗煤量(近似为边际成本)降序排序
# 把发电效率高的机组排在前面(成本小在前面)
sorted_unit = unit.sort_values("coal consumption (g coal/KWh)")
# 这行代码计算每个电厂的累积容量,并将结果存储在新的列 `cumulative_capacity` 中。
# 累积容量表示从第一个电厂到当前电厂的总容量之和。
sorted_unit['cumulative_capacity'] = sorted_unit['Capacity(MW)'].cumsum()
# 创建一个空列表 `prices`,用于存储每个需求对应的煤耗(报价)。
prices = []
# 找到最后一个满足总需求的机组报价
# 遍历需求
for demand in electricity_price["demand"]:
# 对于每个需求值,找到累积容量大于等于当前需求值的第一个机组,并获取该机组的煤耗值。
price = sorted_unit[sorted_unit['cumulative_capacity'] >= demand]["coal consumption (g coal/KWh)"].iloc[0]
prices.append(price)
print(len(prices))
prices[:5]
思路是,把耗煤量小的机组先考虑并入组合,计算所有机组组合的发电量,根据需求电量找机组组合。 至此,我们已经找到了满足需求所需要的机组。
接下来,我们使用线性回归转换耗煤量为机组报价。
model = LinearRegression()
# 55392为训练集的长度。
# electricity price.csv 中价格给了55392行,下面的行数是要预测的目标。
train_length = 55392
# 将 prices 列表转换为 NumPy 数组,并将其重塑为一个二维数组,其中每个价格值是一个单独的样本。
# reshape(-1, 1) 1表示将数组重塑为有一列的二维数组,-1表示行数自动计算。
prices = np.array(prices).reshape(-1, 1)
# X 是前 55392 个价格数据,y 是对应的前 55392 个电力市场的清算价格。(详解1)
X = prices[:train_length]
y = electricity_price["clearing price (CNY/MWh)"].iloc[:train_length].values.reshape(-1, 1)
# 训练模型
model.fit(X, y)
# 预测
y_pred = model.predict(prices[train_length:])
# 2维矩阵转为1维
y_pred = y_pred.flatten()
前5个数据展示:
2维
1维
部分代码详解:
(1)y = electricity_price["clearing price (CNY/MWh)"].iloc[:train_length].values.reshape(-1, 1)
# 获取电价数据的前 train_length 个数据点
# 并将其转换为一个二维数组,每个数据点作为一个单独的行
# 1. 提取指定列的数据
# electricity_price 是一个 Pandas DataFrame
# "clearing price (CNY/MWh)" 是数据框中的一列
column_data = electricity_price["clearing price (CNY/MWh)"]
# 2. 选择前 train_length 个数据点
# iloc 是 Pandas DataFrame 和 Series 的一个索引器
# iloc[:train_length] 表示选择从第一个数据点到第 train_length 个数据点(不包括 train_length)
selected_data = column_data.iloc[:train_length]
# 3. 将数据转换为 NumPy 数组
# values 属性将 Pandas Series 转换为 NumPy 数组
numpy_array = selected_data.values
# 4. 调整数组形状
# reshape(-1, 1) 将一维数组转换为二维数组
# -1 表示自动计算行数,1 表示每行一个数据点
reshaped_array = numpy_array.reshape(-1, 1)
# 5. 将结果赋值给变量 y
y = reshaped_array
4.保存预测结果
sample_submit["clearing price (CNY/MWh)"] = y_pred
sample_submit.to_csv("submit.csv", index=False)