ggplot and label: How to shift the text outside? - r

I'm trying to draw map. Is there a way to write the name of the cities outside their borders? Looking for an answer i've found the package ggrepel, but it seems that it has not been implemented also for geographical maps, indeed if I write
right_join(prov2022, dataset, by = "COD_PROV") %>%
ggplot(aes(fill = `real wage`))+
geom_sf(data = ~ subset(., COD_REG == 7 | COD_REG >= 1 & COD_REG <= 3)) +
theme_void() +
theme(legend.title=element_blank())+
geom_sf_text(data = ~ subset(., COD_REG == 7 ), aes(label = city_name), size = 3) +
scale_fill_gradientn(colors = c( "#FFFFFF","#FFFF00", "#FF0000", "#000000")) +
geom_blank()+
geom_sf_text_repel(aes(label = city_name))
R answers
Error in geom_sf_text_repel(aes(label = city_name)) :
could not find function "geom_sf_text_repel"
Do you know any other way shift the label city_name from within the borders to the outside ?

You can use nudge_x and nudge_y inside geom_sf_text to move the labels an arbitrary amount:
library(ggplot2)
ggplot(df) +
geom_sf(fill = "white") +
geom_sf_text(aes(label = lab), size = 5, nudge_x = 0.05, nudge_y = 0.05)
If you prefer to control the exact position of each label, these parameters take vectorized inputs:
ggplot(df) +
geom_sf(fill = "white") +
geom_sf_text(aes(label = lab), size = 5,
nudge_x = c(0, -0.05, 0.05),
nudge_y = c(0, -0.05, 0.05))
Data used
library(sf)
df <- st_polygon(list(cbind(c(0, 1, 1, 0, 0), c(0, 0, 1, 1, 0)))) |>
list(st_point(c(0.25, 0.25)), st_point(c(0.75, 0.75))) |>
st_sfc(crs = "WGS84") |>
st_as_sf() |>
within(lab <- c("", "City 1", "City 2"))

Related

ggplot and names: is it possible to have bold characters?

I'm trying to draw map. Is there a way to write the name of the cities in bold within the map? ( i've found only the way to choose the size ). This is my script
right_join(prov2022, dataset, by = "COD_PROV") %>%
ggplot(aes(fill = `real wage`))+
geom_sf(data = ~ subset(., COD_REG == 7 | COD_REG >= 1 & COD_REG <= 3)) +
theme_void() +
theme(legend.title=element_blank())+
geom_sf_text(data = ~ subset(., COD_REG == 7 ), aes(label = city_name), size = 3) +
scale_fill_gradientn(colors = c( "#FFFFFF","#FFFF00", "#FF0000", "#000000")) +
geom_blank()
i'd like to have the city_name in bold, but in geom_sf_text(data = ~ subset(., COD_REG == 7 ), aes(label = city_name), size = 3) i cannot find the way to do it.... (increase the size is not a good option in my case bacause i've borders that not to be crossed)
You can simply use fontface = "bold" in geom_sf_text
library(ggplot2)
ggplot(df) +
geom_sf(fill = "white") +
geom_sf_text(aes(label = lab), size = 5, fontface = "bold")
Reproducible example
library(sf)
df <- st_polygon(list(cbind(c(0, 1, 1, 0, 0), c(0, 0, 1, 1, 0)))) |>
st_sfc(crs = "WGS84") |>
st_as_sf() |>
within(lab <- "Bold text")
Use plotmath expression "bold(<city_name>)" and parse = TRUE
right_join(prov2022, dataset, by = "COD_PROV") %>%
mutate(city_name = paste0("bold(\"", city_name, "\")")) %>%
ggplot(aes(fill = `real wage`))+
geom_sf(data = ~ subset(., COD_REG == 7 | COD_REG >= 1 & COD_REG <= 3)) +
theme_void() +
theme(legend.title=element_blank())+
geom_sf_text(data = ~ subset(., COD_REG == 7 ), aes(label = city_name), size = 3,
parse = TRUE) +
scale_fill_gradientn(colors = c( "#FFFFFF","#FFFF00", "#FF0000", "#000000")) +
geom_blank()
Note: cannot test because have not reproducible data.

3 layer donut chart in R

