2017-12-06 98 views
5

Znalazłem wariacje tego pytania i wiem, że modulos można ewentualnie używać, ale mam trudny czas, łącząc to wszystko razem.Jak powtórzyć sekwencję, gdy warunek zostanie spełniony

Mam sekwencję obserwacji według ID i sekund. Kiedy łączna liczba sekund przez id zwiększa się o więcej niż 5 sekund, chciałbym ponownie rozpocząć liczenie. Czy ktoś mógłby mi pomóc odpowiedzieć na to pytanie w dplyr?

Original df

df <- data.frame(id = c(1,1,1,1,1,2,2,2,2,3,3,3,3), 
       val = c(2,10,12,15,17,2,4,7,8,12,15,20,25)) 

df 
    id val 
1 1 2 
2 1 10 
3 1 12 
4 1 15 
5 1 17 
6 2 2 
7 2 4 
8 2 7 
9 2 8 
10 3 12 
11 3 15 
12 3 20 
13 3 25 

pożądanego rezultatu

finalResult 
    id val reset 
1 1 2  1 
2 1 10  2 
3 1 12  2 
4 1 15  3 
5 1 17  3 
6 2 2  1 
7 2 4  1 
8 2 7  2 
9 2 8  2 
10 3 12  1 
11 3 15  1 
12 3 20  2 
13 3 25  3 

Edytuj

Dzięki za odpowiedzi wczoraj ale napotkał pewne problemy z podanych rozwiązań.

Na tym zestawie danych kod działa w niektórych przypadkach.

sub.df <- structure(list(`ID` = c("1", 
               "1", "1", 
               "1", "1", 
               "1", "1", 
               "1", "1" 
), dateFormat = structure(c(1479955726, 1479955726, 1483703713, 
          1495190809, 1495190809, 1497265079, 1497265079, 1474023059, 1474023061 
), class = c("POSIXct", "POSIXt"), tzone = "America/Chicago")), .Names = c("ID", 
                      "dateFormat"), row.names = c(NA, -9L), class = c("tbl_df", "tbl", 
                                  "data.frame")) 

Rozwiązanie Używane:

jj <- sub.df %>% 
    group_by(`ID`) %>% 
    arrange(`ID`,`dateFormat`)%>% 
    mutate(totalTimeInt = difftime(dateFormat,first(dateFormat),units = 'secs'))%>% 
    mutate(totalTimeFormat = as.numeric(totalTimeInt))%>% 
    mutate(reset = cumsum(
    Reduce(
     function(x, y) 
     if (x + y >= 5) 0 
     else x + y, 

     diff(totalTimeFormat), init = 0, accumulate = TRUE 
    ) == 0 
))%>% 
    mutate(reset_2 = cumsum(
    accumulate(
     diff(totalTimeFormat), 
     ~if (.x + .y >= 5) 0 else .x + .y, 
     .init = 0 
    ) == 0 
)) 

Wynik

# A tibble: 9 x 6 
# Groups: ID [1] 
    ID   dateFormat totalTimeInt totalTimeFormat reset reset_2 
    <chr>    <dttm>  <time>   <dbl> <int> <int> 
1  1 2016-09-16 05:50:59  0 secs    0  1  1 
2  1 2016-09-16 05:51:01  2 secs    2  1  1 
3  1 2016-11-23 20:48:46 5932667 secs   5932667  2  2 
4  1 2016-11-23 20:48:46 5932667 secs   5932667  3  3 
5  1 2017-01-06 05:55:13 9680654 secs   9680654  4  4 
6  1 2017-05-19 05:46:49 21167750 secs  21167750  5  5 
7  1 2017-05-19 05:46:49 21167750 secs  21167750  6  6 
8  1 2017-06-12 05:57:59 23242020 secs  23242020  7  7 
9  1 2017-06-12 05:57:59 23242020 secs  23242020  8  8 

Co się dzieje, że przez pierwsze dwa obserwacji prawidłowo liczy, że w 1 instancji. Gdy dojdzie do trzeciej i czwartej obserwacji, należy to traktować tylko jako dwie obserwacje, ponieważ zasadniczo nie ma czasu, który upłynął między tymi dwoma przypadkami.

Prawidłowe wyjściowa:

# A tibble: 9 x 6 
# Groups: ID [1] 
    ID   dateFormat totalTimeInt totalTimeFormat reset reset_2 
    <chr>    <dttm>  <time>   <dbl> <int> <int> 
1  1 2016-09-16 05:50:59  0 secs    0  1  1 
2  1 2016-09-16 05:51:01  2 secs    2  1  1 
3  1 2016-11-23 20:48:46 5932667 secs   5932667  2  2 
4  1 2016-11-23 20:48:46 5932667 secs   5932667  2  2 
5  1 2017-01-06 05:55:13 9680654 secs   9680654  3  3 
6  1 2017-05-19 05:46:49 21167750 secs  21167750  4  4 
7  1 2017-05-19 05:46:49 21167750 secs  21167750  4  4 
8  1 2017-06-12 05:57:59 23242020 secs  23242020  5  5 
9  1 2017-06-12 05:57:59 23242020 secs  23242020  5  5 
+0

