我有一个很大的 stata 文件,我认为其中有一些法国口音的字符保存得不好。
当我导入编码设置为空白的文件时,它不会读入。当我将其设置为
latin1
时,它会读入,但在一个变量中,并且我确信在其他变量中,法语重音字符是未正确渲染。我对另一个 stata 文件也遇到了类似的问题,我尝试在这里应用修复(实际上在这种情况下不起作用,但似乎很重要)。
说实话,这似乎是这里真正的问题。很多乱码是“实际的”,它们符合“预期的”,但我不知道回去。
可重现的代码在这里:
library(haven)
library(here)
library(tidyverse)
library(labelled)
#Download file
temp <- tempfile()
temp2 <- tempfile()
download.file("https://github.com/sjkiss/Occupation_Recode/raw/main/Data/CES-E-2019-online_F1.dta.zip", temp)
unzip(zipfile = temp, exdir = temp2)
ces19web <- read_dta(file.path(temp2, "CES-E-2019-online_F1.dta"), encoding="latin1")
#Try with encoding set to blank, it won't work.
#ces19web <- read_dta(file.path(temp2, "CES-E-2019-online_F1.dta"), encoding="")
unlink(c(temp, temp2))
#### Diagnostic section for accented characters ####
ces19web$cps19_prov_id
#Note value labels are cut-off at accented characters in Quebec.
#I know this occupation has messed up characters
ces19web %>%
filter(str_detect(pes19_occ_text,"assembleur-m")) %>%
select(cps19_ResponseId, pes19_occ_text)
#Check the encodings of the occupation titles and store in a variable encoding
ces19web$encoding<-Encoding(ces19web$pes19_occ_text)
#Check encoding of problematic characters
ces19web %>%
filter(str_detect(pes19_occ_text,"assembleur-m")) %>%
select(cps19_ResponseId, pes19_occ_text, encoding)
#Write out messy occupation titles
ces19web %>%
filter(str_detect(pes19_occ_text,"Ã|©")) %>%
select(cps19_ResponseId, pes19_occ_text, encoding) %>%
write_csv(file=here("Data/messy.csv"))
#Try to fix
source("https://github.com/sjkiss/Occupation_Recode/raw/main/fix_encodings.R")
#store the messy variables in messy
messy<-ces19web$pes19_occ_text
library(stringi)
#Try to clean with the function fix_encodings
ces19web$pes19_occ_text_cleaned<-stri_replace_all_fixed(messy, names(fixes), fixes, vectorize_all = F)
#Examine
ces19web %>%
filter(str_detect(pes19_occ_text_cleaned,"Ã|©")) %>%
select(cps19_ResponseId, pes19_occ_text, pes19_occ_text_cleaned, encoding) %>%
head()
您的数据文件是 dta 版本 113 文件(文件中的第一个字节是 113)。也就是说,它是一个 Stata 8 文件,尤其是 Stata 14 之前的文件,因此使用自定义编码(Stata >=14 使用 UTF-8)。
因此使用
encoding
的 read_dta
参数似乎是正确的。但这里有一些问题,用十六进制编辑器可以看出。
首先,重音字母处的截断标签(例如 Québec → Qu)实际上不是由 Haven 引起的:它们在 dta 文件中存储为截断的。
pes19_occ_text
采用 UTF-8 编码,您可以通过以下方式进行检查:
ces19web <- read_dta("CES-E-2019-online_F1.dta", encoding="UTF-8")
grep("^Producteur", unique(ces19web$pes19_occ_text), value = T)
output: "Producteur télé"
这个
"é"
是读作latin1的UTF-8数据(这里是"é"
)的特征。
但是,如果您尝试使用 encoding="UTF-8"
导入,read_dta
将失败:文件中可能存在其他非 UTF-8 字符,read_dta
无法读取为 UTF-8。导入后我们必须做一些事情。
这里,
read_dta
正在做一些令人讨厌的事情:它像latin1数据一样导入"Producteur télé"
,并转换为UTF-8,因此编码字符串实际上具有UTF-8字符“Ë”和“©”。
要解决此问题,您必须首先转换回 latin1。该字符串仍将是“Producteur télé”,但以 latin1 编码。
然后,您只需将编码强制为 UTF-8,而不需要进行转换,而无需更改数据。
这是代码:
ces19web <- read_dta("CES-E-2019-online_F1.dta", encoding="")
ces19web$pes19_occ_text <- iconv(ces19web$pes19_occ_text, from = "UTF-8", to = "latin1")
Encoding(ces19web$pes19_occ_text) <- "UTF-8"
grep("^Producteur", unique(ces19web$pes19_occ_text), value = T)
output: "Producteur télé"
您可以对带有变音符号的其他变量执行相同的操作。
如果我们使用
iconv
转换为原始数据以查看实际字节,那么这里 charToRaw
的使用可能会更容易理解。导入数据后,“télé”就是“74 c3 83 c2 a9 6c c3 83 c2 a9”在UTF-8中的表示。第一个字节 0x74(十六进制)是字母“t”,0x6c 是字母“l”。在这之间,我们有四个字节,而不是 UTF-8 中的字母“é”只有两个字节(“c3 a9”,即读作 latin1 时的“é”)。
实际上,“c3 83”是“à”,“c2 a9”是“©”。
因此,我们首先要把这些字符转换回latin1,这样它们每个就占一个字节。那么“74 c3 a9 6c c3 a9”就是“télé”的编码,但这次是 latin1。也就是说,该字符串与 UTF-8 编码的“télé”具有相同的字节,我们只需要告诉 R 编码不是 latin1 而是 UTF-8(这不是转换)。 另请参阅
Encoding和 iconv 的帮助页面。
现在一个好问题可能是:您最初是如何得到如此糟糕的 dta 文件的? Stata 8 文件保存 UTF-8 数据是相当令人惊讶的。我想到的第一个想法是
saveold 命令的错误使用,该命令允许将数据保存在旧版本的 Stata 文件中。但根据参考手册,在Stata 14中saveold
只能存储Stata >=11的文件。
回答评论:如何循环字符变量来执行相同的操作? dplyr 有一种更聪明的方法,但这里是一个带有基本 R 的简单循环。
ces19web <- read_dta("CES-E-2019-online_F1.dta")
for (n in names(ces19web)) {
v <- ces19web[[n]]
if (is.character(v)) {
v <- iconv(v, from = "UTF-8", to = "latin1")
Encoding(v) <- "UTF-8"
}
ces19web[[n]] <- v
}