1

我正在处理一项大型调查。主要调查数据存储为 CSV 文件。变量和值标签文件包含在 SAS 格式和 SPSS 格式中,但存储为 TXT 文件。

我已经看到,当数据以SAS/SPSS/STATA 本机格式 (.sas/.sav/.dta)保存时,有几种方法可以轻松地将数据读入 R。到目前为止,我遇到的所有使用haven,解决方案labelledforeign解决方案都假定数据是使用相应的“其他”程序的本机格式存储的。我似乎找不到任何关于如何使用以某些标准 SAS/SPSS 格式存储的 TXT 文件为 CSV 数据添加标签的建议。

因此,假设我拥有的三个文件被命名为:

  • data.csv
  • sas_var_labels.txt
  • sas_val_labels.txt

data.csv看起来像:

AB001; AB002; AC001
-9; -9; -7
-1; -9; -8
-3; -9; 100
-9; -1; 200
-4; -1; 100

sas_var_labels.txt看起来像这样:

AB001         =  "A-Section A, category B, question 1"                                                                                                        
AB002         =  "A-Section A, category B, question 2"                                                                                                         
AC001         =  "A-Section A, category C, question 1"  

最后,sas_val_labels.txt看起来像这样:

; value AB001                       -9      =    "-9.not applicable"                                                                                                                                                                                   
                                    -8      =    "-8.no response"                                                                                                                                                                                      
                                    -7      =    "-7.unknown"                                                                                                                                                                                         
                                    -1      =    "-1.other duration"                                                                                                                                                                                     
                                    1       =    "1.1 year"                                                                                                                                                                                      
                                    2       =    "2.1 to 3 years"                                                                                                                                                                                           
                                    3       =    "3.4 to 6 years"
                                    4       =    "4.More than 6 years"                                                                                                                                                                                       
; value AB002                       -9      =    "-9.not applicable"                                                                                                                                                                                   
                                    -8      =    "-8.no response"                                                                                                                                                                                      
                                    -7      =    "-7.unknown"                                                                                                                                                                                         
                                    -1      =    "-1.other type"                                                                                                                                                                                     
; value AC001                       -9      =    "-9.not applicable"                                                                                                                                                                                   
                                    -8      =    "-8.no response"                                                                                                                                                                                      
                                    -7      =    "-7.unknowns" 
                                    -5      =    "-5.non-codable"
                                    -1      =    "-1.other category"                                                                                                                                                                                     
                                    100     =    "100.First division"                                                                                                                                                                                  
                                    200     =    "200.Second division"            

到目前为止我的方法:

对于主要数据,我只是调用:

dat <- read.csv("data.csv", sep=";", stringsAsFactors=FALSE)

对于变量标签,由于标签的数量与数据中的列完全匹配,我只是以相同的方式读取 TXT 文件,然后使用包中的将第二列分配给var_label()数据labelled

#first I read in the variable labels into R as a dataframe
var_labs <- read.csv("sas_var_label.txt", sep="=", stringsAsFactors=FALSE, header=FALSE, strip.white=TRUE)  

#next, I assign the second column to the data as it matches exactly (for the moment)  
labelled::var_label(dat) <- var_labs$V2

另一方面,对于值标签,我的方法变得更加复杂,因为标签文件没有被所有列和行组合的分隔符整齐地分隔,如您在上面提供的示例中所见。

我的第一个问题是:有没有一种简单的方法可以读取这个文件,以便保留值标签 TXT 文件的结构?

我更普遍的问题是:是否有更好的方法来处理这些标签 TXT 文件?我确定我遗漏了一些东西,所以欢迎提出任何建议。

4

1 回答 1

1

回到这一点,我从来没有找到一个简单的解决方案来解决这个问题,但我确实设法一起破解了一个解决方案,我在这里分享,以防万一其他人遇到这个问题。据我所知,从 SAS 或 SPSS 导出标签文件的 TXT 文件会遇到类似的问题。即,为了将这些标签文件与 R 中的havenlabelled包一起使用(特别是后者),需要将 TXT 文件转换为命名对象。对于变量标签文件,这意味着一个命名字符向量,对于值标签文件,这意味着一个命名列表。我在这里只讨论 SAS 导出,但同样的基本方法也适用于 SPSS 导出。

将变量标签导入 R

所以回到我最初的问题中提供的玩具示例,我们考虑一个变量标签文件的 TXT 导出,sas_var_labels.txt如下所示:

AB001         =  "A-Section A, category B, question 1"                                                                                                        
AB002         =  "A-Section A, category B, question 2"                                                                                                         
AC001         =  "A-Section A, category C, question 1" 

在这种情况下,各个列的间距是均匀的,因此read_delim使用空格作为分隔符的简单调用效果很好:

varlist <- readr::read_delim("sas_var_labels.txt", 
                                 delim = " = ", 
                                 col_names = FALSE, 
                                 col_types = cols(X1 = col_character(),
                                                  X2 = col_skip(),
                                                  X3 = col_character(),
                                                  X4 = col_skip()
                                 ),
                                 locale = locale(encoding = "Latin1"), 
                                 trim_ws=TRUE)

varlist <- tibble::deframe(varlist)

