내림차순으로 argsort를 사용할 수 있습니까?


181

다음 코드를 고려하십시오.

avgDists = np.array([1, 8, 6, 9, 4])
ids = avgDists.argsort()[:n]

이것은 나에게 n가장 작은 요소의 지표를 제공 합니다. 가장 높은 원소 argsort의 인덱스를 얻기 위해 이것을 내림차순 으로 사용할 수 n있습니까?


3
간단하지 ids = np.array(avgDists).argsort()[-n:]않습니까?
Jaime

2
@Jaime : 아니요, 작동하지 않습니다. '정답'은 [3, 1, 2]입니다. 귀하의 라인 생산 [2, 1, 3](예를 들어 n == 3 인 경우)
dawg

2
@drewk 그럼 잘 만드세요 ids = np.array(avgDists).argsort()[-n:][::-1]. 문제는 전체 목록의 복사본을 만드는 것을 피하는 것입니다.이 목록 -앞에 추가하면 얻을 수 있습니다 . OP의 작은 예와 관련이 없으며 더 큰 경우에 해당 될 수 있습니다.
제이미

1
@Jaime : 당신이 맞아요. 업데이트 된 답변을 참조하십시오. 구문 tho는 끝 슬라이스에 대한 귀하의 의견과 반대입니다 np.array(avgDists).argsort()[::-1][:n]. 또한 numpy를 사용하려는 경우 numpy를 유지하십시오. 먼저 목록을 배열로 변환하면 avgDist=np.array(avgDists)다음과 같이됩니다.avgDist.argsort()[::-1][:n}
dawg

답변:


230

배열을 무효화하면 가장 낮은 요소가 가장 높은 요소가되고 그 반대도 마찬가지입니다. 따라서 n가장 높은 요소 의 지수는 다음 과 같습니다.

(-avgDists).argsort()[:n]

의견 에서 언급했듯이 이것에 대해 추론하는 또 다른 방법 은 큰 요소가 argsort 에서 마지막 에 오는 것을 관찰하는 것입니다. 따라서 argsort의 꼬리에서 읽어 n가장 높은 요소 를 찾을 수 있습니다.

avgDists.argsort()[::-1][:n]

두 방법 모두 시간 복잡성에서 O (n log n) 입니다 argsort. 여기서 호출은 지배적 인 용어 이기 때문 입니다. 그러나 두 번째 접근 방식은 배열 의 O (n) 부정을 O (1) 슬라이스로 대체합니다 . 루프 내부의 작은 배열을 사용하는 경우 해당 부정을 피함으로써 성능이 약간 향상 될 수 있으며, 큰 배열을 사용하는 경우 부정이 전체 배열의 복사본을 생성하므로 메모리 사용량을 절약 할 수 있습니다.

이러한 메소드가 항상 동등한 결과를 제공하지는 않습니다. argsort예를 들어 키워드 인수를 전달하여 안정적인 정렬 구현이 요청 된 kind='mergesort'경우 첫 번째 전략은 정렬 안정성을 유지하지만 두 번째 전략은 안정성을 잃습니다 (예 : 항목이 반전됩니다).

타이밍 예 :

100 개의 부동 소수점과 길이가 30 인 작은 배열을 사용하면 뷰 방법이 약 15 % 빨랐습니다.

>>> avgDists = np.random.rand(100)
>>> n = 30
>>> timeit (-avgDists).argsort()[:n]
1.93 µs ± 6.68 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> timeit avgDists.argsort()[::-1][:n]
1.64 µs ± 3.39 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
>>> timeit avgDists.argsort()[-n:][::-1]
1.64 µs ± 3.66 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

더 큰 배열의 경우, argsort가 지배적이며 상당한 타이밍 차이가 없습니다.

