¿Cómo dividir una columna en dos columnas?

196

Tengo un marco de datos con una columna y me gustaría dividirlo en dos columnas, con un encabezado de columna como ' fips'y el otro'row'

Mi dataframe se dfve así:

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

No sé cómo usarlo df.row.str[:]para lograr mi objetivo de dividir la celda de la fila. Puedo usar df['fips'] = hellopara agregar una nueva columna y completarla hello. ¿Algunas ideas?

         fips       row
0    00000 UNITED STATES
1    01000 ALABAMA 
2    01001 Autauga County, AL
3    01003 Baldwin County, AL
4    01005 Barbour County, AL
Alaska
fuente
3
¿Cómo cargaron sus datos en pandas? Es posible que pueda cargar los datos en el formato deseado usando read_table()o read_fwf()
zach

Respuestas:

137

Puede haber una mejor manera, pero este es un enfoque:

                            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
raíz
fuente
66
Tenga en cuenta que .tolist () eliminará los índices que tenía, por lo que su nuevo Dataframe se reindexará desde 0 (no importa en su caso específico).
Crashthatch
10
@Crashthatch: una vez más, solo puedes agregar index = df.indexy eres bueno
root
¿Qué pasa si una celda no se puede dividir?
Nisba
@Nisba: si alguna celda no se puede dividir (por ejemplo, la cadena no contiene ningún espacio para este caso) seguirá funcionando pero una parte de la división estará vacía. Otras situaciones sucederán en caso de que tenga tipos mixtos en la columna con al menos una celda que contenga cualquier tipo de número. Luego, el splitmétodo devuelve NaN y el tolistmétodo devolverá este valor tal como está (NaN), lo que dará como resultado ValueError(para superar este problema, puede convertirlo al tipo de cadena antes de dividirlo). Te recomiendo que lo pruebes por tu cuenta, es la mejor manera de aprender :-)
Nerxis
@techkuz: ¿Está seguro de que dftiene el rowencabezado de columna? Puede pensar que es algún tipo de atributo DataFrame, pero está bastante claro que este es el nombre de la columna. Depende de usted cómo crea y define sus encabezados de columna, de modo que si usa uno diferente, úselo (por ejemplo df.my_column_name.split(...)).
Nerxis
389

TL; versión DR:

Para el caso simple de:

  • Tengo una columna de texto con un delimitador y quiero dos columnas.

La solución más simple es:

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

O puede crear crear un DataFrame con una columna para cada entrada de la división automáticamente con:

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

Debe usar expand=Truesi sus cadenas tienen un número no uniforme de divisiones y desea Nonereemplazar los valores faltantes.

Observe cómo, en cualquier caso, el .tolist()método no es necesario. Tampoco lo es zip().

En detalle:

La solución de Andy Hayden es excelente para demostrar el poder del str.extract()método.

Pero para una división simple sobre un separador conocido (como, división por guiones o división por espacios en blanco), el .str.split()método es suficiente 1 . Funciona en una columna (Serie) de cadenas y devuelve una columna (Serie) de listas:

>>> 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: Si no está seguro de cuáles son los dos primeros parámetros .str.split(), recomiendo los documentos para la versión simple de Python del método .

Pero como vas desde:

  • una columna que contiene listas de dos elementos

a:

  • dos columnas, cada una con el elemento respectivo de las listas?

Bueno, tenemos que echar un vistazo más de cerca al .stratributo de una columna.

Es un objeto mágico que se utiliza para recopilar métodos que tratan cada elemento de una columna como una cadena, y luego aplica el método respectivo en cada elemento de la manera más eficiente posible:

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

Pero también tiene una interfaz de "indexación" para obtener cada elemento de una cadena por su índice:

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

0    A
1    A
Name: AB, dtype: object

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

0    1
1    2
Name: AB, dtype: object

Por supuesto, a esta interfaz de indexación .strrealmente no le importa si cada elemento que está indexando es en realidad una cadena, siempre que pueda indexarse, por lo tanto:

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

Entonces, es una simple cuestión de aprovechar el desempaquetado de tuplas de Python de iterables para hacer

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

Por supuesto, obtener un DataFrame al dividir una columna de cadenas es tan útil que el .str.split()método puede hacerlo por usted con el expand=Trueparámetro:

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

    0   1
0  A1  B1
1  A2  B2

Entonces, otra forma de lograr lo que queríamos es hacer:

>>> 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=Trueversión, aunque más larga, tiene una clara ventaja sobre el método de desempaquetado de tuplas. El desempaquetado de tuplas no funciona bien con divisiones de diferentes longitudes:

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

Pero lo expand=Truemaneja bien colocando Noneen las columnas para las que no hay suficientes "divisiones":

