J'ai le tableau numpy suivant:
import numpy as np
arr = np.array([[1,2,3,4,2000],
[5,6,7,8,2000],
[9,0,1,2,2001],
[3,4,5,6,2001],
[7,8,9,0,2002],
[1,2,3,4,2002],
[5,6,7,8,2003],
[9,0,1,2,2003]
])
Je comprends que np.sum(arr, axis=0)
fournit le résultat:
array([ 40, 28, 36, 34, 16012])
ce que je voudrais faire ( sans une boucle for ) est la somme des colonnes basée sur la valeur de la dernière colonne de sorte que le résultat fourni soit:
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Je me rends compte qu'il peut être difficile de se passer de boucle, mais espérer le meilleur ...
Si une boucle for doit être utilisée, comment cela fonctionnerait-il?
J'ai essayé np.sum(arr[:, 4]==2000, axis=0)
(où je substituerais 2000
par la variable de la boucle for), mais il a donné un résultat de2
Vous pouvez le faire en pure numpy en utilisant une application astucieuse de np.diff
et np.add.reduceat
. np.diff
vous donnera les index où la colonne la plus à droite change:
d = np.diff(arr[:, -1])
np.where
convertira votre index booléen d
dans les indices entiers que np.add.reduceat
attend:
d = np.where(d)[0]
reduceat
s'attendra également à voir un indice nul, et tout doit être déplacé d'un élément:
indices = np.r_[0, e + 1]
Utiliser np.r_
here est un peu plus pratique que np.concatenate
car cela autorise les scalaires. La somme devient alors:
result = np.add.reduceat(arr, indices, axis=0)
Cela peut être combiné dans un one-liner bien sûr:
>>> result = np.add.reduceat(arr, np.r_[0, np.where(np.diff(arr[:, -1]))[0] + 1], axis=0)
>>> result
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Je poste une solution simple avec pandas
et une avec itertools
import pandas as pd
df = pd.DataFrame(arr)
x = df.groupby(4).sum().reset_index()[range(5)] #range(5) adjusts ordering
x[4] *= 2
np.array(x)
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Vous pouvez également utiliser itertools
np.array([sum(x[1]) for x in itertools.groupby(arr, key = lambda k: k[-1])])
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Approche n ° 1: Réduction de somme basée sur NumPy
En voici une basée sur np.add.reduceat
-
def groupbycol(a, assume_sorted_col=False, colID=-1):
if assume_sorted_col==0:
# If a is not already sorted by that col, use argsort indices for
# that colID and re-arrange rows accordingly
sidx = a[:,colID].argsort()
a_s = a[sidx] # sorted by colID col of input array
else:
a_s = a
# Get group shifting indices
cut_idx = np.flatnonzero(np.r_[True, a_s[1:,colID] != a_s[:-1,colID]])
# Use those indices to setup sum reduction at intervals along first axis
return np.add.reduceat(a_s, cut_idx, axis=0)
Exemple de cycle -
In [64]: arr
Out[64]:
array([[ 1, 2, 3, 4, 2000],
[ 5, 6, 7, 8, 2000],
[ 9, 0, 1, 2, 2001],
[ 3, 4, 5, 6, 2001],
[ 7, 8, 9, 0, 2002],
[ 1, 2, 3, 4, 2002],
[ 5, 6, 7, 8, 2003],
[ 9, 0, 1, 2, 2003]])
In [65]: # Shuffle rows off input array to create a generic last col (not sorted)
...: np.random.seed(0)
...: np.random.shuffle(arr)
In [66]: arr
Out[66]:
array([[ 5, 6, 7, 8, 2003],
[ 9, 0, 1, 2, 2001],
[ 5, 6, 7, 8, 2000],
[ 9, 0, 1, 2, 2003],
[ 3, 4, 5, 6, 2001],
[ 1, 2, 3, 4, 2000],
[ 1, 2, 3, 4, 2002],
[ 7, 8, 9, 0, 2002]])
In [67]: groupbycol(arr, assume_sorted_col=False, colID=-1)
Out[67]:
array([[ 6, 8, 10, 12, 4000],
[ 12, 4, 6, 8, 4002],
[ 8, 10, 12, 4, 4004],
[ 14, 6, 8, 10, 4006]])
Approche n ° 2: tirer parti de la matrice-multiplcation
Nous pourrions fondamentalement remplacer ce np.add.reduceat
par une création de masque diffusé + une multiplication de matrice, et par conséquent tirer parti du BLAS rapide et fonctionnant également pour une colonne générique non triée -
import pandas as pd
def groupbycol_matmul(a, colID=-1):
mask = pd.Series(a[:,colID]).unique()[:,None] == arr[:,colID]
return mask.dot(arr)
Vous voudrez peut-être jeter un coup d’œil sur numpy_indexed
. Avec cela, vous pouvez faire:
import numpy as np
import numpy_indexed as npi
arr = np.array([[1,2,3,4,2000],
[5,6,7,8,2000],
[9,0,1,2,2001],
[3,4,5,6,2001],
[7,8,9,0,2002],
[1,2,3,4,2002],
[5,6,7,8,2003],
[9,0,1,2,2003]
])
result = npi.GroupBy(arr[:, 4]).sum(arr)[1]
>>>[[ 6 8 10 12 4000]
[ 12 4 6 8 4002]
[ 8 10 12 4 4004]
[ 14 6 8 10 4006]]