Come dividere una colonna in due colonne?


196

Ho un frame di dati con una colonna e vorrei dividerlo in due colonne, con un'intestazione di colonna come ' fips'e l'altra'row'

Il mio frame di dati dfè simile al seguente:

          row
0    00000 UNITED STATES
1    01000 ALABAMA
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL

Non so come usare df.row.str[:]per raggiungere il mio obiettivo di dividere la cella di riga. Posso usare df['fips'] = helloper aggiungere una nuova colonna e popolarla con hello. Qualche idea?

         fips       row
0    00000 UNITED STATES
1    01000 ALABAMA 
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL

3
come hai caricato i tuoi dati in panda? Potresti essere in grado di inserire i dati nel formato desiderato utilizzando read_table()o read_fwf()
zach

Risposte:


137

Potrebbe esserci un modo migliore, ma questo è un approccio:

                            row
    0       00000 UNITED STATES
    1             01000 ALABAMA
    2  01001 Autauga County, AL
    3  01003 Baldwin County, AL
    4  01005 Barbour County, AL
df = pd.DataFrame(df.row.str.split(' ',1).tolist(),
                                 columns = ['flips','row'])
   flips                 row
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

6
Tieni presente che .tolist () rimuoverà tutti gli indici che avevi, quindi il tuo nuovo Dataframe verrà reindicizzato da 0 (Non importa nel tuo caso specifico).
Crashthatch,

10
@Crashthatch - poi puoi semplicemente aggiungere index = df.indexe sei bravo.
radice

cosa succede se una cella non può essere divisa?
Nisba,

@Nisba: se una cella non può essere divisa (ad esempio la stringa non contiene spazio per questo caso) funzionerà comunque, ma una parte della divisione sarà vuota. Altre situazioni si verificano nel caso in cui nella colonna siano presenti tipi misti con almeno una cella contenente qualsiasi tipo di numero. Quindi il splitmetodo restituisce NaN e il tolistmetodo restituirà questo valore così com'è (NaN), il che comporterà ValueError(per ovviare a questo problema, è possibile eseguirne il cast al tipo di stringa prima di suddividere). Ti consiglio di provarlo da solo, è il modo migliore per imparare :-)
Nerxis,

@techkuz: sei sicuro di dfavere l' rowintestazione di colonna? Potresti pensare che sia una specie di attributo DataFrame ma è abbastanza chiaro che questo è il nome della colonna. Dipende da te come creare e definire le intestazioni delle colonne, quindi se ne usi uno diverso usalo (ad es df.my_column_name.split(...).).
Nerxis,

388

TL; versione DR:

Per il semplice caso di:

  • Ho una colonna di testo con un delimitatore e voglio due colonne

La soluzione più semplice è:

df['A'], df['B'] = df['AB'].str.split(' ', 1).str

Oppure puoi creare creare un DataFrame con una colonna per ogni voce della divisione automaticamente con:

df['AB'].str.split(' ', 1, expand=True)

È necessario utilizzare expand=Truese le stringhe hanno un numero non uniforme di divisioni e si desidera Nonesostituire i valori mancanti.

Notare come, in entrambi i casi, il .tolist()metodo non sia necessario. Nemmeno lo è zip().

In dettaglio:

La soluzione di Andy Hayden è eccellente nel dimostrare la potenza del str.extract()metodo.

Ma per una semplice suddivisione su un separatore noto (come, divisione per trattini o divisione per spazi bianchi), il .str.split()metodo è sufficiente 1 . Funziona su una colonna (Serie) di stringhe e restituisce una colonna (Serie) di elenchi:

>>> import pandas as pd
>>> df = pd.DataFrame({'AB': ['A1-B1', 'A2-B2']})
>>> df

      AB
0  A1-B1
1  A2-B2
>>> df['AB_split'] = df['AB'].str.split('-')
>>> df

      AB  AB_split
0  A1-B1  [A1, B1]
1  A2-B2  [A2, B2]

1: Se non sei sicuro di cosa .str.split()facciano i primi due parametri , consiglio i documenti per la semplice versione del metodo Python .

Ma come vai da:

  • una colonna contenente elenchi di due elementi

per:

  • due colonne, ognuna contenente il rispettivo elemento delle liste?

Bene, dobbiamo dare un'occhiata più da vicino .strall'attributo di una colonna.

