Cannot conditionally make axis labels bold in ggplot - r

I am trying to make select axis labels bold, based on a conditional from a different column. In the code below, if Signif equals 1, then the Predictor axis text should be bold. In addition, the segments should appear in the order of Values increasing value.
However, this code is not changing any of the the axis texts to bold.
library(tidyverse)
library(ggtext)
library(glue)
df <- tibble(Predictor = c("Blue","Red","Green","Yellow"),
Value = c(1,3,2,4),
Signif = c(0,1,0,1))
df <- df %>% mutate(Predictor = ifelse(Signif==1,
glue("<span style = 'font-weight:bold'>{Predictor}</span>"),
glue("<span style = 'font-weight:plain'>{Predictor}</span>"))
)
df %>%
arrange(desc(Value)) %>%
mutate(Predictor=factor(Predictor,
levels=df$Predictor[order(df$Value)])) %>%
ggplot(aes(x=Predictor, y=Value)) +
geom_segment(aes(xend=Predictor, yend=0)) +
theme(axis.text.x = element_markdown())
If instead I use element_text() in the last line, and skip the mutate to markdown step above:
df %>%
arrange(desc(Value)) %>%
mutate(Predictor=factor(Predictor,
levels=df$Predictor[order(df$Value)])) %>%
ggplot(aes(x=Predictor, y=Value)) +
geom_segment(aes(xend=Predictor, yend=0)) +
theme(axis.text.x = element_text(face = if_else(df$Signif==1, "bold", "plain")))
It bolds the 2nd and 4th axis label, which corresponds to the Signif equals 1 in the original df.
How can I get the correct axis text labels to appear in bold?

I would’ve expected your code to work honestly, but you can use <b> instead of <span style...>:
library(tidyverse)
library(ggtext)
library(glue)
df <- df %>%
mutate(Predictor = ifelse(Signif==1,
glue("<b>{Predictor}</b>"),
Predictor))
df %>%
arrange(desc(Value)) %>%
mutate(Predictor=factor(Predictor,
levels=df$Predictor[order(df$Value)])) %>%
ggplot(aes(x=Predictor, y=Value)) +
geom_segment(aes(xend=Predictor, yend=0)) +
theme(axis.text.x = element_markdown())

The issue is that ggtext currently supports only a limited set of CSS properties. From the docs
The CSS properties color, font-size, and font-family are currently supported.
But if you just want to have bold text the answer by #zephryl is the way to go or as a second option use markdown, i.e. wrap inside **:
library(ggplot2)
library(dplyr)
library(ggtext)
library(glue)
df <- df %>%
mutate(Predictor = ifelse(Signif == 1,
glue("**{Predictor}**"),
Predictor
))
df %>%
arrange(desc(Value)) %>%
mutate(Predictor = factor(Predictor,
levels = df$Predictor[order(df$Value)]
)) %>%
ggplot(aes(x = Predictor, y = Value)) +
geom_segment(aes(xend = Predictor, yend = 0)) +
theme(axis.text.x = element_markdown())

All nice answers. The deeper underlying issue however is (not exactly) "hidden" in the warning that comes when you use a vector in a theme element (see plot below).
Your original plot code would work if you would first rearrange your data frame (and re-assign it!) - see code below. I do not encourage that - ggtext::element_markdown was designed exactly with the idea in mind to avoid the use of vectors in theme.
library(tidyverse)
df <- tibble(Predictor = c("Blue","Red","Green","Yellow"),
Value = c(1,3,2,4),
Signif = c(0,1,0,1))
df <- arrange(df, Predictor)
df %>%
arrange(desc(Value)) %>%
mutate(Predictor=factor(Predictor,
levels=df$Predictor[order(df$Value)])) %>%
ggplot(aes(x=Predictor, y=Value)) +
geom_segment(aes(xend=Predictor, yend=0)) +
theme(axis.text.x = element_text(face = if_else(df$Signif==1, "bold", "plain")))
#> Warning: Vectorized input to `element_text()` is not officially supported.
#> ℹ Results may be unexpected or may change in future versions of ggplot2.
Created on 2023-01-29 with reprex v2.0.2

Related

ggplot: No legend when using scale_fill_brewer with geom_contour_filled

