Xếp hạng các mục trong một mảng bằng Python / NumPy, mà không cần sắp xếp mảng hai lần


100

Tôi có một mảng số và tôi muốn tạo một mảng khác đại diện cho thứ hạng của từng mục trong mảng đầu tiên. Tôi đang sử dụng Python và NumPy.

Ví dụ:

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

Đây là phương pháp tốt nhất mà tôi đã nghĩ ra:

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

Có phương pháp nào tốt hơn / nhanh hơn để tránh sắp xếp mảng hai lần không?


6
Dòng cuối cùng của bạn tương đương với ranks = temp.argsort().
Sven Marnach

Câu trả lời:


67

Sử dụng cách cắt ở phía bên trái trong bước cuối cùng:

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

Điều này tránh sắp xếp hai lần bằng cách đảo ngược hoán vị trong bước cuối cùng.


3
Hoàn hảo, cảm ơn bạn! Tôi biết có một giải pháp và nó sẽ hiển nhiên khi tôi nhìn thấy nó. Tôi đã thực hiện một số thử nghiệm với timeit và phương pháp này hơi chậm hơn đối với các mảng nhỏ. Trên máy của tôi, chúng bằng nhau khi mảng có 2.000 phần tử. Với 20.000 phần tử, phương pháp của bạn nhanh hơn khoảng 25%.
joshayers

bất kỳ khuyến nghị nào về cách làm việc này?
Xaser

Để có hơn 1 câu trả lời lờ mờ, hãy xem bên dưới.
mathtick

100

Sử dụng argsort hai lần, trước tiên để lấy thứ tự của mảng, sau đó để nhận thứ hạng:

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

Khi xử lý mảng 2D (hoặc chiều cao hơn), hãy đảm bảo chuyển đối số trục cho argsort để sắp xếp theo trục chính xác.


2
(. Ví dụ Lưu ý rằng nếu con số này được lặp đi lặp lại trong mảng đầu vào của bạn [4,2,7,1,1]) đầu ra sẽ xếp hạng những con số dựa trên vị trí mảng của họ ( [3,2,4,0,1])
rcoup

4
Sắp xếp hai lần là không hiệu quả. Câu trả lời của @Sven Marnach cho thấy cách hoàn thành xếp hạng chỉ với một lệnh gọi tới argsort.
Warren Weckesser

6
@WarrenWeckesser: Tôi vừa kiểm tra sự khác biệt giữa hai và bạn phù hợp với mảng lớn, nhưng đối với bất kỳ mảng nào nhỏ hơn (n <100), double argsort nhanh hơn (nhanh hơn khoảng 20% ​​đối với n = 100 và nhanh hơn khoảng 5 lần cho n = 10). Vì vậy, nếu bạn phải thực hiện nhiều xếp hạng trên rất nhiều bộ giá trị nhỏ, phương pháp này tốt hơn nhiều .
naught101

3
@WarrenWeckesser: Thực ra, tôi sai, phương pháp này tốt hơn. Cả hai phương pháp cũng nhanh hơn nhiều so với phương pháp scipy.stats. Kết quả: gist.github.com/naught101/14042d91a2d0f18a6ae4
naught101

1
@ naught101: Có một lỗi trong tập lệnh của bạn. Dòng array = np.random.rand(10)nên được array = np.random.rand(n).
Warren Weckesser

88

Câu hỏi này đã có từ vài năm trước, và câu trả lời được chấp nhận là tuyệt vời, nhưng tôi nghĩ những điều sau đây vẫn đáng nói. Nếu bạn không ngại phụ thuộc vào scipy, bạn có thể sử dụng 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])

Một tính năng hay rankdatamethodđối số cung cấp một số tùy chọn để xử lý các mối quan hệ. Ví dụ: có ba lần xuất hiện 20 và hai lần xuất hiện 40 trong b:

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

Mặc định chỉ định thứ hạng trung bình cho các giá trị ràng buộc:

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

method='ordinal' xếp các cấp bậc liên tiếp:

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

