I have a chart of a single person's blood pressure readings. I also know when they started and stopped various anti-hypertensive medications and the doses thereof.
How do I add a box above the blood pressure data to show when any particular drug was started and stopped and restarted and at what dose?
Here is a made-up drawing of what I am trying to do.
I could probably use the ggtext package but it looks as if it will be a lot of finnicky messing around for what I want to do. Is there a package that specifically adds this sort of functionality.
This seems like it could be usecase for Paul Murrell's {gggrid} package on github. The package allows you to flexibly draw anything in ggplot2/grid hybrid fashion.
Based on your plot, I'm assuming you have data roughly in the following shapes:
library(ggplot2)
library(gggrid) # remotes::install_github("pmur002/gggrid")
#> Loading required package: grid
df <- data.frame(
x = seq(Sys.Date(), Sys.Date() + 60, by = 1),
y = cumsum(rnorm(61))
)
Along with some annotations for the treatments.
annotation <- data.frame(
label = c("Cardelevovol", "Lisinopril 50 mg", "Lisonopril 100 mg"),
xmin = Sys.Date() + c(0, 0, 40),
xmax = Sys.Date() + c(40, 20, 60),
y = c(1, 0, 0),
fill = c("red", "white", "white")
)
We can then define a function that will draw labelled rectangles in the upper margin of the plot.
annotate_fun <- function(data, coords) {
textheight <- unit(1, "lines")
rectangles <- rectGrob(
x = (coords$xmin + coords$xmax) / 2,
width = coords$xmax - coords$xmin,
y = (data$y + 0.5) * textheight + unit(1, "npc"),
height = textheight,
gp = gpar(fill = coords$fill)
)
text <- textGrob(
label = data$label,
x = (coords$xmin + coords$xmax) / 2,
y = (data$y + 0.5) * textheight + unit(1, "npc")
)
gList(rectangles, text)
}
Which we can then feed to the gggrid::grid_panel() function.
ggplot(df, aes(x, y)) +
geom_point() +
geom_smooth(method = "loess", formula = y ~ x) +
grid_panel(
annotate_fun, data = annotation,
aes(xmin = xmin, xmax = xmax,
label = label, fill = I(fill), x = NULL)
) +
# Turn off clipping and add some extra margin in top
coord_cartesian(clip = "off") +
theme(plot.margin = margin(35, 5.5, 5.5, 5.5))
#> Warning: Ignoring unknown aesthetics: xmin, xmax, label, fill
Created on 2021-10-07 by the reprex package (v2.0.1)
Related
I am trying to add y axis labels to my figure (a compilation of 6 figures in a 2x3 arrangement. The top 3 figures are part of one group, and the bottom 3 are part of another so I'd like to add two separate y axis labels at the start of each row. I am using the ggarrange function in {ggpubr}.
The image below is what I would like to have (i.e. the additional "Abundance" and "Presence/Absence" labels.
Below is the code I am using:
fig <- ggarrange(p1, p2 p3, p4, p5, p6, ncol=3, nrow=2, common.legend=FALSE, align="v", labels=c("A)","B)","C)","D)","E)","F)"))
Thank you in advance for your suggestions!
It seems the thing you're trying to do is not super easy to do natively with ggpubr::ggarrange. I usually prefer {patchwork} but didn't find an easy solution there either. However, there is a feature request on github that contains some code which I was able to turn into a solution here.
library(tidyverse)
library(patchwork)
# borrowed from: https://github.com/thomasp85/patchwork/issues/43
add_global_label <- function(pwobj, Xlab = NULL, Ylab = NULL, Xgap = 0.03, Ygap = 0.03, ...) {
ylabgrob <- patchwork::plot_spacer()
if (!is.null(Ylab)) {
ylabgrob <- ggplot() +
geom_text(aes(x = .5, y = .5), label = Ylab, angle = 90, ...) +
theme_void()
}
if (!is.null(Xlab)) {
xlabgrob <- ggplot() +
geom_text(aes(x = .5, y = .5), label = Xlab, ...) +
theme_void()
}
if (!is.null(Ylab) & is.null(Xlab)) {
return((ylabgrob + patchworkGrob(pwobj)) +
patchwork::plot_layout(widths = 100 * c(Ygap, 1 - Ygap)))
}
if (is.null(Ylab) & !is.null(Xlab)) {
return((ylabgrob + pwobj) +
(xlabgrob) +
patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
widths = c(0, 100),
design = "
AB
CC
"
))
}
if (!is.null(Ylab) & !is.null(Xlab)) {
return((ylabgrob + pwobj) +
(xlabgrob) +
patchwork::plot_layout(heights = 100 * c(1 - Xgap, Xgap),
widths = 100 * c(Ygap, 1 - Ygap),
design = "
AB
CC
"
))
}
return(pwobj)
}
p <- mtcars %>%
ggplot(aes(disp, mpg)) +
geom_point(aes(color = factor(cyl))) +
theme_classic()
top <- ((p+p+p) +
plot_annotation(tag_levels = list(LETTERS[1:3]), tag_suffix = ")")) %>%
add_global_label(Ylab = "Top Row")
bottom <- ((p+p+p) +
plot_annotation(tag_levels = list(LETTERS[4:6]), tag_suffix = ")")) %>%
add_global_label(Ylab = "Bottom Row")
top/bottom
Created on 2022-04-04 by the reprex package (v2.0.1)
The {cowplot} package has a native solution to this problem, but it will probably take a little more fiddling to get the spacing right to avoid the label overlapping the y axis title.
library(tidyverse)
library(cowplot)
# make basic plot
p <- mtcars %>%
ggplot(aes(disp, mpg)) +
geom_point(aes(color = factor(cyl))) +
theme_classic()
# compose top row of plots with labels
t <- plot_grid(
p, p, p,
labels = paste0(LETTERS[1:3], ")"),
label_size = 12,
nrow = 1) %>%
ggdraw() +
draw_label("Top Row", size = 16, angle = 90, x = 0.01, y = 0.5)
# compose bottom row of plots with labels
b <- plot_grid(
p, p, p,
labels = paste0(LETTERS[4:6], ")"),
label_size = 12,
nrow = 1) %>%
ggdraw() +
draw_label("Bottom Row", size = 16, angle = 90, x = 0.01, y = 0.5)
# compose overall layout
plot_grid(t, b, nrow = 2)
Created on 2022-04-04 by the reprex package (v2.0.1)
Suppose I have a raster plot where the fill color gradient isn't used very efficiently because the values are skewed, like this:
library(ggplot2)
set.seed(20)
d = expand.grid(x = seq(0, 10, len = 100), y = seq(0, 10, len = 100))
d = transform(d, z =
1e-4 * ((x - 2)^2 + (2*y - 4)^2 + 10*rnorm(nrow(d)))^2)
ggplot(d) +
geom_raster(aes(x, y, fill = z)) +
scale_fill_distiller(palette = "Spectral",
limits = c(0, 12), breaks = 0 : 12) +
theme(legend.key.height = unit(20, "mm"))
I can quantile-transform the color scale like this:
ggplot(d) +
geom_raster(aes(x, y, fill = z)) +
scale_fill_distiller(palette = "Spectral",
limits = c(0, 12), breaks = 0 : 12,
trans = scales::trans_new("q",
function(x) ecdf(d$z)(x),
function(x) unname(quantile(d$z, x)))) +
theme(legend.key.height = unit(20, "mm"))
I like what this does for the main part of the plot, but not the legend. The legend uses the same gradient as the original, while moving the breaks according to the transformation. I'd prefer to keep the breaks where they are, while transforming the gradient instead. Also, I'd like to avoid the floating-point noise that's been added to the break labels. How can I accomplish these changes?
I had a very similar idea to chemdork123, but wanted to stay a bit closer to the quantile idea. The idea is to set an exact palette of colours (i.e., one colour for every value) and space this out such that it follows the data.
library(ggplot2)
library(scales)
#> Warning: package 'scales' was built under R version 4.0.3
set.seed(20)
d = expand.grid(x = seq(0, 10, len = 100), y = seq(0, 10, len = 100))
d = transform(d, z =
1e-4 * ((x - 2)^2 + (2*y - 4)^2 + 10*rnorm(nrow(d)))^2)
# The 'distiller' palette outside of the scale,
# we need this to generate `length(d$z)` number of colours.
pal <- gradient_n_pal(brewer_pal(palette = "Spectral", direction = -1)(7))
ggplot(d) +
geom_raster(aes(x, y, fill = z)) +
scale_fill_gradientn(
colours = pal(c(0, rescale(seq_along(d$z)), 1)), # <- extra 0, 1 for out-of-bounds
limits = c(0, 12), breaks = 0:12,
values = c(0, rescale(sort(d$z), from = c(0, 12)), 1) # <- extra 0, 1 again
) +
theme(legend.key.height = unit(10, "mm"))
Created on 2021-03-31 by the reprex package (v1.0.0)
You can use the values argument for the scale_fill_distiller() function. The distiller scales extend brewer to continuous scales by interpolating 7 colors from any palette. By default, the scaling is linearly applied from 0 (lowest value on the scale) to 1 (highest value on the scale). You can recreate this mapping via: scales::rescale(1:7). If you supply a new vector to the values argument, you can remap each of the 7 colors to a new location. You do not have to supply 7 values - the rest are interpolated linearly - just as long as you specify the max at 1 (or you'll cut the scale).
Best way is to play around with it - I've tried mapping to specific functions before, but honestly it tends to work for me the best when I just mess with the numbers until I get something I like:
ggplot(d) +
geom_raster(aes(x, y, fill = z)) +
scale_fill_distiller(palette = "Spectral", values = c(0,0.05,0.1, 0.5,1)) +
theme(legend.key.height = unit(20, "mm"))
I want to divide the y axis for the attached figure to take part with a score <25 occupies the majority of the figure while the remaining represent a minor upper part.
I browsed that and I am aware that I should use scale_y_discrete(limits .I used this p<- p+scale_y_continuous(breaks = 1:20, labels = c(1:20,"//",40:100)) but it doesn't work yet.
I used the attached data and this is my code
Code
p<-ggscatter(data, x = "Year" , y = "Score" ,
color = "grey", shape = 21, size = 3, # Points color, shape and size
add.params = list(color = "blue", fill = "lightgray"), # Customize reg. line
add = "loess", #reg.line
conf.int = T,
cor.coef = F, cor.method = "pearson",
xlab = "Year" , ylab= "Score")
p<-p+ coord_cartesian(xlim = c(1980, 2020));p
Here is as close as I could get getting a fake axis break and resizing the upper area of the plot. I still think it's a bad idea and if this were my plot I'd much prefer a more straightforward axis transform.
First, we'd need a function that generates a transform that squeezes all values above some threshold:
library(ggplot2)
library(scales)
# Define new transform
my_transform <- function(threshold = 25, squeeze_factor = 10) {
force(threshold)
force(squeeze_factor)
my_transform <- trans_new(
name = "trans_squeeze",
transform = function(x) {
ifelse(x > threshold,
((x - threshold) * (1 / squeeze_factor)) + threshold,
x)
},
inverse = function(x) {
ifelse(x > threshold,
((x - threshold) * squeeze_factor) + threshold,
x)
}
)
return(my_transform)
}
Next we apply that transformation to the y-axis and add a fake axis break. I've used vanilla ggplot2 code as I find the ggscatter() approach confusing.
ggplot(data, aes(Year, Score)) +
geom_point(color = "grey", shape = 21, size = 3) +
geom_smooth(method = "loess", fill = "lightgray") +
# Add fake axis lines
annotate("segment", x = -Inf, xend = -Inf,
y = c(-Inf, Inf), yend = c(24.5, 25.5)) +
# Apply transform to y-axis
scale_y_continuous(trans = my_transform(25, 10),
breaks = seq(0, 80, by = 10)) +
scale_x_continuous(limits = c(1980, 2020), oob = oob_keep) +
theme_classic() +
# Turn real y-axis line off
theme(axis.line.y = element_blank())
You might find it informative to read Hadley Wickham's view on discontinuous axes. People sometimes mock weird y-axes.
the task I have set for myself is to make a voronoi diagram of the www.politicalcompass.org chart of the currently running democratic candidates. I have coded their positions and combined points that overlap into single observations. I have used two separate ggplot extensions that create voronoi diagrams.
The problem is that politicalcompass.org's chart goes from -10 to +10 on both axes. When I try to plot the voronoi diagrams, they only extend to their original limits and not to the full range of -10 to 10 that I intend to plot. Examples and code below:
https://github.com/McCartneyAC/average_of_polls/blob/master/stupid_voronoi_one.png?raw=true
https://github.com/McCartneyAC/average_of_polls/blob/master/stupid_voronoi_two.png?raw=true
library(tidyverse)
library(ggrepel)
candidates_list_voronoi <- tribble(
~candidate,~party,~economic,~authoritarian,
"Bennet","Democratic",8.5,6,
"Biden","Democratic",5.5,3.5,
"Booker","Democratic",4,2.5,
"Buttigieg/Castro","Democratic",6.5,4.5,
"Delaney","Democratic",4,3.5,
"Gabbard","Democratic",-1.5,-1.5,
"Harris","Democratic",5,4,
"Bullock/Klobuchar","Democratic",5,5,
"Sanders","Democratic",-1.5,-1,
"Sestak","Democratic",5.5,2,
"Warren","Democratic",0.5,1,
"Williamson","Democratic",2,-1.5,
"Yang","Democratic",7,1,
"Hawkins","Green",-5,-3,
"Vohra","Libertarian",10,1.5,
"Corker/Pence","Republican",10,8.5,
"Hogan","Republican",10,8,
"Kasich","Republican",8,9,
"Trump","Republican",8.5,8.5,
"Weld","Republican",9.5,4.5
)
library(ggvoronoi)
candidates_list_voronoi %>%
ggplot(aes(economic, authoritarian, label = candidate, fill = candidate)) +
geom_voronoi(color = "black") +
geom_label_repel(fill = "#FFFFFF") +
scale_x_continuous(limits = c(-10,10))+
scale_y_continuous(limits = c(-10,10))
library(ggforce)
candidates_list_voronoi %>%
ggplot(aes(economic, authoritarian)) +
geom_voronoi_tile(aes(fill = candidate, group = -1L)) +
geom_voronoi_segment() +
geom_label_repel(aes(label = candidate)) +
scale_x_continuous(limits = c(-10,10))+
scale_y_continuous(limits = c(-10,10))
You can specify the bounding box in the outline argument in geom_voronoi (see vignette example here).
outline.df <- data.frame(x = c(-10, 10, 10, -10),
y = c(-10, -10, 10, 10))
candidates_list_voronoi %>%
ggplot(aes(economic, authoritarian, fill = candidate)) +
geom_voronoi(outline = outline.df,
color = "black")
(Leaving out the labels part since it's not critical to the question.)
Here is my raw data, first column is length and second is label:
length label
6.2 sc1__1__62000
0.5 sc1__63001__68000
2.6 sc1__75001__101000
0.7 sc1__103001__110000
....
There are 200 entries as such in the file.
I want to make an image as follow, width of each rectangle is same as corresponding length in the table:
How should I do this in R?
You need to use dplyr to create extra columns for plotting
library(dplyr)
library(ggplot2)
library(readr)
set.seed(20191234)
testdata <- tibble(
length = sample(1:10,10,replace = TRUE),
label = replicate(10,paste0(sample(letters,sample(5:15,10,replace = TRUE)),collapse = ""))
) %>%
# plot data
mutate(
xmax = cumsum(length),
xmin = dplyr::lag(xmax,default = 0),
ymin = 0,
ymax = 2,
text_x = (xmin+xmax)/2,
text_y = nchar(label)
)
Create plot using geom_rect() and geom_text()
text_y_adjust <- -0.032
testdata %>%
ggplot() +
geom_rect(aes(xmin = xmin,xmax = xmax,ymin =ymin,ymax = ymax),
alpha = 0,color = "black",size = 1) +
geom_text(aes(x = text_x,y = text_y_adjust * text_y, label = label),angle = 90) +
ylim(c(-2,2)) +
theme_void()
Note: If you modify any of height of rectangles, text_y_adjust ratio, or ylim, you need to also change other values correspondingly.
I suppose it can be done more elegant, but here is a suggestion:
#create sample dataframe
length<-c(6.2,0.5,2.6,0.7)
label<-letters[1:4]
d<-data.frame(length,label)
#calculate distance for labels
labelLength<-length[1]/2
for (i in 2:length(length)) {
labelLength[i]<-sum(length[1:(i-1)])+length[i]/2
}
#create plot
library(ggplot2)
p<-ggplot()+geom_bar(mapping=aes(x=1,y=length),
stat="identity",fill="white",color="red")+coord_flip()+
scale_y_continuous(breaks=labelLength,labels=label)+
theme(axis.text.x=element_text(angle=90,vjust=0.5,size=20),
axis.text.y=element_blank(),
axis.title.y = element_blank(),
panel.background = element_rect(fill="white"))
plot(p)