Efficient method to calculate the rank vector of a list in Python

后端 未结 11 923
温柔的废话
温柔的废话 2020-12-02 20:06

I\'m looking for an efficient way to calculate the rank vector of a list in Python, similar to R\'s rank function. In a simple list with no ties between the ele

相关标签:
11条回答
  • 2020-12-02 20:45

    Here is a small variation of unutbu's code, including an optional 'method' argument for the type of value of tied ranks.

    def rank_simple(vector):
        return sorted(range(len(vector)), key=vector.__getitem__)
    
    def rankdata(a, method='average'):
        n = len(a)
        ivec=rank_simple(a)
        svec=[a[rank] for rank in ivec]
        sumranks = 0
        dupcount = 0
        newarray = [0]*n
        for i in xrange(n):
            sumranks += i
            dupcount += 1
            if i==n-1 or svec[i] != svec[i+1]:
                for j in xrange(i-dupcount+1,i+1):
                    if method=='average':
                        averank = sumranks / float(dupcount) + 1
                        newarray[ivec[j]] = averank
                    elif method=='max':
                        newarray[ivec[j]] = i+1
                    elif method=='min':
                        newarray[ivec[j]] = i+1 -dupcount+1
                    else:
                        raise NameError('Unsupported method')
    
                sumranks = 0
                dupcount = 0
    
    
        return newarray
    
    0 讨论(0)
  • 2020-12-02 20:48

    I really don't get why all the existing solutions are so complex. This can be done just like this:

    [index for element, index in sorted(zip(sequence, range(len(sequence))))]
    

    You build tuples which contain the elements and a running index. Then you sort the whole thing, and tuples sort by their first element and during ties by their second element. This way one has a sorted list of these tuples and just need to pick out the indices from that afterwards. Also this removes the need to look up elements in the sequence afterwards, which likely makes it a O(N²) operation whereas this is O(N log(N)).

    0 讨论(0)
  • 2020-12-02 20:48

    So.. this is 2019, and I have no idea why nobody suggested the following:

    # Python-only
    def rank_list( x, break_ties=False ):
        n = len(x)
        t = list(range(n))
        s = sorted( t, key=x.__getitem__ )
    
        if not break_ties:
            for k in range(n-1):
                t[k+1] = t[k] + (x[s[k+1]] != x[s[k]])
    
        r = s.copy()
        for i,k in enumerate(s):
            r[k] = t[i]
    
        return r
    
    # Using Numpy, see also: np.argsort
    def rank_vec( x, break_ties=False ):
        n = len(x)
        t = np.arange(n)
        s = sorted( t, key=x.__getitem__ )
    
        if not break_ties:
            t[1:] = np.cumsum(x[s[1:]] != x[s[:-1]])
    
        r = t.copy()
        np.put( r, s, t )
        return r
    

    This approach has linear runtime complexity after the initial sort, it only stores 2 arrays of indices, and does not require values to be hashable (only pairwise comparison needed).

    AFAICT, this is better than other approaches suggested so far:

    • @unutbu's approach is essentially similar, but (I would argue) too complicated for what the OP asked;
    • All suggestions using .index() are terrible, with a runtime complexity of N^2;
    • @Yuvraj Singh improves slightly upon the .index() search using a dictionary, however with search and insert operations at each iteration, this is still highly inefficient both in time (NlogN) and space, and it also requires the values to be hashable.
    0 讨论(0)
  • 2020-12-02 20:51

    These codes give me a lot of inspiration, especially unutbu's code. However my needs are simpler, so I changed the code a little.

    Hoping to help the guys with the same needs.

    Here is the class to record the players' scores and ranks.

    class Player():
        def __init__(self, s, r):
            self.score = s
            self.rank = r
    

    Some data.

    l = [Player(90,0),Player(95,0),Player(85,0), Player(90,0),Player(95,0)]
    

    Here is the code for calculation:

    l.sort(key=lambda x:x.score, reverse=True)    
    l[0].rank = 1
    dupcount = 0
    prev = l[0]
    for e in l[1:]:
        if e.score == prev.score:
            e.rank = prev.rank
            dupcount += 1
        else:
            e.rank = prev.rank + dupcount + 1
            dupcount = 0
            prev = e
    
    0 讨论(0)
  • 2020-12-02 20:51

    This works for the spearman correlation coefficient .

    def get_rank(X, n):
        x_rank = dict((x, i+1) for i, x in enumerate(sorted(set(X))))
        return [x_rank[x] for x in X]
    
    0 讨论(0)
  • 2020-12-02 20:55
    [sorted(l).index(x) for x in l]
    

    sorted(l) will give the sorted version index(x) will give the index in the sorted array

    for example :

    l = [-1, 3, 2, 0,0]
    >>> [sorted(l).index(x) for x in l]
    [0, 4, 3, 1, 1]
    
    0 讨论(0)
提交回复
热议问题