Fractionner (exploser) l'entrée de chaîne du cadre de données pandas pour séparer les lignes

200

J'ai un pandas dataframedans lequel une colonne de chaînes de texte contient des valeurs séparées par des virgules. Je veux diviser chaque champ CSV et créer une nouvelle ligne par entrée (supposez que le CSV est propre et ne doit être divisé que sur ','). Par exemple, adevrait devenir b:

In [7]: a
Out[7]: 
    var1  var2
0  a,b,c     1
1  d,e,f     2

In [8]: b
Out[8]: 
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Jusqu'à présent, j'ai essayé diverses fonctions simples, mais la .applyméthode ne semble accepter qu'une seule ligne comme valeur de retour lorsqu'elle est utilisée sur un axe, et je ne peux pas me mettre .transformau travail. Toute suggestion serait très appréciée!

Exemples de données:

from pandas import DataFrame
import numpy as np
a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
b = DataFrame([{'var1': 'a', 'var2': 1},
               {'var1': 'b', 'var2': 1},
               {'var1': 'c', 'var2': 1},
               {'var1': 'd', 'var2': 2},
               {'var1': 'e', 'var2': 2},
               {'var1': 'f', 'var2': 2}])

Je sais que cela ne fonctionnera pas parce que nous perdons les métadonnées DataFrame en passant par numpy, mais cela devrait vous donner une idée de ce que j'ai essayé de faire:

def fun(row):
    letters = row['var1']
    letters = letters.split(',')
    out = np.array([row] * len(letters))
    out['var1'] = letters
a['idx'] = range(a.shape[0])
z = a.groupby('idx')
z.transform(fun)
Vincent
la source
2
d'autres solutions sur cette page fonctionnent mais j'ai trouvé la suite courte et efficace. stackoverflow.com/questions/27263805/…
desaiankitb
1
Pour ceux qui arrivent sur cette page et recherchent une solution qui conserve plusieurs colonnes, jetez un œil à cette question: stackoverflow.com/questions/17116814/…
Sos

Réponses:

81

Que diriez-vous quelque chose comme ça:

In [55]: pd.concat([Series(row['var2'], row['var1'].split(','))              
                    for _, row in a.iterrows()]).reset_index()
Out[55]: 
  index  0
0     a  1
1     b  1
2     c  1
3     d  2
4     e  2
5     f  2

Ensuite, il vous suffit de renommer les colonnes

Chang She
la source
1
On dirait que ça va marcher. Merci de votre aide! En général, cependant, existe-t-il une approche préférée de Split-Apply-Combine où Apply renvoie un cadre de données de taille arbitraire (mais cohérent pour tous les morceaux), et Combine vstacks juste les DF retournés?
Vincent
GroupBy.apply devrait fonctionner (je viens de l'essayer contre master). Cependant, dans ce cas, vous n'avez pas vraiment besoin de passer par l'étape supplémentaire de regroupement puisque vous générez les données par ligne, n'est-ce pas?
Chang She
1
Salut les gars. Désolé de vous lancer si tard, mais vous vous demandez s'il n'y a pas de meilleure solution à cela. J'essaie d'expérimenter des iterrows pour la première fois car cela semble être le ticket pour cela. Je suis également confus par la solution proposée. Que représente le "_"? Pouvez-vous éventuellement expliquer le fonctionnement de la solution? --Merci
horatio1701d
11
La solution peut-elle être étendue à plus de deux colonnes?
horatio1701d
1
veuillez vérifier cette approche vectorisée ...
MaxU
147

UPDATE2: fonction vectorisée plus générique, qui fonctionnera pour plusieurs normalet plusieurs listcolonnes

def explode(df, lst_cols, fill_value='', preserve_index=False):
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    # create "exploded" DF
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)
    return res

Démo:

Plusieurs listcolonnes - toutes les listcolonnes doivent avoir le même nombre d'éléments dans chaque ligne:

In [134]: df
Out[134]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2         []            []
2   12     3     [1, 2]      [cc, dd]
3   13     4         []            []

