概要


前書き
- 本ドキュメントでは、Rのコーディング規約について明記しております。
- 基本的には、Hadley WickhamによるStyle guidegoogle社で提案された規約をベースにします。

表記について
- パッケージ名は{}で表記
- 関数名は’’で表記

参照サイト
 Advanced R, Google’s R Style Guide, R packages
 Lint_tool_with_R
 Rでコーディングスタイルを適用させる方法
 gepuro流☆変数名の付け方

ご意見やご指摘など
- よりよいものにしてきたいと思います。こうした方がいいのではというご指摘があれば、コメントをよろしくお願いいたします。
- 下記のいずれかでご連絡・ご報告いただけますと励みになります。
 Twitter, GitHub



環境




命名規則


ファイル名

  • Rのスクリプトファイルは、「-」(ハイフン)を区切り文字にした「.R」で終わる

  • 実行順番がある場合は、先頭に付ける
    ex) “00-read-date-file.R”, “01-date-preprocess.R”

  • Rのオブジェクトファイル(’save’で作成して、’load’で読み込み)は、「.RData」で終わる
    ex) “forecast-result-201501.RData”


オブジェクト名

  • 下記に基づき、語彙を選択(『リーダブルコード』を要参照)
     - 機能を推測できる命名
     - 知らせなければならない情報を追加
     - 曖昧性が少ない
     - 汎用的/抽象的な名前を避ける
     - 予約語は避ける

  • 変数名:
     - 英語名詞
     - snake_case(小文字単語を「_」で区切り)
     - ハンガリアン記法は不要(命名に拘束力がないので、名前と実体が異なる混乱を招くため)
     - ({ff}, {ffbase}によるオブジェクトは「ff」とすること(知らずに展開するとメモリを使い切る場合があるため))
     - 名前属性もsnake_case
      ex) names(input_vec) <- c(“var_1”, “var_2”)
     - 定数は「大文字」単語を「
    」で区切る(名前属性も「大文字」単語のルール)
      ex) “SET_FEATURE_NUM”
     - ひとつの変数はひとつの目的だけで使う(目的が変わった場合は変数を流用しない)

  • 関数名:
     - 英語動詞
     - lower Camel Case(小文字で書き始め、単語の先頭が大文字)
      ex) selectValueName
     - ブール値を返す関数は頭にis, has, can, should があるとわかりやすい
      ex) isConvergence
     - 名前から期待される通りの結果を出力、動作や合わせた明確な名前選びを
     – getやsetを安易に使わない(適した語彙を選択すること)
      属性値の取得・設定をイメージするので(属性値の取得・設定する場合はよい)

  • クラス名:
     - Camel Case(単語の先頭が大文字)
      ex) SelectValueName

  • パッケージ名:
     - 「r」 + Camel Case(単語の先頭が大文字)
      ex) rSelectValueName



構造


ディレクトリ構造

  • Rのスクリプトファイルは下記の構造で管理(後述のShinyアプリ化する際、/script/以下の処理を読み込むことで対応できるように)
     - 「/script/」以下は各言語による分析用スクリプトを配置
     - 「/script/R/class/」以下はRのクラスファイルを置く
     - 「/script/R/fun/」以下はRの関数ファイルを置く
     - 「/doc/」以下はドキュメント(非自動生成)ファイルを置く
     - 「/data/」以下はデータセット(「.RData」など)を配置
    (例)「Analytics-Name」が分析名(命名規則は考え中)
     /Analytics-Name/script/R/
     /Analytics-Name/script/R/class/
     /Analytics-Name/script/R/fun/
     /Analytics-Name/script/SQL/
     /Analytics-Name/doc/
     /Analytics-Name/data/

  • Shinyアプリを作る場合
    Shiny, The Shiny Cheat sheet
     - 「app.R」でまとめず「ui.R」と「server.R」に分ける
     - 共通処理は「global.R」で定義
     - ただし、定数の設定は「global.R」と別ファイル(「def-constant.R」)で記述
     - 「ReadMe.Rmd」でReadMeを記述(shinyによるアプリTOPで表示)
    (例)「App-Name」がアプリ名(命名規則は考え中)
     /App-Name/ui.R
     /App-Name/server.R
     /App-Name/global.R
     /App-Name/def-constant.R
     /App-Name/ReadMe.Rmd
     /App-Name/script/R/
     /App-Name/script/SQL/
     /App-Name/data/

  • パッケージ化する場合
     規定の構造に準ずる
     - (例)
     /PACKAGE_NAME/DESCRIPTION
     /PACKAGE_NAME/NAMESPACE
     /PACKAGE_NAME/NEWS
     /PACKAGE_NAME/R/
     /PACKAGE_NAME/man/
     /PACKAGE_NAME/src/
     /PACKAGE_NAME/tests/
     /PACKAGE_NAME/data/
     /PACKAGE_NAME/inst/

 - パッケージ作成時に参照しておいた方がいい資料
  R packages
  東京R非公式おじさんが教える本当に気持ちいいパッケージ作成法


