Python / NumPy kullanarak, diziyi iki kez sıralamadan bir dizideki öğeleri sıralayın


103

Bir sayı dizim var ve ilk dizideki her öğenin sırasını temsil eden başka bir dizi oluşturmak istiyorum. Python ve NumPy kullanıyorum.

Örneğin:

array = [4,2,7,1]
ranks = [2,1,3,0]

İşte bulduğum en iyi yöntem:

array = numpy.array([4,2,7,1])
temp = array.argsort()
ranks = numpy.arange(len(array))[temp.argsort()]

Diziyi iki kez sıralamaktan kaçınan daha iyi / daha hızlı yöntemler var mı?


6
Son satırınız ile eşdeğerdir ranks = temp.argsort().
Sven Marnach

Yanıtlar:


68

Son adımda sol tarafta dilimlemeyi kullanın:

array = numpy.array([4,2,7,1])
temp = array.argsort()
ranks = numpy.empty_like(temp)
ranks[temp] = numpy.arange(len(array))

Bu, son adımda permütasyonu tersine çevirerek iki kez sıralamayı önler.


3
Mükemmel, teşekkürler! Bir çözüm olduğunu biliyordum ve onu gördüğümde apaçık görünecekti. Timeit ile bazı testler yaptım ve bu yöntem küçük diziler için biraz daha yavaş. Benim makinemde dizide 2.000 eleman olduğunda eşitler. 20.000 elementte, yönteminiz yaklaşık% 25 daha hızlıdır.
joshayers

bu satırlar halinde nasıl yapılacağına dair herhangi bir öneri?
Xaser

1'den fazla karartma için aşağıdaki cevaba bakın.
mathtick

101

Argsort'u iki kez kullanın, önce dizinin sırasını elde edin, ardından sıralamayı elde edin:

array = numpy.array([4,2,7,1])
order = array.argsort()
ranks = order.argsort()

2D (veya daha yüksek boyutlu) dizilerle uğraşırken, doğru eksen üzerinde sıralamak için argsort'a bir eksen bağımsız değişkeni ilettiğinizden emin olun.


2
Giriş dizinizde sayılar tekrarlanırsa (örn. [4,2,7,1,1]), Çıktının bu sayıları dizi konumlarına göre [3,2,4,0,1]
sıralayacağını unutmayın

4
İki kez sıralama verimsizdir. @Sven Marnach'ın cevabı, tek bir çağrı ile sıralamaya nasıl ulaşılacağını gösteriyor argsort.
Warren Weckesser

6
@WarrenWeckesser: İkisi arasındaki farkı test ettim ve büyük diziler için haklısınız, ancak daha küçük herhangi bir şey için (n <100), çift bağımsız değişken daha hızlıdır (n = 100 için yaklaşık% 20 ve yaklaşık 5 kat daha hızlıdır) n = 10 için). Dolayısıyla, çok sayıda küçük değer kümesinde çok sayıda sıralama yapmanız gerekiyorsa, bu yöntem çok daha iyidir.
naught101

3
@WarrenWeckesser: Aslında yanılıyorum, bu yöntem daha iyi. Her iki yöntem de scipy.stats yönteminden çok daha hızlıdır. Sonuçlar: gist.github.com/naught101/14042d91a2d0f18a6ae4
naught101

1
@ naught101: Komut dosyanızda bir hata var. Satır array = np.random.rand(10)olmalıdır array = np.random.rand(n).
Warren Weckesser

89

Bu soru birkaç yıl öncesine ait ve kabul edilen cevap harika, ancak aşağıdakilerin hala bahsetmeye değer olduğunu düşünüyorum. Bağımlılığa aldırmazsanız scipy, şunları kullanabilirsiniz scipy.stats.rankdata:

In [22]: from scipy.stats import rankdata

In [23]: a = [4, 2, 7, 1]

In [24]: rankdata(a)
Out[24]: array([ 3.,  2.,  4.,  1.])

In [25]: (rankdata(a) - 1).astype(int)
Out[25]: array([2, 1, 3, 0])

Güzel bir özellik rankdataolduğunu methodargüman bağlarının tutulması için çeşitli seçenekler sunar. Örneğin, aşağıdakilerde 20'nin üç oluşumu ve 40'ın iki oluşumu vardır b:

In [26]: b = [40, 20, 70, 10, 20, 50, 30, 40, 20]

Varsayılan, ortalama sırayı bağlı değerlere atar:

In [27]: rankdata(b)
Out[27]: array([ 6.5,  3. ,  9. ,  1. ,  3. ,  8. ,  5. ,  6.5,  3. ])

method='ordinal' ardışık sıralar atar:

In [28]: rankdata(b, method='ordinal')
Out[28]: array([6, 2, 9, 1, 3, 8, 5, 7, 4])

method='min' bağlı değerlerin minimum sırasını bağlı tüm değerlere atar:

In [29]: rankdata(b, method='min')
Out[29]: array([6, 2, 9, 1, 2, 8, 5, 6, 2])

Daha fazla seçenek için belge dizisine bakın.


1
evet, uç vakaların önemli olduğu her yerde en iyi cevap budur.
naught101

rankdataİlk sıralamayı dahili olarak oluşturmak için kabul edilen yanıtla aynı mekanizmayı kullanmanın ilginç olduğunu düşünüyorum .
AlexV

5

Dizinizi satır satır işlediğinizi varsayarak, birden fazla boyutlu A dizileri için her iki çözümü de genişletmeye çalıştım (eksen = 1).

