Shixiang Wang

>上士闻道
勤而行之

解决R包Check汇报'marked UTF-8 strings'问题

王诗翔 · 2021-03-24

分类: r  
标签: r   package   check  

今天在处理 UCSCXenaShiny 的 R 包 check 时发现报出 Note: found 162 marked UTF-8 strings 这种字符串编码问题(具体 action 报告)。

❯ checking installed package size ... NOTE
    installed size is  6.1Mb
    sub-directories of 1Mb or more:
      doc        1.7Mb
      shinyapp   3.2Mb

❯ checking data for non-ASCII characters ... NOTE
    Note: found 162 marked UTF-8 strings

0 errors ✔ | 0 warnings ✔ | 2 notes ✖

我网上搜索了下然后确定了 DESCRIPTION 文件里已经显式指定了代码文件是 UTF-8 编码,为什么会出这种问题呢?继续搜索发现该问题是出在包中引入的数据对象上,就是我们放在包里的数据存在编码问题。

目前这个我处理的包有 10 个数据集,为了锁定问题源,我采用了 https://github.com/dankelley/oce/issues/1663 提到的策略,即逐步删除 data/ 目录下的文件,然后检查是否该问题还存在。

于是,我写了下面一段代码:

devtools::load_all()
data_files <- dir("data")
for (i in data_files) {
  message("Removing file ", i)
  unlink(file.path("data", i))
  print(tools:::.check_package_datasets("."))
}

发现然并卵,for 循环一直报 Note: found 162 marked UTF-8 strings。我就纳闷了,最后数据文件都删完了,居然还会出这个 NOTE?

我仔细思考🤔了下,是否 tools:::.check_package_datasets() 一直使用的是已经缓存到内存的包?也就是说,虽然我已经删除了文件,但整个包可以已经载入内存,所以无论是否删除这 10 个数据文件,函数内部依旧可以获取到这些数据的信息。通过 debug(tools:::.check_package_datasets) 我发现事情不是我想的这样,该函数内部会检查 data/ 下的文件并获取文件列表用于载入,本人功底不足,加上 RStudio 对这种特别底层的调试支持不怎么爽利,我没有找到它怎么输出 NOTE 结果的,于是弃疗了该策略。

不过在调试中我发现 Encoding() 函数可以获取字符串编码信息:

Encoding("abc")
#> [1] "unknown"

所以我尝试载入数据对它的列进行编码查询,发现都是 “unknown”。既然包检查想要的是 ASCII 编码,那我转换一下不就完了?

我在网络上搜索了 3 种方法,第 1 种没有作用。

  1. 使用 iconv() 函数,用法如下:
nonUTF <- iconv(df$TroubleVector, from="UTF-8", to="ASCII")

参考的问答

这里的问题是 from 参数不支持 unknown,所以无用。

  1. 使用 2 次转换的方法,先转为 Raw 类型,然后再转回来。
df %>%
  mutate_if(is.character, function(x){
    x %>%
      sapply(function(y){
        y %>%
          charToRaw %>%
          rawToChar
      })
   })

参考的问答

  1. 使用 stringi 包中的 stringi::stri_enc_toascii() 函数(推荐),配合 stringi::stri_enc_isascii() 使用:
a <- "Sympathetic\xcaNervous System"
a
#> [1] "Sympathetic\xcaNervous System"
stringi::stri_enc_isascii(a)
#> [1] FALSE
b <- stringi::stri_enc_toascii(a)
b
#> [1] "Sympathetic\032Nervous System"
stringi::stri_enc_isascii(b)
#> [1] TRUE

利用第 3 种方法,我首先将问题锁定到了某一个数据:

is_asc <- function(x) {
  x <- stringi::stri_enc_isascii(x)
  if (FALSE %in% x) {
    return(FALSE)
  } else {
    return(TRUE)
  }
}

FALSE %in% sapply(TCGA.organ, is_asc)
FALSE %in% sapply(ccle_absolute, is_asc)
FALSE %in% sapply(ccle_info, is_asc)
FALSE %in% sapply(tcga_clinical, is_asc)
FALSE %in% sapply(tcga_genome_instability, is_asc)
FALSE %in% sapply(tcga_gtex, is_asc)
FALSE %in% sapply(tcga_purity, is_asc)
FALSE %in% sapply(tcga_subtypes, is_asc)
FALSE %in% sapply(tcga_surv, is_asc)
FALSE %in% sapply(toil_info, is_asc)
sapply(toil_info, is_asc)

然后锁定问题列以及检查问题字符串总数是否能对上:

> sapply(toil_info, is_asc)
                   sample         detailed_category primary disease or tissue             _primary_site 
                     TRUE                      TRUE                      TRUE                     FALSE 
             _sample_type                   _gender                    _study 
                     TRUE                      TRUE                      TRUE 
> length(which(!stringi::stri_enc_isascii(toil_info$`_primary_site`)))
[1] 162

找到具体问题位置就简单了,转换保存即可:

toil_info$`_primary_site` <- stringi::stri_enc_toascii(toil_info$`_primary_site`)

提交的 gh action 也显示问题解决了:


❯ checking installed package size ... NOTE
    installed size is  6.1Mb
    sub-directories of 1Mb or more:
      doc        1.7Mb
      shinyapp   3.2Mb

0 errors ✔ | 0 warnings ✔ | 1 note ✖

action 日志

最后回顾一下,为什么逐步删除的方法不起作用呢???

我还是道行太浅了~