Rでデータクリーニング、変数の生成

この記事は連載「Rのデータ前処理」の全 4 ページ中 3 ページ目です。

このあたりから処理がアドホック寄りになるので、data.tableを使う場合でもパイプ(dplyr)を使って一度に実行するのではなく添字記法を使って1ステップずつ進めていくといい(1行ずつ実行する場合は添字記法を使った方がコード量が少なくて済む)。

行の削除(抽出)

行の抽出

行の並べ替え(ソート)

行の並べ替え

変数の加工(データフレーム/data.frame共通)

標準化(scale)

指定した変数を標準化(平均=0、分散=1のスケールに圧縮/拡大)する。
scale()関数を使う。

# データフレーム
x.dt$purchase_amount <- scale(x.dt$purchase_amount)
# data.table
x.dt[,purchase_amount := scale(purchase_amount)]

複数列をまとめてやるにはmutate_at()を使って

x.dt %>%
  mutate_at(vars(n_purchase, purchase_amount), funs(scale)) -> x.dt

などとする。

mutate_at()の使い方はこちらを参照

欠損値処理

NAをいずれかの列に含む行全体を削除(いわゆるリストワイズ法)→na.omit()関数を使う

# データフレーム
x.df <- na.omit(x.df)
# data.table
x.dt <- na.omit(x.dt)
# パイプを使う場合
x.dt %>%
  na.omit %>% ...

特定の値を代入する(以下の例では平均値)

# データフレーム
x.dt$n_purchase[is.na(x.dt$n_purchase)] <- mean(x.dt$n_purchase, na.rm = T)]
# data.table
x.dt[is.na(n_purchase), n_purchase := mean(n_purchase, na.rm = T)]

一般的な欠損値処理についてはこのあたりを参照

data.tableのままで(data.tableの性質を保持したままで)これらの処理をすることは不可能だが、データフレームに対しては実行できる。そのためdata.tableオブジェクトをそのままこれらの関数に渡し、処理結果のデータフレームをas.data.table()でdata.table化する。

外れ値処理

以下の流れになる。

  1. 各列に対する外れ値を検出
  2. 外れ値を含む行を除外する→行の削除

各列に対する外れ値の検出はdata.tableに固有の方法はない。
スミルノフ・グラブス検定grubbs.test() {outliers}などで外れ値の閾値を検出する。
たとえば

> grubbs.test(x.dt$n_purchase_shoes)

    Grubbs test for one outlier

data:  x
G = 6.55290, U = 0.56187, p-value = 3.242e-12
alternative hypothesis: highest value 50 is an outlier

という結果が出たら、

x.dt %>% filter(n_purchase_shoes != 50) -> x.dt

のようにする。これを各列について実行する。

変数の追加

  • オリジナルのカテゴリでは使いにくく、分析用にカテゴリを組み替えたい
  • 連続量をグループに分けたい
  • 欠損値を特定の値に置換したい
  • 行番号の列を作る

アドホックに変数を作っていきたい場合は添字指定がいい。
まとめて変数を定義する場合にmutate()を使う。

コーディング

# データフレーム
page$monthly <- ifelse(is.na(page$monthly), page$daily*30, page$monthly)
page[page$click==0,"active"] <- 0
page[page$click>0,"active"] <- 1

ifelse()はスカラしか返さないので主にデータフレームのコーディングで使う。
それ以外の処理の条件分岐では使わないほうがいい
特にそれ自体がリストとなるPOSIXltは強制的にdoubleに置換されるので日時の処理では使ってはならない**。

# データフレーム
x <- ifelse(is.na(date_a), as.POSIXct(Sys.Date()), as.POSIXct(date_a))
class(x)
[1] "numeric"

http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q1072289430

NAさえなければ

ifelse(x>10, y, z)
(x>10)*y + (x<=10)*z

上記は同じ結果になる。

オリジナルのカテゴリでは使いにくく、分析用にカテゴリを組み替える

以下は地域名として
prefecture(都道府県)、city(市)、ward(区)という3個の変数があり、
地域名に基づいて変数を作る例である。
prefectureが東京以外の場合はcityがあるが、東京の場合はcityがないのでwardを使いたい。

