Unit-independent position_nudge - r

I would like to apply a position_nudge to an object, but it should always be a certain distance (e.g. in "cm") rather than relative to the scale of the measured variable.
data <- data.frame(
name=c("de","gb","cn","ir","ru") ,
value=c(3,12,5,18,45)*1
)
ggplot(data,
aes(x=name, y=value)) +
geom_bar(stat = "identity") +
geom_text(aes(y = 0,
label = paste0(name,value)),
position = position_nudge(y = -12)) +
coord_cartesian(ylim = c(0, 50), # This focuses the x-axis on the range of interest
clip = 'off') + # This keeps the labels from disappearing
theme(plot.margin = unit(c(1,1,1,1), "lines"))
When changing the scale of the variable, that adjustment should not need to be made in the position_nudge argument, e.g.
factor = 100
data <- data.frame(
name=c("de","gb","cn","ir","ru") ,
value=c(3,12,5,18,45)*factor
)
ggplot(data,
aes(x=name, y=value)) +
geom_bar(stat = "identity") +
geom_text(aes(y = 0,
label = paste0(name,value)),
position = position_nudge(y = -12)) +
coord_cartesian(ylim = c(0, 50*factor), # This focuses the x-axis on the range of interest
clip = 'off') + # This keeps the labels from disappearing
theme(plot.margin = unit(c(1,1,1,1), "lines"))
Currently, this does not work, so that I need to manually change -12 to -1200 to achieve this:
This is of course only a short reproducible example, the actual use-case is placing country flags as x-axis labels below the plot.
The final product will look somewhat like this, but currently requires updating the nudges each time the y-values change:
Thank you very much!

The easiest "hack" is to make this two plots and bind them with patchwork or cowplot. If you try it differently, you'd soon get into deep grid ... trouble.
Related
baptiste on github
baptiste on stackoverflow
Sandy Muspratt's answer
The easy way:
library(ggplot2)
library(patchwork)
foo <- data.frame(
name=c("de","gb","cn","ir","ru") ,
value=c(3,12,5,18,45)*1
)
foo_label = paste(foo$name, foo$value)
p <- ggplot(foo, aes(x=name, y=value)) +
geom_blank() # essential, so that both plots have same scaling
p_1 <-
p + geom_col() +
coord_cartesian(ylim = c(0, 50),clip = 'off') +
theme(plot.margin = margin())
p_text <-
p + annotate("text", label = foo_label, x = 1:5, y = 0, col="red") +
theme_void() +
coord_cartesian(clip = "off") +
theme(plot.margin = margin(1,0,1,0, unit = "lines"))
p_1/p_text + plot_layout(heights = c(1,0)) #this is a workaround to make the height of the text plot minimal!
You can then of course annotate with anything.

For your stated goal, the ggtext library may be more appropriate, as it allows you to embed images directly into the x axis labels. See also here for another example.
library(ggplot2)
library(ggtext)
labels <- c(
setosa = "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/8/86/Iris_setosa.JPG/180px-Iris_setosa.JPG'
width='100' /><br>*I. setosa*",
virginica = "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/3/38/Iris_virginica_-_NRCS.jpg/320px-Iris_virginica_-_NRCS.jpg'
width='100' /><br>*I. virginica*",
versicolor = "<img src='https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/20140427Iris_versicolor1.jpg/320px-20140427Iris_versicolor1.jpg'
width='100' /><br>*I. versicolor*"
)
ggplot(iris, aes(Species, Sepal.Width)) +
geom_boxplot() +
scale_x_discrete(
name = NULL,
labels = labels
) +
theme(
axis.text.x = element_markdown(color = "black", size = 11)
)

Related

Add small arrows instead of axis to UMAP

