Dividi (esplodi) la stringa di stringhe di dataframe dei panda in file separate


200

Ho un pandas dataframein cui una colonna di stringhe di testo contiene valori separati da virgola. Voglio dividere ogni campo CSV e creare una nuova riga per voce (supponiamo che i CSV siano puliti e debbano essere divisi solo su ','). Ad esempio, adovrebbe diventare 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

Finora ho provato varie semplici funzioni, ma il .applymetodo sembra accettare solo una riga come valore di ritorno quando viene utilizzato su un asse e non riesco .transforma lavorare. Qualsiasi suggerimento sarebbe molto apprezzato!

Dati di esempio:

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}])

So che non funzionerà perché perdiamo i metadati di DataFrame passando per numpy, ma dovrebbe darti un'idea di ciò che ho cercato di fare:

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)

2
altre soluzioni in questa pagina stanno funzionando ma ne ho trovato una breve ed efficace. stackoverflow.com/questions/27263805/...
desaiankitb

1
Per gli altri che arrivano a questa pagina e cercano una soluzione che mantenga più colonne, dai un'occhiata a questa domanda: stackoverflow.com/questions/17116814/…
Sos

Risposte:


81

Che ne dici di qualcosa del genere:

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

Quindi devi solo rinominare le colonne


1
Sembra che funzionerà. Grazie per l'aiuto! In generale, tuttavia, esiste un approccio preferito per Split-Apply-Combine in cui Apply restituisce un frame di dati di dimensioni arbitrarie (ma coerente per tutti i blocchi) e Combine vstacks solo i DF restituiti?
Vincent

GroupBy.apply dovrebbe funzionare (l'ho appena provato contro il master). Tuttavia, in questo caso non è necessario eseguire il passaggio aggiuntivo del raggruppamento poiché si stanno generando i dati per riga, giusto?
Chang She

1
Hey ragazzi. Mi dispiace buttarmi così tardi, ma mi chiedo se non esiste una soluzione migliore a questo. Sto provando a sperimentare iterrows per la prima volta da quando questo sembra il biglietto per questo. Sono anche confuso dalla soluzione proposta. Cosa rappresenta "_"? Puoi forse spiegare come funziona la soluzione? --Grazie
horatio1701d

11
La soluzione può essere estesa a più di due colonne?
horatio1701d


147

UPDATE2: funzione vettorializzata più generica, che funzionerà per colonne multiple normale multiplelist

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

demo:

Più listcolonne: tutte le listcolonne devono avere lo stesso numero di elementi in ogni riga:

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

preservare i valori dell'indice originale:

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

Impostare:

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: []}
})

Colonna 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

usando questo piccolo trucco possiamo convertire colonne simili a CSV in 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

AGGIORNAMENTO: approccio vettorializzato generico (funzionerà anche per più colonne):

DF originale:

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

Soluzione:

prima convertiamo le stringhe CSV in liste:

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

Ora possiamo fare questo:

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

VECCHIA risposta:

Ispirato dalla soluzione @AFinkelstein , volevo renderlo un po 'più generalizzato, che poteva essere applicato a DF con più di due colonne e veloce, anche quasi, veloce come la soluzione di 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

7
amico, se riesci ad aprire una discussione in Git Panda, penso che abbiamo bisogno di una funzione integrata come questa !!! Ho visto così tante domande su unlistify e unesting in SO per i panda
YOBEN_S

come usarlo per più colonne. Come se avessi i dati separati da virgola in 2 colonne e volessi farlo in sequenza?
Jaskaran Singh Puri,

@JaskaranSinghPuri, vuoi prima convertire tutte le colonne CSV in elenchi.
MaxU

1
Sfortunatamente, non funziona se gli elementi dell'elenco sono tuple. Ma dopo aver convertito l'intera tupla in stringa, funziona come un incantesimo!
Guido,

2
Sembra che il motivo di WenBen sia stato ascoltato dagli dei panda, hanno installato un .explode()metodo nell'API (vedi anche questa risposta ).
cs95,

117

Dopo una dolorosa sperimentazione di trovare qualcosa di più veloce della risposta accettata, ho fatto in modo che funzionasse. Ha funzionato circa 100 volte più velocemente sul set di dati su cui l'ho provato.

Se qualcuno conosce un modo per renderlo più elegante, modifica il mio codice. Non sono riuscito a trovare un modo che funzioni senza impostare le altre colonne che si desidera mantenere come indice e quindi reimpostare l'indice e rinominare le colonne, ma immagino che ci sia qualcos'altro che funzioni.

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

2
Questa soluzione ha funzionato significativamente più velocemente e sembra utilizzare meno memoria,
cirillo

