Alargar un DataFrame basado en astackr columnas dentro de él en Pandas

Estoy buscando una función que logre lo siguiente. Se muestra mejor en un ejemplo. Considerar:

pd.DataFrame([ [1, 2, 3 ], [4, 5, np.nan ]], columns=['x', 'y1', 'y2']) 

que se parece a

  x y1 y2 0 1 2 3 1 4 5 NaN 

Me gustaría collapear las columnas y1 y y2 , alargando el DataFame donde sea necesario, para que la salida sea:

  xy 0 1 2 1 1 3 2 4 5 

Es decir, una fila para cada combinación entre x y y1 , o x e y2 . Estoy buscando una función que haga esto de manera relativamente eficiente, ya que tengo múltiples y s y muchas filas.

Aquí hay uno basado en NumPy, ya que buscabas rendimiento:

 def gather_columns(df): col_mask = [i.startswith('y') for i in df.columns] ally_vals = df.iloc[:,col_mask].values y_valid_mask = ~np.isnan(ally_vals) reps = np.count_nonzero(y_valid_mask, axis=1) x_vals = np.repeat(df.x.values, reps) y_vals = ally_vals[y_valid_mask] return pd.DataFrame({'x':x_vals, 'y':y_vals}) 

Ejecución de la muestra

 In [78]: df #(added more cols for variety) Out[78]: x y1 y2 y5 y7 0 1 2 3.0 NaN NaN 1 4 5 NaN 6.0 7.0 In [79]: gather_columns(df) Out[79]: xy 0 1 2.0 1 1 3.0 2 4 5.0 3 4 6.0 4 4 7.0 

Si las columnas y siempre comienzan a partir de la segunda columna hasta el final, podemos simplemente dividir el dataframe y, por lo tanto, obtener un aumento adicional del rendimiento, como por ejemplo:

 def gather_columns_v2(df): ally_vals = df.iloc[:,1:].values y_valid_mask = ~np.isnan(ally_vals) reps = np.count_nonzero(y_valid_mask, axis=1) x_vals = np.repeat(df.x.values, reps) y_vals = ally_vals[y_valid_mask] return pd.DataFrame({'x':x_vals, 'y':y_vals}) 

Puedes usar la stack para hacer las cosas, es decir,

 pd.DataFrame(df.set_index('x').stack().reset_index(level=0).values,columns=['x','y']) xy 0 1.0 2.0 1 1.0 3.0 2 4.0 5.0 

Repita todos los elementos en la primera columna según los recuentos de valores no nulos en cada fila. Luego simplemente cree su dataframe final utilizando el rest de valores no nulos en otras columnas. Puede usar el método DataFrame.count() para contar valores no nulos y numpy.repeat() para repetir una matriz basada en una matriz de cuenta respectiva.

 >>> rest = df.loc[:,'y1':] >>> pd.DataFrame({'x': np.repeat(df['x'], rest.count(1)).values, 'y': rest.values[rest.notna()]}) 

Manifestación:

 >>> df x y1 y2 y3 y4 0 1 2.0 3.0 NaN 6.0 1 4 5.0 NaN 9.0 3.0 2 10 NaN NaN NaN NaN 3 9 NaN NaN 6.0 NaN 4 7 6.0 NaN NaN NaN >>> rest = df.loc[:,'y1':] >>> pd.DataFrame({'x': np.repeat(df['x'], rest.count(1)).values, 'y': rest.values[rest.notna()]}) xy 0 1 2.0 1 1 3.0 2 1 6.0 3 4 5.0 4 4 9.0 5 4 3.0 6 9 6.0 7 7 6.0