In [135]: explode(df, ['num','text'], fill_value='')
Out[135]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2
4   12     3   1   cc
5   12     3   2   dd
6   13     4

conservation des valeurs d'index d'origine:

In [136]: explode(df, ['num','text'], fill_value='', preserve_index=True)
Out[136]:
   aaa  myid num text
0   10     1   1   aa
0   10     1   2   bb
0   10     1   3   cc
1   11     2
2   12     3   1   cc
2   12     3   2   dd
3   13     4

Installer:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [], 2: [1, 2], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: [], 2: ['cc', 'dd'], 3: []}
})

Colonne CSV:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

en utilisant cette petite astuce, nous pouvons convertir une colonne de type CSV en listcolonne:

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

MISE À JOUR: approche générique vectorisée (fonctionnera également pour plusieurs colonnes):

DF d'origine:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

Solution:

convertissons d'abord les chaînes CSV en listes:

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

Maintenant, nous pouvons le faire:

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

VIEILLE réponse:

Inspiré par la solution @AFinkelstein , je voulais le rendre un peu plus généralisé qui pourrait être appliqué à DF avec plus de deux colonnes et aussi rapide, enfin presque, aussi rapide que la solution d'AFinkelstein):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ
MaxU
la source
7
mec, si vous pouvez ouvrir une discussion dans Git pandas, je pense que nous avons besoin d'une fonction intégrée comme celle-ci !!! J'ai vu tellement de questions sur la non-inscription et le non-imbrication dans SO pour les pandas
YOBEN_S
comment l'utiliser pour plusieurs colonnes. Comme si j'ai des données séparées par des virgules dans 2 colonnes et que je veux le faire en séquence?
Jaskaran Singh Puri
@JaskaranSinghPuri, vous voulez d'abord convertir toutes les colonnes CSV en listes.
MaxU
1
Malheureusement, cela ne fonctionne pas si vos éléments de liste sont des tuples. Mais après avoir converti l'intégralité du tuple en chaîne, cela fonctionne comme un charme!
Guido
2
On dirait que l'appel de WenBen a été entendu par les dieux pandas, ils ont installé une .explode()méthode dans l'API (voir également cette réponse ).
cs95
117

Après une expérimentation douloureuse pour trouver quelque chose de plus rapide que la réponse acceptée, j'ai réussi à travailler. Il a fonctionné environ 100 fois plus vite sur l'ensemble de données sur lequel je l'ai essayé.

Si quelqu'un connaît un moyen de rendre cela plus élégant, modifiez certainement mon code. Je ne pouvais pas trouver un moyen qui fonctionne sans définir les autres colonnes que vous souhaitez conserver comme index, puis réinitialiser l'index et renommer les colonnes, mais j'imagine qu'il y a autre chose qui fonctionne.

b = DataFrame(a.var1.str.split(',').tolist(), index=a.var2).stack()
b = b.reset_index()[[0, 'var2']] # var1 variable is currently labeled 0
b.columns = ['var1', 'var2'] # renaming var1
DMulligan
la source
2
Cette solution a fonctionné beaucoup plus rapidement et semble utiliser moins de mémoire,
cyril
1
Ceci est une belle solution de pandas vectorisés, je cherchais cela. Merci!
Dennis Golomazov
Lorsque j'essaye ceci sur mon propre ensemble de données, je continue à en arriver TypeError: object of type 'float' has no len()à la toute première étape ( DataFrame(df.var1.str.split(',').tolist()))
user5359531
@ user5359531 votre jeu de données en a probablement NaNdans cette colonne, donc le remplacement estb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair
Juste pour info, voici une belle rédaction de cette solution avec un exemple.
hhbilly
46

Voici une fonction que j'ai écrite pour cette tâche courante. C'est plus efficace que les méthodes Series/ stack. L'ordre et les noms des colonnes sont conservés.

