3 Case 3: 特定期間の指定・切り出し問題
3.1 Question 3
以下のようなデータがあります(データの生成はこのページの末尾に記載):
knitr::kable(head(df_3))
| stamp | id | item | value |
|---|---|---|---|
| 2018-01-01 00:03:40 | 1000044 | item_2 | 500 |
| 2018-01-01 00:09:40 | 1000188 | item_2 | 500 |
| 2018-01-01 00:14:07 | 1000251 | item_1 | 100 |
| 2018-01-01 00:17:58 | 1000212 | item_1 | 100 |
| 2018-01-01 00:24:31 | 1000195 | item_1 | 100 |
| 2018-01-01 00:30:52 | 1000170 | item_1 | 100 |
このデータで、「2018年1月16日から1月31日までの期間とそうでない期間でデータを分けて分析したい」のですが、どのようにしたらいいでしょうか?
3.2 Answer
こんな感じでできます:
library(tidyverse)
library(lubridate)
library(gridExtra)
# intervalオブジェクトを生成
target_interval <- interval(
start = ymd("2018-1-16"),
end = ymd("2018-1-31")
)
# 区間を利用して整理。一定期間とそれ以外で検証
# filterではなくif_elseでmutateする
# 3つ以上ならcase_whenでパターンを準備すればOK
df_3_interval_comp <- df_3 %>%
mutate(target = if_else(
stamp %within% target_interval, "target", "other"
)) %>%
mutate(hour = hour(stamp)) %>%
group_by(hour, target) %>%
summarise(
n_buy = n(),
value_buy = sum(value),
mean_buy = mean(value)
)
# 比較用の可視化
p_int_comp_date <- ggplot(df_3_interval_comp)
p_int_comp_date1 <- p_int_comp_date +
geom_boxplot(aes(x = target, y = mean_buy, color = target)) +
coord_flip()
p_int_comp_date2 <- p_int_comp_date +
geom_line(aes(x = hour, y = mean_buy, color = target))
grid.arrange(p_int_comp_date1, p_int_comp_date2, nrow = 2)

3.3 解説
ここでは、lesson2で準備したdf_logという仮想ログデータを用いて、一定期間での集計をまとめることを目指します。その中でlubridateのintervalオブジェクトと%within%演算子を説明します。
以下、手順を追って説明します。
3.3.1 パッケージ読み込み
ここで使用するパッケージを読み込みます:
library(tidyverse)
library(lubridate)
library(gridExtra)
3.3.2 特定期間を格納したオブジェクトを準備
lubridateパッケージにはintervalというclassが用意してあり、これを用いると「期間(interval)」を作成することができます。詳しくは参考資料のリンクを参照してください:
# intervalオブジェクトを生成
target_interval <- interval(
start = ymd("2018-1-16"),
end = ymd("2018-1-31")
)
これを作成すると、以降の作業が楽になります。
3.3.3 特定期間に含む・含まないという変数を準備
ここがメインです:
df_3_interval_comp <- df_3 %>%
# ここがポイント
mutate(target = if_else(
stamp %within% target_interval, "target", "other"
)) %>%
mutate(hour = hour(stamp)) %>%
group_by(hour, target) %>%
summarise(
n_buy = n(),
value_buy = sum(value),
mean_buy = mean(value)
)
ここのmutateで変数を作成しているところがポイントで、lubridateパッケージには%within%という演算子が準備されています。これを用いると、「左辺のデータは右辺のintervalに含まれるか」を評価します。
そこで、これを利用して「期間内ならtarget、期間外ならotherというようにif_elseで処理しています。これでデータセット内で期間内・期間外を識別できました。あとは集計処理をしています。
ここまでの処理で出来上がるものは、以下のとおりです:
knitr::kable(head(df_3_interval_comp))
| hour | target | n_buy | value_buy | mean_buy |
|---|---|---|---|---|
| 0 | other | 306 | 108500 | 354.5752 |
| 0 | target | 129 | 43100 | 334.1085 |
| 1 | other | 293 | 112300 | 383.2765 |
| 1 | target | 111 | 41000 | 369.3694 |
| 2 | other | 307 | 112300 | 365.7980 |
| 2 | target | 127 | 52600 | 414.1732 |
3.3.4 比較用の可視化
ここは特に問題ないでしょう:
# 比較用の可視化
p_int_comp_date <- ggplot(df_3_interval_comp)
p_int_comp_date1 <- p_int_comp_date +
geom_boxplot(aes(x = target, y = mean_buy, color = target)) +
coord_flip()
p_int_comp_date2 <- p_int_comp_date +
geom_line(aes(x = hour, y = mean_buy, color = target))
grid.arrange(p_int_comp_date1, p_int_comp_date2, nrow = 2)

3.3.5 補足
これらはlubridateパッケージのほんの一部です。ログデータを扱う際には必須といえるパッケージなので、ぜひ他の関数などもチェックしてください。
このCaseで使用しているデータ(df_3)を生成したコードは以下のとおりです:
library(lubridate)
library(tidyverse)
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
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)
# 置換用の名前付きベクトルを作成
# 置換前文字列がnames, 置換後の文字列がベクトルの内容となるように
pat <- as.character(list_price)
names(pat) <- list_item
# itemを正規表現で置換して数値に変換し、列として追加
df_3 <- df_log %>%
# 対応する項目を一気に置換して整数型へ変換
# この変換方法については?stringr::str_replace_allを参照
mutate(value = str_replace_all(item, pat) %>%
as.numeric())
readr::write_csv(df_log, path = "df_log.csv")
3.4 参考資料
- ログデータ処理で始めるlubridate入門
ログデータの生成から加工を通じてlubridateパッケージを紹介しています。この内容もこの内容から一部抜粋しました
- Utilities for creation and manipulation of Interval objects
intervalオブジェクトを作成・操作する関数の説明
- Interval class
intervalクラスの説明
- Tests whether a date or interval falls within an interval
%within%演算子の説明