I am trying to recreate this image in R, however I am unable to work out how to have 3 layers to a donut chart - everything I find (for instance, webr::PieDonut) only allows 2. Using ggplot I am also unable to re-create it.
A MRE is:
library(ggplot2)
library(webr)
library(dplyr)
lexicon <- data.frame("Level1" = c(rep("Flavour", 11), rep("Appearance", 4)),
"Level2" = c(rep("Misc", 6), rep("Pungent", 5), rep("Colour", 4)),
"Level3" = c("Fresh", "Refreshing", "Soapy", "Minty", "Nutty", "Milky", "Peppery", "Sharp", "Horseradish", "Mustard hot", "Spicy", "Colourful"," Fresh Green", "Dark Green", "Bright Green")
)
PieDonut(lexicon, aes(Level1, Level2), title = "Salad Lexicon", showRatioDonut =FALSE, showRatioPie = FALSE)
ggplot(lexicon, aes(Level2, Level3, fill = Level1)) +
geom_col() +
scale_fill_viridis_d() +
coord_polar("y")
While the PieDonut works for 2 levels (not shown), it doesn't allow the final level to be included. The ggplot approach also does not work, as seen in the figure below.
How can I get this style of chart in R? Either with ggplot or base plotting.
I think a nice alternative is to use geom_rect here after some data manipulation. Using the fill, color, and alpha scales can help improve the differentiation of categories. I would also use geom_textpath here, though I might go for circumferential labels if there is room to do so:
lexicon %>%
mutate(top_level = Level1) %>%
pivot_longer(1:3) %>%
group_by(name, value) %>%
mutate(width = n()) %>%
unique() %>%
arrange(name) %>%
group_by(name) %>%
mutate(ymid = as.numeric(sub("\\D+", "", name)),
ymax = ymid + 0.5, ymin = ymid - 0.5,
xmin = c(0, head(cumsum(width), -1)),
xmax = cumsum(width),
xmid = (xmax + xmin) / 2) %>%
ggplot(aes(xmid, ymid, fill = top_level)) +
geom_rect(aes(xmin = xmin, xmax = xmax, ymin = ymin, ymax = ymax,
alpha = name, color = top_level)) +
geomtextpath::geom_textpath(aes(y = ymid + 0.25, label = value,
group = value)) +
scale_alpha_manual(values = c(1, 0.3, 0.1)) +
scale_fill_manual(values = c("#cd9900", "#00817e")) +
scale_colour_manual(values = c("#cd9900", "#00817e")) +
scale_y_continuous(limits = c(-0.5, 3.6)) +
coord_polar() +
theme_void() +
theme(legend.position = "none")
One option would be to reeshape your data to long and do some manual aggregating before passing to ggplot. Additionally I use geomtextpath::geom_textpath to add the labels:
library(ggplot2)
library(dplyr)
library(geomtextpath)
lexicon <- data.frame("Level1" = c(rep("Flavour", 11), rep("Appearance", 4)),
"Level2" = c(rep("Misc", 6), rep("Pungent", 5), rep("Colour", 4)),
"Level3" = c("Fresh", "Refreshing", "Soapy", "Minty", "Nutty", "Milky", "Peppery", "Sharp", "Horseradish", "Mustard hot", "Spicy", "Colourful"," Fresh Green", "Dark Green", "Bright Green")
)
lexicon_long <- lexicon |>
mutate(fill = Level1) |>
tidyr::pivot_longer(-fill, names_to = "level", values_to = "label") |>
mutate(label = forcats::fct_inorder(label)) |>
count(fill, level, label) |>
group_by(level) |>
mutate(pct = n / sum(n))
ggplot(lexicon_long, aes(level, pct, fill = fill)) +
geom_col(color = "white") +
geom_textpath(aes(label = label, group = label),
position = position_stack(vjust = .5),
upright = TRUE, hjust = .5, size = 3
) +
scale_fill_viridis_d() +
coord_polar("y") +
theme_void() +
guides(fill = "none")

How can I add an annotation to a faceted ggplot (with a log scale) outside the plot area

