Подтвердить что ты не робот

Как 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? Так вы сами это разработали?

4b9b3361

Ответ 1

Одна из основных проблем заключается в том, что представление 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"

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

Ответ 2

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

Вот код, который правильно округляет секунды, согласно 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, ...)
}

Фактор fudge 10 ^ (-d-1) отсюда: Точное преобразование из символа character > POSIXct- > с субмиллисекундными datetimes по Аарон.

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

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 возвращает правильные результаты, если двойное представление представляет собой целочисленное значение.