R_Problems icon indicating copy to clipboard operation
R_Problems copied to clipboard

Random stuff on encoding

Open ChrisMuir opened this issue 6 years ago • 2 comments

Just came across this repo, it's 👍

I work with Chinese data in R and Python, and have struggled often with encoding issues in R, so figured I'd share some of the things I've come across and learned in the process. Most of the stuff below is related to file system functions and identifying files. Feel free to add any of this to the repo if you'd like (or not, it's all good).

list.files() versus Sys.glob()

list.files() fails at preserving Chinese chars, use Sys.glob() instead. Here's an example:

# Create a new file with Chinese chars in the file name.
temp_dir <- tempdir()
file_conn <- file(paste0(temp_dir, "/假文件名.txt"))
writeLines("cats", file_conn)
close(file_conn)

# :-(
files <- list.files(temp_dir, pattern = "*.txt")
files
#> [1] "????.txt"

# :-)
files <- Sys.glob(file.path(temp_dir, "*.txt"))
unlist(strsplit(files, "/"))[2]
#> [1] "假文件名.txt"

# Clean-up
unlink(temp_dir)

Issue with Chinese Parenthesis Chars in File Names

library(fs)

# This is a file that exists in my current working directory, it contains 
# Chinese parenthesis.
file_name <- "假文件名(12家).txt"

# Checking if it exists fails.
file.exists(file_name)
#> [1] FALSE

fs::is_file(file_name)
#> 假文件名(12家).txt 
#>                FALSE

## Issue is that the functions above are treating the Chinese paren chars 
## in object "file_name" as English paren chars. 
## Use conversion between utf8 and int to facilitate ID'ing the file.

# Function to take in a string that contains parenthesis chars and 
# replaces them with Chinese parenthesis chars (as ints).
cn_paren <- function(x) {
  x_int <- utf8ToInt(x)
  x_int[x_int == 40] <- 65288
  x_int[x_int == 41] <- 65289
  intToUtf8(x_int)
}

file_name_cn <- cn_paren(file_name)


# Test for the existence, now works.
file.exists(file_name_cn)
#> [1] TRUE

fs::is_file(file_name_cn)
#> 假文件名(12家).txt 
#>                     TRUE


# Can see a slight difference in the parenthesis chars in the two strings 
# when printed.
file_name
#> [1] "假文件名(12家).txt"

file_name_cn
#> [1] "假文件名(12家).txt"


# We also see the difference when using ust8ToInt()
utf8ToInt(file_name)
#> [1] 20551 25991 20214 21517    40    49    50 23478    41    46   116   120   116

utf8ToInt(file_name_cn)
#> [1] 20551 25991 20214 21517 65288    49    50 23478 65289    46   116   120   116

Package fs

The fs package is great, there's been a few times where it's been able to ID a file on my PC for which base functions have failed. I often use fs::is_file() in place of base::file.exists(), and fs::file_copy() in place of base::file.copy().

This Kevin Ushey Blog Post

This blog post by Kevin Ushey on string encoding in R is fantastic (and the comments are full of info as well).

System Info

And here's my system/local info

Sys.getlocale()
#> [1] "LC_COLLATE=English_United States.1252;LC_CTYPE=English_United States.1252;LC_MONETARY=English_United States.1252;LC_NUMERIC=C;LC_TIME=English_United States.1252"

getOption("encoding")
#> [1] "native.enc"

sessionInfo()
#> R version 3.5.0 (2018-04-23)
#> Platform: x86_64-w64-mingw32/x64 (64-bit)
#> Running under: Windows >= 8 x64 (build 9200)

#> Matrix products: default

#> locale:
#> [1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252    LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
#> [5] LC_TIME=English_United States.1252

#> attached base packages:
#> [1] stats     graphics  grDevices utils     datasets  methods   base     

#> loaded via a namespace (and not attached):
#> [1] compiler_3.5.0 tools_3.5.0    fs_1.2.3       yaml_2.1.19    Rcpp_0.12.17

ChrisMuir avatar Jul 25 '18 23:07 ChrisMuir

@ChrisMuir many thanks!

I've already known the fs package, which brought form the libuv library. Maybe you already known the utf8 package

While R encoding is a complex problem. Your code list.files() works fine on my computer, because my native.enc is Chinese Encoding. I advice your to read the params to findout if there is a encoding param, and specify it to a certain encoding, like UTF-8 , GB18030/GBK, etc. Maybe it could avoid encoding problems.

I will add more examples to solve the Chinese Encoding problems when I am not busy.

BTW, I find your geolocChina repo, I may use map api to do that. For more info you can visit: https://lbs.amap.com/api/webservice/guide/api/georegeo/#geo. But it's not as convenient as yours.

> Sys.getlocale()
[1] "LC_COLLATE=Chinese (Simplified)_China.936;LC_CTYPE=Chinese (Simplified)_China.936;LC_MONETARY=Chinese (Simplified)_China.936;LC_NUMERIC=C;LC_TIME=Chinese (Simplified)_China.936"
> l10n_info()
$`MBCS`
[1] TRUE

$`UTF-8`
[1] FALSE

$`Latin-1`
[1] FALSE

$codepage
[1] 936

> Sys.localeconv()
    decimal_point     thousands_sep          grouping   int_curr_symbol   currency_symbol 
              "."                ""                ""             "CNY"              "¥" 
mon_decimal_point mon_thousands_sep      mon_grouping     positive_sign     negative_sign 
              "."               ","            "\003"                ""               "-" 
  int_frac_digits       frac_digits     p_cs_precedes    p_sep_by_space     n_cs_precedes 
              "2"               "2"               "1"               "0"               "1" 
   n_sep_by_space       p_sign_posn       n_sign_posn 
              "0"               "4"               "4" 

BruceZhaoR avatar Jul 26 '18 03:07 BruceZhaoR

Hi Bruce, thanks for the reply!

Yeah I should have mentioned that both of my code examples are specific to an English local.

Ah, I've never used lbs amap, that's cool. I use the Baidu Maps API (via baidumap R package), for situations in which the geolocChina functions fail. It's not ideal though, since there's a daily limit, and getting county/city/province info for a string requires two API calls (the first to get lat/lon, the second takes in the lat/lon and returns location info).

Cheers!

ChrisMuir avatar Jul 26 '18 05:07 ChrisMuir