Label a barplot by number of values with positive and negative bars - r

I have a barplot (stat=identity) and would like to label the bars with N (the number of values of every bar). I have a problem with positive and negative bars. One resolution could be to make the labels of the positive bars in white oder to write them at the top.
ggplot(AP_Group, aes(Labels, Mean))+
geom_bar(stat = "identity") +
theme_minimal() +
ggtitle(expression(paste("Air Pressure - C_PM"[1], " - All Season"))) +
xlab("Air Pressure [hPa]") +
ylab("Shap Value") +
geom_text(aes(label=N), color="black", size=3.5, vjust = 1.8) +
theme(plot.title = element_text(color="black", size=14, margin = margin(t = 0, r = 0, b = 15, l = 0)),
axis.title.x = element_text(color="black", size=14, margin = margin(t = 15, r = 0, b = 0, l = 0)),
axis.title.y = element_text(color="black", size=14, margin = margin(t = 0, r = 15, b = 0, l = 0))) +
theme(plot.title = element_text(hjust=0.5))

One way of doing it would to make vjust an aesthetic like this...
df <- tibble(x = c(1, 2), y = c(-1, 2)) #sample data
df %>% ggplot(aes(x=x, y=y)) +
geom_bar(stat = "identity") +
geom_text(aes(label = y, vjust = -sign(y)))

If you really want to color them conditionally to the sign, here a possible solution:
# fake df
df <- data.frame(Mean = c(1,3,-5),Labels = c("a","b","c"))
# here you decide to put white or black conditionally
df$color <- ifelse(df$Mean > 0, 'white','black')
library(ggplot2)
ggplot(df, aes(Labels, Mean))+
geom_bar(stat = "identity") +
theme_minimal() +
ggtitle(expression(paste("Air Pressure - C_PM", " - All Season"))) +
xlab("Air Pressure [hPa]") +
ylab("Shap Value") +
# here in aes() you put the color made
geom_text(aes(label=Mean, color=color), size=3.5, vjust = 1.8) +
# here you define the colors (it means "white" could be the color you want)
scale_color_manual(values = c("black" = "black", "white" = "white"))+
# you can remove the useless legend
theme(legend.position="none")

Related

ggplot2 plot points when y value in NA

I have a dataframe where the x value (discrete) is present and want to include on the x-axis in the plot; however, its y value is NA
I still want to show the x value even though y is NA. Is there a way to do this in ggplot2?
Currently, it simply skips the first two rows that has the NA value.
ggplot(tChartDF()[['df']], aes(
x = factor(tChartDF()[['df']][['Rare event date']], levels = unique(tChartDF()[['df']][['Rare event date']])),
y = unlist(tChartDF()[['df']][['days_between']]),
)) +
geom_hline(yintercept = unlist(tChartDF()[['timeScaleCL']]), color = input$tChartCLColour, lwd = input$tChartCLWidth) +
geom_hline(yintercept = unlist(tChartDF()[['timeScaleUL']]), linetype = 'dashed', lwd = 1, color = 'red') +
geom_hline(yintercept = unlist(tChartDF()[['timeScaleLL']]), linetype = 'dashed', lwd = 1, color = 'red') +
scale_x_discrete(expand = c(0,0)) +
theme_classic() +
geom_line(aes(group = 1), lwd = input$tChartLineWidth, color= input$tChartLineColour) +
geom_point(size = input$tChartMarkerSize, color = input$tChartMarkerColour) +
labs(title = input$tChartPlotTitle, x = input$tChartPlotXLabel, y = input$tChartPlotYLabel) +
theme(
plot.title = element_text(size = 24, face = 'bold', family = 'Arial', hjust = 0.5),
plot.margin = margin(0, 1, 0, 0, "cm"),
axis.title = element_text(size = 20, face = 'bold', family = 'Arial'),
axis.text = element_text(size = 16, face = 'bold', family = 'Arial'),
axis.text.x = element_text(angle = as.numeric(input$tChartXOrientation), vjust = 0.5),
axis.ticks.length = unit(.25, 'cm'),
) +
coord_cartesian(clip = 'off')
As seen, it only starts plotting at date: 2022/12/15 (ignoring the previous values in the table) columns y and mr have the NA values.
For the plot, I only care about the first two columns (Rare events and days_between). I tried selecting only those two columns and plotting but it still ignores the first two rows.
Desired result:
If we start with something like
mt <- mtcars
mt$mpg[c(3,6,9)] <- NA
and plot the line as in
ggplot(mt, aes(disp, mpg)) +
geom_line()
we don't see the missing points (not a surprise). We can add them this way:
transform(mt, mpg2 = ifelse(is.na(mpg), max(mpg, na.rm = TRUE) + 1, mpg)) |>
ggplot(aes(disp, mpg)) +
geom_line() +
geom_point(aes(y = mpg2), data = ~ subset(., is.na(mpg)), shape = 1)
This can easily be adapted to be at the bottom, using different shapes/colors, perhaps even on an explicitly-gray background (top/bottom ribbon).