İlk kodu satırlar üzerinde bir döngü ile genişlettim; muhtemelen geliştirilebilir

temp = A.argsort(axis=1)
rank = np.empty_like(temp)
rangeA = np.arange(temp.shape[1])
for iRow in xrange(temp.shape[0]): 
    rank[iRow, temp[iRow,:]] = rangeA

Ve ikincisi, k.rooijers'ın önerisini takiben şu olur:

temp = A.argsort(axis=1)
rank = temp.argsort(axis=1)

(1000,100) şeklinde rastgele 400 dizi oluşturdum; ilk kod yaklaşık 7.5, ikincisi 3.8.


5

Ortalama bir sıralamanın vektörleştirilmiş bir versiyonu için aşağıya bakın. Np.unique'i seviyorum, hangi kodun verimli bir şekilde vektörleştirilip vektörize edilemeyeceğinin kapsamını gerçekten genişletiyor. Python for-loop'lardan kaçınmanın yanı sıra, bu yaklaşım aynı zamanda 'a' üzerinden örtük çift döngüyü de önler.

import numpy as np

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

a = np.array([4,2,7,2,1])
rank = a.argsort().argsort()

unique, inverse = np.unique(a, return_inverse = True)

unique_rank_sum = np.zeros_like(unique)
np.add.at(unique_rank_sum, inverse, rank)
unique_count = np.zeros_like(unique)
np.add.at(unique_count, inverse, 1)

unique_rank_mean = unique_rank_sum.astype(np.float) / unique_count

rank_mean = unique_rank_mean[inverse]

print rank_mean

bu arada; Bu kodu, diğer ortalama sıra koduyla aynı çıktıyı üretmek için yaptım, ancak bir grup tekrar eden sayının minimum sırasının da aynı şekilde çalıştığını hayal edebiliyorum. Bu, >>> benzersiz, dizin, ters = np.unique (a, Doğru, Doğru) >>> rank_min = sıra [dizin] [ters]
Eelco Hoogendoorn

Çözümünüzle ilgili şu hatayı alıyorum (numpy 1.7.1): AttributeError: 'numpy.ufunc' nesnesinin 'at' özniteliği yok
korkun

Bu, numpy'nin daha yeni bir sürümünü gerektirir; seninki oldukça eski
Eelco Hoogendoorn

4

Çözümlerin zarafeti ve kısalığının yanı sıra performans meselesi de var. İşte küçük bir ölçüt:

import numpy as np
from scipy.stats import rankdata
l = list(reversed(range(1000)))

%%timeit -n10000 -r5
x = (rankdata(l) - 1).astype(int)
>>> 128 µs ± 2.72 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)

%%timeit -n10000 -r5
a = np.array(l)
r = a.argsort().argsort()
>>> 69.1 µs ± 464 ns per loop (mean ± std. dev. of 5 runs, 10000 loops each)

%%timeit -n10000 -r5
a = np.array(l)
temp = a.argsort()
r = np.empty_like(temp)
r[temp] = np.arange(len(a))
>>> 63.7 µs ± 1.27 µs per loop (mean ± std. dev. of 5 runs, 10000 loops each)

1
İyi fikir, ancak adil bir karşılaştırma için kullanmalısınız rankdata(l, method='ordinal') - 1.
Warren Weckesser

3

Argsort () 'u iki kez kullanın, bunu yapacaktır:

>>> array = [4,2,7,1]
>>> ranks = numpy.array(array).argsort().argsort()
>>> ranks
array([2, 1, 3, 0])

2
cevabınızı vermeden önce bundan çok önce bahsedilmişti
Ciprian Tomoiagă

2

Yukarıdaki yöntemleri denedim, ancak başarısız oldum çünkü birçok zeorum vardı. Evet, kayan öğelerle bile yinelenen öğeler önemli olabilir.

Bu yüzden bir bağ kontrol adımı ekleyerek değiştirilmiş bir 1D çözümü yazdım:

def ranks (v):
    import numpy as np
    t = np.argsort(v)
    r = np.empty(len(v),int)
    r[t] = np.arange(len(v))
    for i in xrange(1, len(r)):
        if v[t[i]] <= v[t[i-1]]: r[t[i]] = r[t[i-1]]
    return r

# test it
print sorted(zip(ranks(v), v))

Olabildiğince verimli olduğuna inanıyorum.


0

Yöntemi k.rooijers tarafından beğendim, ancak rcoup'un yazdığı gibi, tekrarlanan sayılar dizi konumuna göre sıralanır. Bu benim için iyi değildi, bu yüzden sıraları son işlemden geçirmek ve tekrarlanan sayıları birleşik bir ortalama sıralamada birleştirmek için sürümü değiştirdim:

import numpy as np
a = np.array([4,2,7,2,1])
r = np.array(a.argsort().argsort(), dtype=float)
f = a==a
for i in xrange(len(a)):
   if not f[i]: continue
   s = a == a[i]
   ls = np.sum(s)
   if ls > 1:
      tr = np.sum(r[s])
      r[s] = float(tr)/ls
   f[s] = False

print r  # array([ 3. ,  1.5,  4. ,  1.5,  0. ])

Umarım bu başkalarına da yardımcı olabilir, buna başka bir çözüm bulmaya çalıştım ama bulamadım ...


0

argsort ve dilim simetri işlemleridir.

iki kez argsort yerine iki kez dilimi deneyin. dilim argsort'tan daha hızlı olduğu için

array = numpy.array([4,2,7,1])
order = array.argsort()
ranks = np.arange(array.shape[0])[order][order]

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.