2017-12-06 15 views
5

私はこの質問のバリエーションを見つけましたが、モジュロが使用される可能性があることは分かっていますが、条件が満たされたときにシーケンスを繰り返す方法

私はIDと秒による一連の観測をしています。 idによる累積時間(秒)が5秒を超えると、カウントを再開したいと思います。誰かが私にこの質問にdplyrで答えるのを手伝ってもらえますか?

オリジナル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 

望ましい結果

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 

編集応答のための

おかげで、昨日、私は与えられた解決策のいくつかの問題に遭遇しました。

このデータセットでは、コードが一部のインスタンスで動作します。使用さ

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

ソリューション:

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

アウトカム

# 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 

何が起こることは最初の二つの観察のために、それは正しく1つのインスタンスとしてそれを数えることです。それが第3および第4の観察に達するとき、これらの2つの事例の間を本質的に時間がないので、これは2つの観察としてカウントされるべきである。

正しい出力:(:私は今のところ、ここで、このアプローチを残しているものの、alistaireさんbrilliant answerによって、私は間違って証明されたEDIT)が、私は、これは一つだと思い

# 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

idグループ#1では、valが12から15になるとリセットが変更されますが、グループ#3ではリセットされません。私の答えは、最初のグループのロジックと一貫しています。 –

+2

@JosephWoodこれは、その時点でのグループ#1では、リセットのための参照は「10」であり、グループ#3では「12」 – duckmayr

+0

@duckmayrなので、この点を明確にしてくれたことに感謝します(つまり、参照点は単に以前の参照の差異が5以上である値)であることを示す。私の今削除された答えは素朴で、誤ってそのグループの最初の値だけを参照していました。 –

答えて

4

を:私たちは

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

次の関数を作成し、dplyrgroup_by()を使用してidことによってそれを適用することができます合計が0であるかどうかをcumsumと呼び出すと、リセット数が返されます。

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 

または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 

編集に関しては、問題は、差分のいくつかは、リセットを見るために数えているものと同じである、0であるということです。最も簡単な解決策は、リセット値としてNAの代わりにゼロを使用することである:すべての値は、実際NAであるかのように最終的に、このアプローチは、しかし、あまりにも、限界に直面し

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 

、それが同様に増加します。より堅牢なソリューションは、各繰り返しから2つの要素のリストを返すことです.1つはリセットで合計し、もう1つはリセットカウントになります。

​​

合計は少し愚かに見えますが、あなたは差分を見れば、それは実際には正しいです:これは、しかし、実現するためのより多くの仕事です。

+0

その答えに感謝します。それはすばらしいです。しかし、あなたは 'Reduce'機能について説明できますか?私はその部分を理解していない。 – DataTx

+2

'Reduce'はバイナリ(2変数)関数をベクトルの連続する項に適用します。 'Reduce(\' + \ '、1:4)'は 'sum(1:4)'と同じですが、 '(((1 + 2)+ 3)+ 4) 'となる。しかし、 'accumulate = TRUE'を追加すると、中間語が節約されるので、' Reduce(\ '+ \、1:4、accumulate = TRUE)'は 'cumsum(1:4) 'と同じです。リスト(データフレームを含む)をうまく扱う。 'Reduce(\' + \ '、mtcars)'を実行し、複雑なバイナリ関数を受け入れます。 'init'が与えられた場合、それはベクトルの最初の値として使われます。 – alistaire

+0

解決策に問題が発生しました。あなたがそれを修正する方法を知っていれば、私はどんな入力にも感謝します。ありがとうございました。 – DataTx

2

は私が間違っている可能性があります各行のresetの値は、前の行で何が起こったかによって決まるため、ループが実際に必要なインスタンスの数です。私は、Joseph Woodがこれより賢い何かを思い付くことを期待していますが、ここでは、要求通りにdplyrを利用する素朴なアプローチです。あなたがリセットすることができ、(あなたが好む場合、またはpurrr::accumulate)あなたはaccumulate = TRUEReduceを使用する場合は

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 
関連する問題