
简介这份资源面向数据分析初学者与气象数据爱好者围绕“爬取中国天气网某城市天气数据并完成可视化分析”这一完整实验展开帮助读者掌握从数据采集到图表呈现的全流程方法。压缩包共39个文件约1.69MB以20张png图表、15个xml配置文件和4个rels关系文件为主png对应雷达图、条形图等分析结果xml与rels则支撑实验报告文档的结构与样式。源代码基于Jupyter Notebook编写可直接运行复现爬虫抓取与数据处理过程并配有整理后的实验报告便于对照图表理解分析结论。目前已有1852人学习下载适合课程实验、数据分析入门练习或作为报告撰写参考读者可借此获得可运行的爬虫脚本、可视化代码与完整实验文档快速搭建自己的天气数据分析项目。1. 气象数据分析资源包从原始观测到可复现报告的一条龙拆解拿到一份气象观测数据很多人第一反应是打开 Excel 画个折线图交差。但真正做过气象分析报告的人都知道原始数据里藏着大量缺测、格式不统一、时间戳错位的问题直接画图往往得到一条锯齿状的废线。这次拆解的资源包核心就是解决从「拿到原始气象数据」到「产出一份能写进实验报告的分析结论」这条链路。它包含数据清洗脚本、统计指标计算模块、可视化模板和报告生成框架适合气象、环境、地理方向的学生和需要做数据分析报告的从业者。无论你是第一次接触气象数据还是已经用过 pandas 但被时间序列索引折磨过这份资源都能帮你省掉大量重复造轮子的时间。2. 气象数据清洗与预处理把缺测和异常值处理干净2.1 为什么气象数据清洗不能直接 dropna气象观测数据最常见的来源是地面气象站逐小时或逐日观测字段通常包括气温、气压、相对湿度、风速风向、降水量等。这类数据有个特点缺测不是随机的。传感器故障、通讯中断、极端天气导致的设备停机都会让缺测集中在某些时段。如果直接dropna()你会把整个台风过境期间的数据全部丢掉而这恰恰是最需要分析的时段。常见做法是分字段处理。气温、气压这类变化连续的变量用时间序列插值补全降水量这种间歇性变量缺测就保留 NaN不要插值否则会凭空造出虚假降水。资源包里的清洗脚本采用的就是这套策略按字段类型走不同分支。import pandas as pd import numpy as np # 读取原始数据假设时间列为 datetime其余为气象要素 df pd.read_csv(raw_weather.csv, parse_dates[datetime]) df df.set_index(datetime).sort_index() # 气温线性插值限制最大连续插值长度为 3 小时 df[temp] df[temp].interpolate(methodlinear, limit3) # 气压同样插值但气压变化更平滑可以用样条插值 df[pressure] df[pressure].interpolate(methodspline, order2, limit3) # 降水量不插值缺测保持 NaN # 相对湿度插值后裁剪到 0-100 范围 df[humidity] df[humidity].interpolate(methodlinear, limit3).clip(0, 100) # 风速插值后不能为负 df[wind_speed] df[wind_speed].interpolate(methodlinear, limit3).clip(lower0)这段代码的关键参数是limit3意思是连续缺测超过 3 个时间步就不插值了保留 NaN。为什么是 3因为逐小时数据里连续缺 3 小时以上往往意味着设备真的出了问题强行插值会引入虚假的平滑信号。methodspline用于气压是因为气压日变化曲线比较规则样条插值比线性更贴合物理规律。湿度裁剪到 0-100 是硬约束插值可能产生超出物理范围的值。2.2 异常值检测三倍标准差和物理阈值双管齐下清洗完缺测下一步是异常值。气象数据里的异常值分两种一种是物理上不可能的比如气温 60°C、风速 -5 m/s另一种是物理上可能但统计上罕见的比如夏季午后突然出现 5°C 的气温。前者用物理阈值直接卡掉后者用统计方法标记。# 物理阈值检查 df.loc[df[temp] 60, temp] np.nan df.loc[df[temp] -80, temp] np.nan df.loc[df[wind_speed] 0, wind_speed] np.nan # 统计异常值三倍标准差 for col in [temp, pressure, humidity]: mean df[col].mean() std df[col].std() lower mean - 3 * std upper mean 3 * std df[f{col}_outlier] (df[col] lower) | (df[col] upper)这里没有直接把三倍标准差之外的值删掉而是新增了一列布尔标记。原因是气象里的极端值有时候是真实信号比如寒潮导致的气温骤降直接删掉会丢失重要信息。标记出来之后可以在后续分析里决定是否纳入统计或者在报告里单独说明。提示三倍标准差对非正态分布字段如降水量不适用降水量建议用分位数方法比如超过 99.9 分位数的标记为极端降水。3. 统计指标计算与时间序列分析从均值到趋势检验3.1 核心统计量的计算逻辑与陷阱气象分析报告里最常见的统计量包括日均值、月均值、极值、日较差、累积降水量等。这些看起来简单但计算时有几个容易翻车的地方。第一个坑是日均值的计算方式。气象上的日平均气温通常不是 24 小时算术平均而是用 02、08、14、20 时四个定时观测值平均。如果你用逐小时数据算算术平均结果会和气象业务标准有偏差。资源包里提供了两种计算模式可以通过参数切换。def daily_mean_temperature(df, method24h): 计算日平均气温 method24h: 24小时算术平均 method4obs: 02,08,14,20时四个定时值平均 if method 24h: return df[temp].resample(D).mean() elif method 4obs: # 筛选四个定时时刻 hours [2, 8, 14, 20] mask df.index.hour.isin(hours) return df.loc[mask, temp].resample(D).mean()第二个坑是月均值不能直接用日均值再平均。如果某个月有缺测日直接对日均值序列取平均会引入偏差。正确做法是用该月所有有效小时数据的总和除以有效小时数。资源包里的monthly_stat函数处理了这个逻辑会自动统计有效样本数。第三个坑是风向的统计。风向是角度量不能直接算算术平均。比如 350° 和 10° 的平均应该是 0°而不是 180°。需要用矢量平均法把风向分解为 u、v 分量再合成。def wind_direction_mean(angles): 矢量法计算平均风向 rad np.deg2rad(angles) u np.mean(np.cos(rad)) v np.mean(np.sin(rad)) mean_dir np.rad2deg(np.arctan2(v, u)) return mean_dir % 3603.2 趋势检验Mann-Kendall 和线性回归怎么选做气象分析报告趋势分析是绕不开的。常见方法有两种线性回归求斜率或者 Mann-Kendall 非参数检验。两者适用场景不同。线性回归适合数据近似正态、没有明显自相关的情况输出的是「每年变化多少」这种直观的斜率。Mann-Kendall 不要求数据分布对异常值不敏感输出的是趋势是否显著适合检验气温、降水这类可能不满足正态假设的序列。from scipy import stats import pymannkendall as mk # 线性回归趋势 slope, intercept, r_value, p_value, std_err stats.linregress( range(len(annual_temp)), annual_temp ) print(f线性趋势: {slope:.3f} °C/年, p{p_value:.4f}) # Mann-Kendall 趋势检验 result mk.original_test(annual_temp) print(fMK检验: 趋势{result.trend}, p{result.p:.4f}, Sen斜率{result.slope:.3f})资源包里两个方法都封装了建议在报告里同时给出。如果两者结论一致可信度高如果不一致需要检查数据是否存在自相关或周期性问题。Sen 斜率是 MK 检验配套的斜率估计比线性回归斜率更稳健。注意做趋势分析前一定要确认时间序列的完整性。如果某几年缺测严重趋势结果会严重失真。资源包里有个check_completeness函数会输出每年的有效数据比例低于 80% 的年份建议在报告中标注。4. 可视化与报告生成让图表能直接放进实验报告4.1 气象常用图表的代码模板气象分析报告的图表有几类固定套路时间序列曲线、风玫瑰图、气温降水双轴图、空间分布热力图。资源包里每个都有模板改数据源就能用。时间序列曲线是最基础的但要注意多要素叠加时的坐标轴处理。比如气温和气压量级差很大放同一个 y 轴会有一条线被压平。正确做法是用双 y 轴或者归一化。import matplotlib.pyplot as plt import matplotlib.dates as mdates fig, ax1 plt.subplots(figsize(12, 5)) # 气温用左轴 ax1.plot(df.index, df[temp], colortab:red, label气温 (°C)) ax1.set_ylabel(气温 (°C), colortab:red) ax1.tick_params(axisy, labelcolortab:red) # 气压用右轴 ax2 ax1.twinx() ax2.plot(df.index, df[pressure], colortab:blue, label气压 (hPa)) ax2.set_ylabel(气压 (hPa), colortab:blue) ax2.tick_params(axisy, labelcolortab:blue) # 时间轴格式化 ax1.xaxis.set_major_formatter(mdates.DateFormatter(%m-%d)) ax1.xaxis.set_major_locator(mdates.DayLocator(interval5)) plt.setp(ax1.xaxis.get_majorticklabels(), rotation45) fig.tight_layout() plt.savefig(temp_pressure_timeseries.png, dpi300)风玫瑰图需要用到windrose库资源包里已经集成了。关键参数是风向分组数和风速分级阈值这两个参数直接影响图的可读性。分组太细图会碎太粗会丢失信息。一般风向分 16 个方位风速分 4-5 档。from windrose import WindroseAxes ax WindroseAxes.from_ax() ax.bar(df[wind_dir], df[wind_speed], normedTrue, opening0.8, edgecolorwhite, bins[0, 2, 4, 6, 8, 10]) ax.set_legend(title风速 (m/s), locbest) plt.savefig(windrose.png, dpi300)4.2 报告自动化从图表到 Markdown 的流水线资源包的报告生成模块用的是 Jinja2 模板引擎把统计结果和图表路径填入模板直接输出 Markdown 或 HTML。这样做的好处是每次更新数据后报告可以一键重新生成不用手动改数字。from jinja2 import Template report_template Template( # {{ station_name }} 气象数据分析报告 ## 数据概况 - 分析时段{{ start_date }} 至 {{ end_date }} - 有效数据比例{{ valid_ratio }}% ## 气温统计 - 平均气温{{ temp_mean }} °C - 最高气温{{ temp_max }} °C - 最低气温{{ temp_min }} °C - 趋势{{ temp_trend }} ## 图表   ) report report_template.render( station_name北京站, start_date2023-01-01, end_date2023-12-31, valid_ratio96.5, temp_mean13.2, temp_max41.1, temp_min-15.3, temp_trend0.35 °C/年 (p0.05), fig1_pathtemp_pressure_timeseries.png, fig2_pathwindrose.png ) with open(report.md, w, encodingutf-8) as f: f.write(report)这个模板可以根据具体实验报告的要求自由修改。比如课程实验报告要求有「数据与方法」章节直接在模板里加一段就行。资源包里提供了三个模板基础版、课程实验版、科研论文版区别在于章节结构和统计指标的详细程度。提示生成的 Markdown 里的图片路径是相对路径如果要把报告发给别人记得把图片一起打包或者用 base64 嵌入。5. 避坑与常见问题那些让我重跑过三次的坑5.1 时间戳时区问题导致日界错位现象计算日均值时发现某些天的数据被分到了前一天或后一天日统计结果对不上。原因原始数据的时间戳可能是 UTC也可能是本地时间但脚本里没有统一。更隐蔽的情况是数据跨了夏令时切换某些天有 23 或 25 个小时。解决拿到数据第一件事就是确认时间戳的时区。资源包里有个standardize_time函数统一转成 UTC8 并去除夏令时影响。如果数据源明确是本地时间直接tz_localize然后tz_convert到目标时区。5.2 风向数据 0° 和 360° 的边界处理现象风玫瑰图在正北方向出现一个异常缺口或者平均风向算出来是 180° 但实际应该是 0°。原因风向数据里 0° 和 360° 是同一个方向但数值上差了 360。直接做算术运算或者分组统计时这两个值会被分到不同的 bin。解决在做风向分组前先把 360° 转成 0°。资源包里的normalize_wind_dir函数做了这个处理。另外计算平均风向一定要用矢量法不要用算术平均。5.3 插值把降水缺测补成了虚假降水现象某个月降水量统计出来比历史同期高很多检查发现是缺测时段被插值补上了虚假的降水。原因降水量是间歇性变量大部分时间是 0缺测时用线性插值会补出中间值比如前后都是 0 但中间缺测插值可能补出 0.5mm 的虚假降水。解决降水量字段不做插值缺测保持 NaN。统计月累积降水时用sum(min_count1)而不是sum()这样如果全月都是 NaN结果也是 NaN 而不是 0。5.4 趋势分析忽略了自相关导致显著性虚高现象Mann-Kendall 检验显示趋势非常显著p0.01但把数据打乱后重新检验显著性依然很高。原因气象时间序列通常有自相关相邻年份的数据不独立。标准 MK 检验假设样本独立自相关会让 p 值偏小造成假显著。解决资源包里提供了修正版 MK 检验mk.hamed_rao_modification_test会考虑自相关的影响。如果修正后 p 值变大甚至不显著说明原始趋势的显著性被高估了。5.5 图表中文字体缺失导致乱码现象生成的图表里中文标签全部变成方框。原因matplotlib 默认字体不含中文字形。解决在绘图脚本开头设置中文字体。资源包里已经预置了字体配置代码但需要确认系统里有对应字体文件。Linux 服务器上常见做法是下载 SimHei 字体放到指定目录然后font_manager.fontManager.addfont()注册。import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Noto Sans CJK SC] plt.rcParams[axes.unicode_minus] False6. 进阶技巧用滚动统计和合成分析挖出更多信息基础统计和趋势检验做完之后如果想让报告更有深度可以加两个进阶分析滚动统计和合成分析。滚动统计就是用一个滑动窗口计算均值或方差看气象要素的波动特征。比如用 30 天滑动平均看气温的季内变化用 7 天滑动标准差看气温的稳定性。资源包里封装了rolling_stat函数支持自定义窗口和统计量。# 30天滑动平均气温 df[temp_30d_ma] df[temp].rolling(window30*24, min_periods20*24).mean() # 7天滑动标准差 df[temp_7d_std] df[temp].rolling(window7*24, min_periods5*24).std()min_periods参数很关键它控制窗口内最少需要多少个有效值才输出结果。设得太小窗口边缘的结果不可靠设得太大缺测多的时段会全是 NaN。我一般设成窗口长度的 70% 左右。合成分析适合做事件对比比如把所有降水日的气温序列提取出来求平均和所有非降水日对比。资源包里有个composite_analysis函数传入事件日期列表和变量名自动输出合成图和显著性检验结果。# 降水日和非降水日的气温合成对比 rainy_days df[df[precip] 0].index.normalize().unique() dry_days df[df[precip] 0].index.normalize().unique() composite composite_analysis(df, temp, rainy_days, dry_days) # 输出包含合成均值曲线和 t 检验 p 值这个分析在实验报告里很加分因为它不只是描述「气温是多少」而是回答了「降水对气温有什么影响」这种有因果意味的问题。做合成分析时要注意事件窗口的选择一般取事件前后各 5-10 天太短看不出变化太长会混入其他信号。还有一个容易被忽略的技巧把分析结果存成 NetCDF 或 Parquet 格式而不是 CSV。气象数据量大CSV 读写慢且占空间。Parquet 列式存储读取速度快一个量级而且保留数据类型。资源包里的save_processed函数默认输出 Parquet如果下游工具只认 CSV再单独导出一次就行。从那以后我每次拿到新的气象数据都强制先跑一遍check_completeness和standardize_time确认数据完整性和时间轴没问题再往下做。这两个步骤花不了五分钟但能省掉后面几小时的排查时间。希望帮到你。本文还有配套的精品资源点击获取