简介:这份使用Python进行气象数据分析与可视化的完整可运行项目源码,面向高校数据科学、气象课程期末大作业,也适合想通过实际案例入门数据分析的Python学习者。项目完整覆盖数据采集、清洗、特征处理、模型训练与天气可视化展示,代码按GetData、ProcessData、GetModel、main等模块组织,配有文档说明;训练集、测试集、验证集及中国天气数据等CSV文件划分清晰,HTML页面和JPG图表可直接查看预测与可视化结果,方便对照理解完整分析流程,并参考其数据划分与模型保存方式。压缩包共22个文件,包含4个py脚本、4个csv数据集、12张jpg结果图、1个html页面及1个pkl模型文件,整体仅1.42MB,轻量易部署。项目评审分在95分以上,经过严格调试可正常运行,已有509人学习下载,适合需要快速掌握气象分析全流程、借鉴期末大作业设计思路并复用源码的读者。
1. 气象数据分析与可视化:这份高分项目包先认清再动手
一份标题为“使用Python进行气象数据分析与可视化源码+文档说明(高分项目)”,打开的瞬间最容易踩的坑是直奔源码去跑train.py,结果缺依赖、报编码错误,折腾半小时还没看到一张图。实际上这套项目包的核心不是某个模型,而是一条完整的气象数据分析链路:把气象站的温度、降水、风速等原始观测数据读进来,用pandas完成清洗和归并,再用matplotlib、seaborn等库把趋势和相关性画出来,最后配合文档讲清楚结论。它适合三类人:拿它当课程设计参考的学生,刚接触气象数据、想熟悉处理流程的开发者,以及需要快速搭建内部气象报表的从业者。下面按我实际做这类项目的顺序,把每个环节怎么落地、参数怎么调、坑在哪里拆开讲。
2. 从CSV到DataFrame:气象数据清洗与站点归并的五个关键步骤
气象数据分析与可视化项目里,最花时间的不是画图,而是把原始观测文件整理成一份干净、按时间排序的表格。高分项目之所以能拿高分,往往不是因为图表画得花哨,而是清洗逻辑经得起复查。常见做法是先用pandas读入,然后做五步处理:识别字段类型、时间解析与时区归一化、阈值过滤异常值、多站点合并去重、重采样到月均值。每步做错的代价会在后续分析阶段成倍放大,所以不建议直接跳去画图。
2.1 先看字段类型和分隔符,再决定怎么读文件
拿到数据不要急着跑代码。先打开数据文件前5行,确认是纯文本、CSV还是Excel导出文件,再看表头单位。气象数据的CSV经常混用逗号和分号,某些站点甚至用小数点逗号表示小数,这一步错了后面全是乱码。我一般会写一个小型的探查脚本,先确认分隔符和编码,避免read_csv读出一堆NaN。编码方面,常见的有UTF-8和GBK,读文件时给出errors="replace"能在编码出错时保留有用部分而不是直接中断。
from pathlib import Path import pandas as pd fpath = Path("data/weather_station_raw.csv") with fpath.open(encoding="utf-8", errors="replace") as f: for i, line in enumerate(f): if i >= 5: break print(repr(line))这段代码用Python原生方式打印前5行,解决“表头里有隐藏字符却不知道”的问题。repr会把换行符、制表符都显示出来,如果看到文件里实际是分号分隔,而代码里写的是逗号,pd.read_csv会静默地把所有字段读成一列,等到做日期解析时才报错。探查完之后,再按实际情况读入:
df_raw = pd.read_csv( fpath, sep=",", # 如果探查到分号,改成 ";" engine="python", # 文件不规则时用 python 引擎 na_values=["", "NaN", "9999", "9999.0"], # 缺测标记 low_memory=False, dtype={"station_id": "str"}, # 站点ID固定按字符串读 )参数里面,na_values一定要按源数据文档填,很多气象站会用9999、-9999表示缺测,不把这两个值标记成NaN的话,后面求均值时会得到很离谱的数值。engine参数只在分隔符是多个字符或者文件不规则时再切到python,默认的c引擎更快,但遇到转义容易报错。dtype把station_id声明为str是最关键的一步,否则01001这种编号会被读成1001,后面做站点归并时直接错位。
2.2 时间解析与时区归一化:把“8点”变成标准时刻
解析时间和时区归一是气象数据分析与可视化中最容易翻车的第一步。站点记录的时间有两种情况,一种是不带时区的本地时间,另一种是UTC标准时间,项目文档里没写清楚时,必须靠原始数据说明确认。统一做法是全部转成UTC时间再存一列本地时间,这样后面做日平均、月平均的时候不会被夏令时弄乱。如果数据里只有日期没有小时,那就只解析日期,不要擅自补00:00。
df_raw["datetime"] = pd.to_datetime( df_raw["date"] + " " + df_raw["hour"].astype(str), format="%Y-%m-%d %H", errors="coerce", ) # 先把无时区时间标注为站点当地时区,再转 UTC df_raw["datetime_utc"] = ( df_raw["datetime"] .dt.tz_localize("Asia/Shanghai") .dt.tz_convert("UTC") ) df_raw["year"] = df_raw["datetime"].dt.year df_raw["month"] = df_raw["datetime"].dt.month df_raw["day"] = df_raw["datetime"].dt.day这里有三处要特别说明。第一,errors="coerce"会把解析失败的时间变成NaT,如果NaT比例超过5%,要回头查日期格式,而不是继续往下跑。第二,tz_localize和tz_convert不能搞反,前者是给无时区时间贴标签,后者才是换算,顺序错了会得到偏移8小时的数据。第三,时间列名不要用中文,后面和绘图库打交道时字段名越简单越好。还有一个细节是hour列可能有“24”这个值,某些站点用24:00表示第二天0点,pd.to_datetime不认识,需要先替换成0并给日期加一天,否则整行记录会解析失败。
2.3 缺测值和异常值的阈值识别:先讲物理再讲统计
气象数据的异常值往往不是设备坏了,而是传感器在特定天气下进入保护状态,比如温度传感器的量程是-40到60摄氏度,一旦超过这个范围就会输出边界值。处理思路是两层:缺测值直接置NaN,异常值按物理常识判断后也置NaN,而不是删除整行。删除整行会连带丢掉同一时刻其他变量的有效观测,在后续多变量分析里造成不必要的样本损失。
import numpy as np df = df_raw.copy() # 温度范围检查:不超过物理极限就保留 df.loc[(df["temp"] > 60) | (df["temp"] < -60), "temp"] = np.nan # 降水范围检查:降水量不可能为负数 df.loc[df["precip"] < 0, "precip"] = np.nan # 风速范围检查:常规站点超过 80 m/s 基本是故障 df.loc[df["wind_speed"] > 80, "wind_speed"] = np.nan # 分月统计缺失率,而不是只看整体 miss = df.groupby(df["month"])[["temp", "precip", "wind_speed"]].apply( lambda x: x.isna().mean() ) print(miss.round(3))这里走的是物理范围过滤,不是3倍标准差过滤。做气象数据分析与可视化时,如果直接按统计方法去异常,会把极端台风过程当成异常值删掉,导致后续趋势分析偏保守。物理阈值需要从数据说明书里抄,说明书没写就按传感器型号查量程。分月统计缺失率比看整体缺失率有用得多,因为缺测往往集中在冬季,整体缺失率5%看起来不高,但1月可能到了20%,这部分数据参与回归会扭曲趋势。
2.4 站点归并:多站数据不要直接纵向拼接
高分项目里经常出现多个站点文件,很多初学者直接pd.concat一把梭,结果站点编号列变成object类型,或者同名站点被算了两遍。常见的归并做法是先统一列名,再以站点编号和时间共同组成唯一键。
dfs = [] for f in station_files: _df = pd.read_csv(f, dtype={"station_id": "str"}) _df = _df.rename(columns={ "Stn": "station_id", "Date": "date_str", "Time": "hour", }) dfs.append(_df) df_all = pd.concat(dfs, ignore_index=True) df_all = df_all.drop_duplicates(subset=["station_id", "date_str", "hour"])drop_duplicates这一步很重要,因为同一文件在不同存储路径下可能被重复读入。subset参数指定的是“一个站点、一个时间点只能有一条记录”的规则,而不是把所有列完全相同的行去重,否则两个站点在温度上恰好相同就会被误删。归并之后,还要做一次交叉验证:用groupby检查每个站点的记录数量,如果某个站点缺失月份成片,就要回到源文件确认是否漏了文件。另一个常见验证是检查站点的经纬度是否在合理范围内,经纬度字段常常被Excel自动转成了科学计数法,读取时要当字符串处理,否则后续画站点分布图时会出现坐标漂移。
2.5 重采样到月均值:消除日期权重不均的影响
站点观测频率可能是小时级,直接对小时数据做长期趋势分析会引入日内波动和不等权重。常见做法是把数据重采样成月均值,同时保留每月样本量,这样后续绘图和统计检验都更稳定。这里我用datetime_utc作为索引:
df_monthly = ( df_all.set_index("datetime_utc") .resample("ME") .agg({ "temp": "mean", "precip": "sum", "wind_speed": "mean", "station_id": "count", }) )resample("ME")表示月末频率,旧版本写法是"M",新版如果传"M"会报警告。聚合字典里,temp和wind_speed用均值,precip用月累计值,station_id用count来统计这个月有多少条观测。月均值序列做趋势分析时,样本量过少的月份要剔掉,一般阈值是一个月少于20天有效观测就不参与趋势计算。这一层处理到位,后面的统计检验才不会因为某个月只有三天数据而算出虚假的突变。
3. 用Python做气象趋势分析的三个核心统计量
数据洗干净之后,才进入气象数据分析与可视化这个标题里更核心的“分析”部分。趋势分析不要只画一条折线然后目测,要有统计量支撑。常见的做法是:先算滑动平均看波动,再用Sen斜率估计趋势强度,最后用Mann-Kendall检验判断趋势是否显著。这三个统计量组合,是气象论文和课程设计里最常出现的配套。
3.1 滑动平均与气候态基线:先去掉季节循环
温度数据最明显的特征是冬天低夏天高,直接对原序列回归时,季节波动会淹没长期趋势。去掉季节循环的常用做法是用“距平”序列,也就是每天的值减去同一天多年的平均值。另一条路是直接做30天滑动平均,平滑掉日尺度噪音。两种做法可以同时用:先做滑动平均做展示,再做距平做统计检验。
df_daily = df_all.groupby("date_str")["temp"].mean().reset_index() df_daily = df_daily.sort_values("date_str") # 30天滑动平均,min_periods 控制两端缺失 df_daily["temp_avg30"] = df_daily["temp"].rolling( window=30, center=True, min_periods=15, ).mean() # 以365天窗口做气候态基线,计算距平 df_daily["anomaly"] = ( df_daily["temp"] - df_daily["temp"].rolling( window=365, center=True, min_periods=180, ).mean() )注意这里,rolling窗口里min_periods不要省。缺测较多的站点,如果min_periods设得和window一样大,整段结果都会有大量NaN,后面画图时曲线会断成很多截。365天窗口的距平计算,会让每个点都减去“过去一年滑动平均状态”,季节循环基本被削掉,剩下的就是偏离常年状态的波动。center=True让滑动平均值落在窗口中心,画图时不会看到曲线整体向右偏移。如果项目数据跨度不足两年,365天窗口会缺掉开头和结尾各半年,这种情况下改用“逐月气候态”更合适:先按月算多年平均,再用原始值减月平均。
3.2 Sen斜率估计:抗异常值的趋势强度
线性回归的斜率对异常值非常敏感,气象数据里一次传感器故障或极端事件就能把拟合线拉偏。Sen斜率估计取所有数据对之间斜率的中位数,比最小二乘稳定得多。scipy内置了现成实现,直接用就行。
from scipy import stats y = df_daily["temp_avg30"].dropna().values x = np.arange(len(y)) sen_result = stats.mstats.theilslopes(y, x=x, alpha=0.95) sen_slope, intercept, lo_slope, hi_slope = sen_result print(f"Sen斜率: {sen_slope:.4f} °C/天") print(f"95% 置信区间: [{lo_slope:.4f}, {hi_slope:.4f}]")参数说明里有几个容易忽略的点。第一,x必须填等间隔的数值索引,如果把日期的字符串传进去,排序就会出错。第二,alpha=0.95表示置信区间覆盖率为95%,区间如果包含0,说明趋势在统计上不显著。第三,theilslopes返回顺序是斜率、截距、区间下限、区间上限,写代码时不要只取第一个值然后丢掉区间,否则后面写文档时没有依据。Sen斜率的单位需要换算。如果按天算得到的是“摄氏度每天”,在报告里通常换算成“摄氏度每十年”,只需要乘3650。换算后的数通常很小,比如0.18摄氏度每十年,这才是气象报告里常见的表达方式。
3.3 Mann-Kendall检验:趋势是否显著的正式说法
Sen斜率只说增长或下降的强度,不回答“这个趋势是不是随机波动造成的”。Mann-Kendall检验是气象和气候研究里最常见的非参数检验,它只关心序列是单调上升还是下降,不要求数据正态分布。用pymannkendall这个库可以一行算出多个指标,也可以自己实现核验。
import pymannkendall as mk daily_clean = df_daily["temp_avg30"].dropna() result = mk.original_test(daily_clean, alpha=0.05) print("trend:", result.trend) print("p-value:", result.p) print("tau:", result.Tau) print("slope:", result.slope)result的几个属性要会用。trend表示趋势方向,是increasing、decreasing或no trend三种;p小于设定的alpha时,才能说趋势在统计上显著;Tau是秩相关系数,值接近1说明单调性很强;特别注意result.slope是Sen斜率,和前面用scipy算出来的应该是一致的,如果两个结果不一致,先检查输入序列是否缺测过多。这个库的输入只接受一维序列,索引乱序没关系,它会自己处理顺序。如果原始数据是月均值且季节性强,不要直接用original_test,换成mk.seasonal_test(),它把数据按月份分组后检验,避免春夏秋冬的规则波动被误判成趋势。
3.4 趋势结果的三方交叉验证:别只信一个函数
计算完三个统计量之后,还要做一次信息核对。常见的做法是做一个表,把原始序列日距平、月距平、季节平均三套数据分别跑一遍MK检验,如果三套结果方向不一致,说明异常值或缺测填充方式在影响结论。特别是夏季平均和冬季平均,很多时候冬季升温显著而夏季不显著,这在气象上是有意义的现象,直接合并成年平均会丢掉这个信息。我在做这类项目时,会额外输出每个季节的Sen斜率表格,而不是只在文档里写一个总体斜率。这样既方便检查,也让项目的分析深度明显提升。
4. 选择正确的气象图:三种高频图表与参数校准
分析统计量算完,接下来是把结果变成可视化。标题里的“可视化”三个字,很多时候决定读者第一印象。这里不是一张图走天下,而是按数据特征选图:温度用折线加距平柱状,风场用风玫瑰,多站相关性用热力图。每张图都要调坐标轴和配色,否则在报告里打印出来会糊成一团。
4.1 温度趋势折线图:坐标轴、配色与刻度间距
温度图最容易被扣分的点是Y轴起点不标数据范围,或者是两条曲线叠在一起但颜色区分度太低。定好画布大小后,先把Y轴范围限制在数据上下界附近,再画原始距平与滑动平均。
import matplotlib.pyplot as plt import matplotlib.dates as mdates fig, ax = plt.subplots(figsize=(12, 5)) ax.plot(df_daily["date_str"], df_daily["anomaly"], color="#cccccc", linewidth=0.8, label="日距平") ax.plot(df_daily["date_str"], df_daily["temp_avg30"], color="#c0392b", linewidth=2, label="30天滑动平均") ax.axhline(0, color="black", linewidth=0.6) ax.xaxis.set_major_locator(mdates.YearLocator(2)) ax.xaxis.set_major_formatter(mdates.DateFormatter("%Y")) ax.set_ylabel("温度距平 (°C)") ax.legend(frameon=False) fig.tight_layout() fig.savefig("output/temp_anomaly.png", dpi=200)legend里frameon=False去掉边框,让图像更干净。YearLocator(2)表示每两年一个主刻度,如果数据跨度只有五年,要改成YearLocator(1),否则横坐标会显得很空。dpi设成200已经满足大多数课程设计和报告打印需求,不需要更高。颜色选择上,灰色表示原始数据,深红表示平滑曲线,即使打印成黑白文档,灰度差异也能让两条线区分。ax.axhline画在两条线之后,确保0基准线不会被线覆盖。
4.2 风玫瑰与降水的联合展示
风速风向数据的可视化常用风玫瑰图,matplotlib没有直接内置,常见做法是用windrose库或者自己用极坐标实现。课程设计里用库能省很多时间,但库更新慢,旧环境可能装不上,所以这里讲自制方式。
import numpy as np df_wind = df_all[["wind_dir", "wind_speed"]].dropna() df_wind["dir_bin"] = pd.cut( df_wind["wind_dir"], bins=np.arange(0, 361, 45), labels=["N", "NE", "E", "SE", "S", "SW", "W", "NW"], ) ax = plt.subplot(projection="polar") counts = df_wind.groupby("dir_bin", observed=True).size() angles = np.linspace(0, 2 * np.pi, len(counts), endpoint=False) bars = ax.bar(angles, counts, width=2 * np.pi / len(counts)) ax.set_theta_zero_location("N") ax.set_theta_direction(-1)这段代码的关键在风向的语义。很多初学者直接把数据里的风向当数学里的角度画,结果北风跑到了右边。气象上的风向是指风的来向,北风角度是0度,东风是90度,所以要set_theta_zero_location("N")让0度在正上方,set_theta_direction(-1)让角度顺时针增加。pd.cut的bins设计成45度一组,labels要按顺序写,否则风向标签会错位。如果还想叠加风速信息,可以在每个方向上按风速分成三档分别着色,但那样画法会更容易让图例拥挤,我通常只在课程设计里保留单色版本。
4.3 多站点相关矩阵热力图:颜色映射的边界
如果项目包含多个站点的数据,画一个站点间温度相关矩阵是加分项。这里最容易犯的错是直接对包含NaN的DataFrame求corr,矩阵里出现大量空白。处理方式是先用dropna对齐时间索引,再计算相关性。
import seaborn as sns pivot_df = df_all.pivot_table( index="date_str", columns="station_id", values="temp", aggfunc="mean", ) clean_pivot = pivot_df.dropna(thresh=len(pivot_df) * 0.8) corr_matrix = clean_pivot.corr() fig, ax = plt.subplots(figsize=(8, 7)) sns.heatmap( corr_matrix, annot=True, fmt=".2f", cmap="RdBu_r", vmin=-1, vmax=1, cbar_kws={"label": "相关系数"}, )先pivot_table把长表变成宽表,dropna时thresh参数控制只保留有效观测超过80%的行列,相关矩阵不会因为个别站点缺测而全是NaN。cmap选RdBu_r,红色代表正相关,蓝色代表负相关,vmin和vmax固定为-1和1,避免相关性接近1时颜色饱和过度。如果打印成黑白,annot=True的数字还能保留信息,所以不要去掉这个参数。cbar_kws里加一个label,说明色标含义,这是评审时经常被问到的细节。
4.4 组合图布局:一张报告页放下关键结论
课程设计通常需要把多张图组织到一起,常见做法是用GridSpec划分画布,上排放温度趋势和降水累计,下排放风玫瑰和相关矩阵。布局时把风玫瑰放在极坐标子图里,需要注意subplot的投影参数只能在创建时指定,不能在已经创建的子图上临时改。多图组合的另一个问题是字体大小,报告打印时小字号在A4纸上会看不清,坐标轴刻度字号建议至少10号。我不会在一张图里塞超过四个子图,子图越多,读者越难抓住重点。
5. 常见问题与注意点:气象项目里5个容易丢分的坑
气象数据分析与可视化这个方向里,踩坑记录比技术本身更值钱。下面这五个问题是我在类似项目里反复遇到的,每条按现象、原因、解决来写,可以直接对照检查。
5.1 日平均气温偏移半个季节
现象:算出来的日平均气温序列比真实气候态偏高,冬季尤其明显。
原因:站点原始数据用的是当地时区,但代码里没有做时区标注,直接当UTC时间用,把当地时间当成了UTC,导致凌晨0点的数据被归到前一天,日平均值的采样时段发生偏移。
解决:解析出时间后,先确认站点时区。如果数据文件里注明Local Standard Time,就用tz_localize加上时区名,再转成UTC存备份列。最稳妥的做法是做一次抽样校验:手动取某一天,把当天24小时均值与相邻两天的均值做比较,如果出现不连续跳变,说明时区处理反了。
5.2 某一列缺测率超过20%但仍然进了回归
现象:趋势分析结果显示显著升温,但换一种缺测填充方式,结果就反过来。
原因:缺测集中在特定季节,比如冬季传感器结冰后长时间不恢复。直接用插值补全后,冬季数据全是插值拟合出来的假值,回归时这部分假值会放大趋势。
解决:填充前先统计每个月的缺测率,按月输出缺失日历。如果某个季节缺测率超过20%,后续分析只使用完整月份,或者把该站点标记为不可作趋势分析。不要用全局中位数填充,它会抹掉季节信号。要填充也只按同月份中位数填充,并打上标记列。
5.3 跨年边界处滑动平均曲线断开
现象:1月1日附近的滑动平均曲线突然消失,形成一个V形缺口。
原因:rolling窗口默认要求窗口内所有值都非NaN,跨年时数据可能因为节假日文件缺失而不足。min_periods参数没有设置,或者设置成与window相同。
解决:滚动窗口设置min_periods为窗口的60%,比如30天窗口设min_periods=18。这会保留边界附近的有效平滑值,同时避免只有三五个点也强行平滑。要说明的是,滑动平均的缺口用插值补在图上,看起来像原始数据,会误导读者,所以我不建议对平滑曲线做插值,保留缺口反而更诚实。
5.4 站点ID被当成数字读入后丢失前导零
现象:图例里站点编号从01001变成1001,或者合并后同一站点出现了两种类型。
原因:CSV里的站点ID列被pandas自动推断为int64类型,前导零被丢掉。气象站点ID一般是字符串,特别是在把多个文件合并时,不同类型ID还会引发类型错误。
解决:读取时直接指定dtype参数。
df = pd.read_csv(fpath, dtype={"station_id": "str", "wind_dir": "float"})5.5 可视化中文乱码
现象:图表的标题和坐标轴中文全部变成方框。
原因:matplotlib默认字体不包含中文字形,而且不同操作系统可用的字体路径不一样,同一份代码在Windows和Linux下效果不同。
解决:在绘图脚本开始时设置字体。
import matplotlib matplotlib.rcParams["font.sans-serif"] = [ "SimHei", "Noto Sans CJK SC", "Arial Unicode MS" ] matplotlib.rcParams["axes.unicode_minus"] = Falsefont.sans-serif里列了多个字体名,是因为不同系统的字体名称不同,matplotlib会按顺序找第一个存在的字体。axes.unicode_minus设为False解决的是负号变成方块的问题,负号的Unicode和普通连字符在不同字体里表现不一样。设置完后,再用plt.rcParams["font.family"] = "sans-serif"兜底。检查是否生效时,不用重新跑全部代码,画一个最简单的plt.title("测试")就能看出来。
6. 把气象分析包装成可复用模板:三种进阶做法
当课程设计或者一次业务分析已经跑通,下一步是把整套流程变成下次能直接复用的模板。我自己的习惯是:硬编码路径滚出战,配置文件管变量,封装函数但不过度封装。下面三个做法按从轻到重的顺序介绍。
6.1 用yaml配置文件替换硬编码路径
把站点列表、时间范围、阈值参数从代码里提出来,放到一个config.yaml里。下次换数据源时不用动代码,只改配置。
data: station_files: - data/station_a.csv - data/station_b.csv encoding: utf-8 sep: "," na_values: ["9999", "-9999"] temp_range: [-60, 60] wind_max: 80.0 output: dir: output dpi: 200 language: zh在代码里用PyYAML读取后,所有阈值都从config引用。这个做法能把逻辑和参数分离,回头检查时不需要从代码里翻阈值。注意language设置也会影响字体,如果语言设定成zh,脚本里就自动启用中文字体,否则使用默认字体。
6.2 命令行入口与输出目录规范化
给脚本加一个argparse入口,让模板可以通过命令行传参控制输入文件与输出目录。我一般会加两个参数,--config指定配置文件,--output覆盖输出目录,方便批量跑多个站点时按日期建子目录。
import argparse def parse_args(): parser = argparse.ArgumentParser(description="气象数据分析与可视化模板") parser.add_argument("--config", type=str, default="config.yaml") parser.add_argument("--output", type=str, default="output") return parser.parse_args() if __name__ == "__main__": args = parse_args() run(args.config, args.output)这里不要直接读sys.argv,argparse会自动处理帮助信息和错误提示。output参数允许覆盖配置文件里的默认目录,比如output/20240528/,这样不同批次的图不会互相覆盖。
6.3 生成带内嵌图片的HTML报告
如果希望结果能分享给不懂代码的人,把图片和表格嵌入一个HTML报告是更直观的交付方式。用pandas的to_html生成表格片段,再拼接到模板里;图片用base64内嵌,这样整个报告只有一个文件,发邮件不会丢图。
import base64 from pathlib import Path def image_to_base64(path: Path) -> str: return base64.b64encode(path.read_bytes()).decode("utf-8") img_b64 = image_to_base64(Path("output/temp_anomaly.png")) html_content = f""" <html> <body> <h1>气象趋势分析报告</h1> <img src="data:image/png;base64,{img_b64}" /> </body> </html> """ Path("output/report.html").write_text(html_content, encoding="utf-8")base64内嵌后图片体积会膨胀三分之一左右,所以报告里只放关键的三张图:趋势图、风玫瑰和相关矩阵,其他图放到output目录里供人自行查看。做了几轮这样的模板之后,我最大的教训是:把时间留给数据质量检查,而不是画图调颜色。图表再好看,数据里的时区错误和缺测处理不对,最后都会被一句话问穿。先跑通最小案例,再逐步加功能,希望帮到你。
本文还有配套的精品资源,点击获取