lesson: 2 仮想ログデータ生成
ここでは、これから使用していくための仮想ログデータを作成します。またこのログデータ作成を通じて、lubridateのperiodpオブジェクトについて紹介します。
2.1 想定シナリオ
準備するログデータは以下のようなシナリオとします:
- あるサービスのアイテム購入履歴ログデータ
- 2018年1月1日から50日間のデータ
- 購入は24時間常に発生
- 発生確率は(めんどうなので)一様とする
- アイテム名はitem1-5の5種類
- 価格は順に100, 500, 1000, 2000, 5000
- 購入発生比は順に100, 50, 10, 5, 2
- 会員IDは1000001:1000300の300名
- 何度でも購入可能
- ログデータの設計は以下の通り
- stamp: タイムスタンプ
- id: 会員ID
- value: 購入金額
- item: 項目ラベル
- 今回は50日間で10000件の購入があったと仮定
2.2 データ生成
上記シナリオにあうようにデータを生成します。
2.2.1 Rコード
必要なパッケージを読み込みます。
library(tidyverse)
#> ─ Attaching packages ──────────────────── tidyverse 1.2.1 ─
#> ✔ ggplot2 2.2.1 ✔ purrr 0.2.4
#> ✔ tibble 1.4.2 ✔ dplyr 0.7.4
#> ✔ tidyr 0.8.0 ✔ stringr 1.3.0
#> ✔ readr 1.1.1 ✔ forcats 0.3.0
#> ─ Conflicts ────────────────────── tidyverse_conflicts() ─
#> ✖ lubridate::as.difftime() masks base::as.difftime()
#> ✖ lubridate::date() masks base::date()
#> ✖ dplyr::filter() masks stats::filter()
#> ✖ lubridate::intersect() masks base::intersect()
#> ✖ dplyr::lag() masks stats::lag()
#> ✖ lubridate::setdiff() masks base::setdiff()
#> ✖ lubridate::union() masks base::union()
library(lubridate)
パラメータを設定します。
start <- "2018-1-1 00:00:00" #開始日
n <- 10000 # 購入件数
duration_days <- 50 # ログの期間(日数)
list_price <- c(100, 500, 1000, 2000, 5000) # アイテムの価格リスト
list_item <- paste("item", 1:length(list_price), sep = "_") # アイテムリスト
list_item_p <- c(100, 50, 10, 5, 2) # 発生比
list_id <- 1000001:1000300 # 会員id
ログデータのdata.frameを生成します。今回はdf_log
というオブジェクトとします。
df_log <- data.frame(
# タイムスタンプを作成
# 開始日時を生成
stamp = ymd_hms(start) +
# 0-50までの整数からランダムに10000件生成し、それを日数データに変換して足す
days(sample(0:duration_days, n, replace = TRUE)) +
# 0-23までの整数からランダムに10000件生成し、それを時間データに変換して足す
hours(sample(0:23, n, replace = TRUE)) +
# 0-59までの整数からランダムに10000件生成し、それを分データに変換して足す
minutes(sample(0:59, n, replace = TRUE)) +
# 0-59までの整数からランダムに10000件生成し、それを病データに変換して足す
seconds(sample(0:59, n, replace = TRUE)),
# 会員IDをランダムに生成
id = sample(list_id, n, replace = TRUE),
# アイテム名をランダムに生成
item = sample(list_item, n, replace = TRUE, prob = list_item_p)
) %>%
# ログデータっぽく、タイムスタンプで並べ替える
arrange(stamp)
ここまでの処理で、以下のようなデータができます:
knitr::kable(sample_n(df_log, 10))
stamp | id | item | |
---|---|---|---|
3954 | 2018-01-21 08:07:26 | 1000057 | item_2 |
5733 | 2018-01-30 12:11:20 | 1000131 | item_1 |
4285 | 2018-01-23 00:38:05 | 1000088 | item_2 |
1881 | 2018-01-10 22:09:17 | 1000055 | item_1 |
7720 | 2018-02-09 08:39:21 | 1000188 | item_2 |
252 | 2018-01-02 07:19:54 | 1000090 | item_1 |
5229 | 2018-01-27 22:40:43 | 1000110 | item_2 |
2796 | 2018-01-15 11:47:17 | 1000042 | item_1 |
2380 | 2018-01-13 09:58:16 | 1000243 | item_1 |
4921 | 2018-01-26 06:52:28 | 1000086 | item_1 |
アイテム名(item)に対応する価格(value)を当てます。
# 置換用の名前付きベクトルを作成
# 置換前文字列がnames, 置換後の文字列がベクトルの内容となるように
pat <- as.character(list_price)
names(pat) <- list_item
# itemを正規表現で置換して数値に変換し、列として追加
df_log <- df_log %>%
# 対応する項目を一気に置換して整数型へ変換
# この変換方法については?stringr::str_replace_allを参照
mutate(value = str_replace_all(item, pat) %>%
as.numeric())
生成したdf_logの一部を表示します:
knitr::kable(sample_n(df_log, 10))
stamp | id | item | value | |
---|---|---|---|---|
7530 | 2018-02-08 11:16:31 | 1000172 | item_1 | 100 |
3646 | 2018-01-19 20:16:06 | 1000243 | item_1 | 100 |
4070 | 2018-01-21 21:54:21 | 1000063 | item_3 | 1000 |
3034 | 2018-01-16 16:16:03 | 1000143 | item_3 | 1000 |
4262 | 2018-01-22 21:01:11 | 1000078 | item_1 | 100 |
2394 | 2018-01-13 11:28:59 | 1000183 | item_1 | 100 |
153 | 2018-01-01 19:10:26 | 1000083 | item_1 | 100 |
9742 | 2018-02-19 16:02:05 | 1000024 | item_2 | 500 |
7773 | 2018-02-09 15:48:54 | 1000008 | item_1 | 100 |
8060 | 2018-02-11 02:55:54 | 1000156 | item_2 | 500 |
これで仮想ログデータが完成です。一応csvに出力しときます:
readr::write_csv(df_log, path = "df_log.csv")
2.2.2 解説
ここでのポイントはタイムスタンプ(stamp)の生成です。今回は開始日時(2018-1-1 00:00:00)から50日間でランダムに生成させる必要があります。そこで利用したのがdays()
などのperiodオブジェクトを生成する関数です。
periodオブジェクトは、lubridateパッケージで準備している独自のクラスで、時間的な区間です。
# 「1日分」のperiodを生成
# 引数には整数値を指定
days(1)
#> [1] "1d 0H 0M 0S"
# 負の整数だと「マイナス1日間」となる
days(-1)
#> [1] "-1d 0H 0M 0S"
# 足し引きもできる
days(3) - days(1)
#> Note: method with signature 'Period#ANY' chosen for function '-',
#> target signature 'Period#Period'.
#> "ANY#Period" would also be valid
#> [1] "2d 0H 0M 0S"
days(3) + days(-2)
#> [1] "1d 0H 0M 0S"
なぜこのようなことができるかというと、periodオブジェクトは時間的な区間を数値的に置き換えて保持しているからです。なので、日時の加算・減算ができるようになります:
# 日付にperiodを加算
# この場合は日付型(Date)になる
ymd("20180224") + days(3)
#> [1] "2018-02-27"
# 日時にperiodを加算
# この場合はPOSIXct型になる
ymd_hms("20180224 150000") + days(3)
#> [1] "2018-02-27 15:00:00 UTC"
また、ベクトルでの加算・減算も可能です。ベクトルの長さが揃っていない場合はリサイクルされます:
ymd("20180224") + days(1:3)
#> [1] "2018-02-25" "2018-02-26" "2018-02-27"
このperiodオブジェクトはdays以外にも時間(hours()
)やミリ秒(miliseconds()
)など準備してあります。これをうまく活用し、開始日時(2018-1-1 00:00:00)へランダムな日数、時間数、秒数を足し算してタイムスタンプを作成しました。
2.3 参照
- Create a period object
days()
などの関数の解説