NumPy数组中每行独特元素的数量

10

For example, for

a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]])

我想要获取

[2, 2, 3]

有没有一种方法可以不使用for循环或np.vectorize来完成这个任务?

编辑:实际数据由1000行每行100个元素组成,每个元素的范围为1到365。最终目标是确定具有重复项的行的百分比。这是一个作业问题,我已经用for循环解决了它,但我只是想知道是否有更好的方法可以用numpy完成。


4
你的例子是一个包含非常小整数的非常小数组。你的实际数据长什么样? - Warren Weckesser
@WarrenWeckesser 请查看已编辑的帖子。 - DrApe
4个回答

14

方法 #1

一种基于向量化的排序方法 -

In [8]: b = np.sort(a,axis=1)

In [9]: (b[:,1:] != b[:,:-1]).sum(axis=1)+1
Out[9]: array([2, 2, 3])

方法2

对于不是非常大的ints,另一种方法是通过给每行偏移量来区分每个元素,然后进行分组求和,并计算每行中非零分组的数量 -

n = a.max()+1
a_off = a+(np.arange(a.shape[0])[:,None])*n
M = a.shape[0]*n
out = (np.bincount(a_off.ravel(), minlength=M).reshape(-1,n)!=0).sum(1)

运行时测试

将方法视为函数 -

def sorting(a):
    b = np.sort(a,axis=1)
    return (b[:,1:] != b[:,:-1]).sum(axis=1)+1

def bincount(a):
    n = a.max()+1
    a_off = a+(np.arange(a.shape[0])[:,None])*n
    M = a.shape[0]*n
    return (np.bincount(a_off.ravel(), minlength=M).reshape(-1,n)!=0).sum(1)

# From @wim's post   
def pandas(a):
    df = pd.DataFrame(a.T)
    return df.nunique()

# @jp_data_analysis's soln
def numpy_apply(a):
    return np.apply_along_axis(compose(len, np.unique), 1, a) 

案例 #1:正方形的

In [164]: np.random.seed(0)

In [165]: a = np.random.randint(0,5,(10000,10000))

In [166]: %timeit numpy_apply(a)
     ...: %timeit sorting(a)
     ...: %timeit bincount(a)
     ...: %timeit pandas(a)
1 loop, best of 3: 1.82 s per loop
1 loop, best of 3: 1.93 s per loop
1 loop, best of 3: 354 ms per loop
1 loop, best of 3: 879 ms per loop

案例#2:大量行数

In [167]: np.random.seed(0)

In [168]: a = np.random.randint(0,5,(1000000,10))

In [169]: %timeit numpy_apply(a)
     ...: %timeit sorting(a)
     ...: %timeit bincount(a)
     ...: %timeit pandas(a)
1 loop, best of 3: 8.42 s per loop
10 loops, best of 3: 153 ms per loop
10 loops, best of 3: 66.8 ms per loop
1 loop, best of 3: 53.6 s per loop

扩展到每列的唯一元素数量

要进行扩展,我们只需要按照两种提出的方法在另一个轴上进行切片和ufunc操作,如下所示 -

def nunique_percol_sort(a):
    b = np.sort(a,axis=0)
    return (b[1:] != b[:-1]).sum(axis=0)+1

def nunique_percol_bincount(a):
    n = a.max()+1
    a_off = a+(np.arange(a.shape[1]))*n
    M = a.shape[1]*n
    return (np.bincount(a_off.ravel(), minlength=M).reshape(-1,n)!=0).sum(1)

通用的多维数组及其轴

我们来看看如何扩展通用维度的ndarray,并沿着通用轴获取独特计数的数量。我们将利用np.diff及其 axis参数获得这些连续差异,从而使其具有通用性,代码如下所示 -

def nunique(a, axis):
    return (np.diff(np.sort(a,axis=axis),axis=axis)!=0).sum(axis=axis)+1

实例运行情况 -

In [77]: a
Out[77]: 
array([[1, 0, 2, 2, 0],
       [1, 0, 1, 2, 0],
       [0, 0, 0, 0, 2],
       [1, 2, 1, 0, 1],
       [2, 0, 1, 0, 0]])

In [78]: nunique(a, axis=0)
Out[78]: array([3, 2, 3, 2, 3])

In [79]: nunique(a, axis=1)
Out[79]: array([3, 3, 2, 3, 3])

如果您正在使用浮点数并希望基于某个容差值而不是绝对匹配来确定独特性,我们可以使用np.isclose。 两个选项如下 -

(~np.isclose(np.diff(np.sort(a,axis=axis),axis=axis),0)).sum(axis)+1
a.shape[axis]-np.isclose(np.diff(np.sort(a,axis=axis),axis=axis),0).sum(axis)

如果需要自定义公差值,则可以使用np.isclose进行输入。


1
排序肯定是不必要的低效率操作吧?直觉上我认为这应该是O(n)而不是O(n log n)。 - wim
@wim 有没有更好的方法,而不需要排序?嗯,那个排序并不是在Python级别上完成的,所以那个O符号表示将会不同。 - Divakar
1
绝妙的想法! - ChoF
@wim 因为缺少 bincount,感觉有些被忽视了。现在给它加上计时,对这个也要公平一些。 - Divakar
谢谢。bincount非常聪明,但是你必须小心确保你能够承受内存,并且不会溢出dtype! - wim
显示剩余12条评论

6

这个解决方案通过 np.apply_along_axis 并不是矢量化的,需要使用 Python 循环。但是使用 len + np.unique 函数相对直观。

import numpy as np
from toolz import compose

a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]])

np.apply_along_axis(compose(len, np.unique), 1, a)    # [2, 2, 3]

1
如жһњдҢ дёҚжѓідҢүз”ЁtoolzеЊ…пәЊдҢ еЏҮд»ӨйЂљиү‡lambdaе‡Ңж•°дҢүз”Ёд»Өдё‹ж›үд»Әд»Әз Ѓпәљnp.apply_along_axis(lambda x: len(np.unique(x)), axis=1, arr=a)гЂ‚ - aysljc

3
使用sort的一行代码:
In [6]: np.count_nonzero(np.diff(np.sort(a)), axis=1)+1
Out[6]: array([2, 2, 3])

2
你考虑使用Pandas吗?Dataframes有一个专门的方法来实现这个功能。
>>> a = np.array([[1, 0, 0], [1, 0, 0], [2, 3, 4]])
>>> df = pd.DataFrame(a.T)
>>> print(*df.nunique())
2 2 3

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接