如何用Python实现多时间序列聚类?含Sklearn与Tslearn方案
问题描述
需求
将10家门店(实际场景为150+门店、20年数据)基于2021-2022年的points、assists、rebounds多维数据分为6个聚类。
尝试情况
使用sklearn.cluster.KMeans仅能处理单周期数据,接触到tslearn的TimeSeriesKMeans结合动态时间规整(DTW),但对数据重构和预处理步骤存在疑问。
具体问题
- 如何使用
sklearn.cluster.KMeans实现时间序列聚类? - 使用
tslearn.clustering.TimeSeriesKMeans前,正确的数据结构是什么?
测试数据代码
import pandas as pd import numpy as np import matplotlib.pyplot as plt from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler from tslearn.clustering import TimeSeriesKMeans df_full = pd.DataFrame({'year':[2021,2021,2021,2021,2021,2021,2021,2021,2021,2021, 2022,2022,2022,2022,2022,2022,2022,2022,2022,2022], 'store':['store1','store2','store3','store4','store5','store6','store7','store8','store9','store10', 'store1','store2','store3','store4','store5','store6','store7','store8','store9','store10'], 'points': [18, 33, 19, 14, 14, 11, 20, 28, 30, 31, 35, 33, 29, 25, 25, 27, 29, 30, 19, 23], 'assists': [3, 3, 4, 5, 4, 7, 8, 7, 6, 9, 12, 14, 5, 9, 4, 3, 4, 12, 15, 11], 'rebounds': [15, 14, 14, 10, 8, 14, 13, 9, 5, 4, 11, 6, 5, 5, 3, 8, 12, 7, 6, 5]})
单周期Sklearn KMeans尝试代码
# For a single year df = df_full[df_full['year']==2021].copy() # Make year and store as index before applying cluster df.set_index(['year','store'], inplace=True) scaled_df = StandardScaler().fit_transform(df) kmeans_kwargs = { "init": "random", "n_init": 1, "random_state": 1} #create list to hold SSE values for each k sse = [] for k in range(2, 8): kmeans = KMeans(n_clusters=k, **kmeans_kwargs) kmeans.fit(scaled_df) sse.append(kmeans.inertia_) #instantiate the k-means class, using optimal number of clusters kmeans = KMeans(init="random", n_clusters=6 ,n_init=1, random_state=1) #fit k-means algorithm to data kmeans.fit(scaled_df) #view cluster assignments for each observation kmeans.labels_ df['cluster'] = kmeans.labels_ print(df)
Tslearn TimeSeriesKMeans尝试代码
df_dtw = df_full.set_index(['year','store']) model = TimeSeriesKMeans(n_clusters=6, metric="dtw", max_iter=10, random_state=1) model.fit(df_dtw) df_dtw['cluster'] = model.labels_ print(df_dtw)
解决方案
1. 用Sklearn KMeans实现时间序列聚类的方法
Sklearn的KMeans本身不直接支持时间序列,需通过特征工程将时间序列转化为静态特征向量,再执行聚类。针对门店多维时间序列数据,步骤如下:
步骤1:重构数据结构
将每个门店的多周期、多指标数据转化为扁平特征向量。比如每个门店2年数据、3个指标,对应2*3=6个特征(2021_points、2021_assists、2021_rebounds、2022_points、2022_assists、2022_rebounds)。
# 透视重构:每个门店一行,年度指标转为列 df_pivot = df_full.pivot(index='store', columns='year', values=['points', 'assists', 'rebounds']) # 扁平化列名 df_pivot.columns = [f'{col[0]}_{col[1]}' for col in df_pivot.columns] df_pivot.reset_index(inplace=True)
步骤2:数据标准化
时间序列指标量级差异大,需做标准化处理:
# 提取特征列(排除门店标识) features = df_pivot.drop('store', axis=1) scaler = StandardScaler() scaled_features = scaler.fit_transform(features)
步骤3:KMeans聚类
kmeans_kwargs = { "init": "random", "n_init": 10, # 调高n_init保证聚类稳定性 "random_state": 1 } # 执行聚类 kmeans = KMeans(n_clusters=6, **kmeans_kwargs) df_pivot['cluster'] = kmeans.fit_predict(scaled_features) # 查看聚类结果 print(df_pivot[['store', 'cluster']])
注意:若为20年数据,特征维度会膨胀至60维,可结合PCA降维避免维度灾难。
2. TimeSeriesKMeans要求的正确数据结构
tslearn.TimeSeriesKMeans要求输入三维数组,形状为(n_samples, n_timesteps, n_features):
n_samples:样本数量(此处为10家门店)n_timesteps:时间步长(此处为2个年度)n_features:单时间点的特征数(此处为3个指标)
数据重构步骤
# 按门店分组,提取每个门店的时间序列数据 grouped = df_full.groupby('store') # 初始化三维数组 n_samples = len(grouped) n_timesteps = df_full['year'].nunique() n_features = 3 # points、assists、rebounds X = np.zeros((n_samples, n_timesteps, n_features)) # 填充数据(需保证时间顺序正确) for i, (store, data) in enumerate(grouped): data_sorted = data.sort_values('year') X[i, :, 0] = data_sorted['points'].values X[i, :, 1] = data_sorted['assists'].values X[i, :, 2] = data_sorted['rebounds'].values
标准化与聚类
时间序列聚类前建议对每个特征维度做标准化:
# 对每个特征维度标准化(沿时间步长维度) scaler = StandardScaler() X_scaled = scaler.fit_transform(X.reshape(-1, n_features)).reshape(X.shape) # 执行DTW聚类 model = TimeSeriesKMeans( n_clusters=6, metric="dtw", # 用DTW衡量时间序列相似性 max_iter=50, # 调高迭代次数保证收敛 n_init=10, random_state=1 ) cluster_labels = model.fit_predict(X_scaled) # 合并聚类结果到原数据 store_list = [store for store, _ in grouped] cluster_df = pd.DataFrame({'store': store_list, 'cluster': cluster_labels}) df_full_with_cluster = df_full.merge(cluster_df, on='store') print(df_full_with_cluster)
注意:需保证所有样本的时间步长一致(无缺失数据,缺失需先填充);150+门店+20年数据场景下,DTW计算成本较高,可改用
metric="softdtw"或先对时间序列降维。
内容的提问来源于stack exchange,提问作者user032020
相关产品推荐
相关产品推荐

