Numpy resume una matriz por valores de otra

Estoy tratando de encontrar una forma vectorizada para lograr lo siguiente:

Digamos que tengo una matriz de valores de x e y. Tenga en cuenta que los valores de x no siempre son ints y PUEDEN ser negativos:

import numpy as np x = np.array([-1,-1,-1,3,2,2,2,5,4,4], dtype=float) y = np.array([0,1,0,1,0,1,0,1,0,1]) 

Quiero agrupar la matriz y por los valores únicos ordenados de la matriz x y resumir los conteos para cada clase y. Entonces el ejemplo de arriba se vería así:

 array([[ 2., 1.], [ 2., 1.], [ 0., 1.], [ 1., 1.], [ 0., 1.]]) 

Donde la primera columna representa el conteo de valores ‘0’ para cada valor único de x y la segunda columna representa el conteo de valores ‘1’ para cada valor único de x.

Mi implementación actual se ve así:

 x_sorted, y_sorted = x[x.argsort()], y[x.argsort()] def collapse(x_sorted, y_sorted): uniq_ids = np.unique(x_sorted, return_index=True)[1] y_collapsed = np.zeros((len(uniq_ids), 2)) x_collapsed = x_sorted[uniq_ids] for idx, y in enumerate(np.split(y_sorted, uniq_ids[1:])): y_collapsed[idx,0] = (y == 0).sum() y_collapsed[idx,1] = (y == 1).sum() return (x_collapsed, y_collapsed) collapse(x_sorted, y_sorted) (array([-1, 2, 3, 4, 5]), array([[ 2., 1.], [ 2., 1.], [ 0., 1.], [ 1., 1.], [ 0., 1.]])) 

Sin embargo, esto no parece mucho en el espíritu de los números, y espero que exista algún método vectorizado para este tipo de operación. Estoy tratando de hacer esto sin recurrir a los pandas. Sé que la biblioteca tiene una operación grupal muy conveniente.

Como x es float . Yo haría esto:

 In [136]: np.array([(x[y==0]==np.unique(x)[..., np.newaxis]).sum(axis=1), (x[y==1]==np.unique(x)[..., np.newaxis]).sum(axis=1)]).T Out[136]: array([[2, 1], [2, 1], [0, 1], [1, 1], [0, 1]]) 

Velocidad:

 In [152]: %%timeit ux=np.unique(x)[..., np.newaxis] np.array([(x[y==0]==ux).sum(axis=1), (x[y==1]==ux).sum(axis=1)]).T 10000 loops, best of 3: 92.7 µs per loop 

Solución @seikichi

 In [151]: %%timeit >>> x = np.array([1.1, 1.1, 1.1, 3.3, 2.2, 2.2, 2.2, 5.5, 4.4, 4.4]) >>> y = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1]) >>> r = np.r_[np.unique(x), np.inf] >>> np.concatenate([[np.histogram(x[y == v], r)[0]] for v in sorted(set(y))]).T 1000 loops, best of 3: 388 µs per loop 

Para casos más generales, cuando y no es solo {0,1} , como @askewchan señaló:

 In [155]: %%timeit ux=np.unique(x)[..., np.newaxis] uy=np.unique(y) np.asanyarray([(x[y==v]==ux).sum(axis=1) for v in uy]).T 10000 loops, best of 3: 116 µs per loop 

Para explicar con más detalle la transmisión, vea este ejemplo:

 In [5]: np.unique(a) Out[5]: array([ 0. , 0.2, 0.4, 0.5, 0.6, 1.1, 1.5, 1.6, 1.7, 2. ]) In [8]: np.unique(a)[...,np.newaxis] #what [..., np.newaxis] will do: Out[8]: array([[ 0. ], [ 0.2], [ 0.4], [ 0.5], [ 0.6], [ 1.1], [ 1.5], [ 1.6], [ 1.7], [ 2. ]]) In [10]: (a==np.unique(a)[...,np.newaxis]).astype('int') #then we can boardcast (converted to int for readability) Out[10]: array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0]]) In [11]: (a==np.unique(a)[...,np.newaxis]).sum(axis=1) #getting the count of unique value becomes summing among the 2nd axis Out[11]: array([1, 3, 1, 1, 2, 1, 1, 1, 1, 3]) 

¿Qué tal el siguiente código? (use numpy.bincount y numpy.concatenate )

 >>> import numpy as np >>> x = np.array([1,1,1,3,2,2,2,5,4,4]) >>> y = np.array([0,1,0,1,0,1,0,1,0,1]) >>> xmax = x.max() >>> numpy.concatenate([[numpy.bincount(x[y == v], minlength=xmax + 1)] for v in sorted(set(y))], axis=0)[:, 1:].T array([[2, 1], [2, 1], [0, 1], [1, 1], [0, 1]]) 

ACTUALIZACIÓN: Gracias @askewchan!

 >>> import numpy as np >>> x = np.array([1.1, 1.1, 1.1, 3.3, 2.2, 2.2, 2.2, 5.5, 4.4, 4.4]) >>> y = np.array([0, 1, 0, 1, 0, 1, 0, 1, 0, 1]) >>> r = np.r_[np.unique(x), np.inf] >>> np.array([np.histogram(x[y == v], r)[0] for v in sorted(set(y))]).T array([[2, 1], [2, 1], [0, 1], [1, 1], [0, 1]]) 

np.unique y np.bincount son tus amigos aquí. Lo siguiente debería funcionar para cualquier tipo de entradas, no necesariamente enteros consecutivos pequeños:

 >>> x = np.array([1, 1, 1, 3, 2, 2, 2, 5, 4, 4]) >>> y = np.array([0, 1, 2, 2, 0, 1, 0, 2, 2, 1]) >>> >>> x_unq, x_idx = np.unique(x, return_inverse=True) >>> y_unq, y_idx = np.unique(y, return_inverse=True) >>> >>> np.column_stack(np.bincount(x_idx, y_idx == j) for j in range(len(y_unq))) array([[ 1., 1., 1.], [ 2., 1., 0.], [ 0., 0., 1.], [ 0., 1., 1.], [ 0., 0., 1.]]) 

También puede extraer las tags de fila y columna:

 >>> x_unq array([1, 2, 3, 4, 5]) >>> y_unq array([0, 1, 2]) 

No he probado esto, pero creo que debería funcionar. Básicamente, todo lo que hago es tomar los valores de y basados ​​en que x es el valor en cuestión.

 uniques = list(set(x)) uniques.sort() lu = len(uniques) res = np.zeros(lu * 2).reshape(lu, 2) for i, v in enumerate(uniques): cur = y[x == v] s = cur.sum() res[i, 0] = len(cur) - s res[i, 1] = s 

Otra forma es usar numpy MaskedArrays

Aquí hay otra solución:

 y = y[np.argsort(x)] b = np.bincount(x) b = b[b!=0] ans = np.array([[i.shape[0], i.sum()] for i in np.split(y, np.cumsum(b))[:-1]]) ans[:,0] -= ans[:,1] print(ans) #array([[2, 1], # [2, 1], # [0, 1], # [1, 1], # [0, 1]], dtype=int64) 

Sincronización:

  @seikichi solution: 10000 loops, best of 3: 37.2 µs per loop @acushner solution: 10000 loops, best of 3: 65.4 µs per loop @SaulloCastro solution: 10000 loops, best of 3: 154 µs per loop