python - Rellenando huecos de fecha en el marco de datos de MultiIndex Pandas
pandas xs (2)
Me gustaría modificar un marco de datos MultiIndex de pandas para que cada grupo de índices incluya fechas entre un rango específico. Me gustaría que cada grupo complete las fechas que faltan del 2013-06-11 al 2013-12-31 con el valor 0 (o NaN
).
Group A, Group B, Date, Value
loc_a group_a 2013-06-11 22
2013-07-02 35
2013-07-09 14
2013-07-30 9
2013-08-06 4
2013-09-03 40
2013-10-01 18
group_b 2013-07-09 4
2013-08-06 2
2013-09-03 5
group_c 2013-07-09 1
2013-09-03 2
loc_b group_a 2013-10-01 3
He visto algunas discusiones sobre reindex
, pero eso es para datos de series de tiempo simples (no agrupados).
¿Hay una forma fácil de hacer esto?
A continuación hay algunos intentos que he hecho para lograr esto. Por ejemplo: una vez que haya desapilado por [''A'', ''B'']
, puedo reindexar.
df = pd.DataFrame({''A'': [''loc_a''] * 12 + [''loc_b''],
''B'': [''group_a''] * 7 + [''group_b''] * 3 + [''group_c''] * 2 + [''group_a''],
''Date'': ["2013-06-11",
"2013-07-02",
"2013-07-09",
"2013-07-30",
"2013-08-06",
"2013-09-03",
"2013-10-01",
"2013-07-09",
"2013-08-06",
"2013-09-03",
"2013-07-09",
"2013-09-03",
"2013-10-01"],
''Value'': [22, 35, 14, 9, 4, 40, 18, 4, 2, 5, 1, 2, 3]})
df.Date = df[''Date''].apply(lambda x: pd.to_datetime(x).date())
df = df.set_index([''A'', ''B'', ''Date''])
dt_start = dt.datetime(2013,6,1)
all_dates = [(dt_start + dt.timedelta(days=x)).date() for x in range(0,60)]
df2 = df.unstack([''A'', ''B''])
df3 = df2.reindex(index=all_dates).fillna(0)
df4 = df3.stack([''A'', ''B''])
## df4 is about where I want to get, now I''m trying to get it back in the form of df...
df5 = df4.reset_index()
df6 = df5.rename(columns={''level_0'' : ''Date''})
df7 = df6.groupby([''A'', ''B'', ''Date''])[''Value''].sum()
Las últimas líneas me ponen un poco triste. Esperaba que en df6
pudiera simplemente volver a establecer el set_index
en [''A'', ''B'', ''Date'']
, pero eso no agrupaba los valores, ya que están agrupados en el DataFrame inicial de df
.
¿Alguna idea sobre cómo puedo reindexar el DataFrame no apilado, volver a empacar y tener el DataFrame en el mismo formato que el original?
Puede crear un nuevo índice múltiple basado en el producto cartesiano de los niveles del índice múltiple existente. Luego, vuelva a indexar su marco de datos utilizando el nuevo índice.
new_index = pd.MultiIndex.from_product(df.index.levels)
new_df = df.reindex(new_index)
# Optional: convert missing values to zero, and convert the data back
# to integers. See explanation below.
new_df = new_df.fillna(0).astype(int)
¡Eso es! El nuevo marco de datos tiene todos los valores de índice posibles. Los datos existentes están indexados correctamente.
Siga leyendo para una explicación más detallada.
Explicación
Configurar datos de muestra
import pandas as pd
df = pd.DataFrame({''A'': [''loc_a''] * 12 + [''loc_b''],
''B'': [''group_a''] * 7 + [''group_b''] * 3 + [''group_c''] * 2 + [''group_a''],
''Date'': ["2013-06-11",
"2013-07-02",
"2013-07-09",
"2013-07-30",
"2013-08-06",
"2013-09-03",
"2013-10-01",
"2013-07-09",
"2013-08-06",
"2013-09-03",
"2013-07-09",
"2013-09-03",
"2013-10-01"],
''Value'': [22, 35, 14, 9, 4, 40, 18, 4, 2, 5, 1, 2, 3]})
df.Date = pd.to_datetime(df.Date)
df = df.set_index([''A'', ''B'', ''Date''])
Así es como se ven los datos de muestra
Value
A B Date
loc_a group_a 2013-06-11 22
2013-07-02 35
2013-07-09 14
2013-07-30 9
2013-08-06 4
2013-09-03 40
2013-10-01 18
group_b 2013-07-09 4
2013-08-06 2
2013-09-03 5
group_c 2013-07-09 1
2013-09-03 2
loc_b group_a 2013-10-01 3
Hacer nuevo índice
Usando from_product podemos hacer un nuevo índice múltiple. Este nuevo índice es el producto cartesiano de todos los valores de todos los niveles del índice anterior.
new_index = pd.MultiIndex.from_product(df.index.levels)
Reindexar
Utilice el nuevo índice para reindexar el marco de datos existente.
new_df = df.reindex(new_index)
Todas las combinaciones posibles están ahora presentes. Los valores que faltan son nulos (NaN).
El marco de datos expandido y re-indexado se ve así:
Value
loc_a group_a 2013-06-11 22.0
2013-07-02 35.0
2013-07-09 14.0
2013-07-30 9.0
2013-08-06 4.0
2013-09-03 40.0
2013-10-01 18.0
group_b 2013-06-11 NaN
2013-07-02 NaN
2013-07-09 4.0
2013-07-30 NaN
2013-08-06 2.0
2013-09-03 5.0
2013-10-01 NaN
group_c 2013-06-11 NaN
2013-07-02 NaN
2013-07-09 1.0
2013-07-30 NaN
2013-08-06 NaN
2013-09-03 2.0
2013-10-01 NaN
loc_b group_a 2013-06-11 NaN
2013-07-02 NaN
2013-07-09 NaN
2013-07-30 NaN
2013-08-06 NaN
2013-09-03 NaN
2013-10-01 3.0
group_b 2013-06-11 NaN
2013-07-02 NaN
2013-07-09 NaN
2013-07-30 NaN
2013-08-06 NaN
2013-09-03 NaN
2013-10-01 NaN
group_c 2013-06-11 NaN
2013-07-02 NaN
2013-07-09 NaN
2013-07-30 NaN
2013-08-06 NaN
2013-09-03 NaN
2013-10-01 NaN
Nulos en columna entera
Puede ver que los datos en el nuevo marco de datos se han convertido de ints a flotantes. Las pandas no pueden tener nulos en una columna entera . Opcionalmente, podemos convertir todos los nulos a 0 y convertir los datos a números enteros.
new_df = new_df.fillna(0).astype(int)
Resultado
Value
loc_a group_a 2013-06-11 22
2013-07-02 35
2013-07-09 14
2013-07-30 9
2013-08-06 4
2013-09-03 40
2013-10-01 18
group_b 2013-06-11 0
2013-07-02 0
2013-07-09 4
2013-07-30 0
2013-08-06 2
2013-09-03 5
2013-10-01 0
group_c 2013-06-11 0
2013-07-02 0
2013-07-09 1
2013-07-30 0
2013-08-06 0
2013-09-03 2
2013-10-01 0
loc_b group_a 2013-06-11 0
2013-07-02 0
2013-07-09 0
2013-07-30 0
2013-08-06 0
2013-09-03 0
2013-10-01 3
group_b 2013-06-11 0
2013-07-02 0
2013-07-09 0
2013-07-30 0
2013-08-06 0
2013-09-03 0
2013-10-01 0
group_c 2013-06-11 0
2013-07-02 0
2013-07-09 0
2013-07-30 0
2013-08-06 0
2013-09-03 0
2013-10-01 0
Su pregunta no fue clara exactamente en qué fechas faltaba; Solo asumo que desea completar NaN
para cualquier fecha para la cual tenga una observación en otro lugar. Mi solución tendrá que ser enmendada si esta suposición es defectuosa.
Nota al DataFrame
: puede ser bueno incluir una línea para crear el DataFrame
In [55]: df = pd.DataFrame({''A'': [''loc_a''] * 12 + [''loc_b''],
....: ''B'': [''group_a''] * 7 + [''group_b''] * 3 + [''group_c''] * 2 + [''group_a''],
....: ''Date'': ["2013-06-11",
....: "2013-07-02",
....: "2013-07-09",
....: "2013-07-30",
....: "2013-08-06",
....: "2013-09-03",
....: "2013-10-01",
....: "2013-07-09",
....: "2013-08-06",
....: "2013-09-03",
....: "2013-07-09",
....: "2013-09-03",
....: "2013-10-01"],
....: ''Value'': [22, 35, 14, 9, 4, 40, 18, 4, 2, 5, 1, 2, 3]})
In [56]:
In [56]: df.Date = pd.to_datetime(df.Date)
In [57]: df = df.set_index([''A'', ''B'', ''Date''])
In [58]:
In [58]: print(df)
Value
A B Date
loc_a group_a 2013-06-11 22
2013-07-02 35
2013-07-09 14
2013-07-30 9
2013-08-06 4
2013-09-03 40
2013-10-01 18
group_b 2013-07-09 4
2013-08-06 2
2013-09-03 5
group_c 2013-07-09 1
2013-09-03 2
loc_b group_a 2013-10-01 3
Para completar los valores no observados, usaremos los métodos de unstack
y stack
. Desapilar creará los NaN
que nos interesan, y luego los apilaremos para trabajar.
In [71]: df.unstack([''A'', ''B''])
Out[71]:
Value
A loc_a loc_b
B group_a group_b group_c group_a
Date
2013-06-11 22 NaN NaN NaN
2013-07-02 35 NaN NaN NaN
2013-07-09 14 4 1 NaN
2013-07-30 9 NaN NaN NaN
2013-08-06 4 2 NaN NaN
2013-09-03 40 5 2 NaN
2013-10-01 18 NaN NaN 3
In [59]: df.unstack([''A'', ''B'']).fillna(0).stack([''A'', ''B''])
Out[59]:
Value
Date A B
2013-06-11 loc_a group_a 22
group_b 0
group_c 0
loc_b group_a 0
2013-07-02 loc_a group_a 35
group_b 0
group_c 0
loc_b group_a 0
2013-07-09 loc_a group_a 14
group_b 4
group_c 1
loc_b group_a 0
2013-07-30 loc_a group_a 9
group_b 0
group_c 0
loc_b group_a 0
2013-08-06 loc_a group_a 4
group_b 2
group_c 0
loc_b group_a 0
2013-09-03 loc_a group_a 40
group_b 5
group_c 2
loc_b group_a 0
2013-10-01 loc_a group_a 18
group_b 0
group_c 0
loc_b group_a 3
Reordenar los niveles de índice según sea necesario.
Tuve que deslizar esa fillna(0)
en el medio para que los NaN
no se cayeran. stack
tiene un argumento dropna
. Pensaría que establecer ese valor en falso mantendría todas las filas de NaN
alrededor. Un error tal vez?