>>> 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
LeoRochael
fuente
df ['A'], df ['B'] = df ['AB']. str.split ('', 1) .str ¿Cuál es el significado de '1' en split ('', 1)?
Hariprasad
@ Hariprasad, es el número máximo de divisiones. He agregado un enlace a los documentos para la versión de Python del .split()método que explican los dos primeros parámetros mejor que los documentos de Pandas.
LeoRochael
55
pandas 1.0.0 informa "FutureWarning: la iteración en columna sobre los caracteres quedará obsoleta en futuras versiones".
Frank
1
Esto funciona bajo Python 1.0.1. df.join(df['AB'].str.split('-', 1, expand=True).rename(columns={0:'A', 1:'B'}))
Martien Lubberink
59

Puede extraer las diferentes partes con bastante cuidado utilizando un patrón 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]

Para explicar la expresión regular algo larga:

(?P<fips>\d{5})
  • Coincide con los cinco dígitos ( \d) y los nombra "fips".

La siguiente parte:

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

¿Alguna ( |) una de dos cosas:

(?P<state>[A-Z ]*$)
  • Coincide con cualquier número ( *) de letras mayúsculas o espacios ( [A-Z ]) y los nombra "state"antes del final de la cadena ( $),

o

(?P<county>.*?), (?P<state_code>[A-Z]{2}$))
  • coincide con cualquier otra cosa ( .*) entonces
  • una coma y un espacio entonces
  • coincide con los dos dígitos state_codeantes del final de la cadena ( $).

En el ejemplo:
Tenga en cuenta que las dos primeras filas golpean el "estado" (dejando NaN en las columnas de condado y state_code), mientras que las tres últimas golpean el condado, código_de_Estado (dejando NaN en la columna de estado).

Andy Hayden
fuente
Esta es definitivamente la mejor solución, pero puede ser un poco abrumador para algunos con la expresión regular muy extensa. ¿Por qué no hacer eso como parte 2 y tener la parte 1 solo con las columnas fips y row?
Little Bobby Tables
2
@josh ese es un buen punto, mientras que las partes individuales de la expresión regular son "fáciles" de entender, la expresión regular larga puede complicarse rápidamente. ¡Agregué alguna explicación para futuros lectores! (¡También tuve que actualizar el enlace a los documentos que explica la (?P<label>...)sintaxis! No tengo idea de por qué opté por la expresión regular más compleja, claramente la simple podría funcionar hmmmm
Andy Hayden
1
Se ve mucho más amigable. Me alegro de que lo hayas hecho porque me hizo mirar los documentos para entender el <group_name>. Ahora sé que hace que mi código sea muy sucinto.
Little Bobby Tables
44
df[['fips', 'row']] = df['row'].str.split(' ', n=1, expand=True)
Bhagabat Behera
fuente
22

Si no desea crear un nuevo marco de datos, o si su marco de datos tiene más columnas que las que desea dividir, podría:

df["flips"], df["row_name"] = zip(*df["row"].str.split().tolist())
del df["row"]  
keberwein
fuente
1
Me sale un zip argument #1 must support iterationerror, python 2.7
Allan Ruin
20

Puede usar str.splitpor espacios en blanco (separador predeterminado) y parámetro expand=Truepara DataFrameasignar a nuevas columnas:

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

Modificación si es necesario eliminar la columna original 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

¿Qué es lo mismo que:

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

Si obtiene un error:

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

ValueError: las columnas deben tener la misma longitud que la clave

Puede verificar y devolver 4 columnas DataFrame, no 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

Entonces la solución se agrega nueva DataFramepor 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 eliminar columna original (si también hay otras columnas):

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   
jezrael
fuente
8

Si desea dividir una cadena en más de dos columnas en función de un delimitador, puede omitir el parámetro 'divisiones máximas'.
Puedes usar:

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

Esto creará automáticamente tantas columnas como el número máximo de campos incluidos en cualquiera de sus cadenas iniciales.

Jazmín
fuente
6

Sorprendido no he visto este todavía. Si solo necesita dos divisiones, lo recomiendo encarecidamente. . .

Series.str.partition

partition realiza una división en el separador, y generalmente es bastante eficiente.

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

Si necesita cambiar el nombre de las filas,

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

Si necesita volver a unir esto al original, use 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
cs95
fuente
0

Prefiero exportar las series de pandas correspondientes (es decir, las columnas que necesito), usando la función de aplicación para dividir el contenido de la columna en varias series y luego unir las columnas generadas al DataFrame existente. Por supuesto, la columna fuente debe eliminarse.

p.ej

 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)

Para dividir dos palabras, la función de cadenas debería ser algo así:

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

Vi que nadie había usado el método de corte, así que aquí puse mis 2 centavos aquí.

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

Este método creará dos nuevas columnas.

Vingt Cent
fuente