要生成一条24小时温度曲线如果你以为把传感器扔在那儿、隔天导个数据再连成一条线就完事那大概率会翻车。我曾经为了让一条曲线变顺眼折腾了整整两天最后发现问题根本不在画图而在采集端——传感器离热源太近整条曲线比真实室温偏高不说还把一个压根不存在的升温过程画得有模有样。所以这篇文章不打算只讲画图那几步。我想把一条24小时温度曲线从无到有走过的完整路径捋一遍先回答三个前置问题再讲传感器选型和部署然后是数据清洗最后才是画图和解读。适合刚接触环境监测、物联网采集或者想在服务器机柜、小型种植棚、恒温实验环境里做温度记录的朋友参考。文章里用到的代码都是实际跑过的最小版本你完全可以照着改。1. 动工之前先想清楚三个问题1.1 你要的是趋势还是依据这是第一个要分清的问题。同样是24小时温度曲线如果只是想知道屋里晚上冷不冷那随便一个温湿度计App都够用但如果这条曲线要用来做判断甚至存档比如证明某个恒温设备一整夜没超温或者追溯一批样品在某段时间的保存温度那对传感器的精度、采样频率和时间戳可靠性要求会完全不同。我自己吃过这个亏。一开始做某小型恒温箱的记录用的是随手找来的模拟温度探头没做任何校准画出来的曲线整体偏低1.5℃。拿去跟同事对数据时对方直接问了一句你的传感器什么时候校准过当场把我问住了。所以动手之前先回答这条曲线是用来辅助看的还是拿来当证据的如果是后者传感器需要定期校准时间戳需要使用统一时钟源原始数据必须原样留存。1.2 采样间隔要和用途匹配24小时温度曲线天然是时间序列数据采样间隔决定了你能看到多细的变化。一分钟采一次一天正好1440个点存成CSV也就几十KB非常轻松五秒采一次一天一万七千多个点数据量也不是问题但会给后续清洗和绘图增加噪声。温度是一个惯性很大的物理量不会像电压那样瞬间跳变普通室内环境下每秒钟测一次和每分钟测一次肉眼基本看不出差别。所以如果没有特殊需求一分钟间隔是我最常用的默认值。它既不会漏掉开门、空调启停这类分钟级事件也不会让曲线布满毛刺。需要提醒的是采集间隔和展示粒度是两回事。采集时用1分钟画图时完全可以重采样成5分钟或10分钟的平均值让曲线更平滑。反过来就不行——采集间隔太大事后再怎么插值也补不回真实变化。1.3 数据存成什么样决定了后面顺不顺我见过不少人把温度数据往TXT文件里一行行追加第一列是2025-01-15 23:58:12第二列是23.5看起来也没问题。但一旦你有多个传感器、需要跨天查询、或者想回看一周前的某个时段纯文本文件会变得越来越难用。对单机采集场景SQLite是最省心的选择。它就是一个文件不需要额外部署服务支持SQL查询Python和很多开发板都能直接读写。传感器数量再多一些、要长期运行的话可以考虑带时序能力的数据库但对大多数人来说SQLite已经完全够用。建表时我会额外留两个字段一个记录传感器编号一个留给备注。这个习惯帮我省过无数次回查的麻烦。表结构大致长这样CREATE TABLE temp_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, ts TEXT NOT NULL, sensor_id TEXT NOT NULL, temp_c REAL NOT NULL, note TEXT ); CREATE INDEX idx_ts ON temp_log(ts);时间戳我建议统一存UTC或者本地时间加时区偏移这个坑后面单独说。2. 数据源头传感器选型和部署位置2.1 常见温度传感器怎么选市面上的温度传感器五花八门但对只想做24小时温度曲线的人来说基本就在几类里面选。我把常见的列了个对照表类型接口精度采样速度特点数字单总线温度传感器1-Wire±0.5℃每秒最多约2次便宜、可选防水封装、接线简单但要留意总线时序温湿度一体传感器单总线/I²C±0.5℃左右较慢秒级顺带湿度但长期稳定性一般部分型号刷新很慢模拟热敏电阻ADC取决于电路和校准很快便宜但需要自己搭分压电路和查表标定高精度数字传感器I²C±0.1~0.2℃快适合做依据级记录价格也高一些做普通环境温度曲线我更常推数字单总线传感器。它直接输出数字信号不需要校准分压电路防水封装版本可以直接丢到户外、机柜、水箱边上抗干扰能力对低速采集来说足够。缺点是多片挂同一条总线时要处理好设备地址和供电这个后面实操里说。2.2 部署位置比传感器精度更影响曲线可信度这是最容易忽略的一步。传感器精度再高放错位置也是白搭。我踩过的典型错误包括把探头贴在机柜侧板内侧结果测的是外壳温度把探头挂在空调出风口正前方曲线跟着压缩机一跳一跳还有一次把探头放在窗边白天太阳晒到曲线凭空多出一个尖峰。正确做法是让探头处于你真正关心温度的那个空间区域并且远离热源、冷源和气流直吹。室内监测一般放在离地1米左右、靠近空间中心的位置户外或半户外需要加防辐射罩避免太阳直晒导致读数偏高好几度。对大面积房间一片传感器代表不了整个空间至少要按对角线布两三个点先画各自的曲线对比再决定要不要合成一个平均值曲线。2.3 一套最小可跑的采集脚本以常见的单板电脑加数字单总线传感器为例整个采集程序其实很短。核心逻辑就是定时读温度、追加进数据库。下面是我经常改用的一个最小版本import glob import time import sqlite3 from datetime import datetime, timezone SENSOR_BASE /sys/bus/w1/devices/ SENSOR_ID 28-xxxx # 换成你的传感器序列号 DB_PATH /home/pi/temp.db def read_temp(): path glob.glob(SENSOR_BASE SENSOR_ID /w1_slave)[0] with open(path, r) as f: lines f.read().splitlines() if YES not in lines[0]: raise RuntimeError(未完成转换) equals_pos lines[1].find(t) if equals_pos -1: raise RuntimeError(读不到温度值) return int(lines[1][equals_pos 2:]) / 1000.0 def save(ts, temp): conn sqlite3.connect(DB_PATH) conn.execute( INSERT INTO temp_log(ts, sensor_id, temp_c) VALUES (?,?,?), (ts, SENSOR_ID, temp), ) conn.commit() conn.close() while True: try: t read_temp() ts datetime.now(timezone.utc).isoformat() save(ts, t) print(ts, t) except Exception as e: print(read error:, e) time.sleep(60)这段代码里值得注意的细节每次读取失败都要catch住并继续下一轮而不是让程序崩溃时间戳用UTC的ISO格式这是为了后面的时区处理SQLite连接用完就关别看它简单长期跑下来比一直挂着连接更稳。3. 原始数据基本都是脏的清洗与对齐3.1 我见过的几类脏数据采集程序跑了一夜第二天打开数据文件里面什么情况都可能发生。我整理过自己踩过的几类时间戳重复程序重启时上一次的采样被重复写入数据缺失某段时间传感器没响应程序跳过了那一轮毛刺尖峰读数突然跳到30℃下一条又恢复正常恒定不变连着好几条都是同一个温度像一条平直线时间戳错乱跨天时时钟漂移或者程序日志里的时间来自系统时区。如果直接把这样的数据画出来轻则曲线难看重则会得出完全错误的结论。所以清洗这一步不是可选项是必选项。3.2 清洗三步解析、去重、重采样插值我习惯用pandas处理原因是它处理时间序列太方便了。核心代码如下import pandas as pd df pd.read_csv(temp_log.csv, parse_dates[ts]) df df.sort_values(ts) df df.drop_duplicates(subsetts, keeplast) df ( df.set_index(ts) .resample(1min) .mean() ) # 短时间缺口用线性插值超过30分钟的大缺口置为空 df[temp_c] df[temp_c].interpolate(methodtime, limit30)第一行解析时间戳第二步按时间排序并去重我保留了最后一次记录因为那一般是程序重启后最新写进去的值。第三步按分钟重采样把原本可能不规整的时间轴对齐成标准分钟序列。重采样本身会带来一个问题缺数据的分钟会被填成NaN所以最后一步再插值。这里我用的是按时间线性插值并且限制了最多连续插值30分钟。超过30分钟的大洞我宁可让曲线断开也不强行编造一段数据因为插值超过一定长度后画出来的东西就不再是测量值了。3.3 异常尖峰怎么识别异常点最难处理尤其是那种只持续一两个点的毛刺。我用的是一个非常朴素的方案算每个点的滚动中位数然后用原始值和滚动中位数的差来判断。df[median_5min] df[temp_c].rolling(window5, centerTrue, min_periods2).median() df[diff] (df[temp_c] - df[median_5min]).abs() df[is_outlier] df[diff] 1.5这个思路很简单一个正常的温度变化一分钟内的变化量通常不会超过零点几度如果某个点比前后五分钟的中位数高出1.5℃以上那多半不是真实温度突变而是读数异常。被标记为异常的样本我会直接置为NaN然后再走一遍插值。阈值选1.5℃是我根据室内和机柜场景调出来的不同场景建议先画一条原始曲线目测一下正常抖动幅度再定这个值。别一上来就用特别严格的阈值比如超过0.5℃就判异常那样会把正常的空调启停变化也误伤掉。3.4 时区这道坎越早处理越好采集程序记录的时间戳如果用的是本地时间一旦系统时钟被改过或者你所在地区有夏令时切换曲线会在某一天突然多出一小时或者少一小时。更隐蔽的问题是不同设备记录的时间基准不一样多传感器对不齐。我的处理习惯是存储一律用UTC展示的时候再转成本地时间。pandas里一行代码就能转df.index df.index.tz_convert(Asia/Shanghai)画图时用转换后的当地时间这样每天0点到24点的边界才是你直觉里的一天。如果直接把UTC当本地时间画你会发现每天的高温点都往右移了几个小时怎么排查都查不对。4. 画图让24小时曲线真正能看4.1 matplotlib静态图的基本功清洗完之后画图反而是最不费劲的一步但画得好不好看、能不能一眼看懂还是有一些讲究。我的基础模板大概这样import matplotlib.pyplot as plt import pandas as pd df pd.read_sql_query(SELECT ts, temp_c FROM temp_log WHERE date(ts)2025-06-01, conn) df[ts] pd.to_datetime(df[ts]) fig, ax plt.subplots(figsize(12, 5)) ax.plot(df[ts], df[temp_c], lw1.2, color#2a6fb6) ax.axhline(ydf[temp_c].max(), ls--, lw0.8, colorgray) ax.axhline(ydf[temp_c].min(), ls--, lw0.8, colorgray) # 标注最高最低点 t_max df.loc[df[temp_c].idxmax()] t_min df.loc[df[temp_c].idxmin()] ax.annotate(f{t_max[temp_c]:.1f}℃, xy(t_max[ts], t_max[temp_c]), xytext(8, 8), textcoordsoffset points) ax.annotate(f{t_min[temp_c]:.1f}℃, xy(t_min[ts], t_min[temp_c]), xytext(8, -14), textcoordsoffset points) # x轴从当天0点到24点 ax.set_xlim(pd.Timestamp(2025-06-01 00:00), pd.Timestamp(2025-06-02 00:00)) ax.set_xticks(pd.date_range(2025-06-01 00:00, 2025-06-02 00:00, freq4h)) ax.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter(%H:%M)) ax.grid(alpha0.25) ax.set_xlabel(时间) ax.set_ylabel(温度 (℃)) plt.tight_layout() plt.savefig(temp_24h.png, dpi150)有几个细节值得展开。首先明确x轴起始必须是本地时间的0点到24点而不是从第一条数据时间到采集结束那一刻。否则你会画出一条从上午11点跨到次日11点的曲线和24小时温度曲线的语义差很远。其次最高最低温度用虚线标出来同时把数值注在图上这样看曲线的人不需要自己去数刻度。最后x轴刻度用4小时间隔太密了会糊成一片。把整夜时段用浅色背景标出来是我后来加的一个小技巧。方法也简单加一行ax.axvspan(pd.Timestamp(2025-06-01 19:00), pd.Timestamp(2025-06-02 06:00), colorgray, alpha0.08)这样白天和夜晚的温差一眼就能看出来不用解释读者也能get到。4.2 需要交互时再上plotly静态图适合归档和截图发出去但同一批数据自己研究时我更愿意用交互图。鼠标移上去能看到具体时间点和温度值省去拿尺子对刻度的痛苦。最轻量的做法是import plotly.express as px fig px.line(df, xts, ytemp_c, title24小时温度曲线) fig.update_traces(line_shapehv) # 阶梯线适合观察突跳 fig.write_html(temp_24h.html)line_shapehv这个参数是我特别推荐的。默认的直线连接会脑补出两点之间的过渡而温度突变用阶梯状图形表达更诚实在采样时刻它就是这个值时刻之间发生了什么我们不知道。如果你用的是分钟级采样这个区别其实不大但如果你手头只有30分钟一条的数据阶梯线就是必需的了。4.3 多传感器曲线的摆放方式同时测了好几个点画图上就要小心。如果两个传感器的温度范围差不多直接画在同一张图上、用不同颜色区分、加图例就好。但如果一个在室外比如0~35℃一个在机柜里25~40℃温度范围差异太大同一坐标轴会让曲线细节被压缩。这种时候我一般选副坐标轴左边给室外温度右边给室内温度ax.plot(df_outdoor[ts], df_outdoor[temp_c], colorblue) ax2 ax.twinx() ax2.plot(df_indoor[ts], df_indoor[temp_c], colorred)画完记得给两条轴都加标签别学我把标签漏了第二天看回图都分不清哪条线是哪条。5. 曲线不只是好看它会告诉你温控系统的真实状态5.1 恒温控制的锯齿波该怎么读如果你的环境里有空调、恒温箱或者加热器曲线往往会呈现周期性锯齿状。这是温控器回差控制的自然结果温度低于下限加热器启动温度高于上限加热器关闭。锯齿的宽度直接反映了温控器的回差设置锯齿的斜率则反映了加热/制冷功率和热负荷的对比。有一次我在某个恒温箱上画出一条异常陡峭的锯齿曲线锯齿周期特别短频率极高。查了半天才发现是温控器参数被改成了很小的回差导致设备在启停边缘反复横跳。你不需要拆设备看代码从一条24小时温度曲线的锯齿形状就能推断出这么多。这就是画曲线这件事最有价值的地方它让看不见的控制行为变得可视。5.2 温度变化的迟到现象热惯性是理解曲线的关键概念。房间被太阳晒热室温并不是和太阳同拍上身的而是要滞后半小时到几小时取决于墙体和通风条件。对比室内外两条曲线时你能直观看出滞后量。这个滞后不是错误而是真实物理过程。但它会误导判断。如果你根据室内外温度差去做通风决策不考虑这个滞后很可能会在傍晚气温其实还算合适的时候因为室内还是热的就提前开了空调或者反过来。看曲线时我会刻意标注实际室外温度已经下降但室内温度仍维持高位的时间段这个区间在能量管理上非常关键。5.3 用曲线还原昨晚发生了什么一条看起来很奇怪的24小时温度曲线很多时候是在向你报案。比如凌晨2点突然出现一个持续15分钟的尖峰如果曲线旁边没有任何事件记录你只能猜。我也吃过这个亏后来养成了一个习惯凡是能想到的可能影响温度的事件都在采集程序里加一个备注。做法很简单在SQLite的note字段里写入文本比如02:00 保洁进机房、13:30 开门搬设备。有了这些备注再回头读曲线尖峰就能对上号。如果对不上号那才真的值得警惕。6. 我在实际测绘中踩过的坑写在这里免得你再踩6.1 先剔除异常点再算平均值这是新手最容易踩的坑。把一天1440个点直接平均得到的是一个被异常尖峰污染的数。哪怕全天只有一个错误读数比如某分钟读到95℃平均值就被抬高最高值、最低值更不用谈。我现在所有统计指标都是在清洗完的DataFrame上算的而不是在原始数据上算。6.2 采样间隔不是越密越好有一阵我为了曲线更精细把采样间隔改成了1秒。结果是单总线传感器频繁读取时偶尔会返回失败数据里多了一堆NaNCSV文件一天就多出一倍体积画图时三条曲线叠在一起密密麻麻反而看不清趋势。后来我回退到1分钟采样问题全消失。对室温、机柜、养殖棚这类场景1秒采样对解决实际问题毫无帮助只是在制造数据焦虑。6.3 警惕85℃这条幽灵曲线数字单总线传感器有个特征探头进水短路或接线异常时读数会稳稳停在85℃左右。我第一次遇到还以为是某个地方真的烧起来了跑到现场才发现是防水探头被蒸汽浸透内部短路。从那以后我在清洗脚本里加了一条硬规则如果连续三个以上采样点的读数都等于85.0并且和前后温度变化完全不连续直接把这些值置为NaN。机器上的异常特征是可以总结出模式的把这套模式写进清洗代码等于给数据加了一道自动安检。6.4 无论结果多稀奇保留原始数据最后一条也是我放在最高优先级的一条清洗过程永远生成新文件不要覆盖原始记录。我用的是固定的命名习惯——原始数据叫raw_20250601.csv清洗后的叫clean_20250601.csv。就算某天发现自己写错了清洗逻辑只要原始文件还在随时可以推倒重来。温度曲线项目的周期通常很短但数据的价值会留存很久。你在某一天凌晨画出来的那条曲线半年后可能就是判断设备故障的关键证据。多花10秒留一份原始副本是我能想到最划算的操作。最后补一句我自己坚持的小习惯每次跑完一轮24小时曲线我都会把当天的天气状况和现场发生过的事写在备注里存成一个单独的小文件。温度数据是死的天气和事件是活的把它们放在一起这条曲线才真正有了可追溯的生命力。下一次你再看到一条陡峭下降的曲线就不会只对着屏幕发呆而是能翻到备注知道那天下半夜开过窗。
阅读完成 · 觉得有帮助?