จัดอันดับรายการในอาร์เรย์โดยใช้ Python / NumPy โดยไม่ต้องเรียงลำดับอาร์เรย์สองครั้ง


103

ฉันมีอาร์เรย์ของตัวเลขและฉันต้องการสร้างอาร์เรย์อื่นที่แสดงถึงอันดับของแต่ละรายการในอาร์เรย์แรก ฉันใช้ Python และ NumPy

ตัวอย่างเช่น:

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

นี่เป็นวิธีที่ดีที่สุดที่ฉันคิดขึ้น:

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

มีวิธีใดที่ดีกว่า / เร็วกว่าที่หลีกเลี่ยงการเรียงอาร์เรย์สองครั้ง


6
ranks = temp.argsort()บรรทัดสุดท้ายของคุณจะเทียบเท่ากับ
Sven Marnach

คำตอบ:


68

ใช้การหั่นทางด้านซ้ายในขั้นตอนสุดท้าย:

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

วิธีนี้จะหลีกเลี่ยงการเรียงลำดับสองครั้งโดยการย้อนกลับการเปลี่ยนแปลงในขั้นตอนสุดท้าย


3
สมบูรณ์แบบขอบคุณ! ฉันรู้ว่ามีทางแก้ไขและดูเหมือนจะชัดเจนเมื่อฉันเห็น ฉันทำการทดสอบด้วย timeit และวิธีนี้ช้ากว่าเล็กน้อยสำหรับอาร์เรย์ขนาดเล็ก บนเครื่องของฉันมันเท่ากันเมื่ออาร์เรย์มี 2,000 องค์ประกอบ ที่ 20,000 องค์ประกอบวิธีของคุณเร็วขึ้นประมาณ 25%
joshayers

มีคำแนะนำเกี่ยวกับการทำ rowwise นี้หรือไม่?
Xaser

สำหรับมากกว่า 1 สลัวดูคำตอบด้านล่าง
mathtick

101

ใช้ argsort สองครั้งอันดับแรกเพื่อรับลำดับของอาร์เรย์จากนั้นจึงจะได้รับการจัดอันดับ:

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

เมื่อจัดการกับอาร์เรย์ 2 มิติ (หรือมิติที่สูงกว่า) ตรวจสอบให้แน่ใจว่าได้ส่งอาร์กิวเมนต์แกนไปยัง argsort เพื่อเรียงลำดับบนแกนที่ถูกต้อง


2
โปรดทราบว่าหากตัวเลขซ้ำกันในอาร์เรย์อินพุตของคุณ (เช่น[4,2,7,1,1]) เอาต์พุตจะจัดอันดับตัวเลขเหล่านั้นตามตำแหน่งอาร์เรย์ ( [3,2,4,0,1])
rcoup

4
การเรียงลำดับสองครั้งไม่มีประสิทธิภาพ @Sven Marnach argsortของคำตอบที่แสดงให้เห็นถึงวิธีการที่จะประสบความสำเร็จในการจัดอันดับที่มีสายเดียวที่จะ
Warren Weckesser

6
@WarrenWeckesser: ฉันเพิ่งทดสอบความแตกต่างระหว่างสองและคุณเหมาะกับอาร์เรย์ขนาดใหญ่ แต่สำหรับอะไรที่เล็กกว่า (n <100) double argsort จะเร็วกว่า (เร็วขึ้นประมาณ 20% สำหรับ n = 100 และเร็วกว่าประมาณ 5 เท่า สำหรับ n = 10) ดังนั้นหากคุณต้องทำมากของการจัดอันดับทั่วจำนวนมากชุดเล็กของค่าวิธีนี้มากดีกว่า
naught101

3
@WarrenWeckesser: อันที่จริงฉันผิดวิธีนี้ดีกว่า ทั้งสองวิธีนั้นเร็วกว่าวิธี scipy.stats มากเช่นกัน ผลลัพธ์: gist.github.com/naught101/14042d91a2d0f18a6ae4
naught101

1
@ naught101: มีข้อบกพร่องในสคริปต์ของคุณ บรรทัดที่ควรจะเป็นarray = np.random.rand(10) array = np.random.rand(n)
Warren Weckesser

89

คำถามนี้มีอายุไม่กี่ปีและคำตอบที่ได้รับการยอมรับนั้นยอดเยี่ยม แต่ฉันคิดว่าสิ่งต่อไปนี้ยังควรค่าแก่การกล่าวถึง หากคุณไม่คำนึงถึงการพึ่งพาscipyคุณสามารถใช้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])

คุณลักษณะที่ดีrankdataคือmethodอาร์กิวเมนต์มีตัวเลือกมากมายในการจัดการความสัมพันธ์ ตัวอย่างเช่นมีเหตุการณ์ที่เกิดขึ้นสามครั้งจาก 20 ครั้งและเกิดขึ้นสองครั้งจาก 40 ในb:

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

ค่าเริ่มต้นจะกำหนดอันดับเฉลี่ยให้กับค่าที่เชื่อมโยงกัน:

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

method='ordinal' กำหนดตำแหน่งต่อเนื่อง:

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

