Marketechlabo

Rでデータセットの抽出(行の抽出、並べ替え、サンプリング、分割)

概要

前のページではデータフレーム、data.tableの列(変数)の処理について解説したが、今度は行の抽出、並べ替え、サンプリング、分割といった行の処理についてまとめる。

行の削除(抽出)

データフレーム

ad_log <- ad_log[ad_log$imp>1000 & ad_log$click<10, , drop=F]
ad_log <- with(ad_log, ad_log[imp>1000 & click<10, , drop=F])

上下は同じ。with()関数はバッチの中でも使えるので便利。 データフレームの抽出・絞り込みでは第3添字にdrop=FALSEを付けること! 行列の添え字にdrop=FALSEを付けないと1行(列)のみマッチの場合にベクトルとして返す。そうなるとデータフレームを想定してその後の処理にrbind()をしていたのができなくなるなど、行列処理に思わぬ不具合をきたすことになる。 drop=FALSEを付けて1行n列の行列を返すように。 ただしtapply()などで使う1列取得の際は付けてはならない。ベクトルとして処理する必要がある。

data.table

n_purchase < 10の行を抽出

# dtplyrのパイプを使う場合
x.dt |>
  lazy_dt() |>
  filter(n_purchase < 10) |>
  as.data.table() -> x.dt
# 添字を使う場合
x.dt[n_purchase < 10,] -> x.dt

n_purchase < 10の行を削除

# dtplyrのパイプを使う場合
x.dt |>
  lazy_dt() |>
  filter(!(n_purchase < 10)) |>
  as.data.table() -> x.dt
# 添字を使う場合
x.dt[!(n_purchase < 10),] -> x.dt

複数条件(AND)

dtplyrのパイプを使う場合

# dtplyrのパイプを使う場合
x.dt |>
  lazy_dt() |>
  filter(n_purchase < 10, interval_latest_purchase > 30) |>
  as.data.table() -> x.dt
# 添字を使う場合
x.dt[n_purchase < 10 & interval_latest_purchase > 30,] -> x.dt

filter()を使う場合でも&で条件を結合していい。

複数条件(OR)

dtplyrのパイプを使う場合

# dtplyrのパイプを使う場合
x.dt |>
  lazy_dt() |>
  filter(n_purchase < 10 | interval_latest_purchase > 30) |>
  as.data.table() -> x.dt
# 添字を使う場合
x.dt[n_purchase < 10 | interval_latest_purchase > 30,] -> x.dt

いずれも|で条件を結合する。

行の並べ替え(ソート)

データフレーム/行列

行列を第n列をキーに昇順に並び替え

x[order(x[,n]), ]

行列を第n列をキーに降順に並び替え

x[order(x[,n], decreasing=T),]

カラムimp(最優先), click(次に優先)をキーに昇順に並び替え

ad_log <- ad_log[order(ad_log$imp, ad_log$click),]

下記と同じ

ad_log <- with(ad_log, ad_log[order(imp, click),])

data.table

カラムn_purchase(最優先), interval_latest_purchase(次に優先)をキーに昇順に並び替え

# dtplyrのパイプを使う場合
x.dt |>
  lazy_dt() |>
  arrange(n_purchase, interval_latest_purchase) |>
  as.data.table() -> x.dt
# 添字を使う場合
x.dt[order(n_purchase, interval_latest_purchase)] -> x.dt

カラムn_purchaseをキーに降順(最優先)、interval_latest_purchaseをキーに昇順(次に優先)に並び替え

# dtplyrのパイプを使う場合
x.dt |>
  lazy_dt() |>
  arrange(-n_purchase, interval_latest_purchase) |>
  as.data.table() -> x.dt
# 添字を使う場合
x.dt[order(-n_purchase, interval_latest_purchase),] -> x.dt

arrange()を使ってfactorに対して降べきの順を指定する場合にはマイナス符号を使えず、列名をdesc()で囲んでarrange(desc(列名))のようにする。

サンプリング

単純なサンプリング

それなりに大きいデータではサンプリングして分析することになる。 Rのbaseではsample()関数、dplyrをインストールしている場合は便利なslice_sample()関数が使える。 (データフレーム、data.table共通)

使い方は

  • sample(母集団のサイズ, サンプルサイズ)
  • 母集団のデータフレーム |> slice_sample(n = サンプルサイズ)
  • 母集団のデータフレーム |> slice_sample(prop = サンプル率)

である。

サンプルの大きさ(行数)を指定する

データセットclick_logから1000行を抽出する場合

sample()関数では

# set.seed(1234)
row.sampled <- sample(nrow(click_log), 1000)
click_log.sampled <- click_log[row.sampled, , drop=F]

slice_sample()関数を使うと

# set.seed(1234)
click_log |> slice_sample(n = 1000) -> click_log.sampled

となる。

サンプルの抽出率を指定する

データセットclick_logから抽出率20%で無作為抽出する場合、

sample()関数では

nr <- nrow(click_log)
# set.seed(1234)
row.sampled <- sample(nr, floor(nr * 0.2))
click_log.sampled <- click_log[row.sampled, , drop=F]

slice_sample()関数を使うと

# set.seed(1234)
click_log |> slice_sample(prop = 0.2) -> click_log.sampled

確かにこちらのほうが簡単である。

IDでサンプリング

ユーザマスタ-/行動履歴などデータフレームが分かれている場合、行動履歴もユーザマスタのIDに基づいてサンプリングすることになる。 user_idを抽出率20%で無作為抽出し、そのuser_idに基づいてclick_logを抽出する。

  • マスタのオブジェクト:user
  • データのオブジェクト:click_log
  • 連携しているID(マスタのプライマリキー):user_id

という場合

nr <- nrow(user)
# set.seed(1234)
row.sampled <- sample(nr, floor(nr * 0.2))
user.sampled <- user[row.sampled,]
user_id.sampled <- user.sampled$user_id
click_log.sampled <- click_log[is.element(click_log$user_id, user_id.sampled), , drop=F]

user_idを列名にしている場合

nr <- nrow(user)
# set.seed(1234)
row.sampled <- sample(nr, floor(nr * 0.2))
user_id.sampled <- rownames(user)[row.sampled]
click_log.sampled <- click_log[is.element(click_log$user_id, user_id.sampled), , drop=F]

click_logテーブルだけで抽出してしまう場合

# set.seed(1234)
user_id.unique <- unique(click_log$user_id)
user_id.sampled <- sample(user_id.unique, floor(length(user_id.unique) * 0.2))
click_log.sampled <- click_log[is.element(click_log$user_id, user_id.sampled), , drop=F]

分割

学習用と検証用とでデータセットを分割する

nr <- nrow(dat)
# set.seed(1234)
row.train <- sample(nr, floor(0.5 * nr))
dat.train <- dat[row.train,]
dat.test <- dat[-row.train,]