2015-11-04 5 views
6

Cercando di convertire la seguente R data.frame:Dplyr windowing condizionale

structure(list(Time=c("09:30:01" ,"09:30:29" ,"09:35:56", "09:37:17" ,"09:37:21" ,"09:37:28" ,"09:37:35" ,"09:37:51" ,"09:42:11" ,"10:00:31"), 
      Price=c(1,2,3,4,5,6,7,8,9,10), 
      Volume=c(100,200,300,100,200,300,100,200,600,100)), 
     .Names = c("Time", "Price", "Volume"), 
     row.names = c(NA,10L), 
     class = "data.frame") 

      Time Price Volume 
    1 09:30:01  1 100 
    2 09:30:29  2 200 
    3 09:35:56  3 300 
    4 09:37:17  4 100 
    5 09:37:21  5 200 
    6 09:37:28  6 300 
    7 09:37:35  7 100 
    8 09:37:51  8 200 
    9 09:42:11  9 600 
    10 10:00:31 10 100 

in questo

 Time Price Volume Bin 
1 09:30:01  1  100 1 
2 09:30:29  2  200 1 
3 09:35:56  3  200 1 
4 09:35:56  3  100 2 
5 09:37:17  4  100 2 
6 09:37:21  5  200 2 
7 09:37:28  6  100 2 
8 09:37:28  6  200 3 
9 09:37:35  7  100 3 
10 09:37:51  8  200 3 
11 09:42:11  9  500 4 
12 09:42:11  9  100 5 
13 10:00:31 10  100 5 

sostanza, si calcola somme cumulative sul volume e categorizzazione eachtime 500 viene violata. Quindi, bin 1 è 100 + 200 + 200 con il volume alle 09:35:56 diviso in 200/100 e una nuova riga inserita e il contatore bin incrementato.

Questo è relativamente semplice con la base R ma mi chiedevo se ci fosse un modo più elegante e, auspicabilmente, più veloce con dplyr.

Acclamazioni

Aggiornamento:

Grazie @Frank e @AntoniosK.

Per rispondere alla domanda, l'intervallo dei valori di volume è tutti valori di interi positivi compresi tra 1 e 10k.

I microbenchmarked entrambi gli approcci e dplyr era leggermente più veloce ma non molto in esso, su un dataset simile a quello precedente con ~ 200k righe.

davvero apprezzare le risposte rapide e assistenza

+3

Forse dovresti mostrare il modo "relativamente semplice" che hai provato. Immagino che non ci sia un approccio dplyr elegante, ma ci possono essere margini di miglioramento nella virata R di base. – Frank

+0

Hai sempre dei valori come 100.200, ecc.? O questo è un caso semplificato? – AntoniosK

+0

Dai un'occhiata a http://stackoverflow.com/questions/15466880/cumulative-sum-until-maximum-reached-then-repeat-from-zero-in-the-next-row –

risposta

4

Non so se questo è il modo migliore e più veloce, ma sembra veloce per quei Volume valori. La filosofia è semplice. In base al valore Volume, hai creato molte righe di Time e Price con Volume = 1. Quindi lasciare che cumsum aggiunga i numeri e contrassegni ogni volta che si ha un nuovo lotto di 500. Utilizza questi flag per creare i tuoi valori Bin.

structure(list(Time=c("09:30:01" ,"09:30:29" ,"09:35:56", "09:37:17" ,"09:37:21" ,"09:37:28" ,"09:37:35" ,"09:37:51" ,"09:42:11" ,"10:00:31"), 
       Price=c(1,2,3,4,5,6,7,8,9,10), 
       Volume=c(100,200,300,100,200,300,100,200,600,100)), 
      .Names = c("Time", "Price", "Volume"), 
      row.names = c(NA,10L), 
      class = "data.frame") -> dt 

library(dplyr) 

