2 votes

Comment calculer une moyenne sur les colonnes en se basant sur les valeurs des colonnes adjacentes dans Pandas

J'ai un ensemble de données exemple ci-joint. Mon ensemble de données réel est beaucoup plus grand. Les colonnes "yr" sont divisées entre "cd" et "qty" et vont de yr 10 à yr 1. Toutes les lignes ne contiennent pas un ensemble complet de données, certaines contiennent des zéros. Données exemple :

Ventes

yr10cd

yr10qty

yr9cd

yr9qty

yr8cd

yr8qty

42

A

45

A

47

A

49

56

T

58

A

52

0

0

78

A

75

0

0

0

0

Je veux pouvoir prendre une moyenne des colonnes qty (yr10qty, yr9qty, yr8qty, etc.) mais seulement si l'indicateur associé (yr10cd, yr9cd, yr8cd, etc.) dans la colonne adjacente a une valeur "A". Si l'indicateur associé est 0 ou toute autre valeur comme "T", je ne veux pas l'inclure dans mon calcul de moyenne.

J'ai essayé d'utiliser une fonction qui utilise des instructions if pour ajouter des valeurs à une liste, puis de faire la moyenne si différent de zéro. Ensuite, en utilisant df.apply pour appliquer cette fonction à chaque ligne du df. Malheureusement, j'obtiens toujours 0 pour toutes mes moyennes, ce qui n'est pas attendu.

Voici la sortie attendue. Il s'agit d'une colonne ajoutée à mon df qui contient la moyenne pour cette ligne. Sortie attendue :

Ventes

yr10cd

yr10qty

yr9cd

yr9qty

yr8cd

yr8qty

moyenne

42

A

45

A

47

A

49

47

56

T

58

A

52

0

0

52

78

A

75

0

0

0

0

75

J'ai essayé de chercher sur stackoverflow depuis un certain temps et aucune des solutions que j'ai trouvées n'a fonctionné pour mon scénario spécifique.

2voto

Paul Smith Points 406

Une autre solution possible:

d = (df.filter(like='cd', axis=1).eq('A') * df.filter(like='qty', axis=1).values)

df['moyenne'] = d.where(d.ne(0)).mean(axis=1)

Sortie:

   Ventes yr10cd  yr10qty yr9cd  yr9qty yr8cd  yr8qty  moyenne
0     42      A       45     A      47     A      49     47.0
1     56      T       58     A      52     0       0     52.0
2     78      A       75     0       0     0       0     75.0

2voto

M92_ Points 205

Profitez des paires alternatives cd/qty, mask les non A, puis calculez la moyenne :

tmp = df.set_index("Sales") # mettre de côté le S

cds = tmp.iloc[:, ::2]; qty = tmp.iloc[:, 1::2]

df["average"] = (qty.mask(cds.ne("A").set_axis(qty.columns, axis=1))
                     .mean(axis=1).reset_index(drop=True))

Sortie :

print(df)

   Sales yr10cd  yr10qty yr9cd  yr9qty yr8cd  yr8qty  average
0     42      A       45     A      47     A      49     47.0
1     56      T       58     A      52     0       0     52.0
2     78      A       75     0       0     0       0     75.0

2voto

Henry Ecker Points 15100

Dans certaines situations où la structure des colonnes peut être inconnue, il peut être utile de se développer dans un MultiIndex pour effectuer des opérations qui nous permettent de tirer parti de l'indexation de pandas pour garantir l'intégrité des données lors de nos calculs.

En supposant une structure de yr[num]cd associée à yr[num]qty, nous pouvons isoler ces colonnes et créer un multi-index de telle sorte que les valeurs [num] soient à leur propre niveau.

v = df.filter(regex='^yr\d+(cd|qty)$')
v.columns = (
    v.columns
    .str.replace(r'yr(\d+)(cd|qty)', r'\2_\1', regex=True)
    .str.split('_', expand=True)
)

Ici, j'ai isolé les colonnes intéressantes dans la variable v et restructuré les colonnes de telle sorte que les valeurs cd et qty soient au niveau 0 et les chiffres au niveau 1 grâce à l'utilisation de replace et split.

V ressemble à:

  cd qty cd qty cd qty
  10  10  9   9  8   8
0  A  45  A  47  A  49
1  T  58  A  52  0   0
2  A  75  0   0  0   0

Remarquez qu'il existe de nombreuses façons de restructurer les colonnes en un MultiIndex. Voici un autre exemple à titre d'illustration:

v.columns = (
    v.columns
    .str.split(r'(\d+)', regex=True, expand=True)
    .droplevel(0)
    .swaplevel(0, 1)
)

En fonction du format du nom des colonnes, différentes approches peuvent être meilleures pour la restructuration.


Le principal avantage d'un MultiIndex avec cet ordre de niveaux est que nous pouvons très facilement accéder à toutes les colonnes cd et qty en y accédant v['cd'] et v['qty'].

v['qty'] à titre d'illustration:

   10   9   8
0  45  47  49
1  58  52   0
2  75   0   0

Le grand avantage ici est que, quel que soit l'ordre des colonnes, nous pouvons aligner de manière fiable les calculs entre 10, 9 et 8.

Cela nous permet de filtrer les valeurs égales à 'A' avec where is égal à 'A' v['qty'].where(v['cd'].eq('A')):

     10     9     8
