Как R форматирует POSIXct с долями секунды

Я считаю, что R неправильно форматирует типы POSIXct с долями секунды. Я отправил это через R-bugs в качестве запроса на усовершенствование, и меня отмахнули словами: "Мы считаем, что текущее поведение правильное - ошибка удалена". Хотя я очень признателен за работу, которую они проделали и продолжают делать, я хотел, чтобы другие народы взялись за этот конкретный вопрос, и, возможно, за советы, как сделать это более эффективно.

Вот пример:

 > tt <- as.POSIXct('2011-10-11 07:49:36.3')
 > strftime(tt,'%Y-%m-%d %H:%M:%OS1')
 [1] "2011-10-11 07:49:36.2"

То есть tt создается как время POSIXct с дробной частью.3 секунды. Когда он печатается с одной десятичной цифрой, отображается значение.2. Я много работаю с метками времени с точностью до миллисекунды, и это вызывает у меня много головных болей, потому что время часто печатается на одну ступень ниже действительного значения.

Вот что происходит: POSIXct - это число с плавающей точкой, начиная с эпохи. Все целочисленные значения обрабатываются точно, но в плавающей точке base-2 ближайшее значение к.3 очень немного меньше, чем.3. Заявленное поведение strftime() для формата %OSn округлить до запрошенного количества десятичных цифр, поэтому отображаемый результат равен.2. Для других дробных частей значение с плавающей запятой немного выше введенного значения, и на дисплее отображается ожидаемый результат:

 > tt <- as.POSIXct('2011-10-11 07:49:36.4')
 > strftime(tt,'%Y-%m-%d %H:%M:%OS1')
 [1] "2011-10-11 07:49:36.4"

Аргумент разработчиков заключается в том, что для типов времени мы всегда должны округлять до требуемой точности. Например, если время равно 11:59:59.8, распечатайте его в формате %H:%M должен давать "11:59" не "12:00", а %H:%M:%S должен давать "11:59:59", а не "12:00:00". Я согласен с этим для целых чисел секунд и для формата флага %S, но я думаю, что поведение должно быть разным для флагов формата, которые рассчитаны на доли секунды. Я бы хотел увидеть %OSn использовать поведение с округлением до ближайшего, даже для n = 0 в то время как %S использует округление, чтобы печатать формат 11:59:59.8 с форматом %H:%M:%OS0 даст "12:00:00". Это не повлияет ни на что при целых числах секунд, потому что они всегда представлены точно, но более естественно будет обрабатывать ошибки округления за доли секунды.

Вот как обрабатывается печать дробных частей, например C, потому что целочисленное приведение округляется вниз:

 double x = 9.97;
 printf("%d\n",(int) x);   //  9
 printf("%.0f\n",x);       //  10
 printf("%.1f\n",x);       //  10.0
 printf("%.2f\n",x);       //  9.97

Я сделал быстрый обзор того, как обрабатываются дробные секунды в других языках и средах, и, похоже, консенсуса на самом деле нет. Большинство конструкций рассчитаны на целое число секунд, а дробные части являются запоздалой мыслью. Мне кажется, что в этом случае разработчики R сделали выбор, который не является абсолютно необоснованным, но на самом деле не лучшим и не соответствует принятым в других местах соглашениям для отображения чисел с плавающей запятой.

Что думают люди? Правильно ли поведение R? Вы сами это сделали?

2 ответа

Одна из основных проблем заключается в том, что представление POSIXct менее точно, чем представление POSIXlt, и представление POSIXct преобразуется в представление POSIXlt перед форматированием. Ниже мы видим, что если наша строка преобразуется непосредственно в представление POSIXlt, она выводит правильно.

> as.POSIXct('2011-10-11 07:49:36.3')
[1] "2011-10-11 07:49:36.2 CDT"
> as.POSIXlt('2011-10-11 07:49:36.3')
[1] "2011-10-11 07:49:36.3"

Мы также можем увидеть это, посмотрев на разницу между двоичным представлением двух форматов и обычным представлением 0,3.

> t1 <- as.POSIXct('2011-10-11 07:49:36.3')
> as.numeric(t1 - round(unclass(t1))) - 0.3
[1] -4.768372e-08

> t2 <- as.POSIXlt('2011-10-11 07:49:36.3')
> as.numeric(t2$sec - round(unclass(t2$sec))) - 0.3
[1] -2.831069e-15

Интересно, что похоже, что оба представления на самом деле меньше обычного представления 0.3, но второе либо достаточно близко, либо усечено не так, как я себе представляю. Учитывая это, я не буду беспокоиться о трудностях представления с плавающей запятой; они могут все еще случиться, но если мы будем осторожны с тем, какое представление мы используем, мы надеемся, что они будут минимизированы

Стремление Роберта к округленному выводу - это просто проблема вывода, и ее можно решить любым количеством способов. Мое предложение будет примерно таким:

myformat.POSIXct <- function(x, digits=0) {
  x2 <- round(unclass(x), digits)
  attributes(x2) <- attributes(x)
  x <- as.POSIXlt(x2)
  x$sec <- round(x$sec, digits)
  format.POSIXlt(x, paste("%Y-%m-%d %H:%M:%OS",digits,sep=""))
}

Это начинается с ввода POSIXct, и сначала округляется до нужных цифр; затем он конвертируется в POSIXlt и снова округляется. Первое округление гарантирует, что все единицы увеличиваются соответствующим образом, когда мы находимся на границе минута / час / день; второй округляется после преобразования в более точное представление.

> options(digits.secs=1)
> t1 <- as.POSIXct('2011-10-11 07:49:36.3')
> format(t1)
[1] "2011-10-11 07:49:36.2"
> myformat.POSIXct(t1,1)
[1] "2011-10-11 07:49:36.3"

