Considere el siguiente marco de datos:
import pandas as pd from numpy import nan data = [ (111, nan, nan, 111), (112, 112, nan, 115), (113, nan, nan, nan), (nan, nan, nan, nan), (118, 110, 117, nan), ] df = pd.DataFrame(data, columns=[f'num{i}' for i in range(len(data[0]))]) num0 num1 num2 num3 0 111.0 NaN NaN 111.0 1 112.0 112.0 NaN 115.0 2 113.0 NaN NaN NaN 3 NaN NaN NaN NaN 4 118.0 110.0 117.0 NaNSuponiendo que mi índice es único, busco recuperar los valores únicos por fila de índice, a una salida como la que se muestra a continuación. Deseo mantener las filas vacías.
num1 num2 num3 0 111.0 NaN NaN 1 112.0 115.0 NaN 2 113.0 NaN NaN 3 NaN NaN NaN 4 110.0 117.0 118.0Tengo una solución funcional, aunque lenta, ver más abajo. El orden de los números de salida no es relevante, siempre que todos los valores se presenten en la columna más a la izquierda y los valores nulos en la derecha. Estoy buscando mejores prácticas e ideas potenciales para acelerar el código. Gracias de antemano.
def arrange_row(row): values = list(set(row.dropna(axis=1).values[0])) values = [nan] if not values else values series = pd.Series(values, index=[f"num{i}" for i in range(1, len(values)+1)]) return series df.groupby(level=-1).apply(arrange_row).unstack(level=-1) pd.version == '1.2.3'Podemos stack para remodelar el marco de datos, luego agrupar el marco remodelado en el level=0 y agregar usando unqiue para obtener los valores únicos de cada fila, luego puede crear un nuevo marco de datos a partir de estos valores únicos
s = df.stack().groupby(level=0).unique() pd.DataFrame([*s], index=s.index).reindex(df.index) 0 1 2 0 111.0 NaN NaN 1 112.0 115.0 NaN 2 113.0 NaN NaN 3 NaN NaN NaN 4 118.0 110.0 117.0Utilice df.values con List comprehension y df.dropna :
# Create a list of rows of dataframe In [788]: l = df.values # Use List Comprehension to remove dups from above list of lists In [789]: l_without_dupes = [list(dict.fromkeys(i)) for i in l] # Create a new dataframe from above list and drop the column with all NaN's In [795]: res_df = pd.DataFrame(l_without_dupes).dropna(1, how='all') In [796]: res_df Out[796]: 0 1 2 0 111.0 NaN NaN 1 112.0 NaN 115.0 2 113.0 NaN NaN 3 NaN NaN NaN 4 118.0 110.0 117.0Otra opción, aunque más larga:
outcome = (df.melt(ignore_index= False) # keep the index as a tracker .reset_index() # get the unique rows .drop_duplicates(subset=['index','value']) .dropna() # use this to build the new column names .assign(counter = lambda df: df.groupby('index').cumcount() + 1) .pivot('index', 'counter', 'value') .add_prefix('num') .reindex(df.index) .rename_axis(columns=None) ) outcome num1 num2 num3 0 111.0 NaN NaN 1 112.0 115.0 NaN 2 113.0 NaN NaN 3 NaN NaN NaN 4 118.0 110.0 117.0Si desea que coincida exactamente con su salida, puede volcarlo en numpy, ordenar y regresar a pandas:
pd.DataFrame(np.sort(outcome, axis = 1), columns = outcome.columns) num1 num2 num3 0 111.0 NaN NaN 1 112.0 115.0 NaN 2 113.0 NaN NaN 3 NaN NaN NaN 4 110.0 117.0 118.0Otra opción es hacer la clasificación dentro de numpy antes de remodelar en Pandas:
(pd.DataFrame(np.sort(df, axis = 1)) .apply(pd.unique, axis=1) .apply(pd.Series) .dropna(how='all',axis=1) .set_axis(['num1', 'num2','num3'], axis=1) ) num1 num2 num3 0 111.0 NaN NaN 1 112.0 115.0 NaN 2 113.0 NaN NaN 3 NaN NaN NaN 4 110.0 117.0 118.0