def tidy_split(df, column, sep='|', keep=False):
    """
    Split the values of a column and expand so the new DataFrame has one split
    value per row. Filters rows where the column is missing.

    Params
    ------
    df : pandas.DataFrame
        dataframe with the column to split and expand
    column : str
        the column to split and expand
    sep : str
        the string used to split the column's values
    keep : bool
        whether to retain the presplit value as it's own row

    Returns
    -------
    pandas.DataFrame
        Returns a dataframe with the same columns as `df`.
    """
    indexes = list()
    new_values = list()
    df = df.dropna(subset=[column])
    for i, presplit in enumerate(df[column].astype(str)):
        values = presplit.split(sep)
        if keep and len(values) > 1:
            indexes.append(i)
            new_values.append(presplit)
        for value in values:
            indexes.append(i)
            new_values.append(value)
    new_df = df.iloc[indexes, :].copy()
    new_df[column] = new_values
    return new_df

Avec cette fonction, la question d'origine est aussi simple que:

tidy_split(a, 'var1', sep=',')
Daniel Himmelstein
la source
1
C'est extrêmement rapide! Merci beaucoup pour cela.
Anurag N. Sharma
42

Pandas> = 0,25

Les méthodes Series et DataFrame définissent une .explode()méthode qui fait exploser les listes en lignes distinctes. Voir la section docs sur Exploser une colonne de type liste .

Puisque vous avez une liste de chaînes séparées par des virgules, divisez la chaîne sur une virgule pour obtenir une liste d'éléments, puis appelez explodecette colonne.

df = pd.DataFrame({'var1': ['a,b,c', 'd,e,f'], 'var2': [1, 2]})
df
    var1  var2
0  a,b,c     1
1  d,e,f     2

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Notez que explodene fonctionne que sur une seule colonne (pour l'instant).


Les NaN et les listes vides reçoivent le traitement qu'ils méritent sans que vous ayez à sauter à travers des cerceaux pour bien faire les choses.

df = pd.DataFrame({'var1': ['d,e,f', '', np.nan], 'var2': [1, 2, 3]})
df
    var1  var2
0  d,e,f     1
1            2
2    NaN     3

df['var1'].str.split(',')

0    [d, e, f]
1           []
2          NaN

df.assign(var1=df['var1'].str.split(',')).explode('var1')

  var1  var2
0    d     1
0    e     1
0    f     1
1          2  # empty list entry becomes empty string after exploding 
2  NaN     3  # NaN left un-touched

C'est un sérieux avantage sur les solutions basées sur ravel+repeat (qui ignorent complètement les listes vides et s'étouffent avec les NaN).

cs95
la source
4
Celui-ci est le plus simple et s'adapte le mieux dans mon cas! Merci!
Isaac Sim
14

Question similaire: pandas: comment diviser le texte d'une colonne en plusieurs lignes?

Vous pourriez faire:

>> a=pd.DataFrame({"var1":"a,b,c d,e,f".split(),"var2":[1,2]})
>> s = a.var1.str.split(",").apply(pd.Series, 1).stack()
>> s.index = s.index.droplevel(-1)
>> del a['var1']
>> a.join(s)
   var2 var1
0     1    a
0     1    b
0     1    c
1     2    d
1     2    e
1     2    f
inodb
la source
2
Cela fonctionne après avoir ajouté un nouveau code de renommage s.name = 'var1'
Jesse
14

TL; DR

import pandas as pd
import numpy as np

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

Manifestation

explode_str(a, 'var1', ',')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Créons un nouveau dataframe dqui a des listes

d = a.assign(var1=lambda d: d.var1.str.split(','))

explode_list(d, 'var1')

  var1  var2
0    a     1
0    b     1
0    c     1
1    d     2
1    e     2
1    f     2

Observations générales

Je vais utiliser np.arangeavec repeatpour produire des positions d'index de trame de données que je peux utiliser avec iloc.

FAQ

Pourquoi je n'utilise pas loc?

Parce que l'index peut ne pas être unique et l'utilisation locretournera chaque ligne qui correspond à un index interrogé.

Pourquoi n'utilisez-vous pas l' valuesattribut et ne le découpez-vous pas?

