ggplot2 - How do get bins of two different histograms to match? - r

I have the following histogram which uses the default binwidth,
x <- rnorm(100)
p1 <- ggplot() + geom_histogram(aes(x=x))
I want the following histogram to have the exact same bins as p1,
x <- rnorm(100)/2
p2 <- ggplot() + geom_histogram(aes(x=x))
In other words, I want p2 to use the same default bins as p1. How do I do this?

Something that we can do is to extract breaks from the first plot:
x1 <- rnorm(100)
p1 <- ggplot() +
geom_histogram(aes(x = x1))
breaks <- unique(unlist(ggplot_build(p1)$data[[1]][, c("xmin", 'xmax')]))
x2 <- rnorm(100) / 2
p2 <- ggplot() +
geom_histogram(aes(x = x2), breaks = breaks)
library(gridExtra)
grid.arrange(p1, p2, nrow = 1)

I think the easiest way to force the same bins is to facet the plots (because just setting binwidth might start bins at different places on two different plots, and manually setting limits with boundary and breaks will need to be done for the particular data which could be annoying). Plus, this makes the plots directly comparable on their axes as well as the bins, which is presumably the point of putting them on the same bins in the first place
library(tidyverse)
tbl <- tibble(
x = rnorm(100),
y = rnorm(100) / 2
)
tbl %>%
gather(var, val, x, y) %>%
ggplot() +
geom_histogram(aes(x = val)) +
facet_wrap(~var)
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Created on 2018-10-31 by the reprex package (v0.2.0).

Related

Weird behavior with ggplot2 geom_histogram

I've run into a weird issue regarding geom_histogram and it can easily be seen by plotting the uniform distribution.
library(tidyverse)
u <- runif(10000)
ggplot(data = as_tibble(u), aes(x = value)) + geom_histogram()
Generates the following count histogram:
It can be seen that it is rather assymetrical, I've read somewhere that this is because the leftmost (and rightmost) bin is centered around 0, and there are no values being generated from -0.5 up to 0, creating the discrepancy seen. So I've fiddled with the boundary parameter and it got me:
ggplot(data = as_tibble(u), aes(x = value)) + geom_histogram(boundary = 0)
Similarly setting boundary = 1 made it assymetric at the left.
My questions are: what is the preferred way to fix this behavior? Also, what exactly does the boundary argument does? It is not very clear from the docs.
Thank you.
As far as I can tell, boundary specifies a spot to be a split between two bins. The rest of bins are set according to the number of bins or supplied break points. If the supplied boundary is outside the range of the data, some clever shifting is done according to the documentation. Maybe with the following examples it becomes clear what boundary does.
workaround
if you set limits for the x axis, you can circumvent the issue, although not a very elegant solution.
library(tidyverse)
#> Warning: package 'ggplot2' was built under R version 4.1.0
set.seed(123)
u <- runif(1000)
p1 <- ggplot(data = as_tibble(u), aes(x = value)) + geom_histogram(boundary = 0)
p2 <- ggplot(data = as_tibble(u), aes(x = value)) + geom_histogram(boundary = 0) +
scale_x_continuous(limits = c(0, 1))
cowplot::plot_grid(p1, p2, nrow = 2)
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
Boundary examples
in the third plot (p3), boundary is set to 0.5 and you can see that two bins are split exactly at 0.5. The same for the fourth plot at the 0.75 point. Boundary is likely not the best name for what it does but basically states that the given number should be the boundary between two bins.
u <- runif(10)
p3 <- ggplot(data = as_tibble(u), aes(x = value)) +
geom_histogram(bins = 22, boundary = .5, binwidth = 0.1) +
scale_x_continuous(limits = c(0, 1))
p4 <- ggplot(data = as_tibble(u), aes(x = value)) +
geom_histogram(bins = 22, boundary = .75, binwidth = 0.1) +
scale_x_continuous(limits = c(0, 1))
cowplot::plot_grid(p3, p4, nrow = 2)
#> Warning: Removed 2 rows containing missing values (geom_bar).
Created on 2021-06-13 by the reprex package (v2.0.0)

Edit point size with geom_count (ggplot2, R)

