Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misaligned date axis for some custom breaks functions #3965

Closed
geotheory opened this issue Apr 26, 2020 · 2 comments · Fixed by #4430
Closed

Misaligned date axis for some custom breaks functions #3965

geotheory opened this issue Apr 26, 2020 · 2 comments · Fixed by #4430
Labels
bug an unexpected problem or unintended behavior scales 🐍
Milestone

Comments

@geotheory
Copy link

geotheory commented Apr 26, 2020

A custom breaks function on the date axis seem to mis-align the breaks and labels. Another does not. But corresponding date vector inputs for both functions work fine.

Not sure if this has already been reported - I've looked and found this issue, but it's old.

Reproducible example - note how the 30 March break shifts off the maximum data point it should align with.

require(tidyverse)

set.seed(pi)
df <- data.frame(date = Sys.Date() - 0:29, price = runif(30))
p <- ggplot(df, aes(date, price)) + geom_line() +  geom_point() + coord_fixed(ratio=5)

# max price is 0.9101477 on 30 March
df %>% filter(price == max(price))
#>         date     price
#> 1 2020-03-30 0.9101477

weekend_breaks_correct <- function(x) {
  mondays <- scales::fullseq(x, "1 week")
  fridays <- mondays - lubridate::days(3)
  sort(c(mondays, fridays))
}

weekend_breaks_misaligned <- function(x){
  x <- seq(x[1], x[2], by=1)
  x[weekdays(x) %in% c('Monday','Friday')]
}

( breaks_cor = weekend_breaks_correct(range(df$date)) )
#>  [1] "2020-03-20" "2020-03-23" "2020-03-27" "2020-03-30" "2020-04-03"
#>  [6] "2020-04-06" "2020-04-10" "2020-04-13" "2020-04-17" "2020-04-20"
#> [11] "2020-04-24" "2020-04-27"
( breaks_mis = weekend_breaks_misaligned(range(df$date)) )
#> [1] "2020-03-30" "2020-04-03" "2020-04-06" "2020-04-10" "2020-04-13"
#> [6] "2020-04-17" "2020-04-20" "2020-04-24"

# correct function
p + scale_x_date(date_labels = "%a %b %d", breaks = weekend_breaks_correct)

image

# misalisgned function
p + scale_x_date(date_labels = "%a %b %d", breaks = weekend_breaks_misaligned)

image

# vectors are both correct
p + scale_x_date(date_labels = "%a %b %d", breaks = breaks_cor)
p + scale_x_date(date_labels = "%a %b %d", breaks = breaks_mis)

image

@yutannihilation
Copy link
Member

Thanks, confirmed. Here's a bit different version of the reprex. Maybe some transformation or back-transformation of the Date scale doesn't handle the expansion properly...?

library(ggplot2)
library(patchwork)

# In my locale, there's no "Monday" and "Friday" :P
Sys.setlocale(locale = "C")
#> [1] "LC_CTYPE=C;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=ja_JP.UTF-8;LC_PAPER=ja_JP.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=ja_JP.UTF-8;LC_IDENTIFICATION=C"

set.seed(pi)
df <- data.frame(date = Sys.Date() - 0:29, price = runif(30))
p <- ggplot(df, aes(date, price)) + geom_line() +  geom_point() + coord_fixed(ratio=5)

weekend_breaks_correct <- function(x) {
  mondays <- scales::fullseq(x, "1 week")
  fridays <- mondays - lubridate::days(3)
  res <- sort(c(mondays, fridays))
  
  print(res)
  res
}

weekend_breaks_misaligned <- function(x){
  x <- seq(x[1], x[2], by=1)
  res <- x[weekdays(x) %in% c('Monday','Friday')]
  
  print(res)
  res
}

p1 <- p + scale_x_date(date_labels = "%a %b %d", breaks = weekend_breaks_misaligned, expand = c(0.05, 0.05))
p2 <- p + scale_x_date(date_labels = "%a %b %d", breaks = weekend_breaks_misaligned, expand = c(0, 0))

p1 / p2

#>  [1] "2020-03-27" "2020-03-30" "2020-04-03" "2020-04-06" "2020-04-10"
#>  [6] "2020-04-13" "2020-04-17" "2020-04-20" "2020-04-24" "2020-04-27"
#>  [1] "2020-03-27" "2020-03-30" "2020-04-03" "2020-04-06" "2020-04-10"
#>  [6] "2020-04-13" "2020-04-17" "2020-04-20" "2020-04-24" "2020-04-27"
#> [1] "2020-03-30" "2020-04-03" "2020-04-06" "2020-04-10" "2020-04-13"
#> [6] "2020-04-17" "2020-04-20" "2020-04-24" "2020-04-27"
#> [1] "2020-03-30" "2020-04-03" "2020-04-06" "2020-04-10" "2020-04-13"
#> [6] "2020-04-17" "2020-04-20" "2020-04-24" "2020-04-27"

Created on 2020-04-27 by the reprex package (v0.3.0)

@thomasp85 thomasp85 added bug an unexpected problem or unintended behavior scales 🐍 labels Apr 30, 2020
@javlon
Copy link
Contributor

javlon commented Jul 12, 2020

@yutannihilation's assumption is almost right.
Misalignment is appearing due to non-zero decimal part on Date objects [e.g. structure(c(18455, 18455.5, 18455.7), class = "Date") are the same day, but they have different numeric values with the same integer part].

From Date object's documentation:

It is intended that the date should be an integer, but this is not enforced in the internal representation. Fractional days will be ignored when printing.

In above example breaks contain 0.5 fractional part in numeric presentation. That's why breaks moved a little bit to right side.

In above example, first time fractional part appears after expanding limits. So, user's breaks function obtain transformed bad date objects and also return bad date objects. Reverse transformation of breaks is also doesn't check to non-zero fractional part.

There are three solutions to this "bug":

  • coerce results of breaks function to integer for date axis (after this line).
  • coerce expanding limits to integer for date axis. It will guarantee that breaks function obtain numeric presentation of date without decimal part. And throw warning if user's breaks function return date object with non-zero decimal part on internal representation.
  • fix it inside transformation functions by rounding down numeric values. In this case transformed and reverse transformed values will differ but same integer part.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug an unexpected problem or unintended behavior scales 🐍
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants