【问题标题】:Dealing with Messy Dates处理凌乱的日期
【发布时间】:2011-05-31 22:26:54
【问题描述】:

我希望你不认为我在寻求关系方面的建议。

很少,我必须为调查对象提供指定事件发生时间的能力。结果是一个非常混乱的字符串,老实说我不知道​​该怎么处理。超越手动重新编码。

这是一个简短的样本,有数千个:

c("5 月 2 日/中午 12 点", "9:45 am", "11:00 AM AST", "4 月 27 日 / 12:00 AST", “美国东部标准时间上午 11:40”、“2011 年 4 月 25 日”、“2011 年 4 月 12 日 / 8:44”、“2011 年 4 月 12 日 / 8:36am”、 “2011 年 4 月 12 日/上午 8:30”、“2011 年 4 月 12 日/上午 8:18”、“2011 年 4 月 12 日/上午 8:12”、 “2011 年 4 月 11 日 / 下午 5:57”、“2011 年 4 月 11 日 / 下午 5:49”、“2011 年 4 月 11 日 / 下午 5:42”、 “2011 年 4 月 11 日 / 下午 5:36”、“2011 年 4 月 11 日 / 5:27”、“4 月 5 日上午 11:26”、 “8:50”、“4月4日中午12点45分”、“4月4日上午10点左右”、“4月4日上午10点左右”、 “2011 年 3 月 18 日上午 9:33”、“2011 年 3 月 18 日上午 9:27”、“df”、“fg”、“12:16”、 “9:50”、“2011 年 2 月 8 日/下午 12:20”、“2011 年 2 月 4 日上午 8:34”、“2011 年 1 月 31 日下午 2:50”、 “2011 年 1 月 31 日下午 2:45”、“2011 年 1 月 31 日下午 2:38”、“2011 年 1 月 31 日下午 2:26”、 “11h09”、“11:00 am”、“1h02 pm”、“10h03”、“2h10”、“2011 年 1 月 13 日上午 9:50 范”, “2011 年 1 月 12 日”、“2011 年 1 月 12 日下午 3:59”、“1 月 12 日下午 14:19”、 “2011 年 1 月 12 日下午 1:35”、“2011 年 1 月 12 日下午 1:28”、“1h36”、“9h15”、 “9h09”,“8h51”,“8h45”,“8h35”,“1h12pm”,“12h59”,“11h52am”, "10h45", "15h55", "12 月 31 日, 10 日上午 11:11", "12 月 31,10 日上午 10:15", “2010 年 12 月 30 日下午 12:32”、“2010 年 12 月 30 日下午 12:18”、“上午 9:16”、“上午 11 点 16 分”、 "11h12", "9h29 am", "11h38", "2010 年 12 月 16 日", "2010 年 12 月 16 日", "2010 年 12 月 16 日"、"2010 年 12 月 15 日"、"2010 年 12 月 14 日"、"12 月 14 日 11:38"、 “12 月 14 日 11:35”、“12 月 14 日 11:25”、“2010 年 12 月 13 日”、“12 月 10 日下午 1:38”、 “12 月 10 日下午 1:26”、“12 月 10 日下午 1:20”、“12 月 10 日下午 1:12”、“2010 年 12 月 9 日”、 "11h10 am", "10h59 am", "10:50 am", "12 月 7 日星期二, 9:45 Van time", “2010 年 12 月 3 日下午 12:30”、“2010 年 12 月 3 日下午 12:20”、“2010 年 12 月 3 日下午 12:10”、 “2010 年 11 月 30 日下午 4 点”、“2010 年 11 月 30 日”、“11 月 29 日下午 120 点”、 “2010 年 11 月 29 日 11:27”、“2010 年 11 月 29 日上午 10:12”、“2010 年 11 月 26 日下午 1:18”、 “上午 10:56”、“11 月 24 日”、“11 月 24 日/下午 4:20 AST”、“11 月 24 日/4:00 PM AST”、 “11 月 24 日下午 2:10”、“11 月 24 日上午 11:00”、“12:05 MST”、 "3.55PM", "11 月 17 日/2010 年 12:45 pm", "11 月 16/10 日中午 12:00", "11 月 16/10 日 11:50 am", “2010 年 11 月 16 日上午 11:30”,“2010 年 11 月 12 日下午 12:23”,“2010 年 11 月 11 日下午 2:20”, “2010 年 11 月 11 日下午 2:15”、“11 月 11 日下午 2:00”、“11 月 10 日上午 10:22”、 "11 月 8 日...下午 3:19"、"11 月 8 日 1 点;下午 50 点"、"11 月 8 日...中午 12 点"、 “11 月 8 日/10 月 10 日:上午”、“2010 年 11 月 5 日下午 1:10”、“CST 上午 11:32”、 “11 月 4 日 11:10”、“11 月 3 日上午 10 点”、“9:30 上午”、“2010 年 11 月 2 日下午 1:50”、 “2010 年 10 月 29 日下午 2:50”、“10 月 28 日上午 11:20”、“2010 年 10 月 27 日上午 10:40”、“2010 年 10 月 26 日 11:18”、 “10 月 26 日上午 11 点”、“10 月 26 日上午 10:30”、“10 月 26 日 10:50”、“2010 年 10 月 25 日 13:50”、 "2010 年 10 月 22 日 10:15"、"10 年 10 月 22 日上午 10 点"、"2010 年 10 月 21 日下午 3:00"、 "2010 年 10 月 21 日 2:59", "10/21/2010 11:50", "10/21/2010 11:45", “2010 年 10 月 21 日 11:40”、“2010 年 10 月 21 日 11:30”、“11:30”、“10 月 20 日下午 1 点左右”、 "2010 年 10 月 20 日下午 4:50"、"13:48"、"13:45"、"2010 年 10 月 20 日上午 11:45"、 "10 月 19 日下午 3:05", "2010 年 10 月 18 日下午 2:15", "10 月 18 日下午 3:10", “上午 10:30”、“10 月 15 日上午 11:50”、“10 月 14 日上午 11:05”、“10 月 14 日/ 11:06”、 “大西洋时间 10 月 13 日 4:40”,“大西洋时间 10 月 13 日下午 4:05”,“大西洋时间 10 月 13 日 1:45”, "10 月 13 日 / 10:37", "10 月 12 日 3:33", "2010 年 10 月 12 日下午 1:10", "10 月 12 日 / 11:45", “10 月 12 日 / 9:45”、“2010 年 10 月 8 日/ 2:00”、“10 月 8/10- 1145am”、“2010 年 9 月 2 日下午 3.52”、 “2010 年 9 月 2 日上午 10 点 21 分”、“2010 年 9 月 1 日下午 2 点”、“2010 年 9 月 1 日”、“2010 年 8 月 31 日 - 上午 11 点 52 分”、 “8 月 31 日上午 10:40”、“2010 年 8 月 31 日 - 上午 10 点”)