# prefecture == 'Tokyo'の場合ward、それ以外の場合cityを採用する
## データフレーム
x.dt$local <- as.factor(ifelse(x.dt$prefecture == 'Tokyo', x.dt$ward, x.dt$city))
## data.table
x.dt[,local := as.factor(ifelse(prefecture == 'Tokyo', ward, city))]
# Prefectureが'Tokyo', 'Osaka', 'Aichi'の場合、major_prefecture == TRUEにする
## データフレーム
x.dt$major_prefecture <- x.dt$prefecture %in% c('Tokyo', 'Osaka', 'Aichi')
## data.table
x.dt[,major_prefecture := prefecture %in% c('Tokyo', 'Osaka', 'Aichi')]
# major_prefectureを反転
## データフレーム
x.dt$minor_prefecture <- !x.dt$major_prefecture
## data.table
x.dt[,minor_prefecture := !major_prefecture]
# 特定のprefecture, city, wardの場合にフラグを立てる
## データフレーム
x.dt$capital <- x.dt$prefecture == 'Osaka' & x.dt$city == 'Osaka' & x.dt$ward == 'Kita-ku'
## data.table
x.dt[,capital := prefecture == 'Osaka' & city == 'Osaka' & ward == 'Kita-ku']

mutate()でまとめて実行する場合

# data.table
x.dt %>%
  mutate(
    # prefecture == 'Tokyo'の場合ward、それ以外の場合cityを採用する
    local = as.factor(ifelse(prefecture == 'Tokyo', ward, city)),
    # Prefectureが'Tokyo', 'Osaka', 'Aichi'の場合、major_prefecture == TRUEにする
    major_prefecture = prefecture %in% c('Tokyo', 'Osaka', 'Aichi'),
    # major_prefectureを反転
    minor_prefecture = !major_prefecture,
    # 特定のprefecture, city, wardの場合にフラグを立てる
    capital = prefecture == 'Osaka' & city == 'Osaka' & ward == 'Kita-ku'
  ) -> x.dt

連続量をグループに分ける

cut()関数を使う。

年齢を「0−9」「10-19」「20-29」「30-39」「40-49」「50-59」「60-」の0歳区切りで分割し、ラベルを’~9′, ‘~19’, ‘~29’, ‘~39’, ‘~49’, ‘~59’, ’60~’にする。

# データフレーム
x.df$age_grp <- cut(x.df$age, breaks = c(0:6*10, Inf), include.lowest = T, labels = c(paste0('~', 1:6*10-1), '60~'), ordered_result = T)
# data.table
x.dt[,age_grp:=cut(age, breaks = c(0:6*10, Inf), include.lowest = T, labels = c(paste0('~', 1:6*10-1), '60~'), ordered_result = T)]

cut()の使い方はcut(age, breaks = 下限と上限を含む区切り点のベクトル, include.lowest = T, labels = ラベル, ordered_result = T)と覚えておくといい。breaksの長さ - 1 = labelsの長さになる。

mutate()を使う場合

# data.table
x.dt %>%
  mutate(
    age_grp = cut(age, breaks = c(0:6*10, Inf), include.lowest = T, labels = c(paste0('~', 1:6*10-1), '60~'), ordered_result = T)
  ) -> x.dt

欠損値を特定の値に置換する

purchase_amountがNAの場合に「0」にし、値がある場合はそれをそのまま採用する

x.dt[is.na(purchase_amount),purchase_amount:=0]

mutate()を使う場合

x.dt %>%
  mutate(
    purchase_amount = ifelse(is.na(purchase_amount), 0, purchase_amount)
  ) -> x.dt

'NULL'という文字列を欠損値(NA)化する

clicklog[page_name == 'NULL', page_name := NA]

sample_は本来2値変数で、本来TRUEの場合に文字列’Y’、FALSEの場合にそもそも値が入っていない(NA)。これをRのlogical型に変換する。

x.dt[sample_ == 'Y', sample := T]
x.dt[is.na(sample_), sample := F]
x.dt[,sample_ := NULL]

行番号

IDをrownameにする(データフレーム)

データフレームを分析処理の関数にかけるとき、
行を識別する目的の変数(ID)は除外しなければならない。
たとえば

kmeans(x, centers = 5)

とするとき、xにはIDの列が含まれていてはならない。
その都度除外処理を入れるのは面倒なので、データフレームのrownameにしてしまい、
実データを表す列から除外してしまう。