Należy zauważyć, że w grupie id # 1, gdy val przechodzi od 12 do 15, resetowanie zmienia się, ale w grupie nr 3 tak nie jest. Moja odpowiedź poniżej jest zgodna z logiką w pierwszej grupie. –

+2

@JosephWood To dlatego, że w grupie # 1 w tym punkcie odniesienie do resetowania wynosi "10", podczas gdy dla grupy # 3 to '12' – duckmayr

+0

@duckmayr, dzięki za wyjaśnienie tego punktu (tj. Punkt odniesienia nie jest po prostu pierwszą wartością w grupa, ale wartość, przy której różnica poprzedniego odniesienia jest większa lub równa 5). Moja teraz usunięta odpowiedź była naiwna i niepoprawnie odnosiła się tylko do pierwszej wartości w tej grupie. –

Odpowiedz

4

Jeśli używasz Reduce z accumulate = TRUE (lub purrr::accumulate, jeśli wolisz), można przywrócić różnica działająca, gdy jest większa lub równa 5. Wywołanie cumsum, czy suma wynosi 0, zwróci liczbę resetów.

library(tidyverse) 

df <- data.frame(id = c(1,1,1,1,1,2,2,2,2,3,3,3,3), 
       val = c(2,10,12,15,17,2,4,7,8,12,15,20,25)) 

df %>% 
    group_by(id) %>% 
    mutate(reset = cumsum(
     Reduce(
      function(x, y) if (x + y >= 5) 0 else x + y, 
      diff(val), init = 0, accumulate = TRUE 
     ) == 0 
    )) 
#> # A tibble: 13 x 3 
#> # Groups: id [3] 
#>  id val reset 
#> <dbl> <dbl> <int> 
#> 1  1  2  1 
#> 2  1 10  2 
#> 3  1 12  2 
#> 4  1 15  3 
#> 5  1 17  3 
#> 6  2  2  1 
#> 7  2  4  1 
#> 8  2  7  2 
#> 9  2  8  2 
#> 10  3 12  1 
#> 11  3 15  1 
#> 12  3 20  2 
#> 13  3 25  3 

lub purrr::accumulate,

df %>% 
    group_by(id) %>% 
    mutate(reset = cumsum(
     accumulate(
      diff(val), 
      ~if (.x + .y >= 5) 0 else .x + .y, 
      .init = 0 
     ) == 0 
    )) 
#> # A tibble: 13 x 3 
#> # Groups: id [3] 
#>  id val reset 
#> <dbl> <dbl> <int> 
#> 1  1  2  1 
#> 2  1 10  2 
#> 3  1 12  2 
#> 4  1 15  3 
#> 5  1 17  3 
#> 6  2  2  1 
#> 7  2  4  1 
#> 8  2  7  2 
#> 9  2  8  2 
#> 10  3 12  1 
#> 11  3 15  1 
#> 12  3 20  2 
#> 13  3 25  3 

Odnośnie edycji, problem jest, że niektóre z różnic typu 0, który jest taki sam jak co to liczenie zobaczyć resetuje. Najprostszym rozwiązaniem jest użycie NA zamiast zera jako wartości resetu:

library(tidyverse) 

sub.df <- structure(list(`ID` = c("1", "1", "1", "1", "1", "1", "1", "1", "1"), 
         dateFormat = structure(c(1479955726, 1479955726, 1483703713, 
          1495190809, 1495190809, 1497265079, 1497265079, 1474023059, 1474023061), 
          class = c("POSIXct", "POSIXt"), tzone = "America/Chicago")), 
        .Names = c("ID", "dateFormat"), row.names = c(NA, -9L), 
        class = c("tbl_df", "tbl", "data.frame")) 

sub.df %>% 
    group_by(ID) %>% 
    arrange(ID, dateFormat) %>% 
    mutate(reset = cumsum(is.na(
       accumulate(diff(dateFormat), 
          ~{ 
           s <- sum(.x, .y, na.rm = TRUE); 
           if (s >= 5) NA else s 
          }, 
          .init = NA) 
    ))) 
#> # A tibble: 9 x 3 
#> # Groups: ID [1] 
#>  ID   dateFormat reset 
#> <chr>    <dttm> <int> 
#> 1  1 2016-09-16 05:50:59  1 
#> 2  1 2016-09-16 05:51:01  1 
#> 3  1 2016-11-23 20:48:46  2 
#> 4  1 2016-11-23 20:48:46  2 
#> 5  1 2017-01-06 05:55:13  3 
#> 6  1 2017-05-19 05:46:49  4 
#> 7  1 2017-05-19 05:46:49  4 
#> 8  1 2017-06-12 05:57:59  5 
#> 9  1 2017-06-12 05:57:59  5 