0  45.0  47.0  49.0
1   NaN  52.0   NaN
2  75.0   NaN   NaN

Puis prendre la moyenne à travers les lignes v['qty'].where(v['cd'].eq('A')).mean(axis='columns'):

0    47.0
1    52.0
2    75.0
dtype: float64

Cela a le même index que df, nous pouvons donc très simplement assigner les valeurs de retour

df['Average'] = v['qty'].where(v['cd'].eq('A')).mean(axis='columns')

df avec la nouvelle colonne:

   Sales yr8cd yr9cd yr10cd  yr10qty  yr9qty  yr8qty  Average
0     42     A     A      A       45      47      49     47.0
1     56     0     A      T       58      52       0     52.0
2     78     0     0      A       75       0       0     75.0

Encore une fois, l'aspect vraiment génial de cette approche est que l'ordre initial des colonnes de données n'a pas d'importance.

Imaginez une situation où nos colonnes cd sont regroupées avec des nombres par ordre croissant et nos colonnes qty sont regroupées avec des nombres par ordre décroissant.

Sales

yr8cd

yr9cd

yr10cd

yr10qty

yr9qty

yr8qty

42

A

A

A

45

47

49

56

0

A

T

58

52

0

78

0

0

A

75

0

0

Ou, de manière plus réaliste, imaginez un scénario où quelqu'un a accidentellement fait glisser l'une des colonnes hors de l'ordre. L'approche décrite ici donnera toujours les bonnes valeurs moyennes car le calcul et le filtrage des valeurs sont alignés en fonction des valeurs numériques et non de leur position relative dans le DataFrame.

0    47.0
1    52.0
2    75.0
dtype: float64

Ainsi, bien que cette approche ne soit probablement pas la solution la plus rapide ou la plus efficiente en termes de mémoire, elle est assez performante sans sacrifier les vérifications d'intégrité d'alignement que l'indexation peut offrir.


Exemple complet avec numéro de version

import pandas as pd  # v2.1.2

df = pd.DataFrame({
    'Sales': [42, 56, 78],
    'yr10cd': ['A', 'T', 'A'],
    'yr10qty': [45, 58, 75],
    'yr9cd': ['A', 'A', '0'],
    'yr9qty': [47, 52, 0],
    'yr8cd': ['A', '0', '0'],
    'yr8qty': [49, 0, 0]
})

v = df.filter(regex='^yr\d+(cd|qty)$')
v.columns = (
    v.columns
    .str.replace(r'yr(\d+)(cd|qty)', r'\2_\1', regex=True)
    .str.split('_', expand=True)
)

df['Average'] = v['qty'].where(v['cd'].eq('A')).mean(axis='columns')

print(df)

1voto

OSezer Points 41

Méthode de la ligne avec une boucle for:

df = pd.DataFrame(data)

def calculer_moyenne(ligne):
    valeurs_qty = []
    for i in range(10, 1, -1):
        cd_col = f'yr{i}cd'
        qty_col = f'yr{i}qty'
        if cd_col in ligne and qty_col in ligne and ligne[cd_col] == 'A':
            valeurs_qty.append(ligne[qty_col])
    if valeurs_qty:
        return sum(valeurs_qty) / len(valeurs_qty)
    else:
        return 0

df['moyenne'] = df.apply(calculer_moyenne, axis=1)
print(df)

Sortie:

   Ventes yr10cd  yr10qty yr9cd  yr9qty yr8cd  yr8qty  moyenne
0     42      A       45     A      47     A      49     47.0
1     56      T       58     A      52     0       0     52.0
2     78      A       75     0       0     0       0     75.0

Méthode vectorisée:

df = pd.DataFrame(data)
colonnes_cd = df.columns[df.columns.str.contains('cd')].to_list()
colonnes_qty = df.columns[df.columns.str.contains('qty')].to_list()

df[colonnes_qty] = df[colonnes_qty].apply(pd.to_numeric, errors='coerce') # remplacer les valeurs non numériques par NaN
moy_qty = np.where(df[colonnes_cd] == 'A', df[colonnes_qty], np.nan) # moyenne
df['moyenne'] = np.nanmean(moy_qty, axis=1)

print(df)

Sortie:

   Ventes yr10cd  yr10qty yr9cd  yr9qty yr8cd  yr8qty  moyenne
0     42      A       45     A      47     A      49     47.0
1     56      T       58     A      52     0       0     52.0
2     78      A       75     0       0     0       0     75.0

1voto

ah529 Points 26

Voici une réponse très succincte qui devrait fonctionner:

temp_df= df.copy()
columns = list(df.columns)
for col in columns:
    if col.endswith('cd'):
        adj_col = col.replace('cd','qty') 
        temp_df[adj_col] = np.where(temp_df[col] != 'A', None, temp_df[adj_col])

df['average'] = temp_df[[c for c in columns if c.endswith('qty')]].mean(axis=1,skipna = True)

Explication: Créez un df temporaire. Pour chaque colonne 'cd', si la valeur n'est pas 'A', remplacez la valeur de la colonne 'qty' adjacente par None. Ensuite, lorsque nous prenons la moyenne par ligne de toutes les colonnes 'qty', nous pouvons ignorer les colonnes 'qty' avec des valeurs None. Enfin, lorsque nous prenons les moyennes par ligne, nous créons simultanément une nouvelle colonne dans le df d'origine et assignons le résultat de ces moyennes.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X