0

我有一些文本形式的数据,取自网页。它很长,但遵循以下形式:

<p><span class="monthyear">Jan 2001</span>
<br><b>Foo text (2)</b></p>
<p><span class="monthyear">Nov 2006</span>
<br><b>Bar text (29)</b>
<br><b>More bar text (4)</b>
<br><b>Yet more bar text (102)</b></p>
<p><span class="monthyear">Apr 2004</span>
<br><b>Further foo text (1)</b>
<br><b>Combination foo and bar text (41)</b></p>

我想将其相关部分提取到数据框中,如下所示:

  monthyear          info  n
1  Jan 2001      Foo text  2
2  Nov 2006      Bar text 29
3  Nov 2006 More bar text  4

...但我不知道该怎么做。如果我在一个名为 text 的字符向量中有 html,我可以使用stringr包中的函数提取月份数据:

monthyear <- str_extract_all(
text[1],perl("(?<=\\\"monthyear\\\">).*?20[0-9]{2}")
)

我可以以相同的方式提取信息n数据,但鉴于每个月年条目有多个信息n条目,我不知道如何组合它们。我对这一切都错了吗?

4

1 回答 1

2

不幸的是,我们无法始终控制数据源的质量,因此我们不得不求助于一些繁琐的手动处理。(有人说数据分析师的大部分时间都花在清理数据上,而不是分析上。)

正如评论中已经指出的那样,正则表达式并不是处理 HTML 的最佳工具,因为 HTML 通常不是真正的常规语言(我认为它被称为无上下文语言)。但是,如果 HTML 源代码有些规则(就像您提供的示例数据中那样),您仍然可以有效地使用它们。

这是一个分步示例。我已将 HTML 标题标签添加到您的示例文本中并将其存储在此处:http: //ideone.com/O1PC05

  1. 使用读入您的数据readLines

    x1 <- readLines("http://ideone.com/plain/O1PC05")
    
  2. 隔离网页的“正文”

    bodycontent <- grep("<body>|</body>", x1)
    x2 <- x1[(bodycontent[1]+1):(bodycontent[2]-1)]
    
  3. grepl如果在给定的行中找到“monthyear”,则返回 aTRUE或for。FALSE用于cumsum创建“组”,split并将字符向量转换为列表。

    x3 <- split(x2, cumsum(grepl("monthyear", x2)))
    
  4. 如果您愿意,可以分多个步骤执行以下操作。基本思想是lapply覆盖您的列表,用制表符替换所有 HTML 标记,并用制表符替换括号。之后,您可以使用,但由于我们插入的选项卡比我们需要的多得多read.delim,因此期望得到很多包含值的列。NA

    由于多种原因,这很可能是您失败的地方。(1) 它假定源数据确实结构良好...... (2) 但是,文本本身可能有括号...... (3) 或者,正文中可能有其他内容,包括脚本标签、表格标签,依此类推,将被读入并尝试处理。

    x4 <- read.delim(header = FALSE,
                     stringsAsFactors = FALSE,
                     strip.white = TRUE, 
                     sep = "\t", 
                     text = 
                       unlist(lapply(x3, 
                                     function(x) {
                                       temp <- gsub("<(.|\n)*?>", "\t", x)
                                       paste(gsub("[()]", "\t", temp), 
                                             collapse="\t")
                                       })))
    
  5. 我提到在第 4 步中,我们最终会得到很多垃圾列。让我们摆脱那些。

    x5 <- x4[apply(x4, 2, function(x) !all(is.na(x)))]
    
  6. 现在,让我们以更有意义的方式命名列。我们知道第一列是设计的“monthyear”变量,其他的应该是“info”和“n”,所以我们可以做一些基本rep的spaste来获取我们的变量名。当我们使用它时,我们将使用as.yearmon“zoo”包将我们的“monyear”变量转换为实际日期,从而允许我们对实际日期进行排序和做其他漂亮的事情。

    myseq <- ncol(x5[-1])/2 # We expect pairs of columns, right?
    names(x5) <- c("monthyear", 
                   paste(rep(c("info", "n"), myseq), 
                         sep(1:myseq, each = 2), sep = "."))
    library(zoo)
    x5$monthyear <- as.Date(as.yearmon(x5$monthyear, "%b %Y"))
    x5
    #    monthyear           info.1 n.1                       info.2 n.2            info.3 n.3
    # 1 2001-01-01         Foo text   2                               NA                    NA
    # 2 2006-11-01         Bar text  29                More bar text   4 Yet more bar text 102
    # 3 2004-04-01 Further foo text   1 Combination foo and bar text  41                    NA
    
  7. 如果您真的想要长格式的数据,请使用reshape

    x6 <- reshape(x5, 
                  direction = "long", 
                  idvar = "monthyear", 
                  varying = 2:ncol(x5))
    
  8. 进行一些可选的清理,例如按日期排序输出、重置行名和删除不完整的案例:

    x6 <- x6[order(x6$monthyear), ]
    rownames(x6) <- NULL
    x6[complete.cases(x6), ]
    #    monthyear time                         info   n
    # 1 2001-01-01    1                     Foo text   2
    # 4 2004-04-01    1             Further foo text   1
    # 5 2004-04-01    2 Combination foo and bar text  41
    # 7 2006-11-01    1                     Bar text  29
    # 8 2006-11-01    2                More bar text   4
    # 9 2006-11-01    3            Yet more bar text 102
    

无论如何,尝试一下,并根据需要进行修改。我的猜测是,在某些时候,您必须在纯文本编辑器中打开文件并在那里进行一些初步清理,然后才能继续。

于 2013-01-24T07:50:37.857 回答