从lapply或使用print语句从函数调用时,kable的意外行为

时间:2014-08-16 21:17:22

标签: r knitr rstudio lapply

我试图理解使用knitr包编译HTML时在kable函数中的两个意外行为(在Ubuntu 14.04上的RStudio 0.98.977中):

  1. 当从lapply内部进行两次kable调用时,只有第一次调用会在最终HTML中生成漂亮的显示。
  2. 当在一个也使用print语句的函数中进行两次kable调用时,只有最后一次调用才能在最终的HTML中生成漂亮的显示。
  3. 示例代码如下:

    Load library:
    
    ```{r init}
    library("knitr")
    ```
    
    Define dataframe:
    
    ```{r define_dataframe}
    df <- data.frame(letters=c("a", "b", "c"), numbers=c(1, 2, 3))
    rownames(df) <- c("x", "y", "z")
    ```
    
    ### Example 1: pretty display with simple call
    
    The dataframe is displayed nicely twice when knitting HTML with the following code:
    
    ```{r pretty_display1, results="asis"}
    kable(df)
    kable(df)
    ```
    
    ### Example 2: unexpected display with lapply
    
    The dataframe is displayed nicely only the first time when knitting HTML with the following code:
    
    ```{r unexpected_display1, results="asis"}
    lst <- list(df, df)
    lapply(lst, kable)
    ```
    
    ### Example 3: pretty display with function
    
    The dataframe is displayed nicely twice when knitting HTML with the following code:
    
    ```{r pretty_display2, results="asis"}
    foo1 <- function (df) {
      kable(df)
    }
    foo2 <- function (df) {
      foo1(df)
      foo1(df)
    }
    foo2(df)
    ```
    
    ### Example 4: unexpected display with function containing print statements
    
    The dataframe is displayed nicely only the second time when knitting HTML with the following code:
    
    ```{r unexpected_display2, results="asis"}
    foo1 <- function (df) {
      kable(df)
    }
    foo2 <- function (df) {
      print("first display")
      foo1(df)
      print("second display")
      foo1(df)
    }
    foo2(df)
    ```
    

    你对这些奇怪的行为以及如何规避它们有解释吗?

2 个答案:

答案 0 :(得分:9)

kable的输出是副作用;您可以将输出的值存储在变量中,但只运行kable会将某些内容输出到控制台。当您运行kable(df)两次时,这不是问题,您没有存储任何内容,并且该函数将输出转储到控制台两次。

但是,当您运行lapply(lst, kable)时,该函数会将输出转储到控制台,然后显示列表的值。尝试在您的控制台中运行它:

lst <- list(df, df)
lapply(lst, kable)

你应该得到这个:

|   |letters | numbers|
|:--|:-------|-------:|
|x  |a       |       1|
|y  |b       |       2|
|z  |c       |       3|


|   |letters | numbers|
|:--|:-------|-------:|
|x  |a       |       1|
|y  |b       |       2|
|z  |c       |       3|
[[1]]
[1] "|   |letters | numbers|" "|:--|:-------|-------:|"
[3] "|x  |a       |       1|" "|y  |b       |       2|"
[5] "|z  |c       |       3|"

[[2]]
[1] "|   |letters | numbers|" "|:--|:-------|-------:|"
[3] "|x  |a       |       1|" "|y  |b       |       2|"
[5] "|z  |c       |       3|"

注意如何输出正确的降价,然后显示您创建的列表的实际值。这就是产生不良输出的原因。

功能范例在副作用方面效果不佳,因此您有两种选择。您可以通过将kable参数设置为output来存储FALSE的结果,或者您可以使用for来浏览list,或者您可能会阻止显示结果列表。这里有一些可行的例子。

```{r nograpes1, results="asis"}
lst <- list(df, df)
for(x in lst) kable(x) # Don't create a list, just run the function over each element
```

```{r nograpes2, results="asis"}
lst <- list(df, df)
invisible(lapply(lst, kable)) # prevent the displaying of the result list.
```

```{r nograpes3, results="asis"}
lst <- list(df, df)
l <- lapply(lst, kable) # Store the list and do nothing with it.
```

在我看来,这是一个很好的例子,当for 应该在R中使用时,因为它最清楚地表达了你想如何使用基于副作用的函数。< / p>

答案 1 :(得分:4)

nograpes对你关于lapply的问题提出了一个很好的答案。在这里,我试图解决你问题的其他部分。

print函数通过将字符串打印到输出来产生副作用。在print调用之后立即调用第一个kable函数,该函数在RMarkdown表语法之后附加一行。由于RMarkdown语法要求您在表格之前和之后设置换行符,因此print函数会污染kable输出。因此,第一个kable输出未正确解析为表。

如果从块中移除results="asis"部分,则可以打印原始RMarkdown输出,以便查看culprint:

## [1] "first display"
## 
## 
## |   |letters | numbers|
## |:--|:-------|-------:|
## |x  |a       |       1|
## |y  |b       |       2|
## |z  |c       |       3|
## [1] "second display"  # <- here is the culprit! 
## 
## 
## |   |letters | numbers|
## |:--|:-------|-------:|
## |x  |a       |       1|
## |y  |b       |       2|
## |z  |c       |       3|

您可以清楚地看到second display如何在表格后面附加,干扰Markdown处理。

如果您确实要在输出中打印一些字符串/信息,可以使用cat功能。但请记住打印一些新行,以便RMarkdown表语法不会被抹黑。

```{r unexpected_display2, results="asis"}
foo1 <- function (df) {
  kable(df)
}
foo2 <- function (df) {
  cat("\n\nfirst display")
  foo1(df)
  cat("\n\nsecond display")
  foo1(df)
}
foo2(df)
```