你将如何在 python 的数组中对这三个区域进行分组/聚类?

Posted

技术标签:

【中文标题】你将如何在 python 的数组中对这三个区域进行分组/聚类?【英文标题】:How would you group/cluster these three areas in arrays in python? 【发布时间】:2012-02-14 22:30:58 【问题描述】:

所以你有一个数组

1
2
3
60
70
80
100
220
230
250

为了更好地理解:

如何在 python(v2.6) 中对数组中的三个区域进行分组/聚类,所以在这种情况下你会得到三个包含

的数组

[1 2 3] [60 70 80 100] [220 230 250]

背景:

y 轴是频率,x 轴是数字。这些数字是由它们的频率表示的十个最高振幅。我想从中创建三个离散数字以进行模式识别。可能有更多点,但所有点都按相对较大的频率差异分组,如您在本例中看到的大约 50 和大约 0 之间以及大约 100 和大约 220 之间。请注意,什么是大的,什么是小的变化,但是与组/集群的元素之间的差异相比,集群之间的差异仍然显着。

【问题讨论】:

这并不是一个 Python 问题。您首先必须选择一个合适的聚类算法,然后看看如何在 Python 中实现(或者如果它已经实现,例如在 SciPy 中)。 如果问题和数据集总是这样,您可以自己使用“自制”启发式算法,并对其进行微调以处理您的数据。但如果复杂度比这多一点,我认为你不能不学习答案中指出的许多好的建议和算法。 并不总是“像这样”。区别在于: 1. 更多的数字。 2. 集群之间的差距不同。 3.簇中元素之间的不同差距。剩下的就是元素间隙和簇间隙之间的差异是显着的,换句话说:Delta(elements) 事实上,stats.stackexchange.com 会是一个更好的询问地点,而且那里可能已经有几个重复了。 【参考方案1】:

我假设您在这里想要一个非常好的但简单的算法。

如果您知道需要 N 个集群,那么您可以获取(排序的)输入列表的连续成员之间的差异(增量)。例如。在 numpy 中:

 deltas = diff( sorted(input) )

然后,您可以将截止点放在您发现 N-2 个最大差异的地方。

如果您不知道 N 是什么,事情就会变得更加棘手。每当您看到大于特定大小的增量时,您都可以在此处放置截止点。这将是一个手动调整的参数,虽然不是很好,但对您来说可能已经足够了。

【讨论】:

【参考方案2】:

您可以使用最近邻聚类。对于一个属于某个簇的点,它的最近邻居也必须属于该簇。对于您展示的案例,您只需要沿 x 轴迭代并将差异与相邻点进行比较。当与前一点的差异大于与下一点的差异时,表明新集群的开始。

【讨论】:

【参考方案3】:

这是一个在 python 中实现的简单算法,检查一个值是否离集群的平均值太远(就标准差而言)

from math import sqrt

def stat(lst):
    """Calculate mean and std deviation from the input list."""
    n = float(len(lst))
    mean = sum(lst) / n
    stdev = sqrt((sum(x*x for x in lst) / n) - (mean * mean)) 
    return mean, stdev

def parse(lst, n):
    cluster = []
    for i in lst:
        if len(cluster) <= 1:    # the first two values are going directly in
            cluster.append(i)
            continue

        mean,stdev = stat(cluster)
        if abs(mean - i) > n * stdev:    # check the "distance"
            yield cluster
            cluster[:] = []    # reset cluster to the empty list

        cluster.append(i)
    yield cluster           # yield the last cluster

这将返回您在示例中所期望的5 &lt; n &lt; 9

>>> array = [1, 2, 3, 60, 70, 80, 100, 220, 230, 250]
>>> for cluster in parse(array, 7):
...     print(cluster)
[1, 2, 3]
[60, 70, 80, 100]
[220, 230, 250]

【讨论】:

array = [1, 2, 3, 4, 60, 70, 80, 100, 220, 230, 250] 使代码分为两个数组 1->3 和 4->250。 @RichardBremer:问题是我在 python3 中测试了它,而在 python2 sum(lst) / nn 整数中给出了一个整数,所以 mean1 而不是 @987654329 @。将len(lst) 转换为float 解决问题(我编辑了代码) 这可能是迄今为止提出的最明智的方法(例如,在 range(1,15) 上运行 kmeans)。但是,您仍然应该对想要实现的目标进行一些思考。有许多方法会产生这样的数组拆分;哪个合适在很大程度上取决于您使用它的目的以及您的真实数据是什么样的。 +1 对于这个答案,不仅仅是因为它是集群而使用 kmeans,而是实际上考虑了这个问题。 自上次发帖以来我已经很久了,但是您能想到这段代码在字典中使用字典而不是 array=[1,2,3...] @RikPoggi 你介意看看我的问题,这里使用你的代码:***.com/questions/18721774/…【参考方案4】:

