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

Rでテキストファイルの読み込みからデータ加工の流れ（前処理）で覚えておくとかなり便利、あるいはよくハマるポイントを中心に。
細かい知識が多いのだがまとまったものがなかったのでまとめてみた。
ありがちなハマりを回避して作業効率を上げるために。



# データの読み込み

## データファイルの読み込み

プロジェクトごとにワーキングディレクトリをセットするといい

```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", ...)`

```r
clicklog <- read.csv("clicklog.csv", stringsAsFactors = F, fileEncoding = "UTF-8-BOM")
```

### Excelファイルの読み込み

`read.xlsx{openxlsx}`を使う。

```r
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ファイル

```r
banques <- read.table("https://archive.ics.uci.edu/ml/machine-learning-databases/00202/banques.txt", header=T, sep="\t", stringsAsFactors=FALSE)
```

### 圧縮ファイル（.zip）→ダウンロードしてから展開する必要がある

ファイルを圧縮している場合

```r
# ダウンロード
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)
```

ディレクトリを圧縮している場合

```r
# ダウンロード
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を使う→自由度高い

```r
# ダウンロード
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からデータ取得→また今度



# データの形式の整備

## データフレームの列の型確認

```r
sapply(clicklog, class)
```

## 列の型変換

```r
clicklog$categoryName <- as.factor(clicklog$categoryName)
clicklog$click <- as.logical(clicklog$click>10)
```

## 欠損値処理、「NULL」を`NA`に変換

```r
clicklog$pageName[clicklog$pageName=="NULL"] <- NA
```

## 自動で変換されるfactor型の扱い

`factor`として読み込んでしまった値を数値に戻す。そのまま`as.numeric()`すると`levels`を数値にしてしまうため、**一旦文字列にしてから数値にする**。

```r
clicklog$scroll <- as.numeric(as.character(clicklog$scroll))
```

## 日時

**日時は`POSIXct`がスカラなので分かりやすい**（エポック秒のスカラ）。`POSIXlt`はリストで要素が複数あるのでややこしい。

```r
clicklog$datetime <- as.POSIXct(clicklog$datetime)
```

※ただし遅いので速さを求めるならライブラリ{lubridate}を使う手もある。
http://qiita.com/hoxo_m/items/6f18b163946f6f41deca

データフレームの列名変更は困難。data.tableなら簡単なのだが。

## IDをrownameにする

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

```r
kmeans(x, centers = 5)
```

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

```r
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`



# データセットの抽出（サンプリング、分割）

## 分割

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

```r
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％で無作為抽出する場合

```r
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`

という場合

```r
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を列名にしている場合

```r
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`テーブルだけで抽出してしまう場合

```r
# 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]
```



# データの加工

## コーディング
```r
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に置換されるので日時の処理では使ってはならない**。

```r
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`さえなければ

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

上記は同じ結果になる。

## 変数を除外する

指定した列を**除外**する

```r
adlog$adWidth <- NULL
```

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

```r
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

## 絞込み（抽出）

```r
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`

```r
x <- data.frame(a=1:3, b=c(2,NA,4), c=rep(3,3))
na.omit(x)
```

## ソート

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

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

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

```r
x[order(x[,n], decreasing=F),]
```

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

```r
adlog <- adlog[order(adlog$imp, adlog$click),]
```

下記と同じ

```r
adlog <- with(adlog, adlog[order(imp, click),])
```

```r
adlog <- adlog[adlog$click,]
adlog <- adlog[adlog$imp,]
```

並べ替えの際は第3添字の`drop=F`は不要（そもそも複数行が前提）。



# データのマージ

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

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

```r
category <- merge(page, clicklog)
```

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

```r
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

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

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

```r
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`型に合わせるのが安全である。

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