Apakah mungkin menggunakan argsort dalam urutan menurun?

181

Pertimbangkan kode berikut:

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

Ini memberi saya indeks nelemen terkecil. Apakah mungkin untuk menggunakan yang sama ini argsortdalam urutan menurun untuk mendapatkan indeks nelemen tertinggi?

shn
sumber
3
Bukankah itu sederhana ids = np.array(avgDists).argsort()[-n:]?
Jaime
2
@Jaime: Tidak, itu tidak berhasil. 'jawaban yang benar' adalah [3, 1, 2]. Baris Anda menghasilkan [2, 1, 3](jika n == 3 sebagai contoh)
dawg
2
@rewk Yah, lalu buat ids = np.array(avgDists).argsort()[-n:][::-1]. Masalahnya adalah menghindari membuat salinan dari seluruh daftar, yang adalah apa yang Anda dapatkan ketika Anda menambahkannya -di depannya. Tidak relevan untuk contoh kecil OP, bisa untuk kasus yang lebih besar.
Jaime
1
@Jaime: Kamu benar. Lihat jawaban saya yang diperbarui. Sintaksnya hanya kebalikan dari komentar Anda pada irisan akhir: np.array(avgDists).argsort()[::-1][:n]akan melakukannya. Juga, jika Anda akan menggunakan numpy, tetaplah numpy. Pertama-tama ubah daftar menjadi sebuah array: avgDist=np.array(avgDists)lalu ubah menjadiavgDist.argsort()[::-1][:n}
dawg

Jawaban:

230

Jika Anda meniadakan array, elemen terendah menjadi elemen tertinggi dan sebaliknya. Oleh karena itu, indeks nelemen tertinggi adalah:

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

Cara lain untuk bernalar tentang ini, seperti yang disebutkan dalam komentar , adalah untuk mengamati bahwa unsur-unsur besar akan menjadi yang terakhir dalam argumen. Jadi, Anda dapat membaca dari ekor argsort untuk menemukan nelemen tertinggi:

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

Kedua metode adalah O (n log n) dalam kompleksitas waktu, karena argsortpanggilan adalah istilah yang dominan di sini. Tetapi pendekatan kedua memiliki keuntungan yang bagus: ia menggantikan negasi O (n) dari array dengan irisan O (1) . Jika Anda bekerja dengan array dalam loop kecil maka Anda mungkin mendapatkan beberapa keuntungan kinerja dari menghindari negasi itu, dan jika Anda bekerja dengan array besar maka Anda dapat menghemat penggunaan memori karena negasi membuat salinan seluruh array.

Perhatikan bahwa metode ini tidak selalu memberikan hasil yang setara: jika penerapan sortir yang stabil diminta argsort, misalnya dengan melewati argumen kata kunci kind='mergesort', maka strategi pertama akan mempertahankan stabilitas penyortiran, tetapi strategi kedua akan merusak stabilitas (yaitu posisi yang sama barang akan terbalik).

Contoh waktu:

Menggunakan array kecil 100 float dan panjang 30 ekor, metode tampilan sekitar 15% lebih cepat

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

Untuk array yang lebih besar, argsort dominan dan tidak ada perbedaan waktu yang signifikan

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

Harap dicatat bahwa komentar dari nedim di bawah ini salah. Apakah memotong sebelum atau setelah membalikkan tidak membuat perbedaan dalam efisiensi, karena kedua operasi ini hanya berjalan dengan pandangan yang berbeda dari array dan tidak benar-benar menyalin data.

wim
sumber
14
Bahkan lebih efisien untuk memotong sebelum membalikkan, yaitu,np.array(avgDists).argsort()[:-n][::-1]
nedim
3
Jawaban ini tidak setara jika array aslinya berisi nans. Dalam kasus seperti itu, solusi pertama tampaknya memberikan hasil yang lebih alami dengan nans di akhir daripada di awal.
feilchenfeldt
1
Bagaimana ini membandingkan ketika jenis stabil diinginkan? Mungkin strategi pengiris membalik item yang sama?
Eric
1
@ user3666197 Saya merasa itu tidak relevan dengan jawaban. Apakah negasi membuat salinan atau tidak (itu memang) tidak terlalu penting di sini, informasi yang relevan adalah bahwa menghitung negasi adalah O (n) kompleksitas vs mengambil irisan lain yaitu O (1) .
wim
1
@ user3666197 Ya, itu poin yang bagus - jika sebuah array mengambil 50% memori yang tersedia, kami pasti ingin menghindari menyalinnya dan menyebabkan swapping. Saya akan mengedit lagi untuk menyebutkan bahwa salinan dibuat di sana.
wim
70

Sama seperti Python, dalam [::-1]membalik array yang dikembalikan oleh argsort()dan [:n]memberikan n elemen terakhir:

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

