2016-03-23 34 views
5

Ho dati di tempi irregolari che rappresentano un determinato tipo di transazione per gli utenti. Ogni riga di dati è timestamp e rappresenta una transazione in quel momento. A causa della natura irregolare dei dati, alcuni utenti potrebbero avere 100 righe in un giorno e altri utenti potrebbero avere 0 o 1 transazione in un giorno.dplyr: raggruppamento e riepilogo/mutamento dei dati con finestre temporali mobili

I dati potrebbero essere simile a questa:

data.frame(
    id = c(1, 1, 1, 1, 1, 2, 2, 3, 4), 
    date = c("2015-01-01", 
      "2015-01-01", 
      "2015-01-05", 
      "2015-01-25", 
      "2015-02-15", 
      "2015-05-05", 
      "2015-01-01", 
      "2015-08-01", 
      "2015-01-01"), 
    n_widgets = c(1,2,3,4,4,5,2,4,5) 
) 

    id  date n_widgets 
1 1 2015-01-01   1 
2 1 2015-01-01   2 
3 1 2015-01-05   3 
4 1 2015-01-25   4 
5 1 2015-02-15   4 
6 2 2015-05-05   5 
7 2 2015-01-01   2 
8 3 2015-08-01   4 
9 4 2015-01-01   5 

Spesso mi piacerebbe sapere alcune statistiche di laminazione sugli utenti. Ad esempio: per questo utente in un determinato giorno, quante transazioni si sono verificate nei 30 giorni precedenti, quanti widget sono stati venduti nei 30 giorni precedenti ecc.

Corrispondente all'esempio precedente, i dati dovrebbero essere:

id  date n_widgets n_trans_30 total_widgets_30 
1 1 2015-01-01   1   1    1 
2 1 2015-01-01   2   2    3 
3 1 2015-01-05   3   3    6 
4 1 2015-01-25   4   4    10 
5 1 2015-02-15   4   2    8 
6 2 2015-05-05   5   1    5 
7 2 2015-01-01   2   1    2 
8 3 2015-08-01   4   1    4 
9 4 2015-01-01   5   1    5 

Se la finestra di tempo è tutti i giorni, allora la soluzione è semplice: data %>% group_by(id, date) %>% summarize(...)

Allo stesso modo, se la finestra temporale è mensile questo è anche relativamente semplice con lubridate: data %>% group_by(id, year(date), month(date)) %>% summarize(...)

Tuttavia la sfida che sto avendo è come impostare una finestra temporale per un periodo arbitrario: 5 giorni, 10 giorni, ecc

C'è anche la biblioteca RcppRoll ma entrambi RcppRoll e le funzioni di laminazione in zoo sembrano più di setup per serie storiche regolari. Per quanto posso dire queste funzioni della finestra funzionano in base al numero di righe invece di un periodo di tempo specificato - la differenza chiave è che un certo periodo di tempo potrebbe avere un numero diverso di righe a seconda della data e dell'utente.

Ad esempio, è possibile per l'utente 1, che il numero di operazioni nei 5 giorni precedenti di 2015-01-01 è pari a 100 transazioni e per lo stesso utente il numero di transazioni nei 5 giorni precedenti di 2015-02-01 è uguale a 5 transazioni. Quindi guardando indietro un certo numero di righe semplicemente non funzionerà.

Inoltre, c'è un altro SO infilare discutere date di rotolamento per i dati di tipo serie temporali irregolari (Create new column based on condition that exists within a rolling date) tuttavia la soluzione accettata stava usando data.table e sto specificamente alla ricerca di un modo dplyr di raggiungere questo obiettivo.

Suppongo che al centro di questo problema, questo problema può essere risolto rispondendo a questa domanda: come è possibile I periodi di tempo group_by in dplyr. In alternativa, se c'è un diverso modo dplyr per ottenere sopra senza complicato group_by, come posso farlo?

MODIFICA: esempio aggiornato per rendere più chiara la natura della finestra di rotolamento.

risposta

0

MODIFICATO in base al commento seguente.

Si può provare qualcosa di simile per un massimo di 5 giorni:

df %>% 
    arrange(id, date) %>% 
    group_by(id) %>% 
    filter(as.numeric(difftime(Sys.Date(), date, unit = 'days')) <= 5) %>% 
    summarise(n_total_widgets = sum(n_widgets)) 