1
Questa è una bella soluzione di panda vettoriale, che stavo cercando. Grazie!
Dennis Golomazov,

Quando provo questo sul mio set di dati, continuo ad arrivare TypeError: object of type 'float' has no len()al primo passo ( DataFrame(df.var1.str.split(',').tolist()))
user5359531

@ user5359531 il tuo set di dati probabilmente ne ha alcuni NaNin quella colonna, quindi la sostituzione èb = DataFrame(a.var1.str.split(',').values.tolist(), index=a.var2).stack()
Flair

Per fortuna ecco un bel resoconto di questa soluzione con l'esempio.
hhbilly,

46

Ecco una funzione che ho scritto per questo compito comune. È più efficiente dei metodi Series/ stack. L'ordine e i nomi delle colonne vengono mantenuti.

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

Con questa funzione, il domanda originale è semplice come:

tidy_split(a, 'var1', sep=',')

1
Questo è velocissimo! Grazie mille per questo
Anurag N. Sharma,

42

Panda> = 0,25

I metodi Series e DataFrame definiscono un .explode()metodo che esplode gli elenchi in righe separate. Vedi la sezione documenti su Esplodere una colonna simile a un elenco .

Poiché hai un elenco di stringhe separate da virgola, dividi la stringa su virgola per ottenere un elenco di elementi, quindi chiama explodequella colonna.

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

Nota che explodefunziona solo su una singola colonna (per ora).


NaN e liste vuote ottengono il trattamento che meritano senza che tu debba saltare attraverso i cerchi per farlo bene.

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

Questo è un grande vantaggio rispetto alle soluzioniravelrepeat basate su + (che ignorano completamente gli elenchi vuoti e soffocano sui NaN).


4
Questo è il più semplice e si adatta meglio al mio caso! Grazie!
Isaac Sim il

14

Domanda simile come: panda: come posso dividere il testo in una colonna in più righe?

Potresti fare:

>> 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

2
Funziona dopo l'aggiunta di un altro codice di rinomina s.name = 'var1'
Jesse il

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)})

Dimostrazione

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

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

Creiamo un nuovo frame di dati dcon elenchi

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

Commenti generali

Userò np.arangecon repeatper produrre dataframe posizioni dell'indice che posso usare coniloc .

FAQ

Perché non lo uso loc ?

Perché l'indice potrebbe non essere unico e utilizzabile loc restituirà ogni riga corrispondente a un indice interrogato.

Perché non usi il values attributo e lo dividi?

Quando si chiama values, se l'intero frame di dati è in un "blocco" coesivo, Pandas restituirà una vista dell'array che è il "blocco". Altrimenti i panda dovranno mettere insieme un nuovo array. Quando si acciuffa, quell'array deve essere di tipo uniforme. Spesso ciò significa restituire un array con dtype che è object. Usando ilocinvece di affettare il filevalues attributo, mi alleggerisco dal doverlo affrontare.

Perché usi assign ?

Quando uso assign lo stesso nome di colonna che sto esplodendo, sovrascrivo la colonna esistente e mantengo la sua posizione nel frame di dati.

Perché si ripetono i valori dell'indice?

In virtù dell'utilizzo ilocsu posizioni ripetute, l'indice risultante mostra lo stesso schema ripetuto. Una ripetizione per ogni elemento dell'elenco o della stringa.
Questo può essere resettato conreset_index(drop=True)


Per archi

Non voglio dover dividere prematuramente le corde. Quindi, invece, conto le occorrenze sepdell'argomento supponendo che se dovessi dividere, la lunghezza dell'elenco risultante sarebbe uno in più rispetto al numero di separatori.

Quindi lo uso sepper joinle stringhe 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)})

Per gli elenchi

Simile per le stringhe, tranne per il fatto che non ho bisogno di contare le occorrenze di sep perché è già diviso.

Uso Numpy concatenateper bloccare gli elenchi insieme.

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)})


Mi piace questa. Davvero conciso e anche le prestazioni dovrebbero essere davvero buone. Una domanda però: df.iloc [i] è uguale a ripetere le righe del frame di dati o è più efficiente di così? Grazie!
Tim

7

Esiste la possibilità di dividere ed esplodere il frame di dati senza modificare la struttura del dataframe

Dividi ed espandi i dati di colonne specifiche

Ingresso:

    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)

Su:

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

Edit-1

Dividi ed espandi le righe per più colonne

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]

Ri-indicizzazione basata sulla colonna di riferimento e allineamento delle informazioni sul valore della colonna con lo stack

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()

Su:

                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

5

Ho trovato una soluzione per dataframe con un numero arbitrario di colonne (pur separando solo le voci di una colonna alla volta).

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