在上面的代码中,可以看到我们可以直接控制TXT文件中的一些空格,当我们使用空格作为分隔符(即col_skip()参数)时,这些空格会被解析成空列。在我的示例中,我添加了一个locale编码参数来说明具有特殊字符的数据,并直接用trim_ws. 请注意,TXT 标签的 SPSS 导出有一个偏移量行,其顶部的列名会破坏此读取,但skip = 1如果需要,您可以轻松地将 a 添加到此调用中。

作为最后一步,将导入的varlist文件传递到tibble::deframe()将其转换为一个命名的字符向量,您可以使用该向量来用labelled包标记您的数据。最终输出应如下所示:
命名特征向量

将值标签导入 R

从 SAS 和 SPSS 导出的值标签处理起来有点棘手。为了让labelled打包者处理它们,需要将它们转换为命名列表。重要的是,与每个值标签关联的数值需要保存为数值类。

回到上面的 SAS 导出示例,假设我们有一个导出的标签 TXT 文件sas_val_labels.txt,如下所示:

; value AB001                       -9      =    "-9.not applicable"                                                                                                                                                                                   
                                    -8      =    "-8.no response"                                                                                                                                                                                      
                                    -7      =    "-7.unknown"                                                                                                                                                                                         
                                    -1      =    "-1.other duration"                                                                                                                                                                                     
                                    1       =    "1.1 year"                                                                                                                                                                                      
                                    2       =    "2.1 to 3 years"                                                                                                                                                                                           
                                    3       =    "3.4 to 6 years"
                                    4       =    "4.More than 6 years"                                                                                                                                                                                       
; value AB002                       -9      =    "-9.not applicable"                                                                                                                                                                                   
                                    -8      =    "-8.no response"                                                                                                                                                                                      
                                    -7      =    "-7.unknown"                                                                                                                                                                                         
                                    -1      =    "-1.other type"                                                                                                                                                                                     
; value AC001                       -9      =    "-9.not applicable"                                                                                                                                                                                   
                                    -8      =    "-8.no response"                                                                                                                                                                                      
                                    -7      =    "-7.unknowns" 
                                    -5      =    "-5.non-codable"
                                    -1      =    "-1.other category"                                                                                                                                                                                     
                                    100     =    "100.First division"                                                                                                                                                                                  
                                    200     =    "200.Second division"

首先,我们可以通过使用列似乎服从固定的事实将其分解为有序的列。使用read_fwf(), 并删除所有无关的文本和字符,直到我们到达组织整齐的数据框。然后我们可以遍历该数据框以提取变量名称,然后将所有内容重新组织成每个名称下的值的大列表。以下对我有用:

value_df <- readr::read_fwf(valuelab, 
                                    fwf_positions(c(1, 9, 29, 43, 49), 
                                                  c(7, 25, 43, 47, NA), 
                                                  c("junk", "var", "val", "delim", "val_lab")),
                                    col_types = cols(junk = col_skip(),
                                                     var = col_character(),
                                                     val = col_integer(),
                                                     delim = col_skip(),
                                                     val_lab = col_character()),
                                    locale = locale(encoding = "Latin1"),
                                    trim_ws = TRUE)
            
value_df <- value_df %>% tidyr::fill(var, .direction="down")
value_df$val_lab <- value_df$val_lab %>% str_replace_all("\"", "")

#Note: it may or may not be necessary to strip extra chars from variable names,
#depending on how the export was done to the TXT file
#value_df$var <- value_df$var %>% substr(., 1, nchar(.)-1) 

valuelist <- value_df %>% group_split(var) 
names(valuelist) <- value_df %>% group_keys(var) %>% pull
        
valuelist <- sapply(valuelist, function(x) {
            x %>% select(val_lab, val) %>% tibble::deframe

这里要注意的关键点是,read_fwf接受参数fwf_positions,您可以在其中指定要分隔列的确切位置(read_fwf有关详细信息,请阅读帮助文件)。滚动浏览我的 TXT 文件并使用一些试验和错误,我可以得到这个参数的写入数字。

因为生成的列仍然有些混乱,我还在解析时同时命名它们 ( c("junk", "var", "val", "delim", "val_lab"))),以便我可以轻松跟踪我随后如何处理每个新解析的列 ( col_types = cols(junk = col_skip(), var = col_character(), ...)。在此步骤中直接命名它们也有助于稍后我们需要将所有内容分解到命名列表中。

接下来的几行只是去掉了所有无关的字符,并确保变量名被正确填写( tidyr::fill(var, .direction="down"))。

最后,我使用dplyr::group_split()变量名称将数据框分解为一个列表,为列表对象分配这些名称 ( value_df %>% group_keys(var) %>% pull),然后遍历该列表中的每个变量以将值标签转换为正确的命名值标签-整数对 ( sapply(valuelist, function(x) {x %>% select(val_lab, val) %>% tibble::deframe)。

根据给出的示例,最终所需的输出应如下所示: 命名列表

最后,将两个标签文件导入 R,您可以轻松地使用它们来标记数据,如下所示:

labelled::var_labels(data) <- varlist

labelled::val_labels(data) <- valuelist

标记数据后,labelled包中包含的所有其他功能也可供您使用。

我还在我的博客上写了一篇关于我如何将这些函数用于特定用例的文章

于 2020-11-19T09:53:27.967 回答