将需求无监督地聚类为小时组
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)
我有以下 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)