通常,这些事件发生在受访者填写调查表的日期附近,但并非总是如此。调查日期以一致的格式自动记录,并且可以使用as.Date 轻松转换为 POSIX,因此,可以忽略仅包含时间的元素并将其与填写调查的日期合并。

非常感谢您的想法。

注意 1:你们中的一些人可能会说,就验证响应而言,您应该做 X、Y 或 Z。对你,我说——地狱是的——下一次。不是我设计的!我只需要处理它。

一些有助于解决问题的事实:

  • 时间始终是工作日时间,上午 9 点到下午 6 点(因此上午/下午无关紧要)
  • 年份无关紧要,因为我可以将它们从另一个领域拉出来(它永远只会是 2011/2010 年,谢天谢地,这超出了任何表示法的可能时间范围)
  • 我不关心时区,因为我知道它们的地理位置

到目前为止我做了什么:

mos <- strsplit('
jan
feb
mar
apr
may
jun
jul
aug
sep
oct
nov
dec
january
february
march
april
may
june
july
august
september
october
november
december
', '\n')[[1]][-1]

days <- strsplit('
mon
tue
wed
thu
fri
sat
sun
monday
tuesday
wednesday
thursday
friday
saturday
sunday
', '\n')[[1]][-1]
## Messy Date Wrangling
x <- ## that hot ghetto mess above
# minimize
x <- tolower(x)
# remove unnecessary crap
x <- sub("2011"," ",x)
x <- sub("2010"," ",x)
x <- sub("am"," ",x)
x <- sub("pm"," ",x)
x <- sub("[p][.][m]"," ",x)
x <- sub("[a][.][m]"," ",x)
x <- sub("[.]{3}"," ",x)
x <- str_trim(x, side="both")
# divide
x <- strsplit(x,c(" "))
# conquer?

lapply(x, function(x) pmatch(x,mos))
lapply(x, function(x) pmatch(x,days))

【问题讨论】:

  • 你这个可怜的混蛋。看着那个样本,我嘴里吐了一点。虽然我无法用您的编程语言提供解决方案,但我衷心祝您好运。
  • 作为一个调查猴子,我试图给出尽可能完整的答案。我希望你觉得这很有用。
  • 可以在lubridate 包中找到更新的解决方案,它在处理混乱的日期方面有一些技巧。

标签: r


【解决方案1】:

我很同情你的约会结果不如预期的那么美好。 ;-)

我按照@Rguy 的建议构建了一个(仍然是部分)解决方案。

(请注意,此代码仍有一个错误:它并不总是返回正确的时间。出于某种原因,它并不总是对冒号前的数字进行贪婪匹配,因此有时会返回 1:00当时间是 11:00 时。)

首先,构造一个环绕gsubgrep 的辅助函数。此函数将字符向量作为其参数之一,并将其​​折叠成由 | 分隔的单个字符串。这样做的效果是让您可以轻松地传递多个模式以由正则表达式匹配:

find.pattern <- function(x, pattern_list){
  pattern <- paste(pattern_list, collapse="|")
  ret <- gsub(paste("^.*(", pattern, ").*", sep=""), "\\1", x, ignore.case=TRUE)
  ret[ret==x] <- NA 
  ret2 <- grepl(paste("^(", pattern, ")$", sep=""), x, ignore.case=TRUE)
  ret[ret2] <- x[ret2] 
  ret
}

接下来,使用一些内置变量名来构造月份和缩写的向量:

all.month <- c(month.name, month.abb)

最后,用不同的提取物构造一个数据框:

ret <- data.frame(
    data = dat, 
    date1 = find.pattern(dat, "\\d+/\\d+/\\d+"),
    date2 = find.pattern(dat, 
      paste(all.month, "\\s*\\d+[(th)|,]*\\s{0,3}[(2010)|(2011)]*", collapse="|", sep="")),
    year = find.pattern(dat, c(2010, 2011)),
    month = find.pattern(dat, month.abb), #Use base R variable called month.abb for month names
    hour = find.pattern(dat, c("\\d+[\\.:h]\\d+", "12 noon")),
    ampm = find.pattern(dat, c("am", "pm"))
)

结果:

head(ret, 50)
                      data  date1        date2 year month  hour ampm
20   April 4th around 10am   <NA>   April 4th  <NA>   Apr  <NA>   am
21   April 4th around 10am   <NA>   April 4th  <NA>   Apr  <NA>   am
22     Mar 18, 2011 9:33am   <NA> Mar 18, 2011 2011   Mar  9:33   am
23     Mar 18, 2011 9:27am   <NA> Mar 18, 2011 2011   Mar  9:27   am
24                      df   <NA>         <NA> <NA>  <NA>  <NA> <NA>
25                      fg   <NA>         <NA> <NA>  <NA>  <NA> <NA>
26                   12:16   <NA>         <NA> <NA>  <NA> 12:16 <NA>
27                    9:50   <NA>         <NA> <NA>  <NA>  9:50 <NA>
28   Feb 8, 2011 / 12:20pm   <NA>  Feb 8, 2011 2011   Feb  2:20   pm
29         8:34 am  2/4/11 2/4/11         <NA> <NA>  <NA>  8:34   am
30     Jan 31, 2011 2:50pm   <NA> Jan 31, 2011 2011   Jan  2:50   pm
31     Jan 31, 2011 2:45pm   <NA> Jan 31, 2011 2011   Jan  2:45   pm
32     Jan 31, 2011 2:38pm   <NA> Jan 31, 2011 2011   Jan  2:38   pm
33     Jan 31, 2011 2:26pm   <NA> Jan 31, 2011 2011   Jan  2:26   pm
34                   11h09   <NA>         <NA> <NA>  <NA> 11h09 <NA>
35                11:00 am   <NA>         <NA> <NA>  <NA>  1:00   am
36                 1h02 pm   <NA>         <NA> <NA>  <NA>  1h02   pm
37                   10h03   <NA>         <NA> <NA>  <NA> 10h03 <NA>
38                    2h10   <NA>         <NA> <NA>  <NA>  2h10 <NA>
39 Jan 13, 2011 9:50am Van   <NA> Jan 13, 2011 2011   Jan  9:50   am
40            Jan 12, 2011   <NA> Jan 12, 2011 2011   Jan  <NA> <NA>

【讨论】:

  • 我想你想以小时模式转义点:[\\.:h]。换句话说,它的意思是“任何单个字符”。
【解决方案2】:

这可能是少数几个最好使用除 R 之外的其他工具的情况之一。我知道已经开发了一些 Perl 模块来解析看起来凌乱的日期,在模块 DateTime::Format::Natural::Lang::EN 上可以解析字符串,例如:“1st tuesday last november”。我似乎记得另一个模块可以理解诸如“二月第一个星期一之后的第二个星期二”之类的东西。

http://www.datasciencetoolkit.org/ 也有一个工具,它可以抓取文本中的日期并将其转换为标准格式。

【讨论】:

    【解决方案3】:

    我现在不打算尝试编写该函数,但我有一个可行的想法。

    在每个字符串中搜索一个 4 位数字来调用年份。

    使用grep 在每个字符串中搜索月份缩写的前 3 个字母。似乎您的几乎所有数据(至少在上面)都有这样的标识符。我将存储在“月”向量中找到的值,并在没有找到值的地方放置空白。这是代码的一个非常丑陋的版本(我稍后会提高效率,并在月份不大写时添加大小写!)

    mos <- c("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec")   
    blah <- lapply(1:12, function(i) grepl(mos[i], test))   
    lapply(blah, function(i) which(i))   
    months <- 0*(1:length(test))   
    for (i in 1:12) {   
      months[blah[[i]]] <- i   
    }  
    
    
       months
      [1]  5  0  0  4  0  4  4  4  4  4  4  4  4  4  4  4  4  0  4  4  4  3  3  0  0  0  0  2  0  1
     [31]  1  1  1  0  0  0  0  0  1  1  1  1  1  1  0  0  0  0  0  0  0  0  0  0  0 12 12 12 12  0
     [61]  0  0  0  0 12 12 12 12  0 12 12 12 12 12 12 12 12 12  0  0  0 12 12 12 12 11 11  0 11 11
     [91] 11  0 11  0 11  0 11  0  0 11 11 11  0 11  0 11 11 11  0 11 11 11 11  0 11  0  0  0 10 10
    [121] 10  0 10 10 10  0  0 10 10 10  0  0  0  0  0 10 10  0  0 10 10 10 10  0 10  0 10  0  0  0
    [151] 10  0 10 10 10 10 10  9  9  9  9  8  0  0 
    

    “日”通常紧跟在用于月份的单词之后。因此,如果月份后有一位或两位数字(即字符),请提取该数字并将其称为当天。

    时间最常用的是“:”或“.”符号,因此在每个字符串中搜索该字符。如果在字符串中找到,则创建一个“时间”向量,其中包含该字符之前和之后的所有数字(理论上,包括之前的 2 个和之后的 2 个应该不会导致问题)。只要符号不存在,就填上空格。如果所有数据都绝对限制在 12 小时以内,那就太好了,因为这样您就不必担心上午和下午了。如果没有,也可以在字符串中搜索“AM”和“PM”。

    然后,尝试将具有以上所有四个的字符串转换为 POSIXct。那些不转换的,你当然必须手动输入。我认为编写上述函数需要几个小时,并且取决于数据集的可变性和大小,它可能值得也可能不值得。此外,错误输出存在一定风险,因此添加可接受的时间范围将有助于避免这种情况。

    总之,听起来您将不得不编写一个包含大量异常的函数,然后最终还是要手动编写大部分时间。不过,我希望有人可以为您提供更好的解决方案。

    祝你好运!

    【讨论】:

    • 和 ++ 表示“
    【解决方案4】:

    wolfram 阿尔法 http://www.wolframalpha.com/ 绝对是完成这项工作的好工具。

    至少,它成功地解释了您数据中的一些杂乱输入。 值得一试。

    我不确定这个网站是否适合非常大的数据集,但如果数据不是那么大,它会很有用。

    编写一个发送查询、获取数据并解析它的自动化脚本并不难,尽管我不确定网站是否允许这样的使用。

    【讨论】:

      【解决方案5】:

      其他人已经解决了标准方法和包。我会采取不同的观点。使用正则表达式和固定格式将帮助您完成大部分工作。剩下的,我会像处理“模式匹配”中的任何问题一样简单地处理它:统计方法或机器学习。您已经指定了日期和时间范围,日志的时间戳也可以提供信息。通过提取大量文本特征(这正是正则表达式有用的地方),您可以尝试映射到感兴趣的时间。

      要让它正常工作,只需做三件事:

      1. 特征提取
      2. 训练集生成
      3. 构建和部署模型

      构建和部署模型?让我把你介绍给我的朋友 R 和machine learning task view。 :) 要探索的基本模型包括多项式模型(看看glmnet)、决策树和支持向量机。您可以使用决策树和 SVM 作为多项模型的输入(毕竟 SVM 可能不是必需的)。老实说,这部分是模糊的:可以将这种建模作为断开的日期组件或作为改进的过程来进行,例如如果可能,获取年份,然后是分钟(因为范围远大于小时、天、月),然后是月中的某天,最后是小时和月。本质上,我的目标是尝试识别数字/字符串组件的“部分时间”(类似于词性)。

      特征提取:我会尝试使用冒号、逗号、斜杠、破折号、句点等进行拆分。任何不是数值的东西。然后,我将按顺序和任何顺序(即所见特征的指标值,忽略位置)创建基于特征的数据集。

      训练数据:亚马逊的 Mechanical Turk。

      或者,你知道吗,只是忽略所有那些编程和统计的庞然大物,并将所有内容发送给 Mechanical Turk。 :)

      【讨论】:

        猜你喜欢
        • 2019-01-19
        • 2022-01-17
        • 1970-01-01
        • 2021-10-30
        • 1970-01-01
        • 2011-04-17
        • 2013-12-21
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多