125
149

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Rでデータ読み込みから前処理までのTips

Last updated at Posted at 2015-09-02

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)
)
125
149
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
125
149

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?