ファイル記述順

  1. ファイルコメント(名前、最終更新日、スクリプトの目的や入出力など)
    – 可能であれば、「コメント」の項目で触れている{roxygen2}に準じたコメントを書く
  2. ’source’や’library’による外部スクリプトの読み込み
  3. 関数定義
  4. データ処理
     (1) 入力
     (2) 前処理
     (3) 統計処理や集計
     (4) 可視化
     (5) 出力
  • コピーライトは「DESCRIPTION」に書き、TODOやBUGFIXなどは「NEWS」に書く

  • 分析スクリプト(関数定義ファイルは除く)は上から順に読み込んで、正しく実行されることが必須
     (RStudio上で「Ctrl + Alt + R」のショートカットにて、コード全体を実行)


ファイルを分ける基準

  • 100行から200行に収めること
    – 自作関数定義ファイルで、各関数間に依存関係がない場合はひとつのファイルでも可(コメントを残しておくこと)
    –自作関数定義ファイルにて定義した関数を呼び出す関数は、別ファイルで定義すること

関数を分ける基準

  • ひとつの関数にひとつの機能

  • ネストが深くなる場合(3を目安)は別の関数に分ける
    – ただし、機能的に分けるべきでない場合はひとつのままにする(コメントを残しておくこと)



構文


代入

  • オブジェクトへの割り当ては「<-」を使う
  • 関数の引数は「=」を使う

スペース

  • 半角スペースひとつを「スペース」とし、次の条件のときに「スペース」
    – 演算子(「=, +, -, <-」など)の「前後」
     ex) 1 + 1
    – ただし、「:, ::, :::」の後はスペースをあけない
     ex) 1:10, dplyr::select()

– 「,」の「後」(行列や配列の添字操作時の「,」も)
 ex) lm(y ~ x1 + x2, data = data.frame(y = rnorm(100), x1 = rnorm(100), x2 = rnorm(100)))
 ex) iris[1:10, ]

  • ‘if’と’for’, ’function’などのカーリーブラケット({ })でスコープを作る関数「( )」の前後は半角スペースひとつ分をあける
  • 関数呼び出しの前後は半角スペースをあけない
exampleFunction <- function (
  iteration_num = 10,
  ...
){

  # 演算子(「=, +, -, <-」など)の「前後」はスペース
  res_pow <- numeric(iteration_num)
  
  # 「for文の繰り返し指定は改行しない」
  for (iter in seq(from = 1, to = iteration_num)) {
    if (iter > 10) {
      return(-1)  
    } else {
      res_pow[iter] <- iter * iter
    }
  }

  plot(res_pow) # 「関数呼び出しの前後は半角スペースをあけない」

  return(1)
}

インデント

  • 半角スペースふたつ分で字下げして、スコープごとに下げる(Pythonと同じ)
     (RStudioの「Tools -> Global Option -> Code -> Display」の「Show indent guides」でインデント表示を設定可能)
     (RStudioの「Tools -> Global Option -> Code -> Editing」の「Insert space for tab」で「Tab width」を「2」に設定しておくと、タブで半角スペースふたつ分を設定できる)

改行

  • 一行を80文字程度を目安に改行(RStudioの「Tools -> Global Option -> Code -> Display」の「Show margin」でライン表示を設定可能)
  • 関数内に引数は意味合いごとに改行(ただし、関数が引数の値として際には改行しない)
  • ’dplyr’のパイプ処理ではチェイン演算子(%>%)ごとに改行

  • ’for’文の繰り返し指定は改行しない
  • ’if’文や’for’文、’apply’ファミリーはたとえ短くても一行でまとめてかかない(ワンライナー禁止)

res_sum <- 0
for (sum_iter in sample(x = seq(from = 1, to = 10), size = 1000, replace = TRUE)) {
  res_sum <- res_sum + sum_iter
}
  • 「;」で複数コードを一行にまとめられるが、使わずに複数行に書く

