用Pandas处理河北10座光伏电站CSV发电记录:从清洗到性能评估

发布时间:2026/10/7 13:51:20
用Pandas处理河北10座光伏电站CSV发电记录:从清洗到性能评估 简介河北省10座光伏电站的发电历史数据集面向光伏数据分析与功率预测研究者提供可复用的实际运行数据和Python开发示例。资源包共20个文件压缩后仅1.58MB其中10个csv为原始观测数据4个py与2个ipynb构成数据处理与建模演示md/txt/README提供说明与使用指引便于快速了解项目结构。目前已有60人学习下载适合中高级数据分析与新能源方向学习者参考。数据覆盖2018年9月至2019年8月共365天、15分钟分辨率的271968条功率与气象记录包含晴天、阴天、多云、雨雪等天气类型占比统计并给出总辐照度、散射辐照度、温度、湿度、风速及对应电站经纬度的数值天气预报数据以及光伏面板容量、面积、数量、材料和方向等基础参数。配套示例代码与相关性分析图可帮助读者快速完成数据加载、天气影响分析和光伏出力预测建模显著降低入门成本。1. 河北10座光伏电站发电记录数据集这份CSV能拿来做什么做光伏数据分析最头疼的不是模型而是数据。真正从电站现场导出的发电记录往往散落在监控后台里导出来是一堆格式不统一的Excel字段名称千奇百怪时间戳还带着时区问题。所以我第一次看到「中国河北省10座光伏电站的发电记录数据集」这个压缩包时第一反应是终于有人把原始记录整理成规整的CSV了。这份资源的核心是一批以CSV格式存储的发电时序记录覆盖河北地区10座光伏电站附带一份数据说明文档用来解释每个字段的含义和记录口径。它的价值在于你可以直接绕过繁琐的数据对接环节把精力放在发电量分析、出力预测、异常诊断这些真正要紧的事情上。适合三类人做光伏功率预测的研究生、做电站运维分析的工程师、以及想拿真实数据练手的数据分析初学者。接下来我会从文件结构讲到字段含义再给出完整的Pandas处理流程和踩坑记录。2. 解压后先看什么文件组织、字段定义与数据说明文档拿到zip包之后别急着写代码。先把压缩包解开看看里面的文件到底是怎么组织的。这一步虽然枯燥但能帮你避免后面百分之八十的返工。2.1 文件与目录是怎样组织的这类发电记录数据集通常不会只有一个孤零零的CSV文件。按照「10座电站」这个数量级我推断压缩包内部大概率是按电站维度拆分的每座电站一个CSV文件文件名包含电站编号或地名比如plant_01.csv、plant_02.csv这种风格也可能按照数据的时间跨度做了二次切分比如每个电站每年一个文件。除此之外压缩包里应该还有一个数据说明文档.md或.pdf格式这部分往往被忽略但恰恰是整套数据里最值钱的文件。先做一件事用命令行或解压工具把zip解开然后列出全部文件的清单。在Windows上我习惯直接右键解压在Linux服务器上就用unzip命令。unzip 中国河北省10座光伏电站的发电记录数据集csv格式数据集说明.zip -d solar_hebei/ ls -la solar_hebei/这段命令把压缩包解压到solar_hebei目录然后用ls -la查看文件列表。重点看三样东西CSV文件的数量、单个文件的大小、有没有说明文档。文件大小很能说明问题——如果每个CSV有几十MB说明采样间隔可能是分钟级的如果只有几百KB大概率是日累计数据。这一步不用做任何数据分析但它决定了后续所有的技术选型。2.2 字段对照每一列究竟记录了什么光伏电站的发电记录字段设计基本遵循一套约定俗成的规范。常见的列包括时间戳记录时刻、有功功率当前出力单位kW或MW、日发电量从零点开始的累计电量单位kWh、辐照强度水平面或倾斜面辐照单位W/m²、组件温度、环境温度。部分站点的数据还会包含风速、风向因为散热条件会影响组件效率。我建议拿到数据后先不看完整文件而是用head命令瞄一眼前几行head -20 solar_hebei/plant_01.csv这一步能让你快速建立对数据格式的直觉。看什么第一是分隔符是逗号还是制表符第二是时间戳格式是2023-01-01 00:00:00还是202301010000这种纯数字第三是表头确认列名是中文还是英文。我见过不少数据集列名是拼音缩写或者电站监控系统的内部编号这种情况下数据说明文档就是唯一的救命稻草。2.3 数据说明文档里最该先读的几页数据说明文档通常不会太长但有几个信息必须第一时间找到。第一是采样间隔常见的有15分钟、30分钟、1小时这决定了你后面重采样时的基准。第二是数据的时间范围是从哪年哪月到哪年哪月中间有没有中断。第三是单位说明特别是功率和电量的单位kW和MW差了1000倍kWh和MWh同理这个搞错会导致所有后续分析全部失真。第四是缺测标记。很多监控系统在数据异常时会写入特定的占位值比如-1、9999、或者空字符串。数据说明文档里一般会交代这些特殊值的含义。如果没有交代你就需要自己在数据探查阶段去发现。我的习惯是把数据说明文档里关于字段含义和单位的部分单独摘录出来贴在项目笔记最前面后面写代码需要查字段时直接翻笔记不用反复回去翻pdf。这一步看着不起眼但当你同时处理10个电站文件时有一张字段对照表在手效率完全不一样。3. 把CSV装进DataFramePandas导入与批量读取实操文件结构和字段含义摸清楚之后才轮到写代码。这一章我们解决一个核心问题怎么把10个CSV文件干净利落地读进Pandas并且完成初步的数据体检。3.1 CSV读取的标准流程从一个文件开始先别急着循环读所有文件拿一个文件跑通流程再说。我用pd.read_csv来读但有几个参数是必调的。import pandas as pd df pd.read_csv( solar_hebei/plant_01.csv, parse_dates[timestamp], # 把时间列解析成datetime类型 na_values[, NULL, -1], # 指定缺测标记 encodingutf-8 # 中文表头需要指定编码 ) print(df.info()) print(df.head())parse_dates参数告诉Pandas哪一列是时间戳读进来之后直接变成datetime64类型省得后面再手动转换。na_values很关键它把数据里常见的占位值统一映射成NaN后续做缺失值处理时才有统一的入口。encodingutf-8是因为国内很多电站导出的CSV表头是中文文件编码可能是GBK或GB2312如果不指定编码读进来就是一堆乱码。读进来之后用df.info()看每列的非空值数量和数据类型的概况。这一步能立刻发现两件事一是时间列有没有被正确解析二是哪些列存在明显的缺失。3.2 批量读取把10个电站的数据合并成一张大表单文件流程跑通后剩下的文件就可以循环处理了。但这里有一个需要斟酌的点是保持每个电站一个DataFrame还是合并成一张大表。我的建议是合并但保留电站编号作为分组键。合并之后的好处是后续做横向对比、画多电站对比图都方便。import glob file_list sorted(glob.glob(solar_hebei/plant_*.csv)) df_all [] for f in file_list: # 从文件名中提取电站编号例如 plant_01.csv - 01 plant_id f.split(/)[-1].split(.)[0].split(_)[-1] tmp pd.read_csv( f, parse_dates[timestamp], na_values[, NULL, -1], encodingutf-8 ) tmp[plant_id] plant_id df_all.append(tmp) df pd.concat(df_all, ignore_indexTrue) print(df.shape) print(df[plant_id].value_counts())这段代码用glob匹配所有plant_*.csv文件逐个读取每个文件加一列plant_id用来标记数据来自哪座电站最后用pd.concat纵向拼接。ignore_indexTrue表示合并后重新生成索引避免重复。执行完后df.shape会告诉你总行数和总列数比如10个电站各一年的15分钟级数据大约有35万行左右这属于正常规模Pandas完全吃得下。3.3 数据体检时间范围、缺失率、重复检测数据合并完成后先做一轮全面的质量体检。这一步不需要复杂的统计模型就是用Pandas的几个基础方法把数据的家底盘清楚。print(时间范围, df[timestamp].min(), →, df[timestamp].max()) print(电站数量, df[plant_id].nunique()) # 检查缺失比例 missing_ratio df.isnull().mean().sort_values(ascendingFalse) print(各列缺失比例\n, missing_ratio) # 检查重复行 dup_count df.duplicated().sum() print(重复行数, dup_count)这段代码做了三件事。第一确认数据覆盖的起止时间防止说明文档里的时间范围和实际数据对不上。第二按列计算缺失比例正常情况下功率列在夜间会是0但不是缺失所以如果某个电站某一列的缺失率超过5%就要回去看原始文件是不是有问题。第三检查重复行重复行往往意味着数据导出时发生了重复追加需要用drop_duplicates()清掉。做完这三项检查你已经对这个数据集有了完整认知知道有多少数据、覆盖多长周期、缺了多少、有没有脏数据。接下来就可以放心地进入预处理阶段。4. 数据预处理时间戳标准化、缺失值处置与按电站聚合原始CSV读进来只是第一步真正让数据变得可用的是预处理。这一章我从三个最常用的操作讲起时间戳规范化、缺失值处理、按电站维度的聚合统计。这三板斧做完数据就能支撑绝大多数业务分析了。4.1 时间戳标准化排序、去重、对齐频率光伏数据的分析强依赖时间序列的规整性。如果采样间隔是15分钟那么理想情况下每条记录的时间戳应该严格落在整点、15分、30分、45分这些刻度上。实际数据往往有偏移或跳变所以第一步就是把时间戳整理干净。# 按时间排序 df df.sort_values([plant_id, timestamp]) # 删除完全重复的行 df df.drop_duplicates(subset[plant_id, timestamp]) # 设置时间索引便于后续重采样 df df.set_index(timestamp) # 检查每个电站的时间是否连续 for pid in df[plant_id].unique(): sub df.loc[df[plant_id] pid] gap sub.index.to_series().diff().value_counts() print(f电站 {pid} 的主要时间间隔{gap.index[0]}出现 {gap.iloc[0]} 次)这段代码先按电站和时间排序然后删除同一个電站下时间戳完全重复的行。设置时间索引后用diff()计算相邻记录的时间差看主要间隔是不是对齐了采样频率。正常情况下15分钟间隔的数据diff的结果应该绝大多数是00:15:00。如果出现大量00:16:00或00:14:00说明原始数据的记录时刻有抖动后面做重采样时需要用reindex或resample统一对齐。4.2 缺失值和异常值是补还是删要看场景光伏数据有一个天然属性夜间出力为零。所以缺失值处理不能在全局统一进行必须按昼夜分别对待。白天辐照充足时出现缺失通常意味着逆变器通讯中断或数据采集异常夜间缺失则可能只是系统休眠影响不大。# 按小时提取区分白天和夜间粗略按6:00-18:00为白天 hour df.index.hour daytime_mask (hour 6) (hour 18) # 白天发电功率为负值或NaN视为异常 df.loc[daytime_mask (df[power_kw] 0), power_kw] None # 对功率列做线性插值限制最大连续插值窗口 df[power_kw] df[power_kw].interpolate( methodlinear, limit4, # 最多连续插值4个点约1小时 limit_areainside ) # 实在填不上的一天直接按日剔除 daily_bad df[power_kw].isna().resample(D).sum() bad_days daily_bad[daily_bad 20].index print(f缺失严重的日期{bad_days})这里有几个值得说明的决策。第一我把负功率统一置为空值因为光伏逆变器在夜间可能从电网取电供自身运行导致功率记录出现负值这对发电分析来说是干扰信息。第二插值用limit4限制窗口15分钟间隔下最多往前补1小时如果缺失时间过长插值出来的数据就不具备参考价值。第三对于一天内缺失超过20个点约5小时的日期标记为坏日后续做日发电量统计时直接剔除不做强行修复。4.3 按电站聚合从分钟级到小时级、日级原始数据是分钟级或15分钟级颗粒度太细不便于直观对比10座电站的运行表现。聚合是光伏数据分析里最常用的操作把细粒度数据汇总成小时级或日级然后做横向对比。# 按电站日期聚合日发电量假设有累计电量列则取每日最后一个值减第一个值 daily df.groupby([plant_id, df.index.date]).agg( daily_energy_kwh(energy_kwh, lambda x: x.iloc[-1] - x.iloc[0]), peak_power_kw(power_kw, max), avg_power_kw(power_kw, mean), day_mean_temp(temp_air, mean) ).reset_index() print(daily.head())这段代码用groupby按电站和日期分组每日发电量用累计电量的最后一个值减去第一个值来计算避免直接对功率积分带来的累计误差。峰值功率取当天最大值用来评估逆变器是否出现过载或限功率运行。平均功率和日均温度作为辅助分析字段。聚合完成之后10个电站的运行差异就一目了然了哪个电站日发电量最高、哪个电站出力波动大、哪个电站在高温天衰减明显都能从这张日级表里读到。5. 常见问题与避坑解析报错、时区错位、单位不清这一章是血泪经验合集。我拆过不少光伏数据集下面五类问题是最常见的翻车现场每一条都按照「现象→原因→解决」来写你遇到类似问题时可以直接对号入座。5.1 时间解析直接报错或解析出大量NaT现象pd.read_csv加了parse_dates[timestamp]之后运行报错或者df[timestamp]出现大量NaTNot a Time。原因原始CSV里的时间格式并不是Pandas默认能识别的ISO格式。常见的有两种一是2023/01/01 00:00这种斜杠分隔Pandas有时能猜对但遇到2023.01.01或中文日期2023年1月1日就会翻车二是时间列里混入了空字符串或乱码。解决不要依赖parse_dates的自动猜测改成显式指定格式。用pd.to_datetime(..., format%Y-%m-%d %H:%M:%S)把格式写死。如果混入脏值先加errorscoerce强制转换非法值为NaT再单独排查。df[timestamp] pd.to_datetime( df[timestamp], format%Y-%m-%d %H:%M:%S, errorscoerce )5.2 数据整体偏移一小时或23小时现象画出来的日发电量曲线峰值总是出现在中午12点或13点有时甚至出现在早上11点怎么都对不上当地太阳正午时刻。原因十有八九是时区问题。河北属于东八区但如果数据采集设备默认使用了UTC时间或者DCS系统设置时区错误就会出现整小时的偏移。更隐蔽的情况是电站监控后台在导出数据时自动把本地时间转成了UTC但没在文档里说明。解决确认时区是本次数据分析的前提不要想当然认为记录的带时间戳就是北京时间。如果发现偏移先统一转成Asia/Shanghai时区。注意河北没有夏令时如果数据里有疑似夏令时导致的偏移几乎可以断定是时区设置错了。# 如果原始时间被存成了UTC这里统一转成北京时间 df[timestamp] pd.to_datetime(df[timestamp], utcTrue) df[timestamp] df[timestamp].dt.tz_convert(Asia/Shanghai) df[timestamp] df[timestamp].dt.tz_localize(None) # 去掉时区信息变成纯本地时间5.3 功率单位混淆kW写成MW导致数值全错现象某电站的峰值功率算出来高达好几万千瓦明显不合常理。以河北常见的10MW光伏电站为例峰值功率应该在8000-10000kW之间如果算出来是8000MW说明单位搞错了。原因数据说明文档里写的是kW但CSV表头简写成了power或者部分电站的仪表采用了不同量纲。10个电站来自不同厂家很可能有一部分用kW记录另一部分用MW记录拼在一起后没有做统一换算。解决加载数据后立刻对功率列做分位数检查。如果某个电站的power_kw最大值超过该电站装机容量的1.5倍就要怀疑单位问题。最稳妥的办法是在聚合前先按电站检测最大值与装机容量的比值。# 假设电站装机容量已知存放在capacity_dict中 for pid, cap_kw in capacity_dict.items(): sub_max df.loc[df[plant_id] pid, power_kw].max() ratio sub_max / cap_kw if ratio 1.5: print(f电站{pid}功率疑似量纲错误最大值/装机容量比值{ratio:.2f}) # 按1000倍换算回kW df.loc[df[plant_id] pid, power_kw] / 10005.4 多文件合并后列错位数据张冠李戴现象合并后的表里某电站的温度列出现明显异常夏天气温反而在零下十几度跟同时间的功率数据对不上。原因pd.concat默认按列名对齐但如果两个CSV文件的列顺序不同或者其中一个文件的温度列名称多了个空格合并时就会产生新列或者错位。我见过最坑的一种情况是某个电站的文件里多了一列风速导致后面所有列整体右移功率数据跑到了温度列的位置。解决批量读取时不要依赖文件自己的表头统一指定列名顺序。如果担心列名有细微差异可以在读取后强制重命名。# 统一定义列名 standard_columns [timestamp, power_kw, energy_kwh, irradiance_wm2, temp_air, temp_module] tmp pd.read_csv(f, encodingutf-8, header0) tmp.columns standard_columns[:len(tmp.columns)]5.5 夜间大量零值被误判为缺失或异常现象夜间时段的功率列全是0但数据处理脚本把0当成异常值清洗掉了导致日发电量被严重高估或低估。原因光伏电站在夜间确实不发电功率记录为0是正常状态。初学者容易用fillna(0)补缺失值或者反过来用df[df[power_kw] 0]把这些行删掉这两种操作都会破坏数据的完整性。解决处理缺失值之前先用时间判断昼夜。夜间0值是真实有效的数据不应该被插值或删除。白天出现的0值则需要警惕可能是辐照不足阴天、雾霾或设备故障。区分这两种情况的方法是看同一天的辐照度列如果辐照度接近0说明0功率合理如果辐照度很高但功率为0说明该电站存在限电或故障。# 标记白天时段功率为0的异常点 hour df.index.hour daytime (hour 6) (hour 18) zero_power_daytime (df[power_kw] 0) daytime (df[irradiance_wm2] 100) print(f白天高辐照但零功率的点数{zero_power_daytime.sum()})6. 进阶用法用发电记录反推电站性能体检报告前几章把数据清洗和聚合讲完了最后一个技巧是这些数据最有价值的用法之一性能体检。光伏电站运行一段时间后组件衰减、灰尘遮挡、逆变器效率下降都会导致实际发电量低于理论值。通过这份CSV数据你可以用两个指标快速评估每座电站的健康状况。第一个指标是性能比Performance RatioPR它衡量电站实际发电量与理论发电量的比值。理论发电量需要结合装机容量和辐照数据来计算但在没有辐照数据的情况下可以用一个简化方案对比同区域内不同电站的归一化发电量。把每座电站的日发电量除以装机容量得到单位容量日发电量kWh/kW这个值在同一地区的电站之间应该比较接近。如果某座电站长期显著低于平均水平说明它存在发电异常。# 计算各电站的单位容量日发电量 daily[normalized_kwh_per_kw] daily[daily_energy_kwh] / daily[plant_id].map(capacity_dict) # 按月份对比 daily[month] pd.to_datetime(daily[date]).dt.to_period(M) monthly_compare daily.groupby([plant_id, month])[normalized_kwh_per_kw].mean().unstack() print(monthly_compare.round(2))第二个指标是容量因子的月变化趋势。容量因子是实际发电量除以装机容量在满发状态下运行24小时的理论发电量。河北地区光伏电站的年容量因子一般在13%-17%之间月度变化明显冬季和雨季偏低5月和9月左右偏高。如果有某座电站的容量因子在某个月份骤降大概率是清洗不及时、组件故障或逆变器停机。# 容量因子 月发电量 / (装机容量 * 当月天数 * 24) capacity_factor ( daily.groupby([plant_id, month])[daily_energy_kwh].sum() / (daily[plant_id].map(capacity_dict).groupby([plant_id, month]).first() * 30 * 24) )这两个指标配合使用就能把10座电站的运行水平排出先后顺序。我自己做这类分析时会把PR和容量因子的结果按周滚动平均画在一张图上。如果发现某条曲线出现阶梯式下降而不是缓慢衰减优先怀疑逆变器故障而不是组件衰减因为组件老化通常是个渐变过程阶梯式下跌大概率是设备停机。最后多说一句这份数据集里最容易被低估的其实是数据说明文档。第一次拆这份数据时我跳过文档直接开跑代码结果被时区偏移和单位问题反复折磨浪费了大半天。从那以后我每次拿到光伏CSV数据都强制自己先花半小时把数据字典和单位说明看透再碰任何代码。这个习惯救了我很多次。希望帮到你。本文还有配套的精品资源点击获取