I've plotted a specific set of meteorological data using ggplot as described in the R code below. However, when I use scale_fill_brewer to specific the fill color, a legend does not appear.
What changes are necessary for the legend to appear?
library(tidyverse)
library(lubridate)
library(ggplot2)
library(RColorBrewer)
qurl <- "https://www.geo.fu-berlin.de/met/ag/strat/produkte/qbo/singapore.dat"
sing <- read_table(qurl, skip=4)
# the data file adds a 100mb data row starting in 1997 increasing the number of rows per year from
# 14 to 15. So, one calcuation must be applied to rnum <140 and a different to rnum >140.
sing2 <- sing %>% separate(1,into=c('hpa','JAN'),sep='\\s+') %>% drop_na() %>%
subset(hpa != 'hPa') %>%
mutate(rnum = row_number(),
hpa=as.integer(hpa)) %>%
mutate(year = case_when(rnum <=140 ~ 1987 + floor(rnum/14), # the last year with 14 rows of data
rnum >=141 ~ 1987 + floor(rnum+10/15))) %>% # the first year with 15 rows of data
relocate(year, .before='hpa') %>% arrange(year,hpa) %>%
pivot_longer(cols=3:14, names_to='month',values_to='qbo') %>%
mutate(date=ymd(paste0(year,'-',month,'-15')),
hpa=as.integer(hpa),
qbo=as.numeric(qbo))
sing2 <- sing %>% separate(1,into=c('hpa','JAN'),sep='\\s+') %>% drop_na() %>%
subset(hpa != 'hPa') %>%
mutate(year=1987+floor(row_number()/15),
hpa=as.integer(hpa)) %>%
relocate(year, .before='hpa') %>% arrange(year,hpa) %>%
pivot_longer(cols=2:13, names_to='month',values_to='qbo') %>%
mutate(date=ymd(paste0(year,'-',month,'-15')),
hpa=as.integer(hpa),
qbo=as.numeric(qbo))
# End Data Massaging. It's ready to be graphed
# A simple call to ggplot with geom_contour_filled generates a legend
sing2 %>%
ggplot(aes(x=date,y=hpa)) +
geom_contour_filled(aes(z=qbo*0.1)) +
scale_y_reverse()
# Adding scale_fill_brewer removes the legend.
# Adding show.legend = TRUE to the geom_countour_filled options has no effect.
limits = c(-1,1)*max(abs(sing2$qbo),na.rm=TRUE)
zCuts <- round(seq(limits[1], limits[2], length.out = 11), digits=0)
sing2 %>%
ggplot() +
geom_contour_filled(aes(x=date,y=hpa, z = qbo*0.1),breaks=zCuts*0.1) +
scale_y_reverse(expand=c(0,0)) +
scale_x_date(expand=c(0,0), date_breaks = '1 year', date_labels = '%Y') +
scale_fill_brewer(palette = 5,type='div',breaks=zCuts) +
theme_bw() +
theme(legend.position = 'right',
axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))
OP, I don't have a direct answer for you, given that your example is not able to be replicated (unable to access the data you gave). In place, I can give you a bit of advice on debugging, since it seems the issue is related to the breaks= argument of scale_fill_brewer(). As you mention, you get a legend when using geom_contour_filled(), but not when you add the scale_fill_brewer() part.
Let me use the example from the documentation for geom_contour_filled() to illustrate this behavior, which utilizes the built-in dataset, fathfuld.
I'll add in your own palette and type choice, leaving out the breaks argument for example:
v <- ggplot(faithfuld, aes(waiting, eruptions, z = density))
v + geom_contour_filled() +
scale_fill_brewer(palette = 5, type='div')
If you do the same thing, but add in a "nonsensical" breaks argument, you get the same plot, but without a legend (like you are seeing):
v + geom_contour_filled() +
scale_fill_brewer(palette = 5, type='div', breaks=1:4)
For me, this is good evidence that the issue in your code relates to the value for breaks= not being within the range expected. Is this just a typo? Note that breaks=zCuts in scale_fill_brewer(), yet breaks=zCuts*0.1 in geom_contour_filled(). This would put each value for your color scale to be 10 times outside the range of the breaks for the contours themselves. I'd be willing to bet that this change to that scale_fill_brewer() line will do the trick:
# earlier plot code
... +
scale_fill_brewer(palette = 5,type='div',breaks=zCuts*0.1) +
...
# remaining plot code

