92.PKD之Documentation
文档的重要性我就不介绍了,只介绍R的文档框架,它们是.Rd文件,这种文件use a custom syntax, loosely based on latex, and are rendered to html for viewing
与其我们自己写.Rd文件,roxygen2帮我们生成.Rd文件,它的优点如下:
1. 代码与文档交织在一起,当修改函数的时候,很容易同时去修改文档
2. roxygen有一些功能使得我们避免重复性的劳动
3. 容易学
好了,我们先来展示一下整个工作流程,再一步步深入:
1.在.R文件中加入 roxygen comments
2. Run devtools::document() (or press Cmd + Shift + D in RStudio) 把roxygen转变成.Rd文件
3. 用?查看
4. 调整,重新整个过程
用作者的例子稍微展示一下:
第一步: 写R文件并加roxygen注释#'
#' Add together two numbers.<br />
#'<br />
#' @param x A number.<br />
#' @param y A number.<br />
#' @return The sum of \code{x} and \code{y}.<br />
#' @examples<br />
#' add(1, 1)<br />
#' add(10, 1)<br />
add <- function(x, y) {<br />
x + y<br />
}
第二步 devtools::document() (or press Cmd + Shift + D in RStudio)生成对应的.Rd文档
% Generated by roxygen2 (4.0.0): do not edit by hand<br />
\name{add}<br />
\alias{add}<br />
\title{Add together two numbers}<br />
\usage{<br />
add(x, y)<br />
}<br />
\arguments{<br />
\item{x}{A number}</p>
<p> \item{y}{A number}<br />
}<br />
\value{<br />
The sum of \code{x} and \code{y}<br />
}<br />
\description{<br />
Add together two numbers<br />
}<br />
\examples{<br />
add(1, 1)<br />
add(10, 1)<br />
}
第三步,先ctrl+shift+L或B,再使用?add,help("add"),example("add")的时候,R会查看包含\alias{"add"}的.Rd文件,然后显示
具体样子,大家在?sum,?mean等等的时候那个help html早见过了,这里节省篇幅
第四步,重新修改第一步</p>
这里要提醒的是,对于使用ctrl+shift+l,我们再第二步之后,可以?或help是因为devtools重写了这个函数,而example("add")却不能运行,但对于build之后的三个函数都可以
OK,熟悉最基本的整个work flow之后,我们来看看roxygen注释怎么写:
在一个函数之前的都是一个block,而一个block会被打散成很多@tagName details
每个tagName的内容是到下一个开始为止,对于第一个tagName之前的文档(叫做introduction),会被特殊解释,第一个句子是title,会出现在help(packge=newpkg)首页的描述
第二个段落是description
第三个以及之后的段落是details
前两个是每个函数都必须有的,第三个可选,一个简单的例子如下:
#' Sum of vector elements.<br />
#'<br />
#' \code{sum} returns the sum of all the values present in its arguments.<br />
#'<br />
#' This is a generic function: methods can be defined for it directly or via the<br />
#' \code{\link{Summary}} group generic. For this to work properly, the arguments<br />
#' \code{...} should be unnamed, and dispatch is on the first argument.<br />
sum <- function(..., na.rm = TRUE) {}
可以看到这个文档符合上面解释的样子,但我翻看了pipeR的文档,看到了下面的样子,引用一下
#' Pipe an object forward<br />
#'<br />
#' The \code{\%>>\%} operator pipes the object on the left-hand side to the<br />
#' right-hand side as either the first argument and \code{.}, or a symbol<br />
#' defined by lambda expression.<br />
#'<br />
#' @param x object<br />
#' @param expr expression<br />
#' @details<br />
#' \code{\%>>\%} supports the following pipline mechanisms:<br />
#'<br />
#' 1. Pipe to first argument:<br />
#'<br />
#' \code{x \%>>\% f} as \code{f(x)}<br />
#'<br />
#' \code{x \%>>\% f(...)} as \code{f(x,...)}<br />
#'
它是用@details来特殊表明,这样感觉上比起前面介绍的方法写details的位置是可控的
稍微注意的是,\code{} and \link{} are formatting commands会在后面提到,还要介绍个ctrl+shift+/或者code | re-flow来让block在80字符之内</p>
我们可以用任意标签@section tag来把一些很长的介绍来打散成各个醒目的小节,然后来引用作者的原话
[quote]. Section titles should be in sentence case and must be followed a colon, they can only be one line long[/quote]
例如:
#' @section Warning:<br />
#' Do not operate heavy machinery within 8 hours of using this function.
其他常用的有Note,References (例如?lapply)
下面来介绍2个用来在帮助文档之间来导航的标签:
@seealso 让你指向其他资源:web, \url{http://www.r-project.org}, in your package \code{\link{functioname}}, or another package \code{\link[packagename]{functioname}}
@family 用来指向一个家族系列的函数 (还没有完全弄清楚[s:12])
然后再来介绍3个容易让用户找到文档的标签,由于我暂时不能完全理解,还是先引用作者原话
[quote]Three other tags make it easier for the user to find documentation:</p>
@aliases space separated aliases adds additional aliases to the topic. An alias is another name for the topic that can be used with ?.
@concepts adds extra keywords that will be found with help.search().
@keywords keyword1 keyword2 ... adds standardised keywords. Keywords are optional, but if present, must be taken from a predefined list found in file.path(R.home("doc"), "KEYWORDS").
Generally, keywords are not that useful except for @keywords internal. Using the internal keyword removes omits the function from the package index and disables some of their automated tests. It’s common to use @keywords internal for functions that are of interest to other developers extending your package, but not most users.[/quote]
需要注意的是@aliases 在build&reload + Document可以,而不会在load_all()的?显示出来(我猜是devtools的?没有完全重写?函数,导致部分功能不能运行),其它2个,@concepts,@keywords我不知道怎么用,甚至不能ctrl+shift+d 出来这两个标签[s:12]
基于其它要文档化的对象还有其它的标签,下面我们就来介绍函数的文档化标签和R的3种oo系统的标签:
对于函数文档,基本上很简单:@param, @examples and @return. 我们的第一个add例子就展示了,参数注释在一起的时候要注意下, @param x,y Numeric vectors. 的写法,最后最重要的是@ examples 中的3个format commands:
\dontrun{},\dontshow{},\donttest{}
普通没有这些格式命令,既会被run也会被show(我暂时不知道会不会被test,所以不提[s:18]),run是指example()的时候会运行的,show是指?的时候会展示的,test是指R CMD check的时候会Test的,那么好了,解释完了[s:11]
然后需要注意一个@example path/relative/to/packge/root的用法
#' @example<br />
#' test/test.r<br />
#' @example<br />
#' test/test2.r
这个test是在newpkg下的文件夹,然后把需要测试的代码放入其中就可以了,包括可以放前面说的3种format commands
作者提醒:一般的标签第2行以及以后行最好来个indent,但对于经常要跨多行的标签例如@example应该重新开行且不需要indent
我们再来看看怎么文档化3种oo系统:
1.s3
s3的泛型函数可以如同上述方法描述,对于类方法却没有正式的规定,以前的版本roxygen会要求显示的@method generic class,现在不需要了,roxygen2可以自动找出,除了一些模糊的情况,(例如a.b.c)</p>
2.s4
虽然暂时用不到,还是简单说下
首先我们需要再类前放一个block用@slot来描述类的节点,就好像用@param来描述函数参数一样
#' An S4 class to represent a bank account.<br />
#'<br />
#' @slot balance A length-one numeric vector<br />
Account <- setClass("Account",<br />
slots = list(balance = "numeric")<br />
)
其次,S4泛型函数如同一般函数一样,对于S4类方法都必须得注释,由于这个情况,我们又不想对每个类方法给一页,我们可以放这些类方法在以下3个地方:
[quote]In the class. Most appropriate if the corresponding generic uses single dispatch and you created the class.</p>
In the generic. Most appropriate if the generic uses multiple dispatch and you have written both the generic and the method.
In its own file. Most appropriate if the method is complex, or the you’ve written the method but not the class or generic.[/quote]
还要注意的是,s4的装载顺序,使用@include
#' @include class-a.R<br />
setClass("B", contains = "A")
这表示说装载当前文件之前要装入class-a.r文件,否则R按字母序装入文件,会导致错误
同样的:
#' @include foo.R bar.R baz.R<br />
NULL</p>
<p>setMethod("foo", c("bar", "baz"), ...)
我们可以把要装载的放入文件头,然后修饰一个NULL对象
Roxygen会把@include放入Collate field in the DESCRIPTION,当然我们可以使用特殊的方法来替代使用@include,例如我们把所有的类和类方法定义在aaa-classes.R and aaa-generics.R文件中,这样可以确保按字典序会被第一个装入,但这样的缺点是不能灵活的把这样定义放入多个文件
以前的版本会显示要求@usage, @alias and @docType现在都可以自动了</p>
3.RC
由于RC系统的方法定义在类里面,它有个特殊的docstring语法,所有的类方法在类里使用docstring,我们只需要描述@field,roxygen会帮我们弄好RC的类方法:
#' A Reference Class to represent a bank account.<br />
#'<br />
#' @field balance A length-one numeric vector.<br />
Account <- setRefClass("Account",<br />
fields = list(balance = "numeric"),<br />
methods = list(<br />
withdraw = function(x) {<br />
"Withdraw money from account. Allows overdrafts"<br />
balance <<- balance - x<br />
}<br />
)<br />
)
那么它的显示结果如下:
<br />
Account-class {newpkg} R Documentation<br />
A Reference Class to represent a bank account.</p>
<p>Description</p>
<p>A Reference Class to represent a bank account.</p>
<p>Fields</p>
<p>balance<br />
A length-one numeric vector.</p>
<p>Methods</p>
<p>withdraw(x)<br />
Withdraw money from account. Allows overdrafts
好了,暂时基本了解上述介绍就够了,特别对于S4,RC </p>
最后,我们来看看roxygen给我们2种不需要重复劳动的途径:
第一种,@inheritParams
#' @param a This is the first argument<br />
foo <- function(a) a + 10</p>
<p>#' @param b This is the second argument<br />
#' @inheritParams foo<br />
bar <- function(a, b) {<br />
foo(a) * 10<br />
}
就相当于
#' @param a This is the first argument<br />
#' @param b This is the second argument<br />
bar <- function(a, b) {<br />
foo(a) * 10<br />
}
第二种功能是让多个函数文档在同一个文件使用@rdname or @describeIn
首先,使用@describeIn
代码如下
#' Foo bar generic<br />
#'<br />
#' @param x Object to foo.<br />
foobar <- function(x) UseMethod("x")</p>
<p>#' @describeIn foobar Difference between the mean and the median<br />
foobar.numeric <- function(x) abs(mean(x) - median(x))</p>
<p>#' @describeIn foobar First and last values pasted together in a string.<br />
foobar.character <- function(x) paste0(x[1], "-", x[length(x)])
结果如下
<br />
foobar {newpkg} R Documentation<br />
Foo bar generic</p>
<p>Description</p>
<p>Foo bar generic</p>
<p>Usage</p>
<p>foobar(x)</p>
<p>## S3 method for class 'numeric'<br />
foobar(x)</p>
<p>## S3 method for class 'character'<br />
foobar(x)<br />
Arguments</p>
<p>x<br />
Object to foo.</p>
<p>Methods (by class)</p>
<p>numeric: Difference between the mean and the median</p>
<p>character: First and last values pasted together in a string.
我们会看到,在usage中会出现所有的泛型函数和类方法,最后我们的文档在Methods(by class)那里显示了
事实上:
[quote]
@describeIn is designed for the most common cases:
Documenting methods in a generic.
Documenting methods in a class.
Documenting functions with the same (or similar arguments).
It generates a new section, named either “Methods (by class)”, “Methods (by generic)” or “Functions”. The section contains a bulleted list describing each function, labelled so that you know what function or method it’s talking about.[/quote]
我们的例子展示的就是第一种情况
替代@describeIn的@rdname,它使得我们直接用所有的文档来描述另一个对象
#' Basic arithmetic<br />
#'<br />
#' @param x,y numeric vectors.<br />
add <- function(x, y) x + y</p>
<p>#' @rdname add<br />
times <- function(x, y) x * y
或者先建立一个通用的文档来描述一个NULL对象,然后其他对象的文档引用它
#' Basic arithmetic<br />
#'<br />
#' @param x,y numeric vectors.<br />
#' @name arith<br />
NULL</p>
<p>#' @rdname arith<br />
add <- function(x, y) x + y</p>
<p>#' @rdname arith<br />
times <- function(x, y) x * y
我在这里要说下的是,我看了下pipeR的roxygen,注意到了@name这个标签,然后推测它是用来给生成的rd文件取名的,如下
#' Pipe an object forward to expression (deprecated)<br />
#' @param . object<br />
#' @param expr expression<br />
#' @name deprecated<br />
#' @export<br />
<code>%:>%</code> <- function(.,expr) {<br />
...<br />
}</p>
<p>#' @rdname deprecated<br />
#' @export<br />
<code>%|>%</code> <- function(.,expr) {<br />
....}<br />
又如:
#' Basic arithmetic<br />
#'<br />
#' @param x,y numeric vectors.<br />
#' @name arith<br />
NULL</p>
<p>#' @rdname arith<br />
add <- function(x, y) x + y</p>
<p>#' @rdname arith<br />
times <- function(x, y) x * y
我们可以看到生成的.Rd文件的alias会出现 arith,add,times,然后?随便其中一个,根据前面说的,R会找.Rd文件中的alias部分,这3个会展示同样的一个page,大家可以自己看下效果
最后,我们来说下Text formatting reference sheet,来介绍一些重要的命令,完整的命令在< a href=http://cran.r-project.org/doc/manuals/R-exts.html#Marking-text> R extensions
[quote]Character formatting
\emph{italics}.</p>
\strong{bold}.
\code{r_function_call(with = "arguments")}, \code{NULL}, \code{TRUE}.
\pkg{package_name}.
Links
To other documentation:
\code{\link{function}}: function in this package.
\code{\link[MASS]{stats}}: function in another package.
\link[=dest]{name}: link to dest, but show name.
\linkS4class{abc}: link to an S4 class.
To the web:
\url{http://rstudio.com}.
\href{http://rstudio.com}{Rstudio}.
\email{hadley@@rstudio.com} (note the doubled @).
Lists
Ordered (numbered) lists:
#' \enumerate{
#' \item First item
#' \item Second item
#' }
Unordered (bulleted) lists:
#' \itemize{
#' \item First item
#' \item Second item
#' }
Definition (named) lists:
#' \describe{
#' \item{One}{First item}
#' \item{Two}{Second item}
#' }
Mathematics
You can use standard LaTeX math (with no extensions). Choose between either inline or block display:
\eqn{a + b}: inline equation.
\deqn{a + b}: display (block) equation.[/quote]
最后个展示table的功能如下,作者用该函数把R的数据框转成table(这段代码还是值得读的)
tabular <- function(df, ...) {<br />
stopifnot(is.data.frame(df))</p>
<p> align <- function(x) if (is.numeric(x)) "r" else "l"<br />
col_align <- vapply(df, align, character(1))</p>
<p> cols <- lapply(df, format, ...)<br />
contents <- do.call("paste",<br />
c(cols, list(sep = " \\tab ", collapse = "\\cr\n ")))</p>
<p> paste("\\tabular{", paste(col_align, collapse = ""), "}{\n ",<br />
contents, "\n}\n", sep = "")<br />
}</p>
<p>cat(tabular(mtcars[1:5, 1:5]))<br />
#> \tabular{rrrrr}{<br />
#> 21.0 \tab 6 \tab 160 \tab 110 \tab 3.90\cr<br />
#> 21.0 \tab 6 \tab 160 \tab 110 \tab 3.90\cr<br />
#> 22.8 \tab 4 \tab 108 \tab 93 \tab 3.85\cr<br />
#> 21.4 \tab 6 \tab 258 \tab 110 \tab 3.08\cr<br />
#> 18.7 \tab 8 \tab 360 \tab 175 \tab 3.15<br />
#> }
PS: 最后还是要提醒,ctrl+shift+B 可以完成?查看aliases,example(""),包括查看\code{\link{}}等功能而ctrl+shift+L不能完成相应功能,是因为devtools并没有重写这些功能</p>
由于内容太杂,简单总结下思路:
1. 基本的workflow
2. Introduction,@details,@seealso,@family,@aliases等
3. @section warning:(Note,References)
4. 函数文档的@examples,@example,\dontrun{}等
5. 3种OO系统的用法,特别注意s4的@include,RC的docstring
6. 避免重复劳动,@inheritParams,@rdname or @describeIn
7. 一些格式命令