Pandas stack / groupby para hacer un nuevo dataframe

Tengo un problema para crear y reorganizar un conjunto de datos. Miré la función de pandas groupby y creo que podría ayudarme a hacerlo, pero no tengo experiencia para hacer que suceda. He creado un ejemplo de mi problema a continuación: mi df:

vehicle color abcd A1 A2 A3 B1 B2 B3 C1 C2 C3 D1 D2 D3 resp 1 bike green 5 4 1 3 3 4 5 3 5 3 NaN NaN NaN NaN NaN NaN 2 walk red 5 3 3 3 4 5 3 3 5 4 NaN NaN NaN NaN NaN NaN 3 car green 4 2 3 3 4 3 5 4 5 5 NaN NaN NaN NaN NaN NaN 4 car blue 4 5 4 4 NaN NaN NaN NaN NaN NaN 5 5 5 3 3 4 5 bus black 2 4 4 3 NaN NaN NaN 2 3 3 2 2 1 NaN NaN NaN 6 car red 4 2 3 3 3 4 4 NaN NaN NaN 4 4 4 NaN NaN NaN 7 bus blue 5 5 2 3 3 3 5 4 3 2 NaN NaN NaN NaN NaN NaN 8 walk red 3 3 4 3 NaN NaN NaN 5 5 5 5 3 3 NaN NaN NaN 9 car blue 5 3 4 3 3 3 3 NaN NaN NaN 4 3 4 NaN NaN NaN 

El conjunto de datos contiene encuestados y respuestas a un cuestionario. Lo que me gustaría hacer es crear un nuevo dataframe con los resp o el índice y los datos de cómo los encuestados respondieron reorganizados. Los datos de las columnas a, b, c, d, vehículo y color se astackn para los encuestados (espero que esa sea la forma correcta de expresslo) en el nuevo dataframe. También los valores de las columnas A a C están en el nuevo marco bajo las columnas BL_val. Solo se completan los datos que corresponden de la letra mayúscula (A1-D3) a la letra pequeña (a, b, c, d). El rest son NAN.

Me gustaría crear un nuevo dataframe a partir de esto y debería tener el siguiente aspecto:

ds:

  vehicle color sl sl_val BL_val1 BL_val2 BL_val3 resp 1 bike green a 5 3 4 5 1 bike green b 4 3 5 3 1 bike green c 1 NaN NaN NaN 1 bike green d 3 NaN NaN NaN 2 walk red a 5 4 5 3 2 walk red b 3 3 5 4 2 walk red c 3 NaN NaN NaN 2 walk red d 3 NaN NaN NaN 3 car green a 4 4 3 5 3 car green b 2 4 5 5 3 car green c 3 NaN NaN NaN 3 car green d 3 NaN NaN NaN 4 car blue a 4 NaN NaN NaN 4 car blue b 5 NaN NaN NaN 4 car blue c 4 5 5 5 4 car blue d 4 3 3 4 5 bus black a 2 NaN NaN NaN 5 bus black b 4 2 3 3 5 bus black c 4 2 2 1 5 bus black d 3 NaN NaN NaN 6 car red a 4 3 4 4 6 car red b 2 NaN NaN NaN 6 car red c 3 4 4 4 6 car red d 3 NaN NaN NaN 7 bus blue a 5 3 3 5 7 bus blue b 5 4 3 2 7 bus blue c 2 NaN NaN NaN 7 bus blue d 3 NaN NaN NaN 8 walk red a 3 NaN NaN NaN 8 walk red b 3 5 5 5 8 walk red c 4 5 3 3 8 walk red d 3 NaN NaN NaN 9 car blue a 5 3 3 3 9 car blue b 3 NaN NaN NaN 9 car blue c 4 4 3 4 9 car blue d NaN NaN NaN NaN 

Realmente necesito algo de ayuda con esto, ¡¡No puedo resolverlo !!

Puede haber una forma más fácil de hacer esto, pero me parece que el patrón de usar groupby para hacer los grupos, realizar operaciones explícitas en ellos y luego combinarlos es a menudo una forma sencilla de obtener lo que quiero. Claro, podría dedicar media hora a crear algo más elegante, pero no tendría tiempo para pasar el rato en SO ..

De todos modos, ¿qué tal algo como esto?

 df = df.set_index(["resp", "vehicle", "color"]) grouped = df.groupby(lambda x: x[0].lower(), axis=1) new_grouped = [] for key, group in grouped: group.columns = ["sl_val"] + ["BL_val{}".format(i) for i in range(1,4)] group["sl"] = key new_grouped.append(group) df2 = pd.concat(new_grouped).reset_index() df2 = df2.sort(["resp", "vehicle", "color"]).set_index("resp") df2 = df2[["vehicle", "color", "sl"] + [k for k in df2.columns if "_" in k]] 

