Triad sou.

Reference class に roxygen 用のコメントを入れる方法

お詫び

下記の内容は不十分だったため、roxygen2 について記事を書きました。

内容

Reference class を用いたパッケージの R スクリプトroxygen 用のコメントを入れる方法を模索してみました。
S4 の様なドキュメントの自動生成は難しそうですが、充分使えそうな形にはまとまったので、メモしておきます。

roxygen コメントの基本

roxygen コメントをパッケージ用の関数やクラスの定義の前に書いておくと、roxygenize() 関数で Rd ファイルを自動的に作ってくれます。
Rd ファイルを手動でいちいち編集するのは手間がかかるため、とても便利なパッケージだと思います。

とっても簡単な例

f1 というとても簡単な関数に roxygen コメントをつけてみました。

#' Description
#'
#' Details
#'
#' @title f1: A test function
#' @param x numeric number
f1 <- function(x) {
  print(x)
}

roxygen コメントは [#'] で始まります。
一行目には \description フィールドに入力したい内容を書き込みます、一行に収まるようにする必要があります
二行目以降には \details フィールドに入力したい内容を書き込みます、複数行に分かれていても問題ありません。
それ以外のフィールドは、\details フィールドの後に [#' @keyword] の形式で入力しておきます。
そして、最後に続けて関数定義を入力します。
上の例では区切りに空の行を入力していますが、必須ではありません。


上のコメントを入れたものに roxygenize() 関数を適用して生成される f1.Rd ファイルは

\name{f1}
\alias{f1}
\title{f1: A test function}
\usage{f1(x)
}
\description{Description}
\details{Details}
\arguments{\item{x}{numeric number}
}

のようになります。
シンプルな関数の場合は、\name, \alias, \usage フィールドを自動的に作成してくれます。


ヘルプファイルは



こんな感じになります。

Reference class 用サンプルパッケージ

上記のように、パッケージ作成に通常の関数などを利用している場合はとても簡単で、ソース管理の手間が省けるのですが、残念ながら Reference class にはまだ対応していません。
Re: Status of R5 support (Roxygen-devel mailing list) の様に書いても単なるコメントとして判定されるため、S4 class の様にメソッド用の roxygen コメントを別に書かなければなりません。
ということで、Reference class 用サンプルパッケージを作っていきます。

パッケージの Rd ファイル用の roxygen コメント

パッケージの Rd ファイルも以下の様に書いておくことができます。

#' Description: A Test Package for Reference Class Documents Using 'roxygen'
#'
#' Details: This package is an example of how to document reference class methods.
#'
#' @rdname testRefclassPkg-package
#' @docType package
#' @aliases testRefclassPkg-package
#' @keywords documentation
#'
#' @title testRefclassPkg: A test package for reference class documents using 'roxygen'
#' @author Triad sou. \email{triadsou@@gmail.com}
#' @seealso \code{\link[methods:ReferenceClasses]{ReferenceClasses}}
#' @exportPattern '^[^\\.]'
#'
#' @name testRefclassPkg
NULL

ポイント

  • @rdname testRefclassPkg-package: Rd ファイル名を指定します、好きな名前を指定して問題ありません。
  • @docType package: パッケージ用の Rd ファイルであることを指定します。
  • @exportPattern '^[^\\.]': NAMESPACE ファイルを自動生成するために、すべてにマッチする directive を書いておきます。
  • @name testRefclassPkg: \name フィールドを指定します。パッケージの roxygen コメントを書く場合、最後に続けて関数定義やクラス定義などを記入できないため、NULL や {} などを記入します。この場合、関数名やクラス名が存在しないため、\name フィールドを自動的に作成できずエラーが発生してしまいますが、自分で指定しておけば問題ありません。alias が勝手に複製されるバグを回避するため、一番最後に書いてください。

Reference class の定義と Rd ファイル用の roxygen コメント

基本的に S4 class と変わらないです。

#' Description: My Test Class
#'
#' Details: \code{test_refclass} class is a test reference class that has three methods.
#'
#' @docType class
#' @aliases test_refclass-class
#'
#' @title test_refclass: My test class
#' @slot f1 numeric number of a test field
#' @slot f2 numeric number of a test field
#' @keywords documentation
#' @seealso \code{\link{m1}}, \code{\link{m2}}, \code{\link{m3}}
#'
#' @name test_refclass
test_refclass <- setRefClass(

  Class = "test_refclass",
  
  fields = c("f1", "f2"),

  methods = list(

    m1 = function(arg1 = 1) {
      f1 <<- arg1
    },

    m2 = function(arg1) {
      if (class(f1) == "uninitializedField") f1 <<- 2
      f1 + arg1 * 2
    },

    m3 = function(arg1) {
      if (class(f1) == "uninitializedField") f1 <<- 3
      f1 + arg1 * 3
    }

  )
)

ポイント

  • @docType class: クラス用の Rd ファイルであることを指定します。
  • @slot: fields は @slot で記入していきます。複数のフィールドがある場合は @slot を複数書いていけば OK です。roxygen は @slot や @section でエラーは出ないものの、何も処理をしてはいないので、適切にドキュメントを作成できません。roxygen2 を利用した方が良いでしょう。
  • @aliases test_refclass-class: クラス用の Rd ファイルでは、\alias フィールドに [クラス名-method] が指定されていなければならないので指定しておきます。
  • @name test_refclass: 自動判定の場合も、alias が勝手に複製されるバグがあるので、一番最後に書いてください。

メソッドの Rd ファイル用の roxygen コメント

メソッドの Rd ファイル用の roxygen コメントは以下の様に書いておけばよいでしょう。

#' Description: \code{m1} method
#'
#' Details: \code{m1} method set a value to \code{f1}.
#'
#' @rdname test_refclass-m1
#' @docType methods
#' @aliases m1,test_refclass-method
#'
#' @title m1,test_refclass-method: The m1 method of test_refclass class
#' @param arg1 numeric number of a test argument
#' @usage \S4method{m1}{test_refclass}(arg1)
#' @examples
#' classObj <- test_refclass$new()
#' print(classObj$m1(10))
#' @seealso \code{\link{test_refclass}}
#'
#' @name m1
NULL

#' Description: \code{m2} method
#'
#' Details: \code{m1} method return \code{f1 + arg1 * 2}.
#'
#' @rdname test_refclass-m2
#' @docType methods
#' @aliases m2,test_refclass-method
#' 
#' @title m2,test_refclass-method: The m2 method of test_refclass class
#' @param arg1 numeric number of a test argument
#' @usage \S4method{m2}{test_refclass}(arg1)
#' @examples
#' classObj <- test_refclass$new()
#' print(classObj$m2(10))
#' @seealso \code{\link{test_refclass}}
#'
#' @name m2
NULL

#' Description: \code{m3} method
#'
#' Details: \code{m3} method return \code{f1 + arg1 * 3}.
#'
#' @rdname test_refclass-m3
#' @docType methods
#' @aliases m3,test_refclass-method
#'
#' @title m3,test_refclass-method: The m3 method of test_refclass class
#' @param arg1 numeric number of a test argument
#' @usage \S4method{m3}{test_refclass}(arg1)
#' @examples
#' classObj <- test_refclass$new()
#' print(classObj$m3(10))
#' @seealso \code{\link{test_refclass}}
#'
#' @name m3
NULL

ポイント

  • @docType methods: メソッド用の Rd ファイルであることを指定します。
  • @usage: 面倒なので \S4method{generic}{signature_list}(argument_list) を流用します。
  • @name: パッケージの Rd ファイルの場合と同様の理由で、\name フィールドに入力する値を指定しておきます。
表示例

上記のスクリプトを *.R ファイルとして保存し、DESCRIPTION ファイルなど他の必要なファイルと一緒に適切にディレクトリに配置し、

require(roxygen)
roxygenize(path, "testRefclassPkg", copy.package = FALSE, use.Rd2 = TRUE)

の様に roxygenize() 関数を適用すると、Rd ファイルが生成されますので、これらをまとめてパッケージ化すると完成です。


以下に、パッケージ化してインストール後に生成されたドキュメントを示しておきます (表示には helpr を利用)。





同じ値の \alias フィールドが 2 つ入力されてしまう (2011/08/22 追記)

なぜか \alias フィールドに 2 つ同じものが出てきてしまうので、原因を調べてみようと思います。



バグっぽいです が、@name を一番下に書けばうまくいくようです。

Reference class の initialize メソッドについて

initialize メソッドの挙動 を調べていたら、R devel の ML (Ref Classes: bug with using '.self' within initialize methods?)TractoR というパッケージのコードを見つけ、これを参考に uninitializedField に初期値を与えてみるコードを書きました。

myclass0 <- setRefClass(
  Class = "myclass0",
  fields = c("field1"),
  methods = list(
    
    initialize = function(...) {
        object <- initFields(...)
        print(initFields)
        if (class(object$field1) == "uninitializedField") object$field1 <- 5
        return(object)
    },

    myfunc0 = function(a) {
      a - field1
    }

  )
)

myclass1 <- setRefClass(
  Class = "myclass1",
  fields = c("field1"),
  methods = list(
    
    initialize = function(...) {
        object <- callSuper(...)
        print(callSuper)
        if (class(object$field1) == "uninitializedField") object$field1 <- 5
        return(object)
    },

    myfunc0 = function(a) {
      a - field1
    }

  )
)


test0 <- myclass0$new()
test0$myfunc0(5)
test1 <- myclass1$new()
test1$myfunc0(5)
test0 <- myclass0$new(field1 = 10)
test0$myfunc0(5)

ここで、myclass0 の initialize では initFields(...) を呼び出していて、myclass1 の initialize では callSuper(...) を呼んでいるという違いがあります。


これらを実行してみると、

> test0 <- myclass0$new()
function (...) 
{
    if (length(list(...)) > 0) 
        initFieldArgs(.self, .refClassDef, as.environment(.self), 
            ...)
    else .self
}
<environment: 0x0566f5c4>
attr(,"mayCall")
character(0)
attr(,"name")
[1] "initFields"
attr(,"refClassName")
[1] "envRefClass"
attr(,"superClassMethod")
[1] ""
attr(,"class")
[1] "refMethodDef"
attr(,"class")attr(,"package")
[1] "methods"
> test1 <- myclass1$new()
function (...) 
{
    if (length(list(...)) > 0) 
        initFieldArgs(.self, .refClassDef, as.environment(.self), 
            ...)
    else .self
}
<environment: 0x05760468>
attr(,"mayCall")
character(0)
attr(,"name")
[1] "initFields"
attr(,"refClassName")
[1] "envRefClass"
attr(,"superClassMethod")
[1] ""
attr(,"class")
[1] "refMethodDef"
attr(,"class")attr(,"package")

となって、あぁやっぱり initialize 中の callSuper(...) は initFields(...) を呼んでいるんだなぁという事が分かりました確認できました。
methods pacakge のソースを読めばいいのですが、読んでもよく分からなかったので・・・

# refClass.R - Lines 162-177
    ## assign references to the object and to its class definition
    assign(".self", .Object, envir = selfEnv)
    assign(".refClassDef", classDef, envir = selfEnv)
    if(is.function(classDef@refMethods$finalize))
        reg.finalizer(selfEnv, function(x) x$.self$finalize())
    if(is.function(classDef@refMethods$initialize))
        .Object$initialize(...)
    else {
        if(nargs() > 1) {
            .Object <-
                methods::initFieldArgs(.Object, classDef, selfEnv, ...)
            ## reassign in case something changed
            assign(".self", .Object, envir = selfEnv)
        }
        .Object
    }


実行結果はこんな感じでした。

> test0 <- myclass0$new()
> test0$myfunc0(5)
[1] 0
> test1 <- myclass1$new()
> test1$myfunc0(5)
[1] 0
> test0 <- myclass0$new(field1 = 10)
> test0$myfunc0(5)
[1] -5

About the RcmdrPlugin.KMggplot2

I aim to make a brief version of a ggplot2 GUI-frontend "with Japanese translation (I am Japanese)."
I think that the Deducer and the yeroon.net/ggplot2 is pretty good software.


Currently, I'm trying to find bugs and to restructure implementation approach for efficient maintenance.
Next, I will add and improve some functions based on user's feedback. Please send any feedback :)


Already planned

  1. Saving options on each menu (e.g., font size, font family, ...) [Added in version: 0.1-0]
  2. Fixing items in the font family option and the colour pattern option [Unimplemented]
  3. Adding a faceting interface in some menus (Thanks to Professor Murtaza Haider) [Added in version: 0.1-0]
  4. Adding themes [Added in version: 0.1-0]
  5. Adding univariate plots at diagonal positions in the scatter plot matrix [Added in version: 0.1-0]
  6. Adding violin plots in the box plot menu [Added in version: 0.1-0]
  7. Adding stacked bar charts in the bar chart menu [Added in version: 0.1-0]

RcmdrPlugin.KMggplot2 について

日本語化済みの ggplot2 用の GUI フロントエンドはたぶん無いと思うので、簡易版ですがパッケージを作ってみました。
Deduceryeroon.net/ggplot2 の様に、もっとしっかりとした良いソフトもありますので、そちらもお試しください。


しばらくはバグ探しや、メンテナンス性向上のためのコード再構築をやる予定です。
また、機能の追加や改良もやっていく予定ですので、コメント等いただければと思います。


予定

  1. グラフ作成に使ったオプションの保存 (フォントサイズやフォントファミリーをいちいち選び直さないですむように) [Version: 0.1-0 で追加済]
  2. フォントファミリーや色セットを一部削除 (環境によっては使えないものがあるので) [未実装]
  3. いくつかのグラフメニューに facet 用のインターフェースを追加 (Thanks to Professor Murtaza Haider) [Version: 0.1-0 で追加済]
  4. テーマの追加 [Version: 0.1-0 で追加済]
  5. 散布図行列の対角部分に単変量のグラフを追加 [Version: 0.1-0 で追加済]
  6. バイオリンプロットを箱ひげ図のメニューに追加 [Version: 0.1-0 で追加済]
  7. 積み上げ棒グラフを棒グラフのメニューに追加 [Version: 0.1-0 で追加済]

RcmdrPlugin.KMggplot2_0.0-3 is on CRAN now


RcmdrPlugin.KMggplot2 (CRAN)


I posted an Rcmdr plug-in for a "ggplot2" GUI front-end on CRAN.
This version supports Kaplan-Meier plot and other plots as follow:

  • Kaplan-Meier plot
    • Show no. at risk on inside
    • Show no. at risk table on outside
  • Histogram
    • Color coding
    • Density estimation
  • Q-Q plot
    • Create plots based on a maximum likelihood estimate for the parameters of the selected theoretical distribution
    • Create plots based on a user-specified theoretical distribution
  • Box plot / Errorbar plot
    • Box plot
    • Mean ± S.D.
    • Mean ± S.D. (Bar plot)
    • 95% Confidence interval (t distribution)
    • 95% Confidence interval (bootstrap)
  • Scatter plot
    • Fitting a linear regression
    • Smoothing with LOESS for small datasets or GAM with a cubic regression basis for large data
  • Scatter plot matrix
    • Fitting a linear regression
    • Smoothing with LOESS for small datasets or GAM with a cubic regression basis for large data
  • Line chart
    • Normal line chart
    • Line char with a step function
    • Area plot
  • Pie chart
  • Bar chart for discrete variables
  • Contour plot
    • Color coding
    • Heat map
  • Distribution plot
    • Normal distribution
    • t distribution
    • Chi-square distribution
    • F distribution
    • Exponential distribution
    • Uniform distribution
    • Beta distribution
    • Cauchy distribution
    • Logistic distribution
    • Log-normal distribution
    • Gamma distribution
    • Weibull distribution
    • Binomial distribution
    • Poisson distribution
    • Geometric distribution
    • Hypergeometric distribution
    • Negative binomial distribution

Menu tree

Kaplan-Meier plot


Histogram


Q-Q plot


Box plot / Errorbar plot


Scatter plot


Scatter plot matrix


Line chart


Pie chart


Bar chart for discrete variables


Contour plot



Distribution plot






An alternative of the base:setdiff(x, y) for list objects

Using the base::unique(x), the base::setdiff(x, y) has the same problem as follow:

> unique(list(a=1,b=2,c=3,a=1))
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3


So, I wrote an alternative of the base::setdiff(x, y) function, too.

lsetdiff <- function(x, y) {
  xclass   <- class(x)
  yclass   <- class(y)
  if (xclass != yclass) stop("x and y must be the same class")
  class(x) <- ""
  class(y) <- ""
  if (!is.list(x)) stop("x must be a list")
  if (!is.list(y)) stop("y must be a list")

  if (!is.null(names(x))) nx <- names(x)
  else nx <- rep("", length(x))
  nvx <- paste(nx, ",", as.character(x))

  if (!is.null(names(y))) ny <- names(y)
  else ny <- rep("", length(y))
  nvy <- paste(ny, ",", as.character(y))

  x <- lunique(
    if (length(x) || length(y)) {
      x[match(nvx, nvy, 0L) == 0L]
    } else {
      x
    }
  )
  class(x) <- xclass
  x
}

l1 <- list(a=1, b="x", c=c("xxx", "djfk"),
  d=list(y=1,x=2,z=3), e=matrix(c(1,1,1,1), ncol=2))
l2 <- list(a=1, b="x", c=c("xxx", "djfk"),
  d=list(y=1,3), e=matrix(c(1,1,1,1), ncol=2))
lsetdiff(l1, l2)


The lsetdiff(x, y) returns "a named list" that has elements that have different values in the x and y.

> lsetdiff(l1, l2)
$d
$d$y
[1] 1

$d$x
[1] 2

$d$z
[1] 3

Another set operations for list objects as follow

The lunion(x, y)
lunion <- function(x, y, allowSameName = TRUE) {
  xclass   <- class(x)
  yclass   <- class(y)
  if (xclass != yclass) stop("x and y must be the same class")
  class(x) <- ""
  class(y) <- ""
  if (!is.list(x)) stop("x must be a list")
  if (!is.list(y)) stop("y must be a list")

  lunique(c(x, y), allowSameName)
}
l1 <- list(a=1, b="x", c=c("xxx", "djfk"),
  d=list(y=1,x=2,z=3), e=matrix(c(1,1,1,1), ncol=2))
l2 <- list(a=1, b="x", c=c("xxx", "djfk"),
  d=list(y=1,3), e=matrix(c(1,1,1,1), ncol=2))
lunion(l1, l2)
The lintersect(x, y)
lintersect <- function(x, y) {
  xclass   <- class(x)
  yclass   <- class(y)
  if (xclass != yclass) stop("x and y must be the same class")
  class(x) <- ""
  class(y) <- ""
  if (!is.list(x)) stop("x must be a list")
  if (!is.list(y)) stop("y must be a list")  
  if (!is.null(names(x))) nx <- names(x)

  else nx <- rep("", length(x))
  nvx  <- paste(nx, ",", as.character(x))

  if (!is.null(names(y))) ny <- names(y)
  else ny <- rep("", length(y))
  nvy <- paste(ny, ",", as.character(y))

  lunique(y[match(nvx, nvy, 0L)])
}
l1 <- list(a=1, b="x", c=c("xxx", "djfk"),
  d=list(y=1,x=2,z=3), e=matrix(c(1,1,1,1), ncol=2))
l2 <- list(a=1, b="x", c=c("xxx", "djfk"),
  d=list(y=1,3), e=matrix(c(1,1,1,1), ncol=2))
lintersect(l1,l2)

An alternative of the base:unique(x) for list objects

The base::unique(x) function returns a vector, data frame or array that were removed duplicate elements/rows.
When the x is a list object, the base::unique(x) returns an unnamed list object as follow:

> unique(list(a=1,b=2,c=3,a=1))
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3


So, I wrote an alternative of the base::unique(x) function.

lunique <- function(x, allowSameName = TRUE) {
  xclass   <- class(x)
  class(x) <- ""
  if (!is.list(x)) stop("x must be a list.")
  if (!allowSameName) {
    nnlist <- match(names(x), "", 0L)
    if (!is.null(names(x))) {
      unlist <- match(unique(names(x)), names(x))
      unlist <- replace(rep(0, length(x)), unlist, 1)
      x <- x[(nnlist | unlist) != 0]
    }
  }
  if (!is.null(names(x))) n <- names(x)
  else n <- rep("", length(x))
  nv <- paste(n, ",", as.character(x))
  x  <- x[match(unique(nv), nv)]
  class(x) <- xclass
  x
}

l1 <- list(a=1,a=1,a=2,b="Y",c=c(1,1,1,1), d=list(e=1,f=10))
l2 <- list(1,1,"Y",a=3,a=4,c=c(1,1,1,1), d=list(e=1,f=10))
l3 <- list(1,1,c(7,8),list(1,3,2),1,1,1,2,2,2,3,4,5,6)

lunique(l1)
lunique(l1, allowSameName = FALSE)
lunique(l2)
lunique(l3)


The lunique(x) returns a list that were removed duplicate elements.
When the argument allowSameName = TRUE, the lunique(x) allows same named elements with different values.

> lunique(l1, allowSameName = TRUE)
$a
[1] 1
$a
[1] 2
$b
[1] "Y"
$c
[1] 1 1 1 1
$d
$d$e
[1] 1
$d$f
[1] 10

When the argument allowSameName = FALSE, the lunique(x) deletes same named elements except the first element.

> lunique(l1, allowSameName = FALSE)
$a
[1] 1
$b
[1] "Y"
$c
[1] 1 1 1 1
$d
$d$e
[1] 1
$d$f
[1] 10

When list elements are accessed by name, the first element is returned by default.

> l4 <- list(a = 1, a = 2, a = 3)
> l4$a
[1] 1


Even when the x contain unnamed elements, the lunique(x) can remove duplicate elements.

> lunique(l2)
[[1]]
[1] 1
[[2]]
[1] "Y"
$a
[1] 3
$c
[1] 1 1 1 1
$d
$d$e
[1] 1
$d$f
[1] 10