>>> avgDists = np.random.rand(1000)
>>> n = 300
>>> timeit (-avgDists).argsort()[:n]
21.9 µs ± 51.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> timeit avgDists.argsort()[::-1][:n]
21.7 µs ± 33.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> timeit avgDists.argsort()[-n:][::-1]
21.9 µs ± 37.1 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

유의하시기 바랍니다 네딤에서 주석 아래 올바르지 않습니다. 역전 전후에 절단해야하는지 여부는 효율성에 차이가 없습니다. 두 작업 모두 어레이의보기 만 다르게 진행하고 실제로 데이터를 복사하지 않기 때문입니다.


14
반전하기 전에 슬라이스하는 것이 훨씬 더 효율적입니다.np.array(avgDists).argsort()[:-n][::-1]
nedim

3
원래 배열에 nan이 있으면 이러한 답변은 동일하지 않습니다. 이 경우 첫 번째 해결책은 처음이 아니라 끝에 nans에 더 자연스러운 결과를주는 것으로 보입니다.
feilchenfeldt

1
안정적인 정렬이 필요할 때이를 어떻게 비교합니까? 아마도 슬라이싱 전략은 동일한 항목을 되돌 립니까?
Eric

1
@ user3666197 답변과 관련이 없다고 생각했습니다. 부정이 사본을 생성하는지 여부 (그렇지 않은지)가 여기에서 실제로 중요하지 않은 경우, 관련 정보는 부정을 계산하는 것이 O (n) 복잡성 대 다른 조각 인 O (1)을 취하는 것 입니다.
wim

1
@ user3666197 그렇습니다. 좋은 점입니다. 어레이가 50 %의 사용 가능한 메모리를 차지하는 경우 복사 및 스와핑을 피하는 것이 좋습니다. 거기에서 사본이 생성되었음을 언급하기 위해 다시 편집하겠습니다.
wim

70

그냥 파이썬과 같은, 즉에 [::-1]의해 반환 된 배열을 반전 argsort()하고 [:n]마지막 n 개의 요소를 제공합니다 :

>>> avgDists=np.array([1, 8, 6, 9, 4])
>>> n=3
>>> ids = avgDists.argsort()[::-1][:n]
>>> ids
array([3, 1, 2])

이 방법의 장점은 avgDists ids관점 입니다.

>>> ids.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  UPDATEIFCOPY : False

( '거짓'인 'OWNDATA'는 이것이 사본이 아니라보기임을 나타냅니다)

이를 수행하는 다른 방법은 다음과 같습니다.

(-avgDists).argsort()[:n]

문제는 이것이 작동하는 방식이 배열의 각 요소에 대해 음수를 만드는 것입니다.

>>> (-avgDists)
array([-1, -8, -6, -9, -4])

ANd는이를 위해 사본을 작성합니다.

>>> (-avgDists_n).flags['OWNDATA']
True

따라서 매우 작은 데이터 세트로 각각 시간을 정하면 다음과 같습니다.

>>> import timeit
>>> timeit.timeit('(-avgDists).argsort()[:3]', setup="from __main__ import avgDists")
4.2879798610229045
>>> timeit.timeit('avgDists.argsort()[::-1][:3]', setup="from __main__ import avgDists")
2.8372560259886086

보기 방법이 훨씬 빠릅니다 (메모리의 1/2 사용 ...)


4
이 답변은 좋지만 귀하의 문구가 실제 성능 특성을 잘못 나타내고 있다고 생각합니다. "이 아주 작은 데이터 세트라도 뷰 방법이 훨씬 빠릅니다 . " 실제로 부정은 O (n) 이고 인수는 O (n log n) 입니다. 이는 더 큰 데이터 세트에 대해 타이밍 불일치가 감소 한다는 것을 의미합니다 . O (n log n) 항이 우세하지만 제안은 O (n) 부분 의 최적화입니다 . 따라서 복잡성은 동일하게 유지되며이 작은 데이터 세트 의 경우 특히 중요한 차이점이 있습니다.
wim

