Agregar fechas faltantes al marco de datos de pandas

127

Mis datos pueden tener múltiples eventos en una fecha determinada o NO eventos en una fecha. Tomo estos eventos, obtengo un recuento por fecha y los trazo. Sin embargo, cuando los trazo, mis dos series no siempre coinciden.

idx = pd.date_range(df['simpleDate'].min(), df['simpleDate'].max())
s = df.groupby(['simpleDate']).size()

En el código anterior, idx se convierte en un rango de digamos 30 fechas. 01/09/2013 a 30/09/2013 Sin embargo, S solo puede tener 25 o 26 días porque no sucedieron eventos en una fecha determinada. Luego obtengo un AssertionError ya que los tamaños no coinciden cuando intento trazar:

fig, ax = plt.subplots()    
ax.bar(idx.to_pydatetime(), s, color='green')

¿Cuál es la forma correcta de abordar esto? ¿Quiero eliminar fechas sin valores de IDX o (lo que prefiero hacer) es agregar a la serie la fecha que falta con un recuento de 0. Prefiero tener un gráfico completo de 30 días con 0 valores. Si este enfoque es correcto, ¿alguna sugerencia sobre cómo comenzar? ¿Necesito algún tipo de dinámicareindex función ?

Aquí hay un fragmento de S ( df.groupby(['simpleDate']).size() ), no hay entradas para 04 y 05.

09-02-2013     2
09-03-2013    10
09-06-2013     5
09-07-2013     1
KHibma
fuente

Respuestas:

254

Podrías usar Series.reindex:

import pandas as pd

idx = pd.date_range('09-01-2013', '09-30-2013')

s = pd.Series({'09-02-2013': 2,
               '09-03-2013': 10,
               '09-06-2013': 5,
               '09-07-2013': 1})
s.index = pd.DatetimeIndex(s.index)

s = s.reindex(idx, fill_value=0)
print(s)

rendimientos

2013-09-01     0
2013-09-02     2
2013-09-03    10
2013-09-04     0
2013-09-05     0
2013-09-06     5
2013-09-07     1
2013-09-08     0
...
unutbu
fuente
23
reindexEs una función asombrosa. Puede (1) reordenar los datos existentes para que coincidan con un nuevo conjunto de etiquetas, (2) insertar nuevas filas donde no existía ninguna etiqueta previamente, (3) completar los datos de las etiquetas que faltan, (incluyendo el llenado hacia adelante / hacia atrás) (4) seleccionar filas por etiqueta!
unutbu
@unutbu Esto responde parte de una pregunta que tuve también, ¡gracias! ¿Pero me preguntaba si sabía cómo crear dinámicamente una lista con las fechas que tienen eventos?
Nick Duddy
2
Sin embargo, hay un problema (o error) con reindex: no funciona con fechas anteriores al 1/1/1970, por lo que en este caso df.resample () funciona perfectamente.
Sergey Gulbin
1
en su lugar, puede usar esto para que idx omita ingresar las fechas de inicio y finalización manualmente:idx = pd.date_range(df.index.min(), df.index.max())
Reveille
Colocando el enlace a la documentación aquí, para guardar la búsqueda: pandas.pydata.org/pandas-docs/stable/reference/api/…
Harm te Molder
40

Una solución más rápida es usar .asfreq(). Esto no requiere la creación de un nuevo índice para llamar dentro .reindex().

# "broken" (staggered) dates
dates = pd.Index([pd.Timestamp('2012-05-01'), 
                  pd.Timestamp('2012-05-04'), 
                  pd.Timestamp('2012-05-06')])
s = pd.Series([1, 2, 3], dates)

print(s.asfreq('D'))
2012-05-01    1.0
2012-05-02    NaN
2012-05-03    NaN
2012-05-04    2.0
2012-05-05    NaN
2012-05-06    3.0
Freq: D, dtype: float64
Brad Solomon
fuente
1
Realmente prefiero este método; evita tener que llamar date_rangeya que utiliza implícitamente el primer y último índice como inicio y fin (que es lo que casi siempre querría).
Michael Hays
Método muy limpio y profesional. Funciona bien con el uso de interpolar después también.
msarafzadeh
26

