2015-12-30 16 views
11

Ho un frame di dati, il frame di dati è già ordinato in base alle esigenze, ma ora mi piacerebbe "affettarlo" in gruppi.Cumsum condizionale con reset

Questi gruppi devono avere un valore cumulativo massimo di 10. Quando il valore cumulativo è> 10, dovrebbe ripristinare la somma cumulativa e ricominciare da capo

library(dplyr) 
id <- sample(1:15) 
order <- 1:15 
value <- c(4, 5, 7, 3, 8, 1, 2, 5, 3, 6, 2, 6, 3, 1, 4) 
df <- data.frame(id, order, value) 
df 

Questa è l'uscita che sto cercando (L'ho fatto "manualmente")

cumsum_10 <- c(4, 9, 7, 10, 8, 9, 2, 7, 10, 6, 8, 6, 9, 10, 4) 
group_10 <- c(1, 1, 2, 2, 3, 3, 4, 4, 4, 5, 5, 6, 6, 6, 7) 
df1 <- data.frame(df, cumsum_10, group_10) 
df1 

Così sto avendo problemi 2

  1. Come creare una variabile cumulativo che azzera ogni volta che passa un limite superiore (10 in questo caso)
  2. Come contare/gruppo ogni gruppo

Per la prima parte stavo cercando alcune combinazioni di group_by e cumSum senza fortuna

df1 <- df %>% group_by(cumsum(c(False, value < 10))) 

preferirei un tubo (%>%) soluzione invece di un ciclo

Grazie

+1

La seconda parte sarà banale ('group_by') se riesci a capire il primo. Penso che sarà difficile fare il primo senza un ciclo 'for', a meno che qualcuno non sia estremamente intelligente. Vuoi tubi per efficienza, eleganza, ...? Se ci fosse un ciclo for nascosto in una funzione helper, sarebbe OK? –

+0

controllalo http://stackoverflow.com/questions/29054459/how-to-speed-up-or-vectorize-a-for-loop/29055443#29055443 – Khashaa

+2

'group_by (bin (valore, 10))%>% mutare (cumsum (valore)) 'utilizzando la funzione' bin' nel collegamento – Khashaa

risposta

7

credo che questo non è facilmente voctorizabl e .... almeno non so come.

si può fare by hand via:

my_cumsum <- function(x){ 
    grp = integer(length(x)) 
    grp[1] = 1 
    for(i in 2:length(x)){ 
    if(x[i-1] + x[i] <= 10){ 
     grp[i] = grp[i-1] 
     x[i] = x[i-1] + x[i] 
    } else { 
     grp[i] = grp[i-1] + 1 
    } 
    } 
    data.frame(grp, x) 
} 

per i dati questo dà:

> my_cumsum(df$value) 
    grp x 
1 1 4 
2 1 9 
3 2 7 
4 2 10 
5 3 8 
6 3 9 
7 4 2 
8 4 7 
9 4 10 
10 5 6 
11 5 8 
12 6 6 
13 6 9 
14 6 10 
15 7 4 

anche per il mio "contro-esempio" questo dà:

> my_cumsum(c(10,6,4)) 
    grp x 
1 1 10 
2 2 6 
3 2 10 

Come @ Khashaa ha sottolineato che questo può essere implementato in modo più efficiente tramite Rcpp. Si è collegato a questa risposta How to speed up or vectorize a for loop? che trovo molto utile

+0

Grazie! Funziona perfettamente! –

3

La funzione seguente utilizza la ricorsione per costruire un vettore con le lunghezze di ciascun gruppo. È più veloce di un ciclo per vettori di dati di piccole dimensioni (lunghezza inferiore a circa un centinaio di valori), ma più lento per quelli più lunghi. Sono necessari tre argomenti:

1) vec: un vettore di valori che vogliamo raggruppare.

2) i: l'indice della posizione di partenza in vec.

3) glv: Un vettore di lunghezze di gruppo. Questo è il valore di ritorno, ma dobbiamo inizializzarlo e passarlo lungo ogni ricorsione.

# Group a vector based on consecutive values with a cumulative sum <= 10 
gf = function(vec, i, glv) { 

    ## Break out of the recursion when we get to the last group 
    if (sum(vec[i:length(vec)]) <= 10) { 
    glv = c(glv, length(i:length(vec))) 
    return(glv) 
    } 

    ## Keep recursion going if there are at least two groups left 
    # Calculate length of current group 
    gl = sum(cumsum(vec[i:length(vec)]) <= 10) 

    # Append to previous group lengths 
    glv.append = c(glv, gl) 

    # Call function recursively 
    gf(vec, i + gl, glv.append) 
} 

Eseguire la funzione per restituire un vettore di lunghezze gruppo:

group_vec = gf(df$value, 1, numeric(0)) 
[1] 2 2 2 3 2 3 1 

Per aggiungere una colonna a df con lunghezze gruppo, utilizzare rep:

df$group10 = rep(1:length(group_vec), group_vec) 

Nella sua forma attuale la funzione funzionerà solo su vettori che non hanno valori maggiori di 10 e il raggruppamento per somme < = 10 è hardcoded. La funzione può naturalmente essere generalizzata per gestire queste limitazioni.

La funzione può essere velocizzata facendo somme cumulative che guardano avanti solo un certo numero di valori, piuttosto che la lunghezza rimanente del vettore. Ad esempio, se i valori sono sempre positivi, devi solo guardare dieci valori in avanti, poiché non avrai mai bisogno di sommare più di dieci numeri per raggiungere un valore di 10. Anche questo può essere generalizzato per qualsiasi valore target. Anche con questa modifica, la funzione è ancora più lenta di un ciclo per un vettore con più di un centinaio di valori.

Non ho lavorato prima con le funzioni ricorsive in R e sarei interessato a commenti e suggerimenti sul fatto che la ricorsione abbia senso per questo tipo di problema e se possa essere migliorata, in particolare la velocità di esecuzione.

1

Si potrebbe definire la propria funzione e quindi utilizzarlo all'interno mutate dichiarazione di dplyr come segue:

df %>% group_by() %>% 
    mutate(
    cumsum_10 = cumsum_with_reset(value, 10), 
    group_10 = cumsum_with_reset_group(value, 10) 
) %>% 
    ungroup() 

La funzione cumsum_with_reset() prende una colonna e un valore di soglia, che ripristina la somma. cumsum_with_reset_group() è simile ma identifica le righe che sono state raggruppate insieme. Le definizioni sono le seguenti:

# group rows based on cumsum with reset 
cumsum_with_reset_group <- function(x, threshold) { 
    cumsum <- 0 
    group <- 1 
    result <- numeric() 

    for (i in 1:length(x)) { 
    cumsum <- cumsum + x[i] 

    if (cumsum > threshold) { 
     group <- group + 1 
     cumsum <- x[i] 
    } 

    result = c(result, group) 

    } 

    return (result) 
} 

# cumsum with reset 
cumsum_with_reset <- function(x, threshold) { 
    cumsum <- 0 
    group <- 1 
    result <- numeric() 

    for (i in 1:length(x)) { 
    cumsum <- cumsum + x[i] 

    if (cumsum > threshold) { 
     group <- group + 1 
     cumsum <- x[i] 
    } 

    result = c(result, cumsum) 

    } 

    return (result) 
} 

# use functions above as window functions inside mutate statement 
df %>% group_by() %>% 
    mutate(
    cumsum_10 = cumsum_with_reset(value, 10), 
    group_10 = cumsum_with_reset_group(value, 10) 
) %>% 
    ungroup()