2
점진적으로 동등한 복잡성은 여전히 ​​한 알고리즘이 다른 알고리즘보다 두 배 빠릅니다. 그러한 구별을 버리는 것은 결과를 초래할 수 있습니다. 예를 들어 시간 불일치 (백분율)가 0에 가까워도 부정 알고리즘이 여전히 두 배의 메모리를 사용한다고 기꺼이 생각합니다.
버그

@bug 가능하지만이 경우에는 그렇지 않습니다. 내 답변에 타이밍을 추가했습니다. 숫자는 더 큰 배열의 경우 이러한 접근 방식이 유사한 타이밍을 가지며, 이는 argsort가 지배적이라는 가설을 뒷받침합니다. 부정의 경우, 당신은 메모리 사용에 대해서는 맞다고 생각하지만, 사용자는 여전히 nan의 위치에 관심이 있고 안정적인 정렬이 필요한 경우를 선호 할 수 있습니다.
wim

6

당신은 플립 명령을 사용할 수 있습니다 numpy.flipud()또는 numpy.fliplr()사용하여 정렬 한 후 내림차순으로 인덱스를 얻기 위해 argsort명령을 사용합니다. 그게 내가 보통하는 일입니다.


즉 슬라이스보다 훨씬 느리다 stackoverflow.com/a/44921013/125507
endolith

5

가장 낮은 / 가장 높은 n 요소의 인덱스 만 필요한 경우 np.argsort사용 하는 대신 사용할 수 있습니다 np.argpartition.

전체 배열을 정렬 할 필요는 없지만 필요한 부분 만 정렬 할 필요가 있지만 "파티션 내부의 순서"는 정의되어 있지 않으므로 올바른 인덱스를 제공하지만 올바르게 정렬되지 않을 수 있습니다.

>>> avgDists = [1, 8, 6, 9, 4]
>>> np.array(avgDists).argpartition(2)[:2]  # indices of lowest 2 items
array([0, 4], dtype=int64)

>>> np.array(avgDists).argpartition(-2)[-2:]  # indices of highest 2 items
array([1, 3], dtype=int64)

또는 둘을 함께 사용하는 경우 즉, argsort와 argpartition은 작업을 argpartition 작업에서 수행해야합니다.
demongolem

3

배열의 복사본을 만든 다음 각 요소에 -1을 곱할 수 있습니다.
결과적으로 이전의 가장 큰 요소가 가장 작아집니다.
사본에서 n 개의 가장 작은 요소의 수는 원본에서 n 개의 가장 큰 요소입니다.


이것은 다른 답변에서 언급 한 것처럼 배열을 쉽게 무시할 수 있습니다.-array
onofricamila

2

@ Kanmani가 암시 한 것처럼 numpy.flip다음과 같이 구현을 해석하기가 더 쉽습니다 .

import numpy as np

avgDists = np.array([1, 8, 6, 9, 4])
ids = np.flip(np.argsort(avgDists))
print(ids)

멤버 함수가 아닌 방문자 패턴을 사용하면 작업 순서를 쉽게 읽을 수 있습니다.


1

예를 들면 다음과 같습니다.

avgDists = np.array([1, 8, 6, 9, 4])

n 최대 값의 인덱스를 구하십시오.

ids = np.argpartition(avgDists, -n)[-n:]

내림차순으로 정렬하십시오.

ids = ids[np.argsort(avgDists[ids])[::-1]]

결과 얻기 (n = 4의 경우) :

>>> avgDists[ids]
array([9, 8, 6, 4])

-1

또 다른 방법은 "df [np.argsort (-df [:, 0])]"과 같이 argsort의 인수에 '-'만 사용하는 것입니다. df가 데이터 프레임이고 첫 번째로 정렬하려는 경우 열 (열 번호 '0'으로 표시). column-name을 적절히 변경하십시오. 물론, 열은 숫자 여야합니다.

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