Come aggiungere righe per un dataframe timeseries?

Dec 31 2020

Sto scrivendo un programma che caricherà in un file excel timeseries in un dataframe, quindi creo diverse nuove colonne utilizzando alcuni calcoli di base. Il mio programma a volte leggerà file excel che mancano mesi per alcuni record. Quindi, nell'esempio seguente, ho i dati sulle vendite mensili per due diversi negozi. I negozi aprono in mesi diversi, quindi la data di fine del primo mese sarà diversa. Ma entrambi dovrebbero avere dati di fine mese fino al 30/9/2020. Nel mio file, Store BBB non ha record per il 31/8/2020 e il 30/9/2020 perché non ci sono state vendite durante quei mesi.

Negozio Mese aperto Stato Città Data di fine mese I saldi
AAA 31/5/2020 NY New York 31/5/2020 1000
AAA 31/5/2020 NY New York 30/6/2020 5000
AAA 31/5/2020 NY New York 30/7/2020 3000
AAA 31/5/2020 NY New York 31/8/2020 4000
AAA 31/5/2020 NY New York 30/9/2020 2000
BBB 30/6/2020 CT Hartford 30/6/2020 100
BBB 30/6/2020 CT Hartford 30/7/2020 200

Quindi, per qualsiasi istanza come questa, voglio essere in grado di aggiungere due righe per Store BBB per 8/31 e 9/30. Le nuove righe devono utilizzare lo stesso mese di apertura, stato e città dalla data di fine mese più recente. Le vendite dovrebbero essere impostate su 0 per entrambe le nuove righe. A partire da ora, eseguo i seguenti passaggi:

  1. Crea il dataframe "MaxDateData" con il nome del negozio e la data di fine mese massima per ogni negozio e anche la data di fine mese massima per l'intero frame di dati della serie temporale, io chiamo questo campo "Data più recente".
Negozio Data di fine mese max Data più recente
AAA 30/9/2020 30/9/2020
BBB 30/7/2020 30/9/2020
  1. Crea il dataframe "MostRecent" con la riga più recente dal dataframe della serie temporale principale. Per fare ciò, eseguo un join interno tra il dataframe della serie temporale e MaxDateData su Nome negozio e Data di fine mese max.
Negozio Mese aperto Stato Città Data di fine mese I saldi Data di fine mese max Data più recente
AAA 31/5/2020 NY New York 30/9/2020 2000 30/9/2020 30/9/2020
BBB 30/6/2020 CT Hartford 30/7/2020 200 30/7/2020 30/9/2020
  1. Creare un dataframe "RequireBackfill_MostRecent" utilizzando una clausola where per filtrare i negozi in cui Max Month End Date <Most Recent Date. Vedere il codice di seguito. Quindi, in questo esempio, la tabella RequireBackfill_MostRecent avrà solo una riga per l'archivio BBB.
RequireBackfill_Stores_MostRecent = MaxDateData.where(MaxDateData['Max Month End Date'] <MaxDateData['Most Recent Date'])
RequireBackfill_MostRecent = MostRecent.merge(RequireBackfill_Stores_MostRecent,how='inner')
  1. Quindi uso due cicli for nidificati per scorrere le date che devo compilare. Sfrutta il dataframe RequireBackfill_MostRecent che conterrebbe solo Store BBB.
X=[]
end = MaxDateData['Most Recent Date'][0]
for i in MonthlyData['Month End Date'].unique():
    per1 = pd.date_range(start = i,  end = end, freq ='M') 
    for val in per1: 
        Data=[]
        Data = RequireBackfill_MostRecent[["Store"
                                           ,"Month Opened"
                                           ,"City"
                                           ,"State"
                                           ]].where(RequireBackfill_MostRecent['Max Month End date']==i).dropna()   

        Data["Month End Date"]= val                
        Data["Sales"]= 0
        X.append(Data)
NewData = pd.concat(X) 
  1. Quindi aggiungo NewData al mio dataframe timeseries usando concat
FullData_List = [MonthlyData,NewData]
FullData=pd.concat(FullData_List)

L'intero processo funziona, ma esiste un modo molto più efficiente per farlo? Questo potrebbe diventare costoso quando comincio a lavorare con dati più grandi.

Risposte

Ferris Dec 31 2020 at 14:08
  1. prova solo upsamplecon l'indice DateTime. ref: pandas-resample-upsample-last-date-edge-of-data
# group by `Store`
# with `Month End Date` column show be converted to DateTime