コメント

  • コメントの内容
    – どうしてこうしたか、意図を記述(アルゴリズムの選択理由や、パラメータ設定の根拠など)
    – 読めばわかる挙動をひとつひとつ追って書くのは不要
    – 定数の意図(理論値、限界値の決め方)は書く
library(rugarch)
# 2週間分のARMAを考慮するため、「armaOrder = c(14, 14)」に設定
# キャンペーンや広告予算により、日毎に分散が変動すると考えられるため、分散不均一性を説明できるモデルを導入(「variance.model」を定義)
# 分布は「綺麗な正規分布」にはならないと想定できるため、「distribution.model = "snorm"」に設定
model_spec <- rugarch::ugarchspec(
  mean.model = list(
    armaOrder = c(14, 14), include.mean = TRUE,
    arfima = TRUE,
    external.regressors = NULL
  ),
  variance.model = list(
    model = "fGARCH", garchOrder = c(1, 1), submodel = "GARCH",
    external.regressors = NULL
  ),
  distribution.model = "snorm"
)
  • コメントの記述
    – 「#」の後は半角スペースをひとつ空ける
    – コードの後ろには書かない
    – 関数内ではなるべく書かない

  • ブロック分け
    – 「# Load data process —————————」のように、コードの塊を分ける
     (RStudio上で「Ctrl + Shift + R」で挿入可能)

  • 関数定義の際は{roxygen2}のフォーマットで必ずコメントを記述
    – 「title, description, details」の各フィールドと「@param, @return, @examples」は必須項とする
    @param」は引数の説明(引数毎に記述)
    @return」は戻り値の説明
    @examples」は実行例
     http://r-pkgs.had.co.nz/man.html
     http://stackoverflow.com/tags/roxygen2/info

#' Sum of vector elements. (titleフィールド)
#'
#' \code{sum} returns the sum of all the values present in its arguments. (descriptionフィールド)
#'
#' This is a generic function: ... (detailsフィールド)
#'
#' @param ... Numeric, complex, or logical vectors.
#' @param na.rm A logical scalar. Should missing values (including NaN) be removed?
#' @return If all inputs are integer and logical, then the output
#'   will be an integer. If integer overflow ...
#' @examples
#' sum(1:10)
#' sum(1:5, 6:10)
sum <- function(..., na.rm = TRUE) {}

上記の記述(一部省略している)によって生成される’sum’のドキュメントが下記のリンク
 http://stat.ethz.ch/R-manual/R-devel/library/base/html/sum.html

  • コメントは日本語でもよいが、パッケージ化する際は英語にすること

  • パッケージ化する場合は{roxygen2}のフォーマットに準じたコメントを適宜行う
    Introduction to roxygen2


関数呼び出し

  • 必須項目の引数名は省略可
  • オプションの引数名を指定する場合は必須
     ex) read.table(“”, header = TRUE)
     ex) read.table(file = “”, header = TRUE)

  • 関数呼び出し時は「{PACKAGE_NAME}::‘FUNCTION_NAME’」で呼び出す関数の名前空間を明示する
  • ただし、Rの基本パッケージ({stats}, {graphics}, {grDevices}, {utils}, {datasets}, {methods}, {base})内の関数を呼び出す際には、パッケージ名は省略(アドオンパッケージは省略せず)
    【メモ】R のデフォルトパッケージを調べる #rstatsj

# {devtools}の関数呼び出し
devtools::session_info()
Session info --------------------------------------------------------------
 setting  value                       
 version  R version 3.2.0 (2015-04-16)
 system   x86_64, darwin13.4.0        
 ui       X11                         
 language (EN)                        
 collate  ja_JP.UTF-8                 
 tz       Asia/Tokyo                  
