在R中解析此结构化文本文件

时间:2017-11-22 21:37:57

标签: r stringr

我想解析下面的attributes.txt文件(来自Sawtooth调查研究的输出),以便得到的结果如下所示。你可以在下面看到我的尝试。有用。但它非常难看。必须有更好的方法,对吧? (如果有可用的话,我更喜欢tidyverse解决方案)

attributes.txt中:

================================================================================
ATTRIBUTES AND LEVELS
================================================================================

========================================
Display Text
========================================

<Same structure as shown below. But I do not want to extract any of this text>

========================================
Internal Labels
========================================

[Attribute List]:

1   brand
2   rating
3   price

---------------------------
Attribute 1: 
    brand

Levels: 
1   brand01
2   brand02
3   brand03
4   otherbrand

---------------------------
Attribute 2: 
    rating

Levels: 
1   1
2   2
3   3
4   4
5   5

---------------------------
Attribute 3: 
    price

Levels: 
1   99
2   199
3   299

解析所需的输出:

attribute,level,label
1,1,brand01
1,2,brand02
1,3,brand03
1,4,otherbrand
2,1,1
2,2,2
2,3,3
2,4,4
2,5,5
3,1,99
3,2,199
3,3,299

我的尝试:

library(stringr)

parse_attributes_file <- function(ATTRIBUTES_FILE_PATH) {
  con = file(ATTRIBUTES_FILE_PATH, "r")
  reached_internal_labels <- FALSE
  attribute_num <- NA
  datalist <- list()
  idx <- 0

  while ( TRUE ) {
    line = readLines(con, n = 1)
    if ( length(line) == 0 ) {
      break
    }
    if (!reached_internal_labels) {
      reached_internal_labels <- str_detect(line, "Internal Labels")
    } else {
      attribute_num_extract <- str_match(line, "Attribute ([[:digit:]]+): ")[,2]

      if(!is.na(attribute_num_extract)) {
        attribute_num <- attribute_num_extract
      } else {
        if (!is.na(attribute_num)) {
          my_match <- str_match(line, "([[:digit:]]+)\t(.*)")
          if(!is.na(my_match[,1])) {
            idx <- idx + 1
            datalist[[idx]] <- c(attribute_num, my_match[,2], my_match[,3])
          }
        }
      }
    }
  }

  close(con)

  attributes = do.call(rbind, datalist)
  colnames(attributes) <- c("attribute", "level", "label")
  return(attributes)
}

1 个答案:

答案 0 :(得分:1)

使用tidyverse函数执行相同操作的代码少了一些。首先,加载一些样本数据

# you'd do something like
# text <- readLines("yourtextfile")
# but for this sample...
text <- strsplit("================================================================================\nATTRIBUTES AND LEVELS\n================================================================================\n\n========================================\nDisplay Text\n========================================\n\n<Same structure as shown below. But I do not want to extract any of this text>\n\n========================================\nInternal Labels\n========================================\n\n[Attribute List]:\n\n1   brand\n2   rating\n3   price\n\n---------------------------\nAttribute 1: \nbrand\n\nLevels: \n1   brand01\n2   brand02\n3   brand03\n4   otherbrand\n\n---------------------------\nAttribute 2: \nrating\n\nLevels: \n1   1\n2   2\n3   3\n4   4\n5   5\n\n---------------------------\nAttribute 3: \nprice\n\nLevels: \n1   99\n2   199\n3   299", "\n")[[1]]

现在我们解析文件。首先,为每一行找到正确的属性

library(tidyverse)
attributes <- str_match(lines, "Attribute (\\d)")[, 2] %>% 
  accumulate(function(a, b) coalesce(b,a))

然后找到&#34;等级&#34;通过查找&#34;级别:&#34;并停在空白处

markers <- case_when(str_detect(lines, "^Levels:")~2,
          str_detect(lines, "^$")~1, 
          TRUE~0)
levels <- markers %>% accumulate(function(a,b) case_when(b==2~TRUE, b==1~FALSE, TRUE~a), .init=FALSE) %>% head(-1) %>%
  modify_if(markers==3, function(x) FALSE) %>% unlist

现在我们只将属性和级别数据合并到一个表中,只需readr将其解析为一个tibble

read_table(paste(attributes[levels], lines[levels], collapse="\n"),
            col_names=c("attribute", "level", "label"))

返回

# A tibble: 12 x 3
   attribute level      label
       <int> <int>      <chr>
 1         1     1    brand01
 2         1     2    brand02
 3         1     3    brand03
 4         1     4 otherbrand
 5         2     1          1
 6         2     2          2
 7         2     3          3
 8         2     4          4
 9         2     5          5
10         3     1         99
11         3     2        199
12         3     3        299