rownames(customer) <- customer$id
customer$id <- NULL

行番号の列を作る(data.table)

x.dt[,my_id = .I]

mutate()を使う場合

x.dt %>%
  mutate(
    my_id = as.integer(rownames(.))
  ) -> x.dt

変数の削除

不要になった変数を削除する。

# データフレーム
x.df$n_purchase <- NULL
# data.table
x.dt[,n_purchase:=NULL]

分析上意味のない(全レコードの値が同じ)列を除外する

adlog <- adlog[sapply(adlog, function(x) length(levels(factor(x,exclude=NULL)))>1)]

http://stackoverflow.com/questions/8805298/quickly-remove-zero-variance-variables-from-a-data-frame

結合

ジョイン(テーブルを横に結合)

データフレーム同士をマージする

共通の列名がある場合、それをキーにマージする。

category <- merge(page, clicklog)

列名を指定することも可能

category <- merge(page, clicklog, by.x="pageid", by.y="pid")

デフォルトでINNER JOINと同じ挙動。
all.x=TでLEFT JOIN
all.y=TでRIGHT JOIN
– 両方指定するとFULL OUTER JOIN

category <- merge(page, clicklog, all.y=T)

キーがrownameになっている場合、by.x=0で指定できる。

category <- merge(page, clicklog, by.x=0, by.y="pid")

データフレームにベクトルをマージする

IDをキーにマージする

1変量ではIDを名前(names属性)にとる名前付きベクトルを使うことが多い。
またtapply()の出力結果はarrayになっており、names属性が要素を識別するインデックスとなっている。

names()でこれらを取得するとcharacter型で返すので、IDをinteger型で持たせているデータフレームとマージ際はas.integer(names(...))integer型に合わせるのが安全である。

click.category <- tapply(clicklog$click, clicklog$categoryId, sum)
category <- merge(
    category, 
    cbind(categoryId = as.integer(names(click.category)), click = click.category)
)

data.table

データフレームと同様にジョイン処理はmerge()関数を使うことができる

# 列名が同じ場合
merge(x.dt, y.dt, all.x = T, by='store_id') -> x.dt
# 列名が異なる場合
merge(x.dt, y.dt, all.x = T, by=c('reg_store' = 'store_id')) -> x.dt
# それぞれのテーブルに同じ列名がある場合
merge(x.dt, y.dt, all.x = T,, by=c('reg_store' = 'store_id'), suffix = c('', '_store_master')) -> x.dt

***_join()という関数を使ってdplyrのパイプラインの中でジョインを入れることができる。
上と同じことをパイプラインで実現する例

# 列名が同じ場合
x.dt %>% left_join(y.dt, by='store_id') -> x.dt
# 列名が異なる場合
x.dt %>% left_join(y.dt, by=c('reg_store' = 'store_id')) -> x.dt
# それぞれのテーブルに同じ列名がある場合
x.dt %>% left_join(y.dt, by=c('reg_store' = 'store_id'), suffix = c('', '_store_master')) -> x.dt
  • inner_join()left_join()right_join()full_join()がある
    • merge(x, y) == inner_join(x, y)
    • merge(x, y, all.x = T) == left_join(x, y)
    • merge(x, y, all.y = T) == right_join(x, y)
    • merge(x, y, all = T) == full_join(x, y)
  • by=suffix=の与え方はmerge()merge.data.table()と同じ
  • お互いのテーブルのキーとなる列の型が一致していないと使えない
  • それぞれのテーブルに同じ列名がある場合、suffix = c('x側の列につけるsuffix', 'y側の列につけるsuffix')を指定する

以下のようにパイプラインの中でleft_joinを繰り返していくことが多い

データ %>%
  left_join(マスタ) %>%
  left_join(マスタ) %>% 
    :

ユニオン(行結合)

bind_rows()を使う。
x1.dtに行を追記していく例

x1.dt %>%
  bind_rows(x2.dt) %>% 
  bind_rows(x3.dt) -> x1.dt
Series Navigation<< Rでデータの整形(列のデータ型確認、列の抽出、列名の変更、列の型変換)Rでデータセットの抽出(行の抽出、並べ替え、サンプリング、分割) >>