Color/fill bars in geom_col based on another variable?

I have an uncolored geom_col and would like it to display information about another (continuous) variable by displaying different shades of color in the bars.
Example
Starting with a geom_col
library(dplyr)
library(ggplot2)
set.seed(124)
iris[sample(1:150, 50), ] %>%
group_by(Species) %>%
summarise(n=n()) %>%
ggplot(aes(Species, n)) +
geom_col()
Suppose we want to color the bars according to how low/high mean(Sepal.Width) in each grouping
(note: I don't know if there's a way to provide 'continuous' colors to a ggplot, but, if not, the following colors would be fine to use)
library(RColorBrewer)
display.brewer.pal(n = 3, name= "PuBu")
brewer.pal(n = 3, name = "PuBu")
[1] "#ECE7F2" "#A6BDDB" "#2B8CBE"
The end result should be the same geom_col as above but with the bars colored according to how low/high mean(Sepal.Width) is.
Notes
This answer shows something similar but is highly manual, and is okay for 3 bars, but not sustainable for many plots with a high number of bars (since would require too many case_when conditions to be manually set)
This is similar but the coloring is based on a variable already displayed in the plot, rather than another variable
Note also, in the example I provide above, there are 3 bars and I provide 3 colors, this is somewhat manual and if there's a better (i.e. less manual) way to designate colors would be glad to learn it
What I've tried
I thought this would work, but it seems to ignore the colors I provide
library(RColorBrewer)
# fill info from: https://stackoverflow.com/questions/38788357/change-bar-plot-colour-in-geom-bar-with-ggplot2-in-r
set.seed(124)
iris[sample(1:150, 50), ] %>%
group_by(Species) %>%
summarise(n=n(), sep_mean = mean(Sepal.Width)) %>%
arrange(desc(n)) %>%
mutate(colors = brewer.pal(n = 3, name = "PuBu")) %>%
mutate(Species=factor(Species, levels=Species)) %>%
ggplot(aes(Species, n, fill = colors)) +
geom_col()
Do the following
add fill = sep_mean to aes()
add + scale_fill_gradient()
remove mutate(colors = brewer.pal(n = 3, name = "PuBu")) since the previous step takes care of colors for you
set.seed(124)
iris[sample(1:150, 50), ] %>%
group_by(Species) %>%
summarise(n=n(), sep_mean = mean(Sepal.Width)) %>%
arrange(desc(n)) %>%
mutate(Species=factor(Species, levels=Species)) %>%
ggplot(aes(Species, n, fill = sep_mean, label=sprintf("%.2f", sep_mean))) +
geom_col() +
scale_fill_gradient() +
labs(fill="Sepal Width\n(mean cm)") +
geom_text()

Apply color brewer to a single line in ggplot

library(tidyverse)
library(RColorBrewer)
mtcars %>%
count(cyl) %>%
ungroup() %>%
ggplot(aes(cyl, n)) +
geom_line(size = 3) +
scale_color_brewer(palette = "Accent")
I'll often have a whole series of graphs with the color theme for each being scale_color_brewer(palette = "Accent"). I want to maintain this theme throughout my .Rmd file, on all graphs. However, this scale_color_brewer() only works if there's multiple lines on each plot.
For the case above (a single line), how do I apply scale_color_brewer(palette = "Accent"), short of specifying the unique color as an argument in geom_line()? I'm hoping there's a better solution than that manual process. Using different themes and having to look up all the different CMYK values gets tedious.
Two things you can do to take away the tedium are to save the palette(s) you want to keep using to a variable, and set geom defaults. I often do this to keep a couple palettes ready to use throughout a document, like one qualitative and one continuous.
update_geom_defaults takes a list of default arguments for a specified geom, but you can still add to or override those defaults, like below.
library(dplyr)
library(ggplot2)
accent <- RColorBrewer::brewer.pal(7, "Accent")
# item 6 is hot pink
update_geom_defaults("line", list(color = accent[6]))
mtcars %>%
count(cyl) %>%
ggplot(aes(x = cyl, y = n)) +
geom_line()
mpg %>%
group_by(year) %>%
summarise(avg_cty = mean(cty)) %>%
ggplot(aes(x = year, y = avg_cty)) +
geom_line(size = 2)
mpg %>%
group_by(year) %>%
summarise(avg_hwy = mean(hwy)) %>%
ggplot(aes(x = year, y = avg_hwy)) +
geom_line(color = accent[1])
As for knowing what each color in a palette is without sorting through hex codes, RColorBrewer::display.brewer.pal is handy, as are similar functions in other packages like rcartocolor. I have a package of utility functions I use a lot where I wrote a function to display blocks of each color in a vector of hex codes, because it is quite tedious otherwise.
You could always set a color aesthetic and just turn off the legend
mtcars %>%
count(cyl) %>%
ungroup() %>%
ggplot(aes(cyl, n, color="A")) +
geom_line(size = 3) +
scale_color_brewer(palette = "Accent", guide="none")