method='min' กำหนดอันดับต่ำสุดของค่าที่ผูกให้กับค่าที่ผูกทั้งหมด:

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

ดูตัวเลือกเพิ่มเติมใน docstring


1
ใช่นี่คือคำตอบที่ดีที่สุดในกรณีที่ขอบมีความสำคัญ
naught101

ฉันคิดว่ามันน่าสนใจที่rankdataดูเหมือนจะใช้กลไกเดียวกันกับคำตอบที่ได้รับการยอมรับในการสร้างอันดับเริ่มต้นภายใน
AlexV

5

ฉันพยายามขยายโซลูชันทั้งสองสำหรับอาร์เรย์ A มากกว่าหนึ่งมิติสมมติว่าคุณประมวลผลอาร์เรย์ของคุณทีละแถว (แกน = 1)

ฉันขยายรหัสแรกด้วยการวนซ้ำในแถว อาจจะดีขึ้น

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

และอันที่สองตามคำแนะนำของ k.rooijers กลายเป็น:

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

ฉันสร้างอาร์เรย์ 400 แบบสุ่มที่มีรูปร่าง (1,000,100); รหัสแรกใช้เวลาประมาณ 7.5 รหัสที่สอง 3.8


5

สำหรับอันดับเฉลี่ยในเวอร์ชันเวกเตอร์โปรดดูด้านล่าง ฉันชอบ np.unique มันขยายขอบเขตของโค้ดที่สามารถทำได้และไม่สามารถเป็นเวกเตอร์ได้อย่างมีประสิทธิภาพ นอกเหนือจากการหลีกเลี่ยง python for-loops แล้ววิธีนี้ยังหลีกเลี่ยงการวนซ้ำสองครั้งโดยปริยายเหนือ 'a'

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

ยังไงซะ; ฉันสร้างรหัสนี้เพื่อสร้างผลลัพธ์เดียวกันกับรหัสอันดับเฉลี่ยอื่น ๆ แต่ฉันสามารถจินตนาการได้ว่าอันดับต่ำสุดของกลุ่มตัวเลขที่ทำซ้ำก็ใช้ได้เช่นกัน สิ่งนี้สามารถรับได้ง่ายยิ่งขึ้นเช่น >>> unique, index, inverse = np.unique (a, True, True) >>> rank_min = rank [index] [inverse]
Eelco Hoogendoorn

ฉันได้รับข้อผิดพลาดต่อไปนี้กับโซลูชันของคุณ (numpy 1.7.1): AttributeError: วัตถุ 'numpy.ufunc' ไม่มีแอตทริบิวต์ 'at'
Fear

สิ่งนี้ต้องใช้ numpy เวอร์ชันล่าสุด ของคุณค่อนข้างโบราณ
Eelco Hoogendoorn

4

นอกเหนือจากความสวยงามและความสั้นของการแก้ปัญหาแล้วยังมีคำถามเกี่ยวกับประสิทธิภาพ นี่คือเกณฑ์มาตรฐานเล็กน้อย:

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
คิดที่ดี rankdata(l, method='ordinal') - 1แต่สำหรับการเปรียบเทียบธรรมคุณควรใช้
Warren Weckesser


2

ฉันลองใช้วิธีการข้างต้น แต่ล้มเหลวเพราะฉันมีซีออเรสมากมาย ใช่แม้ว่ารายการที่ซ้ำกันจะมีความสำคัญ

ดังนั้นฉันจึงเขียนโซลูชัน 1D ที่แก้ไขแล้วโดยเพิ่มขั้นตอนการตรวจสอบการผูก:

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

ฉันเชื่อว่ามันมีประสิทธิภาพมากที่สุดเท่าที่จะทำได้


0

ฉันชอบวิธีการของ k.rooijers แต่ตามที่ rcoup เขียนตัวเลขที่ซ้ำกันจะถูกจัดอันดับตามตำแหน่งอาร์เรย์ สิ่งนี้ไม่ดีสำหรับฉันดังนั้นฉันจึงแก้ไขเวอร์ชันเพื่อประมวลผลหลังการจัดอันดับและรวมตัวเลขที่ซ้ำ ๆ กันเป็นอันดับเฉลี่ยรวม:

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

ฉันหวังว่านี่อาจช่วยคนอื่นได้เช่นกันฉันพยายามหาวิธีแก้ปัญหานี้ แต่ไม่พบ ...


0

argsort และ slice เป็นการดำเนินการแบบสมมาตร

ลองฝานสองครั้งแทนที่จะเป็น argsort สองครั้ง เนื่องจาก slice เร็วกว่า argsort

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

0

คำตอบรุ่นทั่วไปเพิ่มเติม:

In [140]: x = np.random.randn(10, 3)

In [141]: i = np.argsort(x, axis=0)

In [142]: ranks = np.empty_like(i)

In [143]: np.put_along_axis(ranks, i, np.repeat(np.arange(x.shape[0])[:,None], x.shape[1], axis=1), axis=0)

ดูวิธีใช้ numpy.argsort () เป็นดัชนีมากกว่า 2 มิติ? เพื่อสรุปให้เป็นสลัวมากขึ้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.