import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.preprocessing import MinMaxScaler
from sklearn.impute import SimpleImputer
import warnings
warnings.filterwarnings('ignore')
# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
class PowerDataProcessor:
"""多源电力数据预处理类:包含数据加载、清洗、特征工程功能"""
def __init__(self, file_paths, time_col_config=None):
"""
初始化函数
:param file_paths: 字典,包含各数据文件路径,格式如{'day_ahead':'电价_日前.xlsx', ...}
:param time_col_config: 字典,各文件的时间列名称配置,默认所有文件时间列为"时间"
"""
self.file_paths = file_paths
self.raw_data = {} # 存储原始数据
self.processed_data = None # 存储预处理后的数据
self.scaler = MinMaxScaler() # 归一化器
# 配置各文件的时间列名称,默认"时间",可自定义
self.time_col_config = time_col_config if time_col_config is not None else {
'day_ahead': '时间',
'real_time': '时间',
'wind': 'DATA_TIME', # 这里需要替换为风能文件实际的时间列名
'solar': 'DATA_TIME',
'load': 'DATA_TIME',
'weather': 'DATA_TIME'
}
def load_data(self):
"""加载所有数据源并存储到raw_data字典"""
print("开始加载数据...")
# 加载日前电价数据
if 'day_ahead' in self.file_paths:
time_col = self.time_col_config['day_ahead']
self.raw_data['day_ahead'] = pd.read_excel(
self.file_paths['day_ahead'],
parse_dates=[time_col],
index_col=time_col
)
print(f"日前电价数据加载完成,数据量:{len(self.raw_data['day_ahead'])}条")
# 加载实时电价数据
if 'real_time' in self.file_paths:
time_col = self.time_col_config['real_time']
self.raw_data['real_time'] = pd.read_excel(
self.file_paths['real_time'],
parse_dates=[time_col],
index_col=time_col
)
print(f"实时电价数据加载完成,数据量:{len(self.raw_data['real_time'])}条")
# 加载风能发电数据(关键修改:使用配置的时间列名)
if 'wind' in self.file_paths:
time_col = self.time_col_config['wind'] # 从配置中获取风能文件的时间列名
self.raw_data['wind'] = pd.read_excel(
self.file_paths['wind'],
parse_dates=[time_col],
index_col=time_col
)
print(f"风能发电数据加载完成,数据量:{len(self.raw_data['wind'])}条")
# 加载光伏发电数据
if 'solar' in self.file_paths:
time_col = self.time_col_config['solar']
self.raw_data['solar'] = pd.read_excel(
self.file_paths['solar'],
parse_dates=[time_col],
index_col=time_col
)
print(f"光伏发电数据加载完成,数据量:{len(self.raw_data['solar'])}条")
# 加载高压侧总负荷数据
if 'load' in self.file_paths:
time_col = self.time_col_config['load']
self.raw_data['load'] = pd.read_excel(
self.file_paths['load'],
parse_dates=[time_col],
index_col=time_col
)
print(f"高压侧总负荷数据加载完成,数据量:{len(self.raw_data['load'])}条")
# 加载气象数据
if 'weather' in self.file_paths:
time_col = self.time_col_config['weather']
self.raw_data['weather'] = pd.read_excel(
self.file_paths['weather'],
parse_dates=[time_col],
index_col=time_col
)
print(f"气象数据加载完成,数据量:{len(self.raw_data['weather'])}条")
def merge_data(self, join_type='inner'):
"""
合并多源数据(以时间为索引)
:param join_type: 合并方式,默认inner(仅保留时间完全匹配的数据)
:return: 合并后的DataFrame
"""
print(f"\n开始合并数据,合并方式:{join_type}")
# 初始化合并结果为第一个数据源
merged_df = None
for key, df in self.raw_data.items():
if merged_df is None:
merged_df = df.copy()
else:
merged_df = merged_df.join(df, how=join_type, rsuffix=f'_{key}')
# 重命名可能重复的列(如实时电价和日前电价均可能有"价格"列)
price_cols = [col for col in merged_df.columns if '价格' in col or '电价' in col]
if len(price_cols) > 1:
merged_df.rename(columns={price_cols[0]: '日前电价', price_cols[1]: '实时电价'}, inplace=True)
print(f"数据合并完成,合并后数据量:{len(merged_df)}条,字段数:{len(merged_df.columns)}个")
self.processed_data = merged_df
return merged_df
def handle_missing_values(self, strategy='interpolate'):
"""
缺失值处理
:param strategy: 处理策略:interpolate(线性插值)、median(中位数填充)、mode(众数填充)
:return: 处理缺失值后的DataFrame
"""
if self.processed_data is None:
raise ValueError("请先执行merge_data()合并数据")
print(f"\n开始缺失值处理,策略:{strategy}")
# 统计缺失值情况
missing_info = self.processed_data.isnull().sum()
missing_info = missing_info[missing_info > 0]
if len(missing_info) > 0:
print("缺失值统计:")
for col, count in missing_info.items():
missing_rate = count / len(self.processed_data) * 100
print(f" {col}: {count}条({missing_rate:.2f}%)")
else:
print("无缺失值,无需处理")
return self.processed_data
# 按策略处理缺失值
df_clean = self.processed_data.copy()
numeric_cols = df_clean.select_dtypes(include=[np.number]).columns
categorical_cols = df_clean.select_dtypes(include=['object', 'category']).columns
if strategy == 'interpolate':
# 数值型字段线性插值
df_clean[numeric_cols] = df_clean[numeric_cols].interpolate(method='linear', limit_direction='both')
# 分类字段众数填充
for col in categorical_cols:
mode_val = df_clean[col].mode()[0]
df_clean[col] = df_clean[col].fillna(mode_val)
elif strategy == 'median':
# 数值型字段中位数填充
imputer = SimpleImputer(strategy='median')
df_clean[numeric_cols] = imputer.fit_transform(df_clean[numeric_cols])
# 分类字段众数填充
for col in categorical_cols:
mode_val = df_clean[col].mode()[0]
df_clean[col] = df_clean[col].fillna(mode_val)
elif strategy == 'mode':
# 分类字段众数填充
for col in categorical_cols:
mode_val = df_clean[col].mode()[0]
df_clean[col] = df_clean[col].fillna(mode_val)
# 数值型字段众数填充
for col in numeric_cols:
mode_val = df_clean[col].mode()[0]
df_clean[col] = df_clean[col].fillna(mode_val)
print("缺失值处理完成")
self.processed_data = df_clean
return df_clean
def handle_outliers(self, method='IQR', threshold=1.5):
"""
异常值处理
:param method: 异常值检测方法,默认IQR(四分位距法)
:param threshold: IQR阈值,默认1.5
:return: 处理异常值后的DataFrame
"""
if self.processed_data is None:
raise ValueError("请先执行merge_data()合并数据")
print(f"\n开始异常值处理,方法:{method},阈值:{threshold}")
df_clean = self.processed_data.copy()
numeric_cols = df_clean.select_dtypes(include=[np.number]).columns
outlier_count = {}
if method == 'IQR':
for col in numeric_cols:
# 计算四分位数
Q1 = df_clean[col].quantile(0.25)
Q3 = df_clean[col].quantile(0.75)
IQR = Q3 - Q1
# 异常值边界
lower_bound = Q1 - threshold * IQR
upper_bound = Q3 + threshold * IQR
# 统计异常值数量
outliers = df_clean[(df_clean[col] < lower_bound) | (df_clean[col] > upper_bound)]
outlier_count[col] = len(outliers)
# 用中位数替换异常值
median_val = df_clean[col].median()
df_clean.loc[(df_clean[col] < lower_bound) | (df_clean[col] > upper_bound), col] = median_val
# 输出异常值统计
if sum(outlier_count.values()) > 0:
print("异常值统计(已用中位数替换):")
for col, count in outlier_count.items():
outlier_rate = count / len(df_clean) * 100
print(f" {col}: {count}条({outlier_rate:.2f}%)")
else:
print("无异常值,无需处理")
self.processed_data = df_clean
return df_clean
def feature_engineering(self):
"""
特征工程:衍生时间特征、滞后特征、聚合特征
:return: 新增特征后的DataFrame
"""
if self.processed_data is None:
raise ValueError("请先执行merge_data()合并数据")
print("\n开始特征工程...")
df_feat = self.processed_data.copy()
# 1. 衍生时间特征
df_feat['小时'] = df_feat.index.hour
df_feat['分钟'] = df_feat.index.minute
df_feat['日期'] = df_feat.index.date
df_feat['星期'] = df_feat.index.weekday # 0=周一,6=周日
df_feat['是否工作日'] = df_feat['星期'].apply(lambda x: 1 if x < 5 else 0)
# 时段划分(根据电力负荷特性)
df_feat['用电时段'] = pd.cut(
df_feat['小时'],
bins=[0, 6, 9, 12, 16, 21, 24],
labels=['凌晨(0-6)', '早高峰(7-9)', '上午平段(10-12)', '下午平段(13-16)', '晚高峰(17-21)', '夜间(22-23)']
)
# 2. 衍生电价滞后特征(捕捉时间序列相关性)
if '日前电价' in df_feat.columns:
df_feat['日前电价_滞后15分钟'] = df_feat['日前电价'].shift(1)
df_feat['日前电价_滞后30分钟'] = df_feat['日前电价'].shift(2)
df_feat['日前电价_滞后1小时'] = df_feat['日前电价'].shift(4) # 15分钟/间隔,1小时=4个间隔
# 填充滞后特征产生的缺失值
df_feat['日前电价_滞后15分钟'] = df_feat['日前电价_滞后15分钟'].fillna(df_feat['日前电价'].median())
df_feat['日前电价_滞后30分钟'] = df_feat['日前电价_滞后30分钟'].fillna(df_feat['日前电价'].median())
df_feat['日前电价_滞后1小时'] = df_feat['日前电价_滞后1小时'].fillna(df_feat['日前电价'].median())
if '实时电价' in df_feat.columns:
df_feat['实时电价_滞后15分钟'] = df_feat['实时电价'].shift(1)
df_feat['实时电价_滞后30分钟'] = df_feat['实时电价'].shift(2)
df_feat['实时电价_滞后1小时'] = df_feat['实时电价'].shift(4)
df_feat['实时电价_滞后15分钟'] = df_feat['实时电价_滞后15分钟'].fillna(df_feat['实时电价'].median())
df_feat['实时电价_滞后30分钟'] = df_feat['实时电价_滞后30分钟'].fillna(df_feat['实时电价'].median())
df_feat['实时电价_滞后1小时'] = df_feat['实时电价_滞后1小时'].fillna(df_feat['实时电价'].median())
# 3. 衍生聚合特征(每小时统计值)
hourly_agg = df_feat.groupby(['日期', '小时']).agg({
'日前电价': ['mean', 'max', 'min'],
'实时电价': ['mean', 'max', 'min'],
'DATA_VALUE': ['mean', 'max'] # 假设负荷数据列名为"高压侧总负荷"
}).reset_index()
# 重命名聚合特征列
hourly_agg.columns = ['日期', '小时', '日前电价_小时均值', '日前电价_小时最大值', '日前电价_小时最小值',
'实时电价_小时均值', '实时电价_小时最大值', '实时电价_小时最小值',
'负荷_小时均值', '负荷_小时最大值']
# 合并聚合特征
df_feat['日期_str'] = df_feat['日期'].astype(str)
hourly_agg['日期_str'] = hourly_agg['日期'].astype(str)
df_feat = df_feat.merge(
hourly_agg.drop('日期', axis=1),
on=['日期_str', '小时'],
how='left'
)
df_feat.drop('日期_str', axis=1, inplace=True)
# 4. 分类变量编码(开停状态)
if '开停' in df_feat.columns:
df_feat['开停_编码'] = df_feat['开停'].map({'开': 1, '停': 0})
print(f"特征工程完成,新增特征后字段数:{len(df_feat.columns)}个")
self.processed_data = df_feat
return df_feat
def normalize_data(self, cols_to_normalize=None):
"""
数据归一化(Min-Max标准化到[0,1])
:param cols_to_normalize: 需要归一化的字段列表,默认对所有数值型字段归一化
:return: 归一化后的数据
"""
if self.processed_data is None:
raise ValueError("请先执行merge_data()合并数据")
print("\n开始数据归一化...")
df_norm = self.processed_data.copy()
if cols_to_normalize is None:
cols_to_normalize = df_norm.select_dtypes(include=[np.number]).columns
# 排除不需要归一化的时间类特征
cols_to_normalize = [col for col in cols_to_normalize if col not in ['小时', '分钟', '星期', '是否工作日']]
# 执行归一化
df_norm[cols_to_normalize] = self.scaler.fit_transform(df_norm[cols_to_normalize])
print(f"归一化完成,归一化字段:{cols_to_normalize}")
self.processed_data = df_norm
return df_norm
def save_processed_data(self, save_path='processed_power_data.csv'):
"""
保存预处理后的数据
:param save_path: 保存路径,默认CSV格式
"""
if self.processed_data is None:
raise ValueError("请先完成数据预处理流程")
self.processed_data.to_csv(save_path, encoding='utf-8-sig')
print(f"\n预处理后的数据已保存至:{save_path}")
class PowerDataAnalyzer:
"""多源电力数据描述性分析类:包含单变量、双变量、时间序列分析及可视化"""
def __init__(self, processed_data):
"""
初始化函数
:param processed_data: 预处理后的DataFrame(来自PowerDataProcessor)
"""
self.data = processed_data
# 定义关键分析字段(可根据实际数据调整)
self.price_cols = [col for col in self.data.columns if '电价' in col]
self.power_cols = [col for col in self.data.columns if '发电' in col or '负荷' in col]
self.weather_cols = [col for col in self.data.columns if '温度' in col or '风速' in col or '湿度' in col or '光照' in col]
self.time_cols = ['小时', '星期', '用电时段', '是否工作日']
def univariate_analysis(self, save_fig_path='单变量分析图/'):
"""
单变量分析:统计描述+分布可视化
:param save_fig_path: 图表保存路径
"""
import os
os.makedirs(save_fig_path, exist_ok=True)
print("\n=== 开始单变量分析 ===")
# 1. 输出统计描述
print("\n1. 数值型变量统计描述:")
numeric_data = self.data.select_dtypes(include=[np.number])
print(numeric_data.describe().round(2))
# 2. 电价分布分析
if len(self.price_cols) > 0:
print(f"\n2. 电价分布分析(字段:{self.price_cols})")
fig, axes = plt.subplots(len(self.price_cols), 2, figsize=(15, 5*len(self.price_cols)))
axes = axes.flatten() if len(self.price_cols) > 1 else [axes[0], axes[1]]
for i, col in enumerate(self.price_cols):
# 直方图+核密度图
sns.histplot(self.data[col], kde=True, ax=axes[2*i], color='#2E86AB', alpha=0.7)
axes[2*i].set_title(f'{col}分布直方图', fontsize=12, fontweight='bold')
axes[2*i].set_xlabel(col)
axes[2*i].set_ylabel('频次')
axes[2*i].grid(alpha=0.3)
# 箱线图
sns.boxplot(y=self.data[col], ax=axes[2*i+1], color='#A23B72', width=0.6)
axes[2*i+1].set_title(f'{col}箱线图(异常值已处理)', fontsize=12, fontweight='bold')
axes[2*i+1].set_ylabel(col)
axes[2*i+1].grid(alpha=0.3)
plt.tight_layout()
plt.savefig(f'{save_fig_path}电价分布分析.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"电价分布图表已保存至:{save_fig_path}电价分布分析.png")
# 3. 开停状态分布
if '开停' in self.data.columns:
print("\n3. 开停状态分布分析")
status_count = self.data['开停'].value_counts()
print("开停状态统计:")
for status, count in status_count.items():
rate = count / len(self.data) * 100
print(f" {status}: {count}条({rate:.2f}%)")
# 饼图
fig, ax = plt.subplots(figsize=(8, 8))
colors = ['#F18F01', '#C73E1D']
wedges, texts, autotexts = ax.pie(
status_count.values,
labels=status_count.index,
autopct='%1.1f%%',
colors=colors,
startangle=90,
textprops={'fontsize': 11}
)
ax.set_title('电价交易开停状态分布', fontsize=14, fontweight='bold', pad=20)
plt.savefig(f'{save_fig_path}开停状态分布.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"开停状态图表已保存至:{save_fig_path}开停状态分布.png")
# 4. 气象指标分布
if len(self.weather_cols) > 0:
print(f"\n4. 气象指标分布分析(字段:{self.weather_cols})")
# 计算子图行数(每行2个字段)
n_rows = (len(self.weather_cols) + 1) // 2
fig, axes = plt.subplots(n_rows, 2, figsize=(15, 4*n_rows))
axes = axes.flatten()
for i, col in enumerate(self.weather_cols):
if i < len(axes):
sns.boxplot(y=self.data[col], ax=axes[i], color='#6A994E', width=0.6)
axes[i].set_title(f'{col}分布', fontsize=11, fontweight='bold')
axes[i].set_ylabel(col)
axes[i].grid(alpha=0.3)
# 隐藏多余的子图
for i in range(len(self.weather_cols), len(axes)):
axes[i].set_visible(False)
plt.tight_layout()
plt.savefig(f'{save_fig_path}气象指标分布.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"气象指标图表已保存至:{save_fig_path}气象指标分布.png")
def bivariate_analysis(self, save_fig_path='双变量分析图/'):
"""
双变量分析:相关性分析+关键变量关系可视化
:param save_fig_path: 图表保存路径
"""
import os
os.makedirs(save_fig_path, exist_ok=True)
print("\n=== 开始双变量分析 ===")
# 1. 相关性分析
print("\n1. 数值型变量相关性分析")
numeric_data = self.data.select_dtypes(include=[np.number])
corr_matrix = numeric_data.corr().round(2)
# 输出与电价相关性Top10的变量
for price_col in self.price_cols:
if price_col in corr_matrix.columns:
price_corr = corr_matrix[price_col].sort_values(ascending=False)
print(f"\n与{price_col}相关性Top10的变量:")
print(price_corr.head(11)) # 包含自身,所以取前11个
# 相关性热力图
fig, ax = plt.subplots(figsize=(16, 14))
mask = np.triu(np.ones_like(corr_matrix, dtype=bool)) # 隐藏上三角
cmap = sns.diverging_palette(220, 10, as_cmap=True)
sns.heatmap(
corr_matrix,
mask=mask,
cmap=cmap,
annot=False,
square=True,
linewidths=.5,
cbar_kws={"shrink": .8},
ax=ax
)
ax.set_title('多源电力数据相关性热力图', fontsize=16, fontweight='bold', pad=30)
plt.savefig(f'{save_fig_path}相关性热力图.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"相关性热力图已保存至:{save_fig_path}相关性热力图.png")
# 2. 电价与开停状态关系
if '开停' in self.data.columns and len(self.price_cols) > 0:
print("\n2. 电价与开停状态关系分析")
fig, axes = plt.subplots(1, len(self.price_cols), figsize=(12*len(self.price_cols), 6))
axes = [axes] if len(self.price_cols) == 1 else axes
for i, price_col in enumerate(self.price_cols):
sns.boxplot(
x='开停',
y=price_col,
data=self.data,
ax=axes[i],
palette=['#C73E1D', '#F18F01'],
width=0.6
)
axes[i].set_title(f'{price_col}与开停状态关系', fontsize=12, fontweight='bold')
axes[i].set_xlabel('开停状态')
axes[i].set_ylabel(price_col)
axes[i].grid(alpha=0.3)
plt.tight_layout()
plt.savefig(f'{save_fig_path}电价与开停状态关系.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"电价与开停状态关系图已保存至:{save_fig_path}电价与开停状态关系.png")
# 3. 电价与用电时段关系
if '用电时段' in self.data.columns and len(self.price_cols) > 0:
print("\n3. 电价与用电时段关系分析")
fig, axes = plt.subplots(1, len(self.price_cols), figsize=(14*len(self.price_cols), 7))
axes = [axes] if len(self.price_cols) == 1 else axes
for i, price_col in enumerate(self.price_cols):
sns.violinplot(
x='用电时段',
y=price_col,
data=self.data,
ax=axes[i],
palette='Set3',
inner='quartile'
)
axes[i].set_title(f'{price_col}与用电时段关系', fontsize=12, fontweight='bold')
axes[i].set_xlabel('用电时段')
axes[i].set_ylabel(price_col)
axes[i].tick_params(axis='x', rotation=45)
axes[i].grid(alpha=0.3)
plt.tight_layout()
plt.savefig(f'{save_fig_path}电价与用电时段关系.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"电价与用电时段关系图已保存至:{save_fig_path}电价与用电时段关系.png")
# 4. 电价与关键气象指标关系(选择相关性最高的2个气象指标)
if len(self.weather_cols) > 0 and len(self.price_cols) > 0:
print("\n4. 电价与气象指标关系分析(选择相关性最高的2个气象指标)")
# 选择与第一个电价字段相关性最高的2个气象指标
price_col = self.price_cols[0]
weather_corr = corr_matrix[price_col][self.weather_cols].sort_values(ascending=False)
top2_weather = weather_corr.head(2).index.tolist()
if len(top2_weather) >= 2:
fig, axes = plt.subplots(1, 2, figsize=(16, 6))
for i, weather_col in enumerate(top2_weather):
sns.scatterplot(
x=weather_col,
y=price_col,
data=self.data,
ax=axes[i],
color='#2E86AB',
alpha=0.6,
s=30
)
# 添加趋势线
z = np.polyfit(self.data[weather_col], self.data[price_col], 1)
p = np.poly1d(z)
axes[i].plot(self.data[weather_col], p(self.data[weather_col]), "r--", linewidth=2)
axes[i].set_title(f'{price_col}与{weather_col}关系(相关系数:{corr_matrix.loc[price_col, weather_col]:.2f})',
fontsize=11, fontweight='bold')
axes[i].set_xlabel(weather_col)
axes[i].set_ylabel(price_col)
axes[i].grid(alpha=0.3)
plt.tight_layout()
plt.savefig(f'{save_fig_path}电价与气象指标关系.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"电价与气象指标关系图已保存至:{save_fig_path}电价与气象指标关系.png")
def time_series_analysis(self, save_fig_path='时间序列分析图/'):
"""
时间序列分析:电价时序趋势+周期性分析
:param save_fig_path: 图表保存路径
"""
import os
os.makedirs(save_fig_path, exist_ok=True)
print("\n=== 开始时间序列分析 ===")
# 1. 日内电价趋势(按小时平均)
print("\n1. 日内电价趋势分析")
hourly_price = self.data.groupby('小时')[self.price_cols].mean().reset_index()
fig, ax = plt.subplots(figsize=(14, 7))
for price_col in self.price_cols:
ax.plot(
hourly_price['小时'],
hourly_price[price_col],
marker='o',
linewidth=2.5,
label=price_col,
markersize=6
)
# 添加时段分隔线
ax.axvline(x=6, color='gray', linestyle='--', alpha=0.5, label='时段分界')
ax.axvline(x=9, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=12, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=16, color='gray', linestyle='--', alpha=0.5)
ax.axvline(x=21, color='gray', linestyle='--', alpha=0.5)
ax.set_title('日内电价变化趋势(24小时平均)', fontsize=14, fontweight='bold', pad=20)
ax.set_xlabel('小时', fontsize=12)
ax.set_ylabel('电价', fontsize=12)
ax.set_xticks(range(0, 24))
ax.legend(fontsize=11)
ax.grid(alpha=0.3)
plt.savefig(f'{save_fig_path}日内电价趋势.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"日内电价趋势图已保存至:{save_fig_path}日内电价趋势.png")
# 2. 周内电价趋势(按星期平均)
print("\n2. 周内电价趋势分析")
weekday_price = self.data.groupby('星期')[self.price_cols].mean().reset_index()
weekday_mapping = {0: '周一', 1: '周二', 2: '周三', 3: '周四', 4: '周五', 5: '周六', 6: '周日'}
weekday_price['星期名称'] = weekday_price['星期'].map(weekday_mapping)
fig, ax = plt.subplots(figsize=(12, 6))
for price_col in self.price_cols:
ax.plot(
weekday_price['星期名称'],
weekday_price[price_col],
marker='s',
linewidth=2.5,
label=price_col,
markersize=8
)
# 标记工作日与周末
ax.axvspan(-0.5, 4.5, color='#E8F4FD', alpha=0.3, label='工作日')
ax.axvspan(4.5, 6.5, color='#FFF2E8', alpha=0.3, label='周末')
ax.set_title('周内电价变化趋势(按星期平均)', fontsize=14, fontweight='bold', pad=20)
ax.set_xlabel('星期', fontsize=12)
ax.set_ylabel('电价', fontsize=12)
ax.legend(fontsize=11)
ax.grid(alpha=0.3)
plt.savefig(f'{save_fig_path}周内电价趋势.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"周内电价趋势图已保存至:{save_fig_path}周内电价趋势.png")
# 3. 电价与负荷时序同步性(选择第一个电价字段和负荷字段)
if len(self.power_cols) > 0 and len(self.price_cols) > 0:
print("\n3. 电价与负荷时序同步性分析")
# 选择前7天数据(避免图表过于密集)
sample_data = self.data.head(7*96) # 15分钟/间隔,1天=96个间隔
price_col = self.price_cols[0]
load_col = self.power_cols[0]
fig, ax1 = plt.subplots(figsize=(16, 8))
# 绘制电价曲线
color1 = '#2E86AB'
ax1.set_xlabel('时间', fontsize=12)
ax1.set_ylabel(price_col, color=color1, fontsize=12)
ax1.plot(sample_data.index, sample_data[price_col], color=color1, linewidth=2, label=price_col)
ax1.tick_params(axis='y', labelcolor=color1)
ax1.grid(alpha=0.3)
# 绘制负荷曲线(双Y轴)
ax2 = ax1.twinx()
color2 = '#A23B72'
ax2.set_ylabel(load_col, color=color2, fontsize=12)
ax2.plot(sample_data.index, sample_data[load_col], color=color2, linewidth=2, label=load_col)
ax2.tick_params(axis='y', labelcolor=color2)
# 添加图例
lines1, labels1 = ax1.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax1.legend(lines1 + lines2, labels1 + labels2, loc='upper right', fontsize=11)
plt.title('电价与负荷时序同步性(前7天)', fontsize=14, fontweight='bold', pad=20)
plt.savefig(f'{save_fig_path}电价与负荷时序同步性.png', dpi=300, bbox_inches='tight')
plt.close()
print(f"电价与负荷时序同步性图已保存至:{save_fig_path}电价与负荷时序同步性.png")
def generate_analysis_report(self, report_path='电力数据描述性分析报告.txt'):
"""生成分析报告"""
print(f"\n=== 生成分析报告 ===")
report = []
report.append("多源电力数据预处理与描述性分析报告")
report.append("="*50)
# 1. 数据基本信息
report.append(f"\n一、数据基本信息")
report.append(f"1. 数据时间范围:{self.data.index.min()} ~ {self.data.index.max()}")
report.append(f"2. 数据总量:{len(self.data)}条记录,{len(self.data.columns)}个字段")
report.append(f"3. 核心字段分类:")
report.append(f" - 电价字段:{self.price_cols}")
report.append(f" - 发电/负荷字段:{self.power_cols}")
report.append(f" - 气象字段:{self.weather_cols}")
report.append(f" - 时间特征字段:{self.time_cols}")
# 2. 预处理结果
report.append(f"\n二、数据预处理结果")
report.append(f"1. 缺失值处理:无缺失值(已通过线性插值+众数填充处理)")
report.append(f"2. 异常值处理:已通过IQR法识别并替换异常值(阈值1.5)")
report.append(f"3. 特征工程:新增时间特征(时段、工作日)、滞后特征(15min/30min/1h)、聚合特征(小时均值/最大值)")
report.append(f"4. 数据归一化:已对{len([col for col in self.data.columns if '电价' in col or '发电' in col or '负荷' in col])}个核心字段进行Min-Max归一化")
# 3. 关键发现
report.append(f"\n三、关键分析发现")
# 3.1 电价分布特征
for price_col in self.price_cols:
mean_val = self.data[price_col].mean()
max_val = self.data[price_col].max()
min_val = self.data[price_col].min()
report.append(f"1. {price_col}分布:")
report.append(f" - 均值:{mean_val:.2f},最大值:{max_val:.2f},最小值:{min_val:.2f}")
# 时段差异
hourly_max = self.data.groupby('用电时段')[price_col].mean().sort_values(ascending=False)
report.append(f" - 最高电价时段:{hourly_max.index[0]}(均值:{hourly_max.iloc[0]:.2f})")
report.append(f" - 最低电价时段:{hourly_max.index[-1]}(均值:{hourly_max.iloc[-1]:.2f})")
# 3.2 开停状态影响
if '开停' in self.data.columns:
on_price = self.data[self.data['开停'] == '开'][self.price_cols].mean()
off_price = self.data[self.data['开停'] == '停'][self.price_cols].mean()
report.append(f"\n2. 开停状态影响:")
for price_col in self.price_cols:
diff = (on_price[price_col] - off_price[price_col]) / off_price[price_col] * 100
report.append(f" - {price_col}:开状态均值{on_price[price_col]:.2f},停状态均值{off_price[price_col]:.2f},差异{diff:.2f}%")
# 3.3 相关性特征
numeric_data = self.data.select_dtypes(include=[np.number])
corr_matrix = numeric_data.corr()
report.append(f"\n3. 强相关性特征(|相关系数|>0.7):")
for price_col in self.price_cols:
if price_col in corr_matrix.columns:
high_corr = corr_matrix[price_col][abs(corr_matrix[price_col]) > 0.7]
high_corr = high_corr[high_corr.index != price_col] # 排除自身
if len(high_corr) > 0:
report.append(f" - 与{price_col}强相关的字段:")
for col, corr_val in high_corr.items():
report.append(f" * {col}:{corr_val:.2f}")
# 保存报告
with open(report_path, 'w', encoding='utf-8') as f:
f.write('\n'.join(report))
print(f"分析报告已保存至:{report_path}")
# ------------------- 主执行流程 -------------------
if __name__ == "__main__":
# 1. 配置文件路径(请根据实际文件位置调整)
FILE_PATHS = {
'day_ahead': './data/电价_日前.xlsx',
'real_time': './data/电价_实时.xlsx',
'wind': './data/发电数据_风能.xlsx',
'solar': './data/发电数据_光伏.xlsx',
'load': './data/高压侧总负荷.xlsx',
'weather': './data/气象数据.xlsx'
}
# 2. 配置各文件的时间列名称(关键:替换为你的实际列名)
TIME_COL_CONFIG = {
'day_ahead': '时间',
'real_time': '时间',
'wind': 'DATA_TIME',
'solar': 'DATA_TIME',
'load': 'DATA_TIME',
'weather': 'DATA_TIME'
}
# 3. 数据预处理流程
print("="*60)
print("多源电力数据预处理流程启动")
print("="*60)
# 初始化处理器(传入时间列配置)
processor = PowerDataProcessor(FILE_PATHS, time_col_config=TIME_COL_CONFIG)
# 加载数据
processor.load_data()
# 合并数据
processor.merge_data(join_type='inner')
# 处理缺失值
processor.handle_missing_values(strategy='interpolate')
# 处理异常值
processor.handle_outliers(method='IQR', threshold=1.5)
# 特征工程
processor.feature_engineering()
# 数据归一化
processor.normalize_data()
# 保存预处理后的数据
processor.save_processed_data(save_path='processed_power_data.csv')
# 3. 描述性分析流程
print("\n" + "="*60)
print("多源电力数据描述性分析流程启动")
print("="*60)
# 初始化分析器
analyzer = PowerDataAnalyzer(processor.processed_data)
# 单变量分析
analyzer.univariate_analysis()
# 双变量分析
analyzer.bivariate_analysis()
# 时间序列分析
analyzer.time_series_analysis()
# 生成分析报告
analyzer.generate_analysis_report()
print("\n" + "="*60)
print("数据预处理与描述性分析全部完成!")
print("="*60)根据代码用文字逐步描述数据预处理的步骤,包括数据重采样对齐,缺失值处理方法,异常值处理方法,单变量统计与可视化等
最新发布