method='min' chỉ định thứ hạng tối thiểu của các giá trị được ràng buộc cho tất cả các giá trị được ràng buộc:

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

Xem docstring để có thêm tùy chọn.


1
vâng, đây là câu trả lời tốt nhất ở bất kỳ nơi nào mà các trường hợp cạnh là quan trọng.
naught101

Tôi thấy điều thú vị là rankdatadường như sử dụng cơ chế giống như câu trả lời được chấp nhận để tạo xếp hạng ban đầu trong nội bộ.
AlexV

5

Tôi đã cố gắng mở rộng cả hai giải pháp cho các mảng A có nhiều hơn một chiều, giả sử bạn xử lý mảng của mình theo từng hàng (axis = 1).

Tôi đã mở rộng mã đầu tiên với một vòng lặp trên các hàng; có lẽ nó có thể được cải thiện

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

Và cái thứ hai, theo gợi ý của k.rooijers, trở thành:

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

Tôi tạo ngẫu nhiên 400 mảng có hình dạng (1000,100); mã đầu tiên mất khoảng 7,5, mã thứ hai 3,8.


5

Để biết phiên bản vector hóa của xếp hạng trung bình, hãy xem bên dưới. Tôi yêu np.unique, nó thực sự mở rộng phạm vi của những gì mã có thể và không thể được vector hóa một cách hiệu quả. Ngoài việc tránh các vòng lặp for trong python, cách tiếp cận này cũng tránh được vòng lặp kép ngầm trên '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

nhân tiện; Tôi đã tạo mã này để tạo ra cùng một đầu ra như mã xếp hạng trung bình khác, nhưng tôi có thể tưởng tượng xếp hạng tối thiểu của một nhóm các số lặp lại cũng hoạt động như vậy. Điều này có thể thu được thậm chí nhiều hơn một cách dễ dàng như >>> độc đáo, chỉ số, nghịch đảo = np.unique (a, True, True) >>> rank_min = rank [index] [nghịch]
Eelco Hoogendoorn

Tôi nhận được lỗi sau với giải pháp của bạn (NumPy 1.7.1): AttributeError: 'numpy.ufunc' đối tượng không có thuộc tính 'at'
Fear

Điều này yêu cầu một phiên bản mới hơn của numpy; bạn là khá cổ xưa
Eelco Hoogendoorn

4

Ngoài sự sang trọng và ngắn gọn của các giải pháp, còn có câu hỏi về hiệu suất. Đây là một điểm chuẩn nhỏ:

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
Ý tưởng hay, nhưng để so sánh công bằng, bạn nên sử dụng rankdata(l, method='ordinal') - 1.
Warren Weckesser

3

Sử dụng argsort () hai lần sẽ làm được điều đó:

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

2
này đã được đề cập cách trước khi bạn đặt ra câu trả lời của bạn
Ciprian Tomoiagă

2

Tôi đã thử các phương pháp trên, nhưng không thành công vì tôi có nhiều điểm không. Có, ngay cả với float các mục trùng lặp có thể quan trọng.

Vì vậy, tôi đã viết một giải pháp 1D được sửa đổi bằng cách thêm bước kiểm tra ràng buộc:

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

Tôi tin rằng nó hiệu quả nhất có thể.


0

Tôi thích phương pháp của k.rooijers, nhưng như rcoup đã viết, các số lặp lại được xếp hạng theo vị trí mảng. Điều này không tốt cho tôi, vì vậy tôi đã sửa đổi phiên bản để xử lý sau xếp hạng và hợp nhất mọi số lặp lại thành xếp hạng trung bình kết hợp:

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

Tôi hy vọng điều này cũng có thể giúp ích cho những người khác, tôi đã cố gắng tìm giải pháp an toàn cho vấn đề này, nhưng không tìm thấy ...


0

argsort và slice là các phép toán đối xứng.

thử cắt hai lần thay vì argsort hai lần. vì lát cắt nhanh hơn argsort

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.