Transforme un conjunto de números en números para que cada número se convierta en un número de otros números que sean menores que él.

Considere un conjunto de números:

In [8]: import numpy as np In [9]: x = np.array([np.random.random() for i in range(10)]) In [10]: x Out[10]: array([ 0.62594394, 0.03255799, 0.7768568 , 0.03050498, 0.01951657, 0.04767246, 0.68038553, 0.60036203, 0.3617409 , 0.80294355]) 

Ahora quiero transformar este conjunto en otro conjunto y de la siguiente manera: para cada elemento i en x , el elemento correspondiente j en y sería el número de otros elementos en x que son menores que i . Por ejemplo, la x dada anteriormente se vería como:

 In [25]: y Out[25]: array([ 6., 2., 8., 1., 0., 3., 7., 5., 4., 9.]) 

Ahora, puedo hacer esto usando simples bucles de python:

 In [16]: for i in range(len(x)): ...: tot = 0 ...: for j in range(len(x)): ...: if x[i] > x[j]: tot += 1 ...: y[i] = int(tot) 

Sin embargo, cuando la longitud de x es muy grande, el código se vuelve extremadamente lento. Me preguntaba si alguna magia numpy puede ser rescatada. Por ejemplo, si tuviera que filtrar todos los elementos de menos de 0.5 , simplemente habría usado una máscara booleana:

 In [19]: z = x[x < 0.5] In [20]: z Out[20]: array([ 0.03255799, 0.03050498, 0.01951657, 0.04767246, 0.3617409 ]) 

¿Se puede usar algo como esto para poder lograr lo mismo mucho más rápido?

Lo que realmente necesita hacer es obtener el inverso del orden de clasificación de su matriz:

 import numpy as np x = np.random.rand(10) y = np.empty(x.size,dtype=np.int64) y[x.argsort()] = np.arange(x.size) 

Ejemplo de ejecución (en ipython):

 In [367]: x Out[367]: array([ 0.09139335, 0.29084225, 0.43560987, 0.92334644, 0.09868977, 0.90202354, 0.80905083, 0.4801967 , 0.99086213, 0.00933582]) In [368]: y Out[368]: array([1, 3, 4, 8, 2, 7, 6, 5, 9, 0]) 

Alternativamente, si desea obtener el número de elementos mayor que cada elemento correspondiente en x , debe revertir la clasificación de ascendente a descendente. Una posible opción para hacer esto es simplemente intercambiar la construcción de la indexación:

 y_rev = np.empty(x.size,dtype=np.int64) y_rev[x.argsort()] = np.arange(x.size)[::-1] 

otra, como @unutbu sugirió en un comentario , es asignar la matriz original a la nueva:

 y_rev = x.size - y - 1 

Aquí hay un enfoque usando np.searchsorted

 np.searchsorted(np.sort(x),x) 

Otro basado principalmente en @Andras Deak's solution usando argsort()

 x.argsort().argsort() 

Ejecución de la muestra

 In [359]: x Out[359]: array([ 0.62594394, 0.03255799, 0.7768568 , 0.03050498, 0.01951657, 0.04767246, 0.68038553, 0.60036203, 0.3617409 , 0.80294355]) In [360]: np.searchsorted(np.sort(x),x) Out[360]: array([6, 2, 8, 1, 0, 3, 7, 5, 4, 9]) In [361]: x.argsort().argsort() Out[361]: array([6, 2, 8, 1, 0, 3, 7, 5, 4, 9]) 

Además de las otras respuestas, otra solución que utiliza la indexación booleana podría ser:

 sum(x > i for i in x) 

Para su ejemplo:

 In [10]: x Out[10]: array([ 0.62594394, 0.03255799, 0.7768568 , 0.03050498, 0.01951657, 0.04767246, 0.68038553, 0.60036203, 0.3617409 , 0.80294355]) In [10]: y = sum(x > i for i in x) In [11]: y Out[10]: array([6, 2, 8, 1, 0, 3, 7, 5, 4, 9]) 

Quería contribuir a esta publicación proporcionando algunas pruebas en la solución de @Andras Deak en lugar de argsort nuevamente.


Parece que argsort nuevo es más rápido para arreglos cortos. Una idea simple es evaluar cuál es la longitud de la matriz en la que vemos el cambio de balance.

Definiré tres funciones.

  • construct cuál es la solución de Andras Deak.
  • argsortagain que es obvio
  • attempted_optimal que se negocia en len(a) == 400

funciones

 def argsortagain(s): return s.argsort() def construct(s): u = np.empty(s.size, dtype=np.int64) u[s] = np.arange(s.size) return u def attempted_optimal(s): return argsortagain(s) if len(s) < 400 else construct(s) 

pruebas

 results = pd.DataFrame( index=pd.RangeIndex(10, 610, 10, 'len'), columns=pd.Index(['construct', 'argsortagain', 'attempted_optimal'], name='function')) for i in results.index: a = np.random.rand(i) s = a.argsort() for j in results.columns: results.set_value( i, j, timeit( '{}(s)'.format(j), 'from __main__ import {}, s'.format(j), number=10000) ) results.plot() 

introduzca la descripción de la imagen aquí

conclusión

attempted_optimal hace lo que se supone que haga. Pero no estoy seguro de que valga la pena por el beneficio marginal obtenido en un espectro de longitud de matriz (sub 400) donde apenas importa. Yo abogaría completamente por solo constructed .

Este análisis me ayudó a llegar a esta conclusión.