R(dplyr)を使ったデータ操作
ここでは主に、データの読み込み -> 整形/変形 -> 書き出し に関する方法を紹介します。
トップページでは、関数のヘルプの見方やパイプ演算子%>%
の使い方を記載していますので、そちらも御覧ください。
このチュートリアルはlearnr
パッケージを使用して書かれており、ソースコードはこちらで公開しています。 項目の追加リクエストや、バグレポートはGitHubにてお願いします。
はじめに
研究においてデータクリーニングは欠かせません。
この項では、データのRへの読み込み、読み込んだデータの処理と成型、そして他のソフトで使える形式で書き出す方法を順を追って説明します。
このサイトの使い方
このサイトでは、Rが皆さんのPCにインストールされていなくても、ブラウザ内でRのコマンドを実行し結果を見ることができます。
例えば下の1行目に1 +
と入力しておいたので、1 + 1
と数式を完成させてから、「▶コードを実行」を押してみてください。
1 +
次に、3行目にSys.time()
と入力して実行したらどうでしょう?
このようにいろいろとRのコードを試せますので、これから紹介するサンプルコードを自分なりに書き換えて実行してみてください。
データの読み込み
csvの読み込み read_csv()
read.table()
関数など使える関数はいくつかありますが、readr
パッケージのread_csv()
関数を使います。
data_q <- read_csv("data/data_questionnaire.csv")
data_p <- read_csv("data/data_phyis.csv")
Excelブックの読み込み read_excel()
readxl::read_excel()
を使うと、.xlsファイルと.xlsxファイルが読み込める。
ここでは紹介にとどめるので、気になる人は関数のヘルプを見てみてください。
## excelファイルの読み込み
data_xlsx <- readxl::read_excel("sample.xlsx", sheet = "sheet1", na = c("#N/A", "", "NA"))
Excelをコピーしたクリップボードの中身の読み取り
本格的なコーディングをする前の準備段階では、クリップボードを使ったほうが楽だったりすることもある。ここではWindowsを前提に説明しますが、ググったらMacやLinuxでの方法も出てきます。
Excelの表を範囲選択してからコピーし、Rで以下のコマンドをタイプすると読み込めるはずです。なお、Excelのクリップボードへのコピーはタブ区切りになるので、sep = "\t"
と指定します。データに列名が含まれない場合は、header = FALSE
とします。
data_clipb <- read.table("clipboard", header = TRUE, sep = "\t")
なお、クリップボードは容量制限があるため、データが大きすぎるとうまく読み込めません。そういったときはあきらめてCSVやXLSXファイルを読みましょう。
複数ファイルを読み込む
list.files()
関数を使うと、カレントディレクトリ(作業フォルダ)にあるファイルの一覧が取得できます。pattern = ""
を使うと、一覧に採用するファイルを絞り込める。
このCSVファイル一覧を使って、すべてのファイルの読み込みを一つのコマンドでまとめて行う。
file.list <- list.files(pattern = ".csv", path = "data/", full.names = T)
file.list
lapply()
を使って行う方法
結果は一つのリストに入ります。
result <- lapply(file.list, read_csv)
result[[1]]
result[[2]]
for()
文を使う方法。ここでは、assign()
関数を使って、別々の変数にデータを格納します。変数名はもともとのファイル名を使用しました。 データフレームを作って、その下に結合することも可能です(メモリ効率が悪いが人間にはわかりやすい方法)。
for (i in 1:length(file.list)) {
result <- read_csv(file.list[i])
assign(str_replace_all(file.list[i], "data//|.csv", ""), result) #linux vs windowsで/に対するエスケープの要否が異なる。
}
(data_p <- data_phyis)
(data_q <- data_questionnaire)
データ構造の把握
構造を表示する
base::str()やdplyr::glimpse()は、データの構造を確認するのに便利。
data_q %>% str()
data_q %>% glimpse()
列名を確認する
データフレームの列名だけ取得したいとき
data_q %>% colnames()
列の絞り込み
dplyr::select()
関数を使うと、データフレームから特定の列だけを取り出す事ができます。また、ここで使う列の指定方法は、表の操作で使うmutate(across())
や集計で使うsummarise(across())
でも同じなので、覚えておくと便利。
列名を直接指定する
select()関数の第2引数以降に取り出したい列の名前を直接タイプします。
例えば、data_pのidと条件1のデータだけほしい時は以下のようにします。他の列名も試してみよう。
data_p %>% select(id, condition1)
列の順番がわかっている場合は、コロンで繋いだ範囲を取り出すこともできます。
data_q %>% select(id, q6:q10)
列名の一部で絞り込む
以下の関数群を使うと、列名の一部で絞り込みができます。
- 列名検索_先頭
starts_with()
- 列名検索_後端
ends_with()
- 列名検索_含有
contains()
- 列名検索_正規表現
matches()
- 指定する変数名全て
all_of()
- 指定する変数名全て
any_of()
指定した変数名が存在しなくてもエラーにならない
starts_with()
を使った例を下に示すので、他の方法も試してみよう。
data_p %>% select(starts_with("cond"))
列のデータ型や内容で絞り込む
where()
関数を併用すると、列のデータ型などで絞り込める。
where(is.numeric)
数値列のみwhere(is.factor)
因子型の列のみwhere(~is.numeric && mean(.) > 0)
数値型の列でかつ列内の平均が0を超える
where(is.factor)
の例を下に示す。
#idとgroup列をfactor型にする
data_p$id <- as.factor(data_p$id) #mutate()を使った方法は後ほど
data_p$group <- as.factor(data_p$group) #mutate()を使った方法は後ほど
data_p %>% select(where(is.factor))
その他
また、上記の関数の先頭に-
や!
をつけると、条件に当てはまる列を削除できます。
data_p %>% select(-group)
- すべて
everything()
- 指定する変数名全て
all_of()
- 指定する変数名全て
any_of()
指定した変数名が存在しなくてもエラーにならない
data_p %>% select(group, everything())
以下のコードはエラーになります。どこを直せばよいだろうか。
var_list <- c("id", "condition")
data_p %>% select(all_of(var_list))
行の絞り込み
dplyr::filter()関数を使うと、データフレームから特定の行を抽出する事ができます。
因子型(や文字列型)で直接指定
group_a <- data_p %>% filter(group == "a")
group_a %>% summary()
ある列の各セルに特定の文字が含まれるかどうか
filt_char <- iris %>% filter(str_detect(Species, "ni|ve"))
summary(filt_char)
数値的な基準
ある列の数値を基準にします。別の数字を試してみよう。
num_filt <- data_q %>% filter(q1 > 0.5)
summary(num_filt)
計算した数値的な基準を使う
たとえば、ある列の各セルがその列の平均値を超えるかどうかで絞ることもできます。
num_filt2 <- data_q %>% filter(q1 > mean(q1))
summary(num_filt2)
データフレームの並べ替え
データフレームの列の並べ替え relocate()
dplyr::relocate()
で列の並べ替えができます。引数に、先頭に持ってきたい列を左から順番に指定します。ここでは列名を直接タイプしたり、先述したstarts_with()
やmatches()
、all_of()
などのselecting functionsをつかった列指定が可能です。
なお、relocate(列指定)
はselect(列指定, everything())
と同義。試してみよう。
data_p %>%
relocate(starts_with("condition"))
データフレームの行の並べ替え arrange()
dplyr::arrange()
でデータフレームを並べ替える事ができます。
data_q %>% arrange(q1)
複数条件での並び替え
data_q %>% arrange(q1, q2, q3)
降順での並べ替え
desc()で列名を囲ってやると指定した列は降順になります。
data_q %>% arrange(desc(q1))
列の追加/編集
dplyr::mutate()
と、dplyr::transmute()
を使うと、データフレームに新たな列を作ったり、既存の列に何らかの処理を加えて上書きすることができます。
mutate()
とtransmute()
の違いは、mutate()
では元データに含まれるすべての列が返り値のデータフレームに含まれるのに対して、transmute()
では処理対象に指定した列以外の列は返り値に含まれないことです。
なので、既存のデータフレームに変更を加えるときはmutate()
、新しいデータフレームを作りたいときはtranmsmute()
を使うことが多いです。ここでは、mutate()
を実例に処理方法を説明します。
新しい列を追加する
mutate()の中で、新しい列名 = 実施する処理を書くと、指定した新しい列に処理結果が代入されます。
result <- data_q %>%
mutate(new_col_num = q1 + q2,
new_col_bool = q1 > 3)
result %>% select(id, q1:q2, starts_with("new"))
既存の列を上書きする
mutate()の中で、既存の列名 = 実施する処理を書くと、指定した既存の列に処理結果が上書きされます。
result <- data_q %>%
mutate(id = as.factor(id)) #id列を因子型に変換する
result %>% select(id, q1:q2)
注意 mean()
やsd()
など引数をvectorでとる関数の場合
一部の関数は引数をvectorで取るため、以下の様な処理は望んだ結果が得られない。
1列目にrelocate()
で持ってきたsum列が全行で同じ数値になっているのを確認してみよう。
result <- data_q %>%
mutate(sum = sum(q1, q2, q3))
result %>% relocate(sum)
この様な関数を使うときは、予め行ごとの処理を命令rowwise()
しておく必要がある。また、rowwise()
はのちの集計作業summarise()
などに影響したりするので、必要な処理が終わったらungroup()
しておくことをおすすめします。
result <- data_q %>%
rowwise() %>%
mutate(sum = sum(q1, q2, q3)) %>%
ungroup()
result %>% select(q1:q3, sum)
更に、q1:q3
といったセミコロン:
で列を指定する場合は、rowwise()
に加えてc_across()
で列名をくくってやる必要がある。
これは、行毎の計算に必要な処理であって、以下の例のselect()
のように、列選択だけではc_across()
は必要ない。正直わかりにくいのでなんとかしてほしいですよね。
個人的には、mutate(acorss(cols, .fns))
という形で列と処理を一括で指定する方法をおすすめしたい。(後述予定)
result <- data_q %>%
rowwise() %>%
mutate(sum = sum(c_across(q1:q3))) %>%
ungroup()
result %>% select(q1:q3, sum)
ロングデータとワイドデータの変換
今サンプルとして用いているdata_q
もdata_p
もどちらも、ワイドデータ(繰り返し測定が別の列として表現される)と呼ばれるデータ形式になっています。
この形式は人間には分かり易いが、コンピューターにはわかりにくいため、統計ソフトなどではロングデータ(データフレーム内に計測値は1列しかなく、繰り返し測定は列名ではなくセルの値で表現される)が求められることも多いです。
そこで、ここではワイドデータからロングデータ、そしてロングデータからワイドデータの変換方法を説明します。
ワイドデータ→ロングデータ pivot_longer()
改めてdata_p
のデータ構造を確認してみよう。1:100のid
(参加者ID), aとbで構成される(被験者間要因の)group
、そして繰り返し測定のcondition1:condition4
がある。
data_p %>%
mutate(group = as.factor(group)) %>%
str()
ここで、ロングデータを作ったときに想定される列は、id, group, condition, value
となります。idとgroupは中身は変わらないが繰り返し分で縦に長くなり、conditionにはcondition1~4のいずれかが入り、valueは計測値が入る。
この関数一つで、エクセル作業からずいぶん開放されますね!
long_data <- data_p %>%
pivot_longer(cols = condition1:condition4, #計測値の入っている列範囲
names_to = "condition", #新しく作る条件用の列名
values_to = "value") #値を入れる列名
long_data
100行のワイドデータが400行のロングデータになっただろうか。
ロングデータ→ワイドデータ pivot_wider()
続いて、今作ったlong_data
をワイドデータにしてみよう。つまり、もともとのdata_p
と同じ形に戻すことになります。
wide_data <- long_data %>%
pivot_wider(id_cols = id:group, #idや条件を示す列
names_from = condition, #ワイドデータの列名のもと
values_from = value) #値が入っている列
wide_data
もとのデータと見比べてみよう
str(data_p)
str(wide_data)
データフレームの結合
データフレームの横結合 joinファミリー
データフレームを横方向に結合(して新しいデータフレームを作る)するには、以下のjoin系の関数を使います。
inner_join()
xとyの両方に含まれる行(共通する行)を使うleft_join()
xに含まれるすべての行を使うright_join()
yに含まれるすべての行を使うfull_join()
xとyに含まれる行をすべて使う
join系の関数では、データフレームxとデータフレームyに共通するid列(必ずしもidという列名である必要はない)を指定して、そのid列で名寄せを行う。
まず、解説用のデータを作ります。data_xとdata_yはそれぞれ機器1と2から得られた条件1~3のデータですが、機器1では参加者2番さんと5番さん、機器2では参加者4番さんのデータが欠損しています。
これらdata_x
とdata_y
をjoin系の関数で結合してみます。
inner_join()
joined_data <- inner_join(data_x, data_y, by = "id")
joined_data
7行7列のデータが出来ました。
left_join()
/ right_join()
次は、left_join()とright_join()を同じデータに使って挙動を確認してみる。
left_joined_data <- left_join(data_x, data_y, by = "id")
right_joined_data <- right_join(data_x, data_y, by = "id")
left_joined_data
right_joined_data
full_join()
full_join()
は両方のデータフレームを尊重して結合がおこわなれる。
full_joined_data <- full_join(data_x, data_y, by = "id")
full_joined_data
10行7列のデータができた。
ちなみに
- xとyで結合に使うid列の列名が異なる場合は、
by = c("id.x" = "id.y")
のように指定できます。 filter-joins
やnest_join()
もある
データフレームの縦結合 bind_rows()
次は、データを縦に結合する方法。bind_rows()
はxとyで共通する列は縦に結合し、共通しない列はNA
を使って新規列が作られる。以下の例だと、idだけが共通する列である。
bind_rows(data_x, data_y)
データの書き出し
CSV形式での書き出し
readr::write_excel_csv()
では、エクセルで読み込むのに都合の良いCSVを書き出してくれる。
write_excel_csv(data_p, "data/write_data_to.csv")
クリップボードへの書き出し
デバッグ作業などでクリップボードに表の内容を保存し、エクセルに貼り付けたいときもあると思います。そういったときは以下のコマンドで可能です(Windowsの例)。
write.table(data_p, "clipboard", col.names = TRUE, row.names = FALSE, sep = "\t")
バイナリ形式での書き出し
.Rdata形式でデータを保存しておくと、データ型の指定などを保ったままデータを保存できます。が基本Rでしか読み込めないです。
save()
は特定の変数を保存するのに使います。save.image()
は現在のGlobal Environment内の全変数をまとめて保存できます。
なお、.Rdataの読み込みはload()
でできます。
save(data_p, file = "data_p.Rdata")