问题描述

在R Markdown文件渲染后生成的Word或者html文件中,中文正文能够正常显示,但表格中的中文编码错误。

代码及结果

下面是Rmd文件中的内容:

---
title: "能耗计算"
output: html_document
---

以下是各种车的能耗数据:
```{r}
#Sys.setlocale(category = "LC_ALL", locale = "Chinese")
table_energy <- data.frame(
  "子类型" = c("moto","轿车 ","轻型客车 ","大型客车 ",
             "轻型货车 ","中型货车 ","重型货车 ","农用运输车"), 
  "汽油消费比重" = c(2.87,63.55,7.27,9.97,5.8,3.57,2.8,4.18)
)
table_energy
```

Knit后在viewer中显示如下,可见中文文字部分正常显示,而由data.frame生成的表格编码不对:

但是这段data.frame的代码在console面板运行是没问题的:

根据之前站内一个帖子《Rmarkdown生成html时,中文显示乱码》,我已经设置了:
Tools - Project Options - Code Editing - 编码设为UTF-8
Tools - Global Options - Code - Saving - Default text encoding设为 UTF-8
打开rmdFile - Save with Encoding - UTF-8
在最后一个设置中,除了UTF-8还尝试了好几种中文相关编码,或者是把三个设置均改为GB2312,均没有效果。

然后我又尝试了输出PDF文档,表格部分仍然无法显示中文。具体操作是:根据《在R Markdown文档中使用中文》一文,先升级了tinytextinytex::tlmgr_update(),然后将Rmd文件的头部改成下面这样再输出:

title: "能耗计算"
documentclass: ctexart
output: rticles::ctex

由于这个问题可能和系统设置有关,所以说下我用的电脑的背景:在日本买的Surface,系统是Windows 10,平时系统语言设置为英文,Current language for non-Unicode program设置为中文,sessioninfo()如下:

sessionInfo()
## R version 4.0.3 (2020-10-10)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19042)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=Chinese (Simplified)_China.936 
## [2] LC_CTYPE=Chinese (Simplified)_China.936   
## [3] LC_MONETARY=Chinese (Simplified)_China.936
## [4] LC_NUMERIC=C                              
## [5] LC_TIME=Chinese (Simplified)_China.936    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## loaded via a namespace (and not attached):
##  [1] compiler_4.0.3  magrittr_1.5    tools_4.0.3     htmltools_0.5.0
##  [5] yaml_2.2.1      stringi_1.5.3   rmarkdown_2.5   knitr_1.30     
##  [9] stringr_1.4.0   xfun_0.22       digest_0.6.27   rlang_0.4.8    
## [13] evaluate_0.14