How do I neatly align my stacked barchart labels, with differing alignments to each side of the bar?

I have my labels roughly aligned to each side of my stacked bar chart. The problem is that they look like a mess because they aren't right and left justified on either side of the bar. How do I fix this so that they look professional?
df3 <- data.frame(
Label = c("Dasher", "Dancer", "Comet", "Cupid", "Prancer", "Blitzen", "Rudolph"),
Amount = c(650.01, 601.01, 340.05, 330.20, 260.01, 250.80, 10.10)
)
# Sort order
level_order <- df3 %>%
arrange(desc(Amount))
ggplot(level_order, aes(fill=fct_inorder(Label), y=Amount, x="")) +
geom_bar(position="stack", stat="identity", width = 0.55) +
scale_fill_brewer(palette = "Blues", direction = -1) +
theme_void() +
geom_text(aes(label = paste0("$", Amount)),
position = position_stack(vjust = 0.5),
hjust = -3.1,
size = 5) +
geom_text(aes(label = Label),
position = position_stack(vjust = 0.5),
hjust = 5,
size = 5) +
theme(legend.position = "none") +
theme(plot.title = element_text(size = 50, hjust = .5, vjust = 0)) +
ggtitle("Food Costs by Reindeer")
hjust determines the text alignment (with 0 being left-aligned, and 1 right-aligned). The x co-ordinate of your geom_text at the moment is defaulted to 1, so changing this will change the position of the text.
ggplot(level_order, aes(fill=fct_inorder(Label), y=Amount, x="")) +
geom_bar(position="stack", stat="identity", width = 0.55) +
scale_fill_brewer(palette = "Blues", direction = -1) +
theme_void() +
geom_text(aes(x=0.6, label = paste0("$", Amount)),
position = position_stack(vjust = 0.5),
hjust = 0.5,
size = 5) +
geom_text(aes(x=1.4, label = Label),
position = position_stack(vjust = 0.5),
hjust = 0.5,
size = 5) +
theme(legend.position = "none") +
theme(plot.title = element_text(size = 50, hjust = .5, vjust = 0)) +
ggtitle("Food Costs by Reindeer")
You can also pass hjust as an aesthetic. In order to do that, you will need to prepare the labelling as a separate data frame. Then, you only need to call geom_text once. I don't say this is necessarily better, but just pointing out that this is possible. A few more comments in the code, also regarding a few common pitfalls.
library(tidyverse)
df3 <- data.frame(
Label = c("Dasher", "Dancer", "Comet", "Cupid", "Prancer", "Blitzen", "Rudolph"),
Amount = c(650.01, 601.01, 340.05, 330.20, 260.01, 250.80, 10.10)
) %>%
## arrange step here
arrange(desc(Amount))
## I like to prepare the data outside ggplot
label_df <- df3 %>%
mutate(Amount_lab = paste0("$", Amount)) %>%
pivot_longer(-Amount) %>%
## this adds a column for your adjustment, and the x position compared with the central column
mutate(hjust = rep(0:1, nrow(.)/2),
x = rep(c(1.21, .79), nrow(.)/2))
ggplot(mapping = aes(y = Amount)) +
## geom_col is geom_bar(stat = "identity"), stack is default, so you can omit it
## call data in the geom layers
## set x to 1
## width = .4 so it matches your selected x from above
geom_col(data = df3, aes(x = 1, fill=fct_inorder(Label)), width = .4) +
scale_fill_brewer(palette = "Blues", direction = -1) +
## need to reverse both y and value, weirdly
geom_text(data = label_df, aes(x, y = rev(Amount), label = rev(value),
## this is the main trick
hjust = hjust),
position = position_stack(vjust = 0.5) ) +
## sadly, need to turn clip off
coord_cartesian(clip = "off") +
theme_void() +
## call theme only once!!
theme(legend.position = "none",
plot.title = element_text(size = 20, hjust = .5, vjust = 0),
## you need to add a margin
plot.margin = margin(r = .6, l = .6, unit = "in")) +
ggtitle("Food Costs by Reindeer")
Created on 2021-12-20 by the reprex package (v2.0.1)
Try fixing the x co-ordinate in the call to geom_text and managing alignment with hjust...
df3 <- data.frame(
Label = c("Dasher", "Dancer", "Comet", "Cupid", "Prancer", "Blitzen", "Rudolph"),
Amount = c(650.01, 601.01, 340.05, 330.20, 260.01, 250.80, 10.10)
)
library(ggplot2)
library(dplyr)
library(forcats)
level_order <- df3 %>%
arrange(desc(Amount))
ggplot(level_order, aes(fill=fct_inorder(Label), y=Amount, x="")) +
geom_bar(position="stack", stat="identity", width = 0.55) +
scale_fill_brewer(palette = "Blues", direction = -1) +
theme_void() +
geom_text(aes(x = 1.3, label = paste0("$", Amount)),
position = position_stack(vjust = 0.5),
hjust = 0,
size = 5) +
geom_text(aes(x = 0.6, label = Label),
position = position_stack(vjust = 0.5),
hjust = 0,
size = 5) +
theme(legend.position = "none") +
theme(plot.title = element_text(size = 50, hjust = .5, vjust = 0)) +
ggtitle("Food Costs by Reindeer")
Created on 2021-12-19 by the reprex package (v2.0.1)
Using Peter's answer above (reminding me of the "x" position argument I forgot existed), this was the final fix that got what I was looking for. hjust = 0 is left-justification and hjust = 1 is right justification.
library(tidyverse)
library(grid)
df3 <- data.frame(
Label = c("Dasher", "Dancer", "Comet", "Cupid", "Prancer", "Blitzen", "Rudolph"),
Amount = c(650.01, 601.01, 340.05, 330.20, 260.01, 250.80, 10.10)
)
# Sort order
level_order <- df3 %>%
arrange(desc(Amount))
ggplot(level_order, aes(fill=fct_inorder(Label), y=Amount, x="")) +
geom_bar(position="stack", stat="identity", width = 0.55) +
scale_fill_brewer(palette = "Blues", direction = -1) +
theme_void() +
geom_text(aes(x = 1.3, label = paste0("$", Amount)),
position = position_stack(vjust = 0.5),
hjust = 0,
size = 5) +
geom_text(aes(x = 0.7, label = Label),
position = position_stack(vjust = 0.5),
hjust = 1,
size = 5) +
theme(legend.position = "none",
plot.margin = unit(c(0,0,2,0), "cm"))
grid.text("Food Costs by Reindeer", x = unit(0.5, "npc"), y = unit(0, "npc"),
vjust = -0.5, gp = gpar(cex=4))

