と。

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

データ分析をちゃんと管理しよう with R 【ファイル名編】

年間50記事書くにはあと3ヶ月で32記事書かないといけない

……マジで?算数できないにも程がある。
ここから3ヶ月で30記事書くってことは、1ヶ月10記事……
ネタを探さないとなあと思いましたが、直近はいくつかあるのであんしん!

前回のあらすじ

こんな記事を前回書きました。

socinuit.hatenablog.com

私達はRからワーキングディレクトリを確認でき、
そこにあるファイル類を確認でき、
新しくフォルダを作成することができるようになりました。

と思ったら、その週のTokyoRで、運営の方による初心者セッションで取り上げていただきました*1
スライドは近いうちに公開されるでしょう。

もとはといえば、きぬいとの勝手な問題意識だったのですが、コミュニティの運営の方が拾ってくださり、
その重要性と方法論についてオープンにしてくれた、というのことには大きな意義があるなあと考えています。
プログラム言語コミュニティとして、IT領域以外のユーザが多いという特徴は、大変に特殊です。
それ故に、エンジニアリングでは「常識」となるようなことが浸透しづらい側面があり、
それが業務の効率や再現性などに支障をきたす要因になりうる場面がある、という問題意識があります。
そしてこの問題は、簡単なコツといくつかの基本的な関数で解決できる問題なので、
解決しようじゃないか、というモチベーションです。

ちなみに、応用的な管理方法は、以下のスライドがめちゃくちゃ有効です。

speakerdeck.com

今回のお話

フォルダを管理できても、ファイル名がわかりにくく、統一感のない名前であると全くもって意味がありません。

「ある程度分かりやすい構成」は前回も示したとおりですが、例えば以下です。

分析プロジェクト
├ 入力データ
 |    ├ 20200806_実験データ.csv
 |    ├ 20200807_実験データ.csv
 |    └ 20200808_実験データ.csv
├ 出力データ
 |    ├ csvファイル
 |     |    ├ 20200806_140340_実験回帰分析結果.csv
 |     |    ├ 20200807_143040_実験回帰分析結果.csv
 |     |    └ 20200808_145520_実験回帰分析結果.csv
 |    ├ グラフ
 |     |    ├ 20200808_153422_回帰直線.png
 |     |    └ 20200808_173006_残差プロット.png
├ ソースコード
 |   ├ 20200805_前処理自動化.R 
 |   ├ 20200806_回帰分析.R
 |   ├ 20200807_回帰分析.R
 |   └ 20200808_回帰分析.R
├ 実験管理
 |   └ 分析実施記録.**txt**

そして、ダメな例はこちらです。

分析プロジェクト
├ 入力データ
 |    ├ 実験データ.csv
 |    ├ 実験データ_なんか修正.csv
 |    └ 実験データ_最終版.csv
├ 出力データ
 |    ├ csvファイル
 |     |    ├ 実験結果.csv
 |     |    ├ 実験結果_修正版.csv
 |     |    └ 実験結果_先輩.csv
 |    ├ グラフ
 |     |    ├ グラフ_きれい.png
 |     |    └ グラフ_毛虫.png
├ ソースコードこちらです。
 |   ├ 前処理.R 
 |   ├ 分析.R
 |   ├ 分析_修正.R
 |   └ 分析_本番.R
├ 実験管理
 |   └ 分析実施記録.txt

これでは、いけませんね。何がいけないのか……

  • いつやったのかが分からない
  • 何をやったのか分からない
  • 何が出力されているのかがわからない

よろしくないですね。どうにかならんのか。
どうにかなります。そう、Rならね。

特定の文字列を含むファイルを検索する

文字列の検索には grep() 関数などを使うことができます。
今、私達は上記の「分析プロジェクト」ディレクトリにいて、
2020年8月8日の実験結果を参照したいとします。
その時は、例えば以下のような感じ。

getwd()
# 出力例はこんな感じ
> "C:/Users/Documents/分析プロジェクト" 

# フォルダの全てのリストを確保する
output_file_list <- list.files("./出力データ/csvファイル")

# そのうち、"20200808"が含まれるファイルを出力する
output_0808 <- output_file_list[grep("20200808", output_file_list)]
output_0808
# 出力例はこんな感じ
> [1] "20200807_143040_実験回帰分析結果.csv" "20200808_145520_実験回帰分析結果.csv"

grep() 関数は「その文字列が含まれるリストが、何番目にあるか」を数字で答えてくれます。
ですので(気持ち悪いですが)「全リストのうち、『20200808』が含まれるファイルの要素」だけを取り出す、
という操作でファイルを見つけています。気持ち悪い。でも返ってくればよいのです。

これで、我々は「規則性のある」ファイル名であれば、
その規則を指定することでファイルを検索することができる可能性を手にしました。
ですが、規則性のあるファイル名をつけるのは人間です。
人間は規則性のあるファイル名をいちいち間違えずに付与することはとても苦手です。
苦手でなければきぬいとはこんな記事を書いていません。