此外,我之前碰到过其他的一些编码相关问题,发了个帖子《求助:RStudio读取日文文件名乱码》,可能跟这个问题的解决也有关系(也可能没有)。

    先贴上一个我目前的解决办法吧。这个编码问题产生于Rmd文档中的data.frame()相关代码块,但是在R script中运行是没问题的;另一方面,在Rmd代码块中,可以通过readxl::read_excel()_读取Excel文档中的表格并显示中文内容。因此,可以先在R script中运行data.frame()相关代码块,将生成的data frame写入Excel文件中,然后再在Rmd文档中读取该Excel文件。具体代码和结果如下。
    新建一个R script,命名为”data_ana.R”,写入代码如下:

    Sys.setlocale(category = "LC_ALL", locale = "Chinese") #这一行不写也可以
    table_energy <- data.frame(
      "子类型" = c("moto","轿车 ","轻型客车 ","大型客车 ",
                "轻型货车 ","中型货车 ","重型货车 ","农用运输车"), 
      "汽油消费比重" = c(2.87,63.55,7.27,9.97,5.8,3.57,2.8,4.18)
    )
    writexl::write_xlsx(table_energy, "table_energy.xlsx")

    然后将Rmd文件中的内容改为:

    ---
    title: "能耗计算"
    output: html_document
    ---
    
    以下是各种车的能耗数据:
    
    ```{r}
    library(magrittr)
    
    Sys.setlocale(category = "LC_ALL", locale = "Chinese")
    
    readxl::read_excel("table_energy.xlsx") %>% 
      knitr::kable()
    ```

    先运行”data_ana.R”再KnitRmd文档即可:

    那么直接在Rmd中调用”data_ana.R”行不行呢?我在Rmd文档代码块的library(magrittr)之前加了一行source("data_ana.R"),结果发现输出html又变回乱码了,猜测原因可能是导致乱码的编码问题产生于Rmd文件,因此在Rmd中调用R script就会使问题再次出现,而独立运行R script再KnitRmd文档就没问题。

      KANG1943 在上面这个回复的基础上继续话题。通常我们做数据分析的时候都会分别建立一个Script和一个Rmd文档,并且在后者中通过source()调用前者,其中Script用来写分析代码,Rmd用来呈现结果。在Rmd中写报告的时候不可避免地需要调用一些Script中生成的对象,从这个角度说,上面帖子说的“先运行Script再运行Rmd(而非source()Sciprt文件)”的方法就会产生诸多不便,因为Script中生成的对象就无法呈现在Rmd报告中。
      根据《Render reports directly from R scripts》这篇文章介绍的神奇操作,我们可以用两个Script解决这个问题,但是需要多费点心思。以下是具体操作。
      现在已经有了一个包含分析代码的Script叫做“data_ana.R”,我们再建立一个写报告用的Script叫做“fake_rmd.R”,这个Script的内容和提问帖中Rmd文档内容相近,如下:

      #' ---
      #' title: 能耗计算
      #' output: word_document
      #' ---
      
      #' 以下是各种车的能耗数据:
      source("data_ana.R", encoding = "utf-8")
      
      library(magrittr)
      
      Sys.setlocale(category = "LC_ALL", locale = "Chinese")
      
      readxl::read_excel("table_energy.xlsx") %>% 
        knitr::kable()

      保存了这两个Script文件后,在console面板执行rmarkdown::render("fake_rmd.R")即可在文件夹中输出这样一个html文件:

      当然,这样也很麻烦,且不说每次输入正文之前要加个“#‘”,Rmd的各种格式功能、对代码块的操作等也都难以使用。让我再想想……

      KANG1943 问题解决了!要点有两个:

      • 在Rmd中调用Script的时候,加个编码参数;
      • 在导出html文件时,不要用“Knit”按钮,而是在console面板中执行rmarkdown::render()
        具体操作如下。

      在二楼KANG1943 的基础上,我们现在手头有个叫做”data_ana.R”的Script文件,和一开始提问帖建立的Rmd文档,姑且称之为“test.Rmd”好了。”data_ana.R”的内容保持不变,“test.Rmd”的内容变成:

      ---
      title: "能耗计算"
      output: html_document
      ---
      
      以下是各种车的能耗数据:
      
      ```{r, include=FALSE}
      source("data_ana.R", encoding = "utf-8")
      ```
      
      ```{r}
      
      library(magrittr)
      
      Sys.setlocale(category = "LC_ALL", locale = "Chinese")
      
      readxl::read_excel("table_energy.xlsx") %>% 
        knitr::kable()
      ```
      为了测试三楼提到的调用Script中生成的对象的可能性,我们这里引用了data_ana.R生成的:
      `r table_energy[2,1]`

      这时候如果用“Knit”按钮,会报错:

      只能在console面板中执行rmarkdown::render("test.Rmd"),生成:

      不过如果可以的话,还是希望有不依赖于写入-读取Excel文件的解决办法。

        KANG1943 平时系统语言设置为英文,Current language for non-Unicode program设置为中文

        后者的设置是关键,你设置为中文之后再加上 Sys.setlocale(, "Chinese") 应该就没问题了,但这个 Sys.setlocale() 最好是在 R Markdown 之外就设置,比如在 ~/.Rprofile 里。帖子里提到的其它的操作应该都是无关的,例如编码、TinyTeX、readxl 等。其中 readxl 能显示出数据应该只是个巧合,不是因为 readxl 的原因,而是因为 kable() 跳过了 R 自身的打印函数 print(),我估计你不把数据折腾成 Excel 格式、而是直接把数据框传给 kable() 一样可以出结果。

          6 天 后

          KANG1943 正如 yihui 所说,编码设置是关键。作为总结,写一下最后的解决办法。

          首先是数据分析代码基本上保持原样,但是不再将结果写出:

          table_energy <- data.frame(
            "子类型" = c("moto","轿车 ","轻型客车 ","大型客车 ",
                      "轻型货车 ","中型货车 ","重型货车 ","农用运输车"), 
            "汽油消费比重" = c(2.87,63.55,7.27,9.97,5.8,3.57,2.8,4.18)
          )

          而Rmd文件需要设置编码为中文,全文如下:

          ---
          title: "能耗计算"
          output: html_document
          ---
          
          以下是各种车的能耗数据:
          
          ```{r, include=FALSE}
          Sys.setlocale(category = "LC_ALL", locale = "Chinese")
          source("data_ana.R", encoding = "utf-8")
          ```
          
          ```{r}
          # 不依赖于读写
          knitr::kable(table_energy)
          ```
          
          为了测试三楼提到的调用Script中生成的对象的可能性,我们这里引用了data_ana.R生成的:
          `r table_energy[2,1]`