Skip to main content
fix error that happens with newer versions of ggplot2
Source Link
Stibu
  • 16k
  • 6
  • 61
  • 76
library(scales) squish_trans <- function(from, to, factor) { trans <- function(x) { if (any(is.na(x))) return(x) # get indices for the relevant regions isq <- x > from & x < to ito <- x >= to # apply transformation x[isq] <- from + (x[isq] - from)/factor x[ito] <- from + (to - from)/factor + (x[ito] - to) return(x) } inv <- function(x) {    if (any(is.na(x))) return(x)  # get indices for the relevant regions isq <- x > from & x < from + (to - from)/factor ito <- x >= from + (to - from)/factor # apply transformation x[isq] <- from + (x[isq] - from) * factor x[ito] <- to + (x[ito] - (from + (to - from)/factor)) return(x) } # return the transformation return(trans_new("squished", trans, inv)) } 

The first line in trans() and inv() handles the case when the transformation is called with x = c(NA, NA). (It seems that this did not happen with the version of ggplot2 when I originally wrote this question. Unfortunately, I don't know with which version this startet.)

library(scales) squish_trans <- function(from, to, factor) { trans <- function(x) { # get indices for the relevant regions isq <- x > from & x < to ito <- x >= to # apply transformation x[isq] <- from + (x[isq] - from)/factor x[ito] <- from + (to - from)/factor + (x[ito] - to) return(x) } inv <- function(x) { # get indices for the relevant regions isq <- x > from & x < from + (to - from)/factor ito <- x >= from + (to - from)/factor # apply transformation x[isq] <- from + (x[isq] - from) * factor x[ito] <- to + (x[ito] - (from + (to - from)/factor)) return(x) } # return the transformation return(trans_new("squished", trans, inv)) } 
library(scales) squish_trans <- function(from, to, factor) { trans <- function(x) { if (any(is.na(x))) return(x) # get indices for the relevant regions isq <- x > from & x < to ito <- x >= to # apply transformation x[isq] <- from + (x[isq] - from)/factor x[ito] <- from + (to - from)/factor + (x[ito] - to) return(x) } inv <- function(x) {    if (any(is.na(x))) return(x)  # get indices for the relevant regions isq <- x > from & x < from + (to - from)/factor ito <- x >= from + (to - from)/factor # apply transformation x[isq] <- from + (x[isq] - from) * factor x[ito] <- to + (x[ito] - (from + (to - from)/factor)) return(x) } # return the transformation return(trans_new("squished", trans, inv)) } 

The first line in trans() and inv() handles the case when the transformation is called with x = c(NA, NA). (It seems that this did not happen with the version of ggplot2 when I originally wrote this question. Unfortunately, I don't know with which version this startet.)

added general solution
Source Link
Stibu
  • 16k
  • 6
  • 61
  • 76

But you can also define custom transformation functions by supplying the trans argument to scale_y_continuous() (and similarly for scale_x_continuous()). To this end, you use the function trans_new() from the scales package. It takes as arguments the transformation function and its inverse. As an example, 

I define herediscuss first a function that shrinksspecial solution for the OP's example and then also show how this can be generalised.

OP's example

The OP wants to shrink the interval between -2 and 2 together. The following defines a function (and its inverse) that shrinks this interval by a factor 4:

General solution

The function below defines a transformation where you can choose the lower and upper end of the region to be squished, as well as the factor to be used. It directly returns the trans object that can be used inside scale_y_continuous:

library(scales) squish_trans <- function(from, to, factor) { trans <- function(x) { # get indices for the relevant regions isq <- x > from & x < to ito <- x >= to # apply transformation x[isq] <- from + (x[isq] - from)/factor x[ito] <- from + (to - from)/factor + (x[ito] - to) return(x) } inv <- function(x) { # get indices for the relevant regions isq <- x > from & x < from + (to - from)/factor ito <- x >= from + (to - from)/factor # apply transformation x[isq] <- from + (x[isq] - from) * factor x[ito] <- to + (x[ito] - (from + (to - from)/factor)) return(x) } # return the transformation return(trans_new("squished", trans, inv)) } 

This function can now be used to conveniently redo the plot from the first section:

p + scale_y_continuous(trans = squish_trans(-2, 2, 4), breaks = seq(-6, 6, by = 2)) 

The following example shows that you can squish the scale at an arbitrary position and that this also works for other geoms than points:

df <- data.frame(class = LETTERS[1:4], val = c(1, 2, 101, 102)) ggplot(df, aes(x = class, y = val)) + geom_bar(stat = "identity") + scale_y_continuous(trans = squish_trans(3, 100, 50), breaks = c(0, 1, 2, 3, 50, 100, 101, 102)) 

enter image description here

Let me close by stressing what other already mentioned in comments: this kind of plot could be misleading and should be used with care!

But you can also define custom transformation functions by supplying the trans argument to scale_y_continuous() (and similarly for scale_x_continuous()). To this end, you use the function trans_new() from the scales package. It takes as arguments the transformation function and its inverse. As an example, I define here a function that shrinks the interval between -2 and 2 together by a factor 4:

But you can also define custom transformation functions by supplying the trans argument to scale_y_continuous() (and similarly for scale_x_continuous()). To this end, you use the function trans_new() from the scales package. It takes as arguments the transformation function and its inverse. 

I discuss first a special solution for the OP's example and then also show how this can be generalised.

OP's example

The OP wants to shrink the interval between -2 and 2. The following defines a function (and its inverse) that shrinks this interval by a factor 4:

General solution

The function below defines a transformation where you can choose the lower and upper end of the region to be squished, as well as the factor to be used. It directly returns the trans object that can be used inside scale_y_continuous:

library(scales) squish_trans <- function(from, to, factor) { trans <- function(x) { # get indices for the relevant regions isq <- x > from & x < to ito <- x >= to # apply transformation x[isq] <- from + (x[isq] - from)/factor x[ito] <- from + (to - from)/factor + (x[ito] - to) return(x) } inv <- function(x) { # get indices for the relevant regions isq <- x > from & x < from + (to - from)/factor ito <- x >= from + (to - from)/factor # apply transformation x[isq] <- from + (x[isq] - from) * factor x[ito] <- to + (x[ito] - (from + (to - from)/factor)) return(x) } # return the transformation return(trans_new("squished", trans, inv)) } 

This function can now be used to conveniently redo the plot from the first section:

p + scale_y_continuous(trans = squish_trans(-2, 2, 4), breaks = seq(-6, 6, by = 2)) 

The following example shows that you can squish the scale at an arbitrary position and that this also works for other geoms than points:

df <- data.frame(class = LETTERS[1:4], val = c(1, 2, 101, 102)) ggplot(df, aes(x = class, y = val)) + geom_bar(stat = "identity") + scale_y_continuous(trans = squish_trans(3, 100, 50), breaks = c(0, 1, 2, 3, 50, 100, 101, 102)) 

enter image description here

Let me close by stressing what other already mentioned in comments: this kind of plot could be misleading and should be used with care!

use same breaks in first figure, clean up mess with figures
Source Link
Stibu
  • 16k
  • 6
  • 61
  • 76

You could do this by defining a coordinate transformation. A standard example are logarithmic coordinates, which can be achieved in ggplot by using scale_y_log10().

But you can also define custom transformation functions by supplying the trans argument to scale_y_continuous() (and similarly for scale_x_continuous()). To this end, you use the function trans_new() from the scales package. It takes as arguments the transformation function and its inverse. As an example, I define here a function that shrinks the interval between -2 and 2 together by a factor 4:

library(scales) trans <- function(x) { ifelse(x > 2, x - 1.5, ifelse(x < -2, x + 1.5, x/4)) } inv <- function(x) { ifelse(x > 0.5, x + 1.5, ifelse(x < -0.5, x - 1.5, x*4)) } my_trans <- trans_new("my_trans", trans, inv) 

This defines the transformation. To see it in action, I define some sample data:

x_val <- 0:250 y_val <- c(-6:-2, 2:6) set.seed(1234) data <- data.frame(x = sample(x_val, 30, replace = TRUE), y = sample(y_val, 30, replace = TRUE)) 

I first plot it without transformation:

p <- ggplot(data, aes(x, y)) + geom_point() p + scale_y_continuous(breaks = seq(-6, 6, by = 2)) 

enter image description hereenter image description here

Now I use scale_y_continuous() with the transformation and appropriate breaks:

p + scale_y_continuous(trans = my_trans, breaks = seq(-6, 6, by = 2)) 

enter image description hereenter image description here

If you want another transformation, you have to change the definition of trans() and inv() and run trans_new() again. You have to make sure that inv() is indeed the inverse of inv(). I checked this as follows:

x <- runif(100, -100, 100) identical(x, trans(inv(x))) ## [1] TRUE 

You could do this by defining a coordinate transformation. A standard example are logarithmic coordinates, which can be achieved in ggplot by using scale_y_log10().

But you can also define custom transformation functions by supplying the trans argument to scale_y_continuous() (and similarly for scale_x_continuous()). To this end, you use the function trans_new() from the scales package. It takes as arguments the transformation function and its inverse. As an example, I define here a function that shrinks the interval between -2 and 2 together by a factor 4:

library(scales) trans <- function(x) { ifelse(x > 2, x - 1.5, ifelse(x < -2, x + 1.5, x/4)) } inv <- function(x) { ifelse(x > 0.5, x + 1.5, ifelse(x < -0.5, x - 1.5, x*4)) } my_trans <- trans_new("my_trans", trans, inv) 

This defines the transformation. To see it in action, I define some sample data:

x_val <- 0:250 y_val <- c(-6:-2, 2:6) set.seed(1234) data <- data.frame(x = sample(x_val, 30, replace = TRUE), y = sample(y_val, 30, replace = TRUE)) 

I first plot it without transformation:

p <- ggplot(data, aes(x, y)) + geom_point() p 

enter image description here

Now I use scale_y_continuous() with the transformation and appropriate breaks:

p + scale_y_continuous(trans = my_trans, breaks = seq(-6, 6, by = 2)) 

enter image description here

If you want another transformation, you have to change the definition of trans() and inv() and run trans_new() again. You have to make sure that inv() is indeed the inverse of inv(). I checked this as follows:

x <- runif(100, -100, 100) identical(x, trans(inv(x))) ## [1] TRUE 

You could do this by defining a coordinate transformation. A standard example are logarithmic coordinates, which can be achieved in ggplot by using scale_y_log10().

But you can also define custom transformation functions by supplying the trans argument to scale_y_continuous() (and similarly for scale_x_continuous()). To this end, you use the function trans_new() from the scales package. It takes as arguments the transformation function and its inverse. As an example, I define here a function that shrinks the interval between -2 and 2 together by a factor 4:

library(scales) trans <- function(x) { ifelse(x > 2, x - 1.5, ifelse(x < -2, x + 1.5, x/4)) } inv <- function(x) { ifelse(x > 0.5, x + 1.5, ifelse(x < -0.5, x - 1.5, x*4)) } my_trans <- trans_new("my_trans", trans, inv) 

This defines the transformation. To see it in action, I define some sample data:

x_val <- 0:250 y_val <- c(-6:-2, 2:6) set.seed(1234) data <- data.frame(x = sample(x_val, 30, replace = TRUE), y = sample(y_val, 30, replace = TRUE)) 

I first plot it without transformation:

p <- ggplot(data, aes(x, y)) + geom_point() p + scale_y_continuous(breaks = seq(-6, 6, by = 2)) 

enter image description here

Now I use scale_y_continuous() with the transformation:

p + scale_y_continuous(trans = my_trans, breaks = seq(-6, 6, by = 2)) 

enter image description here

If you want another transformation, you have to change the definition of trans() and inv() and run trans_new() again. You have to make sure that inv() is indeed the inverse of inv(). I checked this as follows:

x <- runif(100, -100, 100) identical(x, trans(inv(x))) ## [1] TRUE 
Source Link
Stibu
  • 16k
  • 6
  • 61
  • 76
Loading