¿Cómo dividir una columna en dos columnas?

Tengo un dataframe con una columna y me gustaría dividirlo en dos columnas, con un encabezado de columna como ‘ fips' y la otra 'row'

Mi df dataframe se ve 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 usar df.row.str[:] para lograr mi objective de dividir la celda de la fila. Puedo usar df['fips'] = hello para agregar una nueva columna y rellenarla con 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 

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

 In [34]: import pandas as pd In [35]: df Out[35]: row 0 00000 UNITED STATES 1 01000 ALABAMA 2 01001 Autauga County, AL 3 01003 Baldwin County, AL 4 01005 Barbour County, AL In [36]: df = pd.DataFrame(df.row.str.split(' ',1).tolist(), columns = ['flips','row']) In [37]: df Out[37]: flips row 0 00000 UNITED STATES 1 01000 ALABAMA 2 01001 Autauga County, AL 3 01003 Baldwin County, AL 4 01005 Barbour County, AL 

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

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

En detalle:

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

Pero para una división simple sobre un separador conocido (como, división por guiones, o división por espacio en blanco), el método .str.split() 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 lo que hacen los dos primeros parámetros de .str.split() , recomiendo los documentos para la versión de Python simple del método .

Pero, ¿cómo vas desde:

  • una columna que contiene listas de dos elementos

a:

  • ¿Dos columnas, cada una conteniendo el elemento respectivo de las listas?

Bueno, necesitamos echar un vistazo más de cerca al atributo .str de una columna.

Es un objeto mágico que se utiliza para recostackr métodos que tratan cada elemento de una columna como una cadena, y luego aplican el método respectivo en cada elemento lo 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 UL 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 de .str realmente no le importa si cada elemento que indexa es en realidad una cadena, siempre que se pueda indexar, así que:

 >>> 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 la tupla de Python de los resultados para hacer

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

Por supuesto, obtener un DataFrame fuera de la división de una columna de cadenas es tan útil que el método .str.split() puede hacerlo por ti con el parámetro expand=True :

 >>> 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 AB 0 A1-B1 A1 B1 1 A2-B2 A2 B2 

Puede extraer las diferentes partes de forma bastante ordenada utilizando un patrón de expresiones regulares:

 In [11]: df.row.str.extract('(?P\d{5})((?P[AZ ]*$)|(?P.*?), (?P[AZ]{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 el regex algo largo:

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

La siguiente parte:

 ((?P[AZ ]*$)|(?P.*?), (?P[AZ]{2}$)) 

Hace ya sea ( | ) una de dos cosas:

 (?P[AZ ]*$) 
  • Coincide con cualquier número ( * ) de letras mayúsculas o espacios ( [AZ ] ) y nombra este "state" antes del final de la cadena ( $ ),

o

 (?P.*?), (?P[AZ]{2}$)) 
  • coincide con cualquier otra cosa ( .* ) entonces
  • una coma y un espacio entonces
  • coincide con el state_code dos dígitos antes del final de la cadena ( $ ).

En el ejemplo:
Tenga en cuenta que las dos primeras filas llegan al “estado” (dejando a NaN en las columnas del condado y código de estado), mientras que las tres últimas golpean al condado, código del estado (que deja a NaN en la columna del estado).

 df[['fips', 'row']] = df['row'].str.split(' ', n=1, expand=True) 

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

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

Puede usar str.split por espacio en blanco (separador predeterminado) y el parámetro expand=True para DataFrame con asignación 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 ab 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) ab 0 00000 UNITED STATES 1 01000 ALABAMA 2 01001 Autauga County, AL 3 01003 Baldwin County, AL 4 01005 Barbour County, AL 

Lo que es igual como:

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

Si recibe 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 DataFrame columna, 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 es agregar un nuevo DataFrame por 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 

Si desea dividir una cadena en más de dos columnas basadas en un delimitador, puede omitir el parámetro ‘splits máximos’.
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.

Series.str.partition
Sorprendido no he visto este todavía. partition realiza una división en el separador y, en general, tiene bastante rendimiento.

 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 unir esto de nuevo al original, use join o 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 

Prefiero exportar la serie de pandas correspondiente (es decir, las columnas que necesito), usar 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 de origen debe ser eliminada.

p.ej

  col1 = df[""].apply() col2 = ... df = df.join(col1.to_frame(name="")) df = df.join(col2.toframe(name="")) df = df.drop([""], axis=1) 

Para dividir la función de dos cadenas de palabras debe ser algo como eso:

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