web-dev-qa-db-fra.com

Numpy: somme conditionnelle

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

4
Infinity Cliff

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]])
2
Mad Physicist

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]])
2
RafaelC

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)
2
Divakar

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]]
0
Jacques Gaudin