Development Tip

R이 소수 초로 POSIXct를 포맷하는 방법

yourdevel 2021. 1. 8. 22:28
반응형

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는 분수 부분이 .3 초인 POSIXct 시간으로 생성됩니다. 십진수 한 자리로 인쇄하면 표시되는 값은 .2입니다. 저는 밀리 초 정밀도의 타임 스탬프로 작업을 많이하는데 시간이 실제 값보다 한 단계 낮게 인쇄되는 경우가 많습니다.

무슨 일이 일어나고 있는지 : POSIXct는 에포크 이후 부동 소수점 초 수입니다. 모든 정수 값은 정확하게 처리되지만 base-2 부동 소수점에서 .3에 가장 가까운 값은 .3보다 약간 작습니다. strftime()for format 의 명시된 동작은 %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"12:00"이 아닌 "11:59" %H:%M:%S가 제공 되고 "12:00:00"이 아닌 "11:59:59"가 제공되어야합니다. 나는 정수 초 수와 형식 플래그에 대해 동의 %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 동작이 정확합니까? 당신 자신이 디자인하는 방식입니까?


한 가지 근본적인 문제는 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보다 작지만 두 번째 표현은 충분히 가까우거나 여기에서 상상하는 것과 다른 방식으로 잘립니다. 이를 감안할 때 부동 소수점 표현의 어려움에 대해 걱정하지 않을 것입니다. 여전히 발생할 수 있지만 우리가 사용하는 표현에 대해주의를 기울이면 최소화 될 것입니다.

반올림 된 출력에 대한 Robert의 욕구는 단순히 출력 문제이며 다양한 방법으로 해결할 수 있습니다. 내 제안은 다음과 같습니다.

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"

마지막으로, 표준이 최대 2 개의 윤초를 허용한다는 것을 알고 계셨습니까?

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

좋아요, 한 가지 더. 동작은 OP ( Bug 14579 ) 에서 제출 한 버그로 인해 5 월에 실제로 변경되었습니다 . 그 전에 분수 초를 반올림했습니다. 불행히도 그것은 때때로 불가능했던 1 초까지 반올림 될 수 있음을 의미했습니다. 버그 보고서에서 다음 분으로 롤오버되어야 할 때 60 개까지 올라갔습니다. 원형 대신 자르기로 결정한 한 가지 이유는 각 단위가 별도로 저장되는 POSIXlt 표현에서 인쇄하기 때문입니다. 따라서 다음 분 / 시간 / 등으로 롤오버하는 것은 단순한 반올림 작업보다 더 어렵습니다. 쉽게 반올림하려면 POSIXct 표현으로 반올림 한 다음 다시 변환해야합니다.


나는이 문제에 부딪 혔고 그래서 해결책을 찾기 시작했습니다. @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, ...)
}

10 ^ (-d-1)의 퍼지 계수는 여기에서 나온 것입니다 : Aaron이 밀리 초 미만의 datetimes가진 character-> POSIXct-> character에서 정확하게 변환 합니다.

몇 가지 예 :

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.POSIXctmyformat.POSIXct아론의 대답은에서로의 전환이다 POSIXctPOSIXlt그대로 초 필드.

This points to a bug in that conversion. I'm not using any data that isn't available to as.POSIXlt().

Update

The bug is in src/main/datetime.c:434 in the static function localtime0, but I am not sure yet of the correct fix:

Lines 433-434:

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

The extra 0.5 for rounding the value is the culprit. Note that the subsecond value of t3 above exceeds .5. localtime0 deals with seconds only, and the subseconds are added in after localtime0 returns.

localtime0 returns correct results if the double presented is an integer value.

ReferenceURL : https://stackoverflow.com/questions/7726034/how-r-formats-posixct-with-fractional-seconds

반응형