seleccionar recorrer filas documentacion datos data columnas columna agregar python pandas numpy dataframe

python - filas - recorrer data frame pandas



Dividir(explotar) la entrada de cadena de cuadros de datos de pandas en filas separadas (12)

Aquí hay un mensaje bastante sencillo que usa el método de split de acceso de pandas str y luego usa NumPy para aplanar cada fila en una sola matriz.

Los valores correspondientes se recuperan repitiendo la columna no dividida la cantidad correcta de veces 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

Tengo un pandas dataframe de pandas dataframe en el que una columna de cadenas de texto contiene valores separados por comas. Quiero dividir cada campo CSV y crear una nueva fila por entrada (supongamos que CSV está limpio y solo se debe dividir en '',''). Por ejemplo, a debe convertirse en 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

Hasta ahora, he intentado varias funciones simples, pero el método .apply parece aceptar solo una fila como valor de retorno cuando se usa en un eje, y no puedo hacer que la .transform funcione. ¡Cualquier sugerencia sería muy apreciada!

Ejemplo de datos:

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

Sé que esto no funcionará porque perderemos los metadatos del DataFrame pasando por numpy, pero debería darle una idea de lo que traté de hacer:

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)


Aquí hay una función que escribí para esta tarea común. Es más eficiente que los métodos de Series / stack . El orden de la columna y los nombres se conservan.

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 esta función, la pregunta original es tan simple como:

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


Después de una dolorosa experimentación para encontrar algo más rápido que la respuesta aceptada, conseguí que esto funcionara. Funcionó alrededor de 100 veces más rápido en el conjunto de datos en el que lo probé.

Si alguien sabe una manera de hacerlo más elegante, por favor, modifique mi código. No pude encontrar una manera que funcione sin configurar las otras columnas que desea mantener como índice y luego restablecer el índice y volver a nombrar las columnas, pero me imagino que hay algo más que funciona.

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


He encontrado la siguiente solución a este 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"])


La función de cadena dividida puede tomar una opción argumento booleano ''expand''.

Aquí hay una solución usando este argumento:

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


Otra solución que usa el paquete de copia 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)


Pregunta similar a: pandas: ¿cómo puedo dividir texto en una columna en varias filas?

Podrías hacerlo:

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


Qué tal algo como esto:

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

Entonces solo tienes que cambiar el nombre de las columnas


Se me ocurrió una solución para dataframes con números arbitrarios de columnas (mientras que aún separaba las entradas de una columna a la vez).

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


Sobre la base de la excelente solution de @DMulligan, aquí hay una función vectorizada genérica (sin bucles) que divide una columna de un marco de datos en varias filas y la fusiona de nuevo al marco de datos original. También utiliza una gran función genérica change_column_order partir de esta answer .

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)

Ejemplo:

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

Tenga en cuenta que conserva el índice original y el orden de las columnas. También funciona con dataframes que tienen un índice no secuencial.


Solo usé la excelente respuesta de jiln desde arriba, pero necesitaba expandir para dividir varias columnas. Pensé que compartiría.

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


ACTUALIZACIÓN2: función vectorizada más genérica, que funcionará para múltiples columnas de list normal y múltiple

def explode(df, lst_cols, fill_value=''''): # make sure `lst_cols` is a list if lst_cols and not isinstance(lst_cols, list): 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() if (lens > 0).all(): # ALL lists in cells aren''t empty return pd.DataFrame({ col:np.repeat(df[col].values, lens) for col in idx_cols }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) / .loc[:, df.columns] else: # at least one list in cells is empty return pd.DataFrame({ col:np.repeat(df[col].values, lens) for col in idx_cols }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) / .append(df.loc[lens==0, idx_cols]).fillna(fill_value) / .loc[:, df.columns]

Manifestación:

Varias columnas de list : todas las columnas de list deben tener el mismo número de elementos en cada fila:

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

Preparar:

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: [1, 2], 2: [], 3: []}, ''text'': {0: [''aa'', ''bb'', ''cc''], 1: [''cc'', ''dd''], 2: [], 3: []} })

Columna 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 este pequeño truco podemos convertir columna tipo CSV a columna de list :

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

ACTUALIZACIÓN: enfoque vectorizado genérico (también funcionará para columnas múltiples):

Original DF:

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

Solución:

primero vamos a convertir cadenas de CSV a listas:

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

Ahora podemos hacer esto:

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

ANTIGUA respuesta:

Inspirado por la solución @AFinkelstein , quería hacerlo un poco más generalizado, que podría aplicarse a DF con más de dos columnas y tan rápido, casi, tan rápido como la solución de 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