On dirait que j'ai atteint ma limite avec les pandas sur celui-ci. Il se passe trop de choses ici pour que je puisse comprendre et je n'ai pas pu trouver de réponse sur SO.

J'essaie d'effectuer une somme conditionnelle sur des groupes de lignes définis par une liste de valeurs de colonne arbitraires. Par somme conditionnelle, j'entends la somme des valeurs dans une colonne uniquement si la valeur d'une deuxième colonne est supérieure à un seuil. Il peut y avoir des chevauchements entre les groupes et le nombre d'éléments dans chaque groupe peut être différent.

Par exemple, étant donné le cadre de données suivant:

data = {
   'id': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
   'counter': [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
   'output': [5, 10, 15, 20, 25, 35, 20, 15, 10, 5]
}
df = pd.DataFrame(data)
>>> df
   id  counter  output
0   1       10       5
1   2        9      10
2   3        8      15
3   4        7      20
4   5        6      25
5   6        5      35
6   7        4      20
7   8        3      15
8   9        2      10
9  10        1       5

Et les entrées suivantes (je suis flexible si nous devons changer leur format):

group_ids = {'Group A': [1, 2, 3, 4], 'Group B': [6, 7, 8, 9], 'Group C': [4, 5, 6]}
output_threshold = 12

Je voudrais générer le nouveau dataframe suivant, qui est la somme de counter pour chaque groupe défini par la liste de group_ids uniquement si output dépasse le output_threshold spécifié. Des points bonus si je peux ajouter le titre à chacun de ces groupes:

title    sum
Group A   15
Group B   12
Group C   18
3
DV82XL 1 juin 2020 à 17:37

3 réponses

Meilleure réponse

Vous pouvez utiliser isin pour vérifier les valeurs et la somme:

mask = (df['output'] > output_threshold).astype(int)
for k,v in group_ids.items():
    df[k] = df['id'].isin(v) * mask * df['counter']

df[group_ids.keys()].sum()

Sortie (ne peut pas tout à fait correspondre à votre attendu):

Group A    15
Group B    12
Group C    18
dtype: int64
2
Quang Hoang 1 juin 2020 à 15:00

Travailler dans des dictionnaires et reconstruire le dataframe pourrait également fonctionner:

from collections import defaultdict
from itertools import product
d = defaultdict(list)

#get the product of M and group_ids
for entry, groups in product(M,group_ids.items()):
    #pass in the condition
    if entry['id'] in groups[-1] and entry['output'] > output_threshold:
        #extract relevant counter value
        d[groups[0]].append(entry['counter'])

#sum the list values
d = {k:sum(v) for k,v in d.items()}

#create dataframe
res = pd.DataFrame.from_dict(d,orient='index',columns=['Total'])

res

            Total
Group A     15
Group C     18
Group B     12
1
sammywemmy 1 juin 2020 à 15:30

J'ai choisi la réponse de @Quang Hoang parce qu'elle était concise et lisible. Il avait une solution différente au départ, mais comme cela ne traitait pas des groupes qui se chevauchent, il a modifié sa réponse pour tenir compte des groupes qui se chevauchent. La nouvelle solution était complètement différente. Sa première réponse était également très bonne, alors j'ai pensé la partager ici.

Ce qui suit est une solution à la question de cet article, à l'exception du fait que les groupes NE PEUVENT PAS se chevaucher:

Vous pouvez créer un dictionnaire inverse_group, mapper l'identifiant et groupby:

inverse_groups={x:k for k,v in group_ids.items() for x in v}

(df[df['output']>output_threshold]
   .groupby(df['id'].map(inverse_groups))
   .counter.sum()
)
Output:

id
Group A     8
Group B     7
Group C    18
Name: counter, dtype: int64
0
DV82XL 1 juin 2020 à 22:00