如何基于单变量时间戳数据集实现Univariate Time series clustering
单变量时间序列聚类落地流程
1. 数据预处理
先把原始数据规整成算法能接收的格式,这步偷懒后面全是无效结果:
- 按
sample#字段分组,每个分组对应一条独立时间序列,组内按time-stamp升序排列。如果是固定间隔采样的场景,先补全所有缺失的时间点,缺失的观测值根据业务场景选线性插值、前向填充处理;如果序列本身不等长,要么统一截断/补值到相同长度,要么后续选支持不等长输入的距离度量。 - 做基础异常值过滤:用3σ原则或者滑动窗口中位数滤波去掉明显离群的尖峰值,避免极端值干扰距离计算。
- 按需做归一化:如果不同样本的观测值基线差异很大(比如不同传感器的读数偏移差几个量级),对每条序列单独做Z-score标准化,避免幅值大的样本主导距离计算;如果聚类需要保留原始幅值差异(比如要区分高负载、低负载的运行序列)就跳过这步。
2. 相似度度量选择
这步是决定聚类效果的核心,90%的无效聚类都是度量选错了,单变量时序常用的度量按优先级选择:
- DTW(动态时间规整)距离:通用场景首选,支持时间轴上的弹性对齐,对序列的时间平移、局部拉伸压缩鲁棒,不等长序列也能直接计算。计算时可以加10%序列长度的窗口约束,能把计算速度提好几倍。
- 特征向量距离:如果样本量很大、序列很长,DTW计算太慢,可以先给每条序列提取固定维度的特征:比如时域的均值、方差、峰值、自相关系数,频域的FFT主成分、小波分解系数,把时序转成普通特征向量之后再算欧氏距离就行,速度快但会损失部分时序形状细节。
- Shapelet距离:如果你的聚类目标是识别具有相似局部形状的序列(比如都包含同形态的故障脉冲),可以选Shapelet度量,可解释性强但计算成本高,适合小样本场景。
- 直接用欧氏距离:仅适合长度完全一致、时间轴完全对齐、没有任何偏移的序列,大部分场景效果很差,不推荐优先尝试。
3. 聚类算法选型
根据数据规模和是否已知聚类数选择即可:
- 层次聚类(AGNES):样本量小于1000时优先选,不需要初始化质心,直接喂提前算好的DTW距离矩阵就能跑,可以通过树状图直观选聚类数,链接规则选
average或者ward效果最稳。 - K-Shape/DTW-KMeans:专门针对时序优化的K-Means变种,中等规模样本(几千条)用这个速度快,需要提前指定聚类数,比普通K-Means对时序的适配性好很多。
- DBSCAN:如果你不知道要聚成几类、数据里混有异常离群序列,就搭配DTW距离用DBSCAN,能自动筛掉噪声样本、自动确定簇数,缺点是需要调邻域半径
eps和最小样本数min_samples两个参数。
注意:别直接拿原始时序喂普通K-Means,它默认用欧氏距离计算相似度,对时序的时间偏移极其敏感,出来的簇基本没有业务意义。
4. 聚类数确定与结果校验
- 用需要指定聚类数的算法时,不要随意拍k值,遍历2到业务上能接受的最大簇数,算每个k对应的轮廓系数(越接近1说明簇内越紧凑、簇间差异越大)、邓恩指数(值越大效果越好),选指标拐点对应的k值就行。
- 别只信数值指标,聚完一定要把同一个簇的所有序列叠到同一张折线图上看,确认同簇序列的趋势、形状确实符合业务逻辑,很多时候数值指标好看,但聚出来的结果完全不符合业务常识。
5. 最简实现参考(Python栈)
import pandas as pd import numpy as np from tslearn.utils import to_time_series_dataset from tslearn.preprocessing import TimeSeriesScalerMeanVariance from tslearn.clustering import TimeSeriesKMeans, silhouette_score from scipy.spatial.distance import squareform from scipy.cluster.hierarchy import linkage, fcluster from fastdtw import fastdtw # 读取并规整数据 df = pd.read_csv("your_dataset.csv") series_collection = [] valid_sample_ids = [] for sample_id, group in df.groupby("sample#"): # 组内按时间戳排序 group_sorted = group.sort_values("time-stamp") # 这里可以自行补全缺失时间点、填充缺失值的逻辑 obs_values = group_sorted["single_obs"].values series_collection.append(obs_values.reshape(-1, 1)) valid_sample_ids.append(sample_id) # 转成时序算法要求的数组格式,自动补零对齐不等长序列 X = to_time_series_dataset(series_collection) # 按需做单序列标准化 scaler = TimeSeriesScalerMeanVariance() X_scaled = scaler.fit_transform(X) # 方案1:小样本场景 DTW+层次聚类 sample_count = len(X_scaled) dtw_dist_mat = np.zeros((sample_count, sample_count)) for i in range(sample_count): for j in range(i+1, sample_count): d, _ = fastdtw(X_scaled[i].flatten(), X_scaled[j].flatten(), radius=10) # radius是DTW的窗口约束 dtw_dist_mat[i,j] = d dtw_dist_mat[j,i] = d # 层次聚类,t设为你选的聚类数 linkage_matrix = linkage(squareform(dtw_dist_mat), method="average") cluster_labels = fcluster(linkage_matrix, t=3, criterion="maxclust") # 方案2:中大规模样本 DTW-KMeans # 先遍历选最优k for k in range(2, 7): model = TimeSeriesKMeans(n_clusters=k, metric="dtw", random_state=42) labels = model.fit_predict(X_scaled) s_score = silhouette_score(X_scaled, labels, metric="dtw") print(f"聚类数为{k}时的轮廓系数:{round(s_score,3)}") # 选最优k训练最终模型 final_model = TimeSeriesKMeans(n_clusters=3, metric="dtw", random_state=42) final_labels = final_model.fit_predict(X_scaled)
常见踩坑点
- 不要上来就用复杂的深度学习聚类模型,先拿DTW+层次聚类/K-Shape跑通基线,90%的场景下传统方法效果比随便搭的深度模型好,调参成本还低。
- 不要不做序列对齐、缺失值处理就直接跑算法,出来的结果没有任何参考价值。
- 聚类结果最终要服务业务,不要纯看数学指标选结果,比如做设备故障聚类,同个簇的序列要对应同一种故障类型才是有效结果。
内容的提问来源于stack exchange,提问作者Farooq
相关产品推荐
相关产品推荐

