複数の.csvファイル(じゃなくてもいいけど)を一気に結合する

本記事について

Online Psychological Experiment Advent Calendar 2020 第17日目の記事です。

最初は気軽にポエムでも書こうかと思っていたものの、他の記事がどれも真面目だったので、「だから、オレだってなんかしなくっちゃあな…カッコ悪くて2021年に行けねーぜ…」ということで急きょtipsを書くことにしました。

オンライン実験のデータがどのように保存されるか

もちろん、どんな実験をするかや、どのサービスを利用するかによって、話は変わります。しかし一般的には、「協力してくださった実験参加者の数だけ、ファイルが蓄積される」と思います。オフラインの(実験室で行うような)実験ならなおさらですね。

例えば僕自身は、こちらの記事を参考にして、lab.jsで作成した.jsonファイルを、Open Labにアップロードし、単語記憶課題のオンライン実験を行いました。その結果、実験参加者の数だけ個別の.csvファイルがサーバ上に蓄積されました。

複数の.csvファイル(じゃなくてもいいけど)を一気に結合したい

もちろん.csvファイルに限らず、全てのファイルが.xlsxでも良いのですが、ともかく拡張子が同じ複数のファイルを、1つのファイルに結合しなければ、前処理も分析もできないわけです。

実験参加者数が少なければ(例えば10人など)、1つのファイルを開いてデータをコピーして、別のファイルにペーストするのを繰り返す...という手作業でも、さほど手間にはならないでしょう(※言うまでもなく、これはヒューマンエラーが混入する恐れがあるので、避けるべきですが)。

しかしオンライン実験となると、一般的にサンプルサイズが大きくなりやすいと思うので、手作業は労力の観点から現実的ではありません(※くどいようですが、労力以外の観点からも、データをコピペでまとめるのは避けるべきです)。

purrrパッケージを使おう

R環境限定の話になりますが、purrrパッケージを用いれば、同じディレクトリ内に存在する、拡張子が同じ全てのファイルを、1つのデータフレームに結合することが、容易にできます。

その方法は...これだっ!(他の方が作成された資料を共有していくツェペリ魂)

上の画像のコードは、tidyverseパッケージをロードする前提で書かれているので、最低限必要なpurrrパッケージとreadrパッケージだけをロードした場合には、以下の書き方になります。
tidyverseパッケージをロードしたら、これら2つのパッケージも自動的にロードされますが)

library(purrr)
library(readr)


target_files = list.files("data/",
                          pattern = ".csv$",
                          full.names = TRUE)

dat = purrr::map_df(target_files, readr::read_csv)

まず、 特定のディレクトリ内に存在する、拡張子が.csvのファイル名を全て、target_filesという名前のリストにまとめています。この場合は、現在の作業ディレクトリの中に、「data」という名前のフォルダがあり、その中に全ファイルが存在する想定です。

次に、 readr::read_csv()関数で個々の.csvファイルを読み込むたび、purrr::map_df()で1つのデータフレームとして結合していきます。結合されたデータフレームは、datという名前のオブジェクトに格納されます。

自分用にカスタマイズも可能

データによっては、

  • ファイルを読み込む際に、空欄のセルをNAにしたい
  • ファイル名も、データフレーム内の変数として保存したい

などの、細かいカスタムが必要となることもあるでしょう。そのような場合には、データを読み込む関数(上のコードではreadr::read_csv())をカスタムした自作関数を定義すればよいです。

ここで新たにdplyrパッケージをロードしているので、パイプ演算子%>%を使用しています(それだったらtidyverseパッケージを1つロードすればよい気もしなくもない)。

library(purrr)
library(readr)
library(dplyr)

# 自作関数
read_func = function(x){readr::read_csv(file = x, na = "") %>%
    dplyr::mutate(filename = x)}


target_files = list.files("data/",
                          pattern = ".csv$",
                          full.names = TRUE)

dat = purrr::map_df(target_files, read_func)

このコードでは、read_func()という関数を自作しています。
まずreadr::read_csv()でファイルを読み込む際に、空白のセルをNAにするよう、引数を指定しています。さらに、1つのファイルをそのようにして読み込んだあとで、dplyr::mutate()によって、ファイル名をfilenameという名前の列に追記しています。
あとはpurrr::map_df()のなかで、自作した関数read_func()を使用することを明記するだけです。

おわり

全てのデータが、1つのデータフレームにまとめられれば、第15日目の記事のように前処理を行うことも、分析することもできます。

Enjoy !!