bugfix> r > 投稿

私は多くの人々のためのトランザクションのリストを持っています。特定の人がトランザクションの合計の特定のしきい値を超えたときを見つけたいと思います。

これは私がすでにやったことの例です: データセットの例:

df <- data.frame(name = rep(c("a","b"),4), 
    dates = seq(as.Date("2017-01-01"), by = "month", length.out = 8), amt = 11:18)
setorderv(df, "name")

これにより、次のデータフレームが得られます

 name      dates amt
1    a 2017-01-01  11
3    a 2017-03-01  13
5    a 2017-05-01  15
7    a 2017-07-01  17
2    b 2017-02-01  12
4    b 2017-04-01  14
6    b 2017-06-01  16
8    b 2017-08-01  18

次に、累積和を見つけるために次のコードを書きました

df$cumsum <- ave(df$amt, df$name, FUN = cumsum)

これにより、次のデータフレームが得られます。

 name      dates amt cumsum
1    a 2017-01-01  11     11
3    a 2017-03-01  13     24
5    a 2017-05-01  15     39
7    a 2017-07-01  17     56
2    b 2017-02-01  12     12
4    b 2017-04-01  14     26
6    b 2017-06-01  16     42
8    b 2017-08-01  18     60

今、私は各人が20と40を越えたときを知りたいです。これを見つけるために次のコードを書きました。

names <- unique(df$name)    
for (i in seq_along(names)){
    x1 <- Position(function(x) x >= 20, df$cumsum[df$name == names[i]])
    x2 <- Position(function(x) x >= 40, df$cumsum[df$name == names[i]])
    result_df[i,] <- c(df$name[i], 
                         df[df$name == names[i],2][x1],
                         df[df$name == names[i],2][x2])
}

このコードは、しきい値を超えた場所をチェックし、行番号を変数に保存します。次に、2番目の列のその行から値を抽出し、別のデータフレームに格納します。

問題は、このコードが本当に遅いことです。データセットには200,000人を超え、1,000万行を超えています。このコードは、最初の50人のユーザーで実行するのに約25秒かかります。つまり、データセット全体で約30時間かかる可能性があります。

これを行うより速い方法はありますか?

回答 2 件
  • dplyrを使用すると、個人ごとにグループ化でき、cumsumが上記の場合にフィルタリングできます>20以上>40、次にslice(1)を使用して、1人ごとに最初の関連行を選択します。ループよりもずっと速いはずです。

    df <- read.table(text = '
    name      dates amt cumsum
    a 2017-01-01  11     11
    a 2017-03-01  13     24
    a 2017-05-01  15     39
    a 2017-07-01  17     56
    b 2017-02-01  12     12
    b 2017-04-01  14     26
    b 2017-06-01  16     42
    b 2017-08-01  18     60', header = T)
    df %>% 
      group_by(name) %>% 
      filter(cumsum > 20) %>% 
      slice(1)
           name      dates   amt cumsum
          <fctr> <fctr> <int>  <int>
    1      a 2017-03-01    13     24
    2      b 2017-04-01    14     26
    df %>% 
      group_by(name) %>% 
      filter(cumsum > 40) %>% 
      slice(1)
       name      dates   amt cumsum
      <fctr>     <fctr> <int>  <int>
          a 2017-07-01    17     56
          b 2017-06-01    16     42
    
    

    もちろん、後でこれらのデータフレームを再バインドし、直接調整することもできます。これは役立ちますか?

  • データテーブルの使用は次のようになります。

    library(data.table)
    dt <- data.table(df[order(df$dates), ])
    dt[ ,':='(minDate20 = min(dates[cumsum(amt) > 20]), minDate40 = min(dates[cumsum(amt) > 40])), by = .(name)]
    
    dt[dates == minDate20, ]
    dt[dates == minDate40, ]
    
    

あなたの答え