メモ:時系列とか連番のデータを補完するときはtidyrのcomplete()とfull_seq()が便利そう

追記:これは「補間」とは言わないかも…。期待外れだったらすみません。


前にこんな記事を書きました。

で、これはたまたま毎分データがあったからよかったんですが、もっととびとびのデータの時どうするんだろう?と思ってたらtidyrパッケージのcomplete()full_seq()が便利そうだったのでメモ。

おさらい

complete()

complete()は、分かりづらいんですが、暗黙の欠損値を明示的な欠損値(NAとか0とか)して出現させる関数です。ヘルプにもそう書かれています。

Turns implicit missing values into explicit missing values.

これはどういうことかというと、例えばこういうデータがあったとき、何を思うでしょうか。

library(tidyverse)

d <- tribble(
  ~name,    ~lang, ~chottodekiru,
    "A",      "R",          TRUE,
    "A", "Python",          TRUE,
    "B",      "R",          TRUE,
    "B", "Python",          TRUE,
    "C",      "R",          TRUE
)

Cさんが果たしてPythonがチョットデキル人なのかどうか、このデータからはわかりません。これをはっきりさせるためにcomplete()を使うと、こういう感じにchottodekiru列をNAで埋めたデータをつくってくれます。

complete(d, name, lang)
#> # A tibble: 6 × 3
#>    name   lang chottodekiru
#>   <chr>  <chr>        <lgl>
#> 1     A Python         TRUE
#> 2     A      R         TRUE
#> 3     B Python         TRUE
#> 4     B      R         TRUE
#> 5     C Python           NA
#> 6     C      R         TRUE

NA以外のもので埋めたいときはfill引数にリストを指定します。例えば、fill = list(chottodekiru = FALSE)を指定するとchottodekiru列ではFALSEが欠損値を表すものとしてあてがわれます。

full_seq()

full_seq()は、いい感じに数値列を補完してくれる関数です。

full_seq(c(1, 2, 4, 5, 10), period = 1)
#>  [1]  1  2  3  4  5  6  7  8  9 10

DatePOSIXctにも使うことができます。ただしhmsとかdifftimeには使えないので注意。periodに指定している数値の単位は秒です。

x <- c(lubridate::now(), lubridate::now() - lubridate::hours(1))
full_seq(x, period = 600)
#> [1] "2017-04-04 20:30:02 JST" "2017-04-04 20:40:02 JST"
#> [3] "2017-04-04 20:50:02 JST" "2017-04-04 21:00:02 JST"
#> [5] "2017-04-04 21:10:02 JST" "2017-04-04 21:20:02 JST"
#> [7] "2017-04-04 21:30:02 JST"

組み合わせる。

complete()は、すでにデータ中に登場している組み合わせしか埋められませんでしたが、full_seq()complete()の中で使うとそれをさらに拡張できます。例えば、毎年取られるデータみたいなのがあって、2002年の分だけが欠けているとします。

d <- tribble(
  ~year, ~value,
   1999,      1,
   2000,      2,
   2001,      3,
   2003,      5
)

このとき、full_seq()complete()の中で使うとかけている年を補完して欠損値で埋めることができます。

complete(d, year = full_seq(year, 1))
#> # A tibble: 5 × 2
#>    year value
#>   <dbl> <dbl>
#> 1  1999     1
#> 2  2000     2
#> 3  2001     3
#> 4  2002    NA
#> 5  2003     5

時系列データを埋める

何か毎分行われる処理があって、エラーが起きた時のタイムスタンプだけを記録したデータがあるとします。

set.seed(12)
d <- data.frame(
  timestamps = lubridate::today() +
    lubridate::minutes(ceiling(runif(30, min = 0, max = 24 * 60)))
)
d
#>             timestamps
#> 1  2017-04-04 01:40:00
#> 2  2017-04-04 19:38:00
#> 3  2017-04-04 22:38:00
#> 4  2017-04-04 06:28:00
#> 5  2017-04-04 04:04:00
#> 6  2017-04-04 00:49:00
#> 7  2017-04-04 04:18:00
#> 8  2017-04-04 15:24:00
#> 9  2017-04-04 00:33:00
#> 10 2017-04-04 00:12:00
...
ggplot(d, aes(timestamps, 1)) + geom_point()

f:id:yutannihilation:20170404214522p:plain

で、これのエラー率みたいなのを計算したい。というとき、とりあえず毎分データを埋めてみてRcppRollとかを使う、という手があります。

d %>%
  # 元のデータにはerror=TRUEを入れておく
  mutate(error = TRUE) %>%
  # full_seqで補完されるデータにはerror=FALSEを入れる
  complete(timestamps = full_seq(timestamps, 60), fill = list(error = FALSE)) %>%
  # 前後5個づつのデータの平均(前後10分のエラー率)を計算
  mutate(error_rate = RcppRoll::roll_mean(error, n = 10, fill = NA)) %>%
  # プロットする
  ggplot(aes(timestamps, error_rate)) +
  geom_line()

f:id:yutannihilation:20170404215243p:plain

感想

とはいえこの例だとあんまり効率的じゃない感じはするので、なんかもっといいやり方ありますよね…。誰かいい感じの方法を教えてください。とりあえずcomplete()full_seq()の使い方のメモということで。