问题描述
我正在尝试使用 R 中的 cut() 函数将一年的日期划分为 6 个两个月的 bin。当我这样做时,它会生成 7 个垃圾箱而不是 6 个垃圾箱,最后一个垃圾箱是空的。我正在使用以下代码:
dates <- seq(as.Date("2021-1-1"),as.Date("2021-12-31"),by="day")
months <- cut(dates,"month",labels=1:12)
table(months)
# months
# 1 2 3 4 5 6 7 8 9 10 11 12
# 31 28 31 30 31 30 31 31 30 31 30 31
sextiles <- cut(dates,"2 months",labels=1:6)
# Error in cut.default(unclass(x),unclass(breaks),labels = labels,right = right,:
# lengths of 'breaks' and 'labels' differ
sextiles <- cut(dates,labels=1:7)
table(sextiles)
# sextiles
# 1 2 3 4 5 6 7
# 59 61 61 62 61 61 0
当我将年份划分为单个月份时,代码工作正常,但是当我划分为 2 个月时会产生错误,除非我在标签参数中考虑了 7 个而不是 6 个。如果我从年末开始删除日期,则在删除一年的最后 3 天后,代码最终可以使用 6 个 bin:
dates_364 <- dates[-length(dates)]
sextiles <- cut(dates_364,:
# lengths of 'breaks' and 'labels' differ
dates_363 <- dates_364[-length((dates_364))]
sextiles <- cut(dates_363,:
# lengths of 'breaks' and 'labels' differ
dates_362 <- dates_363[-length((dates_363))]
sextiles <- cut(dates_362,labels=1:6)
table(sextiles)
# sextiles
# 1 2 3 4 5 6
# 59 61 61 62 61 58
这似乎是函数中的一个错误。任何人都可以对我缺少的东西有所了解吗?谢谢!
解决方法
有两种方法可以为一个数字范围定义“bins”,以便所有提供的数字都在其中一个 bin 内:
- 找到最小值,找到最大值,并且由于
Date
-bins 通常是right=FALSE
表示右开,因此将最大值稍微突出一点;或 - 找到最小值,不要找到最大值,而是使用
Inf
以便它始终包含最大值。
cut.Date
选择了两者中的第一个。此外,它不是“从最大值跳出 1 天”,而是选择“跳出‘一步’”。这意味着当您说 "2 months"
时,它将确保下一个 bin“边缘”距离倒数第二个边界 2 个月。
也就是说,如果您查看 cut.Date
的来源:
start <- as.POSIXlt(min(x,na.rm = TRUE))
# ...
end <- as.POSIXlt(max(x,na.rm = TRUE))
# and then if 'months',then
end <- as.POSIXlt(end + (31 * step * 86400))
# and eventually
breaks <- as.Date(seq(start,end,breaks))
所以我会debug(cut.Date)
看看cut(dates,"2 months")
:
start
# [1] "2021-01-01 UTC"
# debug: end <- as.POSIXlt(max(x,na.rm = TRUE))
# debug: step <- if (length(by2) == 2L) as.integer(by2[1L]) else 1L
end
# [1] "2021-12-31 UTC"
step
# [1] 2
# debug: as.integer(by2[1L])
# debug: end <- as.POSIXlt(end + (31 * step * 86400))
end
# [1] "2022-03-03 UTC"
# debug: end$mday <- 1L
# debug: end$isdst <- -1L
# debug: breaks <- as.Date(seq(start,breaks))
breaks
# [1] "2021-01-01" "2021-03-01" "2021-05-01" "2021-07-01" "2021-09-01" "2021-11-01" "2022-01-01"
# [8] "2022-03-01"
它最终会执行 breaks[-length(breaks)]
,这就解释了为什么我们没有看到 8。我的猜测是,在某些极端情况下(也许是闰年?),31 * step * 86400
(或其他 by
-单位)并不总是完美对齐,因此他们对其进行了一些缓冲。
长话短说(为时已晚),我建议您改用 labels=FALSE
。
sextiles <- cut(dates,"2 months",labels = FALSE)
table(sextiles)
# sextiles
# 1 2 3 4 5 6
# 59 61 61 62 61 61
如果您希望它们看起来像整数factor
(它们是下面带有真正整数的字符串级别),那么也许
sextiles <- factor(sextiles)
head(sextiles)
# [1] 1 1 1 1 1 1
# Levels: 1 2 3 4 5 6
,
感谢@r2evans 提供的见解,我找到了问题的答案。
cut.Date 函数的代码中存在错误。第 31 行到第 41 行处理以月为单位的中断情况:
if (valid == 3L) {
start$mday <- 1L
start$isdst <- -1L
end <- as.POSIXlt(max(x,na.rm = TRUE))
step <- if (length(by2) == 2L)
as.integer(by2[1L])
else 1L
end <- as.POSIXlt(end + (31 * step * 86400))
end$mday <- 1L
end$isdst <- -1L
breaks <- as.Date(seq(start,breaks))
第 38 行,end <- as.POSIXlt(end + (31 * step * 86400))
将结束提前 31 天乘以步长或每个 bin 中的月数进行调整。因为并不是所有的月份都有 31 天,所以有些情况下,最后会被推到足够远的地方,从而产生一个多余的垃圾箱。这可以通过几行代码轻松纠正,正如我们在休息时间为四分之一的情况下所看到的那样。请参阅第 57 至 75 行:
else if (valid == 5L) {
qtr <- rep(c(0L,3L,6L,9L),each = 3L)
start$mon <- qtr[start$mon + 1L]
start$mday <- 1L
start$isdst <- -1L
maxx <- max(x,na.rm = TRUE) # Note this line
end <- as.POSIXlt(maxx) # Note this line
step <- if (length(by2) == 2L)
as.integer(by2[1L])
else 1L
end <- as.POSIXlt(end + (93 * step * 86400))
end$mon <- qtr[end$mon + 1L]
end$mday <- 1L
end$isdst <- -1L
breaks <- as.Date(seq(start,paste(step * 3L,"months")))
lb <- length(breaks) # Note this line
if (maxx < breaks[lb - 1]) # If extra bin
breaks <- breaks[-lb] # Then remove extra bin
如果我们使用同样的方法并修改处理break="months"的代码部分:
if (valid == 3L) {
start$mday <- 1L
start$isdst <- -1L
maxx <- max(x,na.rm = TRUE) # Line added
end <- as.POSIXlt(maxx) # Line modified
step <- if (length(by2) == 2L)
as.integer(by2[1L])
else 1L
end <- as.POSIXlt(end + (31 * step * 86400))
end$mday <- 1L
end$isdst <- -1L
breaks <- as.Date(seq(start,breaks))
lb <- length(breaks) # Line added
if (maxx < breaks[lb - 1]) # Line added
breaks <- breaks[-lb] # Line added
将修改后的函数存储在 cut_Date 中,我们得到以下内容:
dates <- seq(as.Date("2021-1-1"),as.Date("2021-12-31"),by="day")
sextiles <- cut(dates,labels=1:6)
# Error in cut.default(unclass(x),unclass(breaks),labels = labels,right = right,:
# lengths of 'breaks' and 'labels' differ
sextiles <- cut_Date(dates,labels=1:6)
table(sextiles)
# sextiles
# 1 2 3 4 5 6
# 59 61 61 62 61 61
错误已修复!