为什么lubridate包中的dmy()函数不能处理NA值?有什么好的解决方法?

12
我在`lubridate`包中发现了一种奇怪的行为:`dmy(NA)`会引发错误而不是只返回NA。当我想要转换一个列,其中一些元素是NA而其他日期字符串通常可以轻松转换时,这会给我带来问题。
下面是一个最小示例:
library(lubridate)
df <- data.frame(ID=letters[1:5],
              Datum=c("01.01.1990", NA, "11.01.1990", NA, "01.02.1990"))
df_copy <- df
#Question 1: Why does dmy(NA) not return NA, but throws an error?
df$Datum <- dmy(df$Datum)
Error in function (..., sep = " ", collapse = NULL)  : invalid separator
df <- df_copy
#Question 2: What's a work around?
#1. Idea: Only convert those elements that are not NAs
#RHS works, but assigning that to the LHS doesn't work (Most likely problem::
#column "Datum" is still of class factor, while the RHS is of class POSIXct)
df[!is.na(df$Datum), "Datum"] <- dmy(df[!is.na(df$Datum), "Datum"])
Using date format %d.%m.%Y.
Warning message:
In `[<-.factor`(`*tmp*`, iseq, value = c(NA_integer_, NA_integer_,  :
invalid factor level, NAs generated
df #Only NAs, apparently problem with class of column "Datum"
ID Datum
1  a  <NA>
2  b  <NA>
3  c  <NA>
4  d  <NA>
5  e  <NA>
df <- df_copy
#2. Idea: Use mapply and apply dmy only to those elements that are not NA
df[, "Datum"] <- mapply(function(x) {if (is.na(x)) {
                                 return(NA)
                               } else {
                                 return(dmy(x))
                               }}, df$Datum)
df #Meaningless numbers returned instead of date-objects
ID     Datum
1  a 631152000
2  b        NA
3  c 632016000
4  d        NA
5  e 633830400

总结一下,我有两个问题:1)为什么dmy(NA)不起作用?根据大多数其他函数的表现,我会认为每次转换(例如dmy())都应该返回NA(就像2 + NA一样),这是良好的编程实践。如果这是预期的行为,那么如何通过dmy()函数转换包含NA的data.frame列呢?

众所周知,lubridate无法正确解析NA值:https://github.com/hadley/lubridate/issues/88 - Andrie
不是一个解决方案,而是“Error in function (..., sep = " ", collapse = NULL) : invalid separator”的原因是lubridate:::guess_format()函数引起的。在调用paste()时,将NA传递给了sep,具体地说是在fmts <- unlist(mlply(with_seps, paste))这里。 - jthetzel
2个回答

6
Error in function (..., sep = " ", collapse = NULL) : invalid separator这个错误是由于lubridate:::guess_format()函数引起的。在调用paste()时,NA被传递为sep,具体地说是在fmts <- unlist(mlply(with_seps, paste))处。您可以尝试改进lubridate:::guess_format()以解决该问题。
否则,您只需将NA更改为字符("NA")即可。
require(lubridate)
df <- data.frame(ID=letters[1:5],
    Datum=c("01.01.1990", "NA", "11.01.1990", "NA", "01.02.1990")) #NAs are quoted
df_copy <- df

df$Datum <- dmy(df$Datum)

感谢@jthetzel,这澄清了问题。然而,我对R特别是开源项目并不那么自信,无法检查源代码、修复它并发送补丁。希望有一天我能做到,但在那之前,我宁愿依赖于可能更稳定的基础Date类,而不是继续使用lubridate并遇到另一个问题。 - Christoph_J
1
没问题,@Christoph_J。我对这个函数进行了小修补,修复了错误。我会提交给维护者。同时,如果您想尝试它,源代码可在以下网址获取:http://commondatastorage.googleapis.com/jthetzel-public/lubridate_0.2.5.tar.gz,Windows二进制文件可在以下网址获取:http://commondatastorage.googleapis.com/jthetzel-public/lubridate_0.2.5.zip。 - jthetzel
谢谢@jthetzel,这很好用。现在我看到了一个示例,了解了如何提出补丁。 - Christoph_J

3

由于您的日期格式相对简单,可能更简单的方法是使用as.Date函数并指定适当的format参数:

df$Date <- as.Date(df$Datum, format="%d.%m.%Y")
df

  ID      Datum       Date
1  a 01.01.1990 1990-01-01
2  b       <NA>       <NA>
3  c 11.01.1990 1990-01-11
4  d       <NA>       <NA>
5  e 01.02.1990 1990-02-01

如果您想查看as.Date使用的格式代码列表,请参见?strptime


1
谢谢@Andrie。虽然我认为这个包相当直观(否则,我总是在R中苦苦挣扎日期;-),但我仍然对为什么“dmy”不能胜任感兴趣。所以我把它留给未回答的问题。如果没有好的解决方法或解释提出来,我会遵循你的建议并使用基本的“as.Date”。 - Christoph_J
2
由于这是一个已知问题(https://github.com/hadley/lubridate/issues/88),正确的做法是下载代码,修复问题并向软件包作者发送补丁。 - Andrie
我的错!我浏览了那个列表,但没有发现这个。所以我会遵循你的建议!再次感谢。 - Christoph_J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接