In questo caso, non ci sono giorni entro cinque della corrente. Quindi, non produrrà alcun output.

Per ottenere ultimi cinque giorni per ogni ID, si può fare qualcosa di simile:

df %>% 
    arrange(id, date) %>% 
    group_by(id) %>% 
    filter(as.numeric(difftime(max(date), date, unit = 'days')) <= 5) %>% 
    summarise(n_total_widgets = sum(n_widgets)) 

uscita risultante sarà:

Source: local data frame [4 x 2] 

    id n_total_widgets 
    (dbl)   (dbl) 
1  1    4 
2  2    5 
3  3    4 
4  4    5 
+1

Ho modificato ... è possibile modificare il difftime in suite esattamente come lo si vuole calcolare. Includere la data corrente, o no, o iniziare a contare da un'altra data. – Gopala

+0

Quanto sopra è sicuramente utile per quando sto confrontando ogni riga con la data corrente o contro una data impostata. Tuttavia, ogni riga corrisponderà a una finestra temporale diversa da verificare: ad esempio per il 2015-01-01, ho bisogno di aggregare i dati dal 2014-12-28 al 2015-01-01 e per il 2015-01-25 ho bisogno di dati aggregati dal 2015-01-21 al 2015-01-25. –

+0

Puoi usare 'max (date)' invece di 'Sys.Date()' per esempio in modo da avere un riferimento diverso per gruppo ('id'). – Gopala

4

Questo può essere fatto utilizzando SQL:

library(sqldf) 

dd <- transform(data, date = as.Date(date)) 
sqldf("select a.*, count(*) n_trans30, sum(b.n_widgets) 'total_widgets30' 
     from dd a 
     left join dd b on b.date between a.date - 30 and a.date 
         and b.id = a.id 
         and b.rowid <= a.rowid 
     group by a.rowid") 

con:

id  date n_widgets n_trans30 total_widgets30 
1 1 2015-01-01   1   1    1 
2 1 2015-01-01   2   2    3 
3 1 2015-01-05   3   3    6 
4 1 2015-01-25   4   4    10 
5 2 2015-05-05   5   1    5 
6 2 2015-01-01   2   1    2 
7 3 2015-08-01   4   1    4 
8 4 2015-01-01   5   1    5 
2

Un altro approccio è quello di espandere il set di dati per contenere tutte le possibili giorni (usando tidyr::complete), quindi utilizzare la funzione di laminazione (RcppRoll::roll_sum)

Il fatto che si dispone di più osservazioni al giorno è probabilmente la creazione di un problema però .. .

library(tidyr) 
library(RcppRoll) 
df2 <- df %>% 
    mutate(date=as.Date(date)) 

## create full dataset with all possible dates (go even 30 days back for first observation) 
df_full<- df2 %>% 
mutate(date=as.Date(date)) %>% 
    complete(id, 
     date=seq(from=min(.$date)-30,to=max(.$date), by=1), 
     fill=list(n_widgets=0)) 

## now use rolling function, and keep only original rows (left join) 
df_roll <- df_full %>% 
    group_by(id) %>% 
    mutate(n_trans_30=roll_sum(x=n_widgets!=0, n=30, fill=0, align="right"), 
     total_widgets_30=roll_sum(x=n_widgets, n=30, fill=0, align="right")) %>% 
    ungroup() %>% 
    right_join(df2, by = c("date", "id", "n_widgets")) 

il risultato è lo stesso come il vostro (per caso)

 id  date n_widgets n_trans_30 total_widgets_30 
    <dbl>  <date>  <dbl>  <dbl>   <dbl> 
1  1 2015-01-01   1   1    1 
2  1 2015-01-01   2   2    3 
3  1 2015-01-05   3   3    6 
4  1 2015-01-25   4   4    10 
5  1 2015-02-15   4   2    8 
6  2 2015-05-05   5   1    5 
7  2 2015-01-01   2   1    2 
8  3 2015-08-01   4   1    4 
9  4 2015-01-01   5   1    5 

Ma, come detto, sarà fallire per alcuni giorni contando gli ultimi 30 obs, non gli ultimi 30 giorni. Quindi potresti volere prima a summarise le informazioni di giorno, quindi applicare questo.