Empezando desde

 >>> df = df.set_index(["resp", "vehicle", "color"]) >>> df abcd A1 A2 A3 B1 B2 B3 C1 C2 C3 D1 D2 D3 resp vehicle color 1 bike green 5 4 1 3 3 4 5 3 5 3 NaN NaN NaN NaN NaN NaN 2 walk red 5 3 3 3 4 5 3 3 5 4 NaN NaN NaN NaN NaN NaN 3 car green 4 2 3 3 4 3 5 4 5 5 NaN NaN NaN NaN NaN NaN 4 car blue 4 5 4 4 NaN NaN NaN NaN NaN NaN 5 5 5 3 3 4 5 bus black 2 4 4 3 NaN NaN NaN 2 3 3 2 2 1 NaN NaN NaN 6 car red 4 2 3 3 3 4 4 NaN NaN NaN 4 4 4 NaN NaN NaN 7 bus blue 5 5 2 3 3 3 5 4 3 2 NaN NaN NaN NaN NaN NaN 8 walk red 3 3 4 3 NaN NaN NaN 5 5 5 5 3 3 NaN NaN NaN 9 car blue 5 3 4 3 3 3 3 NaN NaN NaN 4 3 4 NaN NaN NaN 

Podemos agrupar por la letra inicial en minúsculas:

 >>> grouped = df.groupby(lambda x: x[0].lower(), axis=1) 

Produciendo un montón de grupos que se parecen a:

 >>> next(iter(grouped)) ('a', a A1 A2 A3 resp vehicle color 1 bike green 5 3 4 5 2 walk red 5 4 5 3 3 car green 4 4 3 5 4 car blue 4 NaN NaN NaN 5 bus black 2 NaN NaN NaN 6 car red 4 3 4 4 7 bus blue 5 3 3 5 8 walk red 3 NaN NaN NaN 9 car blue 5 3 3 3) 

Luego simplemente cambiamos los nombres, agregamos la columna "sl" y los pd.concat usando pd.concat . Los últimos bits simplemente coinciden con su orden deseado.

Resultado final:

 >>> df2 vehicle color sl sl_val BL_val1 BL_val2 BL_val3 resp 1 bike green a 5 3 4 5 1 bike green b 4 3 5 3 1 bike green c 1 NaN NaN NaN 1 bike green d 3 NaN NaN NaN 2 walk red a 5 4 5 3 2 walk red b 3 3 5 4 2 walk red c 3 NaN NaN NaN 2 walk red d 3 NaN NaN NaN 3 car green a 4 4 3 5 3 car green b 2 4 5 5 3 car green c 3 NaN NaN NaN 3 car green d 3 NaN NaN NaN 4 car blue a 4 NaN NaN NaN 4 car blue b 5 NaN NaN NaN 4 car blue c 4 5 5 5 4 car blue d 4 3 3 4 5 bus black a 2 NaN NaN NaN 5 bus black b 4 2 3 3 5 bus black c 4 2 2 1 5 bus black d 3 NaN NaN NaN 6 car red a 4 3 4 4 6 car red b 2 NaN NaN NaN 6 car red c 3 4 4 4 6 car red d 3 NaN NaN NaN 7 bus blue a 5 3 3 5 7 bus blue b 5 4 3 2 7 bus blue c 2 NaN NaN NaN 7 bus blue d 3 NaN NaN NaN 8 walk red a 3 NaN NaN NaN 8 walk red b 3 5 5 5 8 walk red c 4 5 3 3 8 walk red d 3 NaN NaN NaN 9 car blue a 5 3 3 3 9 car blue b 3 NaN NaN NaN 9 car blue c 4 4 3 4 9 car blue d 3 NaN NaN NaN 

Una forma ingenua sería escribir una función auxiliar para extraer los subDataFrames relevantes:

 In [11]: def get_letter(df, letter): res = df.loc[:, ['vehicle', 'color', letter] + [letter.upper() + str(i) for i in xrange(1, 4)]] res.columns = ['vehicle', 'color', 'sl_val', 'BL_val1', 'BL_val2', 'BL_val3'] res['sl'] = letter return res In [12]: get_letter(df, 'a') Out[12]: vehicle color sl_val BL_val1 BL_val2 BL_val3 sl resp 1 bike green 5 3 4 5 a 2 walk red 5 4 5 3 a 3 car green 4 4 3 5 a 4 car blue 4 NaN NaN NaN a 5 bus black 2 NaN NaN NaN a 6 car red 4 3 4 4 a 7 bus blue 5 3 3 5 a 8 walk red 3 NaN NaN NaN a 9 car blue 5 3 3 3 a 

Luego, conciliar estos obtiene el resultado que estás buscando (posiblemente con una clasificación):

 In [13]: pd.concat(get_letter(df, letter) for letter in 'abcd') In [14]: pd.concat(get_letter(df, letter) for letter in 'abcd').sort()