Keuntungan dari metode ini idsadalah pandangan para avgDists:

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

(The 'OWNDATA' menjadi False menunjukkan ini adalah tampilan, bukan salinan)

Cara lain untuk melakukan ini adalah sesuatu seperti:

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

Masalahnya adalah cara kerjanya adalah membuat negatif dari setiap elemen dalam array:

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

ANd membuat salinan untuk melakukannya:

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

Jadi, jika Anda menghitung waktu masing-masing, dengan kumpulan data yang sangat kecil ini:

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

Metode tampilan jauh lebih cepat (dan menggunakan 1/2 memori ...)

dawg
sumber
4
Jawaban ini bagus, tetapi saya merasa kata-kata Anda salah menggambarkan karakteristik kinerja nyata: "bahkan dengan kumpulan data yang sangat kecil ini, metode tampilan jauh lebih cepat" . Pada kenyataannya, negasi adalah O (n) dan argumennya adalah O (n log n) . Ini berarti perbedaan waktu akan berkurang untuk kumpulan data yang lebih besar - istilah O (n log n) mendominasi, namun saran Anda adalah pengoptimalan bagian O (n) . Jadi kompleksitas tetap sama, dan itu untuk ini set data kecil khususnya yang kita lihat perbedaan yang signifikan.
wim
2
Kompleksitas yang setara secara asimptot masih dapat berarti bahwa satu algoritma secara asimptot dua kali lebih cepat dari yang lain. Membuang perbedaan seperti itu dapat memiliki konsekuensi. Sebagai contoh, bahkan jika perbedaan waktu (dalam persentase) mendekati 0, saya berani bertaruh bahwa algoritma dengan negasi masih menggunakan memori dua kali lebih banyak.
bug
@bug Itu bisa, tetapi tidak dalam kasus ini. Saya telah menambahkan beberapa timing ke jawaban saya. Angka-angka menunjukkan bahwa untuk array yang lebih besar pendekatan ini memiliki timing yang mirip, yang mendukung hipotesis bahwa argsort dominan. Untuk negasi, saya kira Anda benar tentang penggunaan memori, tetapi pengguna mungkin masih lebih suka jika mereka peduli tentang posisi nan dan / atau perlu jenis yang stabil.
wim
6

Anda dapat menggunakan perintah flip numpy.flipud()atau numpy.fliplr()untuk mendapatkan indeks dalam urutan menurun setelah mengurutkan menggunakan argsortperintah. Itulah yang biasanya saya lakukan.

Kanmani
sumber
Itu jauh lebih lambat daripada mengiris stackoverflow.com/a/44921013/125507
endolith
5

Alih-alih menggunakan np.argsortAnda bisa menggunakan np.argpartition- jika Anda hanya memerlukan indeks elemen n terendah / tertinggi.

Itu tidak perlu mengurutkan seluruh array tetapi hanya bagian yang Anda butuhkan tetapi perhatikan bahwa "pesanan di dalam partisi Anda" tidak ditentukan, jadi sementara itu memberikan indeks yang benar mereka mungkin tidak dipesan dengan benar:

>>> 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)
MSeifert
sumber
Atau, jika Anda menggunakan keduanya bersama-sama, yaitu argsort dan argpartition, operasi harus dilakukan pada operasi argpartition.
demongolem
3

Anda bisa membuat salinan array dan kemudian mengalikan setiap elemen dengan -1.
Akibatnya elemen sebelum terbesar akan menjadi yang terkecil.
Induktus dari n elemen terkecil dalam salinan adalah elemen terbesar dalam orisinal.

MentholBonbon
sumber
ini dilakukan dengan mudah meniadakan array, seperti yang dinyatakan dalam jawaban lain:-array
onofricamila
2

Seperti @Kanmani mengisyaratkan, implementasi implementasi yang lebih mudah dapat digunakan numpy.flip, seperti pada yang berikut:

import numpy as np

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

Dengan menggunakan pola pengunjung daripada fungsi anggota, lebih mudah untuk membaca urutan operasi.

Adam Erickson
sumber
1

Dengan contoh Anda:

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

Dapatkan indeks n nilai maksimal:

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

Sortir dalam urutan menurun:

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

Dapatkan hasil (untuk n = 4):

>>> avgDists[ids]
array([9, 8, 6, 4])
Alexey Antonenko
sumber
-1

Cara lain adalah dengan menggunakan hanya '-' dalam argumen untuk argsort seperti pada: "df [np.argsort (-df [:, 0])]", asalkan df adalah kerangka data dan Anda ingin mengurutkannya dengan yang pertama kolom (diwakili oleh nomor kolom '0'). Ubah nama kolom yang sesuai. Tentu saja, kolom harus berupa angka.

Biswajit Ghoshal
sumber