と。

Github: https://github.com/8-u8

データ分析をちゃんと管理しよう【コーディング編】

今月10記事も書けるのか?

ううう……がんばりましゅ……

フォルダ構成編 socinuit.hatenablog.com

ファイル名編 socinuit.hatenablog.com

ここまででできるようになったこと

  • データを扱うプロジェクトはフォルダ構成が重要。
    • 入力データを保存するフォルダinput
    • 出力結果を保存するフォルダoutput
    • 分析に使ったコードを保存するフォルダsource
  • 基本的にそのプロジェクトフォルダの中で全ての作業が完結する。
  • フォルダへのアクセスについてのRの関数
    • フォルダを「作る」dir.create
    • フォルダ内のファイルのリストを表示するlist.files
  • フォルダ名・ファイル名に規則性を設ける
    • 自分の成果をコンピュータを使って探しやすくするため
    • いつやったかの記録Sys.Date
    • 文字列の結合pastepaste0
    • 文字列のマッチングgrep

使いこなせずとも「Rだけでこのあたりの話が完結する」ということが分かってくれたら嬉しいなあと思います。

今回できるようになりたいこと

  • Rのコードを「タスクや工程によって分ける」こと。

基本思想

1つのコードで前処理・分析・結果の出力の全てを記述すると、
修正や編集が必要となった時に、様々な部分を同時に変更しなければならず、
分析結果の再現性を担保することが難しくなります。
例えばシンプルに「前処理用コード」「分析用コード」「結果確認・出力用コード」に分けるとすると、
例えば前処理の追加編集のためには「前処理用コード」を編集すればよく、
解析のためのパラメータの編集をする場合には「分析用コード」を編集すればよい、
という形で「どこを編集するべきなのか」を限定することができます。
結果の管理は前回まででファイル名やフォルダの階層で行えばよいので、
コード内で使う変数名やアウトプットの名前をいちいち変更する必要はありません。
これを実行できれば、効率的で再現性のある形での分析ワークが進むと思いませんか!?
それを実現するために、Rにはsource関数という関数があります。
この関数は自分で作成したRコードを、別のRコードで読み込むことができる関数です。

こんな感じでやっていきをしよう

修士学生だったきぬいとは、こんなコードで分析をしていました。

ファイル名: 分析.R
# ライブラリ読み込み
library(reshape)
library(lme4)
library(data.table)

# データ読み込み
data_1 <- fread("dat1.csv")
data_2 <- fread("dat2.csv")

## ....(中身の確認・EDAムーブで数十行)

# データ前処理
data_1 <- hoge(data_1)
data_2 <- hoge(data_2)

#### ...(数十行)

# 分析
reg1 <- regfunc(y~., data = data_1)
summary(reg1)
reg1$coefficients
# ...(確認の行が数十行)

# 出力
write.csv(reg1$coefficients, "結果.csv")

##(合計300行以上)

……かなり省略しましたが、このコードの何が問題だったのか。

  • データの読み込み・前処理・分析・結果の確認・出力の全てが1ファイル
    • 修正・実行は各工程を見直して適宜編集しなければならない
  • 前処理後のテーブルを出力していない
    • 分析に使うデータを再現できない
  • 変数名が抽象的すぎる
    • data_1はなんのデータなのか全くわからない

こんなコードを明日から書かなくていいようにしよう*1

ファイル名: 前処理.R

# ライブラリも前処理で使うものだけを入れる。
library(tidyverse)
library(data.table)

research_data <- data.table::fread("./input/research_data.csv",
                                   encoding = "unknown")
geom_data     <- data.frame::fread("./input/geometry_data.csv",
                                   encodinf = "UTF-8")

# hoge関数は前処理に関する任意のいろいろな手続きです。
# みんなでいろいろ書いてみましょう。
for_regression_data <- hoge(research_data, geom_data)

注意するべきことに、source関数は日本語を含むマルチバイト文字が入っているRコードの読み込みでエラーを履くことがあります。
これは文字コードのエラーだったりするので、encoding = "UTF-8" などと設定すると良いです*2

ファイル名: 回帰分析.R
# ライブラリは必要なものだけを入れる
library(lme4)
library(glmnet)

# これで for_regression_data を読み込むことができる。
source("./source/前処理.R", encoding = "UTF-8")

nb_regression <- lme4::glmer.nb(y~., data = for_regression_data)
l1_regression <- glmnet::glmnet(y = y, x = x, ...)

同様に、結果の確認や出力のスクリプトはこんな感じです。

ファイル名: 結果確認.R
source("./source/回帰分析.R", encoding = "UTF-8")

## ...(結果の確認やアウトプットについての記述)

……というような形で、それぞれでsource関数を呼び出すことで、
それまでの手続きの継承が可能です。
上記の例だと回帰分析.Rを呼び出すことで、連鎖的に前処理.Rを呼び出されているのですが、
例えば前処理.Rfor_regression_dataを中間データとして出力し、
回帰分析.Rsource部分をread.csv(中間データ名)のように編集すれば、
連鎖的な呼び出しのない形で呼び出しが可能です。
こういうスタイルだと、例えばEDAのパートを分析の実行時に呼び出さずに構成可能ですし、
結果の確認と出力を分ければ、なんか変な結果になったときにも立ち戻ることも楽にできます。

次回予告

まだつづくのか。
次回は「エラーから逃げるな」という話をします。

*1:この記事を書きながら黒歴史と向き合い発狂しかけている

*2:ここの設定はRStudioでのデフォルトの文字エンコーディングに依存するのですが、だいたいCP932かUTF-8で解決する場合が多いように思います。