Python 通过多列对数据帧中的连续项进行群集/分组 问题:
假设我有k个标量列,并且我想将条目分组,如果它们沿着每列彼此之间的距离在一定范围内 假设simpicity k为2,它们是我唯一的列Python 通过多列对数据帧中的连续项进行群集/分组 问题:,python,pandas,numpy,scipy,Python,Pandas,Numpy,Scipy,假设我有k个标量列,并且我想将条目分组,如果它们沿着每列彼此之间的距离在一定范围内 假设simpicity k为2,它们是我唯一的列 pd.DataFrame(list(zip(sorted(choices(range(0,10), k=20)), choices(range(20,29), k=20))), columns=['a','b']) 屈服 [(1, 27), (1, 27), (1, 21), (2, 23), (3, 25), (4, 23), (4, 28),
pd.DataFrame(list(zip(sorted(choices(range(0,10), k=20)), choices(range(20,29), k=20))), columns=['a','b'])
屈服
[(1, 27),
(1, 27),
(1, 21),
(2, 23),
(3, 25),
(4, 23),
(4, 28),
(4, 27),
(4, 22),
(4, 24),
(5, 26),
(6, 21),
(7, 26),
(7, 20),
(8, 24),
(8, 25),
(8, 23),
(9, 20),
(9, 28),
(9, 21)]
我需要分组,以便分组包括a列中最多相隔m
的条目,以及最多相隔n
的b列中的条目。如果m
=n
=1,则集群将为:
(1, 27), (1, 27)
(1, 21)
(2, 23)
(3, 25), (4, 23), (4, 22), (4, 24)
(4, 28), (4, 27), (5, 26)
(6, 21), (7, 20)
(7, 26), (8, 24), (8, 25), (8, 23)
(9, 20), (9, 21)
(9, 28),
笔记
实现这一点的一种方法是使用,但这不是一个好的解决方案,因为:
- 我有很多数据,不想做平方运算
- 数据已经排序,m,n相对于列的范围较小
- m=/=n(不总是),否则m+n的曼哈顿距离阈值将起作用
我相信这可能是一个非常相关的问题,但它没有一个普遍的答案:
一种可能帮助您找到答案的方法的草图:
a, b, c, d, e = tee(range(10), 5)
next(b, None)
next(c, None);next(c, None)
next(d, None);next(d, None);next(d, None)
next(e, None);next(e, None);next(e, None);next(e, None)
list(zip(a, b, c, d, e))
[(0, 1, 2, 3, 4),
(1, 2, 3, 4, 5),
(2, 3, 4, 5, 6),
(3, 4, 5, 6, 7),
(4, 5, 6, 7, 8),
(5, 6, 7, 8, 9)]
你的问题让我想起了lag
操作和cumsum
。这里有一个答案。如果您的数据很大,我认为使用pythonlist
和tuple
就可以了,默认模块必须有函数来完成我们的任务
步骤1:获取数据
第2步:滞后1个长度
输出:
步骤3:定义custum函数,然后将观察结果分组
首先,我们使用metric='chebyshev'
test = np.array([(1, 27),
(1, 27),
(1, 21),
(2, 23),
(3, 25),
(4, 23),
(4, 28),
(4, 27),
(4, 22),
(4, 24),
(5, 26),
(6, 21),
(7, 26),
(7, 20),
(8, 24),
(8, 25),
(8, 23),
(9, 20),
(9, 28),
(9, 21)])
from scipy.spatial.distance import pdist, squareform
c_mat = squareform(pdist(test, metric = 'chebyshev')) <= 1
现在c_mat
是True
如果有任何链连接两行,那么out
是所有单独组的布尔索引。现在返回结果:
for mask in list(out):
print(np.unique(test[mask], axis = 0))
[[ 9 28]]
[[ 9 20]
[ 9 21]]
[[ 7 26]
[ 8 23]
[ 8 24]
[ 8 25]]
[[ 6 21]
[ 7 20]]
[[ 4 27]
[ 4 28]
[ 5 26]]
[[ 3 25]
[ 4 22]
[ 4 23]
[ 4 24]]
[[ 2 23]]
[[ 1 21]]
[[ 1 27]]
您还可以使用这些布尔索引访问原始DataFrame
编辑1:
现在,我们可以利用输入是半排序的这一事实来加快速度。但要做到这一点,我们需要numba
from numba import jit
@jit
def find_connected(data, dist = 1):
i = list(range(data.shape[0]))
j = list(range(data.shape[0]))
l = data.shape[0]
for x in range(1, l):
for y in range(x, l):
v = np.abs(data[x] - data[y])
if v.max() <= dist:
i += [x, y]
j += [y, x]
if v.min() > dist:
break
d = [1] * len(i)
return (d, (i, j))
csr
做dot产品的速度要快得多,因此c_mat=c_mat@c_mat
可以工作,但停止标准被打破。您可以使用Anreas K.的优秀答案,也可以只做out=np.unique(c_mat.todense(),axis=0)
编辑2:
在我解决这个问题之前,我无法从脑海中摆脱出来,除非我没有制作一个稠密的矩阵
import numba as nb
import numpy as np
@nb.njit
def find_connected_semisort(data, dist = 1):
l = data.shape[0]
out = []
for x in range(l):
for y in range(x, l):
v = np.abs(data[x] - data[y])
if v.max() <= dist:
out.append(set([x, y]))
if v.min() > dist:
break
outlen = len(out)
for x in range(outlen):
for y in range(x + 1, outlen):
if len(out[x] & out[y]) > 0:
out[y] |= out[x]
out[x].clear()
return [list(i) for i in out if len(i) > 0]
[np.unique(test[i], axis = 0).squeeze() for i in find_connected_semisort(test)]
Out[]:
[array([ 1, 27]), array([ 1, 21]), array([ 2, 23]), array([[ 3, 25],
[ 4, 22],
[ 4, 23],
[ 4, 24]]), array([[ 4, 27],
[ 4, 28],
[ 5, 26]]), array([[ 6, 21],
[ 7, 20]]), array([[ 7, 26],
[ 8, 23],
[ 8, 24],
[ 8, 25]]), array([ 9, 28]), array([[ 9, 20],
[ 9, 21]])]
将numba作为nb导入
将numpy作为np导入
@nb.njit
def find_connected_semisort(数据,dist=1):
l=数据。形状[0]
out=[]
对于范围(l)内的x:
对于范围(x,l)内的y:
v=np.abs(数据[x]-数据[y])
如果v.max()距离:
打破
outlen=len(out)
对于范围内的x(outlen):
对于范围内的y(x+1,outlen):
如果len(out[x]&out[y])>0:
out[y]|=out[x]
out[x].clear()
return[如果len(i)>0,则i in out的列表(i)]
[np.unique(test[i],axis=0).find\u connected\u semisort(test)中i的挤压()
出[]:
[array([1,27])、array([1,21])、array([2,23])、array([3,25]),以及,
[ 4, 22],
[ 4, 23],
[4,24]),数组([4,27],
[ 4, 28],
[5,26]),数组([6,21],
[7,20]),数组([7,26],
[ 8, 23],
[ 8, 24],
[8,25]),数组([9,28]),数组([9,20],
[ 9, 21]])]
可能有一些方法可以不用两个循环就完成,但我无法尝试。在您的输出中,在不同的输出列表中有(6,28)的两个副本?当我们生成数据时,可能在不同的位置有相同的值。我没有弄清楚,在你的帖子中,观察的顺序是否应该与你的例子中的(3,25)
和(4,23)、(4,22)、(4,24)
放在一起?@AndreasK。我对此也感到困惑。看来观察的顺序很重要。例如,假设(3,25)
的下一个样本是(4,23)
,因为4-3=1,23-25=-2
所以(4,23)与(3,25)分开。但这并不能解释为什么(3,25)和(4,24)不在同一组中……pdist
不需要使用平方运算。使用metric='cityblock'
(曼哈顿距离)你将非常接近你想要的。@AndreasK你是对的,对此表示抱歉,我已经修复了这个示例。@DanielF你能给我指一些文献吗pdist如何使用cityblock获得加速?很好的解决方案+1.但是,如果m=/=n怎么办?我想你可以做一些像c_mat=squareform((pdist(test[:,0][:,None],'cityblock'))
1 [(1, 26), (1, 27), (1, 28)]
2 [(2, 22)]
3 [(2, 24)]
4 [(3, 20)]
5 [(3, 26)]
6 [(4, 21), (4, 20)]
7 [(5, 28)]
8 [(5, 26), (5, 26)]
9 [(6, 28)]
10 [(6, 24)]
11 [(6, 28)]
12 [(7, 23)]
13 [(7, 26)]
14 [(8, 28), (8, 28)]
15 [(9, 26)]
dtype: object
test = np.array([(1, 27),
(1, 27),
(1, 21),
(2, 23),
(3, 25),
(4, 23),
(4, 28),
(4, 27),
(4, 22),
(4, 24),
(5, 26),
(6, 21),
(7, 26),
(7, 20),
(8, 24),
(8, 25),
(8, 23),
(9, 20),
(9, 28),
(9, 21)])
from scipy.spatial.distance import pdist, squareform
c_mat = squareform(pdist(test, metric = 'chebyshev')) <= 1
out = np.ones((c_mat.shape[0], 2))
while out.sum(0).max() >1:
c_mat = c_mat @ c_mat
out = np.unique(c_mat, axis = 0)
for mask in list(out):
print(np.unique(test[mask], axis = 0))
[[ 9 28]]
[[ 9 20]
[ 9 21]]
[[ 7 26]
[ 8 23]
[ 8 24]
[ 8 25]]
[[ 6 21]
[ 7 20]]
[[ 4 27]
[ 4 28]
[ 5 26]]
[[ 3 25]
[ 4 22]
[ 4 23]
[ 4 24]]
[[ 2 23]]
[[ 1 21]]
[[ 1 27]]
from numba import jit
@jit
def find_connected(data, dist = 1):
i = list(range(data.shape[0]))
j = list(range(data.shape[0]))
l = data.shape[0]
for x in range(1, l):
for y in range(x, l):
v = np.abs(data[x] - data[y])
if v.max() <= dist:
i += [x, y]
j += [y, x]
if v.min() > dist:
break
d = [1] * len(i)
return (d, (i, j))
from scipy.sparse import csr_matrix
c_mat = csr_matrix(find_connected(test), dtype = bool)
import numba as nb
import numpy as np
@nb.njit
def find_connected_semisort(data, dist = 1):
l = data.shape[0]
out = []
for x in range(l):
for y in range(x, l):
v = np.abs(data[x] - data[y])
if v.max() <= dist:
out.append(set([x, y]))
if v.min() > dist:
break
outlen = len(out)
for x in range(outlen):
for y in range(x + 1, outlen):
if len(out[x] & out[y]) > 0:
out[y] |= out[x]
out[x].clear()
return [list(i) for i in out if len(i) > 0]
[np.unique(test[i], axis = 0).squeeze() for i in find_connected_semisort(test)]
Out[]:
[array([ 1, 27]), array([ 1, 21]), array([ 2, 23]), array([[ 3, 25],
[ 4, 22],
[ 4, 23],
[ 4, 24]]), array([[ 4, 27],
[ 4, 28],
[ 5, 26]]), array([[ 6, 21],
[ 7, 20]]), array([[ 7, 26],
[ 8, 23],
[ 8, 24],
[ 8, 25]]), array([ 9, 28]), array([[ 9, 20],
[ 9, 21]])]