En appelant values , si l'intégralité de la trame de données est dans un "bloc" cohérent, Pandas retournera une vue du tableau qui est le "bloc". Sinon, les Pandas devront bricoler un nouveau tableau. Lors du bricolage, ce tableau doit être d'un type uniforme. Cela signifie souvent renvoyer un tableau avec dtype object. En utilisant ilocau lieu de trancher levalues attribut, je me soulage de devoir gérer cela.

Pourquoi utilisez-vous assign?

Quand j'utilise assign le même nom de colonne que j'explose, j'écrase la colonne existante et conserve sa position dans la trame de données.

Pourquoi les valeurs d'index sont-elles répétées?

Grâce à l'utilisation ilocsur des positions répétées, l'indice résultant montre le même motif répété. Une répétition pour chaque élément de la liste ou de la chaîne.
Cela peut être réinitialisé avecreset_index(drop=True)


Pour cordes

Je ne veux pas avoir à couper les cordes prématurément. Donc, au lieu de cela, je compte les occurrences de l' separgument en supposant que si je devais diviser, la longueur de la liste résultante serait un de plus que le nombre de séparateurs.

J'utilise ensuite cela seppour joinles cordes split.

def explode_str(df, col, sep):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.count(sep) + 1)
    return df.iloc[i].assign(**{col: sep.join(s).split(sep)})

Pour les listes

Similaire aux chaînes, sauf que je n'ai pas besoin de compter les occurrences sepcar elles sont déjà divisées.

J'utilise Numpy's concatenatepour brouiller les listes ensemble.

import pandas as pd
import numpy as np

def explode_list(df, col):
    s = df[col]
    i = np.arange(len(s)).repeat(s.str.len())
    return df.iloc[i].assign(**{col: np.concatenate(s)})

piRSquared
la source
J'aime celui la. Vraiment concis et les performances devraient également être très bonnes. Une question cependant: df.iloc [i] est-il le même que la répétition de lignes de la trame de données ou est-ce plus efficace que cela? Merci!
Tim
7

Il est possible de diviser et d'exploser la trame de données sans changer la structure de la trame de données

Fractionner et développer les données de colonnes spécifiques

Contribution:

    var1    var2
0   a,b,c   1
1   d,e,f   2



#Get the indexes which are repetative with the split 
temp = df['var1'].str.split(',')
df = df.reindex(df.index.repeat(temp.apply(len)))


df['var1'] = np.hstack(temp)

En dehors:

    var1    var2
0   a   1
0   b   1
0   c   1
1   d   2
1   e   2
1   f   2

Édition-1

Fractionner et développer des lignes pour plusieurs colonnes

