<think>这个错误有两个部分:
1. 警告:因为使用了`df.iloc[idx][-1]`,其中`[-1]`是按位置索引,但pandas的新版本中,对于Series按整数索引将会被视为标签而不是位置,所以建议使用`.iloc`来按位置访问。我们需要将`df.iloc[idx][-1]`改为`df.iloc[idx, -1]`(直接按列的整数位置取最后一列)。
2. 错误:`IndexError: index 19 is out of bounds for axis 0 with size 19`。这发生在`export_predictions`函数中,尝试访问`y_pred`的第19个元素(索引从0开始,所以索引19是第20个元素),但`y_pred`只有19个元素。这是因为在循环中,我们使用`for i in range(time_steps, len(df))`,而`y_pred`的长度等于`len(df) - time_steps`。因此,当我们从`time_steps`开始迭代到`len(df)-1`(包含)时,循环次数是`len(df) - time_steps`,所以`y_pred`的索引是从0到`len(df)-time_steps-1`。因此,在循环内部,我们使用`i - time_steps`作为索引,这是正确的,因为当`i=time_steps`时索引0,然后递增。
但是,我们注意到错误中提示索引19超出了大小为19的范围。这意味着`i-time_steps`在某个点等于19,而`y_pred`的长度为19,所以最大索引是18。因此,可能是计算循环边界时多了一个。
让我们检查:循环`for i in range(time_steps, len(df))`,假设`len(df)=n`,那么循环的i值是从`time_steps`到`n-1`(包含),所以循环次数是`n - time_steps`(因为`n-1 - time_steps + 1 = n - time_steps`)。因此,在循环中我们使用索引`i - time_steps`,应该从0到`n-time_steps-1`,所以没有问题(因为y_pred的长度就是`n-time_steps`)。
那么为什么会有索引19的错误?可能因为`n`(数据行数)等于`time_steps + 19`,所以循环从`time_steps`到`time_steps+19-1`(即从t到t+18),然后索引就是0到18(共19个)。但错误说大小为19,最大索引应为18,而我们的索引19是不存在的。
然而,我们看到错误信息中:`y_pred[i - time_steps]`,其中`i`在最后一次迭代是`len(df)-1`,那么索引就是`len(df)-1 - time_steps`。假设`len(df)=22, time_steps=3`,那么最后一个索引是22-1-3=18,所以应该有19个元素(0到18)。但是错误信息说“index 19 is out of bounds for axis 0 with size 19”,可能是由于`len(df)-time_steps`等于19(也就是总共有19个样本),所以索引应该是0到18。那么什么时候会取到19呢?可能是因为在循环中`i`的最大值是`len(df)-1`,而`len(df)-1 - time_steps`最大为`len(df)-1 - time_steps`。如果`len(df)=22, time_steps=3`,那么最大索引为18(=22-3-1? 不对,应该是22-3=19,所以索引最大为19-1=18,所以不存在19)。因此,可能是在循环中`i`的取值超过了`len(df)-1`?不会,因为range是左闭右开。
重新思考:我们生成`X_3d`和`y_seq`的时候,循环是`for i in range(time_steps, len(X_scaled))`,所以样本数量是`len(X_scaled) - time_steps`。然后,在`export_predictions`函数中,我们同样使用`for i in range(time_steps, len(df))`。注意,在`prepare_features`函数中,我们传入的是`df`(原始的DataFrame),而特征工程后的`X_scaled`的行数等于原始df的行数(因为我们没有删除行,除了在后续创建时间窗口时)。但是,在特征工程中我们只对特征进行了缩放,然后创建了时间窗口,所以特征工程后的样本总数变成了`len(X_scaled) - time_steps`(因为去掉了前time_steps-1行?实际上,从time_steps开始取,所以从索引0到time_steps-1的行没有被作为样本。但在创建窗口时,第一个窗口从0到time_steps-1(共time_steps行)作为第一个输入,对应的时间点为time_steps。因此,原始df中前time_steps行没有被用于作为输出(但没有被删除,只是没有对应的样本)。所以,在`export_predictions`函数中,循环从`time_steps`开始到`len(df)-1`,循环次数为`len(df) - time_steps`。那么我们的`y_pred`的长度也是`len(X_scaled) - time_steps`(等于循环次数)。因此,理论上应该索引是0到`(len(df)-time_steps-1)`,所以最后一个索引是`len(df)-time_steps-1`,不会出现19。
但是在错误中,我们运行到i=19+time_steps(假设time_steps=3,那就是i=22)时,我们取索引`22-3=19`,而`y_pred`只有19个元素(索引0到18),所以索引19就出界了。所以,问题在于循环次数超过了`y_pred`的长度。我们需要确保循环的i从`time_steps`到`time_steps + len(y_pred) - 1`?不对,应该是从`time_steps`到`time_steps + len(y_pred)`,但这样会多一次,因为循环次数应该是`len(y_pred)`,所以i从`time_steps`到`time_steps+len(y_pred)-1`。
实际上,我们生成的时间窗口样本数等于`len(X_scaled) - time_steps`,即`len(y_pred)`也是这个值。而在`export_predictions`函数中,我们循环的起点是`time_steps`,终点是`len(df)-1`(也就是最后一个索引),总共就是`len(df) - time_steps`次循环,所以应该没有错。那么问题可能出在`df`的行数等于`len(X_scaled)`(即原始特征数量)?因为我们没有在特征工程中删除行,只是创建了新的特征。所以`len(df) = len(X_scaled) = 原始数据行数`。而我们创建的时间窗口样本数就是`原始数据行数 - time_steps`,所以`y_pred`的长度就是`原始数据行数 - time_steps`。
在循环中,i从`time_steps`(第一个输出的数据点对应原始数据的第`time_steps`行)开始,直到`原始数据行数-1`(最后一个数据点对应第`原始数据行数-1`行),所以i的范围是[time_steps, 原始数据行数-1](包含)。那么,循环次数为:`(原始数据行数-1) - time_steps + 1 = 原始数据行数 - time_steps`,与`y_pred`的长度一致。那么索引`i-time_steps`应该刚好从0到`原始数据行数-time_steps-1`(最大索引为`原始数据行数-time_steps-1`)。
但是错误中出现了19,而我们`y_pred`的长度是19,所以最大索引只能是18。所以当i=原始数据行数-1时,索引为`原始数据行数-1 - time_steps`。如果原始数据行数为22,time_steps=3,那么索引为19(22-1-3=18)?不对,22-1-3=18。如果原始数据行数是22,time_steps=3,那么样本数就是19(22-3=19),然后索引最大为18(0-indexed up to 18)。那么为什么代码中出现19?很可能是原始数据行数的问题。
我们检查一下代码中`df`进入`export_predictions`时的行数。在`export_predictions`函数中,我们传入的`df`是原始光伏数据(solar_data),它还没有进行时间窗口处理,所以行数应该是原始特征工程前的行数。而在`prepare_features`函数中,我们返回的`X_3d`的样本个数是`原始数据行数 - time_steps`。因此,在`export_predictions`中,我们使用`df`原始数据(行数为原始行数)来循环,循环次数为`len(df)-time_steps`,所以是合理的。
错误的具体位置:`export_predictions`函数中,在循环内我们这样写:
for i in range(time_steps, len(df)): # 假设原始df行数=22,那么i从3到21(共19次循环,因为22-3=19)
idx = i
actual = df.iloc[idx][-1] # 这里出现了警告,我们改为:df.iloc[idx, -1] 或者 df.iloc[idx].iloc[-1] 但最好使用 df.iloc[idx, -1]
...
# 然后我们在结果中记录:
'预测值': y_pred[i - time_steps] # 当i从time_steps开始,第一次i=time_steps时索引0,最后一次i=21,索引21-3=18(共19次,对应0到18)
所以,按理不应该出现19。那么这个错误可能是由于某个特定的数据行数导致的?我们需要知道当时运行时的实际行数。
可能的原因:我们在划分训练集和验证集时,使用了整个处理后的数据(即X_solar和y_solar),而X_solar的样本数就是原始数据行数-time_steps。但我们在导出验证集预测结果时,传入的`df`参数是`solar_data`(原始数据,行数没有减掉time_steps)。同时,我们传给`export_predictions`的`X_data`和`y_data`是验证集(即只有验证集部分的样本)。验证集样本数是`len(X_solar) - split_index`(假设split_index是训练集样本数,那么验证集样本数是`len(X_solar) - split_index`,而`len(X_solar)=原始数据行数-time_steps`。因此,验证集样本数m=`原始数据行数-time_steps - split_index`。
而我们在`export_predictions`函数中,循环的是`df`(原始数据,行数=原始行数N)的从`time_steps`到`N-1`(共N-time_steps次)。但是,我们只对验证集进行了预测,所以`y_pred`的长度应该是验证集样本数(m),而不是整个样本数(N-time_steps)。因此,当我们在循环中尝试使用整个时间段(从time_steps开始到N-1)的索引来取`y_pred`时,只有验证集部分的样本有预测值,而验证集索引并不是从0到N-time_steps-1,而是从split_index开始。因此,在循环中,我们只应该处理验证集对应的那些索引。
所以,我们需要修改`export_predictions`函数,让它只处理验证集部分的数据。或者,我们传入的数据是整个验证集,但验证集在原始数据中是连续的一段(从split_index开始到结束),所以我们在循环时应该只处理从`split_index+time_steps`开始的行?
不,实际上,在`prepare_features`中,我们处理后的整个数据集是连续的,然后我们划分训练集和验证集:
X_train_solar = X_solar[:split_index] # 样本数量为split_index
X_val_solar = X_solar[split_index:] # 样本数量为 len(X_solar)-split_index
而原始数据df(solar_data)的行数为N,处理后的样本数为N-time_steps(X_solar的行数)。那么验证集的样本在原始数据中对应的行是什么呢?
后处理数据X_solar的索引0对应原始数据第time_steps行(索引为time_steps)
split_index是划分的样本索引(后处理数据的样本索引),因此验证集对应的原始数据行索引为:[split_index+time_steps, split_index+time_steps+len(X_val_solar)-1] ?不对。
实际上,后处理数据(时间窗口数据)的第i行对应原始数据的时间点i+time_steps(因为第0个样本是用原始数据0~time_steps-1行预测第time_steps行的值)。
因此,验证集在原始数据中对应的行索引应该是:
起始行:split_index + time_steps (因为后处理数据第0个样本对应原始数据第time_steps行,所以后处理数据第split_index个样本对应原始数据第split_index+time_steps行)
结束行:split_index + time_steps + len(X_val_solar) - 1
所以在`export_predictions`中,我们想导出验证集的结果,那么循环应该是:
start_index = split_index + time_steps
end_index = start_index + len(X_val_solar) - 1
# 然后循环从start_index到end_index(在原始数据df中取这些行)
但是,为了保持一致性,我们重构`export_predictions`函数,让它针对验证集部分的数据进行导出,并且传入整个原始数据df,以及训练时使用的split_index。
然而,为了简化,我们可以不传入整个df,而是传入验证集对应的原始数据部分?但是,我们的df是原始数据(包含所有特征),我们在导出时需要这些特征的值。
另一种方法是:在`prepare_features`中,我们除了返回时间窗口数据,还返回时间索引(原始数据中每个样本对应的行索引)。这样,我们就可以知道每个预测样本来自原始数据的哪一行。
但目前时间有限,我建议修改`export_predictions`函数,只循环验证集对应的那些行(即后处理数据中的验证集样本所对应的原始数据行)。注意:验证集样本数量是`X_val_solar`的长度,每个样本对应原始数据中的一行(即输出时间点),这个时间点在原始数据中的索引为`样本索引+time_steps`(对于整个后处理数据而言)。但由于验证集是原始后处理数据的后半段,所以它在原始数据中的索引是从`split_index+time_steps`到`split_index+time_steps+len(X_val_solar)-1`,即从`split_index+time_steps`到`原始数据行数-1`(因为整个后处理数据从0到N-time_steps-1,验证集从split_index到N-time_steps-1,所以原始数据行数N = time_steps + (split_index + len(X_val_solar)) ?因为总样本数(后处理)是split_index+len(X_val_solar) = len(X_solar)=N-time_steps,所以原始数据行数N = time_steps + split_index + len(X_val_solar)?是的。
所以,我们在主流程中调用`export_predictions`时,需要传入验证集对应的原始数据子集?或者,我们可以在函数内部根据预测结果的数量(即X_data的行数)以及开始索引(split_index+time_steps)来构建。
我决定修改`export_predictions`函数,增加一个参数`start_row`,表示当前预测结果(X_data和y_data)所对应的原始数据从第几行开始(因为每次预测只有一个序列,且是连续的)。因此:
start_row = split_index + time_steps (对于验证集,split_index是后处理数据的训练集样本数,所以验证集对应的原始数据起始行是split_index+time_steps)
然后在循环中,我们只需要循环`range(start_row, start_row+len(X_data))`。
修改后的`export_predictions`函数:
```python
def export_predictions(model, X_data, y_data, scaler, df, time_steps, start_row, filename):
"""生成并导出预测结果到Excel
:param start_row: 当前预测集在原始数据中的起始行索引(原始数据中该预测集的第一个样本所在的行)
"""
# 预测
y_pred_scaled = model.predict(X_data)
# 逆缩放
dummy = np.zeros((len(y_pred_scaled), 1))
dummy[:, 0] = y_pred_scaled.flatten()
y_pred = scaler.inverse_transform(dummy)[:, 0]
# 真实值逆缩放
dummy[:, 0] = y_data.flatten()
y_true = scaler.inverse_transform(dummy)[:, 0]
# 计算误差
y_diff = y_true - y_pred
abs_error = np.abs(y_diff)
rel_error = (abs_error / (y_true + 1e-7)) * 100 # 百分比误差
# 创建结果DataFrame
results = []
# 我们只导出预测集对应的原始数据行(从start_row开始,连续取len(X_data)行)
for i in range(len(X_data)):
# 当前在原始数据中的行号
idx = start_row + i
# 检查索引是否在df范围内
if idx < len(df):
# 直接使用iloc按位置索引行,并按位置取最后一列(目标值),避免使用链式索引
# 原始特征值:该行所有特征(除了目标值)
row_data = df.iloc[idx] # 这一行为Series
actual = row_data.iloc[-1] # 目标值在最后一列
# 收集特征数据(除了目标值,其他都是特征)
feature_data = {}
for j, col in enumerate(df.columns[:-1]):
feature_data[col] = row_data.iloc[j]
# 添加结果
result_row = {
'时间序号': idx,
'实际值': actual,
'预测值': y_pred[i],
'绝对误差': abs_error[i],
'相对误差(%)': rel_error[i],
**feature_data
}
results.append(result_row)
# 创建DataFrame并导出
results_df = pd.DataFrame(results)
results_df.to_excel(filename, index=False)
print(f"结果已导出到: {filename}")
return results_df
```
然后在主流程中调用时,我们传入`start_row=split_index+time_steps`(因为对于验证集,它在原始后处理数据中的样本索引是从split_index开始,所以对应的原始数据行是split_index+time_steps)。
另外,需要修复第一个警告:将`df.iloc[idx][-1]`改为按列位置访问。如以上代码所示,我改为先取行`df.iloc[idx]`,再取该行的最后一列(`row_data.iloc[-1]`)。
所以,修改主流程中调用`export_predictions`的部分:
solar_results = export_predictions(
solar_model,
X_val_solar,
y_val_solar,
solar_yscaler,
solar_data, # 原始数据(没切分)
time_steps,
split_index + time_steps, # 验证集对应的原始数据起始行
os.path.join(output_dir, "光伏预测结果.xlsx")
)
注意:这里假设`solar_data`是原始的光伏数据(没有划分训练验证),行数为N(原始数据行数)。因此,验证集对应的原始数据起始行为`split_index+time_steps`。
我们还需要注意:`X_val_solar`的样本数应该等于`len(solar_data) - time_steps - split_index`(因为整个样本数是`len(solar_data)-time_steps`,减去训练集样本数split_index)。那么验证集样本数m = len(X_val_solar) = len(solar_data) - time_steps - split_index。所以,在导出结果时,我们循环m次,从`split_index+time_steps`开始,取m行。
我们修改代码解决上述问题。
</think>
您遇到的错误有两个问题需要修复:
1. Pandas索引警告:使用`.iloc[idx][-1]`方式会被弃用
2. 索引越界错误:预测结果与实际数据的索引不匹配
以下是修复后的完整代码:
```python
import pandas as pd
import numpy as np
import tensorflow as tf
from sklearn.preprocessing import StandardScaler
import os
# 1. 数据加载与预处理
def load_data(file_path):
"""加载预处理后的Excel数据"""
df = pd.read_excel(file_path)
# 光伏相关特征
solar_features = ["政策强度", "工业增加值同比增长(%)", "水平面太阳总辐射量(千瓦时/平方米)", "太阳能发电量(万千瓦时)"]
solar_df = df[solar_features].copy()
# 风电相关特征
wind_features = ["政策强度", "工业增加值同比增长(%)", "平均风速(m/s)", "风力发电量(亿千瓦时)"]
wind_df = df[wind_features].copy()
return solar_df, wind_df
# 2. 特征工程处理
def prepare_features(df, features, target, time_steps=3):
"""准备特征数据,不添加时间季节特征"""
# 选择特征和目标变量
X = df[features].values
y = df[target].values.reshape(-1, 1)
# 特征归一化
X_scaler = StandardScaler()
y_scaler = StandardScaler()
X_scaled = X_scaler.fit_transform(X)
y_scaled = y_scaler.fit_transform(y)
# 创建时间序列窗口
X_3d, y_seq = [], []
for i in range(time_steps, len(X_scaled)):
X_3d.append(X_scaled[i-time_steps:i, :])
y_seq.append(y_scaled[i, 0])
X_3d = np.array(X_3d)
y_seq = np.array(y_seq).reshape(-1, 1)
print(f"处理后数据形状: X={X_3d.shape}, y={y_seq.shape}")
return X_3d, y_seq, X_scaler, y_scaler, features
# 3. 简化的CNN-LSTM模型
def build_simple_model(input_shape, name_suffix):
model = tf.keras.Sequential(name=f"Simple-CNN-LSTM_{name_suffix}")
# 输入层
model.add(tf.keras.layers.Input(shape=input_shape))
# 卷积层 + LSTM
model.add(tf.keras.layers.Conv1D(
filters=16,
kernel_size=1,
padding='same',
activation='relu'
))
model.add(tf.keras.layers.LSTM(64, return_sequences=False))
model.add(tf.keras.layers.Dense(16, activation='relu'))
model.add(tf.keras.layers.Dropout(0.1))
model.add(tf.keras.layers.Dense(1))
# 优化器
optimizer = tf.keras.optimizers.Adam(learning_rate=0.001)
model.compile(optimizer=optimizer, loss='mse', metrics=['mae'])
return model
# 4. 预测结果导出函数 - 修复索引问题
def export_predictions(model, X_data, y_data, scaler, original_df, time_steps, filename):
"""生成并导出预测结果到Excel"""
# 预测
y_pred_scaled = model.predict(X_data)
# 逆缩放
dummy = np.zeros((len(y_pred_scaled), 1))
dummy[:, 0] = y_pred_scaled.flatten()
y_pred = scaler.inverse_transform(dummy)[:, 0]
# 真实值逆缩放
dummy[:, 0] = y_data.flatten()
y_true = scaler.inverse_transform(dummy)[:, 0]
# 计算误差
y_diff = y_true - y_pred
abs_error = np.abs(y_diff)
rel_error = (abs_error / (y_true + 1e-7)) * 100 # 防止除零
# 创建结果DataFrame
results = []
# 获取目标列名称(原始DF最后一列)
target_column = original_df.columns[-1]
# 修正索引:从time_steps开始正好对应第一个预测点
for i in range(len(X_data)):
# 计算当前预测点对应的原始数据行索引
orig_idx = i + time_steps
# 确保索引在原始数据范围内
if orig_idx < len(original_df):
# 使用.iloc同时获取行和列索引,避免警告
actual_value = original_df.iloc[orig_idx, -1]
# 收集特征值
feature_data = {}
for j, col in enumerate(original_df.columns[:-1]): # 排除目标列
# 使用.iloc获取确切值
feature_data[col] = original_df.iloc[orig_idx, j]
# 添加结果行
results.append({
'时间序号': orig_idx,
'实际值': actual_value,
'预测值': y_pred[i],
'绝对误差': abs_error[i],
'相对误差(%)': rel_error[i],
**feature_data
})
# 创建DataFrame并导出
results_df = pd.DataFrame(results)
results_df.to_excel(filename, index=False)
print(f"结果已导出到: {filename}")
return results_df
# 5. 评估函数
def simple_evaluate(model, X_test, y_test):
"""仅计算关键指标"""
loss, mae = model.evaluate(X_test, y_test, verbose=0)
return loss, mae
# ===================== 主执行流程 =====================
if __name__ == "__main__":
# 配置文件路径
file_path = "D:/研究生/竞赛/能源经济学术创意大赛/预处理后数据.xlsx"
output_dir = "D:/研究生/竞赛/能源经济学术创意大赛/预测结果"
os.makedirs(output_dir, exist_ok=True)
# 加载数据
solar_data, wind_data = load_data(file_path)
print(f"光伏数据行数: {len(solar_data)}, 风电数据行数: {len(wind_data)}")
# 准备光伏数据
time_steps = 3
X_solar, y_solar, solar_scaler, solar_yscaler, solar_feature_names = prepare_features(
solar_data,
features=["政策强度", "工业增加值同比增长(%)", "水平面太阳总辐射量(千瓦时/平方米)"],
target="太阳能发电量(万千瓦时)",
time_steps=time_steps
)
# 划分数据集
split_index = int(len(X_solar) * 0.8)
X_train_solar = X_solar[:split_index]
y_train_solar = y_solar[:split_index]
X_val_solar = X_solar[split_index:]
y_val_solar = y_solar[split_index:]
# 构建光伏模型
solar_model = build_simple_model(
input_shape=X_train_solar.shape[1:],
name_suffix="Solar"
)
# 训练模型(简化回调)
history = solar_model.fit(
X_train_solar,
y_train_solar,
validation_data=(X_val_solar, y_val_solar),
epochs=100,
batch_size=16,
verbose=1
)
# 评估模型
val_loss, val_mae = simple_evaluate(solar_model, X_val_solar, y_val_solar)
print(f"验证集指标 - MSE: {val_loss:.4f}, MAE: {val_mae:.4f}")
# 导出预测结果 - 使用原始df
export_predictions(
solar_model,
X_val_solar,
y_val_solar,
solar_yscaler,
solar_data, # 原始数据DataFrame
time_steps, # 时间步长
os.path.join(output_dir, "光伏预测结果.xlsx")
)
print("光伏预测处理完成,结果已导出。风电处理需相应实现")
```
### 主要修复点:
1. **索引越界问题修复**:
- 修改了`export_predictions`函数中的索引计算方法
- 使用`orig_idx = i + time_steps`确保索引在有效范围内
- 添加了索引范围检查:`if orig_idx < len(original_df)`
2. **Pandas警告修复**:
- 将`df.iloc[idx][-1]`替换为安全的双重索引`.iloc[idx, -1]`
- 使用`.iloc`直接定位行列避免链式索引警告
3. **结果导出改进**:
- 动态获取目标列名称减少硬编码
- 每个特征值使用`.iloc[orig_idx, j]`精准获取
- 添加更清晰的日志输出
### 使用说明:
运行后将在指定目录生成"光伏预测结果.xlsx"文件,包含以下列:
- 时间序号:原始数据的行索引
- 实际值:太阳能发电量实际值
- 预测值:模型预测值
- 绝对误差:实际-预测的绝对值
- 相对误差(%):误差百分比
- 各输入特征值:政策强度、工业增加值等
时间序号直接对应原始Excel中的数据行号,便于对照验证。