More often I see in publications that instead of printing the UMAP axis in scRNAseq experiments (or even t-SNE or PCA) they just add two small arrows in the bottom left corner.
Something like this:
I really like the aesthetics of it but I don´t know how to replicate this in R. I guess this is normally done separately with some image editor but it can probably be done with ggplot2 package to make it more reproducible.
So far I only got the arrows in the axis:
x <- data.frame(UMAP1=rnorm(300),UMAP2=rnorm(300))
ggplot(x, aes(UMAP1,UMAP2)) + geom_point() + theme_minimal() +
theme(axis.line = element_line(arrow = arrow(type='closed',
length = unit(10,'pt'))))
But I don't know how to make them smaller and with the title underneath. Does anyone have any suggestions on how to do this?
In code below, adjust unit(3, "cm") and hjust = 0 to taste.
Disclaimer: I wrote ggh4x.
library(ggplot2)
axis <- ggh4x::guide_axis_truncated(
trunc_lower = unit(0, "npc"),
trunc_upper = unit(3, "cm")
)
x <- data.frame(UMAP1=rnorm(300),UMAP2=rnorm(300))
ggplot(x, aes(UMAP1, UMAP2)) +
geom_point() +
guides(x = axis, y = axis) +
theme(axis.line = element_line(arrow = arrow()),
axis.title = element_text(hjust = 0))
Created on 2022-12-07 by the reprex package (v2.0.1)
Optionally, add the code below if you want to get rid of the ticks and labels (which don't make any sense in terms of UMAP/tSNE anyway)
scale_x_continuous(breaks = NULL) +
scale_y_continuous(breaks = NULL)
I'd suggest faking it with an annotation:
library(dplyr); library(umap); library(ggplot2)
arr <- list(x = -10, y = -15, x_len = 5, y_len = 5)
ggplot(storms_umap_extract, aes(x,y, color = category, alpha = wind)) +
geom_point() +
annotate("segment",
x = arr$x, xend = arr$x + c(arr$x_len, 0),
y = arr$y, yend = arr$y + c(0, arr$y_len),
arrow = arrow(type = "closed", length = unit(10, 'pt'))) +
theme_void()
Here's the umap data:
storms_umap <- storms |>
select(lat,long, wind, pressure) |>
umap() # this took about a minute to run
storms_umap_extract <- tibble(
x = storms_umap$layout[,1],
y = storms_umap$layout[,2],
wind = storms_umap$data[,3],
category = storms$category
)

Bunched up x axis ticks on multi panelled plot in ggplot

I am attempting to make a multi-panelled plot from three individual plots (see images).However, I am unable to rectify the bunched x-axis tick labels when the plots are in the multi-panel format. Following is the script for the individual plots and the multi-panel:
Individual Plot:
NewDat [[60]]
EstRes <- NewDat [[60]]
EstResPlt = ggplot(EstRes,aes(Distance3, `newBa`))+geom_line() + scale_x_continuous(n.breaks = 10, limits = c(0, 3500))+ scale_y_continuous(n.breaks = 10, limits = c(0,25))+ xlab("Distance from Core (μm)") + ylab("Ba:Ca concentration(μmol:mol)") + geom_hline(yintercept=2.25, linetype="dashed", color = "red")+ geom_vline(xintercept = 1193.9, linetype="dashed", color = "grey")+ geom_vline(xintercept = 1965.5, linetype="dashed", color = "grey") + geom_vline(xintercept = 2616.9, linetype="dashed", color = "grey") + geom_vline(xintercept = 3202.8, linetype="dashed", color = "grey")+ geom_vline(xintercept = 3698.9, linetype="dashed", color = "grey")
EstResPlt
Multi-panel plot:
MultiP <- grid.arrange(MigrPlt,OcResPlt,EstResPlt, nrow =1)
I have attempted to include:
MultiP <- grid.arrange(MigrPlt,OcResPlt,EstResPlt, nrow =1)+
theme(axis.text.x = element_text (angle = 45)) )
MultiP
but have only received errors. It's not necessary for all tick marks to be included. An initial, mid and end value is sufficient and therefore they would not need to all be included or angled. I'm just not sure how to do this. Assistance would be much appreciated.
There are several options to resolve the crowded axes. Let's consider the following example which parallels your case. The default labelling strategy wouldn't overcrowd the x-axis.
library(ggplot2)
library(patchwork)
library(scales)
df <- data.frame(
x = seq(0, 3200, by = 20),
y = cumsum(rnorm(161))
)
p <- ggplot(df, aes(x, y)) +
geom_line()
(p + p + p) / p &
scale_x_continuous(
name = "Distance (um)"
)
However, because you've given n.breaks = 10 to the scale, it becomes crowded. So a simple solution would just be to remove that.
(p + p + p) / p &
scale_x_continuous(
n.breaks = 10,
name = "Distance (um)"
)
Alternatively, you could convert the micrometers to millimeters, which makes the labels less wide.
(p + p + p) / p &
scale_x_continuous(
n.breaks = 10,
labels = label_number(scale = 1e-3, accuracy = 0.1),
name = "Distance (mm)"
)
Yet another alternative is to put breaks only every n units, in the case below, a 1000. This happens to coincide with omitting n.breaks = 10 by chance.
(p + p + p) / p &
scale_x_continuous(
breaks = breaks_width(1000),
name = "Distance (um)"
)
Created on 2021-11-02 by the reprex package (v2.0.1)
I thought it would be better to show with an example.
What I mean was, you made MigrPlt, OcResPlt, EstResPlt each with ggplot() +...... For plot that you want to rotate x axis, add + theme(axis.text.x = element_text (angle = 45)).
For example, in iris data, only rotate x axis text for a like
a <- ggplot(iris, aes(Sepal.Width, Sepal.Length)) +
geom_point() +
theme(axis.text.x = element_text (angle = 45))
b <- ggplot(iris, aes(Petal.Width, Petal.Length)) +
geom_point()
gridExtra::grid.arrange(a,b, nrow = 1)

How to use your own image for geom_point in gganimate?

I am trying to use my own image for geom_point, something I can just read in. I am aware geom_point allows you to choose many shapes (well over 300) by simply writing shape = 243 but I want my own image such as a logo.
When I have not specified color = factor(Name) then it works as expected. When I do specify the colour of the line then the image becomes a solid single colour. I want this line to be coloured so is there any way around this? Thanks!
library(gganimate)
library(gifski)
library(png)
library(ggimage)
Step <- 1:50
Name <- rep("A",50)
Image <- rep(c("https://jeroenooms.github.io/images/frink.png"),50)
Value <- runif(50,0,10)
Final <- data.frame(Step, Name, Value, Image)
a <- ggplot(Final, aes(x = Step, y = Value, group = Name, color = factor(Name))) +
geom_line(size=1) +
geom_image(aes(image=Image)) +
transition_reveal(Step) +
coord_cartesian(clip = 'off') +
theme_minimal() +
theme(plot.margin = margin(5.5, 40, 5.5, 5.5)) +
theme(legend.position = "none")
options(gganimate.dev_args = list(width = 7, height = 6, units = 'in', res=100))
animate(a, nframes = 100)
Is this what your are looking for ?
I Just changed the color = factor(Name) position to geom_line statement.
If you use color = factor(Name) with ggplot in first row, it will affect to whole plot. So you should take care when using this statement.
a <- ggplot(Final, aes(x = Step, y = Value, group = Name)) +
geom_line(size=1, aes(color = factor(Name))) +
geom_image(aes(image=Image)) +
transition_reveal(Step) +
coord_cartesian(clip = 'off') +
theme_minimal() +
theme(plot.margin = margin(5.5, 40, 5.5, 5.5)) +
theme(legend.position = "none")
For convenience, i captured the picture .

ggplot2 add data from additional data frame next to plot

I would like to be able to extend my boxplots with additional information. Here is a working example for ggplot2:
library(ggplot2)
ToothGrowth$dose <- as.factor(ToothGrowth$dose)
# Basic box plot
p <- ggplot(ToothGrowth, aes(x=dose, y=len)) +
geom_boxplot()
# Rotate the box plot
p + coord_flip()
I would like to add additional information from a separate data frame. For example:
extra <- data.frame(dose=factor(c(0.5,1,2)), label=c("Label1", "Label2", "Label3"), n=c("n=42","n=52","n=35"))
> extra
dose label n
1 0.5 Label1 n=42
2 1 Label2 n=52
3 2 Label3 n=35
I would like to create the following figure where the information to each dose (factor) is outside the plot and aligns with each of the dose levels (I made this in powerpoint as an example):
EDIT:
I would like to ask advice for an extension of the initial question.
What about this extension where I use fill to split up dose by the two groups?
ToothGrowth$dose <- as.factor(ToothGrowth$dose)
ToothGrowth$group <- head(rep(1:2, 100), dim(ToothGrowth)[1])
ToothGrowth$group <- factor(ToothGrowth$group)
p <- ggplot(ToothGrowth, aes(x=dose, y=len, fill=group)) +
geom_boxplot()
# Rotate the box plot
p + coord_flip()
extra <- data.frame(
dose=factor(rep(c(0.5,1,2), each=2)),
group=factor(rep(c(1:2), 3)),
label=c("Label1A", "Label1B", "Label2A", "Label2B", "Label3A", "Label3B"),
n=c("n=12","n=30","n=20", "n=32","n=15","n=20")
)
Is it possible to align data from the new data frame (extra, 6 rows) with each of the dose/group combinations?
We can use geom_text with clip = "off" inside coord_flip:
ggplot(ToothGrowth, aes(x=dose, y=len)) +
geom_boxplot() +
geom_text(
y = max(ToothGrowth$len) * 1.1,
data = extra,
aes(x = dose, label = sprintf("%s\n%s", label, n)),
hjust = 0) +
coord_flip(clip = "off") +
theme(plot.margin = unit(c(1, 5, 0.5, 0.5), "lines"))
Explanation: We place text outside of the plot area with geom_text and disable clipping with clip = "off" inside coord_flip. Lastly, we increase the plot margin to accommodate the additional labels. You can adjust the vertical y position in the margin (so the horizontal position in the plot because of the coordinate flip) by changing the factor in y = max(ToothGrowth$len) * 1.1.
In response to your edit, here is a possibility
extra <- data.frame(
dose=factor(rep(c(0.5,1,2), each=2)),
group=factor(rep(c(1:2), 3)),
label=c("Label1A", "Label1B", "Label2A", "Label2B", "Label3A", "Label3B"),
n=c("n=12","n=30","n=20", "n=32","n=15","n=20")
)
library(tidyverse)
ToothGrowth %>%
mutate(
dose = as.factor(dose),
group = as.factor(rep(1:2, nrow(ToothGrowth) / 2))) %>%
ggplot(aes(x = dose, y = len, fill = group)) +
geom_boxplot(position = position_dodge(width = 1)) +
geom_text(
data = extra %>%
mutate(
dose = as.factor(dose),
group = as.factor(group),
ymax = max(ToothGrowth$len) * 1.1),
aes(x = dose, y = ymax, label = sprintf("%s\n%s", label, n)),
position = position_dodge(width = 1),
size = 3,
hjust = 0) +
coord_flip(clip = "off", ylim = c(0, max(ToothGrowth$len))) +
theme(
plot.margin = unit(c(1, 5, 0.5, 0.5), "lines"),
legend.position = "bottom")
A few comments:
We ensure that labels match the dodged bars by using position_dodge(with = 1) inside geom_text and geom_boxplot.
It seems that position_dodge does not like a global y (outside of aes). So we include the y position for the labels in extra and use it inside aes. As a result, we need to explicitly limit the range of the y axis. We can do that inside coord_flip with ylim = c(0, max(ToothGrowth$len)).

