【问题标题】:How do I add SAS/SPSS-format labels stored as a .txt file to R?如何将存储为 .txt 文件的 SAS/SPSS 格式标签添加到 R?
【发布时间】:2020-07-23 23:00:23
【问题描述】:

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

我已经看到,当数据以 SAS/SPSS/STATA 本机格式 (.sas/.sav/.dta)保存时,有几种方法可以轻松地将数据读入 R。到目前为止,我遇到的所有使用havenlabelledforeign 解决方案的解决方案都假定数据是使用各自的“其他”程序的本机格式存储的。我似乎找不到任何关于如何使用以某些标准 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() 将第二列分配给数据987654335@包:

#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 文件?我确定我遗漏了一些东西,所以欢迎提出任何建议。

【问题讨论】:

  • 也许您应该添加部分预期输出,以便更容易理解最终目标?

标签: r sas r-haven


【解决方案1】:

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

将变量标签导入R

回到我最初的问题中提供的玩具示例,我们考虑一个变量标签文件sas_var_labels.txt 的 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 添加到此调用中。

作为最后一步,将导入的 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 %&gt;% group_keys(var) %&gt;% pull),然后遍历该列表中的每个变量以将值标签转换为正确的命名值标签-整数对 (sapply(valuelist, function(x) {x %&gt;% select(val_lab, val) %&gt;% tibble::deframe)。

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

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

labelled::var_labels(data) <- varlist

labelled::val_labels(data) <- valuelist

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

我还写了一篇关于我如何将这些函数用于a specific use case on my blog

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-25
    • 1970-01-01
    • 2021-09-11
    • 2021-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多