È un oggetto magico che viene utilizzato per raccogliere metodi che trattano ogni elemento di una colonna come una stringa e quindi applicare il rispettivo metodo in ciascun elemento nel modo più efficiente possibile:

>>> upper_lower_df = pd.DataFrame({"U": ["A", "B", "C"]})
>>> upper_lower_df

   U
0  A
1  B
2  C
>>> upper_lower_df["L"] = upper_lower_df["U"].str.lower()
>>> upper_lower_df

   U  L
0  A  a
1  B  b
2  C  c

Ma ha anche un'interfaccia di "indicizzazione" per ottenere ogni elemento di una stringa dal suo indice:

>>> df['AB'].str[0]

0    A
1    A
Name: AB, dtype: object

>>> df['AB'].str[1]

0    1
1    2
Name: AB, dtype: object

Naturalmente, questa interfaccia di indicizzazione di .strnon importa davvero se ogni elemento che sta indicizzando è in realtà una stringa, purché possa essere indicizzata, quindi:

>>> df['AB'].str.split('-', 1).str[0]

0    A1
1    A2
Name: AB, dtype: object

>>> df['AB'].str.split('-', 1).str[1]

0    B1
1    B2
Name: AB, dtype: object

Quindi, si tratta semplicemente di trarre vantaggio dalla tupla di Python che consente di decomprimere iterable

>>> df['A'], df['B'] = df['AB'].str.split('-', 1).str
>>> df

      AB  AB_split   A   B
0  A1-B1  [A1, B1]  A1  B1
1  A2-B2  [A2, B2]  A2  B2

Naturalmente, ottenere un DataFrame dalla divisione di una colonna di stringhe è così utile che il .str.split()metodo può farlo per te con il expand=Trueparametro:

>>> df['AB'].str.split('-', 1, expand=True)

    0   1
0  A1  B1
1  A2  B2

Quindi, un altro modo di realizzare ciò che volevamo è fare:

>>> df = df[['AB']]
>>> df

      AB
0  A1-B1
1  A2-B2

>>> df.join(df['AB'].str.split('-', 1, expand=True).rename(columns={0:'A', 1:'B'}))

      AB   A   B
0  A1-B1  A1  B1
1  A2-B2  A2  B2

La expand=Trueversione, sebbene più lunga, presenta un netto vantaggio rispetto al metodo di decompressione delle tuple. Il disimballaggio di tuple non si occupa bene di divisioni di diverse lunghezze:

>>> df = pd.DataFrame({'AB': ['A1-B1', 'A2-B2', 'A3-B3-C3']})
>>> df
         AB
0     A1-B1
1     A2-B2
2  A3-B3-C3
>>> df['A'], df['B'], df['C'] = df['AB'].str.split('-')
Traceback (most recent call last):
  [...]    
ValueError: Length of values does not match length of index
>>> 

Ma lo expand=Truegestisce bene posizionandolo Nonenelle colonne per le quali non ci sono abbastanza "divisioni":

>>> df.join(
...     df['AB'].str.split('-', expand=True).rename(
...         columns={0:'A', 1:'B', 2:'C'}
...     )
... )
         AB   A   B     C
0     A1-B1  A1  B1  None
1     A2-B2  A2  B2  None
2  A3-B3-C3  A3  B3    C3

df ['A'], df ['B'] = df ['AB']. str.split ('', 1) .str Qual è il significato di '1' in split ('', 1)?
Hariprasad,

@Hariprasad, è il numero massimo di divisioni. Ho aggiunto un collegamento ai documenti per la versione Python del .split()metodo che spiega meglio i primi due parametri rispetto ai documenti Pandas.
LeoRochael

5
pandas 1.0.0 riporta "FutureWarning: l'iterazione colonnare sui personaggi sarà deprecata nelle versioni future."
Frank,

1
Funziona con Python 1.0.1. df.join(df['AB'].str.split('-', 1, expand=True).rename(columns={0:'A', 1:'B'}))
Martien Lubberink

59

Puoi estrarre le diverse parti abbastanza ordinatamente usando un modello regex:

In [11]: df.row.str.extract('(?P<fips>\d{5})((?P<state>[A-Z ]*$)|(?P<county>.*?), (?P<state_code>[A-Z]{2}$))')
Out[11]: 
    fips                    1           state           county state_code