Trying to find a solution to adjust point size when using geom_count. Geom_count enlarges points when points are overlapping. When creating different plots with geom_count, they all show different point sizes (which can be confusing when comparing the plots). I've seen other options in ggplot2 to change point size, but then geom_count is overruled.
Here's an example version of the script:
x <- c(10,30,60,80,80,100,30,40)
y <- c(30,70,50,80,80,80,40,50)
db <- merge(x,y)
ggplot(db, aes(x, y))+
geom_count(alpha=0.8, colour="steelblue")+
scale_size_area(breaks = round)+
theme_bw() + theme(panel.grid.minor = element_blank(), plot.tag.position = "bottomleft")+
labs(title= "X vs Y",subtitle=" ", tag = "A")
If you want to harmonise the point sizes across different plots, you'd need to give it appropriate limits manually. Consider the following example:
library(ggplot2)
library(patchwork)
#> Warning: package 'patchwork' was built under R version 4.0.3
n <- 10000
df1 <- data.frame(
x = sample(LETTERS[1:5], n, replace = TRUE),
y = sample(LETTERS[1:5], n, replace = TRUE)
)
df2 <- data.frame(
x = sample(LETTERS[1:5], n / 10, replace = TRUE),
y = sample(LETTERS[1:5], n / 10, replace = TRUE)
)
plot1 <- ggplot(df1, aes(x, y)) +
geom_count()
plot2 <- ggplot(df2, aes(x, y)) +
geom_count()
The size scales of the two plots are horribly out of sync:
plot1 + plot2
You can manually calculate what the limits of the scales should be and apply these to the different plots.
range <- range(c(table(df1$x, df1$y), table(df2$x, df2$y)))
# '&' operation is about the same as adding this scale to individual plots
plot1 + plot2 & scale_size_area(limits = range)
Created on 2021-03-31 by the reprex package (v1.0.0)

Why does ggplot geom_jitter plots extra values? [duplicate]