How to organize geom_bar() values ​after applying coord.flip()? Package ggplot2

How to place the numbers centralized and in their correct positions? I have tested numerous parameters but I have not found a solution. Everything was very confusing after coordin_flip (). Observe the image and code below.
Code:
# Package
library(ggplot2)
# Create a dataframe
RATE <- c('IgG','IgG','IgA/IgG','IgA/IgG')
GROUP <- c('Asymptomatic','Symptomatic','Asymptomatic','Symptomatic')
N_POSITIVE <- c(12,100,14,107)
PORCENT <- c(7.05, 58.8, 7.73, 59.1)
df <- data.frame(RATE, GROUP, N_POSITIVE, PORCENT)
# Plot
ggplot(df, aes(x = RATE, y = PORCENT, fill = GROUP)) +
geom_bar(stat="identity", width = 0.5) +
geom_text(aes(label=N_POSITIVE),
vjust = -0.3, color = 'black',
size = 3) +
coord_flip() +
labs(x = '', y = 'Percentage (%)\n') +
scale_fill_manual(values = c("#0073c2", "#efc000")) +
theme_classic() +
theme(
legend.position = "top",
legend.title = element_blank(),
axis.text = element_text(angle = 0, color = "black", size = 10, face = 0),
axis.title.x = element_text(angle = 0, color = "black", size = 12, face = 0))
Image:
Does this do what you want? (minus the other formatting which I left out)
ggplot(df, aes(x = PORCENT, y = RATE, fill = GROUP)) +
geom_col(width = 0.5) +
geom_text(aes(label=N_POSITIVE),
vjust = -0.3, color = 'black', hjust = 1.1,
size = 3, position = "stack")
(Note, since ggplot2 3.3.0 in March 2020, most geom's don't need coord_flip if you assign them to the axis you want. If it doesn't interpret correctly, there's also an "orientation" parameter but that doesn't seem necessary here. Also, geom_col is equal to geom_bar(stat="identity").)

Log scale on y axis but data have negative values

I am trying to create a boxplot with a log y axis as I have some very small values and then some much higher values which do not work well in a boxplot with a continuous y axis. However, I have negative values which obviously do not work with a log scale. I was wondering if there was a way around this so that I can display my data on a boxplot which is still easy to interpret but has a more appropriate scale on the y axis.
p <- ggplot(data = Elstow.monthly.fluxes, aes(x = Month1, y = CH4.Flux)) + stat_boxplot(geom = "errorbar", linetype = 1, width = 0.5) + geom_boxplot() +
xlab(expression("Month")) + ylab(expression(~CH[4]~Flux~(µg~CH[4]~m^{-2}~d^{-1}))) +
scale_y_continuous(breaks = seq(-5000,40000,5000), limits = c(-5000,40000))+
theme(axis.text.x = element_text(colour = "black")) + theme(axis.text.y = element_text(colour =
"black")) +
theme(panel.background = element_rect("white", "black")) +
theme(panel.border = element_rect(colour = "black", fill=NA, size=0.5)) +
theme(axis.text = element_text(size = 12))+ theme(axis.title = element_text(size = 14))+
theme(axis.title.y = element_text(margin = margin(t = 0, r = 15, b = 0, l = 0))) +
theme(axis.title.x = element_text(margin = margin(t = 15, r = 0, b = 0, l = 0))) +
geom_hline(yintercept = 0, linetype ="dashed", colour = "black")
While you could indeed use the secondary axis to get the labels you want as Zhiqiang suggests, you could also use a transformation that fits your needs.
Consider the following skewed boxplots:
df <- data.frame(
x = rep(letters[1:2], each = 500),
y = rlnorm(1000) - 2
)
ggplot(df, aes(x, y)) +
geom_boxplot()
Instead, you could use the pseudo-log transformation to visualise your data:
ggplot(df, aes(x, y)) +
geom_boxplot() +
scale_y_continuous(trans = scales::pseudo_log_trans())
Alternatively, you could make any transformation you want. I personally like the inverse hyperbolic sine transformation, which is very much like the pseudo-log:
asinh_trans <- scales::trans_new(
"inverse_hyperbolic_sine",
transform = function(x) {asinh(x)},
inverse = function(x) {sinh(x)}
)
ggplot(df, aes(x, y)) +
geom_boxplot() +
scale_y_continuous(trans = asinh_trans)
I have a silly solution: trick the secondary axis to re-scale y axis. I do not have your data, just made up some numbers for the purpose of demonstration.
First convert y values as logy = log(y + 5000). When generating the graph, transform the values back to the original scale. I borrow the second axis to display the values. I am pretty sure others may have more elegant ways to do this.
I was lazy for not trying to find the right way to remove the primary y axis tick labels, just used breaks = c(0).
df<-data.frame(y = runif(33, min=-5000, max=40000),
x = rep(c("Aug", "Sep", "Oct"),33))
library(tidyverse)
df$logy = log(df$y+5000)
p <- ggplot(data = df, aes(x = x, y = logy)) +
stat_boxplot(geom = "errorbar", linetype = 1, width = 0.5) +
geom_boxplot() +
xlab(expression("Month")) +
ylab(expression(~CH[4]~Flux~(µg~CH[4]~m^{-2}~d^{-1}))) +
scale_y_continuous(sec.axis = sec_axis(~(exp(.) -5000),
breaks = c(-4000, 0, 5000, 10000, 20000, 40000)),
breaks = c(0))+
theme(axis.text.x = element_text(colour = "black")) +
theme(axis.text.y = element_text(colour = "black")) +
theme(panel.background = element_rect("white", "black")) +
theme(panel.border = element_rect(colour = "black", fill=NA, size=0.5)) +
theme(axis.text = element_text(size = 12))+
theme(axis.title = element_text(size = 14))+
theme(axis.title.y = element_text(margin = margin(t = 0, r = 15, b = 0, l = 0))) +
theme(axis.title.x = element_text(margin = margin(t = 15, r = 0, b = 0, l = 0))) +
geom_hline(yintercept = log(5000), linetype ="dashed", colour = "black")
p
coord_trans() is applied after the statistics are calculated (unlike scale). This can be combined with the pseudo_log_trans to cope with negatives.
library(plotly)
set.seed(1234)
dat <- data.frame(cond = factor(rep(c("A","B"), each=200)), rating = c(rnorm(200),rnorm(200, mean=500)))
pseudoLog <- scales::pseudo_log_trans(base = 10)
p <- ggplot(dat, aes(x=cond, y=rating)) + geom_boxplot() + coord_trans(y=pseudoLog)

How do I reverse the order in which error bars appear using geom_errorbar() in R?

I have the following example plot:
test <- data.frame("Factor" = as.factor(c("O", "C", "A")),
b = c(0.18, .34, .65, -.13, .38, .26),
lower95 = c(-.1, .09, .34, -.52, .10, -.02),
upper95 = c(.48, .58, .98, .26, .67, .56),
group = factor(c("Experiment 1","Experiment 2")))
test$Factor <- factor(test$Factor, as.character(test$Factor))
test$group <- factor(test$group, as.character(test$group))
ggplot(test, aes(Factor, b, colour = group)) +
geom_errorbar(aes(ymin = lower95, ymax = upper95),
size = 1,
width = .5,
position = 'dodge') +
geom_hline(yintercept = 0) +
ylim(-1.25, 1.25) +
coord_flip() +
theme_bw() +
ggtitle("Title") +
theme(
axis.text=element_text(size = 20),
axis.title=element_text(size = 18),
plot.title = element_text(size = 20, face = "bold"),
axis.text.y=element_text(size = 12)
)
As you'll see, the error bars appear in the reverse order (from top to bottom) as they do in the legend. I would like Experiment 1 error bars to appear above Experiment 2 error bars.
I have tried
ggplot(test, aes(Factor, b, colour = forcats::fct_rev(groups)
But this reverses the order of the group labels in the legend, not the order of the colours in the legend–which is what would work. I have also tried reversing the order in which I enter them in the data frame and this does not solve the problem.
I would appreciate some help!
Re-factoring will change the order of the plot, but, as you saw, also changes the order of the legend. In addition to reversing the levels of group, you can reverse the order the legend is displayed with the reverse argument in guide_legend.
ggplot(test, aes(Factor, b, colour = forcats::fct_rev(group))) +
geom_errorbar(aes(ymin = lower95, ymax = upper95),
size = 1,
width = .5,
position = 'dodge') +
geom_hline(yintercept = 0) +
ylim(-1.25, 1.25) +
coord_flip() +
theme_bw() +
ggtitle("Title") +
theme(
axis.text=element_text(size = 20),
axis.title=element_text(size = 18),
plot.title = element_text(size = 20, face = "bold"),
axis.text.y=element_text(size = 12)
) +
guides(color = guide_legend(reverse = TRUE) )
If you are using scale_color_discrete or scale_color_manual to control other scale elements like the legend name, you can use guide_legend there instead of via guides.
+
scale_color_discrete(name = "Experiment", guide = guide_legend(reverse = TRUE) )
Do you mean something like this?
test$Factor <- factor(test$Factor, levels = rev(levels(test$Factor)));
test$group <- factor(test$group, levels = rev(levels(test$group)));
ggplot(test, aes(Factor, b, colour = group)) +
geom_errorbar(aes(ymin = lower95, ymax = upper95),
size = 1,
width = .5,
position = 'dodge') +
geom_hline(yintercept = 0) +
ylim(-1.25, 1.25) +
coord_flip() +
theme_bw() +
ggtitle("Title") +
theme(
axis.text=element_text(size = 20),
axis.title=element_text(size = 18),
plot.title = element_text(size = 20, face = "bold"),
axis.text.y=element_text(size = 12)
)
I'm not entirely clear on whether you want to reverse the ordering of test$Factor as well; just (un)comment the corresponding line depending on what you're after.

Resources