Operación pandby groupby con datos faltantes

En un dataframe de pandas tengo una columna que parece:

0 M 1 E 2 L 3 M.1 4 M.2 5 M.3 6 E.1 7 E.2 8 E.3 9 E.4 10 L.1 11 L.2 12 M.1.a 13 M.1.b 14 M.1.c 15 M.2.a 16 M.3.a 17 E.1.a 18 E.1.b 19 E.1.c 20 E.2.a 21 E.3.a 22 E.3.b 23 E.4.a 

Necesito agrupar todos los valores donde los primeros elementos son E, M, or L y luego, para cada grupo, necesito crear un subgrupo donde el índice es 1, 2, or 3 que contendrá un registro para cada lowercase letter (a, b, c, …) Potencialmente, la solución debería funcionar para cualquier número de niveles de elementos concatenados (en este caso, el número de niveles es 3 (por ejemplo: A.1.a))

 0 1 2 E 1 a b c 2 a 3 a b 4 a L 1 2 M 1 a b c 2 a 3 a 

Lo intenté con:

 df.groupby([0,1,2]).count() 

Pero al resultado le falta el nivel L porque no tiene registros en el último subnivel

Una solución es agregar una variable ficticia y luego eliminarla … como:

 df[2][(df[0]=='L') & (df[2].isnull()) & (df[1].notnull())]='x' df = df.replace(np.nan,' ', regex=True) df.sort_values(0, ascending=False, inplace=True) newdf = df.groupby([0,1,2]).count() 

lo que da:

 0 1 2 E 1 a b c 2 a 3 a b 4 a L 1 x 2 x M 1 a b c 2 a 3 a 

Luego trato con la entrada dummy x más adelante en mi código …

¿Cómo se puede evitar esta forma ackish de usar groupby ?

Suponiendo que la columna en cuestión esté representada por s , podemos:

  1. Dividir en "." Delimitador junto con expand=True para producir un DF expandido.

  2. fnc : verifica si todos los elementos del marco agrupado están formados por None , luego los reemplaza por una entrada "" ficticia que se establece mediante una lista de comprensión . Posteriormente se llama a un constructor de series en la lista filtrada. Cualquier None presente aquí se elimina posteriormente utilizando dropna .

  3. Realice los nombres de las columnas groupby & wrt 0 & 1 y aplique fnc a 2 .


 split_str = s.str.split(".", expand=True) fnc = lambda g: pd.Series(["" if all(x is None for x in g) else x for x in g]).dropna() split_str.groupby([0, 1])[2].apply(fnc) 

produce:

 0 1 E 1 1 a 2 b 3 c 2 1 a 3 1 a 2 b 4 1 a L 1 0 2 0 M 1 1 a 2 b 3 c 2 1 a 3 1 a Name: 2, dtype: object 

Para obtener un DF aplanado, reinicie los índices igual que los niveles utilizados para agrupar el DF antes:

 split_str.groupby([0, 1])[2].apply(fnc).reset_index(level=[0, 1]).reset_index(drop=True) 

produce:

  0 1 2 0 E 1 a 1 E 1 b 2 E 1 c 3 E 2 a 4 E 3 a 5 E 3 b 6 E 4 a 7 L 1 8 L 2 9 M 1 a 10 M 1 b 11 M 1 c 12 M 2 a 13 M 3 a 

Tal vez tengas que encontrar una manera con expresiones regulares.

 import pandas as pd df = pd.read_clipboard(header=None).iloc[:, 1] df2 = df.str.extract(r'([AZ])\.?([0-9]?)\.?([az]?)') print df2.set_index([0,1]) 

y el resultado es,

  2 0 1 MELM 1 2 3 E 1 2 3 4 L 1 2 M 1 a 1 b 1 c 2 a 3 a E 1 a 1 b 1 c 2 a 3 a 3 b 4 a