Numpy : 1d 배열 요소의 인덱스를 2d 배열로 가져옵니다.


10

다음과 같은 numpy 배열이 있습니다. [1 2 2 0 0 1 3 5]

요소의 인덱스를 2D 배열로 가져올 수 있습니까? 예를 들어 위의 입력에 대한 답변은 다음과 같습니다.[[3 4], [0 5], [1 2], [6], [], [7]]

현재 다른 값을 반복하고 각 값을 호출 numpy.where(input == i)해야합니다.이 값은 충분히 큰 입력으로 끔찍한 성능을 발휘합니다.


np.argsort([1, 2, 2, 0, 0, 1, 3, 5])제공합니다 array([3, 4, 0, 5, 1, 2, 6, 7], dtype=int64). 다음 요소 만 비교하면됩니다.
vb_rises

답변:


11

다음은 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

이것은
@Randy

루프 기반의 것보다 좋습니다 np.split.
Divakar

@Divakar 좋은 지적, 감사합니다!
Paul Panzer

8

데이터의 크기에 따라 하나의 가능한 옵션은 다음을 제거 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 초가 걸립니다.


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.


매우 넓은 의미에서 numba와 비슷한 pythran을 알고 있는지 궁금합니다. 내 게시물에 pythran 솔루션을 추가했습니다. 이 경우 pythran은 더 빠르고 더 많은 pythonic 솔루션을 제공하는 것처럼 보입니다.
Paul Panzer

@PaulPanzer 재미있는! 나는 그것을 듣지 못했습니다. List 코드가 안정되면 numba 개발자가 예상되는 구문 설탕을 추가 할 것이라고 수집합니다. 또한 편리한 / 속도의 절충점이있는 것 같습니다. jit 데코레이터는 별도의 사전 컴파일 된 모듈이 필요한 접근 방식과 비교하여 일반 Python 코드 기반에 매우 쉽게 통합 할 수 있습니다. 그러나 scipy 접근법에 비해 3 배 빠른 속도는 실제로 놀랍고 놀랍습니다!
발신자

방금 전에 기본적으로 stackoverflow.com/q/55226662/7207392을 수행했음을 기억했습니다 . numba 및 cython 버전을 해당 Q & A에 추가 하시겠습니까? 유일한 차이점은 인덱스 0,1,2, ...를 대신하지 않고 다른 배열을 비우는 것입니다. 그리고 실제로 결과 배열을 자르지 않아도됩니다.
Paul Panzer

@PaulPanzer 아 정말 멋지다. 나는 오늘이나 내일 어느 시점에 그것을 추가하려고 노력할 것입니다. 별도의 답변을 제안하거나 답변을 수정 하시겠습니까? 어느 쪽이든 행복합니다!
발신자

큰! 나는 별도의 게시물이 더 좋을 것이라고 생각하지만 강한 선호는 없습니다.
Paul Panzer

6

끔찍한 일을하는 정말 이상한 방법이 있지만 공유하지 않는 것이 너무 재미 있다는 것을 알았습니다 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)

2

숫자 사전을 만들어서 할 수 있습니다. 키는 숫자이며 값은 그 숫자가 본 색인이어야합니다. 이것은 가장 빠른 방법 중 하나입니다. 코드가 다음과 같이 표시됩니다.

>>> 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] 

1

의사 코드 :

  1. numpy 배열의 최소값을 최대 값에서 빼고 1을 더하여 "2d 배열의 1d 배열 수"를 얻습니다. 귀하의 경우 5-0 + 1 = 6입니다.

  2. 그 안에 1d 배열의 수로 2d 배열을 초기화하십시오. 귀하의 경우 6d 1d 배열로 2d 배열을 초기화하십시오. 각 1d 배열은 numpy 배열의 고유 한 요소에 해당합니다. 예를 들어 첫 번째 1d 배열은 '0'에 해당하고 두 번째 1d 배열은 '1'에 해당합니다 ...

  3. numpy 배열을 반복하고 요소의 색인을 해당하는 1d 배열에 넣으십시오. 귀하의 경우, numpy 배열의 첫 번째 요소 색인은 두 번째 1d 배열에 배치되고 numpy 배열의 두 번째 요소 색인은 세 번째 1d 배열에 배치됩니다 ....

이 의사 코드는 numpy 배열의 길이에 따라 실행되는 데 선형 시간이 걸립니다.


1

이것은 정확히 당신이 원하는 것을 제공하고 내 컴퓨터에서 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()))

0

따라서 요소 목록이 주어지면 (요소, 색인) 쌍을 만들고 싶습니다. 선형 시간으로 다음과 같이 수행 할 수 있습니다.

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) 시간이 걸립니다. 현재로서는 더 빠른 솔루션을 생각할 수 없지만 그렇게하면 여기에서 업데이트됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.