Packages ------------------------------------------------------------------
 package               * version   date      
 devtools              * 1.7.0     2015-01-17
 digest                * 0.6.8     2014-12-31
 DistributionUtils     * 0.5-1     2015-01-05
 evaluate              * 0.7       2015-04-21
 expm                  * 0.99-1.1  2014-02-12
 formatR               * 1.2       2015-04-21
 GeneralizedHyperbolic * 0.8-1     2015-01-05
 htmltools             * 0.2.6     2014-09-08
 KernSmooth            * 2.23-14   2015-02-11
 knitr                   1.10      2015-04-23
 ks                    * 1.9.4     2015-03-07
 lattice               * 0.20-31   2015-03-30
 magrittr              * 1.5       2014-11-22
 Matrix                * 1.2-0     2015-04-04
 misc3d                * 0.8-4     2013-01-25
 mvtnorm               * 1.0-2     2014-12-18
 nloptr                * 1.0.4     2014-08-04
 numDeriv              * 2012.9-1  2012-10-14
 Rcpp                  * 0.11.6    2015-05-01
 rgl                   * 0.95.1201 2014-12-21
 rmarkdown             * 0.6.2.4   2015-06-07
 Rsolnp                * 1.15      2014-11-24
 rstudioapi            * 0.3.1     2015-04-07
 rugarch                 1.3-4     2014-11-09
 SkewHyperbolic        * 0.3-2     2015-01-05
 spd                   * 2.0-0     2014-07-02
 stringi               * 0.4-1     2014-12-14
 stringr               * 1.0.0     2015-04-30
 truncnorm             * 1.0-7     2014-01-21
 xts                   * 0.9-7     2014-01-02
 yaml                  * 2.1.13    2014-06-12
 zoo                   * 1.7-12    2015-03-16
 source                            
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 Github (rstudio/rmarkdown@8c9e25b)
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
 CRAN (R 3.2.0)                    
# 基本パッケージ
sessionInfo()
R version 3.2.0 (2015-04-16)
Platform: x86_64-apple-darwin13.4.0 (64-bit)
Running under: OS X 10.10.2 (Yosemite)

locale:
[1] ja_JP.UTF-8/ja_JP.UTF-8/ja_JP.UTF-8/C/ja_JP.UTF-8/ja_JP.UTF-8

attached base packages:
[1] parallel  stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
[1] rugarch_1.3-4 knitr_1.10   

loaded via a namespace (and not attached):
 [1] Rcpp_0.11.6                 rstudioapi_0.3.1           
 [3] magrittr_1.5                devtools_1.7.0             
 [5] misc3d_0.8-4                lattice_0.20-31            
 [7] Rsolnp_1.15                 stringr_1.0.0              
 [9] tools_3.2.0                 xts_0.9-7                  
[11] SkewHyperbolic_0.3-2        GeneralizedHyperbolic_0.8-1
[13] spd_2.0-0                   grid_3.2.0                 
[15] KernSmooth_2.23-14          htmltools_0.2.6            
[17] yaml_2.1.13                 digest_0.6.8               
[19] rgl_0.95.1201               numDeriv_2012.9-1          
[21] Matrix_1.2-0                nloptr_1.0.4               
[23] formatR_1.2                 DistributionUtils_0.5-1    
[25] ks_1.9.4                    evaluate_0.7               
[27] rmarkdown_0.6.2.4           stringi_0.4-1              
[29] expm_0.99-1.1               truncnorm_1.0-7            
[31] mvtnorm_1.0-2               zoo_1.7-12                 
  • RStudioで「パッケージ名」+「::」で(+「キーボードのTabキー」)パッケージリストがサジェスト可

  • 下記リンクにある’lsp’を定義して使うと、一覧リストがRコンソール上で取得できる
    Show names of everything in a package

lsp <- function(package, what = 'all') {

  ns <- asNamespace(package)

  # base package does not have NAMESPACE
  if (isBaseNamespace(ns))
    return(ls(.BaseNamespaceEnv, all.names = TRUE))
  else {
    # for non base packages
    if (exists('.__NAMESPACE__.', envir = ns, inherits = FALSE)) {
      wh <- get('.__NAMESPACE__.', envir = asNamespace(package, base.OK = FALSE),
                inherits = FALSE)
      if ('?' %in% what) 
        return(ls(wh))
      if (!is.null(what) && !any(what %in% c('all', ls(wh))))
        stop('what is invalid; see ?rawr::lsp \'details\'')
      tmp <- sapply(ls(wh), function(x) getNamespaceInfo(ns, x))
      tmp <- rapply(tmp, ls, classes = 'environment', 
                    how = 'replace', all.names = TRUE)
      if (is.null(what))
        return(tmp)
      if (what %in% 'all')
        return(ls(getNamespace(package), all.names = TRUE))
      if (any(what %in% ls(wh)))
        return(tmp[what])
    } else 
      stop(sprintf('no NAMESPACE file found for package %s', package))
  }
}