I'm looking to add some annotations (ideally a text and an arrow) to a faceted ggplot outside the plot area.
What's that, you say? Hasn't someone asked something similar here, here and here? Well yes. But none of them were trying to do this below an x-axis with a log scale.
With the exception of this amazing answer by #Z.Lin — but that involved a specific package and I'm looking for a more generic solution.
At first glance this would appear to be a very niche question, but for those of you familiar with forest plots this may tweak some interest.
Firstly, some context... I'm interested in presenting the results of a coxph model using a forest plot in a publication. My goal here is to take the results of a model (literally a standalone coxph object) and use it to produce output that is customisable (gotta match the style guide) and helps translate the findings for an audience that might not be au fait with the technical details of hazard ratios. Hence the annotations and directional arrows.
Before you start dropping links to r packages/functions that could help do this... here are those that I've tried so far:
ggforestplot — this package produces lovely customisable forest plots (if you are using odds ratios), but it hard codes a geom_vline at zero which doesn't help for HR's
ggforest — this package is a nerd paradise of detail, but good luck a) editing the variable names and b) trying to theme it (I mentioned earlier that I'm working with a coxph object, what I didn't mention was that the varnames are ugly — they need to be changed for a punter to understand what we're trying to communicate)
finalfit offers a great workflow and its hr_plot kicks out some informative output, but it doesn't play nice if you've already got a coxph object and you just want to plot it
So... backstory out of the way. I've created my own framework for a forest plot below to which I'd love to add — in the space below the x-axis labels and the x-axis title — two annotations that help interpret the result. My current code struggles with:
repeating the code under each facet (this is something I'm trying to avoid)
mirroring the annotations of either side of the geom_vline with a log scale
Any advice anyone might have would be much appreciated... I've added a reproducible example below.
## LOAD REQUIRED PACKAGES
library(tidyverse)
library(survival)
library(broom)
library(ggforce)
library(ggplot2)
## PREP DATA
model_data <- lung %>%
mutate(inst_cat = case_when(
inst %% 2 == 0 ~ 2,
TRUE ~ 1)) %>%
mutate(pat.karno_cat = case_when(
pat.karno < 75 ~ 2,
TRUE ~ 1)) %>%
mutate(ph.karno_cat = case_when(
ph.karno < 75 ~ 2,
TRUE ~ 1)) %>%
mutate(wt.loss_cat = case_when(
wt.loss > 15 ~ 2,
TRUE ~ 1)) %>%
mutate(meal.cal_cat = case_when(
meal.cal > 900 ~ 2,
TRUE ~ 1))
coxph_model <- coxph(
Surv(time, status) ~
sex +
inst_cat +
wt.loss_cat +
meal.cal_cat +
pat.karno_cat +
ph.karno_cat,
data = model_data)
## PREP DATA
plot_data <- coxph_model %>%
broom::tidy(
exponentiate = TRUE,
conf.int = TRUE,
conf.level = 0.95) %>%
mutate(stat_sig = case_when(
p.value < 0.05 ~ "p < 0.05",
TRUE ~ "N.S.")) %>%
mutate(group = case_when(
term == "sex" ~ "gender",
term == "inst_cat" ~ "site",
term == "pat.karno_cat" ~ "outcomes",
term == "ph.karno_cat" ~ "outcomes",
term == "meal.cal_cat" ~ "outcomes",
term == "wt.loss_cat" ~ "outcomes"))
## PLOT FOREST PLOT
forest_plot <- plot_data %>%
ggplot() +
aes(
x = estimate,
y = term,
colour = stat_sig) +
geom_vline(
aes(xintercept = 1),
linetype = 2
) +
geom_point(
shape = 15,
size = 4
) +
geom_linerange(
xmin = (plot_data$conf.low),
xmax = (plot_data$conf.high)
) +
scale_colour_manual(
values = c(
"N.S." = "black",
"p < 0.05" = "red")
) +
annotate(
"text",
x = 0.45,
y = -0.2,
col="red",
label = "indicates y",
) +
annotate(
"text",
x = 1.5,
y = -0.2,
col="red",
label = "indicates y",
) +
labs(
y = "",
x = "Hazard ratio") +
coord_trans(x = "log10") +
scale_x_continuous(
breaks = scales::log_breaks(n = 7),
limits = c(0.1,10)) +
ggforce::facet_col(
facets = ~group,
scales = "free_y",
space = "free"
) +
theme(
legend.position = "bottom",
legend.title = element_blank(),
strip.text = element_text(hjust = 0),
axis.title.x = element_text(margin = margin(t = 25, r = 0, b = 0, l = 0))
)
Created on 2022-05-10 by the reprex package (v2.0.1)
I think I would use annotation_custom here. This requires standard coord_cartesian with clip = 'off', but it should be easy to re-jig your x axis to use scale_x_log10
plot_data %>%
ggplot() +
aes(
x = estimate,
y = term,
colour = stat_sig) +
geom_vline(
aes(xintercept = 1),
linetype = 2
) +
geom_point(
shape = 15,
size = 4
) +
geom_linerange(
xmin = (log10(plot_data$conf.low)),
xmax = (log10(plot_data$conf.high))
) +
scale_colour_manual(
values = c(
"N.S." = "black",
"p < 0.05" = "red")
) +
annotation_custom(
grid::textGrob(
x = unit(0.4, 'npc'),
y = unit(-7.5, 'mm'),
label = "indicates yada",
gp = grid::gpar(col = 'red', vjust = 0.5, hjust = 0.5))
) +
annotation_custom(
grid::textGrob(
x = unit(0.6, 'npc'),
y = unit(-7.5, 'mm'),
label = "indicates bada",
gp = grid::gpar(col = 'blue', vjust = 0.5, hjust = 0.5))
) +
annotation_custom(
grid::linesGrob(
x = unit(c(0.49, 0.25), 'npc'),
y = unit(c(-10, -10), 'mm'),
arrow = arrow(length = unit(3, 'mm')),
gp = grid::gpar(col = 'red'))
) +
annotation_custom(
grid::linesGrob(
x = unit(c(0.51, 0.75), 'npc'),
y = unit(c(-10, -10), 'mm'),
arrow = arrow(length = unit(3, 'mm')),
gp = grid::gpar(col = 'blue'))
) +
labs(
y = "",
x = "Hazard ratio") +
scale_x_log10(
breaks = c(0.1, 0.3, 1, 3, 10),
limits = c(0.1,10)) +
ggforce::facet_col(
facets = ~group,
scales = "free_y",
space = "free"
) +
coord_cartesian(clip = 'off') +
theme(
legend.position = "bottom",
legend.title = element_blank(),
strip.text = element_text(hjust = 0),
axis.title.x = element_text(margin = margin(t = 25, r = 0, b = 0, l = 0)),
panel.spacing.y = (unit(15, 'mm'))
)

R label with commas but no decimals

My goal is to produce labels with commas, but no decimals. Let's say I have a ggplot with the following section:
geom_text(aes(y = var,
label = scales::comma(round(var))), hjust = 0, nudge_y = 300 )
This is almost what I need. It gives me the commas, but has a decimal. I have seen here (axis labels with comma but no decimals ggplot) that comma_format() could be good, but I think the label in my case needs a data argument, which comma_format() does not take. What can I do?
Update:
As an example of when this problem occurs, see the following, which uses gganimate and has a lot more going on. Code derived from Jon Spring's answer at Animated sorted bar chart with bars overtaking each other
library(gapminder)
library(gganimate)
library(tidyverse)
gap_smoother <- gapminder %>%
filter(continent == "Asia") %>%
group_by(country) %>%
complete(year = full_seq(year, 1)) %>%
mutate(gdpPercap = spline(x = year, y = gdpPercap, xout = year)$y) %>%
group_by(year) %>%
mutate(rank = min_rank(-gdpPercap) * 1) %>%
ungroup() %>%
group_by(country) %>%
complete(year = full_seq(year, .5)) %>%
mutate(gdpPercap = spline(x = year, y = gdpPercap, xout = year)$y) %>%
mutate(rank = approx(x = year, y = rank, xout = year)$y) %>%
ungroup() %>%
arrange(country,year)
gap_smoother2 <- gap_smoother %>% filter(year<=2007 & year>=1999)
gap_smoother3 <- gap_smoother2 %<>% filter(rank<=8)
p <- ggplot(gap_smoother3, aes(rank, group = country,
fill = as.factor(country), color = as.factor(country))) +
geom_tile(aes(y = gdpPercap/2,
height = gdpPercap,
width = 0.9), alpha = 0.8, color = NA) +
geom_text(aes(y = 0, label = paste(country, " ")), vjust = 0.2, hjust = 1) +
geom_text(aes(y = gdpPercap,
label = scales::comma(round(gdpPercap))), hjust = 0, nudge_y = 300 ) +
coord_flip(clip = "off", expand = FALSE) +
scale_x_reverse() +
guides(color = FALSE, fill = FALSE) +
labs(title='{closest_state %>% as.numeric %>% floor}',
x = "", y = "GFP per capita") +
theme(plot.title = element_text(hjust = 0, size = 22),
axis.ticks.y = element_blank(), # These relate to the axes post-flip
axis.text.y = element_blank(), # These relate to the axes post-flip
plot.margin = margin(1,1,1,4, "cm")) +
transition_states(year, transition_length = 1, state_length = 0) +
enter_grow() +
exit_shrink() +
ease_aes('linear')
animate(p, fps = 2, duration = 5, width = 600, height = 500)
In addition to the solution provided by #drf, you need to add scale_y_continuous(scales::comma) to your ggplot commands. But put it before the coord_flip function.
p <- ggplot(gap_smoother3, aes(rank, group = country,
fill = as.factor(country), color = as.factor(country))) +
geom_tile(aes(y = gdpPercap/2,
height = gdpPercap,
width = 0.9), alpha = 0.8, color = NA) +
geom_text(aes(y = gdpPercap,
label = scales::comma(round(gdpPercap), accuracy=1)),
hjust = 0, nudge_y = 300 ) +
scale_y_continuous(labels = scales::comma) +
... etc.

Use geom_curve() to make an arrow without x and xend argument

I have a seemingly small problem. I want to plot an arrow with geom_curve() on top of a bar chart within ggplot. The goal is to point out a single value from the bar chart.
In every other case, in order to plot the arrow with geom_curve(), we need to fill in the arguments x, xend, y and x yend. However, when plotting a bar chart with geom_bar(), theaes(x = x) is not specified. My question is how are we able to plot the arrow, even without specifying the x arguments.
Some sample code can be found below. How to use geom_segment() can be found here. Any help would be much appreciated
library(tidyverse)
library(tidyquant)
tbl <- tibble(
Var1 = c(0, 2, 4, 6, 8),
pct = c(rep(0.2, 5))
) %>%
mutate(Var1 = Var1 %>% as_factor() %>% fct_rev())
tbl %>%
ggplot(aes(x = "", y = pct, fill = Var1)) +
geom_bar(stat = "identity") +
coord_flip() +
scale_fill_tq() +
geom_text(aes(label = if_else(Var1 == 0, "20 %", "")), nudge_y = -0.025, nudge_x = 0.55, size = 5, color = "#2C3E50")
Are you looking for something like that ? Or did I misunderstand your question ?
In ggplot2, even if you don't have x axis values, each bar of a bargraph can be associated to a number starting at 1.
So, if you have two bar on your graph, their x position are 1 and 2.
Here, you have only one bar, you can use that to add your arrow slight up to 1 (here I choose 1.50)
tbl %>%
ggplot(aes(x = "", y = pct, fill = Var1)) +
geom_bar(stat = "identity") +
coord_flip() +
#scale_fill_tq() +
geom_text(aes(label = if_else(Var1 == 0, "20 %", "")), nudge_y = -0.025, nudge_x = 0.55, size = 5, color = "#2C3E50")+
geom_segment(x = 1.50, xend = 1.50, y = 0.1, yend = 0.75, arrow = arrow(length = unit(0.03, "npc")))
Does it answer your question ?
Thanks to #dc37, I got the arrow at the right place and pointing to a specific part of the bar chart.
tbl %>%
ggplot(aes(x = "", y = pct, fill = Var1)) +
geom_bar(stat = "identity") +
coord_flip() +
geom_text(aes(label = if_else(Var1 == 0, "20 %", "")), nudge_y = -0.05, nudge_x = 0.51, size = 5, color = "#2C3E50") +
geom_curve(aes(x = 1.51, xend = 1.46, y = 0.1, yend = 0.05), curvature = 1, color = "#2C3E50", arrow = arrow(length = unit(0.02, "npc")))

Resources