Insertar valores 0 para fechas faltantes dentro de MultiIndex

Supongamos que tengo un MultiIndex que consta de la fecha y algunas categorías (una para simplificar en el siguiente ejemplo) y para cada categoría tengo una serie de tiempo con valores de algún proceso. Solo tengo un valor cuando hubo una observación y ahora quiero agregar un “0” cuando no haya ninguna observación en esa fecha. Encontré una forma que parece muy ineficiente (astackr y desastackr que creará muchas columnas en el caso de millones de categorías).

import datetime as dt import pandas as pd days= 4 #List of all dates that should be in the index all_dates = [datetime.date(2013, 2, 13) - dt.timedelta(days=x) for x in range(days)] df = pd.DataFrame([ (datetime.date(2013, 2, 10), 1, 4), (datetime.date(2013, 2, 10), 2, 7), (datetime.date(2013, 2, 11), 2, 7), (datetime.date(2013, 2, 13), 1, 2), (datetime.date(2013, 2, 13), 2, 3)], columns = ['date', 'category', 'value']) df.set_index(['date', 'category'], inplace=True) print df print df.unstack().reindex(all_dates).fillna(0).stack() # insert 0 values for missing dates print all_dates value date category 2013-02-10 1 4 2 7 2013-02-11 2 7 2013-02-13 1 2 2 3 value category 2013-02-13 1 2 2 3 2013-02-12 1 0 2 0 2013-02-11 1 0 2 7 2013-02-10 1 4 2 7 [datetime.date(2013, 2, 13), datetime.date(2013, 2, 12), datetime.date(2013, 2, 11), datetime.date(2013, 2, 10)] 

¿Alguien sabe una manera más inteligente de lograr lo mismo?

EDITAR: Encontré otra posibilidad para lograr lo mismo:

 import datetime as dt import pandas as pd days= 4 #List of all dates that should be in the index all_dates = [datetime.date(2013, 2, 13) - dt.timedelta(days=x) for x in range(days)] df = pd.DataFrame([(datetime.date(2013, 2, 10), 1, 4, 5), (datetime.date(2013, 2, 10), 2,1, 7), (datetime.date(2013, 2, 10), 2,2, 7), (datetime.date(2013, 2, 11), 2,3, 7), (datetime.date(2013, 2, 13), 1,4, 2), (datetime.date(2013, 2, 13), 2,4, 3)], columns = ['date', 'category', 'cat2', 'value']) date_col = 'date' other_index = ['category', 'cat2'] index = [date_col] + other_index df.set_index(index, inplace=True) grouped = df.groupby(level=other_index) df_list = [] for i, group in grouped: df_list.append(group.reset_index(level=other_index).reindex(all_dates).fillna(0)) print pd.concat(df_list).set_index(other_index, append=True) value category cat2 2013-02-13 1 4 2 2013-02-12 0 0 0 2013-02-11 0 0 0 2013-02-10 1 4 5 2013-02-13 0 0 0 2013-02-12 0 0 0 2013-02-11 0 0 0 2013-02-10 2 1 7 2013-02-13 0 0 0 2013-02-12 0 0 0 2013-02-11 0 0 0 2013-02-10 2 2 7 2013-02-13 0 0 0 2013-02-12 0 0 0 2013-02-11 2 3 7 2013-02-10 0 0 0 2013-02-13 2 4 3 2013-02-12 0 0 0 2013-02-11 0 0 0 2013-02-10 0 0 0 

Puede crear un nuevo índice múltiple basado en el producto cartesiano de los niveles de índice que desee. Luego, vuelva a indexar su dataframe utilizando el nuevo índice.

 (date_index, category_index) = df.index.levels new_index = pd.MultiIndex.from_product([all_dates, category_index]) 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 dataframe 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 datetime as dt import pandas as pd days= 4 #List of all dates that should be in the index all_dates = [dt.date(2013, 2, 13) - dt.timedelta(days=x) for x in range(days)] df = pd.DataFrame([ (dt.date(2013, 2, 10), 1, 4), (dt.date(2013, 2, 10), 2, 7), (dt.date(2013, 2, 11), 2, 7), (dt.date(2013, 2, 13), 1, 2), (dt.date(2013, 2, 13), 2, 3)], columns = ['date', 'category', 'value']) df.set_index(['date', 'category'], inplace=True) 

Así es como se ven los datos de muestra

  value date category 2013-02-10 1 4 2 7 2013-02-11 2 7 2013-02-13 1 2 2 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 que se pasan a la función.

 (date_index, category_index) = df.index.levels new_index = pd.MultiIndex.from_product([all_dates, category_index]) 

Reindexar

Utilice el nuevo índice para reindexar el dataframe existente.

Todas las combinaciones posibles están ahora presentes. Los valores que faltan son nulos (NaN).

 new_df = df.reindex(new_index) 

Ahora, el dataframe expandido y re-indexado se ve así:

  value 2013-02-13 1 2.0 2 3.0 2013-02-12 1 NaN 2 NaN 2013-02-11 1 NaN 2 7.0 2013-02-10 1 4.0 2 7.0 

Nulos en columna entera

Puede ver que los datos en el nuevo dataframe 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 2013-02-13 1 2 2 3 2013-02-12 1 0 2 0 2013-02-11 1 0 2 7 2013-02-10 1 4 2 7 

Verifique esta respuesta: ¿Cómo llenar el registro faltante del dataframe de Pandas de forma pirónica?

Puedes hacer algo como:

 import datetime import pandas as pd #make an empty dataframe with the index you want def get_datetime(x): return datetime.date(2013, 2, 13)- datetime.timedelta(days=x) all_dates = [ get_datetime(x) for x in range(4)] categories = [1,2,3,4] index = [ [date, cat] for cat in categories for date in all_dates ] #this df will be just an index df = pd.DataFrame(index) df =print df.set_index([0,1]) df.columns = ['date', 'category'] df = df.set_index(['date', 'category']) #now if your original df is called df_original you can reindex against the other values df_orig = df_orig.reindex_axis(df.index) #and to add zeros df_orig.fillna(0)