Python 按小时分组的无监督需求聚类

Python 按小时分组的无监督需求聚类,python,cluster-analysis,Python,Cluster Analysis,我有以下数据框,其中包含每小时对应的产品消耗量。我想根据类似的需求对这些小时进行分组,但是,为了有意义,时间分组必须是连续的。例如,一个有意义的小时分组可以是10-12,但不能是(10-12,2,4-5) 上面的代码(以及所有无监督聚类算法)生成一些聚类值范围,但它不知道小时数必须是连续的;它只是对值进行聚类。有没有关于如何解决这个限制并同时强制执行连续的工作时间组的想法?这是一个非常类似的启发式方法,它试图实现您想要的 基本上,您只需在一个数组中列出您的需求,并找出最大的连续子数组,其中连续元

我有以下数据框,其中包含每小时对应的产品消耗量。我想根据类似的需求对这些小时进行分组,但是,为了有意义,时间分组必须是连续的。例如,一个有意义的小时分组可以是10-12,但不能是(10-12,2,4-5)


上面的代码(以及所有无监督聚类算法)生成一些聚类值范围,但它不知道小时数必须是连续的;它只是对值进行聚类。有没有关于如何解决这个限制并同时强制执行连续的工作时间组的想法?

这是一个非常类似的启发式方法,它试图实现您想要的

基本上,您只需在一个数组中列出您的需求,并找出最大的连续子数组,其中连续元素差的绝对值在阈值内。您可以改变阈值以获得所需的输出。 安排事情:

import numpy as np, pandas as pd, datetime as dt
date = lambda i: dt.datetime.now()+dt.timedelta(i)
df = pd.DataFrame({"date":[date(i) for i in range(25)], "demand": np.random.randint(0,20,25)})
原始阵列:

arr = df.demand.tolist()
(绝对)差异数组:

diff = [abs(arr[i]-arr[i-1]) for i in range(1,len(arr))]
将T设置为5。T是用于窗口的阈值。这是您愿意在连续日期/小时内接受的需求差异的最大值。如果要增加或减少差异的可接受值,请对其进行调整

T = 5
当前子阵列在每个时间戳小于T的间隔长度:

counter = 0
intervals = []
for i in range(len(diff)):
    if diff[i]<T:
        counter += 1
    else:
        counter = 0
    intervals.append(counter)
满足条件的最大连续间隔:

max_interval_idx = max(range(len(intervals)), key=lambda i: intervals[i])
max_interval = intervals[max_interval_idx]
验证答案:

print(arr[max_interval_idx-max_interval +1: max_interval_idx +2])
请注意,所有连续差异都小于5

这是你的答案:

df["date"][max_interval_idx-max_interval +1: max_interval_idx +2]

现在你可以改变你的T来得到不同的分组。

我只是通过@Sebastian Hoffmann来扩展答案。我假设您的数据没有“死亡时间”。如果不是这样,您将需要用(比如)
-100
填充缺少的行,获取集群ID并删除随后添加的行。因为集群ID不必是连续的

df = pd.DataFrame([('1970-01-01', '08:00:00',  9), ('1970-01-01', '09:00:00', 11), ('1970-01-01', '10:00:00', 28), ('1970-01-01', '11:00:00', 26), ('1970-01-01', '12:00:00', 26), ('1970-01-01', '13:00:00', 32), ('1970-01-01', '14:00:00', 24), ('1970-01-01', '15:00:00', 30), ('1970-01-01', '16:00:00', 23), ('1970-01-01', '17:00:00', 32), ('1970-01-01', '18:00:00', 27), ('1970-01-01', '19:00:00', 21), ('1970-01-01', '20:00:00', 16), ('1970-01-01', '21:00:00', 13), ('1970-01-01', '22:00:00',  1), ('1970-01-01', '23:00:00',  0)], columns=['Date','Time','data'])    

thresh = 5.4
df['cluster_id'] = (df.data.diff().abs() > thresh).cumsum()
结果是

           Date      Time  data  cluster_id
0   1970-01-01  08:00:00     9           0
1   1970-01-01  09:00:00    11           0
2   1970-01-01  10:00:00    28           1
3   1970-01-01  11:00:00    26           1
4   1970-01-01  12:00:00    26           1
5   1970-01-01  13:00:00    32           2
6   1970-01-01  14:00:00    24           3
7   1970-01-01  15:00:00    30           4
8   1970-01-01  16:00:00    23           5
9   1970-01-01  17:00:00    32           6
10  1970-01-01  18:00:00    27           6
11  1970-01-01  19:00:00    21           7
12  1970-01-01  20:00:00    16           7
13  1970-01-01  21:00:00    13           7
14  1970-01-01  22:00:00     1           8
15  1970-01-01  23:00:00     0           8
要获取群集ID,请筛选具有多个条目的群集ID:

clusters = (df.cluster_id.value_counts() > 1)
clusters[clusters].index.values


array([7, 1, 8, 6, 0], dtype=int64)

使用有限差分,即
np.diff
。一旦差异超过某个阈值
t
,当前箱子将结束,新箱子将启动。@sebastianhofmann,谢谢。例子?嗨,皮尤斯·辛格,我知道这个方法,它很有趣。将+1和+2添加到最后一个答案中的索引中有什么意义?
diff
中的@azal索引与原始数组的距离为1,因此在开始索引中添加了1+在结束索引中的2出现,因为每当计数器看到小于T的某个事物时,它都以1开始
[12, 8, 11, 15, 16, 14, 18, 14]
df["date"][max_interval_idx-max_interval +1: max_interval_idx +2]
df = pd.DataFrame([('1970-01-01', '08:00:00',  9), ('1970-01-01', '09:00:00', 11), ('1970-01-01', '10:00:00', 28), ('1970-01-01', '11:00:00', 26), ('1970-01-01', '12:00:00', 26), ('1970-01-01', '13:00:00', 32), ('1970-01-01', '14:00:00', 24), ('1970-01-01', '15:00:00', 30), ('1970-01-01', '16:00:00', 23), ('1970-01-01', '17:00:00', 32), ('1970-01-01', '18:00:00', 27), ('1970-01-01', '19:00:00', 21), ('1970-01-01', '20:00:00', 16), ('1970-01-01', '21:00:00', 13), ('1970-01-01', '22:00:00',  1), ('1970-01-01', '23:00:00',  0)], columns=['Date','Time','data'])    

thresh = 5.4
df['cluster_id'] = (df.data.diff().abs() > thresh).cumsum()
           Date      Time  data  cluster_id
0   1970-01-01  08:00:00     9           0
1   1970-01-01  09:00:00    11           0
2   1970-01-01  10:00:00    28           1
3   1970-01-01  11:00:00    26           1
4   1970-01-01  12:00:00    26           1
5   1970-01-01  13:00:00    32           2
6   1970-01-01  14:00:00    24           3
7   1970-01-01  15:00:00    30           4
8   1970-01-01  16:00:00    23           5
9   1970-01-01  17:00:00    32           6
10  1970-01-01  18:00:00    27           6
11  1970-01-01  19:00:00    21           7
12  1970-01-01  20:00:00    16           7
13  1970-01-01  21:00:00    13           7
14  1970-01-01  22:00:00     1           8
15  1970-01-01  23:00:00     0           8
clusters = (df.cluster_id.value_counts() > 1)
clusters[clusters].index.values


array([7, 1, 8, 6, 0], dtype=int64)