2011-03-12 22 views
49

Tengo una matriz de números y me gustaría crear otra matriz que represente el rango de cada elemento en la primera matriz. Estoy usando Python y NumPy.Clasifique los elementos en una matriz usando Python/NumPy

Por ejemplo:

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

es el mejor método que he llegado con Aquí:

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

¿Hay métodos mejores/más rápidas que eviten la clasificación de la matriz dos veces?

+5

Su última línea es equivalente a 'ranks = temp.argsort()'. –

Respuesta

33

Uso rebanar en el lado izquierdo en el último paso:

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

Esto evita clasificar en dos ocasiones mediante la inversión de la permutación en el último paso.

+1

Perfecto, gracias! Sabía que había una solución y parecería obvio una vez que la viera. Hice algunas pruebas con timeit, y este método es un poco más lento para matrices pequeñas. En mi máquina son iguales cuando la matriz tiene 2,000 elementos. Con 20,000 elementos, su método es aproximadamente un 25% más rápido. – joshayers

+0

alguna recomendación sobre cómo hacer esto rowwise? – Xaser

62

Uso argsort dos veces, primero para obtener el orden de la matriz, a continuación, para obtener mejores resultados:

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

Cuando se trata de 2D (o mayores dimensiones) arrays, asegúrese de pasar un argumento eje para argsort a orden sobre el eje correcto.

+2

Tenga en cuenta que si los números se repiten en su matriz de entrada (por ejemplo, '[4,2,7,1,1]') la salida clasificará esos números en función de su posición de matriz ('[3,2,4,0, 1] ') – rcoup

+0

¡Gran respuesta! Muy pitónico. –

+4

Clasificar dos veces es ineficiente. La respuesta de @Sven Marnach muestra cómo lograr el ranking con una sola llamada a 'argsort'. –

4

Intenté extender ambas soluciones para las matrices A de más de una dimensión, suponiendo que procesa su matriz fila por fila (eje = 1).

Amplié el primer código con un bucle en las filas; Probablemente se puede mejorar

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 

Y el segundo, tras k.rooijers sugerencia, se convierte en:

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

me genera aleatoriamente 400 matrices con forma (1000,100); el primer código tomó aproximadamente 7.5, el segundo 3.8.

2

Probé los métodos anteriores, pero fallaron porque tenía muchos zeores. Sí, incluso con flotadores, los elementos duplicados pueden ser importantes.

Así que escribí una solución 1D modificado mediante la adición de un paso lazo de comprobación:

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

creo que es todo lo eficiente que puede ser.

0

Me gustó el método de k.rooijers, pero como escribió rcoup, los números repetidos se clasifican según la posición de la matriz. Esto no era bueno para mí, así que modificó la versión de post-procesamiento de las filas y combinar cualquier número repetidas en un rango promedio combinado:

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

espero que esto podría ayudar a los demás también, he intentado encontrar anothers solución a este , pero no pudo encontrar ninguna ...

3

Para obtener una versión vectorizada de un rango promediado, consulte a continuación. Me encanta np.unique, realmente amplía el alcance de lo que el código puede y no puede vectorizarse eficientemente. Además de evitar los bucles for de python, este enfoque también evita el doble bucle implícito sobre '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 
+0

por cierto; Hice este código para producir el mismo resultado que el otro código de rango promediado, pero puedo imaginar que el rango mínimo de un grupo de números repetitivos también funciona. Esto se puede obtener aún más fácilmente como >>> unique, index, inverse = np.unique (a, True, True) >>> rank_min = rank [index] [inverse] –

+0

Recibo el siguiente error con su solución (numpy 1.7.1): AttributeError: 'numpy.El objeto ufunc 'no tiene ningún atributo' en ' – Fear

+0

Esto requiere una versión más reciente de numpy; el tuyo es bastante antiguo –

3

Uso argsort() dos veces lo hará:

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

esto ya fue [ya mencionado] (http://stackoverflow.com/a/6266510/786559) mucho antes de que plantearas tu respuesta –

46

Esta pregunta se hace un par de años, y la respuesta aceptada es grande, pero creo que la siguiente es todavía vale la pena mencionar. Si no te importa la dependencia de scipy, puede utilizar 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]) 

Una característica interesante de rankdata es que el argumento method ofrece varias opciones para controlar las coincidencias. Por ejemplo, hay tres ocurrencias de 20 y dos apariciones de 40 en b:

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

El valor predeterminado asigna el rango promedio de los valores atadas:

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

method='ordinal' asigna filas consecutivas:

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

method='min' asigna el rango mínimo de los valores vinculados a todos los valores vinculados:

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

Consulte la docstring para ver más opciones.

+1

Tu respuesta es de hecho muy apropiada. No estoy seguro de por qué no votaste por dar una opción perfectamente legítima. – Dexter

+1

Sí, esta es la mejor respuesta en cualquier lugar donde los casos extremos son importantes. – naught101

Cuestiones relacionadas