2
bello ma tristemente lento a causa di questa conversione todict () :(
MAQ

4

Ecco un messaggio abbastanza semplice che usa il splitmetodo dei pandastr e quindi utilizza NumPy per appiattire ogni riga in un singolo array.

I valori corrispondenti vengono recuperati ripetendo la colonna non suddivisa il numero corretto di volte con 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

1
Potrebbe essere una risposta bellissima. Sfortunatamente, non si adatta a molte colonne, vero?
Michael Dorner,

3

Ho lottato con un'esperienza di memoria insufficiente utilizzando vari modi per esplodere i miei elenchi, quindi ho preparato alcuni parametri di riferimento per aiutarmi a decidere quali risposte migliorare. Ho testato cinque scenari con proporzioni variabili della lunghezza dell'elenco rispetto al numero di elenchi. Condivisione dei risultati di seguito:

Tempo: (meno è meglio, fai clic per visualizzare la versione grande)

Velocità

Picco di utilizzo della memoria: (meno è meglio)

Picco di utilizzo della memoria

Conclusioni :

  • @ Risposta di MaxU (aggiornamento 2), nome in codice concatenato offre la migliore velocità in quasi tutti i casi, mantenendo basso il consumo di memoria,
  • vedere la risposta di @ DMulligan ( stack di nome in codice ) se è necessario elaborare molte righe con elenchi relativamente piccoli e può permettersi un aumento della memoria di picco,
  • la risposta di @ Chang accettata funziona bene per i frame di dati con poche righe ma elenchi molto grandi.

Tutti i dettagli (funzioni e codice di benchmarking) sono in questa sintesi di GitHub . Si noti che il problema del benchmark è stato semplificato e non includeva la suddivisione delle stringhe nell'elenco, che la maggior parte delle soluzioni ha eseguito in modo simile.


Bel confronto! Ti dispiace pubblicare un codice che hai usato per tracciare i benchmark?
MaxU

1
Si prega di consultare questo link: gist.github.com/krassowski/0259a2cd2ba774ccd9f69bbcc3187fbf (già incluso nella risposta) - IMO sarebbe un po 'troppo lungo per incollarlo tutto qui.
Krassowski,

2

Basato sull'eccellente soluzione @ DMulligan , ecco una generica funzione vettorializzata (senza loop) che divide una colonna di un frame di dati in più righe e la fonde nuovamente con il frame di dati originale. Utilizza anche una grande change_column_orderfunzione generica da questa risposta .

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)

Esempio:

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    

Si noti che conserva l'indice e l'ordine originali delle colonne. Funziona anche con i frame di dati che hanno un indice non sequenziale.


2
questo ha rotto questo per me, bel lavoro: stackoverflow.com/a/48554655/6672746
Evan

2

La suddivisione della funzione stringa può accettare un'opzione booleana 'espandi'.

Ecco una soluzione che utilizza questo argomento:

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

1

Ho appena usato l'eccellente risposta di jiln dall'alto, ma doveva espandersi per dividere più colonne. Ho pensato di condividere.

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

1

ha aggiornato la risposta di MaxU con il supporto 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

1

One-liner utilizzando split(___, expand=True)e l' levele nameargomenti per 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

Se hai bisogno bdi apparire esattamente come nella domanda, puoi anche fare:

>>> 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

0

Ho trovato la seguente soluzione a questo problema:

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"])

0

Un'altra soluzione che utilizza il pacchetto di copia di 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)

0

Ci sono molte risposte qui, ma sono sorpreso che nessuno abbia menzionato la funzione di esplosione dei panda incorporati. Dai un'occhiata al link qui sotto: https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html#pandas.DataFrame.explode

Per qualche motivo non sono stato in grado di accedere a quella funzione, quindi ho usato il codice seguente:

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

inserisci qui la descrizione dell'immagine

Sopra è un esempio dei miei dati. Come puoi vedere le persone rubrica delle aveva una serie di persone e stavo cercando di esplodere. Il codice che ho fornito funziona per i dati di tipo elenco. Quindi prova a ottenere i dati di testo separati da virgola nel formato elenco. Inoltre, poiché il mio codice utilizza funzioni integrate, è molto più veloce delle funzioni personalizzate / applica.

Nota: potrebbe essere necessario installare pandas_explode con pip.


0

Ho avuto un problema simile, la mia soluzione era di convertire prima il frame di dati in un elenco di dizionari, quindi eseguire la transizione. Ecco la funzione:

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)

Esempio:

>>> 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

È inoltre possibile modificare un po 'la funzione per supportare la separazione delle righe del tipo di elenco.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.