命名規則を定義する

コンピュータは「規則的な構造」を処理することが非常に得意です。
コンピュータが探しやすい名前にすると、「いつのデータを参照したい」か、
「どの分析を参照したい」か、「なんの出力結果を確認したい」か、
などが探しやすくなります。
ここに「これが正解だ」という明確な正解は実際のところありません。
少なくとも「いつやったか」の明記はある程度広く同意されるかと思っていますが、
たとえば「どの分析か」は、どの程度の詳しさで書く必要があるのかはプロジェクトに強く依存します。
プロジェクトによっては「回帰分析」と分かればいいものもあれば「ポアソン回帰モデル」であるところまで分かる必要がある場面もあるし、
「前処理パターン1を使ったロジスティック回帰分析」など、より細かいファイル名が必要かも知れません。
ここは「どれくらいの詳しさで命名規則を定めるか」と「どこからを実験管理記録で管理するか」の問題なので、
みんなで試行錯誤して決めましょう。
今回は「規則性のある名前が重要ですよ」という話が伝わればよいので、最低限の情報でコントロールします。

日付や時刻を取得する with R

Rで今日の日付を知りたい場合、 Sys.Date()関数を使います。

Sys.Date()
> "2020-09-19"

とても簡単ですね。え?「時刻も取得したい」?
そんなときは format 関数と Sys.time() 関数を組合せて使いましょう。
ちなみに単純に Sys.time() を使うと以下のような値が返ってきます。

Sys.time()
> "2020-09-19 16:07:39 JST"

これでもいいですが、もっと自由に時間表記を抜き出したいです。
そんなときに format 関数を使うと便利。

nowtime <- format(Sys.time(), "%H:%M:%S")
nowtime
> "16:08:54" # 実行した時の時間

ここで、 "" でくくった文字列の中に % というものがありますが、
これは format 関数で表現する際に、どの時間単位を当てはめるかを指定するキーとして、
続くアルファベットを用いるという宣言のための演算子です。
「何時」は H 、「何分」は M 、そして「何秒」は S で指定します。
% がない場合は、それぞれ文字列として表現されます。
ここ以外にもいろいろのアルファベットによって出力の仕方を指定できます。
もちろん日本語でも。いろいろ調べていじってみてください。

基本的に、Sys.Date()format(Sys.time(), %y-%m-%d) と等価です。TPOとかで使い分けましょう。

文字列をつなげる

Rには paste() 関数があります。, で文字列を区切るだけで文字列を結合してくれます。
これで「日付+分析名」を作成できますね。

Exp_day <- Sys.Date()
Exp_name <- "回帰分析"

file_name <- paste(Exp_day, Exp_name, sep = "_")
## 出力例
file_name
> [1] "2020-09-19_回帰分析"

sep は、文字列の区切りとして使う記号を指定できます。今回はスタンダードに _ を指定しました。
これより柔軟に文字列を結合できる関数に paste0() 関数があります。きぬいとはこっち派です。

Exp_day <- Sys.Date()
Exp_name <- "回帰分析"

file_name <- paste0(Exp_day, "_", Exp_name)
## 出力例
file_name
> [1] "2020-09-19_回帰分析"

「つなげたい順に並べる」だけ。なんてシンプルなんだ。あとは上に .csv とかつければ良いのです。
例えば model という名前で回帰分析の結果を保持しているとします。
以下のような形で、出力結果を然るべき場所に格納できます。
相対パスとかの情報は、コタママさんのスライドか、前の記事を参照してください。

output_name <- paste0("./出力データ/csvファイル/",file_name, ".csv")
write.csv(model, output_file, row.names = FALSE)

こんな感じで、規則性のあるファイル名を最初に定義してしまうと、
規則性のある形で出力することが可能ですね!べんり!
え?「いちいち日付や時刻を呼び出して、ファイル名作るのめんどくさい?」
関数化しちゃえばいいじゃない…… *2

次回予告

ここまで1個1個をRのコンソールにぶん投げて行う方法でしたが、
もちろん、ここは自動化したいと思うのが人間。次回はそんなお話。

  • インプットとアウトプットのフォルダを指定しちゃう関数を作っちゃう
    • それを呼び出したい(source()メソッド)
  • インプットからアウトプットまでの一連の流れを関数にしてしまう
    • 分析する方法、データ、アウトプットの形式が全部定まっている場合
  • 探索的な分析場合の命名規則はどうすればいいの?
    • 探索パターンを列挙しそれ別に出力する
  • 「分析プロジェクト」の拡張

*1:日付的に今日

*2:今回はbaseライブラリ周辺にある基本的な関数を使って、レガシーなコーディングでやっていますが、もちろんモダンな管理方法はいくらかあるでしょう。そのうち記事にしたいです。