group.set_index(['Month End Date']).resample('M').asfreq()
  1. si noti che: 7/30/2020non è il giorno di fine luglio. 7/31/2020è. quindi l'utilizzo di questo metodo 7/30/2020sarà un problema (converti la data di fine del mese come data di fine effettiva).
JoeFerndz Dec 31 2020 at 16:14

Ecco l'approccio passo passo per farlo. Se hai domande, fammi sapere.

import pandas as pd
pd.set_option('display.max_columns', None)
c = ['Store','Month Opened','State','City','Month End Date','Sales']
d = [['AAA','5/31/2020','NY','New York','5/31/2020',1000],
['AAA','5/31/2020','NY','New York','6/30/2020',5000],
['AAA','5/31/2020','NY','New York','7/30/2020',3000],
['AAA','5/31/2020','NY','New York','8/31/2020',4000],
['AAA','5/31/2020','NY','New York','9/30/2020',2000],
['BBB','6/30/2020','CT','Hartford','6/30/2020',100],
['BBB','6/30/2020','CT','Hartford','7/30/2020',200],
['CCC','3/31/2020','NJ','Cranbury','3/31/2020',1500]]

df = pd.DataFrame(d,columns = c)
df['Month Opened'] = pd.to_datetime(df['Month Opened'])
df['Month End Date'] = pd.to_datetime(df['Month End Date'])

#select last entry for each Store
df1 = df.sort_values('Month End Date').drop_duplicates('Store', keep='last').copy()

#delete all rows that have 2020-09-30. We want only ones that are less than 2020-09-30
df1 = df1[df1['Month End Date'] != '2020-09-30']

#set target end date to 2020-09-30
df1['Target_End_Date'] = pd.to_datetime ('2020-09-30')

#calculate how many rows to repeat
df1['repeats'] = df1['Target_End_Date'].dt.to_period('M').astype(int) - df1['Month End Date'].dt.to_period('M').astype(int)

#add 1 month to month end so we can start repeating from here
df1['Month End Date'] = df1['Month End Date'] + pd.DateOffset(months =1)

#set sales value as 0 per requirement
df1['Sales'] = 0

#repeat each row by the value in column repeats
df1 = df1.loc[df1.index.repeat(df1.repeats)].reset_index(drop=True)

#reset repeats to start from 0 thru n using groupby cumcouunt
#this will be used to calculate months to increment from month end date
df1['repeats'] = df1.groupby('Store').cumcount()

#update month end date based on value in repeats
df1['Month End Date'] = df1.apply(lambda x: x['Month End Date'] + pd.DateOffset(months = x['repeats']), axis=1)

#set end date to last day of the month
df1['Month End Date'] = pd.to_datetime(df1['Month End Date']) + pd.offsets.MonthEnd(0)

#drop columns that we don't need anymore. required before we concat dfs
df1.drop(columns=['Target_End_Date','repeats'],inplace=True)

#concat df and df1 to get the final dataframe
df = pd.concat([df, df1], ignore_index=True)

#sort values by Store and Month End Date
df = df.sort_values(by=['Store','Month End Date'],ignore_index=True)

print (df)

L'output di questo è:

   Store Month Opened State      City Month End Date  Sales
0    AAA   2020-05-31    NY  New York     2020-05-31   1000
1    AAA   2020-05-31    NY  New York     2020-06-30   5000
2    AAA   2020-05-31    NY  New York     2020-07-30   3000
3    AAA   2020-05-31    NY  New York     2020-08-31   4000
4    AAA   2020-05-31    NY  New York     2020-09-30   2000
5    BBB   2020-06-30    CT  Hartford     2020-06-30    100
6    BBB   2020-06-30    CT  Hartford     2020-07-30    200
7    BBB   2020-06-30    CT  Hartford     2020-08-30      0
8    BBB   2020-06-30    CT  Hartford     2020-09-30      0
9    CCC   2020-03-31    NJ  Cranbury     2020-03-31   1500
10   CCC   2020-03-31    NJ  Cranbury     2020-04-30      0
11   CCC   2020-03-31    NJ  Cranbury     2020-05-31      0
12   CCC   2020-03-31    NJ  Cranbury     2020-06-30      0
13   CCC   2020-03-31    NJ  Cranbury     2020-07-31      0
14   CCC   2020-03-31    NJ  Cranbury     2020-08-31      0
15   CCC   2020-03-31    NJ  Cranbury     2020-09-30      0

Nota ho aggiunto un'altra voce con CCC per mostrarti più variazioni.