R: Convert data.frame to color matrix using ggplot2?

Does anyone know how to use ggplot2 to convert a data frame in R with continous values into a pretty figure. This would be similar to the answer from this post but with ggplot2.
Is this possible?
New to R and ggplot2 so thanks in advance for any advice.
Here's an example using the mtcars data (scaled to give comparable values, so the numbers don't mean much).
The key things are the use of gather to tidy the data, geom_tile filled by value, and geom_text for the labels. Everything else is just manipulation of that particular data frame.
You could also just use one of the scale_fill_gradient geoms.
library(tidyverse)
library(viridis)
mtcars %>%
scale() %>%
as.data.frame() %>%
rownames_to_column(var = "make") %>%
gather(var, val, -make) %>%
ggplot(aes(var, make)) +
geom_tile(aes(fill = val)) +
geom_text(aes(label = round(val, 2)),
size = 3) +
coord_fixed() +
scale_fill_viridis() +
guides(fill = FALSE)
Or using:
+ scale_fill_gradient2(midpoint = 1.5)

r dplyr non standard evaluation - ordering bar plot in a function

I have read http://dplyr.tidyverse.org/articles/programming.html about non standard evaluation in dplyr but still can't get things to work.
plot_column <- "columnA"
raw_data %>%
group_by(.dots = plot_column) %>%
summarise (percentage = mean(columnB)) %>%
filter(percentage > 0) %>%
arrange(percentage) %>%
# mutate(!!plot_column := factor(!!plot_column, !!plot_column))%>%
ggplot() + aes_string(x=plot_column, y="percentage") +
geom_bar(stat="identity", width = 0.5) +
coord_flip()
works fine when the mutate statement is disabled. However, when enabling it in order to order the bars by height only a single bar is returned.
How can I convert the statement above into a function / to use a variable but still plot multiple bars ordered by their size.
An example Dataset could be:
columnA,columnB
a, 1
a, 0.4
a, 0.3
b, 0.5
edit
a sample:
mtcars %>%
group_by(mpg) %>%
summarise (mean_col = mean(cyl)) %>%
filter(mean_col > 0) %>%
arrange(mean_col) %>%
mutate(mpg := factor(mpg, mpg))%>%
ggplot() + aes(x=mpg, y=mean_col) +
geom_bar(stat="identity")
coord_flip()
will output an ordered bar chart.
How can I wrap this into a function where the column can be replaced and I get multiple bars?
This works with dplyr 0.7.0 and ggplot 2.2.1:
rm(list = ls())
library(ggplot2)
library(dplyr)
raw_data <- tibble(columnA = c("a", "a", "b", "b"), columnB = c(1, 0.4, 0.3, 0.5))
plot_col <- function(df, plot_column, val_column){
pc <- enquo(plot_column)
vc <- enquo(val_column)
pc_name <- quo_name(pc) # generate a name from the enquoted statement!
df <- df %>%
group_by(!!pc) %>%
summarise (percentage = mean(!!vc)) %>%
filter(percentage > 0) %>%
arrange(percentage) %>%
mutate(!!pc_name := factor(!!pc, !!pc)) # insert pc_name here!
ggplot(df) + aes_(y = ~percentage, x = substitute(plot_column)) +
geom_bar(stat="identity", width = 0.5) +
coord_flip()
}
plot_col(raw_data, columnA, columnB)
plot_col(mtcars, mpg, cyl)
Problem I ran into was kind of that ggplot and dplyr use different kinds of non-standard evaluation. I got the answer at this question: Creating a function using ggplot2 .
EDIT: parameterized the value column (e.g. columnB/cyl) and added mtcars example.

Resources