r - ggplot2 identical scales (non-continuous) on both sides -


goal

use ggplot2 (latest version) produce graph duplicates x- or y-axis on both sides of plot, scale not continuous.

minimal reprex

# example data dat1 <- tibble::tibble(x = c(rep("a", 50), rep("b", 50)),                         y = runif(100))  # standard scatterplot p1 <- ggplot2::ggplot(dat1) +     ggplot2::geom_boxplot(ggplot2::aes(x = x, y = y)) 

when scale continuous, easy identity transformation (clearly one-to-one).

# works p1 + ggplot2::scale_y_continuous(sec.axis = ggplot2::sec_axis(~ .)) 

however, when scale not continuous, doesn't work, other scale_* functions don't have sec.axis argument (which makes sense).

# doesn't work p1 + ggplot2::scale_x_discrete(sec.axis = ggplot2::sec_axis(~ .))  error in discrete_scale(c("x", "xmin", "xmax", "xend"), "position_d",  :    unused argument (sec.axis = <environment>) 

i tried using position argument in scale_* functions, doesn't work either.

# doesn't work either p1 + ggplot2::scale_x_discrete(position = c("top", "bottom"))  error in match.arg(position, c("left", "right", "top", "bottom")) :    'arg' must of length 1 

edit

for clarity, hoping duplicate x- or y-axis scale anything, not discrete (a factor variable). used discrete variable in minimal reprex simplicity.

for example, issue arises in context non-continuous scale datetime or time format.

duplicating (and modifying) discrete axis in ggplot2

you can adapt answer putting same labels on both sides. far "you can convert non-continuous factor, that's more inelegant!" comment above, that's non-continuous axis is, i'm not sure why problem you.

tl:dr use as.numeric(...) categorical aesthetic , manually supply labels original data, using scale_*_continuous(..., sec_axis(~., ...)).


edited update:

i happened through thread , see asked dates , times. makes question worded incorrectly: dates , times continuous not discrete. discrete scales factors. dates , times ordered continuous scales. under hood, they're either days or seconds since "1970-01-01".

scale_x_date indeed throw error if try pass sec.axis argument, if it's dup_axis. work around this, convert dates/times number, , fool scales using labels. while requires bit of fiddling, it's not complicated.

library(lubridate) library(dplyr)  df <- data_frame(tm = ymd("2017-08-01") + 0:10,                  y = cumsum(rnorm(length(tm)))) %>%    mutate(tm_num = as.numeric(tm))  
df  # tibble: 11 x 3            tm          y tm_num        <date>      <dbl>  <dbl>  1 2017-08-01 -2.0948146  17379  2 2017-08-02 -2.6020691  17380  3 2017-08-03 -3.8940781  17381  4 2017-08-04 -2.7807154  17382  5 2017-08-05 -2.9451685  17383  6 2017-08-06 -3.3355426  17384  7 2017-08-07 -1.9664428  17385  8 2017-08-08 -0.8501699  17386  9 2017-08-09 -1.7481911  17387 10 2017-08-10 -1.3203246  17388 11 2017-08-11 -2.5487692  17389 

i made simple vector of 11 days (0 10) added "2017-08-01". if run as.numeric on that, number of days since beginning of unix epoch. (see ?lubridate::as_date).

df %>%    ggplot(aes(tm_num, y)) + geom_line() +   scale_x_continuous(sec.axis = dup_axis(),                      breaks = function(limits) {                        seq(floor(limits[1]), ceiling(limits[2]),                             = as.numeric(as_date(days(2))))                        },                      labels = function(breaks) {as_date(breaks)}) 

when plot tm_num against y, it's treated normal numbers, , can use scale_x_continuous(sec.axis = dup_axis(), ...). have figure out how many breaks want , how label them.