Ostatecznie takie podejście napotyka ograniczenia, zbyt jednak, jakby wszelkie wartości faktycznie NA będzie zwiększać podobnie. Bardziej niezawodnym rozwiązaniem byłoby zwrócenie listy dwóch elementów z każdej iteracji, jedna dla sumy z resetami i jedna dla licznika resetowania. Jest to więcej pracy do wykonania, choć:

sub.df %>% 
    group_by(ID) %>% 
    arrange(ID, dateFormat) %>% 
    mutate(total_reset = accumulate(
     transpose(list(total = diff(dateFormat), reset = rep(0, n() - 1))), 
     ~{ 
      s <- .x$total + .y$total; 
      if (s >= 5) { 
       data_frame(total = 0, reset = .x$reset + 1) 
      } else { 
       data_frame(total = s, reset = .x$reset) 
      } 
     }, 
     .init = data_frame(total = 0, reset = 1) 
    )) %>% 
    unnest() 
#> # A tibble: 9 x 4 
#> # Groups: ID [1] 
#>  ID   dateFormat total reset 
#> <chr>    <dttm> <dbl> <dbl> 
#> 1  1 2016-09-16 05:50:59  0  1 
#> 2  1 2016-09-16 05:51:01  2  1 
#> 3  1 2016-11-23 20:48:46  0  2 
#> 4  1 2016-11-23 20:48:46  0  2 
#> 5  1 2017-01-06 05:55:13  0  3 
#> 6  1 2017-05-19 05:46:49  0  4 
#> 7  1 2017-05-19 05:46:49  0  4 
#> 8  1 2017-06-12 05:57:59  0  5 
#> 9  1 2017-06-12 05:57:59  0  5 

Całkowita wygląda trochę głupie, ale jeśli spojrzeć na diff, to rzeczywiście poprawna.

+0

dzięki za tę odpowiedź. Działa wspaniale. Ale czy mógłbyś wyjaśnić funkcję "Zmniejsz"? Nie rozumiem tej części. – DataTx

+2

'Reduce' stosuje funkcję binarną (2-zmienną) do kolejnych wyrazów wektora. Domyślnie wszystko jest zwijane do pojedynczego terminu, więc 'Reduce (\' + \ ', 1: 4)' jest takie samo jak 'sum (1: 4)', chociaż oblicza się jako '(((1 + 2) + 3) + 4) '. Jednak jeśli dodasz 'kumulować = PRAWDA', to zapisuje terminy pośrednie, więc' Zmniejsz (\ '+ \', 1: 4, akumuluj = PRAWDA) 'jest równoważne' kumulacji (1: 4) '. Obsługuje również listy (w tym ramki danych), np. 'Reduce (\' + \ ', mtcars)' i zaakceptuje funkcję binarną o dowolnej złożoności. Jeśli podano "init", jest ono używane jako pierwsza wartość wektora. – alistaire

+0

Wpadłem na problem z rozwiązaniem. Byłbym wdzięczny za wszelkie dane wejściowe, jeśli wiesz, jak to poprawić. Dziękuję Ci. – DataTx

2

może się mylę (EDIT: I został sprawdzony źle, przez alistaire „s brilliant answer, choć Wyjeżdżam to podejście tutaj na razie), ale myślę, że jest to jeden instancji, w których faktycznie potrzebujesz pętli, ponieważ wartość reset w każdym wierszu będzie zależeć od tego, co się stało dla poprzednich wierszy. Mam nadzieję, że Joseph Wood wymyśli coś mądrzejszego, ale w międzyczasie mamy do czynienia z naiwnym podejściem, które zgodnie z prośbą wykorzystuje dplyr. Możemy wykonać następującą funkcję

count_resets <- function(x) { 
    N <- length(x) 
    value <- 1 
    result <- rep(1, N) 
    threshold <- x[1] 
    for (i in 2:N) { 
     if (abs(x[i] - threshold) >= 5) { 
      value <- value + 1 
      threshold <- x[i] 
     } 
     result[i] <- value 
    } 
    return(result) 
} 

i stosować go przez id użyciu dplyr „s group_by():

library(dplyr) 

df %>% 
    group_by(id) %>% 
    mutate(reset = count_resets(val)) 

# A tibble: 13 x 3 
# Groups: id [3] 
     id val reset 
    <dbl> <dbl> <dbl> 
1  1  2  1 
2  1 10  2 
3  1 12  2 
4  1 15  3 
5  1 17  3 
6  2  2  1 
7  2  4  1 
8  2  7  2 
9  2  8  2 
10  3 12  1 
11  3 15  1 
12  3 20  2 
13  3 25  3 
Powiązane problemy