1. 监测站点数据读取与时间序列规整
任务拆解:从原始 CSV 到分钟级规整序列
环境监测站点的原始导出文件通常存在三类“时间轴问题”:时间列是字符串、采样间隔内存在缺失时间戳、同一时间戳因设备重传或人工补录出现重复记录。本案例用 pandas 完成时间列解析、缺失补齐与去重,并对比规整前后的记录数,得到可直接用于后续分析的分钟级时间序列。
读取与时间列解析
原始文件假设包含 timestamp、pm25、pm10、temperature 四列,时间格式为 YYYY-MM-DD HH:MM:SS。读取时先不做类型推断,随后用 pd.to_datetime 显式解析,并用 errors='coerce' 把无法解析的脏时间转为 NaT,便于后续统计与剔除。
import pandas as pd
raw = pd.read_csv("station_A_raw.csv", dtype={"timestamp": "string"})
raw["timestamp"] = pd.to_datetime(raw["timestamp"], errors="coerce")
n_invalid_time = raw["timestamp"].isna().sum()
raw = raw.dropna(subset=["timestamp"]).set_index("timestamp").sort_index()
print("无法解析的时间记录数:", n_invalid_time)
print("解析后记录数:", len(raw))
解析后必须按时间索引排序,否则后续 asfreq 补齐会因索引非单调而报错或产生错误结果。
重复时间戳去重
同一分钟出现多条记录,通常来自设备重传或人工补录。这里约定“保留最后一条”,即 keep='last',因为重传值一般比首次上报更完整。去重前先记录原始条数,去重后计算差值即为重复记录数。
n_raw = len(raw)
dedup = raw[~raw.index.duplicated(keep="last")]
n_dedup = len(dedup)
n_duplicate = n_raw - n_dedup
print("原始记录数:", n_raw)
print("去重后记录数:", n_dedup)
print("重复记录数:", n_duplicate)
缺失时间戳补齐
用 asfreq("1min") 生成从首条到末条记录的完整分钟轴,缺失分钟自动填充为 NaN。补齐后记录数应等于时间跨度内的分钟总数,可用 pd.date_range 交叉验证。
full = dedup.asfreq("1min")
n_full = len(full)
n_missing = n_full - n_dedup
expected = pd.date_range(dedup.index.min(), dedup.index.max(), freq="1min")
assert len(expected) == n_full, "补齐后长度与理论分钟数不一致"
print("补齐后记录数:", n_full)
print("缺失分钟数:", n_missing)
print("时间范围:", full.index.min(), "→", full.index.max())
规整前后记录数对比与输出
把三个关键数字汇总成一张对比表,并输出规整后的分钟级序列。缺失值保持 NaN 而不做插值,是为了让后续异常识别与统计能区分“真实缺失”和“测量值”,避免引入虚假数据。
summary = pd.DataFrame({
"阶段": ["原始", "去重后", "补齐后"],
"记录数": [n_raw, n_dedup, n_full],
})
summary["相对上一步变化"] = summary["记录数"].diff().fillna(0).astype(int)
print(summary.to_string(index=False))
full.to_csv("station_A_minute.csv")
print(full.head())
验证要点:
n_full 必须等于 pd.date_range 生成的分钟数;n_raw − n_dedup 应等于重复时间戳个数;n_full − n_dedup 应等于缺失分钟数。三者对不上时,优先检查时间解析是否产生了 NaT 被误删,以及索引是否已排序。若原始文件时间列混用本地时区与 UTC,直接解析会造成同一物理时刻出现两个不同时间戳,表现为“假重复”。本案例假设文件内时间已统一为同一时区;跨时区数据需先统一时区再进入上述流程。