想象一下你有一个2D数组(作为NumPy int
数组),如:
[[2,2,3,3],
[2,3,3,3],
[3,3,4,4]]
现在,您希望获得具有相同形状的数组,但不是原始值,而是希望将数字替换为其出现次数。这意味着,数字2变为3,因为它发生了3次,3s变为7s,4s变为2s。
所以输出结果为:
[[3,3,7,7],
[3,7,7,7],
[7,7,2,2]]
我的解决方案是首先创建一个字典,它将所有原始值保存为键,并将值保存为出现次数。但对于形状为2000x2000的阵列,这似乎很慢。
我怎样才能更有效地实现这一目标?
谢谢!
答案 0 :(得分:4)
我相信你应该能够在return_inverse
内使用np.unique()
留在NumPy:
如果为True,也返回唯一数组的索引(对于 指定的轴(如果提供),可用于重建
ar
。
>>> import numpy as np
>>> a = np.array([[2,2,3,3],
... [2,3,3,3],
... [3,3,4,4]])
>>> _, inv, cts = np.unique(a, return_inverse=True, return_counts=True)
>>> cts[inv].reshape(a.shape)
array([[3, 3, 7, 7],
[3, 7, 7, 7],
[7, 7, 2, 2]])
这也适用于未对展平数组进行排序的情况,例如b = np.array([[1, 2, 4], [4, 4, 1]])
。
答案 1 :(得分:2)
一种方法是使用numpy.unique
来提取值计数。
然后转换为字典并使用numpy.vectorize
来利用此字典映射。
import numpy as np
A = np.array([[2,2,3,3],
[2,3,3,3],
[3,3,4,4]])
d = dict(zip(*np.unique(A.ravel(), return_counts=True)))
res = np.vectorize(d.get)(A)
array([[3, 3, 7, 7],
[3, 7, 7, 7],
[7, 7, 2, 2]], dtype=int64)
<强>性能强>
我看到上述方法对于2000x2000阵列需要~2s而通过基于collections.Counter
字典的方法需要3s。但PaulPanzer和BradSolomon的纯numpy
解决方案仍然更快。
import numpy as np
from collections import Counter
A = np.random.randint(0, 10, (2000, 2000))
MAX_LOOKUP = 2**24
def map_count(A):
d = dict(zip(*np.unique(A.ravel(), return_counts=True)))
return np.vectorize(d.get)(A)
def map_count2(A):
d = Counter(A.ravel())
return np.vectorize(d.get)(A)
def bs(A):
_, inv, cts = np.unique(A, return_inverse=True, return_counts=True)
return cts[inv].reshape(A.shape)
def pp(a):
mn, mx = a.min(), a.max()
span = mx-mn+1
if span > MAX_LOOKUP:
raise RuntimeError('values spread to wide')
a = a - mn
return np.bincount(a.ravel(), None, span)[a]
%timeit map_count(A) # 1.9 s ± 24.2 ms per loop
%timeit map_count2(A) # 3 s ± 33.1 ms per loop
%timeit bs(A) # 887 ms ± 20 ms per loop
%timeit pp(A) # 149 ms ± 6.32 ms per loop
答案 2 :(得分:2)
这是一种利用您的值为int的事实的方法:
MAX_LOOKUP = 2**24
def f_pp(a):
mn, mx = a.min(), a.max()
span = mx-mn+1
if span > MAX_LOOKUP:
raise RuntimeError('values spread to wide')
a = a - mn
return np.bincount(a.ravel(), None, span)[a]
Timings(很大程度上基于@jpp的工作):
>>> from timeit import timeit
>>> kwds = dict(globals=globals(), number=3)
>>>
>>> for l, r in [(0, 10), (0, 1000), (-8000000, 8000000)]:
... a = np.random.randint(l, r, (2000, 2000))
... print(l, r)
... print('mc ', timeit('map_count(a)', **kwds))
... print('mc2', timeit('map_count2(a)', **kwds))
... print('bs ', timeit('bs(a)', **kwds))
... print('pp ', timeit('f_pp(a)', **kwds))
...
0 10
mc 2.462232475867495
mc2 3.820418732939288
bs 1.266723491018638
pp 0.11216754489578307
0 1000
mc 2.972961534978822
mc2 4.3769155589398
bs 2.1607728030066937
pp 0.14146877988241613
-8000000 8000000
mc 10.753600731957704
mc2 8.373655589064583
bs 2.700256273150444
pp 0.7070535880047828