다음과 같은 numpy 배열이 있습니다. [1 2 2 0 0 1 3 5]
요소의 인덱스를 2D 배열로 가져올 수 있습니까? 예를 들어 위의 입력에 대한 답변은 다음과 같습니다.[[3 4], [0 5], [1 2], [6], [], [7]]
현재 다른 값을 반복하고 각 값을 호출 numpy.where(input == i)
해야합니다.이 값은 충분히 큰 입력으로 끔찍한 성능을 발휘합니다.
다음과 같은 numpy 배열이 있습니다. [1 2 2 0 0 1 3 5]
요소의 인덱스를 2D 배열로 가져올 수 있습니까? 예를 들어 위의 입력에 대한 답변은 다음과 같습니다.[[3 4], [0 5], [1 2], [6], [], [7]]
현재 다른 값을 반복하고 각 값을 호출 numpy.where(input == i)
해야합니다.이 값은 충분히 큰 입력으로 끔찍한 성능을 발휘합니다.
답변:
다음은 O (max (x) + len (x)) 접근 방식입니다 scipy.sparse
.
import numpy as np
from scipy import sparse
x = np.array("1 2 2 0 0 1 3 5".split(),int)
x
# array([1, 2, 2, 0, 0, 1, 3, 5])
M,N = x.max()+1,x.size
sparse.csc_matrix((x,x,np.arange(N+1)),(M,N)).tolil().rows.tolist()
# [[3, 4], [0, 5], [1, 2], [6], [], [7]]
이것은 (x [0], 0), (x [1], 1) 위치에 항목이있는 희소 행렬을 생성하여 작동합니다. CSC
(압축 된 희소 열) 형식을 사용하면이 작업이 간단합니다. 그런 다음 매트릭스는 LIL
(연결된 목록) 형식으로 변환 됩니다. 이 형식은 각 행의 열 인덱스를 해당 rows
속성 의 목록으로 저장 하므로이를 수행하여 목록으로 변환하면됩니다.
작은 어레이 argsort
기반 솔루션의 경우 아마도 더 빠를 수 있지만 약간 크지 않은 크기에서는 이것이 교차됩니다.
편집하다:
argsort
기반 numpy
전용 솔루션 :
np.split(x.argsort(kind="stable"),np.bincount(x)[:-1].cumsum())
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]
그룹 내 인덱스 순서가 중요하지 않은 argpartition
경우 시도해 볼 수도 있습니다 (이 작은 예에서는 차이가 없지만 일반적으로 보장되지는 않습니다).
bb = np.bincount(x)[:-1].cumsum()
np.split(x.argpartition(bb),bb)
# [array([3, 4]), array([0, 5]), array([1, 2]), array([6]), array([], dtype=int64), array([7])]
편집하다:
@Divakar는의 사용을 권장하지 않습니다 np.split
. 대신 루프가 더 빠를 것입니다.
A = x.argsort(kind="stable")
B = np.bincount(x+1).cumsum()
[A[B[i-1]:B[i]] for i in range(1,len(B))]
또는 새로운 (Python3.8 +) 해마 연산자를 사용할 수 있습니다.
A = x.argsort(kind="stable")
B = np.bincount(x)
L = 0
[A[L:(L:=L+b)] for b in B.tolist()]
편집 (편집) :
(순수하지 않음) : numba (@senderle의 게시물 참조)의 대안으로 pythran을 사용할 수도 있습니다.
와 컴파일 pythran -O3 <filename.py>
import numpy as np
#pythran export sort_to_bins(int[:],int)
def sort_to_bins(idx, mx):
if mx==-1:
mx = idx.max() + 1
cnts = np.zeros(mx + 2, int)
for i in range(idx.size):
cnts[idx[i] + 2] += 1
for i in range(3, cnts.size):
cnts[i] += cnts[i-1]
res = np.empty_like(idx)
for i in range(idx.size):
res[cnts[idx[i]+1]] = i
cnts[idx[i]+1] += 1
return [res[cnts[i]:cnts[i+1]] for i in range(mx)]
다음 numba
은 성능 측면에서 수염에 의한 것입니다.
repeat(lambda:enum_bins_numba_buffer(x),number=10)
# [0.6235917090671137, 0.6071486569708213, 0.6096088469494134]
repeat(lambda:sort_to_bins(x,-1),number=10)
# [0.6235359431011602, 0.6264424560358748, 0.6217901279451326]
오래된 것들 :
import numpy as np
#pythran export bincollect(int[:])
def bincollect(a):
o = [[] for _ in range(a.max()+1)]
for i,j in enumerate(a):
o[j].append(i)
return o
타이밍 vs. numba (이전)
timeit(lambda:bincollect(x),number=10)
# 3.5732191529823467
timeit(lambda:enumerate_bins(x),number=10)
# 6.7462647299980745
np.split
.
데이터의 크기에 따라 하나의 가능한 옵션은 다음을 제거 numpy
하고 사용하는 것입니다 collections.defaultdict
.
In [248]: from collections import defaultdict
In [249]: d = defaultdict(list)
In [250]: l = np.random.randint(0, 100, 100000)
In [251]: %%timeit
...: for k, v in enumerate(l):
...: d[v].append(k)
...:
10 loops, best of 3: 22.8 ms per loop
그런 다음의 사전으로 끝납니다 {value1: [index1, index2, ...], value2: [index3, index4, ...]}
. 시간 스케일링은 어레이의 크기와 거의 선형에 가깝기 때문에 10,000,000은 ~ 2.7 초가 걸립니다.
요청은 numpy
솔루션에 대한 것이지만 흥미로운 numba
기반 솔루션 이 있는지 확인하기로 결정했습니다 . 그리고 실제로 있습니다! 다음은 사전 할당 된 단일 버퍼에 저장된 비정형 배열로 분할 된 목록을 나타내는 방법입니다. Paul Panzer가argsort
제안한 접근 방식 에서 영감을 얻습니다 . (하지 않았지만 더 간단한 이전 버전은 아래를 참조하십시오.)
@numba.jit(numba.void(numba.int64[:],
numba.int64[:],
numba.int64[:]),
nopython=True)
def enum_bins_numba_buffer_inner(ints, bins, starts):
for x in range(len(ints)):
i = ints[x]
bins[starts[i]] = x
starts[i] += 1
@numba.jit(nopython=False) # Not 100% sure this does anything...
def enum_bins_numba_buffer(ints):
ends = np.bincount(ints).cumsum()
starts = np.empty(ends.shape, dtype=np.int64)
starts[1:] = ends[:-1]
starts[0] = 0
bins = np.empty(ints.shape, dtype=np.int64)
enum_bins_numba_buffer_inner(ints, bins, starts)
starts[1:] = ends[:-1]
starts[0] = 0
return [bins[s:e] for s, e in zip(starts, ends)]
이것은 100ms 항목 목록을 75ms로 처리하는데, 이는 순수한 Python으로 작성된 목록 기반 버전보다 거의 50 배 빠른 속도입니다.
느리지 만 읽기 쉬운 버전의 경우 동적으로 크기가 조정 된 "유형 목록"에 대한 최근의 추가 된 실험 지원을 기반으로 이전에 가지고 있던 내용이 있습니다.이를 통해 각 빈을 비 순차적으로 훨씬 빠르게 채울 수 있습니다.
이 numba
형식 유추 엔진과 조금 씨름하고 , 그 부분을 처리하는 더 좋은 방법이 있다고 확신합니다. 이것은 위의 것보다 거의 10 배 느리다는 것이 밝혀졌습니다.
@numba.jit(nopython=True)
def enum_bins_numba(ints):
bins = numba.typed.List()
for i in range(ints.max() + 1):
inner = numba.typed.List()
inner.append(0) # An awkward way of forcing type inference.
inner.pop()
bins.append(inner)
for x, i in enumerate(ints):
bins[i].append(x)
return bins
나는 이것을 다음에 대해 테스트했다.
def enum_bins_dict(ints):
enum_bins = defaultdict(list)
for k, v in enumerate(ints):
enum_bins[v].append(k)
return enum_bins
def enum_bins_list(ints):
enum_bins = [[] for i in range(ints.max() + 1)]
for x, i in enumerate(ints):
enum_bins[i].append(x)
return enum_bins
def enum_bins_sparse(ints):
M, N = ints.max() + 1, ints.size
return sparse.csc_matrix((ints, ints, np.arange(N + 1)),
(M, N)).tolil().rows.tolist()
또한 미리 컴파일 된 cython 버전 enum_bins_numba_buffer
(아래에 자세히 설명되어 있음) 과 비교하여 테스트했습니다 .
천만 임의의 정수 목록 ( ints = np.random.randint(0, 100, 10000000)
)에 다음과 같은 결과가 나타납니다.
enum_bins_dict(ints)
3.71 s ± 80.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_list(ints)
3.28 s ± 52.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_sparse(ints)
1.02 s ± 34.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_numba(ints)
693 ms ± 5.81 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
enum_bins_cython(ints)
82.3 ms ± 1.77 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
enum_bins_numba_buffer(ints)
77.4 ms ± 2.06 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
놀랍게도 이러한 작업 방식은 경계 검사 기능이 꺼져 있어도 동일한 기능 버전 numba
보다 성능이 뛰어 cython
납니다. 나는 pythran
이 방법을 사용 하여이 접근법을 테스트 하기에 아직 익숙하지 않지만 비교를보고 싶습니다. 이 속도 향상에 따라이 방법으로 pythran
버전이 약간 더 빠를 수도 있습니다.
다음 cython
은 참조 용 버전이며 일부 빌드 지침이 있습니다. cython
설치 한 후에는 다음 setup.py
과 같은 간단한 파일 이 필요합니다 .
from distutils.core import setup
from distutils.extension import Extension
from Cython.Build import cythonize
import numpy
ext_modules = [
Extension(
'enum_bins_cython',
['enum_bins_cython.pyx'],
)
]
setup(
ext_modules=cythonize(ext_modules),
include_dirs=[numpy.get_include()]
)
그리고 cython 모듈 enum_bins_cython.pyx
:
# cython: language_level=3
import cython
import numpy
cimport numpy
@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
cdef void enum_bins_inner(long[:] ints, long[:] bins, long[:] starts) nogil:
cdef long i, x
for x in range(len(ints)):
i = ints[x]
bins[starts[i]] = x
starts[i] = starts[i] + 1
def enum_bins_cython(ints):
assert (ints >= 0).all()
# There might be a way to avoid storing two offset arrays and
# save memory, but `enum_bins_inner` modifies the input, and
# having separate lists of starts and ends is convenient for
# the final partition stage.
ends = numpy.bincount(ints).cumsum()
starts = numpy.empty(ends.shape, dtype=numpy.int64)
starts[1:] = ends[:-1]
starts[0] = 0
bins = numpy.empty(ints.shape, dtype=numpy.int64)
enum_bins_inner(ints, bins, starts)
starts[1:] = ends[:-1]
starts[0] = 0
return [bins[s:e] for s, e in zip(starts, ends)]
작업 디렉토리에이 두 파일이 있으면 다음 명령을 실행하십시오.
python setup.py build_ext --inplace
그런 다음을 사용하여 함수를 가져올 수 있습니다 from enum_bins_cython import enum_bins_cython
.
끔찍한 일을하는 정말 이상한 방법이 있지만 공유하지 않는 것이 너무 재미 있다는 것을 알았습니다 numpy
.
out = np.array([''] * (x.max() + 1), dtype = object)
np.add.at(out, x, ["{} ".format(i) for i in range(x.size)])
[[int(i) for i in o.split()] for o in out]
Out[]:
[[3, 4], [0, 5], [1, 2], [6], [], [7]]
편집 : 이것은이 경로를 따라 찾을 수있는 가장 좋은 방법입니다. @PaulPanzer argsort
솔루션 보다 여전히 10 배 느립니다 .
out = np.empty((x.max() + 1), dtype = object)
out[:] = [[]] * (x.max() + 1)
coords = np.empty(x.size, dtype = object)
coords[:] = [[i] for i in range(x.size)]
np.add.at(out, x, coords)
list(out)
숫자 사전을 만들어서 할 수 있습니다. 키는 숫자이며 값은 그 숫자가 본 색인이어야합니다. 이것은 가장 빠른 방법 중 하나입니다. 코드가 다음과 같이 표시됩니다.
>>> import numpy as np
>>> a = np.array([1 ,2 ,2 ,0 ,0 ,1 ,3, 5])
>>> b = {}
# Creating an empty list for the numbers that exist in array a
>>> for i in range(np.min(a),np.max(a)+1):
b[str(i)] = []
# Adding indices to the corresponding key
>>> for i in range(len(a)):
b[str(a[i])].append(i)
# Resulting Dictionary
>>> b
{'0': [3, 4], '1': [0, 5], '2': [1, 2], '3': [6], '4': [], '5': [7]}
# Printing the result in the way you wanted.
>>> for i in sorted (b.keys()) :
print(b[i], end = " ")
[3, 4] [0, 5] [1, 2] [6] [] [7]
의사 코드 :
numpy 배열의 최소값을 최대 값에서 빼고 1을 더하여 "2d 배열의 1d 배열 수"를 얻습니다. 귀하의 경우 5-0 + 1 = 6입니다.
그 안에 1d 배열의 수로 2d 배열을 초기화하십시오. 귀하의 경우 6d 1d 배열로 2d 배열을 초기화하십시오. 각 1d 배열은 numpy 배열의 고유 한 요소에 해당합니다. 예를 들어 첫 번째 1d 배열은 '0'에 해당하고 두 번째 1d 배열은 '1'에 해당합니다 ...
numpy 배열을 반복하고 요소의 색인을 해당하는 1d 배열에 넣으십시오. 귀하의 경우, numpy 배열의 첫 번째 요소 색인은 두 번째 1d 배열에 배치되고 numpy 배열의 두 번째 요소 색인은 세 번째 1d 배열에 배치됩니다 ....
이 의사 코드는 numpy 배열의 길이에 따라 실행되는 데 선형 시간이 걸립니다.
이것은 정확히 당신이 원하는 것을 제공하고 내 컴퓨터에서 10,000,000 동안 약 2.5 초가 걸릴 것입니다.
import numpy as np
import timeit
# x = np.array("1 2 2 0 0 1 3 5".split(),int)
x = np.random.randint(0, 100, 100000)
def create_index_list(x):
d = {}
max_value = -1
for i,v in enumerate(x):
if v > max_value:
max_value = v
try:
d[v].append(i)
except:
d[v] = [i]
result_list = []
for i in range(max_value+1):
if i in d:
result_list.append(d[i])
else:
result_list.append([])
return result_list
# print(create_index_list(x))
print(timeit.timeit(stmt='create_index_list(x)', number=1, globals=globals()))
따라서 요소 목록이 주어지면 (요소, 색인) 쌍을 만들고 싶습니다. 선형 시간으로 다음과 같이 수행 할 수 있습니다.
hashtable = dict()
for idx, val in enumerate(mylist):
if val not in hashtable.keys():
hashtable[val] = list()
hashtable[val].append(idx)
newlist = sorted(hashtable.values())
O (n) 시간이 걸립니다. 현재로서는 더 빠른 솔루션을 생각할 수 없지만 그렇게하면 여기에서 업데이트됩니다.
np.argsort([1, 2, 2, 0, 0, 1, 3, 5])
제공합니다array([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64)
. 다음 요소 만 비교하면됩니다.