0  00000        UNITED STATES   UNITED STATES              NaN        NaN
1  01000              ALABAMA         ALABAMA              NaN        NaN
2  01001   Autauga County, AL             NaN   Autauga County         AL
3  01003   Baldwin County, AL             NaN   Baldwin County         AL
4  01005   Barbour County, AL             NaN   Barbour County         AL

[5 rows x 5 columns]

Per spiegare la regex piuttosto lunga:

(?P<fips>\d{5})
  • Abbina le cinque cifre ( \d) e le nomina "fips".

La prossima parte:

((?P<state>[A-Z ]*$)|(?P<county>.*?), (?P<state_code>[A-Z]{2}$))

Fa una ( |) una delle due cose:

(?P<state>[A-Z ]*$)
  • Corrisponde a qualsiasi numero ( *) di lettere maiuscole o spazi ( [A-Z ]) e nomina questo "state"prima della fine della stringa ( $),

o

(?P<county>.*?), (?P<state_code>[A-Z]{2}$))
  • corrisponde a qualsiasi altra cosa ( .*) quindi
  • una virgola e uno spazio quindi
  • corrisponde alle due cifre state_codeprima della fine della stringa ( $).

Nell'esempio:
Nota che le prime due righe colpiscono lo "stato" (lasciando NaN nelle colonne county e state_code), mentre le ultime tre colpiscono la contea, state_code (lasciando NaN nella colonna state).


Questa è sicuramente la soluzione migliore, ma potrebbe essere un po 'travolgente per alcuni con la regex molto estesa. Perché non farlo come parte 2 e avere parte 1 con solo le colonne fips e row?
Tavolini Bobby,