Un problema es que reindexfallará si hay valores duplicados. Digamos que estamos trabajando con datos con fecha y hora, que queremos indexar por fecha:

df = pd.DataFrame({
    'timestamps': pd.to_datetime(
        ['2016-11-15 1:00','2016-11-16 2:00','2016-11-16 3:00','2016-11-18 4:00']),
    'values':['a','b','c','d']})
df.index = pd.DatetimeIndex(df['timestamps']).floor('D')
df

rendimientos

            timestamps             values
2016-11-15  "2016-11-15 01:00:00"  a
2016-11-16  "2016-11-16 02:00:00"  b
2016-11-16  "2016-11-16 03:00:00"  c
2016-11-18  "2016-11-18 04:00:00"  d

Debido a la 2016-11-16fecha duplicada , un intento de reindexar:

all_days = pd.date_range(df.index.min(), df.index.max(), freq='D')
df.reindex(all_days)

falla con:

...
ValueError: cannot reindex from a duplicate axis

(con esto significa que el índice tiene duplicados, no es que sea un duplicado)

En cambio, podemos usar .locpara buscar entradas para todas las fechas en el rango:

df.loc[all_days]

rendimientos

            timestamps             values
2016-11-15  "2016-11-15 01:00:00"  a
2016-11-16  "2016-11-16 02:00:00"  b
2016-11-16  "2016-11-16 03:00:00"  c
2016-11-17  NaN                    NaN
2016-11-18  "2016-11-18 04:00:00"  d

fillna se puede usar en la serie de columnas para llenar espacios en blanco si es necesario.

Nick Edgar
fuente
¿Alguna idea de qué hacer si la columna Fecha contiene Blankso NULLS? df.loc[all_days]no funcionará en ese caso.
Furqan Hashim
1
Pasar los me gusta de la lista a .loc o [] con cualquier etiqueta faltante aumentará KeyError en el futuro, puede usar .reindex () como alternativa. Vea la documentación aquí: pandas.pydata.org/pandas-docs/stable/…
Dmitrii Magas
19

Un enfoque alternativo es resample, que puede manejar fechas duplicadas además de las fechas faltantes. Por ejemplo:

df.resample('D').mean()

resamplees una operación diferida, groupbypor lo que debe seguirla con otra operación. En este caso meanfunciona bien, pero también puedes usar muchos otros métodos de pandas como max,sum , etc.

Aquí están los datos originales, pero con una entrada adicional para '2013-09-03':

             val
date           
2013-09-02     2
2013-09-03    10
2013-09-03    20    <- duplicate date added to OP's data
2013-09-06     5
2013-09-07     1

Y aquí están los resultados:

             val
date            
2013-09-02   2.0
2013-09-03  15.0    <- mean of original values for 2013-09-03
2013-09-04   NaN    <- NaN b/c date not present in orig
2013-09-05   NaN    <- NaN b/c date not present in orig
2013-09-06   5.0
2013-09-07   1.0

Dejé las fechas faltantes como NaNs para dejar en claro cómo funciona esto, pero puede agregar fillna(0)para reemplazar NaNs con ceros según lo solicitado por el OP o alternativamente usar algo como interpolate()llenar con valores distintos de cero basados ​​en las filas vecinas.

JohnE
fuente
6

Aquí hay un buen método para completar las fechas que faltan en un marco de datos, con su elección fill_value, days_backcompletar y ordenar ( date_order) por el cual ordenar el marco de datos:

def fill_in_missing_dates(df, date_col_name = 'date',date_order = 'asc', fill_value = 0, days_back = 30):

    df.set_index(date_col_name,drop=True,inplace=True)
    df.index = pd.DatetimeIndex(df.index)
    d = datetime.now().date()
    d2 = d - timedelta(days = days_back)
    idx = pd.date_range(d2, d, freq = "D")
    df = df.reindex(idx,fill_value=fill_value)
    df[date_col_name] = pd.DatetimeIndex(df.index)

    return df
eiTan LaVi
fuente