the breaks = function takes limits of data, , calculates nice looking breaks. first round limits, make sure integers (dates don't work non-integers). generate sequence of desired width (the days(2)). use weeks(1) or months(3) or whatever, check out ?lubridate::days. under hood, days(x) generates number of seconds (86400 per day, 604800 per week, etc.), as_date converts number of days since unix epoch, , as.numeric converts integer.

the labels = function takes sequence of integers generated , converts displayable dates.

enter image description here

this works times instead of dates. while dates integer days, times integer seconds (either since unix epoch, datetimes, or since midnight, times).

let's had observations on scale of minutes, not days.

the code similar, few tweaks:

df <- data_frame(tm = ymd_hms("2017-08-01 23:58:00") + 60*0:10,            y = cumsum(rnorm(length(tm)))) %>%    mutate(tm_num = as.numeric(tm))  
df  # tibble: 11 x 3                     tm        y     tm_num                 <dttm>    <dbl>      <dbl>  1 2017-08-01 23:58:00 1.375275 1501631880  2 2017-08-01 23:59:00 2.373565 1501631940  3 2017-08-02 00:00:00 3.650167 1501632000  4 2017-08-02 00:01:00 2.578420 1501632060  5 2017-08-02 00:02:00 5.155688 1501632120  6 2017-08-02 00:03:00 4.022228 1501632180  7 2017-08-02 00:04:00 4.776145 1501632240  8 2017-08-02 00:05:00 4.917420 1501632300  9 2017-08-02 00:06:00 4.513710 1501632360 10 2017-08-02 00:07:00 4.134294 1501632420 11 2017-08-02 00:08:00 3.142898 1501632480 
df %>%    ggplot(aes(tm_num, y)) + geom_line() +   scale_x_continuous(sec.axis = dup_axis(),                      breaks = function(limits) {                        seq(floor(limits[1] / 60) * 60, ceiling(limits[2] / 60) * 60,                             = as.numeric(as_datetime(minutes(2))))                        },                      labels = function(breaks) {                        stamp("jan 1,\n0:00:00", orders = "md hms")(as_datetime(breaks))                        }) 

here updated dummy data span 11 minutes before midnight after midnight. in breaks = modified make sure got integer number of minutes create breaks on, changed as_date as_datetime, , used minutes(2) make break every 2 minutes. in labels = added functional stamp(...)(...), creates nice format display.

enter image description here

finally times.

df <- data_frame(tm = milliseconds(1234567 + 0:10),            y = cumsum(rnorm(length(tm)))) %>%    mutate(tm_num = as.numeric(tm))   df 
# tibble: 11 x 3              tm          y   tm_num    <s4: period>      <dbl>    <dbl>  1    1234.567s  0.2136745 1234.567  2    1234.568s -0.6376908 1234.568  3    1234.569s -1.1080997 1234.569  4     1234.57s -0.4219645 1234.570  5    1234.571s -2.7579118 1234.571  6    1234.572s -1.6626674 1234.572  7    1234.573s -3.2298175 1234.573  8    1234.574s -3.2078864 1234.574  9    1234.575s -3.3982454 1234.575 10    1234.576s -2.1051759 1234.576 11    1234.577s -1.9163266 1234.577 
df %>%    ggplot(aes(tm_num, y)) + geom_line() +   scale_x_continuous(sec.axis = dup_axis(),                      breaks = function(limits) {                        seq(limits[1], limits[2],                             = as.numeric(milliseconds(3)))                        },                      labels = function(breaks) {format((as_datetime(breaks)),                                                        format = "%h:%m:%os3")}) 

here we've got observation every millisecond 11 hours starting @ t = 20min34.567sec. in breaks = dispense rounding, since don't want integers now. use breaks every milliseconds(2). labels = needs formatted accept decimal seconds, "%os3" means 3 digits of decimals seconds place (can accept 6, see ?strptime).

enter image description here

is of worth it? not, unless really want duplicated time axis. i'll post issue on ggplot2 github, because dup_axis should "just work" datetimes.


Comments

Popular posts from this blog

php - Vagrant up error - Uncaught Reflection Exception: Class DOMDocument does not exist -

vue.js - Create hooks for automated testing -

.htaccess - ERR_TOO_MANY_REDIRECTS htaccess -