Rでテキストファイルの読み込みからデータ加工の流れ(前処理)で覚えておくとかなり便利、あるいはよくハマるポイントを中心に。
細かい知識が多いのだがまとまったものがなかったのでまとめてみた。
ありがちなハマりを回避して作業効率を上げるために。
データの読み込み
データファイルの読み込み
プロジェクトごとにワーキングディレクトリをセットするといい
setwd('/home/ruser/data/')
CSVファイルの読み込み
-
read.csv()
はヘッダありが前提。 - 文字コード
fileEncoding="文字コード名"
で指定できる。Windowsで作ったCSVを読み込む際、BOMが付いていることがあるのでfileEncoding="UTF-8-BOM"
とする -
文字列の扱い
文字列が自動的にfactor
型になるので、不都合な場合はstringsAsFactors=F
を指定する -
欠損値の扱い
欠損値が空欄であったり、データベースからエクスポートしたCSVファイルではNULL
という文字列になっていることがある。これをRの欠損値NA
に変換する場合はna.strings="NULL"
やna.strings=c("", "NULL")
を指定する。これを指定しないと、空欄に対してシステムが勝手な解釈で値を振ってしまう - 変数名と変数の型
バッチ処理では変数が決まっているので変数名と型をあらかじめ指定して読み込むことができる。
col.names=c("pid", "pageName", "sales", ...)
colClasses=c("integer", "character", "numeric", ...)
clicklog <- read.csv("clicklog.csv", stringsAsFactors = F, fileEncoding = "UTF-8-BOM")
Excelファイルの読み込み
read.xlsx{openxlsx}
を使う。
m_user.dt <- read.xlsx('C:/home/user1/doc/マスタ.xlsx', na.string = '', sheet = 'ユーザ') %>% as.data.table %>% mutate(id = as.integer(id))
データファイルのダウンロード
R-3.2.2以降デフォルトでHTTPSでのダウンロードをサポートするようになり、HTTPSでのファイルの取得が容易になった。ただし自己署名型の証明書には対応していないため、オレオレ証明書のサイトでは{RCurl}を使う必要がある。
CSVファイル
banques <- read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/00202/banques.txt", header=T, sep="\t", stringsAsFactors=FALSE)
圧縮ファイル(.zip)→ダウンロードしてから展開する必要がある
ファイルを圧縮している場合
# ダウンロード
tmp <- tempfile()
download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/00206/slice_localization_data.zip", tmp)
# 解凍と読み込み
localization <- read.table(unzip(tmp), header=T, sep=",", stringsAsFactors = FALSE)
# 使い終わったファイルの削除
unlink(tmp);rm(tmp)
ディレクトリを圧縮している場合
# ダウンロード
tmp <- tempfile()
download.file("https://archive.ics.uci.edu/ml/machine-learning-databases/00296/dataset_diabetes.zip", tmp)
# 解凍と読み込み
files <- unzip(tmp)
diabetes <- read.table(files[1], header=T, sep=",", stringsAsFactors = FALSE)
# 使い終わったファイルの削除
unlink(files);rm(files)
unlink(tmp);rm(tmp)
RCurlを使う→自由度高い
# ダウンロード
require(RCurl)
binData <- getBinaryURL("https://archive.ics.uci.edu/ml/machine-learning-databases/00296/dataset_diabetes.zip", ssl.verifypeer=F)
tmp <- tempfile()
conObj <- file(tmp, open = "wb")
writeBin(binData, conObj)
close(conObj)
# 解凍と読み込み
files <- unzip(tmp)
diabetes <- read.csv(files[1], stringsAsFactors = FALSE)
# 使い終わったファイルの削除
unlink(files);rm(files)
unlink(tmp);rm(tmp)
APIからデータ取得→また今度
データの形式の整備
データフレームの列の型確認
sapply(clicklog, class)
列の型変換
clicklog$categoryName <- as.factor(clicklog$categoryName)
clicklog$click <- as.logical(clicklog$click>10)
欠損値処理、「NULL」をNA
に変換
clicklog$pageName[clicklog$pageName=="NULL"] <- NA
自動で変換されるfactor型の扱い
factor
として読み込んでしまった値を数値に戻す。そのままas.numeric()
するとlevels
を数値にしてしまうため、一旦文字列にしてから数値にする。
clicklog$scroll <- as.numeric(as.character(clicklog$scroll))
日時
日時はPOSIXct
がスカラなので分かりやすい(エポック秒のスカラ)。POSIXlt
はリストで要素が複数あるのでややこしい。
clicklog$datetime <- as.POSIXct(clicklog$datetime)
※ただし遅いので速さを求めるならライブラリ{lubridate}を使う手もある。
http://qiita.com/hoxo_m/items/6f18b163946f6f41deca
データフレームの列名変更は困難。data.tableなら簡単なのだが。
IDをrownameにする
データフレームを分析処理の関数にかけるとき、
行を識別する目的の変数(ID)は除外しなければならない。
たとえば
kmeans(x, centers = 5)
とするとき、x
にはIDの列が含まれていてはならない。
その都度除外処理を入れるのは面倒なので、データフレームのrownameにしてしまい、
実データを表す列から除外してしまう。
rownames(customer) <- customer$id
customer$id <- NULL
基本的な変数の型
boolean
logical
: TRUE
or FALSE
、短縮してT
or F
としても可能
数値
- 整数は
integer
明示的にinteger
として扱うにはx <- 5L
のようにL
を付ける - 小数を含めると
numeric
- bigint相当は
library(bit64)
を使うとinteger64
として指定できる
文字列の扱い
- 単純な文字列
character
- カテゴリカル変数として扱うには
factor
- 順序つきカテゴリカル変数として
ordered
データセットの抽出(サンプリング、分割)
分割
学習用と検証用とでデータセットを分割する
nr <- nrow(dat)
# set.seed(1234)
row.train <- sample(nr, floor(0.5 * nr))
dat.train <- dat[row.train,]
dat.test <- dat[-row.train,]
サンプリング
単純なサンプリング
それなりに大きいデータではサンプリングして分析することになる。データセットclicklog
から抽出率20%で無作為抽出する場合
nr <- nrow(clicklog)
# set.seed(1234)
row.sampled <- sample(nr, floor(nr * 0.2))
clicklog.sampled <- clicklog[row.sampled, , drop=F]
IDでサンプリング
ユーザマスタ-/行動履歴などデータフレームが分かれている場合、行動履歴もユーザマスタのIDに基づいてサンプリングすることになる。
userId
を抽出率20%で無作為抽出し、そのuserId
に基づいてclicklog
を抽出する。
- マスタのオブジェクト:
user
- データのオブジェクト:
clicklog
- 連携しているID(マスタのプライマリキー):
userId
という場合
nr <- nrow(user)
# set.seed(1234)
row.sampled <- sample(nr, floor(nr * 0.2))
user.sampled <- user[row.sampled,]
clicklog.sampled <- clicklog[is.element(clicklog$userId, userId.sampled), , drop=F]
userIdを列名にしている場合
nr <- nrow(user)
# set.seed(1234)
row.sampled <- sample(nr, floor(nr * 0.2))
user.sampled <- rownames(user)[row.sampled]
clicklog.sampled <- clicklog[is.element(clicklog$userId, userId.sampled), , drop=F]
clicklog
テーブルだけで抽出してしまう場合
# set.seed(1234)
userId.unique <- unique(clicklog$userId)
userId.sampled <- sample(userId.unique, floor(length(userId.unique) * 0.2))
clicklog.sampled <- clicklog[is.element(clicklog$userId, userId.sampled), , drop=F]
データの加工
コーディング
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()
はスカラしか返さないので主にデータフレームのコーディングで使う。
それ以外の処理の条件分岐では使わないほうがいい
特にそれ自体がリストとなるPOSIXt系は強制的にdoubleに置換されるので日時の処理では使ってはならない。
x <- ifelse(is.na(date_a), as.POSIXct(Sys.Date()), as.POSIXct(date_a))
class(x)
[1] "numeric"
NA
さえなければ
ifelse(x>10, y, z)
(x>10)*y + (x<=10)*z
上記は同じ結果になる。
変数を除外する
指定した列を除外する
adlog$adWidth <- NULL
分析上意味のない(全レコードの値が同じ)列を除外する
adlog <- adlog[sapply(adlog, function(x) length(levels(factor(x,exclude=NULL)))>1)]
絞込み(抽出)
adlog <- adlog[adlog$imp>1000 & adlog$click<10, , drop=F]
adlog <- with(adlog, adlog[imp>1000 & click<10, , drop=F])
上下は同じ。with()
関数はバッチの中でも使えるので便利。
データフレームの抽出・絞り込みでは第3添字にdrop=FALSE
を付けること!
行列の添え字にdrop=FALSE
を付けないと1行(列)のみマッチの場合にベクトルとして返す。そうなるとデータフレームを想定してその後の処理にrbind()
をしていたのができなくなるなど、行列処理に思わぬ不具合をきたすことになる。
drop=FALSE
を付けて1行n列の行列を返すように。
ただしtapply()
などで使う1列取得の際は付けてはならない。ベクトルとして処理する必要がある。
NAを含む行を除外→na.omit
x <- data.frame(a=1:3, b=c(2,NA,4), c=rep(3,3))
na.omit(x)
ソート
行列を第n列をキーに昇順に並び替え
x[order(x[,n]), ]
行列を第n列をキーに降順に並び替え
x[order(x[,n], decreasing=F),]
カラムimp
(最優先), click
(次に優先)をキーに昇順に並び替え
adlog <- adlog[order(adlog$imp, adlog$click),]
下記と同じ
adlog <- with(adlog, adlog[order(imp, click),])
adlog <- adlog[adlog$click,]
adlog <- adlog[adlog$imp,]
並べ替えの際は第3添字のdrop=F
は不要(そもそも複数行が前提)。
データのマージ
データフレーム同士をマージする
共通の列名がある場合、それをキーにマージする。
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)
)