Filename    RGB                                             RGB_type
0   A   [[0, 1650, 6, 39], [0, 1691, 1, 59], [50, 1402...   [r, g, b]
1   B   [[0, 1423, 16, 38], [0, 1445, 16, 46], [0, 141...   [r, g, b]

Réindexation basée sur la colonne de référence et alignement des informations de valeur de colonne avec la pile

df = df.reindex(df.index.repeat(df['RGB_type'].apply(len)))
df = df.groupby('Filename').apply(lambda x:x.apply(lambda y: pd.Series(y.iloc[0])))
df.reset_index(drop=True).ffill()

En dehors:

                Filename    RGB_type    Top 1 colour    Top 1 frequency Top 2 colour    Top 2 frequency
    Filename                            
 A  0       A   r   0   1650    6   39
    1       A   g   0   1691    1   59
    2       A   b   50  1402    49  187
 B  0       B   r   0   1423    16  38
    1       B   g   0   1445    16  46
    2       B   b   0   1419    16  39
Naga kiran
la source
5

J'ai trouvé une solution pour les cadres de données avec un nombre arbitraire de colonnes (tout en ne séparant que les entrées d'une colonne à la fois).

def splitDataFrameList(df,target_column,separator):
    ''' df = dataframe to split,
    target_column = the column containing the values to split
    separator = the symbol used to perform the split

    returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
    The values in the other columns are duplicated across the newly divided rows.
    '''
    def splitListToRows(row,row_accumulator,target_column,separator):
        split_row = row[target_column].split(separator)
        for s in split_row:
            new_row = row.to_dict()
            new_row[target_column] = s
            row_accumulator.append(new_row)
    new_rows = []
    df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
    new_df = pandas.DataFrame(new_rows)
    return new_df
jlln
la source
2
agréable mais malheureusement lent à cause de cette conversion todict () :(
MAQ
4

Voici un message assez simple qui utilise la splitméthode de l' straccessoire pandas , puis utilise NumPy pour aplatir chaque ligne en un seul tableau.

Les valeurs correspondantes sont récupérées en répétant la colonne non fractionnée le nombre correct de fois avec np.repeat.

var1 = df.var1.str.split(',', expand=True).values.ravel()
var2 = np.repeat(df.var2.values, len(var1) / len(df))

pd.DataFrame({'var1': var1,
              'var2': var2})

  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
Ted Petrou
la source
1
Cela pourrait être une très belle réponse. Malheureusement, il n'est pas mis à l'échelle pour de nombreuses colonnes, n'est-ce pas?
Michael Dorner
3

J'ai eu du mal avec une expérience de mémoire insuffisante en utilisant diverses façons d'exploser mes listes, j'ai donc préparé des repères pour m'aider à décider quelles réponses à voter. J'ai testé cinq scénarios avec des proportions variables de la longueur de la liste par rapport au nombre de listes. Partager les résultats ci-dessous:

Temps: (moins c'est mieux, cliquez pour voir la grande version)

La vitesse

Utilisation maximale de la mémoire: (moins c'est mieux)

Utilisation maximale de la mémoire

Conclusions :

  • @ Réponse de MaxU (mise à jour 2), concaténation du nom de code offre la meilleure vitesse dans presque tous les cas, tout en maintenant une utilisation de la mémoire rapide,
  • voir la réponse de @ DMulligan ( pile de nom de code ) si vous avez besoin de traiter de nombreuses lignes avec des listes relativement petites et que vous pouvez vous permettre d'augmenter la mémoire de pointe,
  • la réponse acceptée de @ Chang fonctionne bien pour les trames de données qui ont quelques lignes mais de très grandes listes.

Tous les détails (fonctions et code de référence) sont dans cet essentiel GitHub . Veuillez noter que le problème de référence a été simplifié et n'incluait pas le fractionnement des chaînes dans la liste - ce que la plupart des solutions ont effectué de manière similaire.

krassowski
la source
Belle comparaison! Cela vous dérange de publier un code que vous avez utilisé pour tracer les repères?
MaxU
1
Veuillez consulter ce lien: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (déjà inclus dans la réponse) - OMI, il serait un peu trop long de tout coller ici.
krassowski
2

Basé sur l'excellente solution de @ DMulligan , voici une fonction générique vectorisée (sans boucles) qui fractionne une colonne d'une trame de données en plusieurs lignes, et la fusionne à nouveau à la trame de données d'origine. Il utilise également une excellente change_column_orderfonction générique de ce réponse .

def change_column_order(df, col_name, index):
    cols = df.columns.tolist()
    cols.remove(col_name)
    cols.insert(index, col_name)
    return df[cols]

def split_df(dataframe, col_name, sep):
    orig_col_index = dataframe.columns.tolist().index(col_name)
    orig_index_name = dataframe.index.name
    orig_columns = dataframe.columns
    dataframe = dataframe.reset_index()  # we need a natural 0-based index for proper merge
    index_col_name = (set(dataframe.columns) - set(orig_columns)).pop()
    df_split = pd.DataFrame(
        pd.DataFrame(dataframe[col_name].str.split(sep).tolist())
        .stack().reset_index(level=1, drop=1), columns=[col_name])
    df = dataframe.drop(col_name, axis=1)
    df = pd.merge(df, df_split, left_index=True, right_index=True, how='inner')
    df = df.set_index(index_col_name)
    df.index.name = orig_index_name
    # merge adds the column to the last place, so we need to move it back
    return change_column_order(df, col_name, orig_col_index)

Exemple:

df = pd.DataFrame([['a:b', 1, 4], ['c:d', 2, 5], ['e:f:g:h', 3, 6]], 
                  columns=['Name', 'A', 'B'], index=[10, 12, 13])
df
        Name    A   B
    10   a:b     1   4
    12   c:d     2   5
    13   e:f:g:h 3   6

split_df(df, 'Name', ':')
    Name    A   B
10   a       1   4
10   b       1   4
12   c       2   5
12   d       2   5
13   e       3   6
13   f       3   6    
13   g       3   6    
13   h       3   6    

Notez qu'il conserve l'index et l'ordre d'origine des colonnes. Il fonctionne également avec les trames de données qui ont un index non séquentiel.

Dennis Golomazov
la source
2
cela a craqué celui-ci pour moi, beau travail: stackoverflow.com/a/48554655/6672746
Evan
2

Le split de fonction de chaîne peut prendre un argument booléen option 'expand'.

Voici une solution utilisant cet argument:

(a.var1
  .str.split(",",expand=True)
  .set_index(a.var2)
  .stack()
  .reset_index(level=1, drop=True)
  .reset_index()
  .rename(columns={0:"var1"}))
cgels
la source
1

Je viens d'utiliser l'excellente réponse de jiln ci-dessus, mais il fallait l'étendre pour diviser plusieurs colonnes. Je pensais partager.

def splitDataFrameList(df,target_column,separator):
''' df = dataframe to split,
target_column = the column containing the values to split
separator = the symbol used to perform the split

returns: a dataframe with each entry for the target column separated, with each element moved into a new row. 
The values in the other columns are duplicated across the newly divided rows.
'''
def splitListToRows(row, row_accumulator, target_columns, separator):
    split_rows = []
    for target_column in target_columns:
        split_rows.append(row[target_column].split(separator))
    # Seperate for multiple columns
    for i in range(len(split_rows[0])):
        new_row = row.to_dict()
        for j in range(len(split_rows)):
            new_row[target_columns[j]] = split_rows[j][i]
        row_accumulator.append(new_row)
new_rows = []
df.apply(splitListToRows,axis=1,args = (new_rows,target_column,separator))
new_df = pd.DataFrame(new_rows)
return new_df
Couteau Derryn Webster
la source
1

mise à jour de la réponse de MaxU avec prise en charge de MultiIndex

def explode(df, lst_cols, fill_value='', preserve_index=False):
    """
    usage:
        In [134]: df
        Out[134]:
           aaa  myid        num          text
        0   10     1  [1, 2, 3]  [aa, bb, cc]
        1   11     2         []            []
        2   12     3     [1, 2]      [cc, dd]
        3   13     4         []            []

        In [135]: explode(df, ['num','text'], fill_value='')
        Out[135]:
           aaa  myid num text
        0   10     1   1   aa
        1   10     1   2   bb
        2   10     1   3   cc
        3   11     2
        4   12     3   1   cc
        5   12     3   2   dd
        6   13     4
    """
    # make sure `lst_cols` is list-alike
    if (lst_cols is not None
        and len(lst_cols) > 0
        and not isinstance(lst_cols, (list, tuple, np.ndarray, pd.Series))):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)
    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()
    # preserve original index values    
    idx = np.repeat(df.index.values, lens)
    res = (pd.DataFrame({
                col:np.repeat(df[col].values, lens)
                for col in idx_cols},
                index=idx)
             .assign(**{col:np.concatenate(df.loc[lens>0, col].values)
                            for col in lst_cols}))
    # append those rows that have empty lists
    if (lens == 0).any():
        # at least one list in cells is empty
        res = (res.append(df.loc[lens==0, idx_cols], sort=False)
                  .fillna(fill_value))
    # revert the original index order
    res = res.sort_index()
    # reset index if requested
    if not preserve_index:        
        res = res.reset_index(drop=True)

    # if original index is MultiIndex build the dataframe from the multiindex
    # create "exploded" DF
    if isinstance(df.index, pd.MultiIndex):
        res = res.reindex(
            index=pd.MultiIndex.from_tuples(
                res.index,
                names=['number', 'color']
            )
    )
    return res
Shahar Katz
la source
1

One-liner using split(___, expand=True)et les arguments levelet namepour reset_index():

>>> b = a.var1.str.split(',', expand=True).set_index(a.var2).stack().reset_index(level=0, name='var1')
>>> b
   var2 var1
0     1    a
1     1    b
2     1    c
0     2    d
1     2    e
2     2    f

Si vous devez bressembler exactement à la question, vous pouvez également:

>>> b = b.reset_index(drop=True)[['var1', 'var2']]
>>> b
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2
1''
la source
0

J'ai trouvé la solution suivante à ce problème:

def iter_var1(d):
    for _, row in d.iterrows():
        for v in row["var1"].split(","):
            yield (v, row["var2"])

new_a = DataFrame.from_records([i for i in iter_var1(a)],
        columns=["var1", "var2"])
Pavel
la source
0

Une autre solution qui utilise un package de copie python

import copy
new_observations = list()
def pandas_explode(df, column_to_explode):
    new_observations = list()
    for row in df.to_dict(orient='records'):
        explode_values = row[column_to_explode]
        del row[column_to_explode]
        if type(explode_values) is list or type(explode_values) is tuple:
            for explode_value in explode_values:
                new_observation = copy.deepcopy(row)
                new_observation[column_to_explode] = explode_value
                new_observations.append(new_observation) 
        else:
            new_observation = copy.deepcopy(row)
            new_observation[column_to_explode] = explode_values
            new_observations.append(new_observation) 
    return_df = pd.DataFrame(new_observations)
    return return_df

df = pandas_explode(df, column_name)
Ankit Maheshwari
la source
0

Il y a beaucoup de réponses ici, mais je suis surpris que personne n'ait mentionné la fonction d'explosion des pandas intégrée. Consultez le lien ci-dessous: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Pour une raison quelconque, je n'ai pas pu accéder à cette fonction, j'ai donc utilisé le code ci-dessous:

import pandas_explode
pandas_explode.patch()
df_zlp_people_cnt3 = df_zlp_people_cnt2.explode('people')

entrez la description de l'image ici

Ci-dessus est un échantillon de mes données. Comme vous pouvez voir les gens colonne des avait une série de personnes, et j'essayais de l'exploser. Le code que j'ai donné fonctionne pour les données de type liste. Essayez donc d'obtenir vos données de texte séparées par des virgules au format liste. De plus, comme mon code utilise des fonctions intégrées, il est beaucoup plus rapide que les fonctions personnalisées / appliquées.

Remarque: vous devrez peut-être installer pandas_explode avec pip.

Harsha Reddy
la source
0

J'ai eu un problème similaire, ma solution consistait d'abord à convertir la trame de données en une liste de dictionnaires, puis à effectuer la transition. Voici la fonction:

import copy
import re

def separate_row(df, column_name):
    ls = []
    for row_dict in df.to_dict('records'):
        for word in re.split(',', row_dict[column_name]):
            row = copy.deepcopy(row_dict)
            row[column_name]=word
            ls(row)
    return pd.DataFrame(ls)

Exemple:

>>> from pandas import DataFrame
>>> import numpy as np
>>> a = DataFrame([{'var1': 'a,b,c', 'var2': 1},
               {'var1': 'd,e,f', 'var2': 2}])
>>> a
    var1  var2
0  a,b,c     1
1  d,e,f     2
>>> separate_row(a, "var1")
  var1  var2
0    a     1
1    b     1
2    c     1
3    d     2
4    e     2
5    f     2

Vous pouvez également modifier un peu la fonction pour prendre en charge la séparation des lignes de type liste.

Zhiwei
la source