2
@josh questo è un buon punto, mentre le singole parti del regex sono "facili" da capire, il regex lungo può complicarsi rapidamente. Ho aggiunto alcune spiegazioni per i futuri lettori! (Ho anche dovuto aggiornare il link ai documenti che spiegano la (?P<label>...)sintassi! Non ho idea del motivo per cui ho scelto la regex più complessa, chiaramente quella semplice potrebbe funzionare hmmmm
Andy Hayden

1
Sembra molto più amichevole. Sono contento che tu l'abbia fatto perché mi ha fatto guardare i documenti per capire <group_name>. Ora lo so che rende il mio codice molto succinto.
Tavolini Bobby


22

Se non vuoi creare un nuovo frame di dati o se il tuo frame di dati ha più colonne di quelle che desideri dividere, puoi:

df["flips"], df["row_name"] = zip(*df["row"].str.split().tolist())
del df["row"]  

1
Ottengo un zip argument #1 must support iterationerrore, Python 2.7
Allan Ruin

20

È possibile utilizzare lo str.splitspazio bianco (separatore predefinito) e il parametro expand=Trueper DataFramecon assegnazione a nuove colonne:

df = pd.DataFrame({'row': ['00000 UNITED STATES', '01000 ALABAMA', 
                           '01001 Autauga County, AL', '01003 Baldwin County, AL', 
                           '01005 Barbour County, AL']})
print (df)
                        row
0       00000 UNITED STATES
1             01000 ALABAMA
2  01001 Autauga County, AL
3  01003 Baldwin County, AL
4  01005 Barbour County, AL



df[['a','b']] = df['row'].str.split(n=1, expand=True)
print (df)
                        row      a                   b
0       00000 UNITED STATES  00000       UNITED STATES
1             01000 ALABAMA  01000             ALABAMA
2  01001 Autauga County, AL  01001  Autauga County, AL
3  01003 Baldwin County, AL  01003  Baldwin County, AL
4  01005 Barbour County, AL  01005  Barbour County, AL

Se necessario, modificare la colonna originale con DataFrame.pop

df[['a','b']] = df.pop('row').str.split(n=1, expand=True)
print (df)
       a                   b
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Come è lo stesso:

df[['a','b']] = df['row'].str.split(n=1, expand=True)
df = df.drop('row', axis=1)
print (df)

       a                   b
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Se ottieni errore:

#remove n=1 for split by all whitespaces
df[['a','b']] = df['row'].str.split(expand=True)

ValueError: le colonne devono avere la stessa lunghezza della chiave

Puoi controllare e restituire 4 colonne DataFrame, non solo 2:

print (df['row'].str.split(expand=True))
       0        1        2     3
0  00000   UNITED   STATES  None
1  01000  ALABAMA     None  None
2  01001  Autauga  County,    AL
3  01003  Baldwin  County,    AL
4  01005  Barbour  County,    AL

Quindi la soluzione viene aggiunta nuova DataFrameda join:

df = pd.DataFrame({'row': ['00000 UNITED STATES', '01000 ALABAMA', 
                           '01001 Autauga County, AL', '01003 Baldwin County, AL', 
                           '01005 Barbour County, AL'],
                    'a':range(5)})
print (df)
   a                       row
0  0       00000 UNITED STATES
1  1             01000 ALABAMA
2  2  01001 Autauga County, AL
3  3  01003 Baldwin County, AL
4  4  01005 Barbour County, AL

df = df.join(df['row'].str.split(expand=True))
print (df)

   a                       row      0        1        2     3
0  0       00000 UNITED STATES  00000   UNITED   STATES  None
1  1             01000 ALABAMA  01000  ALABAMA     None  None
2  2  01001 Autauga County, AL  01001  Autauga  County,    AL
3  3  01003 Baldwin County, AL  01003  Baldwin  County,    AL
4  4  01005 Barbour County, AL  01005  Barbour  County,    AL

Con rimuovi colonna originale (se ci sono anche altre colonne):

df = df.join(df.pop('row').str.split(expand=True))
print (df)
   a      0        1        2     3
0  0  00000   UNITED   STATES  None
1  1  01000  ALABAMA     None  None
2  2  01001  Autauga  County,    AL
3  3  01003  Baldwin  County,    AL
4  4  01005  Barbour  County,    AL   

8

Se si desidera dividere una stringa in più di due colonne in base a un delimitatore, è possibile omettere il parametro "divisioni massime".
Puoi usare:

df['column_name'].str.split('/', expand=True)

Ciò creerà automaticamente quante colonne quanti saranno il numero massimo di campi inclusi in una qualsiasi delle tue stringhe iniziali.


6

Sorpreso non l'ho ancora visto. Se hai solo bisogno di due divisioni, lo consiglio vivamente. . .

Series.str.partition

partition esegue una divisione sul separatore ed è generalmente abbastanza performante.

df['row'].str.partition(' ')[[0, 2]]

       0                   2
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Se devi rinominare le righe,

df['row'].str.partition(' ')[[0, 2]].rename({0: 'fips', 2: 'row'}, axis=1)

    fips                 row
0  00000       UNITED STATES
1  01000             ALABAMA
2  01001  Autauga County, AL
3  01003  Baldwin County, AL
4  01005  Barbour County, AL

Se è necessario ricollegarsi all'originale, utilizzare joino concat:

df.join(df['row'].str.partition(' ')[[0, 2]])

pd.concat([df, df['row'].str.partition(' ')[[0, 2]]], axis=1)

                        row      0                   2
0       00000 UNITED STATES  00000       UNITED STATES
1             01000 ALABAMA  01000             ALABAMA
2  01001 Autauga County, AL  01001  Autauga County, AL
3  01003 Baldwin County, AL  01003  Baldwin County, AL
4  01005 Barbour County, AL  01005  Barbour County, AL

0

Preferisco esportare le corrispondenti serie di panda (ovvero le colonne di cui ho bisogno), usando la funzione apply per dividere il contenuto della colonna in più serie e quindi unire le colonne generate al DataFrame esistente. Naturalmente, la colonna di origine dovrebbe essere rimossa.

per esempio

 col1 = df["<col_name>"].apply(<function>)
 col2 = ...
 df = df.join(col1.to_frame(name="<name1>"))
 df = df.join(col2.toframe(name="<name2>"))
 df = df.drop(["<col_name>"], axis=1)

Per dividere due parole, la funzione di stringhe dovrebbe essere qualcosa del genere:

lambda x: x.split(" ")[0] # for the first element
lambda x: x.split(" ")[-1] # for the last element

0

Ho visto che nessuno aveva usato il metodo slice, quindi qui ho messo i miei 2 centesimi qui.

df["<col_name>"].str.slice(stop=5)
df["<col_name>"].str.slice(start=6)

Questo metodo creerà due nuove colonne.


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.