从R中的字符向量创建数据框

时间:2013-01-23 20:59:12

标签: xml regex r stringr

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

<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 数据:

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

我可以用同样的方式提取 info n 数据,但假设有多个 info monthyear 条目的> n 条目,我不知道如何将它们组合起来。我是不是错了?

1 个答案:

答案 0 :(得分:2)

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

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

这是一个循序渐进的例子。我已将HTML标头标记添加到您的示例文本中,并将其存储在此处:http://ideone.com/O1PC05

  1. 使用readLines

    读入您的数据
    x1 <- readLines("http://ideone.com/plain/O1PC05")
    
  2. 隔离&#34;身体&#34;的网页

    bodycontent <- grep("<body>|</body>", x1)
    x2 <- x1[(bodycontent[1]+1):(bodycontent[2]-1)]
    
  3. grepl返回TRUEFALSE for if&#34; monthyear&#34;在给定的行中找到了。使用cumsum创建&#34; groups&#34;和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. 现在,让我们以更有意义的方式命名列。我们知道第一栏将是&#34; monthyear&#34;设计变量,其他应该&#34; info&#34;和&#34; n&#34;,所以我们可以在rep中包含一些基本的paste来获取变量名。虽然我们正在使用它,但我们将使用动物园中的as.yearmon&#34;包转换我们的&#34; monyear&#34;变量到实际日期,允许我们对实际日期进行排序和做其他漂亮的事情。

    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
    
  9. 无论如何,试试看,并根据需要进行修改。我的猜测是,在某些时候,您必须在纯文本编辑器中打开文件,然后在那里进行一些初步清理。