将需求无监督地聚类为小时组

Unsupervised clustering of demand into groups of hours

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

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

import scipy.cluster.hierarchy as hcluster
temp_data = df.values

ndata = [[td, td] for td in temp_data]
data = np.array(ndata)

# clustering
thresh = (15.0 / 100.0) * (
            max(temp_data) - min(temp_data))  # Threshold 15% of the total range of data

clusters = hcluster.fclusterdata(data, thresh, criterion="distance")

total_clusters = max(clusters)

clustered_index = []
for i in range(total_clusters):
    clustered_index.append([])

for i in range(len(clusters)):
    clustered_index[clusters[i] - 1].append(i)

clustered_range = []
for x in clustered_index:
    clustered_index_x = [temp_data[y] for y in x]
    clustered_range.append((min(clustered_index_x), max(clustered_index_x)))
print(clustered_range)

上面的代码(以及所有无监督聚类算法)产生了一些聚类值范围,但它不知道时间必须是连续的;它只是将值聚类。关于如何解决此限制并同时强制执行连续的几组小时,您有什么想法吗?

这是一个非常相似的试探法,试图实现您想要的结果。

本质上就是把你的需求列在一个数组中,找出连续元素差的绝对值在一个阈值内的最大连续子数组。您可以改变阈值以获得所需的输出。 设置:

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()
[7, 11, 11, 4, 6, 6, 8, 10, 18, 11, 2, 12, 16, 0, 12, 8, 11, 15, 16, 14, 18, 14, 19, 3, 15]

(绝对)差异数组:

diff = [abs(arr[i]-arr[i-1]) for i in range(1,len(arr))]
[4, 0, 7, 2, 0, 2, 2, 8, 7, 9, 10, 4, 16, 12, 4, 3, 4, 1, 2, 4, 4, 5, 16, 12]

将 T 设置为 5。T 是用于 window 的阈值。这是连续dates/hours内您愿意接受的最大需求差值。如果您想增加或减少可接受的差异值,请对其进行调整。

T = 5

当前子数组在每个时间戳小于 T 的区间长度:

counter = 0
intervals = []
for i in range(len(diff)):
    if diff[i]<T:
        counter += 1
    else:
        counter = 0
    intervals.append(counter)
[1, 2, 0, 1, 2, 3, 4, 0, 0, 0, 0, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 0, 0, 0]

满足条件的最大连续区间:

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])
[12, 8, 11, 15, 16, 14, 18, 14]

注意所有连续的差异都小于 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,请过滤具有多个条目的集群:

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


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