
简介SolarData是一个面向太阳能领域数据分析的R语言工具包适合需要快速获取与处理公开太阳能数据集的研究者或开发者。该包整合了NREL栅格化卫星辐照度、夏威夷瓦胡岛传感器网络、NOAA地面辐照度、NASA数字高程模型以及林克浊度等多类数据源的访问与操作接口能有效简化跨源数据下载和预处理流程。压缩包共34个文件总大小约168KB内容以9个R源码脚本、14个Rd帮助文档和3个RData示例数据为主另含工程配置、演示脚本与说明文档结构清晰便于二次开发。目前已有1839人学习下载适合具备一定R语言基础、希望快速搭建太阳能数据工作流的读者。通过该资源可掌握各数据集的调用方法、站点定位与读取示例并借鉴作者整理的项目组织方式降低入门门槛。 做光伏发电量预测、建筑能耗分析或者太阳能资源评估的人大概率都经历过同一个尴尬阶段——数据集不好找。网上公开的太阳能数据其实不少但分散在不同机构、不同平台格式五花八门有的要注册申请有的接口文档写得跟天书一样有的下载下来一堆缺失值和异常点光是清洗就得耗掉大半天。我自己在项目里折腾过好几轮之后干脆把常用的几个公开数据源、下载脚本和清洗流程整理成了一个工具集取名 SolarData。这篇文章就把这套思路完整拆开讲一遍从数据源选型到下载、清洗、质量检查再到怎么把数据做成能直接喂给模型的时间序列全程记录实操细节和踩过的坑给同样被太阳能数据折磨过的人一个参考。1. 内容整体设计与思路拆解1.1 核心需求解析到底什么场景需要公开太阳能数据先说清楚一个事情不是所有项目都需要自己架设气象站。很多研究场景、工程预研、课程设计甚至早期产品原型用公开的太阳辐射数据完全够用。我大致统计过自己经手的几个方向需求基本落在三类场景。第一类是光伏发电量预测。这类场景需要的历史数据最讲究不仅要太阳总辐照度GHI还要散射辐照度DHI、法向直接辐照度DNI、温度、风速时间分辨率最好到小时甚至分钟级。因为发电量预测模型的特征工程里辐照度是决定性变量温度影响组件效率风速影响散热这几个特征缺一个模型精度就会明显掉一截。第二类是太阳能资源评估和选址分析。比如判断某个区域适不适合建分布式光伏电站需要多年的长期统计资料重点看年均总辐照量、季节性波动、峰值日照小时数。这类场景对实时性没要求但对数据年限和连续性要求高最好有十年以上的历史数据。第三类是设备仿真和教学演示。比如做光伏逆变器选型、储能容量配置、微电网仿真需要一个标准化的“典型气象年”数据文件或者一段连续且干净的时间序列来跑仿真模型。这类场景数据量不大但格式要规整字段要齐全。SolarData 这个工具集的定位就是服务这三类场景核心解决两个问题一是把分散的数据源统一入口二是把“下载到能用的数据”之间的脏活累活自动化。1.2 数据源选型思路为什么锚定这几个公开数据源公开太阳辐射数据的提供方不少但真正稳定、权威、适合做研究的实际筛下来就那几个。我在整理 SolarData 的时候选型标准有三条数据质量有学术背书、接口稳定能长期使用、字段覆盖足够全。第一条先排除了很多爬虫类聚合平台。那些平台的数据来源不明精度没有验证过做做展示还行写进论文或者商用系统里风险太大。最终锚定的数据源包括美国国家可再生能源实验室的 NSRDBNational Solar Radiation Database、欧洲委员会的 PVGISPhotovoltaic Geographical Information System、以及 Solcast 的学术 API。这三家各有侧重NSRDB 覆盖北美和部分亚洲区域时间分辨率最高到小时级数据质量校验机制最成熟PVGIS 主打欧洲和非洲提供的“典型气象年”文件是光伏系统仿真的事实标准Solcast 则是卫星反演数据覆盖全球更新快适合需要近期数据的场景。各数据源优缺点对比如下数据源覆盖区域时间分辨率主要字段优点注意点NSRDB北美、部分亚洲30分钟/1小时GHI、DHI、DNI、温度、风速、GQI等质量校验严格、字段全、历史跨度长部分区域需申请PVGIS欧洲、非洲、亚洲1小时/日/月GHI、DHI、DNI、温度、PVOUT典型气象年文件好用、接口简单全球覆盖不均匀Solcast全球5分钟/30分钟/1小时GHI、DHI、DNI、云量、温度覆盖广、分辨率高、更新快API有日调用限额选型背后的逻辑是不同场景用不同源而不是一个源吃到底。做北美区域的发电量预测NSRDB 是首选做欧洲项目的前期仿真PVGIS 的 TMY 文件省事得多需要实时或者近实时的辐照度数据做短临预测就得靠 Solcast 这类卫星反演数据。SolarData 把这几个源封装成统一的接口不用每次换数据源就重写一套代码。2. 数据下载核心流程与接口对接2.1 环境准备与依赖工具链先说工具链。SolarData 采用 Python 实现这是数据领域最不折腾的选择。核心依赖就四个库requests 负责 HTTP 请求pandas 做数据处理numpy 做数值计算pyarrow 用来加速 Parquet 格式的读写。另外建议装一个 tqdm下载大文件时用来显示进度条虽然不装也不影响功能但真下载十年以上的小时级数据时没有进度提示会让人怀疑程序是不是卡死了。pip install requests pandas numpy pyarrow tqdm如果你用的是 conda 环境建议建一个独立环境避免和项目里其他包的版本冲突conda create -n solardata python3.10 -y conda activate solardata pip install requests pandas numpy pyarrow tqdmPython 版本建议 3.9 以上pandas 2.0 以上对 Parquet 和时区处理的支持更友好。实际开发中遇到过一个坑pandas 1.x 版本里pd.to_datetime对某些带时区字符串的解析方式不一样同样的代码在 1.5 和 2.1 下跑出来的结果可能不同所以固定版本是有必要的。2.2 NSRDB 数据下载实操NSRDB 提供两种获取方式一种是直接通过网页端选择参数后生成下载链接另一种是调用 PSM API。项目里我主要使用 API 方式方便做批量化和自动化。NSRDB 的 API 地址是https://developer.nrel.gov/api/nsrdb/v2/solar/psm3-download.csv需要先注册一个 NREL Developer Network 账号获取 API Key。下载指定经纬度和时间范围的小时级数据核心参数列在下面参数含义说明api_keyAPI密钥注册后获取注意不要提交到公开仓库wkt地理范围支持点和多边形点用POINT(lon lat)格式names数据源年份对应年份的 NSRDB 数据集版本leap_day是否包含闰日建议设为 trueinterval时间分辨率60 或 30 分钟utc时区处理false 表示返回本地太阳时email注册邮箱必填用于接受下载通知attributes字段列表用逗号分隔常用ghi,dhi,dni,air_temperature,wind_speed一个典型的下载请求示例import requests API_KEY your_api_key_here params { api_key: API_KEY, wkt: POINT(-105.27 39.95), names: 2022, leap_day: true, interval: 60, utc: false, email: your_emailexample.com, attributes: ghi,dhi,dni,air_temperature,wind_speed, reason: research, affiliation: university, mailing_list: false } url https://developer.nrel.gov/api/nsrdb/v2/solar/psm3-download.csv resp requests.get(url, paramsparams, timeout60) if resp.status_code 200: with open(nsrdb_2022.csv, w) as f: f.write(resp.text) else: print(fRequest failed: {resp.status_code} - {resp.text})这里要注意NSRDB 返回的 CSV 文件头部有一段元数据注释用#开头真正的列名要从第 3 行左右才开始。直接用pd.read_csv会读出一堆没用的注释行需要手动跳过。代码如下import pandas as pd df pd.read_csv(nsrdb_2022.csv, skiprows2) df[Year] df[Year].astype(int) df[Month] df[Month].astype(int) df[Day] df[Day].astype(int) df[Hour] df[Hour].astype(int) df[timestamp] pd.to_datetime( df[[Year, Month, Day, Hour]] ) df df.set_index(timestamp).sort_index()一个小细节NSRDB 的字段里有Year、Month、Day、Hour四个单独的列要自己拼成时间戳。另外不管请求时设没设utcfalse返回数据里的时间默认是本地太阳时和北京时间有差如果项目里需要统一用 UTC 时间需要在处理时注意换算。这个坑我在后面还要专门讲。2.3 PVGIS 与 Solcast 的接入方式PVGIS 和 Solcast 在数据格式上比 NSRDB 友好很多都是标准 JSON 返回但各自也有一些需要注意的细节。PVGIS 的接口地址分几个版本我常用的是https://re.jrc.ec.europa.eu/api/v5_2/seriescalc。这个接口不需要 API Key请求参数很简单lat、lon、startyear、endyear、horizontal是否包含水平面辐照、components是否拆分 DHI/DNI、outputformatjson。返回的结构是一个嵌套 JSON里面outputs字段下是每小时的数据monthly字段是月平均值。注意 PVGIS 默认返回的是 UTC 时间但数据里没有显式的时区标记直接用的时候就当 UTC 处理即可。import requests url https://re.jrc.ec.europa.eu/api/v5_2/seriescalc params { lat: 40.4168, lon: -3.7038, startyear: 2020, endyear: 2022, components: 1, horizontal: 1, outputformat: json } resp requests.get(url, paramsparams, timeout30) data resp.json() df pd.DataFrame(data[outputs]) df[time] pd.to_datetime(df[time])Solcast 的接入稍微麻烦一点它的 API 采用 Bearer Token 认证并且按调用次数计费。学术账号每天有一定免费额度大概 10 次请求每次可以拉取一个地点最多 90 天的历史数据。所以做长期数据回填的话得循环请求注意控制频率。import requests TOKEN your_solcast_token headers {Authorization: fBearer {TOKEN}} url https://api.solcast.com.au/data/historic/radiation_and_weather params { latitude: 31.2304, longitude: 121.4737, hours: 24, period: PT60M } resp requests.get(url, headersheaders, paramsparams, timeout30) data resp.json() df pd.DataFrame(data[radiation_and_weather])Solcast 返回的数据里没有现成的时间戳而是period_end和period两个字段period_end是 ISO 格式的字符串period表示采样间隔。需要自己把period_end转成datetime并根据period推算出每条记录对应的起始时间。这个细节文档里写得很隱蔽我第一次用的时候直接在 DataFrame 里找不到timestamp列才发现的。3. 数据处理链路与质量检查3.1 数据清洗从原始字段到标准格式不管从哪个数据源拿到的原始数据第一步都是统一成一套内部标准格式。我在 SolarData 里定义的目标格式包括timestampUTC 时间、ghi、dhi、dni、air_temperature、wind_speed全部用国际标准单位辐照度单位是 W/m²温度是摄氏度风速是 m/s。各数据源字段名和单位有差异需要做映射和单位换算。比如 NSRDB 里的Temperature单位是摄氏度但有些版本的字段名是air_temperaturePVGIS 的T2m也是摄氏度但WS10m风速单位是 m/s而 Solcast 的wind_speed_10m也是 m/s。统一格式后后续建模代码不需要关心数据来自哪个源直接读取标准化列名即可。单位换算是容易出事故的地方。我做过一个项目从某个第三方平台拿到一份看起来干干净净的 CSVradiation列数值在 1000 左右想着应该是 W/m²结果后来和当地气象站数据一对比发现实际是 MJ/m²差了 3.6 倍。这种量纲问题如果不在清洗阶段发现后面整个模型的输出都会跟着错而且很难排查。3.2 缺失值处理策略太阳能数据最大的痛点就是缺失值。无论是地面站维护、卫星反演失败还是数据传输丢包都会造成时间序列断裂。SolarData 里的处理策略分两档数据量大、缺失率低的时候直接插值数据稀疏或者连续缺失时间长的时候宁可标记成无效也不硬补。对于短时间缺失一般按连续缺失不超过 4 个小时来卡可以采用线性插值。但线性插值只对平滑变化的气象要素有效辐照度这东西受云遮挡影响波动剧烈相邻一小时的值可能差好几倍线性插值结果不理想。所以实际处理中我倾向于对 GHI 用时间戳前后值做线性插值但同步计算一个插值误差估计——如果前后两小时的值差距超过 200 W/m²就把插值结果标记为低置信度在后续模型训练里降权处理。还有一个物理约束的处理技巧太阳辐照度在夜间理论上为 0但观测数据里经常有传感器零点漂移导致夜间 GHI 出现几个 W/m² 的微小非零值。这个直接用阈值处理当太阳高度角小于某个值通常取 0° 或者 -0.5°时强制把 GHI、DNI 设为 0。太阳高度角可以用pvlib库的solar_position函数计算不用自己写天文算法。3.3 质量指标与异常值识别NSRDB 数据里自带了两个质量相关的字段一个是Fill Flag表示该时次的数据是实际观测还是填充值0 表示实测非 0 表示通过各种方式补全另一个是 GQIGlobal Quality Index范围 0 到 1越接近 1 越可信。下载数据后应该先对 GQI 的分布做个统计如果某个时间段 GQI 长期低于 0.7那这一段数据基本是不可用的不建议参与建模。除了数据源自带的指标还可以做交叉验证的异常检测。用 PVLIB 计算每个时次的理论晴空 GHI和实际 GHI 对比如果实际值明显高于晴空模型的理论上限比如超过 1.2 倍那大概率是传感器故障或者数据记录错误。这类异常值直接剔除不要参与后续统计和建模。3.4 Parquet 存储与多源数据整合清洗后的数据建议统一保存为 Parquet 格式而不是 CSV。Parquet 的列存格式在压缩率和读取速度上都有明显优势同样的十年小时级数据CSV 可能占 200MBParquet 压缩后大概 40MB 左右读取速度快几倍。而且 Parquet 能原生保留数据类型和时区信息省去了每次读取都要重新解析的麻烦。df.to_parquet(solar_data_cleaned.parquet, enginepyarrow)多源数据整合时需要注意时间基准的统一。NSRDB 默认返回本地太阳时PVGIS 返回 UTCSolcast 返回 UTC如果直接合并时间戳会对不齐。我的办法是在清洗阶段就把所有时间转换为 UTC并显式设置tz属性而不是挂一个不带时区的 naive datetime。这样后续做时间序列分析或者训练深度学习模型时不会出现时区错乱导致的周期性问题。df_utc df.tz_localize(UTC) # 原始数据是 UTC # 如果原始数据是本地太阳时先标记为 UTC再转换到目标时区4. 常见问题与排查技巧实录4.1 时间戳时区处理时区问题是处理气象数据时最容易翻车的点尤其在跨国项目里。NSRDB 的utcfalse参数看起来是返回本地太阳时实际上这个“本地太阳时”和行政时区不是一回事。对太阳能数据来说本地太阳时确实更合理——正午 12 点太阳在正头顶而行政时区比如北京时间在新疆地区正午太阳高度角最高的时间其实是下午 2 点左右。但如果要做全球多个站点数据的对比分析必须统一到 UTC否则模型会学到错误的“日周期规律”。实际处理建议从 NSRDB 下载时直接设utctrue拿到的就是 UTC 时间省得后续转换。如果你已经下载了本地太阳时的数据也没关系只要记录里包含经纬度就可以用pvlib的tz参数自定义时区然后用tz_convert转成 UTC。4.2 数据断档与节假日的隐藏问题太阳能数据看起来是连续的时间序列处理时却隐藏着不少“坑”。PVGIS 的月平均数据是按自然月统计的节假日不休息但地面站维护往往安排在周末或节假日导致非工作日的观测数据质量反而更不稳定。如果你拿这些数据做光伏运维的故障预测模型可能会把“节假日数据缺失规律”当成一种特征学到上线后遇到节假日就误报这类问题排查起来非常头疼。我的应对方案是模型训练阶段明确区分工作日和非工作日或者在特征工程里显式加入is_holiday这个布尔特征。虽然不能完全消除这种相关性但至少让模型能区分数据质量变化和真实的发电规律变化。4.3 请求频率限制与断点续传NSRDB 的 API 对单个 Key 有请求频率限制实测快速连续请求会返回 429 状态码。PVGIS 官方文档虽然没有明确写限流但实际使用中短时间大量请求也会被暂时封禁。Solcast 则是按账号额度计费超出后直接返回 403。应对措施很简单在请求循环里加time.sleep()只对大型批量下载有影响半小时以内的请求加个 2 到 3 秒的延迟就够了。但更省事的是做断点续传——把每个请求的响应保存成独立文件下次脚本运行先检查文件是否已存在存在就跳过。我第一次做全美 500 个站点十年前的小时级数据回填时就是靠这个机制跑了三天三夜中间断了好几次每次都直接从断点恢复了。import os import time import requests output_dir raw_data os.makedirs(output_dir, exist_okTrue) for site_id, lat, lon in site_list: fname f{output_dir}/{site_id}_{year}.csv if os.path.exists(fname): continue # 构造请求并发送 resp requests.get(url, paramsparams, timeout60) if resp.status_code 200: with open(fname, w) as f: f.write(resp.text) else: print(fFailed: {site_id}_{year}: {resp.status_code}) time.sleep(2) # 等2秒留出请求间隔4.4 数据源一致性校验最后说一个我后来才补上的环节多源数据交叉校验。不同数据源对同一地点同一时刻的辐照度估计值往往存在偏差NSRDB 和 PVGIS 在北美区域的数据一致性还可以但到了欧洲或者亚洲区域偏差可能达到 10% 以上。这个偏差不是错误是不同反演算法和输入数据造成的系统性差异。如果项目里同时用了多个数据源建议在预处理阶段做一次交叉统计画出各数据源 GHI 月均值的对比曲线。如果发现某个时间段某数据源明显偏离多半不是物理现象变化而是数据版本更新或检索参数变更。我自己就遇到过 NSRDB 数据版本从 PSM3 升级到 PSM3-v2 之后同一经纬度和时间段的 GHI 均值整体抬高了 5% 左右差点以为是全球变暖了。5. 总结与扩展方向整理完 SolarData 这套流程最大的体会是太阳能数据处理的难点不在于“下载”本身而在于下载之后那一连串不容易被察觉的细节——时区、单位、缺测、质量标志、数据版本差异。任何一个环节没处理好最后进入模型的数据都可能带着隐性问题而这些问题的排查成本远远高于一开始就把清洗流程做扎实的成本。还在实际项目中验证过的一个值得尝试的方向把多源数据做融合。用 NSRDB 或者 PVGIS 的长期再分析数据作为基础背景场用 Solcast 或者其他卫星反演的高分辨率数据去修正短期波动效果通常好于任何单一数据源。这个方法还在持续迭代中后面如果跑通了新的验证结果再单独写一篇分享。本文还有配套的精品资源点击获取