如果x 仅代表一个索引,请注意您的数据点实际上是一维的。您可以使用 Scipy 的 cluster.vq 模块对您的点进行聚类,该模块实现了 k-means 算法。

>>> import numpy as np
>>> from scipy.cluster.vq import kmeans, vq
>>> y = np.array([1,2,3,60,70,80,100,220,230,250])
>>> codebook, _ = kmeans(y, 3)  # three clusters
>>> cluster_indices, _ = vq(y, codebook)
>>> cluster_indices
array([1, 1, 1, 0, 0, 0, 0, 2, 2, 2])

结果表示:前三个点形成簇1(任意标签),接下来的四个点形成簇0,最后三个点形成簇2。根据索引对原始点进行分组留给读者作为练习。

有关 Python 中的更多聚类算法,请查看scikit-learn。

【讨论】:

我不喜欢“留给读者做练习”这句话。因为它的傲慢。 @RichardBremer:这句话只是表明我太忙/太懒了,无法解决列表处理的苦差事,我相信你可以自己解决。它也会分散答案的核心。我看不出有什么傲慢,我当然不是故意傲慢的。 好吧,好像是个简单的误会。 如果您发现结果非常有说服力,请在数据集array=list(range(15)) 上尝试此操作。 K-means 不是一维数据的好选择,尤其是当您不知道k 时。事实上,关于 k-means 唯一要说的就是它的实现非常简单。 我不是想让你的答案看起来很糟糕。 k-means 对一维数据没有那么大的意义,但我的观点是 a) k-means 对数据做了很多隐含的假设:集群的数值大小相等,k 是已知的; b) 聚类不仅仅是对对象进行分组,而且实际上是以对要解决的特定任务有意义的方式对对象进行分组,这不能由算法来回答,而是由领域专家来回答。【参考方案5】:

您可以通过多种方式解决此问题。当您抛出关键字“聚类”时,显而易见的一个是使用 kmeans(请参阅其他回复)。

但是,您可能希望首先更深入地了解您实际在做什么或试图做什么。而不是仅仅在你的数据上抛出一个随机函数。

据我所知,您有许多一维值,并且您想将它们分成 未知 个组,对吗?好吧,k-means 可能会奏效,但事实上,您可以在数据集中寻找 k 个最大的差异。 IE。对于任何索引i &gt; 0,计算k[i] - k[i-1],并选择大于其余索引的k 索引。最有可能的是,您的结果实际上会比使用 k-means 更好更快

在python代码中:

k = 2
a = [1, 2, 3, 60, 70, 80, 100, 220, 230, 250]
a.sort()
b=[] # A *heap* would be faster
for i in range(1, len(a)):
  b.append( (a[i]-a[i-1], i) )
b.sort()
# b now is [... (20, 6), (20, 9), (57, 3), (120, 7)]
# and the last ones are the best split points.
b = map(lambda p: p[1], b[-k:])
b.sort()
# b now is: [3, 7]
b.insert(0, 0)
b.append(len(a) + 1)
for i in range(1, len(b)):
  print a[b[i-1]:b[i]],
# Prints [1, 2, 3] [60, 70, 80, 100] [220, 230, 250]

(顺便说一句,这可以看作是一个简单的单链接聚类!)

一种更高级的方法,实际上去掉了参数k,计算b[*][1] 的均值和标准差,并在值大于mean+2*stddev 的地方分割。这仍然是一个相当粗略的启发式方法。另一种选择是实际假设一个值分布,例如k 正态分布,然后使用例如Levenberg-Marquardt 将分布拟合到您的数据。

但这真的是你想要做的吗?

首先尝试定义什么应该是一个集群,什么不是。第二部分更为重要。

【讨论】:

我认为我的定义是详尽无遗的。如果不具体说明您缺少什么。 insert() 只需要 2 个参数(给定 1 个) @RichardBremer:我认为他/她的意思是b.insert(0,0),因为b 拥有“断路器”索引,因此它还需要第一个 (0) 来启动。

以上是关于你将如何在 python 的数组中对这三个区域进行分组/聚类?的主要内容,如果未能解决你的问题,请参考以下文章

每天一个随笔

在Python中对关联数组进行排序[重复]

如何在Python中对版本标签列表进行排序[重复]

如何在 MATLAB 中对感兴趣区域进行自动裁剪?

在python中对数组列表进行分类

在 Python 中对单个数组进行更快的双重迭代