2014-05-07 27 views
18

Sto cercando di calcolare le aggregazioni basate sul tempo in panda in base ai valori di data memorizzati in tabelle separate.Come fare un join condizionale in Python Pandas?

La parte superiore della prima table_a tavolo assomiglia a questo:

COMPANY_ID DATE   MEASURE 
    1 2010-01-01 00:00:00  10 
    1 2010-01-02 00:00:00  10 
    1 2010-01-03 00:00:00  10 
    1 2010-01-04 00:00:00  10 
    1 2010-01-05 00:00:00  10 

Ecco il codice per creare la tabella:

table_a = pd.concat(\ 
    [pd.DataFrame({'DATE': pd.date_range("01/01/2010", "12/31/2010", freq="D"),\ 
    'COMPANY_ID': 1 , 'MEASURE': 10}),\ 
    pd.DataFrame({'DATE': pd.date_range("01/01/2010", "12/31/2010", freq="D"),\ 
    'COMPANY_ID': 2 , 'MEASURE': 10})]) 

La seconda tabella, table_b assomiglia a questo:

 COMPANY  END_DATE 
     1 2010-03-01 00:00:00 
     1 2010-06-02 00:00:00 
     2 2010-03-01 00:00:00 
     2 2010-06-02 00:00:00 

e il codice per crearlo è:

table_b = pd.DataFrame({'END_DATE':pd.to_datetime(['03/01/2010','06/02/2010','03/01/2010','06/02/2010']),\ 
        'COMPANY':(1,1,2,2)}) 

Desidero essere in grado di ottenere la somma della colonna di misure per ogni COMPANY_ID per ogni periodo di 30 giorni prima del giorno END_DATE in table_b.

Questo è (credo) lo SQL equivalente:

 select 
b.COMPANY_ID, 
b.DATE 
sum(a.MEASURE) AS MEASURE_TO_END_DATE 
from table_a a, table_b b 
where a.COMPANY = b.COMPANY and 
     a.DATE < b.DATE and 
     a.DATE > b.DATE - 30 
group by b.COMPANY; 

Grazie per qualsiasi aiuto

+0

"end_date" in table_b ogni ha finestre sovrapposte; ad esempio, la società 1 potrebbe avere un end_date del 2010-03-01 e 2010-03-15. –

+0

Ciao @KarlD sì potenzialmente. – JAB

risposta

25

Beh, mi viene in mente un paio di modi. (1) esplodere essenzialmente il dataframe fondendosi su company e quindi filtrare sulle finestre di 30 giorni dopo l'unione. Questo dovrebbe essere veloce ma potrebbe usare molta memoria. (2) Spostare la fusione e il filtraggio sulla finestra di 30 giorni in un groupby. Ciò si traduce in una fusione per ogni gruppo in modo che sarebbe stato più lento ma dovrebbe utilizzare meno memoria

Opzione # 1

Supponiamo che i dati è simile al seguente (ho ampliato i tuoi dati di esempio):

print df 

    company  date measure 
0   0 2010-01-01  10 
1   0 2010-01-15  10 
2   0 2010-02-01  10 
3   0 2010-02-15  10 
4   0 2010-03-01  10 
5   0 2010-03-15  10 
6   0 2010-04-01  10 
7   1 2010-03-01  5 
8   1 2010-03-15  5 
9   1 2010-04-01  5 
10  1 2010-04-15  5 
11  1 2010-05-01  5 
12  1 2010-05-15  5 

print windows 

    company end_date 
0  0 2010-02-01 
1  0 2010-03-15 
2  1 2010-04-01 
3  1 2010-05-15 

Creare una data di inizio per le finestre di 30 giorni:

windows['beg_date'] = (windows['end_date'].values.astype('datetime64[D]') - 
         np.timedelta64(30,'D')) 
print windows 

    company end_date beg_date 
0  0 2010-02-01 2010-01-02 
1  0 2010-03-15 2010-02-13 
2  1 2010-04-01 2010-03-02 
3  1 2010-05-15 2010-04-15 

Ora fare una fusione e quindi selezionare o base n se date cadute all'interno beg_date e end_date:

df = df.merge(windows,on='company',how='left') 
df = df[(df.date >= df.beg_date) & (df.date <= df.end_date)] 
print df 

    company  date measure end_date beg_date 
2   0 2010-01-15  10 2010-02-01 2010-01-02 
4   0 2010-02-01  10 2010-02-01 2010-01-02 
7   0 2010-02-15  10 2010-03-15 2010-02-13 
9   0 2010-03-01  10 2010-03-15 2010-02-13 
11  0 2010-03-15  10 2010-03-15 2010-02-13 
16  1 2010-03-15  5 2010-04-01 2010-03-02 
18  1 2010-04-01  5 2010-04-01 2010-03-02 
21  1 2010-04-15  5 2010-05-15 2010-04-15 
23  1 2010-05-01  5 2010-05-15 2010-04-15 
25  1 2010-05-15  5 2010-05-15 2010-04-15 

