はじめに
この記事は R Advent Calendar 2021の23日目の記事です。
何を話すか
少し早いですが、2022年に向けてのtidymodels使用時の命名規則を勝手に打ち出したいと思います。
コードの理解促進のため以下のように、関数から作ったオブジェクトの名前を統一していきましょう。
以下の設計に至った理由も説明していきます。
names[suggestion] | objects |
---|---|
ames | data(ames) |
ames_split | initial_split() |
ames_train | training() |
ames_test | testing() |
ames_cv | mc_cv() vfold_cv() bootstraps() validation_split() |
ames_rec | recipes objects finalize_recipe() |
ames_spec | parsnip models finalize_model() |
ames_wflow | workflows objects finalize_workflow() update() |
ames_fit | fit() |
ames_cv_res | fit_resample() |
ames_grid | grid_*() crossing() |
ames_tune_res | tune_*() |
ames_last_res | last_fit() |
*_param | select_best() parameters() |
*_perf | yardstick result collect_metrics() rank_results() |
Don't create | control_*() prep() bake() metric_set() |
R言語の命名規則
プログラミング言語の界隈ではオブジェクトの命名規則について、様々な議論がなされています。
好きな名前を付けた方が楽しい、と考えられるかもしれません。
正直、私も命名規則に縛られて、名前を付けるのは面倒だと感じます。
しかし命名規則は存在しています。
その理由はコードを書いている際のストレスの軽減と、後から読み直した時の分かりやすさでしょう。
オブジェクトの名前と中身が結びつかない場合、すべてのコードを読み直してオブジェクトと中身の関係を理解しなおす必要があるかもしれません。
好きな名前を付けていては、他の人が理解できないため自分以外がコードを読む時に不親切です。
別の例で表現するならば、メールアドレスが人の名前でなく意味がない名前だと、送り先とメールアドレスが一致せず本当に合っているのか不安になってしまうでしょう。
R言語には公式のスタイルガイドは無いのですが、googleの出しているR styleguideがよく参考にされています。
最近ではtidyverseのstyleguidも発行されています。
R言語という広い範囲のスタイルガイドからいくつかルールを抜き出すと、
- 変数名は短く簡潔であること
- 関数名は動詞
- 変数名は名詞
- 変数名にドット記号は使わない
などがあります。
また、スタイルガイドごとに推奨されている規則が異なっていることもあります。
- 変数名は大文字を使う[google]
- 変数名は小文字とアンダースコアを使う[tidyverse]
あくまでもルールであり正解は無いのです。
ここには載せていませんが、古くからのRユーザーは変数名にドット記号を使うというルールに従っていた人もいたのではないでしょうか?
Rでドット記号ってなんにつこたらええんやろ? stackexchangeより
実際にそのようなスタイルが流行っていました。
絶対使ってはいけないわけではありませんが、ドット記号はジェネリック関数の中で使われるため混乱の原因になるためいくつかのスタイルガイドはその方式を採用していないのでしょう。
そして命名規則を守ることは大変だとstyle guideで言及されています。
Generally, variable names should be nouns and function names should be verbs. Strive for names that are concise and meaningful (this is not easy!).
-- The tidyverse style guide [2.1 Object names]
tidymodelsに関する規則
tidymodelsはtidyverseと同じくRstudio社が主導で開発しているパッケージです。
開発に関わっているメンバーもtidyverseやRのルールには詳しい方々ばかりです。
以下のTidy Modeling with R:通称TMwRはtidymodels開発陣が書いたドキュメントであり、規則や哲学について少し書かれています。
しかし、TMwRのコードを読んでいてもオブジェクトの中身が同じにもかかわらず異なる名前を付けている場合があります。
そこで、TMwRに出てくる名前と中身の関係を整理し、私から命名規則を提案することで、皆さんがtidyなコードを書きたいと感じた時の助けになれたらと考えています。
追記:tidyverseの哲学や設計を知りたい人はこちら Tidyverse design guide
オブジェクト名と中身の関係
オブジェクトの名前は接尾語によって理解されやすく書かれていましたが、綺麗ではないと感じています。
主にTMwRを見て確認しましたが、tidymodelsのコードを書いている人のブログなども参考にしました。
以下のような関係で名前が付けられていました。
names | objects | names | objects | |
---|---|---|---|---|
*_split *_spl |
initial_split() | *_train | training() | |
spl | smooth.spline() | *_test | testing() | |
resample | mc_cv() | *_mod *_model *_spec |
parsnip models | |
*_folds | vfold_cv() | *_rec *_recipe |
recipes objects | |
rs | bootstraps() | *_rec_trained | prep() | |
*_val_set | validation_split() | *_processed | bake() | |
*_metrics | metric_set() | *_wfl *_wflow |
workflows objects | |
ctrl keep_pred ctrl_AAA |
control_AAA() | *_param | parameters() | |
*_grid | grid_AAA | *_tune | tune_grid() | |
grid_AAA | tune_race_AAA | *_fit | fit() | |
*_res | collect_metrics() collect_predictions() fit_resample() last_fit() and mode... |
何人かの書いたtidymodelsコードを読んで思ったことは、
- 交差検証に名前のばらつきがある
- _resの役割が曖昧
- 細かい設定を変数にしてしまう
これらが名前付きオブジェクトを複雑にしている原因だと考えています。
そこである程度のグループに分けて簡便にしてみようと思います。
以下の表を理解しやすいものにするため、データのamesを利用しているという想定で説明します。
names[suggestion] | objects |
---|---|
ames | data(ames) |
ames_split | initial_split() |
ames_train | training() |
ames_test | testing() |
ames_cv | mc_cv() vfold_cv() bootstraps() validation_split() |
ames_rec | recipes objects finalize_recipe() |
ames_spec | parsnip models finalize_model() |
ames_wflow | workflows objects finalize_workflow() update() |
ames_fit | fit() |
ames_cv_res | fit_resample() |
ames_grid | grid_*() crossing() |
ames_tune_res | tune_*() |
ames_last_res | last_fit() |
*_param | select_best() parameters() |
*_perf | yardstick result collect_metrics() rank_results() |
Don't create | control_*() prep() bake() metric_set() |
なぜ上の表のように分類したのか説明します。
1. parsnipではspecificationとしてデータと分離する思想なので_specとする
modelなどの表現を使わずにspecとしました。
2. prepやbakeは一時変数です、使う際に実行してください
tidymodelsを使ってモデリングする中で、prep,bakeによって作り出したデータはモデル作成というよりもデータの確認の目的に使われていたので命名規則を設けませんでした。もしもモデルの入出力に指定するのであれば、_trainや_testとして扱うことをお奨めします。
3. control_*から始まる関数は直接オプションに書き込む
control引数に直接control_()を入力するようにしましょう。コードの一文は長くなりますが、control_()は複数種存在しているので複雑な変数を作成する必要がなくなります。
4. metric_setも同様に作らず直接オプションに書き込む
3と同じ意図です。metric_set()に種類はありません。指標を多く選択するとコードを長くしてしまいますが、大きな問題ではない考えているので変数を作らずにオプションへ書き込んでください。
5. 交差検証目的である変数はbootstrapsも含めて*_cvとする
*_cvの接尾語に統一しました。
bootstrapsは様々な使い方ができますが、機械学習の目的では交差検証データとして使いましょう。bootstrapsもvalidation_splitも関数の名前にcvがついていませんが、どちらも「rsetクラス」でありfit_resample等に入力することができるという共通点があります。
余談ですがinitial_splitは「rsetクラス」でなく「rsplitクラス」です。
複数の交差検証オブジェクトを一回のコードで作り出す時には、それぞれに個別の名前を付けてあげてください。
6. 交差検証データを使った結果に*_resを使う
なんでも任されていた*resの使い方を定義しました。
tune*やfit_resampleは交差検証データでfit()させた結果を返します。集計もされていないですし、学習済みモデルとして提供されているわけでもない、という観点から「結果」という性質が強いと考えています。"resample_results" "tune_results"をクラスとして持っているのでcollect_metrics()が使用できます。更に言えばstacks::add_candidates()やtidyposterior::perf_mod()の入力にもなります。
last_fit()の実行結果はクラスに"last_fit"を持ち、これもまたcollect_metrics()が使えます。
7. 性能を確認している場合は*_perfを使う
今まで「_res」とされてきたオブジェクトたちの中には「評価指標を使い計測された結果」も含まれていました。
これらを「パフォーマンス」という名前で表現することで「結果」との差別化を図りました。
よって性能評価の結果が含まれる場合は「_perf」を、予測結果などを蓄積させたものに「_res」を使うようにしましょう。
最後に
抜け漏れがあれば教えてください。
tidymodelsは関数が変化する可能性があるので、これで決定でなく修正していく必要があると考えています。
更新歴
2021-12-19:英語版もつくった
2021-12-18:初版投稿