Tengo dos matrices, una es una lista de valores y otra es una lista de ID correspondientes a cada valor. Algunas identificaciones tienen múltiples valores. Quiero crear una nueva matriz que contenga el valor máximo registrado para cada identificación, que tendrá una longitud igual a la cantidad de identificaciones únicas.
Ejemplo usando un bucle for :
import numpy as np values = np.array([5, 3, 2, 6, 3, 4, 8, 2, 4, 8]) ids = np.array([0, 1, 3, 3, 3, 3, 5, 6, 6, 6]) uniq_ids = np.unique(ids) maximums = np.ones_like(uniq_ids) * np.nan for i, id in enumerate(uniq_ids): maximums[i] = np.max(values[np.where(ids == id)]) print(uniq_ids) print(maximums) [0 1 3 5 6] [5. 3. 6. 8. 8.]¿Es posible vectorizar esto para que funcione rápido? Me imagino una línea que puede crear la matriz de "máximos" usando solo funciones NumPy, pero no he podido encontrar nada que funcione.
Aquí hay una solución que, aunque no está 100% vectorizada (según mis puntos de referencia), toma aproximadamente la mitad del tiempo que usted (usando sus datos de muestra). La mejora del rendimiento probablemente se vuelve más drástica con más datos:
maximums = [a.max() for a in np.split(values, np.arange(1, ids.shape[0])[(np.diff(ids) != 0)])]Producción:
>>> maximums [5, 3, 6, 8, 8]Al tratar de visualizar el problema:
In [82]: [np.where(ids==id) for id in uniq_ids] Out[82]: [(array([0]),), (array([1]),), (array([2, 3, 4, 5]),), (array([6]),), (array([7, 8, 9]),)] unique también puede devolver:
In [83]: np.unique(ids, return_inverse=True) Out[83]: (array([0, 1, 3, 5, 6]), array([0, 1, 2, 2, 2, 2, 3, 4, 4, 4])) Que es una variante de lo que produjo richardec :
In [88]: [a for a in np.split(ids, np.arange(1, ids.shape[0])[(np.diff(ids) != ...: 0)])] Out[88]: [array([0]), array([1]), array([3, 3, 3, 3]), array([5]), array([6, 6, 6])] Ese inverso también se produce haciendo where on all == a la vez:
In [90]: ids[:,None] == uniq_ids Out[90]: array([[ True, False, False, False, False], [False, True, False, False, False], [False, False, True, False, False], [False, False, True, False, False], [False, False, True, False, False], [False, False, True, False, False], [False, False, False, True, False], [False, False, False, False, True], [False, False, False, False, True], [False, False, False, False, True]]) In [91]: np.nonzero(ids[:,None] == uniq_ids) Out[91]: (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]), array([0, 1, 2, 2, 2, 2, 3, 4, 4, 4]))Todavía estoy pensando en esto...
EDITAR: Dejaré esto como un ejemplo de por qué no siempre podemos usar np.vectorize() para hacer que todo sea mágicamente más rápido:
Una solución es usar la función vectorizar de numpy:
import numpy as np values = np.array([5, 3, 2, 6, 3, 4, 8, 2, 4, 8]) ids = np.array([0, 1, 3, 3, 3, 3, 5, 6, 6, 6]) def my_func(id): return np.max(values[np.where(ids==id)]) vector_func = np.vectorize(my_func) maximums = vector_func(np.unique(ids))que regresa
array([5, 3, 6, 8, 8])Pero en cuanto a la velocidad, su versión tiene aproximadamente el mismo rendimiento cuando usamos
values = np.array([random.randint(1, 100) for i in range(1000000)]) ids = [] for i in range(100000): r = random.randint(1, 4) if r == 3: for x in range(3): ids.append(i) elif r == 2: for x in range(4): ids.append(i) else: ids.append(i) ids = np.array(ids)Son unos 12 segundos por ejecución.
Use np.lexsort para ordenar ambas listas simultáneamente:
idx = np.lexsort([values, ids])Los índices del último ID único vienen dados por
last = np.r_[np.flatnonzero(np.diff(ids[idx])), len(ids) - 1]Puedes usar esto para obtener el máximo de cada grupo:
values[idx[last]] Esto es lo mismo que values[idx][last] , pero más rápido porque solo necesita extraer elementos len(last) de esta manera, en lugar de reorganizar toda la matriz y luego extraer.
Tenga en cuenta que np.unique básicamente hace sort y flatnonzero cuando hace return_index=True .
Aquí hay una colección de todas las soluciones hasta ahora, junto con un punto de referencia. Sugiero una solución modificada para la receta de pandas de @ bb1 implementada a continuación.
def Shannon(values, ids): uniq_ids = np.unique(ids) maxima = np.ones_like(uniq_ids) * np.nan for i, id in enumerate(uniq_ids): maxima[i] = np.max(values[np.where(ids == id)]) return maxima def richardec(values, ids): return [a.max() for a in np.split(values, np.arange(1, ids.shape[0])[(np.diff(ids) != 0)])] def MadPhysicist(values, ids): idx = np.lexsort([values, ids]) return values[idx[np.r_[np.flatnonzero(np.diff(ids[idx])), len(ids) - 1]]] def PeptideWitch(values, ids): return np.vectorize(lambda id: np.max(values[np.where(ids == id)]))(np.unique(ids)) def mathfux(values, ids): idx = np.argsort(ids) return np.maximum.reduceat(values[idx], np.r_[0, np.flatnonzero(np.diff(ids[idx])) + 1]) def bb1(values, ids): return DataFrame({'ids': ids, 'vals': values}).groupby('ids')['values'].max().to_numpy() def bb1_modified(values, ids): return Series(values).groupby(ids).max().to_numpy() values = np.array([5, 3, 2, 6, 3, 4, 8, 2, 4, 8]) ids = np.array([0, 1, 3, 3, 3, 3, 5, 6, 6, 6]) %timeit Shannon(values, ids) 42.1 µs ± 561 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) %timeit richardec(values, ids) 27.7 µs ± 181 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) %timeit MadPhysicist(values, ids) 19.3 µs ± 268 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each) %timeit PeptideWitch(values, ids) 55.9 µs ± 1.29 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each) %timeit mathfux(values, ids) 20.9 µs ± 308 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each) %timeit bb1(values, ids) 865 µs ± 34.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit bb1_modified(values, ids) 537 µs ± 3.36 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) values = np.random.randint(10000, size=10000) ids = np.random.randint(100, size=10000) %timeit Shannon(values, ids) 1.76 ms ± 13.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit richardec(values, ids) 29.1 ms ± 510 µs per loop (mean ± std. dev. of 7 runs, 10 loops each) %timeit MadPhysicist(values, ids) 904 µs ± 20 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit PeptideWitch(values, ids) 1.74 ms ± 16.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit mathfux(values, ids) 372 µs ± 3.54 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit bb1(values, ids) 964 µs ± 9.35 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) %timeit bb1_modified(values, ids) 679 µs ± 7.86 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each) La sugerencia de @mathfux de usar np.maximum.reduceat es, con mucho, la más rápida, seguida de la solución pandas modificada y luego lexsorting. La solución de pandas probablemente sea muy similar a la de reduceat , pero con muchos más gastos generales. Puede ver que el cambio incremental para la solución pandas modificada es pequeño debido a eso.
Para matrices grandes (que son las únicas que importan), parece que encontrar los máximos individualmente es comparable a la pregunta, mientras que la comprensión de richardec es mucho más lenta, a pesar de que no ordena los valores de entrada según las ID.
Con pandas:
import pandas as pd def with_pandas(ids, vals): df = pd.DataFrame({'ids': ids, 'vals': values}) return df.groupby('ids')['vals'].max().to_numpy()Momento:
import numpy as np values = np.random.randint(10000, size=10000) ids = np.random.randint(100, size=10000) %timeit with_pandas(ids, values) 692 µs ± 21.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)np.lexsort ordena por varias columnas. Sin embargo, esto no es obligatorio. Puede ordenar las ids primero y luego elegir el elemento máximo de cada grupo dividido usando numpy.maximum.reduceat
def mathfux(values, ids, return_groups=False): argidx = np.argsort(ids) #70% time ids_sort, values_sort = ids[argidx], values[argidx] #4% time div_points = np.r_[0, np.flatnonzero(np.diff(ids_sort)) + 1] #11% time (the most part for np.flatnonzero) if return_groups: return ids[div_points], np.maximum.reduceat(values_sort, div_points) else: return np.maximum.reduceat(values_sort, div_points) mathfux(values, ids, return_groups=True) >>> (array([0, 1, 3, 5, 6]), array([5, 3, 6, 8, 8])) mathfux(values, ids) >>> mathfux(values, ids) array([5, 3, 6, 8, 8]) Por lo general, algunas partes de los códigos numpy podrían optimizarse aún más en numba . Tenga en cuenta que np.argsort es un cuello de botella en la mayoría de los problemas de groupby que no se pueden reemplazar con ningún otro método. Es poco probable que se mejore pronto en numba o numpy . Por lo tanto, está alcanzando un rendimiento óptimo aquí y no puede hacer mucho en futuras optimizaciones.