> t2 <- as.POSIXct('2011-10-11 23:59:59.999')
> format(t2)
[1] "2011-10-11 23:59:59.9"
> myformat.POSIXct(t2,0)
[1] "2011-10-12 00:00:00"
> myformat.POSIXct(t2,1)
[1] "2011-10-12 00:00:00.0"

Напоследок: знаете ли вы, что стандарт допускает до двух дополнительных секунд?

> as.POSIXlt('2011-10-11 23:59:60.9')
[1] "2011-10-11 23:59:60.9"

ОК, еще одна вещь. Поведение фактически изменилось в мае из-за ошибки, поданной ОП ( Ошибка 14579); до этого это делало круглые доли секунды. К сожалению, это означало, что иногда это могло округлять до секунды, что было невозможно; в отчете об ошибках он поднялся до 60, когда должен был перенести на следующую минуту. Одна из причин, по которой было принято решение обрезать вместо округления, заключается в том, что он печатает из представления POSIXlt, где каждый модуль хранится отдельно. Таким образом, переход на следующую минуту / час / и т. Д. Более сложен, чем простая операция округления. Для простого округления необходимо округлить представление POSIXct, а затем выполнить обратное преобразование, как я предлагаю.

Я столкнулся с этой проблемой, и поэтому начал искать решение. @ Ответ Аарона хорош, но все еще перерывы на большие даты.

Вот код, который округляет секунды правильно, в соответствии с format или же option("digits.secs"):

form <- function(x, format = "", tz= "", ...) {
  # From format.POSIXct
  if (!inherits(x, "POSIXct")) 
    stop("wrong class")
  if (missing(tz) && !is.null(tzone <- attr(x, "tzone"))) 
    tz <- tzone

  # Find the number of digits required based on the format string
  if (length(format) > 1)
    stop("length(format) > 1 not supported")

  m <- gregexpr("%OS[[:digit:]]?", format)[[1]]
  l <- attr(m, "match.length")
  if (l == 4) {
    d <- as.integer(substring(format, l+m-1, l+m-1))
  } else {
    d <- unlist(options("digits.secs"))
    if (is.null(d)) {
      d <- 0
    }
  }  


  secs.since.origin <- unclass(x)            # Seconds since origin
  secs <- round(secs.since.origin %% 60, d)  # Seconds within the minute
  mins <- floor(secs.since.origin / 60)      # Minutes since origin
  # Fix up overflow on seconds
  if (secs >= 60) {
    secs <- secs - 60
    mins <- mins + 1
  }

  # Represents the prior minute
  lt <- as.POSIXlt(60 * mins, tz=tz, origin=ISOdatetime(1970,1,1,0,0,0,tz="GMT"));
  lt$sec <- secs + 10^(-d-1)  # Add in the seconds, plus a fudge factor.
  format.POSIXlt(as.POSIXlt(lt), format, ...)
}

Коэффициент выдумки 10^(-d-1) отсюда: точное преобразование от символа->POSIXct-> символа с датами времени меньше миллисекунды по Аарону.

Некоторые примеры:

f  <- "%Y-%m-%d %H:%M:%OS"
f3 <- "%Y-%m-%d %H:%M:%OS3"
f6 <- "%Y-%m-%d %H:%M:%OS6"

Из почти идентичного вопроса:

x <- as.POSIXct("2012-12-14 15:42:04.577895")

> format(x, f6)
[1] "2012-12-14 15:42:04.577894"
> form(x, f6)
[1] "2012-12-14 15:42:04.577895"
> myformat.POSIXct(x, 6)
[1] "2012-12-14 15:42:04.577895"

Сверху:

> format(t1)
[1] "2011-10-11 07:49:36.2"
> myformat.POSIXct(t1,1)
[1] "2011-10-11 07:49:36.3"
> form(t1)
[1] "2011-10-11 07:49:36.3"

> format(t2)
[1] "2011-10-11 23:59:59.9"
> myformat.POSIXct(t2,0)
[1] "2011-10-12 00:00:00"
> myformat.POSIXct(t2,1)
[1] "2011-10-12 00:00:00.0"

> form(t2)
[1] "2011-10-12"
> form(t2, f)
[1] "2011-10-12 00:00:00.0"

Настоящее веселье наступает в 2038 году на некоторые даты. Я предполагаю, что это потому, что мы теряем еще одну точность в мантиссе. Обратите внимание на значение поля секунд.

> t3 <- as.POSIXct('2038-12-14 15:42:04.577895')
> format(t3)
[1] "2038-12-14 15:42:05.5"
> myformat.POSIXct(t3, 1)
[1] "2038-12-14 15:42:05.6"
> form(t3)
[1] "2038-12-14 15:42:04.6"

Этот код, кажется, работает для других крайних случаев, которые я пробовал. Общее между format.POSIXct а также myformat.POSIXct в ответ Аарона является преобразование из POSIXct в POSIXlt с полем секунд без изменений.

Это указывает на ошибку в этом преобразовании. Я не использую данные, которые не доступны для as.POSIXlt(),

Обновить

Ошибка в src/main/datetime.c:434 в статической функции localtime0, но я пока не уверен в правильном исправлении:

Строки 433-434:

day = (int) floor(d/86400.0);
left = (int) (d - day * 86400.0 + 0.5);

Экстра 0.5 для округления значение является виновником. Обратите внимание, что подсекундное значение t3 выше превышает.5. localtime0 имеет дело только с секундами, а подсекунды добавляются после localtime0 возвращается.

localtime0 возвращает правильные результаты, если представленное двойное число является целочисленным значением.

Другие вопросы по тегам