È possibile calcolare le somme periodo di 30 giorni dal raggruppamento su company e end_date:

print df.groupby(['company','end_date']).sum() 

        measure 
company end_date   
0  2010-02-01  20 
     2010-03-15  30 
1  2010-04-01  10 
     2010-05-15  15 

Opzione # 2 spostare tutti si fonde in una GroupBy.Questo dovrebbe essere meglio sulla memoria, ma penso che sarebbe molto più lento:

windows['beg_date'] = (windows['end_date'].values.astype('datetime64[D]') - 
         np.timedelta64(30,'D')) 

def cond_merge(g,windows): 
    g = g.merge(windows,on='company',how='left') 
    g = g[(g.date >= g.beg_date) & (g.date <= g.end_date)] 
    return g.groupby('end_date')['measure'].sum() 

print df.groupby('company').apply(cond_merge,windows) 

company end_date 
0  2010-02-01 20 
     2010-03-15 30 
1  2010-04-01 10 
     2010-05-15 15 

Un'altra opzione Ora, se le finestre non si sovrappongono (come nei dati di esempio), si potrebbe fare qualcosa di simile a quanto segue come un'alternativa che doesn 't saltare in aria un dataframe ma è abbastanza veloce:

windows['date'] = windows['end_date'] 

df = df.merge(windows,on=['company','date'],how='outer') 
print df 

    company  date measure end_date 
0   0 2010-01-01  10  NaT 
1   0 2010-01-15  10  NaT 
2   0 2010-02-01  10 2010-02-01 
3   0 2010-02-15  10  NaT 
4   0 2010-03-01  10  NaT 
5   0 2010-03-15  10 2010-03-15 
6   0 2010-04-01  10  NaT 
7   1 2010-03-01  5  NaT 
8   1 2010-03-15  5  NaT 
9   1 2010-04-01  5 2010-04-01 
10  1 2010-04-15  5  NaT 
11  1 2010-05-01  5  NaT 
12  1 2010-05-15  5 2010-05-15 

Questa fusione inserisce in sostanza, le date di fine finestra nel dataframe, quindi ricoprire le date di fine (per gruppo) vi darà una struttura per creare facilmente sommatoria finestre :

df['end_date'] = df.groupby('company')['end_date'].apply(lambda x: x.bfill()) 

print df 

    company  date measure end_date 
0   0 2010-01-01  10 2010-02-01 
1   0 2010-01-15  10 2010-02-01 
2   0 2010-02-01  10 2010-02-01 
3   0 2010-02-15  10 2010-03-15 
4   0 2010-03-01  10 2010-03-15 
5   0 2010-03-15  10 2010-03-15 
6   0 2010-04-01  10  NaT 
7   1 2010-03-01  5 2010-04-01 
8   1 2010-03-15  5 2010-04-01 
9   1 2010-04-01  5 2010-04-01 
10  1 2010-04-15  5 2010-05-15 
11  1 2010-05-01  5 2010-05-15 
12  1 2010-05-15  5 2010-05-15 

df = df[df.end_date.notnull()] 
df['beg_date'] = (df['end_date'].values.astype('datetime64[D]') - 
        np.timedelta64(30,'D')) 

print df 

    company  date measure end_date beg_date 
0   0 2010-01-01  10 2010-02-01 2010-01-02 
1   0 2010-01-15  10 2010-02-01 2010-01-02 
2   0 2010-02-01  10 2010-02-01 2010-01-02 
3   0 2010-02-15  10 2010-03-15 2010-02-13 
4   0 2010-03-01  10 2010-03-15 2010-02-13 
5   0 2010-03-15  10 2010-03-15 2010-02-13 
7   1 2010-03-01  5 2010-04-01 2010-03-02 
8   1 2010-03-15  5 2010-04-01 2010-03-02 
9   1 2010-04-01  5 2010-04-01 2010-03-02 
10  1 2010-04-15  5 2010-05-15 2010-04-15 
11  1 2010-05-01  5 2010-05-15 2010-04-15 
12  1 2010-05-15  5 2010-05-15 2010-04-15 

df = df[(df.date >= df.beg_date) & (df.date <= df.end_date)] 
print df.groupby(['company','end_date']).sum() 

        measure 
company end_date   
0  2010-02-01  20 
     2010-03-15  30 
1  2010-04-01  10 
     2010-05-15  15 

Un'altra alternativa è ricampionare il primo dataframe ai dati giornalieri e quindi calcolare rolling_sums con una finestra di 30 giorni; e seleziona le date alla fine a cui sei interessato. Anche questo potrebbe richiedere un notevole dispendio di memoria.

+0

Grazie a @Karl D questa è stata un'ottima risposta. – JAB

+0

+1 per mostrare due strategie e i loro punti di forza/debolezza. – ojdo