How would I ignore outliers in ggplot2 boxplot? I don't simply want them to disappear (i.e. outlier.size=0), but I want them to be ignored such that the y axis scales to show 1st/3rd percentile. My outliers are causing the "box" to shrink so small its practically a line. Are there some techniques to deal with this?
Edit
Here's an example:
y = c(.01, .02, .03, .04, .05, .06, .07, .08, .09, .5, -.6)
qplot(1, y, geom="boxplot")
Use geom_boxplot(outlier.shape = NA) to not display the outliers and scale_y_continuous(limits = c(lower, upper)) to change the axis limits.
An example.
n <- 1e4L
dfr <- data.frame(
y = exp(rlnorm(n)), #really right-skewed variable
f = gl(2, n / 2)
)
p <- ggplot(dfr, aes(f, y)) +
geom_boxplot()
p # big outlier causes quartiles to look too slim
p2 <- ggplot(dfr, aes(f, y)) +
geom_boxplot(outlier.shape = NA) +
scale_y_continuous(limits = quantile(dfr$y, c(0.1, 0.9)))
p2 # no outliers plotted, range shifted
Actually, as Ramnath showed in his answer (and Andrie too in the comments), it makes more sense to crop the scales after you calculate the statistic, via coord_cartesian.
coord_cartesian(ylim = quantile(dfr$y, c(0.1, 0.9)))
(You'll probably still need to use scale_y_continuous to fix the axis breaks.)
Here is a solution using boxplot.stats
# create a dummy data frame with outliers
df = data.frame(y = c(-100, rnorm(100), 100))
# create boxplot that includes outliers
p0 = ggplot(df, aes(y = y)) + geom_boxplot(aes(x = factor(1)))
# compute lower and upper whiskers
ylim1 = boxplot.stats(df$y)$stats[c(1, 5)]
# scale y limits based on ylim1
p1 = p0 + coord_cartesian(ylim = ylim1*1.05)
I had the same problem and precomputed the values for Q1, Q2, median, ymin, ymax using boxplot.stats:
# Load package and generate data
library(ggplot2)
data <- rnorm(100)
# Compute boxplot statistics
stats <- boxplot.stats(data)$stats
df <- data.frame(x="label1", ymin=stats[1], lower=stats[2], middle=stats[3],
upper=stats[4], ymax=stats[5])
# Create plot
p <- ggplot(df, aes(x=x, lower=lower, upper=upper, middle=middle, ymin=ymin,
ymax=ymax)) +
geom_boxplot(stat="identity")
p
The result is a boxplot without outliers.
One idea would be to winsorize the data in a two-pass procedure:
run a first pass, learn what the bounds are, e.g. cut of at given percentile, or N standard deviation above the mean, or ...
in a second pass, set the values beyond the given bound to the value of that bound
I should stress that this is an old-fashioned method which ought to be dominated by more modern robust techniques but you still come across it a lot.
gg.layers::geom_boxplot2 is just what you want.
# remotes::install_github('rpkgs/gg.layers')
library(gg.layers)
library(ggplot2)
p <- ggplot(mpg, aes(class, hwy))
p + geom_boxplot2(width = 0.8, width.errorbar = 0.5)
https://rpkgs.github.io/gg.layers/reference/geom_boxplot2.html
If you want to force the whiskers to extend to the max and min values, you can tweak the coef argument. Default value for coef is 1.5 (i.e. default length of the whiskers is 1.5 times the IQR).
# Load package and create a dummy data frame with outliers
#(using example from Ramnath's answer above)
library(ggplot2)
df = data.frame(y = c(-100, rnorm(100), 100))
# create boxplot that includes outliers
p0 = ggplot(df, aes(y = y)) + geom_boxplot(aes(x = factor(1)))
# create boxplot where whiskers extend to max and min values
p1 = ggplot(df, aes(y = y)) + geom_boxplot(aes(x = factor(1)), coef = 500)
Simple, dirty and effective.
geom_boxplot(outlier.alpha = 0)
The "coef" option of the geom_boxplot function allows to change the outlier cutoff in terms of interquartile ranges. This option is documented for the function stat_boxplot. To deactivate outliers (in other words they are treated as regular data), one can instead of using the default value of 1.5 specify a very high cutoff value:
library(ggplot2)
# generate data with outliers:
df = data.frame(x=1, y = c(-10, rnorm(100), 10))
# generate plot with increased cutoff for outliers:
ggplot(df, aes(x, y)) + geom_boxplot(coef=1e30)

How to expand ggplot y axis limits to include maximum value

Often in plots the Y axis value label is chopped off below the max value being plotted.
For example:
library(tidyverse)
mtcars %>% ggplot(aes(x=mpg, y = hp))+geom_point()
I know of scale_y_continous - but I can't figure out a smart way to do this. Maybe I'm just overthinking things. I don't wish to mess up the 'smart' breaks that are generated automatically.
I might try to go about this manually...
mtcars %>% ggplot(aes(x=mpg, y=hp, color=as.factor(carb)))+geom_point() + scale_y_continuous(limits = c(0,375))
But this doesn't work like I mentioned above because of the 'smart breaks'. Is there anyway for me to extend the default break interval to 1 more, so that in this case it would be 400? Of course I would want this to be flexible for whatever dataset I am working with.
You can use expand_limits() to increase the maximum y-axis value. You can also ensure that the maximum y-axis value is rounded up to the next highest value on the scale of the data, e.g., next highest tens value, next highest hundreds value, etc., depending on the whether the highest value in the data is within the tens, hundreds, etc.
For example, the function below finds the base 10 log of the maximum y value and rounds it down. This gives us the base ten scale of the maximum y value (e.g., tens, hundreds, thousands, etc.). It then rounds the maximum y-axis value up to the nearest ten, hundred, etc., that is higher than the maximum y value.
expandy = function(vec, ymin=NULL) {
max.val = max(vec, na.rm=TRUE)
min.log = floor(log10(max.val))
expand_limits(y=c(ymin, ceiling(max.val/10^min.log)*10^min.log))
}
p = mtcars %>% ggplot(aes(x=mpg, y = hp)) +
geom_point()
p + expandy(mtcars$hp)
p + expandy(mtcars$hp, 0)
Or, to make things a bit easier, you could set up the function so that the y-range data is collected directly from the plot:
library(gridExtra)
expandy = function(plot, ymin=0) {
max.y = max(layer_data(plot)$y, na.rm=TRUE)
min.log = floor(log10(max.y))
expand_limits(y=c(ymin, ceiling(max.y/10^min.log)*10^min.log))
}
p = mtcars %>% ggplot(aes(x=mpg, y = hp)) +
geom_point()
grid.arrange(p, p + expandy(p), ncol=2)
p = iris %>% ggplot(aes(x=Sepal.Width, y=Petal.Width)) +
geom_point()
grid.arrange(p, p + expandy(p), ncol=2)
Choosing a step for breaking the y axis you can use the ceiling() function
library(gridExtra)
p1 <- mtcars %>% ggplot(aes(x=mpg, y = hp)) + geom_point()
p2 <- p1 +
scale_y_continuous(
limits = c(0, ceiling(max(mtcars$hp)/50)*50),
breaks = seq(0, ceiling(max(mtcars$hp)/50)*50, 50)
)
p3 <- p1 + scale_y_continuous(
limits = c(0, ceiling(max(mtcars$hp)/100)*100),
breaks = seq(0, ceiling(max(mtcars$hp)/100)*100, 100)
)
grid.arrange(p1, p2, p3, ncol=3)
For the p2 the ste is 50 while for p3 the step is 100
Here a solution that allow any kind of numeric scales:
expandy <- function(y, base, v_min = NULL) {
max.val <- max(y, na.rm = TRUE)
expand_limits(
y = c(
v_min,
base * (max.val %/% base + as.logical(max.val %% base))
)
)
}
here is a rather simple answer, just set one limit to NA:
mtcars %>%
ggplot(aes(x=mpg, y=hp, color=as.factor(carb))) +
geom_point() +
scale_y_continuous(limits = c(0, NA))

Ignore outliers in ggplot2 boxplot

How would I ignore outliers in ggplot2 boxplot? I don't simply want them to disappear (i.e. outlier.size=0), but I want them to be ignored such that the y axis scales to show 1st/3rd percentile. My outliers are causing the "box" to shrink so small its practically a line. Are there some techniques to deal with this?
Edit
Here's an example:
y = c(.01, .02, .03, .04, .05, .06, .07, .08, .09, .5, -.6)
qplot(1, y, geom="boxplot")
Use geom_boxplot(outlier.shape = NA) to not display the outliers and scale_y_continuous(limits = c(lower, upper)) to change the axis limits.
An example.
n <- 1e4L
dfr <- data.frame(
y = exp(rlnorm(n)), #really right-skewed variable
f = gl(2, n / 2)
)
p <- ggplot(dfr, aes(f, y)) +
geom_boxplot()
p # big outlier causes quartiles to look too slim
p2 <- ggplot(dfr, aes(f, y)) +
geom_boxplot(outlier.shape = NA) +
scale_y_continuous(limits = quantile(dfr$y, c(0.1, 0.9)))
p2 # no outliers plotted, range shifted
Actually, as Ramnath showed in his answer (and Andrie too in the comments), it makes more sense to crop the scales after you calculate the statistic, via coord_cartesian.
coord_cartesian(ylim = quantile(dfr$y, c(0.1, 0.9)))
(You'll probably still need to use scale_y_continuous to fix the axis breaks.)
Here is a solution using boxplot.stats
# create a dummy data frame with outliers
df = data.frame(y = c(-100, rnorm(100), 100))
# create boxplot that includes outliers
p0 = ggplot(df, aes(y = y)) + geom_boxplot(aes(x = factor(1)))
# compute lower and upper whiskers
ylim1 = boxplot.stats(df$y)$stats[c(1, 5)]
# scale y limits based on ylim1
p1 = p0 + coord_cartesian(ylim = ylim1*1.05)
I had the same problem and precomputed the values for Q1, Q2, median, ymin, ymax using boxplot.stats:
# Load package and generate data
library(ggplot2)
data <- rnorm(100)
# Compute boxplot statistics
stats <- boxplot.stats(data)$stats
df <- data.frame(x="label1", ymin=stats[1], lower=stats[2], middle=stats[3],
upper=stats[4], ymax=stats[5])
# Create plot
p <- ggplot(df, aes(x=x, lower=lower, upper=upper, middle=middle, ymin=ymin,
ymax=ymax)) +
geom_boxplot(stat="identity")
p
The result is a boxplot without outliers.
One idea would be to winsorize the data in a two-pass procedure:
run a first pass, learn what the bounds are, e.g. cut of at given percentile, or N standard deviation above the mean, or ...
in a second pass, set the values beyond the given bound to the value of that bound
I should stress that this is an old-fashioned method which ought to be dominated by more modern robust techniques but you still come across it a lot.
gg.layers::geom_boxplot2 is just what you want.
# remotes::install_github('rpkgs/gg.layers')
library(gg.layers)
library(ggplot2)
p <- ggplot(mpg, aes(class, hwy))
p + geom_boxplot2(width = 0.8, width.errorbar = 0.5)
https://rpkgs.github.io/gg.layers/reference/geom_boxplot2.html
If you want to force the whiskers to extend to the max and min values, you can tweak the coef argument. Default value for coef is 1.5 (i.e. default length of the whiskers is 1.5 times the IQR).
# Load package and create a dummy data frame with outliers
#(using example from Ramnath's answer above)
library(ggplot2)
df = data.frame(y = c(-100, rnorm(100), 100))
# create boxplot that includes outliers
p0 = ggplot(df, aes(y = y)) + geom_boxplot(aes(x = factor(1)))
# create boxplot where whiskers extend to max and min values
p1 = ggplot(df, aes(y = y)) + geom_boxplot(aes(x = factor(1)), coef = 500)
Simple, dirty and effective.
geom_boxplot(outlier.alpha = 0)
The "coef" option of the geom_boxplot function allows to change the outlier cutoff in terms of interquartile ranges. This option is documented for the function stat_boxplot. To deactivate outliers (in other words they are treated as regular data), one can instead of using the default value of 1.5 specify a very high cutoff value:
library(ggplot2)
# generate data with outliers:
df = data.frame(x=1, y = c(-10, rnorm(100), 10))
# generate plot with increased cutoff for outliers:
ggplot(df, aes(x, y)) + geom_boxplot(coef=1e30)

Resources