library(tidyverse)
df <- data.frame(x = c(1, 2, 3, 4),
y1 = c(1, 3, 2, 4),
y2 = c(3000, 2000, 4000, 1000))
label_1 = c("1", "2", "3") %>% format(width = 5, justify = "right")
label_2 = c("1000", "2000", "3000") %>% format(width = 5, justify = "right")
p1 <- df %>% ggplot(aes(x = x, y = y1))+
geom_line()+
scale_y_continuous(breaks = c(1, 2, 3),
labels = label_1)
p2 <- df %>% ggplot(aes(x = x, y = y2))+
geom_line()+
scale_y_continuous(breaks = c(1000, 2000, 3000),
labels = label_2)
p1
p2
In the above code, I try to make p1 and p2 to have the same length of y-axis label, by format(width = 5). In the actual graph, labels in p1 are still shorter than those in p2.
By trial and error, I get the right length when setting width =8.
label_3 = c("1", "2", "3") %>% format(width = 8, justify = "right")
p1 <- df %>% ggplot(aes(x = x, y = y1))+
geom_line()+
scale_y_continuous(breaks = c(1, 2, 3),
labels = label_3)
p1
Could someone please explain this or guide me to the relevant previous posts?
The regular space does not have the same width as a digits (in non-monospaced fonts). Unicode has a 'figure space' that has 'tabular width', i.e. the same width as digits. If you make a helper function that replaces spaces with the tabular widths (\u2007), both plots should have the same size for their y-axis. The function:
pad_numbers <- function(x, width, justify = "right") {
x <- format(x, width = width, justify = justify)
gsub(" ", "\u2007", x)
}
In action:
library(tidyverse)
df <- data.frame(x = c(1, 2, 3, 4),
y1 = c(1, 3, 2, 4),
y2 = c(3000, 2000, 4000, 1000))
label_1 = c("1", "2", "3") %>% pad_numbers(width = 5, justify = "right")
label_2 = c("1000", "2000", "3000") %>% pad_numbers(width = 5, justify = "right")
p1 <- df %>% ggplot(aes(x = x, y = y1))+
geom_line()+
scale_y_continuous(breaks = c(1, 2, 3),
labels = label_1)
p2 <- df %>% ggplot(aes(x = x, y = y2))+
geom_line()+
scale_y_continuous(breaks = c(1000, 2000, 3000),
labels = label_2)
p1
p2
Created on 2022-02-17 by the reprex package (v2.0.1)
Lastly, if you want the y-axis to have the same width for plot composition purposes, I recommend the {ragg} package, which does a good job at aligning the panels between two plots. This wouldn't require the helper function we wrote at the beginning.
Related
I would like to display a scale_color_gradient scale bar that (i) only has a set number of decimal points and (ii) also always displays "0" not "0.00". What is the best way to do this?
library(ggplot2)
dat <- data.frame(x = rnorm(10, 30, .2), y = runif(10, 3, 5),z = rnorm(10, 30, .2))
scaled.dat <- data.frame(scale(dat))
ggplot(scaled.dat, aes(x, y, colour = z)) + geom_point()+
# Modify the number of decimal points
scale_color_gradient(labels = function(x) sprintf("%.5f", x))
# Make zero value display "0" only
#scale_color_gradient(labels = ~sub("0.0", "0", sprintf("%.1f", .x)))
Using an ifelse you could do:
library(ggplot2)
set.seed(123)
dat <- data.frame(x = rnorm(10, 30, .2), y = runif(10, 3, 5), z = rnorm(10, 30, .2))
scaled.dat <- data.frame(scale(dat))
ggplot(scaled.dat, aes(x, y, colour = z)) +
geom_point() +
scale_color_gradient(
labels = ~ ifelse(.x != 0, sprintf("%.5f", .x), sprintf("%.0f", .x))
)
set.seed(3)
df <- data.frame(lambda = c(rep(0, 6), rep(1, 6), rep(1.5, 6)),
approach = rep(c(rep("A", 3), rep("B", 3)), 3),
value = rnorm(18, 0, 1))
ggplot(data = df, aes(x = lambda, y = value)) + geom_boxplot(aes(fill = approach))
I want to plot 3 sets of boxplots at lambda = 0, 1, and 1.5, respectively. Within each set are 2 boxplots, one corresponds to approach A and the other to approach B. However, the current code is only plotting two boxplots, whereas I'm looking for a total of six.
I think you want "lambda" to be a factor, e.g.
library(tidyverse)
set.seed(3)
df <- data.frame(lambda = c(rep(0, 6), rep(1, 6), rep(1.5, 6)),
approach = rep(c(rep("A", 3), rep("B", 3)), 3),
value = rnorm(18, 0, 1))
ggplot(data = df, aes(x = factor(lambda), y = value)) +
geom_boxplot(aes(fill = approach))
I try to find a clear approach for combined scatter and line plots with ggplot2 that have an appropriate legend. The following works, in principle, but with warnings:
library("ggplot2")
library("dplyr")
## 2 data sets, one for the lines, one for the points
tbl <- tibble(
f = rep(letters[1:2], each = 10),
x = rep(1:10, 2),
y = c(1e-4 * exp(1:10), log(1:10))
)
obs <- tibble(
f = rep("c", 5),
x = seq(2, 10, 2),
y = log(seq(2, 10, 2)) + rnorm(5, sd = 0.1)
)
rbind(tbl, obs) %>%
ggplot(aes(x, y, color = f, linetype = f)) +
geom_line(show.legend = TRUE) +
geom_point(show.legend = TRUE, aes(shape = f), size = 3) +
scale_linetype_manual(values=c("solid", "solid", "blank")) +
scale_shape_manual(values=c(NA, NA, 16))
but I would like to get rid of warnings and to write something like:
scale_shape_manual(values=c("none", "none", "circle"))
Is there already a "none" or "empty" shape code? Several past answers have been suggested on SO, but I wonder if there is a recent canonical way.
I am actually very amazed to see I cannot quickly find a guide to how to do this. Here is an example:
library(ggplot2)
library(gganimate)
library(data.table)
library(magrittr)
dt <- lapply(seq(10), function(i){
mean = i
label = paste0("T = ", i)
dt = data.table(x = seq(0, 50, length.out = 100))
set(dt, j = "y", value = dt[, dlnorm(x, meanlog = log(mean), sdlog = 0.2)])
set(dt, j = "frameN", value = i)
return(dt)
}) %>% rbindlist
print(dt)
p <- ggplot(dt, aes(x = x, y = y)) +
geom_line() +
scale_x_continuous(name = "x", breaks = c(0, 1)) +
transition_manual(frameN)
animate(p)
I want the breaks and labels of scale_x_continuous to follow my own definitions:
arr_breaks <- c(1, 3, 2, 4, 3, 5, 4, 6, 5, 7)
arr_labels <- paste0(seq(10, 100, 10), " kg")
And then
breaks = arr_breaks[1], labels = arr_labels[1] for frame 1
breaks = arr_breaks[2], labels = arr_labels[2] for frame 2
...
breaks = arr_breaks[10], labels = arr_labels[10] for frame 10
No matter how I do it I got errors. Any idea?
As #z-lin noted, gganimate is not currently set up (to my knowledge) to animate scales with different breaks. The effect could be closely approximated using geoms, and with some more work you could probably make an exact visual match to a changing scale.
breaks_df <- data.frame(
frameN = c(1:10),
arr_breaks = c(1, 3, 2, 4, 3, 5, 4, 6, 5, 7),
arr_labels = paste0(seq(10, 100, 10), " kg")
)
p <- ggplot(dt, aes(x = x, y = y)) +
geom_segment(data = breaks_df, color = "white",
aes(x = arr_breaks, xend = arr_breaks,
y = -Inf, yend = Inf)) +
geom_text(data = breaks_df, vjust = 3, size = 3.5, color = "gray30",
aes(x = arr_breaks, y = 0, label = arr_labels)) +
geom_line() +
scale_x_continuous(name = "x", breaks = c(0)) +
coord_cartesian(clip = "off") +
transition_manual(frameN)
animate(p, width = 600, height = 250)
Sample data
set.seed(123)
par(mfrow = c(1,2))
dat <- data.frame(years = rep(1980:2014, each = 8), x = sample(1000:2000, 35*8 ,replace = T))
boxplot(dat$x ~ dat$year, ylim = c(500, 4000))
I have another dataset that has a single value for some selected years
ref.dat <- data.frame(years = c(1991:1995, 2001:2008), x = sample(1000:2000, 13, replace = T))
plot(ref.dat$years, ref.dat$x, type = "b")
How can I add the line plot on top of the boxplot
With ggplot2 you could do this:
ggplot(dat, aes(x = years, y = x)) +
geom_boxplot(data = dat, aes(group = years)) +
geom_line(data = ref.dat, colour = "red") +
geom_point(data = ref.dat, colour = "red", shape = 1) +
coord_cartesian(ylim = c(500, 4000)) +
theme_bw()
The trick here is to figure out the x-axis on the boxplot. You have 35 boxes and they are plotted at the x-coordinates 1, 2, 3, ..., 35 - i.e. year - 1979. With that, you can add the line with lines as usual.
set.seed(123)
dat <- data.frame(years = rep(1980:2014, each = 8),
x = sample(1000:2000, 35*8 ,replace = T))
boxplot(dat$x ~ dat$year, ylim = c(500, 2500))
ref.dat <- data.frame(years = c(1991:1995, 2001:2008),
x = sample(1000:2000, 13, replace = T))
lines(ref.dat$years-1979, ref.dat$x, type = "b", pch=20)
The points were a bit hard to see, so I changed the point style 20. Also, I used a smaller range on the y-axis to leave less blank space.