R - ggplot2: geom_area loses its fill if limits are defined to max and min values from a data.frame

I am trying to reproduce a sparkline with ggplot2 like the one at the bottom of this image:
Using the following code I get the result displayed at the end of the code.
Note: My actual data.frame has only 2 rows. Therefore the result looks like a single line.
# Create sparkline for MM monthly
# sparkline(dailyMM2.aggregate.monthly$Count, type = 'line')
p <- ggplot(dailyMM2.aggregate.monthly, aes(x=seq(1:nrow(dailyMM2.aggregate.monthly)), y=Count)) +
geom_area(fill="#83CAF5") +
geom_line(color = "#2C85BB", size = 1.5) +
scale_x_continuous(expand=c(0,0)) +
scale_y_continuous(expand=c(0,0))
p + theme(axis.line=element_blank(),axis.text.x=element_blank(),
axis.text.y=element_blank(),axis.ticks=element_blank(),
axis.title.x=element_blank(),
axis.title.y=element_blank(),legend.position="none",
panel.background=element_blank(),panel.border=element_blank(),panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),plot.background=element_blank())
However, as I try to only show trends with the sparkline and, therefore, absolute values aren't relevant for me, I have to adapt the config of the ggplot to limit the visible area between the min and max of my axis.y. I do it using the limits option:
# Create sparkline for MM monthly
# sparkline(dailyMM2.aggregate.monthly$Count, type = 'line')
p <- ggplot(dailyMM2.aggregate.monthly, aes(x=seq(1:nrow(dailyMM2.aggregate.monthly)), y=Count)) +
geom_area(fill="#83CAF5") +
geom_line(color = "#2C85BB", size = 1.5) +
scale_x_continuous(expand=c(0,0)) +
scale_y_continuous(expand=c(0,0), limits = c(min(dailyMM2.aggregate.monthly$Count)-100, max(dailyMM2.aggregate.monthly$Count)+100))
p + theme(axis.line=element_blank(),axis.text.x=element_blank(),
axis.text.y=element_blank(),axis.ticks=element_blank(),
axis.title.x=element_blank(),
axis.title.y=element_blank(),legend.position="none",
panel.background=element_blank(),panel.border=element_blank(),panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),plot.background=element_blank())
However, the result is not like expected, as the whole geom_area's fill dissapears, as shown in the folllowing image:
Can anyone shed light why this behaviour is happening and maybe help me with a proper way to solve this problem?
If you check ?geom_area you will note that the minimum is fixed to 0. It might be easier to use geom_ribbon. It has a ymin aesthetic. Set the maximum y value using limits or coord_cartesian.
library(reshape2)
library(ggplot2)
# Some data
df=data.frame(year = rep(2010:2014, each = 4),
quarter=rep(c("Q1","Q2","Q3","Q4"),5),
da=c(46,47,51,50,56.3,53.6,55.8,58.9,61.0,63,58.8,62.5,59.5,61.7,60.6,63.9,68.4,62.2,62,70.4))
df.m <- melt(data = df, id.vars = c("year", "quarter"))
ymin <- min(df.m$value)
ymax <- max(df.m$value)
ggplot(data = df.m, aes(x = interaction(quarter,year), ymax = value, group = variable)) +
geom_ribbon(aes(ymin = ymin), fill = "#83CAF5") +
geom_line(aes(y = value), size = 1.5, colour = "#2C85BB") +
coord_cartesian(ylim = c(ymin, ymax)) +
scale_y_continuous(expand = c(0,0)) +
scale_x_discrete(expand = c(0,0)) +
theme_void()

Resources