dt %>% 
    group_by(Time, Price) %>%      ## for each Time and Price 
    do(data.frame(Volume = rep(1,.$Volume))) %>% ## create as many rows, with Volume = 1, as the value of Volume 
    ungroup() %>%         ## forget about the grouping 
    mutate(CumSum = cumsum(Volume),    ## cumulative sums 
     flag_500 = ifelse(CumSum %in% seq(501,sum(dt$Volume),by=500),1,0), ## flag 500 batches (at 501, 1001, etc.) 
     Bin = cumsum(flag_500)+1) %>%   ## create Bin values 
    group_by(Bin, Time, Price) %>%    ## for each Bin, Time and Price 
    summarise(Volume = sum(Volume)) %>%   ## get new Volume values 
    select(Time, Price, Volume, Bin) %>%   ## use only if you want to re-arrange column order 
    ungroup()          ## use if you want to forget the grouping 

#  Time Price Volume Bin 
#  (chr) (dbl) (dbl) (dbl) 
# 1 09:30:01  1 100  1 
# 2 09:30:29  2 200  1 
# 3 09:35:56  3 200  1 
# 4 09:35:56  3 100  2 
# 5 09:37:17  4 100  2 
# 6 09:37:21  5 200  2 
# 7 09:37:28  6 100  2 
# 8 09:37:28  6 200  3 
# 9 09:37:35  7 100  3 
# 10 09:37:51  8 200  3 
# 11 09:42:11  9 500  4 
# 12 09:42:11  9 100  5 
# 13 10:00:31 10 100  5 
3

Questo non è "semplice".

Con data.table, è ancora molte righe di codice:

library(data.table) 
setDT(DF) 
DF[, c("cV","cVL") := shift(cumsum(Volume), 0:1, type="lag", fill=0) ] 
DF[, end := (cV %/% 500) - (cV %% 500 == 0) ] 
DF[, start := shift(end, type = "lag", fill = -1) + (cVL %% 500 == 0) ] 

badcols = c("Volume","cV","cVL","start","end") 
DF[,{ 
    V = 
    if (start==end) Volume 
    else c((start+1)*500-cVL, rep(500, max(end-start-2,0)), cV - end*500) 

    c(.SD[, !badcols, with=FALSE], list(Volume = V, Bin = 1+start:end)) 
}, by=.(r=seq(nrow(DF)))][,!"r",with=FALSE] 

che dà

 Time Price Volume Bin 
1: 09:30:01  1 100 1 
2: 09:30:29  2 200 1 
3: 09:35:56  3 200 1 
4: 09:35:56  3 100 2 
5: 09:37:17  4 100 2 
6: 09:37:21  5 200 2 
7: 09:37:28  6 100 2 
8: 09:37:28  6 200 3 
9: 09:37:35  7 100 3 
10: 09:37:51  8 200 3 
11: 09:42:11  9 500 4 
12: 09:42:11  9 100 5 
13: 10:00:31 10 100 5 
3

Ecco un modo utilizzando data.table ed è rotolamento join caratteristica:

require(data.table) # v1.9.6+ 
setDT(df)[, csum := cumsum(Volume)] 
ans = rbind(df, df[.(csum=500 * seq_len(max(csum)%/% 500L)), roll=-Inf, on="csum"]) 
setorder(ans, Price, csum) 
ans = ans[, `:=`(Volume = c(csum[1L], diff(csum)), 
       id  = (csum-1L) %/% 500L + 1L, 
       csum = NULL)][Volume > 0L] 

Il primo passaggio aggiunge un nuovo co lumn con la somma cumulativa di Volume.

Il secondo passaggio è forse il più importante. Diamo un'occhiata alla seconda parte. Per ciascun multiplo di 500 fino a max(csum), si aggiunge al primo valore> = un multiplo di 500 su df$csum. È un che si unisce al NOCB join (prossima osservazione riportata indietro). Con questo ci piacerebbe avere:

#  Time Price Volume csum 
# 1: 09:35:56  3 300 500 
# 2: 09:37:28  6 300 1000 
# 3: 09:37:51  8 200 1500 
# 4: 09:42:11  9 600 2000 

Questi sono i punti di rottura che deve essere aggiunto al tuo data.table originale. Questo è ciò che facciamo con rbind().

Quindi, tutto ciò che facciamo è ordinare per Price, csum, generare indietro colonna Volume. Da lì, la generazione della colonna id può essere eseguita utilizzando la colonna csum come mostrato.