web-dev-qa-db-fra.com

pandas - Fusionner des lignes presque en double en fonction de la valeur de la colonne

J'ai un cadre de données pandas avec plusieurs lignes qui sont presque des doublons les uns des autres, à l'exception d'une valeur. Mon objectif est de fusionner ou de "fusionner" ces lignes en une seule ligne, sans additionner les valeurs numériques.

Voici un exemple de ce avec quoi je travaille:

Name   Sid   Use_Case  Revenue
A      xx01  Voice     $10.00
A      xx01  SMS       $10.00
B      xx02  Voice     $5.00
C      xx03  Voice     $15.00
C      xx03  SMS       $15.00
C      xx03  Video     $15.00

Et voici ce que j'aimerais:

Name   Sid   Use_Case            Revenue
A      xx01  Voice, SMS          $10.00
B      xx02  Voice               $5.00
C      xx03  Voice, SMS, Video   $15.00

La raison pour laquelle je ne veux pas additionner la colonne "Revenue" est parce que ma table est le résultat d'un pivot sur plusieurs périodes où "Revenue" finit simplement par être répertorié plusieurs fois au lieu d'avoir une valeur différente par "Use_Case" .

Quelle serait la meilleure façon de résoudre ce problème? J'ai examiné la fonction groupby() mais je ne la comprends toujours pas très bien.

17
Matthew Rosenthal

Je pense que vous pouvez utiliser groupby avec aggregatefirst et la fonction personnalisée ', '.join:

df = df.groupby('Name').agg({'Sid':'first', 
                             'Use_Case': ', '.join, 
                             'Revenue':'first' }).reset_index()

#change column order                           
print df[['Name','Sid','Use_Case','Revenue']]                              
  Name   Sid           Use_Case Revenue
0    A  xx01         Voice, SMS  $10.00
1    B  xx02              Voice   $5.00
2    C  xx03  Voice, SMS, Video  $15.00

Belle idée de commentaire, merci Goyo :

df = df.groupby(['Name','Sid','Revenue'])['Use_Case'].apply(', '.join).reset_index()

#change column order                           
print df[['Name','Sid','Use_Case','Revenue']]                              
  Name   Sid           Use_Case Revenue
0    A  xx01         Voice, SMS  $10.00
1    B  xx02              Voice   $5.00
2    C  xx03  Voice, SMS, Video  $15.00
24
jezrael

J'utilisais du code que je ne pensais pas être optimal et j'ai finalement trouvé réponse de Jezrael . Mais après l'avoir utilisé et exécuté un test timeit, je suis en fait retourné à ce que je faisais, qui était:

cmnts = {}
for i, row in df.iterrows():
    while True:
        try:
            if row['Use_Case']:
                cmnts[row['Name']].append(row['Use_Case'])

            else:
                cmnts[row['Name']].append('n/a')

            break

        except KeyError:
            cmnts[row['Name']] = []

df.drop_duplicates('Name', inplace=True)
df['Use_Case'] = ['; '.join(v) for v in cmnts.values()]

Selon mon test 100 run timeit, la méthode itérer et remplacer est un ordre de grandeur plus rapide que la méthode groupby.

import pandas as pd
from my_stuff import time_something

df = pd.DataFrame({'a': [i / (i % 4 + 1) for i in range(1, 10001)],
                   'b': [i for i in range(1, 10001)]})

runs = 100

interim_dict = 'txt = {}\n' \
               'for i, row in df.iterrows():\n' \
               '    try:\n' \
               "        txt[row['a']].append(row['b'])\n\n" \
               '    except KeyError:\n' \
               "        txt[row['a']] = []\n" \
               "df.drop_duplicates('a', inplace=True)\n" \
               "df['b'] = ['; '.join(v) for v in txt.values()]"

grouping = "new_df = df.groupby('a')['b'].apply(str).apply('; '.join).reset_index()"

print(time_something(interim_dict, runs, beg_string='Interim Dict', glbls=globals()))
print(time_something(grouping, runs, beg_string='Group By', glbls=globals()))

rendements:

Interim Dict
  Total: 59.1164s
  Avg: 591163748.5887ns

Group By
  Total: 430.6203s
  Avg: 4306203366.1827ns

time_something est une fonction qui chronomètre un extrait avec timeit et renvoie le résultat dans le format ci-dessus.

3
Eric Ed Lohmar

Vous pouvez groupby et apply la fonction list:

>>> df['Use_Case'].groupby([df.Name, df.Sid, df.Revenue]).apply(list).reset_index()
    Name    Sid     Revenue     0
0   A   xx01    $10.00  [Voice, SMS]
1   B   xx02    $5.00   [Voice]
2   C   xx03    $15.00  [Voice, SMS, Video]

(Si vous êtes préoccupé par les doublons, utilisez set au lieu de list.)

1
Ami Tavory