lsp("devtools", "exports")$exports
  [1] "add_path"                  "add_rstudio_project"      
  [3] "add_test_infrastructure"   "add_travis"               
  [5] "as.package"                "bash"                     
  [7] "build"                     "build_github_devtools"    
  [9] "build_vignettes"           "build_win"                
 [11] "check"                     "check_cran"               
 [13] "check_doc"                 "clean_dll"                
 [15] "clean_source"              "clean_vignettes"          
 [17] "compile_dll"               "compiler_flags"           
 [19] "cran_env_vars"             "create"                   
 [21] "create_description"        "dev_example"              
 [23] "dev_help"                  "dev_meta"                 
 [25] "dev_mode"                  "dev_packages"             
 [27] "devtest"                   "document"                 
 [29] "eval_clean"                "evalq_clean"              
 [31] "find_rtools"               "find_topic"               
 [33] "get_path"                  "github_pat"               
 [35] "github_pull"               "github_release"           
 [37] "has_devel"                 "has_tests"                
 [39] "imports_env"               "in_dir"                   
 [41] "inst"                      "install"                  
 [43] "install_bitbucket"         "install_deps"             
 [45] "install_git"               "install_github"           
 [47] "install_gitorious"         "install_local"            
 [49] "install_svn"               "install_url"              
 [51] "install_version"           "is.package"               
 [53] "lint"                      "load_all"                 
 [55] "load_code"                 "load_data"                
 [57] "load_dll"                  "loaded_packages"          
 [59] "missing_s3"                "ns_env"                   
 [61] "on_path"                   "parse_deps"               
 [63] "parse_ns_file"             "pkg_env"                  
 [65] "r_env_vars"                "release"                  
 [67] "release_checks"            "reload"                   
 [69] "revdep"                    "revdep_check"             
 [71] "revdep_check_save_logs"    "revdep_check_save_summary"
 [73] "revdep_check_summary"      "revdep_maintainers"       
 [75] "run_examples"              "session_info"             
 [77] "set_path"                  "setup"                    
 [79] "show_news"                 "source_gist"              
 [81] "source_url"                "submit_cran"              
 [83] "test"                      "unload"                   
 [85] "use_appveyor"              "use_build_ignore"         
 [87] "use_cran_comments"         "use_data"                 
 [89] "use_data_raw"              "use_git_hook"             
 [91] "use_package"               "use_package_doc"          
 [93] "use_rcpp"                  "use_readme_rmd"           
 [95] "use_revdep"                "use_rstudio"              
 [97] "use_testthat"              "use_travis"               
 [99] "use_vignette"              "wd"                       
[101] "with_collate"              "with_debug"               
[103] "with_env"                  "with_envvar"              
[105] "with_lib"                  "with_libpaths"            
[107] "with_locale"               "with_options"             
[109] "with_par"                  "with_path"                

関数定義

  • 用途がない限り、引数に「…」(dotsMethods)を与えない(引数名を間違えた際にも実行できてしまうケースを避ける)

  • パラメータに関しては、用途ごとにlistにまとめた引数を用いる

  • 明示的に’return’を書く


クラス定義


条件分岐

  • 条件式の引数の並びは文法と同じに
    if (age >= 20) 「もし年齢が20才以上ならば」(よい)
    if (20 <= age) 「もし20才が年齢以下ならば」(だめ)

  • (左辺が「調査対象」(変化する)、右辺が「比較対象」(あまり変化しない))

  • 可読性を考慮して、’ifelse’はネストさせてはいけない


ループ

  • 極力ループは避け、ベクトル・行列演算または’apply’ファミリーで処理する

  • 理由がない限り、’while’は使わない(無限ループを起こしやすい)

  • i, j, k などのfor文の添字は事前に必ず初期化する

  • ループ回数が多くなる場合は{iterators}と{foreach}を利用する

library(iterators)
library(foreach)

SET_ITER_NUM <- 5000
i <- res_sum <- 0
foreach_res <- foreach::foreach (i = iterators::icount(SET_ITER_NUM), .combine = c) %do% {
  res_sum <- res_sum + i
  i / res_sum
}
  • 並列処理が考えられる場合の{foreach}は、{pforeach}を利用しておく(並列化・非並列化の切り替えが手軽にできる)
    pforeach
# library(devtools)
# devtools::install_github("hoxo-m/pforeach")
library(pforeach)
library(randomForest)
library(kernlab)

SET_PARALLE <- list(
  IS_PARALLEL = TRUE,
  CORE = 3
)

data(spam)
fit_rf <- pforeach::pforeach(
  ntree = rep(250, SET_PARALLE$CORE), .c = c,
  .export = "spam", .packages = "randomForest",
  .parallel = SET_PARALLE$IS_PARALLEL, .cores = SET_PARALLE$CORE
)({
  randomForest(type ~ ., data = spam, ntree = ntree)
})


分析


データ処理


モデリング


パッケージ


可視化



その他



[1] "2015-06-11 23:13:22 JST"