web-dev-qa-db-fra.com

Fractionner la cellule en plusieurs lignes dans pandas dataframe

J'ai un cadre de données contenant des données de commandes, chaque commande a plusieurs packages stockés sous forme de chaîne séparée par des virgules [package & package_code] colonnes

Je souhaite diviser les données des packages et créer une ligne pour chaque package, y compris les détails de sa commande

Voici un exemple de trame de données d'entrée:

import pandas as pd
df = pd.DataFrame({"order_id":[1,3,7],"order_date":["20/5/2018","22/5/2018","23/5/2018"], "package":["p1,p2,p3","p4","p5,p6"],"package_code":["#111,#222,#333","#444","#555,#666"]})

Input Dataframe

Et voici ce que j'essaie de réaliser en sortie: Output

Comment puis-je faire ça avec des pandas?

18
Nobel

Voici une façon d'utiliser numpy.repeat et itertools.chain. Conceptuellement, c'est exactement ce que vous voulez faire: répéter certaines valeurs, enchaîner d'autres. Recommandé pour un petit nombre de colonnes, sinon les méthodes basées sur stack peuvent mieux fonctionner.

import numpy as np
from itertools import chain

# return list from series of comma-separated strings
def chainer(s):
    return list(chain.from_iterable(s.str.split(',')))

# calculate lengths of splits
lens = df['package'].str.split(',').map(len)

# create new dataframe, repeating or chaining as appropriate
res = pd.DataFrame({'order_id': np.repeat(df['order_id'], lens),
                    'order_date': np.repeat(df['order_date'], lens),
                    'package': chainer(df['package']),
                    'package_code': chainer(df['package_code'])})

print(res)

   order_id order_date package package_code
0         1  20/5/2018      p1         #111
0         1  20/5/2018      p2         #222
0         1  20/5/2018      p3         #333
1         3  22/5/2018      p4         #444
2         7  23/5/2018      p5         #555
2         7  23/5/2018      p6         #666
10
jpp

Cela devrait fonctionner pour n'importe quel nombre de colonnes comme celle-ci. L'essence est une petite magie de dépilage de pile avec str.split.

(df.set_index(['order_date', 'order_id'])
   .stack()
   .str.split(',', expand=True)
   .stack()
   .unstack(-2)
   .reset_index(-1, drop=True)
   .reset_index()
)

  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
3  22/5/2018         3      p4         #444
4  23/5/2018         7      p5         #555
5  23/5/2018         7      p6         #666

Il existe une autre alternative performante impliquant chain, mais vous devez chaîner explicitement et répéter chaque colonne (un petit problème avec beaucoup de colonnes). Choisissez ce qui correspond le mieux à la description de votre problème, car il n'y a pas de réponse unique.


Détails

Tout d'abord, définissez les colonnes qui ne doivent pas être touchées comme index.

df.set_index(['order_date', 'order_id'])

                      package    package_code
order_date order_id                          
20/5/2018  1         p1,p2,p3  #111,#222,#333
22/5/2018  3               p4            #444
23/5/2018  7            p5,p6       #555,#666

Ensuite, stack les lignes.

_.stack()

order_date  order_id              
20/5/2018   1         package               p1,p2,p3
                      package_code    #111,#222,#333
22/5/2018   3         package                     p4
                      package_code              #444
23/5/2018   7         package                  p5,p6
                      package_code         #555,#666
dtype: object

Nous avons maintenant une série. Appelez donc str.split Par une virgule.

_.str.split(',', expand=True)

                                     0     1     2
order_date order_id                               
20/5/2018  1        package         p1    p2    p3
                    package_code  #111  #222  #333
22/5/2018  3        package         p4  None  None
                    package_code  #444  None  None
23/5/2018  7        package         p5    p6  None
                    package_code  #555  #666  None

Nous devons nous débarrasser des valeurs NULL, appelez donc à nouveau stack.

_.stack()

order_date  order_id                 
20/5/2018   1         package       0      p1
                                    1      p2
                                    2      p3
                      package_code  0    #111
                                    1    #222
                                    2    #333
22/5/2018   3         package       0      p4
                      package_code  0    #444
23/5/2018   7         package       0      p5
                                    1      p6
                      package_code  0    #555
                                    1    #666
dtype: object

Nous y sommes presque. Maintenant, nous voulons que l'avant-dernier niveau de l'index devienne nos colonnes, alors décompilez en utilisant unstack(-2) (unstack sur l'avant-dernier niveau)

_.unstack(-2)

                      package package_code
order_date order_id                       
20/5/2018  1        0      p1         #111
                    1      p2         #222
                    2      p3         #333
22/5/2018  3        0      p4         #444
23/5/2018  7        0      p5         #555
                    1      p6         #666

Débarrassez-vous du dernier niveau superflu en utilisant reset_index:

_.reset_index(-1, drop=True)

                    package package_code
order_date order_id                     
20/5/2018  1             p1         #111
           1             p2         #222
           1             p3         #333
22/5/2018  3             p4         #444
23/5/2018  7             p5         #555
           7             p6         #666

Et enfin,

_.reset_index()

  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
3  22/5/2018         3      p4         #444
4  23/5/2018         7      p5         #555
5  23/5/2018         7      p6         #666
27
cs95

Jetez un œil à la pandas release 0.25: https://pandas.pydata.org/pandas-docs/stable/whatsnew/v0.25.0.html#series-explode- to-split-list-like-values-to-rows

df = pd.DataFrame([{'var1': 'a,b,c', 'var2': 1}, {'var1': 'd,e,f', 'var2': 2}])
df.assign(var1=df.var1.str.split(',')).explode('var1').reset_index(drop=True)
4
Heraknos

Proche de la méthode du froid :-)

df.set_index(['order_date','order_id']).apply(lambda x : x.str.split(',')).stack().apply(pd.Series).stack().unstack(level=2).reset_index(level=[0,1])
Out[538]: 
  order_date  order_id package package_code
0  20/5/2018         1      p1         #111
1  20/5/2018         1      p2         #222
2  20/5/2018         1      p3         #333
0  22/5/2018         3      p4         #444
0  23/5/2018         7      p5         #555
1  23/5/2018         7      p6         #666
4
WeNYoBen