Agrupar por max o min en una matriz numpy

Tengo dos matrices de 1D de igual longitud, id y data , donde id es una secuencia de enteros ordenados y repetitivos que definen subventanas en los data . Por ejemplo,

 id data 1 2 1 7 1 3 2 8 2 9 2 10 3 1 3 -10 

Me gustaría agregar data agrupándolos en id y tomando el máximo o el mínimo. En SQL, esta sería una consulta de agregación típica como SELECT MAX(data) FROM tablename GROUP BY id ORDER BY id . ¿Hay alguna manera de evitar los bucles de Python y hacer esto de forma vectorial, o tengo que bajar a C?

He estado viendo algunas preguntas muy similares en el desbordamiento de stack en los últimos días. El siguiente código es muy similar a la implementación de numpy.unique y debido a que aprovecha la maquinaria de numpy subyacente, es más probable que sea más rápido que cualquier cosa que pueda hacer en un bucle de python.

 import numpy as np def group_min(groups, data): # sort with major key groups, minor key data order = np.lexsort((data, groups)) groups = groups[order] # this is only needed if groups is unsorted data = data[order] # construct an index which marks borders between groups index = np.empty(len(groups), 'bool') index[0] = True index[1:] = groups[1:] != groups[:-1] return data[index] #max is very similar def group_max(groups, data): order = np.lexsort((data, groups)) groups = groups[order] #this is only needed if groups is unsorted data = data[order] index = np.empty(len(groups), 'bool') index[-1] = True index[:-1] = groups[1:] != groups[:-1] return data[index] 

En puro Python:

 from itertools import groupby, imap, izip from operator import itemgetter as ig print [max(imap(ig(1), g)) for k, g in groupby(izip(id, data), key=ig(0))] # -> [7, 10, 1] 

Una variacion:

 print [data[id==i].max() for i, _ in groupby(id)] # -> [7, 10, 1] 

Basado en la respuesta de @Bago :

 import numpy as np # sort by `id` then by `data` ndx = np.lexsort(keys=(data, id)) id, data = id[ndx], data[ndx] # get max() print data[np.r_[np.diff(id), True].astype(np.bool)] # -> [ 7 10 1] 

Si se instala pandas :

 from pandas import DataFrame df = DataFrame(dict(id=id, data=data)) print df.groupby('id')['data'].max() # id # 1 7 # 2 10 # 3 1 

Soy bastante nuevo en Python y Numpy, pero parece que puedes usar el método .at de ufunc s en lugar de reduceat :

 import numpy as np data_id = np.array([0,0,0,1,1,1,1,2,2,2,3,3,3,4,5,5,5]) data_val = np.random.rand(len(data_id)) ans = np.empty(data_id[-1]+1) # might want to use max(data_id) and zeros instead np.maximum.at(ans,data_id,data_val) 

Por ejemplo:

 data_val = array([ 0.65753453, 0.84279716, 0.88189818, 0.18987882, 0.49800668, 0.29656994, 0.39542769, 0.43155428, 0.77982853, 0.44955868, 0.22080219, 0.4807312 , 0.9288989 , 0.10956681, 0.73215416, 0.33184318, 0.10936647]) ans = array([ 0.98969952, 0.84044947, 0.63460516, 0.92042078, 0.75738113, 0.37976055]) 

Por supuesto, esto solo tiene sentido si sus valores de data_id son adecuados para su uso como índices (es decir, enteros no negativos y no grandes … presumiblemente, si son grandes / dispersos, podría inicializar ans utilizando np.unique(data_id) o algo así).

Debo señalar que el data_id realidad no necesita ser ordenado.

He empaquetado una versión de mi respuesta anterior en el paquete numpy_indexed ; es bueno tener todo esto envuelto y probado en una interfaz limpia; Además tiene mucha más funcionalidad también:

 import numpy_indexed as npi group_id, group_max_data = group_by(id).max(data) 

Y así

Creo que esto logra lo que estás buscando:

 [max([val for idx,val in enumerate(data) if id[idx] == k]) for k in sorted(set(id))] 

Para la comprensión de la lista externa, de derecha a izquierda, set(id) agrupa los id , sorted() ordena, for k ... itera sobre ellos, y max toma el máximo de, en este caso, otra comprensión de lista. Entonces, al pasar a esa lista interna de comprensión: enumerate(data) devuelve tanto el índice como el valor de los data , if id[val] == k selecciona los miembros de data correspondientes a id k .

Esto se repite en la lista completa de data para cada id . Con un poco de preprocesamiento en sublistas, podría ser posible acelerarlo, pero entonces no será de una sola línea.

La siguiente solución solo requiere una clasificación de los datos (no un lexsort) y no requiere encontrar límites entre los grupos. Se basa en el hecho de que si o es una matriz de índices en r , r[o] = x llenará r con el último valor x para cada valor de o , de manera que r[[0, 0]] = [1, 2] devolverá r[0] = 2 . Requiere que sus grupos sean enteros de 0 a número de grupos – 1, como para numpy.bincount , y que haya un valor para cada grupo:

 def group_min(groups, data): n_groups = np.max(groups) + 1 result = np.empty(n_groups) order = np.argsort(data)[::-1] result[groups.take(order)] = data.take(order) return result def group_max(groups, data): n_groups = np.max(groups) + 1 result = np.empty(n_groups) order = np.argsort(data) result[groups.take(order)] = data.take(order) return result 

Una respuesta algo más rápida y general que la ya aceptada; Al igual que la respuesta de Joeln, evita el lexsort más caro, y funciona para funciones arbitrarias. Por otra parte, solo exige que las claves se puedan ordenar, en lugar de ser ints en un rango específico. Sin embargo, la respuesta aceptada aún puede ser más rápida, considerando que el máximo / mínimo no se computa explícitamente. La capacidad de ignorar nans de la solución aceptada es clara; pero uno también puede simplemente asignar valores nan una clave ficticia.

 import numpy as np def group(key, value, operator=np.add): """ group the values by key any ufunc operator can be supplied to perform the reduction (np.maximum, np.minimum, np.substract, and so on) returns the unique keys, their corresponding per-key reduction over the operator, and the keycounts """ #upcast to numpy arrays key = np.asarray(key) value = np.asarray(value) #first, sort by key I = np.argsort(key) key = key[I] value = value[I] #the slicing points of the bins to sum over slices = np.concatenate(([0], np.where(key[:-1]!=key[1:])[0]+1)) #first entry of each bin is a unique key unique_keys = key[slices] #reduce over the slices specified by index per_key_sum = operator.reduceat(value, slices) #number of counts per key is the difference of our slice points. cap off with number of keys for last bin key_count = np.diff(np.append(slices, len(key))) return unique_keys, per_key_sum, key_count names = ["a", "b", "b", "c", "d", "e", "e"] values = [1.2, 4.5, 4.3, 2.0, 5.67, 8.08, 9.01] unique_keys, reduced_values, key_count = group(names, values) print 'per group mean' print reduced_values / key_count unique_keys, reduced_values, key_count = group(names, values, np.minimum) print 'per group min' print reduced_values unique_keys, reduced_values, key_count = group(names, values, np.maximum) print 'per group max' print reduced_values