Related
I am currently writing a theoretical article where no data is used and unfortunately I must say that I find ggplot hard to use in such applications for showing theoretical examples. I've been using ggplot for years on real, empirical data and there I liked ggplot very much. However, consider my current case. I am trying to plot two exponential functions together on a graph. One function is 0.5^x and the other one is 0.8^x. In order to produce a ggplot graph, I have to do the following:
x <- 1:20
a <- 0.5^x
b <- 0.8^x
data.frame(x, a, b) %>%
pivot_longer(c(a, b)) %>%
ggplot(aes(x = x, y = value, color = name, group = name))+
geom_line()
Output:
Which completely doesn't correspond to the psychological process in my head to create such a graph - mainly becasue of converting it to the long format to be able to group it.
In my head, I am creating two simple, but distinct curves on the same canvas. So I should be able to use something like:
qplot(x, 0.5^x, geom = "line")+
qplot(x, 0.8^x, geom = "line")
However, that doesn't work because
Can't add `qplot(x, 0.8^x, geom = "line")` to a ggplot object.
Any help with how to create such a simple graph without reshaping the data would be appreciated, thanks.
Using geom_function you could do:
library(ggplot2)
ggplot() +
geom_function(fun = ~ 0.5^.x, mapping = aes(color = "a")) +
geom_function(fun = ~ 0.8^.x, mapping = aes(color = "b")) +
xlim(1, 20)
Created on 2022-05-08 by the reprex package (v2.0.1)
Maybe something like this. It is possible to keep the data in wide format. But generally it is better to bring it long foramt:
library(ggplot2)
ggplot()+
geom_line(aes(x, 0.5^x, color="red"))+
geom_line(aes(x, 0.8^x, color = "blue"))+
scale_color_identity()
This question already has answers here:
using stat_function and facet_wrap together in ggplot2 in R
(6 answers)
Closed 2 years ago.
I'm looking to plot the following histograms:
library(palmerpenguins)
library(tidyverse)
penguins %>%
ggplot(aes(x=bill_length_mm, fill = species)) +
geom_histogram() +
facet_wrap(~species)
For each histogram, I would like to add a Normal Distribution to each histogram with each species mean and standard deviation.
Of course I'm aware that I could compute the group specific mean and SD before embarking on the ggplot command, but I wonder whether there is a smarter/faster way to do this.
I have tried:
penguins %>%
ggplot(aes(x=bill_length_mm, fill = species)) +
geom_histogram() +
facet_wrap(~species) +
stat_function(fun = dnorm)
But this only gives me a thin line at the bottom:
Any ideas?
Thanks!
Edit
I guess what I'm trying to recreate is this simple command from Stata:
hist bill_length_mm, by(species) normal
which gives me this:
I understand that there are some suggestions here: using stat_function and facet_wrap together in ggplot2 in R
But I'm specifically looking for a short answer that does not require me creating a separate function.
A while I ago I sort of automated this drawing of theoretical densities with a function that I put in the ggh4x package I wrote, which you might find convenient. You would just have to make sure that the histogram and theoretical density are at the same scale (for example counts per x-axis unit).
library(palmerpenguins)
library(tidyverse)
library(ggh4x)
penguins %>%
ggplot(aes(x=bill_length_mm, fill = species)) +
geom_histogram(binwidth = 1) +
stat_theodensity(aes(y = after_stat(count))) +
facet_wrap(~species)
#> Warning: Removed 2 rows containing non-finite values (stat_bin).
You can vary the bin size of the histogram, but you'd have to adjust the theoretical density count too. Typically you'd multiply by the binwidth.
penguins %>%
ggplot(aes(x=bill_length_mm, fill = species)) +
geom_histogram(binwidth = 2) +
stat_theodensity(aes(y = after_stat(count)*2)) +
facet_wrap(~species)
#> Warning: Removed 2 rows containing non-finite values (stat_bin).
Created on 2021-01-27 by the reprex package (v0.3.0)
If this is too much of a hassle, you can always convert the histogram to density instead of the density to counts.
penguins %>%
ggplot(aes(x=bill_length_mm, fill = species)) +
geom_histogram(aes(y = after_stat(density))) +
stat_theodensity() +
facet_wrap(~species)
While the ggh4x package is the way to go in this case, a more generalizable approach is with tapply and the use of the PANEL variable which is added to the data when a facet is applied.
penguins %>%
ggplot(aes(x=bill_length_mm, fill = species)) +
geom_histogram(aes(y = after_stat(density)), bins = 30) +
facet_wrap(~species) +
geom_line(aes(y = dnorm(bill_length_mm,
mean = tapply(bill_length_mm, species, mean, na.rm = TRUE)[PANEL],
sd = tapply(bill_length_mm, species, sd, na.rm = TRUE)[PANEL])))
I want to make a bar plot where one of the values is much bigger than all other values. Is there a way of having a discontinuous y-axis? My data is as follows:
df <- data.frame(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))
p <- ggplot(data = df, aes(x = b, y = a)) + geom_bar()
p <- p + opts(axis.text.x=theme_text(angle= 90, hjust=1)) + coord_flip()
p
Is there a way that I can make my axis run from 1- 10, then 490 - 500? I can't think of any other way of plotting the data (aside from transforming it, which I don't want to do)
[Edit 2019-05-06]:
8 years later, above code needs to be amended to work with version 3.1.1 of ggplot2 in order to create the same chart:
library(ggplot2)
ggplot(df) +
aes(x = b, y = a) +
geom_col() +
coord_flip()
As noted elsewhere, this isn't something that ggplot2 will handle well, since broken axes are generally considered questionable.
Other strategies are often considered better solutions to this problem. Brian mentioned a few (faceting, two plots focusing on different sets of values). One other option that people too often overlook, particularly for barcharts, is to make a table:
Looking at the actual values, the 500 doesn't obscure the differences in the other values! For some reason tables don't get enough respect as data a visualization technique. You might object that your data has many, many categories which becomes unwieldy in a table. If so, it's likely that your bar chart will have too many bars to be sensible as well.
And I'm not arguing for tables all the time. But they are definitely something to consider if you are making barcharts with relatively few bars. And if you're making barcharts with tons of bars, you might need to rethink that anyway.
Finally, there is also the axis.break function in the plotrix package which implements broken axes. However, from what I gather you'll have to specify the axis labels and positions yourself, by hand.
Eight years later, the ggforce package offers a facet_zoom() extension which is an implementation of Hadley Wickham's suggestion to show two plots (as referenced in Brian Diggs' answer).
Zoom facet
library(ggforce)
ggplot(df) +
aes(x = b, y = a) +
geom_col() +
facet_zoom(ylim = c(0, 10))
Unfortunately, the current version 0.2.2 of ggforce throws an error with coord_flip() so only vertical bars can be shown.
The zoomed facet shows the variations of the small values but still contains the large - now cropped - a4 bar. The zoom.data parameter controls which values appear in the zoomed facet:
library(ggforce)
ggplot(df) +
aes(x = b, y = a) +
geom_col() +
facet_zoom(ylim = c(0, 10), zoom.data = ifelse(a <= 10, NA, FALSE))
Two plots
Hadley Wickham suggested
I think it's much more appropriate to show two plots - one of all the
data, and one of just the small values.
This code creates two plots
library(ggplot2)
g1 <- ggplot(df) +
aes(x = b, y = a) +
geom_col() +
coord_flip()
g2 <- ggplot(df) +
aes(x = b, y = a) +
geom_col() +
coord_flip() +
ylim(NA, 10)
which can be combined into one plot by
cowplot::plot_grid(g1, g2) # or ggpubr::ggarrange(g1, g2)
or
gridExtra::grid.arrange(g1, g2) # or egg::ggarrange(g1, g2)
Two facets
This was suggested in a comment by Chase and also by Brian Diggs in his answer who interpreted Hadley's suggestion to use
faceted plots, one with all the data, one zoomed in a particular region
but no code was supplied for this approach, so far.
As there is no simple way to scale facets separately (see related question, e.g.) the data needs to be manipulated:
library(dplyr)
library(ggplot2)
ggplot() +
aes(x = b, y = a) +
geom_col(data = df %>% mutate(subset = "all")) +
geom_col(data = df %>% filter(a <= 10) %>% mutate(subset = "small")) +
coord_flip() +
facet_wrap(~ subset, scales = "free_x")
No, not using ggplot. See the discussion in the thread at http://groups.google.com/group/ggplot2/browse_thread/thread/8d2acbfc59d2f247 where Hadley explains why it is not possible but gives a suggested alternative (faceted plots, one with all the data, one zoomed in a particular region).
Not with ggplot, but with plotrix you can easily do that:
library(plotrix)
gap.barplot(df$a, gap=c(5,495),horiz=T)
No, unfortunately not
The fear is that allowing discontinuous axes will lead to deceit of the audience. However, there are cases where not having a discontinuous axis leads to distortion.
For example, if the axis is truncated, but usually lies within some interval (say [0,1]), the audience may not notice the truncation and make distorted conclusions about the data. In this case, an explicit discontinuous axis would be more appropriate and transparent.
Compare:
An option could be using the ggbreak package using the scale_y_cut() or scale_x_cut() function. This function makes it possible to cut the ggplot object into parts with the possibility to specify which part is zoom in or zoom out. Here is a reproducible example with left plot normal and right plot with the function used:
df <- data.frame(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))
library(ggplot2)
library(ggbreak)
library(patchwork)
p1 <- ggplot(df) +
aes(x = b, y = a) +
geom_col()
p2 <- ggplot(df) +
aes(x = b, y = a) +
geom_col() +
scale_y_cut(breaks=c(4, 30), which=c(1, 3), scales=c(0.5, 3))
p1 + p2
Created on 2022-08-22 with reprex v2.0.2
As you can see from the example, some parts are zoomed in and zoomed out. This can be changed by using different arguments.
Arguments used:
breaks:
a numeric or numeric vector, the points to be divided
which:
integer, the position of subplots to scales, started from left to
right or top to bottom.
scales:
numeric, relative width or height of subplots.
To change the space between the subplots, you can use the argument space.
For some extra information and examples check this tutorial.
A clever ggplot solution is provided by Jörg Steinkamp, using facet_grid. Simplified, it is something like this:
library("tidyverse")
df <- data.frame(myLetter=LETTERS[1:4], myValue=runif(12) + rep(c(4,0,0),2)) # cluster a few values well above 1
df$myFacet <- df$myValue > 3
(ggplot(df, aes(y=myLetter, x=myValue))
+ geom_point()
+ facet_grid(. ~ myFacet, scales="free", space="free")
+ scale_x_continuous(breaks = seq(0, 5, .25)) # this gives both facets equal interval spacing.
+ theme(strip.text.x = element_blank()) # get rid of the facet labels
)
As of 2022-06-01, we have the elegant-looking ggbreak package, which appears to answer the OP's question. Although I haven't tried it on my own data, it looks to be compatible with many or all other ggplot2 functionality. Offers differential scaling too, perhaps useful to OP's and similar uses.
library(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22))
p1 <- ggplot(d, aes(y, x)) + geom_col(orientation="y") +
theme_minimal()
p1 + scale_x_break(c(7, 17), scales = 1.5) + scale_x_break(c(18, 21), scales=2)
I doubt there's anything off the shelf in R, but you could show the data as a series of 3D partial cubes. 500 is only 5*10*10, so it would scale well. The exact value could be a label.
This probably should only be used if you must have a graphic representation for some reason.
One strategy is to change the axis to plot Log Scale. This way you get to reduce exponentially higher value by a factor of 10
Does anyone know how to create a scatterplot in R to create plots like these in PRISM's graphpad:
I tried using boxplots but they don't display the data the way I want it. These column scatterplots that graphpad can generate show the data better for me.
Any suggestions would be appreciated.
As #smillig mentioned, you can achieve this using ggplot2. The code below reproduces the plot that you are after pretty well - warning it is quite tricky. First load the ggplot2 package and generate some data:
library(ggplot2)
dd = data.frame(values=runif(21), type = c("Control", "Treated", "Treated + A"))
Next change the default theme:
theme_set(theme_bw())
Now we build the plot.
Construct a base object - nothing is plotted:
g = ggplot(dd, aes(type, values))
Add on the points: adjust the default jitter and change glyph according to type:
g = g + geom_jitter(aes(pch=type), position=position_jitter(width=0.1))
Add on the "box": calculate where the box ends. In this case, I've chosen the average value. If you don't want the box, just omit this step.
g = g + stat_summary(fun.y = function(i) mean(i),
geom="bar", fill="white", colour="black")
Add on some error bars: calculate the upper/lower bounds and adjust the bar width:
g = g + stat_summary(
fun.ymax=function(i) mean(i) + qt(0.975, length(i))*sd(i)/length(i),
fun.ymin=function(i) mean(i) - qt(0.975, length(i)) *sd(i)/length(i),
geom="errorbar", width=0.2)
Display the plot
g
In my R code above I used stat_summary to calculate the values needed on the fly. You could also create separate data frames and use geom_errorbar and geom_bar.
To use base R, have a look at my answer to this question.
If you don't mind using the ggplot2 package, there's an easy way to make similar graphics with geom_boxplot and geom_jitter. Using the mtcars example data:
library(ggplot2)
p <- ggplot(mtcars, aes(factor(cyl), mpg))
p + geom_boxplot() + geom_jitter() + theme_bw()
which produces the following graphic:
The documentation can be seen here: http://had.co.nz/ggplot2/geom_boxplot.html
I recently faced the same problem and found my own solution, using ggplot2.
As an example, I created a subset of the chickwts dataset.
library(ggplot2)
library(dplyr)
data(chickwts)
Dataset <- chickwts %>%
filter(feed == "sunflower" | feed == "soybean")
Since in geom_dotplot() is not possible to change the dots to symbols, I used the geom_jitter() as follow:
Dataset %>%
ggplot(aes(feed, weight, fill = feed)) +
geom_jitter(aes(shape = feed, col = feed), size = 2.5, width = 0.1)+
stat_summary(fun = mean, geom = "crossbar", width = 0.7,
col = c("#9E0142","#3288BD")) +
scale_fill_manual(values = c("#9E0142","#3288BD")) +
scale_colour_manual(values = c("#9E0142","#3288BD")) +
theme_bw()
This is the final plot:
For more details, you can have a look at this post:
http://withheadintheclouds1.blogspot.com/2021/04/building-dot-plot-in-r-similar-to-those.html?m=1
I want to make a bar plot where one of the values is much bigger than all other values. Is there a way of having a discontinuous y-axis? My data is as follows:
df <- data.frame(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))
p <- ggplot(data = df, aes(x = b, y = a)) + geom_bar()
p <- p + opts(axis.text.x=theme_text(angle= 90, hjust=1)) + coord_flip()
p
Is there a way that I can make my axis run from 1- 10, then 490 - 500? I can't think of any other way of plotting the data (aside from transforming it, which I don't want to do)
[Edit 2019-05-06]:
8 years later, above code needs to be amended to work with version 3.1.1 of ggplot2 in order to create the same chart:
library(ggplot2)
ggplot(df) +
aes(x = b, y = a) +
geom_col() +
coord_flip()
As noted elsewhere, this isn't something that ggplot2 will handle well, since broken axes are generally considered questionable.
Other strategies are often considered better solutions to this problem. Brian mentioned a few (faceting, two plots focusing on different sets of values). One other option that people too often overlook, particularly for barcharts, is to make a table:
Looking at the actual values, the 500 doesn't obscure the differences in the other values! For some reason tables don't get enough respect as data a visualization technique. You might object that your data has many, many categories which becomes unwieldy in a table. If so, it's likely that your bar chart will have too many bars to be sensible as well.
And I'm not arguing for tables all the time. But they are definitely something to consider if you are making barcharts with relatively few bars. And if you're making barcharts with tons of bars, you might need to rethink that anyway.
Finally, there is also the axis.break function in the plotrix package which implements broken axes. However, from what I gather you'll have to specify the axis labels and positions yourself, by hand.
Eight years later, the ggforce package offers a facet_zoom() extension which is an implementation of Hadley Wickham's suggestion to show two plots (as referenced in Brian Diggs' answer).
Zoom facet
library(ggforce)
ggplot(df) +
aes(x = b, y = a) +
geom_col() +
facet_zoom(ylim = c(0, 10))
Unfortunately, the current version 0.2.2 of ggforce throws an error with coord_flip() so only vertical bars can be shown.
The zoomed facet shows the variations of the small values but still contains the large - now cropped - a4 bar. The zoom.data parameter controls which values appear in the zoomed facet:
library(ggforce)
ggplot(df) +
aes(x = b, y = a) +
geom_col() +
facet_zoom(ylim = c(0, 10), zoom.data = ifelse(a <= 10, NA, FALSE))
Two plots
Hadley Wickham suggested
I think it's much more appropriate to show two plots - one of all the
data, and one of just the small values.
This code creates two plots
library(ggplot2)
g1 <- ggplot(df) +
aes(x = b, y = a) +
geom_col() +
coord_flip()
g2 <- ggplot(df) +
aes(x = b, y = a) +
geom_col() +
coord_flip() +
ylim(NA, 10)
which can be combined into one plot by
cowplot::plot_grid(g1, g2) # or ggpubr::ggarrange(g1, g2)
or
gridExtra::grid.arrange(g1, g2) # or egg::ggarrange(g1, g2)
Two facets
This was suggested in a comment by Chase and also by Brian Diggs in his answer who interpreted Hadley's suggestion to use
faceted plots, one with all the data, one zoomed in a particular region
but no code was supplied for this approach, so far.
As there is no simple way to scale facets separately (see related question, e.g.) the data needs to be manipulated:
library(dplyr)
library(ggplot2)
ggplot() +
aes(x = b, y = a) +
geom_col(data = df %>% mutate(subset = "all")) +
geom_col(data = df %>% filter(a <= 10) %>% mutate(subset = "small")) +
coord_flip() +
facet_wrap(~ subset, scales = "free_x")
No, not using ggplot. See the discussion in the thread at http://groups.google.com/group/ggplot2/browse_thread/thread/8d2acbfc59d2f247 where Hadley explains why it is not possible but gives a suggested alternative (faceted plots, one with all the data, one zoomed in a particular region).
Not with ggplot, but with plotrix you can easily do that:
library(plotrix)
gap.barplot(df$a, gap=c(5,495),horiz=T)
No, unfortunately not
The fear is that allowing discontinuous axes will lead to deceit of the audience. However, there are cases where not having a discontinuous axis leads to distortion.
For example, if the axis is truncated, but usually lies within some interval (say [0,1]), the audience may not notice the truncation and make distorted conclusions about the data. In this case, an explicit discontinuous axis would be more appropriate and transparent.
Compare:
An option could be using the ggbreak package using the scale_y_cut() or scale_x_cut() function. This function makes it possible to cut the ggplot object into parts with the possibility to specify which part is zoom in or zoom out. Here is a reproducible example with left plot normal and right plot with the function used:
df <- data.frame(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))
library(ggplot2)
library(ggbreak)
library(patchwork)
p1 <- ggplot(df) +
aes(x = b, y = a) +
geom_col()
p2 <- ggplot(df) +
aes(x = b, y = a) +
geom_col() +
scale_y_cut(breaks=c(4, 30), which=c(1, 3), scales=c(0.5, 3))
p1 + p2
Created on 2022-08-22 with reprex v2.0.2
As you can see from the example, some parts are zoomed in and zoomed out. This can be changed by using different arguments.
Arguments used:
breaks:
a numeric or numeric vector, the points to be divided
which:
integer, the position of subplots to scales, started from left to
right or top to bottom.
scales:
numeric, relative width or height of subplots.
To change the space between the subplots, you can use the argument space.
For some extra information and examples check this tutorial.
A clever ggplot solution is provided by Jörg Steinkamp, using facet_grid. Simplified, it is something like this:
library("tidyverse")
df <- data.frame(myLetter=LETTERS[1:4], myValue=runif(12) + rep(c(4,0,0),2)) # cluster a few values well above 1
df$myFacet <- df$myValue > 3
(ggplot(df, aes(y=myLetter, x=myValue))
+ geom_point()
+ facet_grid(. ~ myFacet, scales="free", space="free")
+ scale_x_continuous(breaks = seq(0, 5, .25)) # this gives both facets equal interval spacing.
+ theme(strip.text.x = element_blank()) # get rid of the facet labels
)
As of 2022-06-01, we have the elegant-looking ggbreak package, which appears to answer the OP's question. Although I haven't tried it on my own data, it looks to be compatible with many or all other ggplot2 functionality. Offers differential scaling too, perhaps useful to OP's and similar uses.
library(ggplot2)
library(ggbreak)
set.seed(2019-01-19)
d <- data.frame(x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22))
p1 <- ggplot(d, aes(y, x)) + geom_col(orientation="y") +
theme_minimal()
p1 + scale_x_break(c(7, 17), scales = 1.5) + scale_x_break(c(18, 21), scales=2)
I doubt there's anything off the shelf in R, but you could show the data as a series of 3D partial cubes. 500 is only 5*10*10, so it would scale well. The exact value could be a label.
This probably should only be used if you must have a graphic representation for some reason.
One strategy is to change the axis to plot Log Scale. This way you get to reduce exponentially higher value by a factor of 10