2015-12-30 18 views
11

Ich habe einen Datenrahmen, der Datenrahmen ist bereits nach Bedarf sortiert, aber jetzt möchte ich es in Gruppen "in Scheiben schneiden".Bedingte Cumsum mit Reset

Diese Gruppen sollten einen maximalen kumulativen Wert von 10 haben, wenn der kumulierte Wert> 10 ist, sollte es die kumulative Summe zurückgesetzt und wieder von vorn beginnen

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 

Dies ist die Ausgabe, die ich suche (ich habe es getan "von Hand")

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 

So 2 Probleme, die ich habe

  1. Wie eine kumulative Variable erstellen, jedes Mal setzt es eine obere Grenze passiert (10 in diesem Fall)
  2. Wie zählen/Gruppe jede Gruppe

Für den ersten Teil ich ohne Glück einige Kombinationen von group_by und cumsum versuche

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

ich würde statt einer for-Schleife

Dank

+1

Der zweite Teil wird trivial sein ('group_by'), wenn Sie die erste herausfinden können. Ich denke, es wird schwierig sein, das erste ohne eine "for" -Schleife zu machen, es sei denn, jemand wird extrem clever. Willst du Rohre für Effizienz, Eleganz, ...? Wenn in einer Hilfsfunktion eine for-Schleife versteckt wäre, wäre das in Ordnung? –

+0

überprüfen Sie es http://stackoverflow.com/questions/29054459/how-to-speed-up-or-vectorize-a-for-loop/29055443#29055443 – Khashaa

+2

'group_by (bin (Wert, 10))%>% muate (cumsum (Wert)) 'mit' bin' Funktion in der Verbindung – Khashaa

Antwort

7

ich denke, das ist nicht leicht voctorizabl ein Rohr (%>%) Lösung bevorzugen e .... zumindest weiß ich nicht wie.

Sie können es tun by hand über:

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) 
} 

Für Ihre Daten ergibt dies:

> 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 

auch für meine "Gegenbeispiel" ergibt dies:

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

Wie @ Khashaa wies darauf hin, dass dies effizienter über Rcpp implementiert werden kann. Er verknüpfte diese Antwort How to speed up or vectorize a for loop?, die ich sehr nützlich finde

+0

Danke! Das funktioniert perfekt! –

3

Die folgende Funktion verwendet Rekursion, um einen Vektor mit den Längen jeder Gruppe zu erstellen. Es ist schneller als eine Schleife für kleine Datenvektoren (Länge weniger als ungefähr hundert Werte), aber langsamer für längere. Es benötigt drei Argumente:

1) vec: Ein Vektor von Werten, die wir gruppieren möchten.

2) i: Der Index der Startposition in vec.

3) glv: Ein Vektor von Gruppenlängen. Dies ist der Rückgabewert, aber wir müssen ihn initialisieren und ihn durch jede Rekursion weiterleiten.

# 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) 
} 

Ausführen der Funktion einen Vektor von Gruppenlängen zurückzukehren:

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

Um eine Spalte zu df mit den Gruppenlängen hinzuzufügen, verwenden rep:

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

In seiner jetzigen Form Die Funktion funktioniert nur bei Vektoren, die keine Werte größer als 10 haben, und die Gruppierung nach Summen < = 10 ist fest codiert. Die Funktion kann natürlich verallgemeinert werden, um mit diesen Einschränkungen umzugehen.

Die Funktion kann etwas beschleunigt werden, indem kumulative Summen verwendet werden, die nur eine bestimmte Anzahl von Werten voraussehen, und nicht die verbleibende Länge des Vektors. Wenn die Werte beispielsweise immer positiv sind, müssen Sie nur zehn Werte voraussehen, da Sie nie mehr als zehn Zahlen addieren müssen, um den Wert 10 zu erreichen. Auch dies kann für jeden Zielwert verallgemeinert werden. Selbst mit dieser Modifikation ist die Funktion immer noch langsamer als eine Schleife für einen Vektor mit mehr als etwa hundert Werten.

Ich habe noch nicht mit rekursiven Funktionen in R gearbeitet und wäre an irgendwelchen Kommentaren und Vorschlägen interessiert, ob Rekursion für diese Art von Problem sinnvoll ist und ob es verbessert werden kann, insbesondere die Ausführungsgeschwindigkeit.

1

Sie könnten Ihre eigene Funktion definieren und es dann in der dplyr mutate-Anweisung wie folgt:

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

Die cumsum_with_reset() Funktion nimmt eine Spalte und einen Schwellenwert, der die Summe zurücksetzt. cumsum_with_reset_group() ist ähnlich, identifiziert aber Zeilen, die zusammen gruppiert wurden. Definitionen sind wie folgt:

# 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()