My goal is to make a simple column chart in ggplot2 that looks like the following chart (made in Excel):
What I'm finding is that, with example data such as this (where one percentage value is very close to 100%), my options for plotting this data in ggplot2 leave something to be desired. In particular, I haven't found a way to make the following two simple things happen together:
1) Make the y-axis line end at 100%
and
2) Make the percentage labels over each bar visible
To address this issue, I've tried experimenting with different arguments to scale_y_continuous() but haven't found a way to meet both of the goals above at the same time. You can see this in the example plots and code below.
My question is: how do I expand the y scale so that my percentage labels over each data point are visible, but the y-axis line ends at 100%?
library(dplyr)
library(ggplot2)
library(scales)
example_df <- data_frame(Label = c("A", "B"),
Percent = c(0.5, 0.99))
example_plot <- example_df %>%
ggplot(aes(x = Label, y = Percent)) +
geom_bar(stat = "identity",
fill = "dodgerblue4", width = .6) +
geom_text(aes(label = percent(Percent)),
size = 3, vjust = -0.5) +
scale_x_discrete(NULL, expand = c(0, .5)) +
theme_classic()
Plot with desired y-axis line, but non-visible label over bar
Here is what happens when I set the limit on scale_y_continuous() to c(0,1):
example_plot +
scale_y_continuous(NULL, limits = c(0, 1.0), breaks = seq(0, 1, .2),
labels = function(x) scales::percent(x),
expand = c(0, 0)) +
labs(title = "Y axis line looks perfect, but the label over the bar is off")
Plot with y-axis line too long, but visible label over bar
And here is what happens when I set the limit on scale_y_continuous() to c(0,1.05):
example_plot +
scale_y_continuous(NULL, limits = c(0, 1.05), breaks = seq(0, 1, .2),
labels = function(x) scales::percent(x),
expand = c(0, 0)) +
labs(title = "Y axis line is too long, but the label over the bar is visible")
You could remove the regular axis line and then use geom_segment to create a new one:
example_df %>%
ggplot(aes(x = Label, y = Percent)) +
geom_bar(stat = "identity", fill = "dodgerblue4", width = .6) +
geom_text(aes(label = percent(Percent)), size = 3, vjust = -0.5) +
scale_x_discrete("", expand = c(0, .5)) +
scale_y_continuous("", breaks = seq(0, 1, .2), labels = percent, limits=c(0,1.05),
expand=c(0,0)) +
theme_classic() +
theme(axis.line.y=element_blank()) +
geom_segment(x=.5025, xend=0.5025, y=0, yend=1.002)
To respond to your comment: Even when it's outside the plot area, the 99% label is still being drawn, but it's "clipped", meaning that plot elements outside the plot area are masked. So, another option, still hacky, but less hacky than my original answer, is to turn off clipping so that the label appears:
library(grid)
p = example_df %>%
ggplot(aes(x = Label, y = Percent)) +
geom_bar(stat = "identity", fill = "dodgerblue4", width = .6) +
geom_text(aes(label = percent(Percent)), size = 3, vjust = -0.5) +
scale_x_discrete("", expand = c(0, .5)) +
scale_y_continuous("", breaks = seq(0, 1, .2), labels = percent, limits=c(0,1),
expand=c(0,0)) +
theme_classic() +
theme(plot.margin=unit(c(10,0,0,0),'pt'))
# Turn off clipping
pg <- ggplot_gtable(ggplot_build(p))
pg$layout$clip[pg$layout$name=="panel"] <- "off"
grid.draw(pg)
Related
I guess my question is very simple for someone who knows ggplot very well but I spent a lot of time trying different ways. I want to draw a horizontal line that pass-through y axis to separate each band of the bars produced using geom_col. For example, I want to draw a horizontal line that separate bars of meat from maize. Here is my code, example data and the design of the figure I want to produce.
library(tidyverse)
library(ggplot2)
# sample data
Food = c("meat", "meat", "meat", "meat", "wheat","wheat","wheat", "wheat", "maize","maize","maize","maize")
Subgroup = c("Male", "Female", "Urban", "Rural", "Male", "Female", "Urban", "Rural", "Male", "Female","Urban", "Rural")
mean = c(8.66, 10.45, 9.88, 7.32, 21.04, 19.65, 20.26, 20.87, 51.06 , 44.51, 47.60, 48.40)
df <- data.frame(Food, Subgroup, mean)
#Color code
colorPanel = c('#083c5d','#2d004b','#106d8e','#7d103d')
# Plot
Plot_FBGDS <- ggplot(df, aes(x = Food, y = mean, fill = Subgroup)) +
geom_col(stat = "identity", position = position_dodge(-0.9), width = 0.82) +
scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
limits = c(0,100),
labels = function(x) paste0(x, "%")) +
coord_flip() +
scale_fill_manual(values = colorPanel) +
labs( x= " ",
y = " ")
Try this, using geom_vline with manually specified xintercept.
# Plot
ggplot(df, aes(x = Food, y = mean, fill = Subgroup)) +
geom_col(stat = "identity", position = position_dodge(-0.9), width = 0.82) +
scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
limits = c(0,100),
labels = function(x) paste0(x, "%")) +
geom_vline(xintercept = c(0.5, 1.5, 2.5, 3.5)) +
coord_flip() +
scale_fill_manual(values = colorPanel) +
labs( x= " ",
y = " ")
Note that geom_vline typically produces a vertical line, but since you have coord_flip it becomes horizontal. Without the coord_flip, you would use geom_hline and set the yintercept parameter instead.
Also, if I may suggest an alternative way to visualize this using facet_wrap instead, I would say the option below looks a lot better and you can style the facets using the strip_ properties in plot_theme
# Plot
ggplot(df, aes(x = Subgroup, y = mean, fill = Subgroup)) +
geom_col(stat = "identity", position = position_dodge(-0.9), width = 0.82) +
scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
limits = c(0,100),
labels = function(x) paste0(x, "%")) +
coord_flip() +
scale_fill_manual(values = colorPanel) +
facet_wrap(~Food, ncol=1)
labs( x= " ",
y = " ") +
theme(
legend.position = "none"
)
I'm going to build off the answer already posted here from #geoff. OP requested lines that extend beyond the plot area into the axis label. It's true that normally geoms are restricted to the panel area alone, but that's only by default. For any of the coord_*() functions, you can change the default clipping from "on" to "off" via clip="off".
For geom_vline() and geom_hline(), these seem to be automatically clipped to the panel area (more on that below), but for just about all other geoms, you can extend beyond the plot area. We can use this to our advantage in using geom_segment() and specifying the lines.
In this example, I'm going to need to create a linesdata data frame outside of the plot to make drawing the lines a bit easier (and reference that dataset in geom_segment()). We also need to change clip="off" within coord_flip(), and finally, I had to adjust the value for the staring value of the line (y here because we flip the axis) to be some negative value to get below the axis.
linesdata <- data.frame(
xvals = c(0.5, 1.5, 2.5, 3.5),
Subgroup=NA # required because it complains for fill, which I cannot specify again for geom_segment.
)
ggplot(df, aes(x = Food, y = mean, fill = Subgroup)) +
geom_col(position = position_dodge(-0.9), width = 0.82) +
scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
limits = c(0,100),
labels = function(x) paste0(x, "%")) +
coord_flip(clip="off") +
geom_segment(
data=linesdata, y=-5, yend=Inf,
aes(x=xvals, xend=xvals)
) +
scale_fill_manual(values = colorPanel) +
labs( x= " ",
y = " ")
Note that I had to include Subgroup as a column in the linesdata data frame. The way to avoid having to do this would be to specify the fill= aesthetic inside geom_col instead of globally... but it works this way too.
The reason why geom_hline and vline always clip
Interestingly, you'll note that even though yend=Inf, the line does not extend beyond the panel area in the positive direction! I had no idea this works this way, but It seems Inf is specially designed to clip to the panel area no matter what. I'm pretty sure that geom_vline() and geom_hline() are using values of Inf and -Inf under the hood. If I change the value to y=-Inf inside geom_segment() you can see it doesn't extend the same way as specifying a number:
ggplot(df, aes(x = Food, y = mean, fill = Subgroup)) +
geom_col(position = position_dodge(-0.9), width = 0.82) +
scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
limits = c(0,100),
labels = function(x) paste0(x, "%")) +
coord_flip(clip="off") +
geom_segment(
data=linesdata, y=-Inf, yend=Inf,
aes(x=xvals, xend=xvals)
) +
scale_fill_manual(values = colorPanel) +
labs( x= " ",
y = " ")
The only problem here is that you have to play around a bit to find the right value for the starting y value via trial and error. I found -5 did the trick pretty well.
I am making a set of scorecards where I am generating a set of graphs that show the distribution of responses from a survey and also where the response for a specific company falls. I need to modify the formatting of a graph, a stacked barchart, and add a few features I’ve outlined below. I’ve already spent a few hours getting my chart to where it is now and would appreciate your help with the features I outline below.
Data is
Data<-data.frame(Reviewed = c("Annually", "Annually", "Hourly", "Monthly", "Weekly","Monthly","Weekly","Other","Other","Monthly","Weekly"),Company=c("a","b","c","d","e","f","g","h","i","j","k"),Question="Q1")
So far I’ve developed this
ggplot(Data, aes(x="Question", fill=Reviewed)) + geom_bar(position='fill' ) +
coord_flip()
I would like to do the following:
Order the variables so they are arranged on plot as follows: Annually,Monthly,Weekly,Hourly,Other
Express the y axis in terms of percent. I.e. 0.25 turns into 25%
Move y-axis directly underneath the bar.
Remove the legend but move the terms underneath the respective part of the graph on a diagonal slant.
Add a black line that cuts down the 50% mark
Add a dot in at the midpoint of the stack for the value of company “e”.
Remove gray background
This is what I'm hoping the finished graph will look like.
There's a lot to unpack here, so I'll break it down bit by bit:
Order the variables so they are arranged on plot as follows: Annually,Monthly,Weekly,Hourly,Other
Assign "Reviewed" as an ordered factor. I'm reversing the order here since it wants to plot the "lowest" factor first (to the left).
Data$Reviewed <- factor(Data$Reviewed,
levels = rev(c('Annually', 'Monthly', 'Weekly', 'Hourly', 'Other')),
ordered = T)
ggplot(Data, aes(x="Question", fill=Reviewed)) + geom_bar(position='fill' ) +
coord_flip()
Express the y axis in terms of percent. I.e. 0.25 turns into 25%
Use scale_y_continuous(labels = scales::percent) to adjust the labels. I believe that the scales was pulled in when you installed ggplot2.
ggplot(Data, aes(x="Question", fill=Reviewed)) +
geom_bar(position = 'fill') +
scale_y_continuous(labels = scales::percent) +
coord_flip()
Move y-axis directly underneath the bar.
Remove gray background
These are done all at once by adding expand = F to coord_flip.
ggplot(Data, aes(x="Question", fill=Reviewed)) +
geom_bar(position = 'fill') +
scale_y_continuous(labels = scales::percent) +
coord_flip(expand = F)
Remove the legend...
Add theme(legend.position = 'none').
ggplot(Data, aes(x="Question", fill=Reviewed)) +
geom_bar(position = 'fill') +
scale_y_continuous(labels = scales::percent) +
coord_flip(expand = F) +
theme(legend.position = 'none')
but move the terms underneath the respective part of the graph on a diagonal slant.
This is tougher and takes a good amount of fiddling.
Use geom_text to make the labels
Calculate the position along the bar using the 'count' stat
Move the labels to the bottom of the plot by providing a fake x coordinate
Align the labels in the center of the bars using position_stack, and make them abut the x axis using hjust.
Add angle.
Use clip = 'off' in coord_flip to make sure that these values are not cut out since they're outside the plotting area.
Fiddle with the x limits to crop out empty plotting area.
Adjust the plot margin in theme to make sure everything can be seen.
ggplot(Data, aes(x="Question", fill=Reviewed)) +
geom_bar(position = 'fill') +
geom_text(aes(label = Reviewed, x = 0.45,
y = stat(..count../sum(..count..))), stat = 'count',
position = position_stack(0.5),
hjust = 0,
angle = 45) +
scale_y_continuous(labels = scales::percent) +
coord_flip(xlim = c(0.555, 1.4), clip = 'off',expand = F) +
theme(plot.margin = margin(0, 0, 35, 10),
legend.position = 'none')
Add a black line that cuts down the 50% mark
Use geom_hline(yintercept = 0.5); remember that it's a "horizontal" line since the coordinates are flipped.
ggplot(Data, aes(x="Question", fill=Reviewed)) +
geom_bar(position = 'fill') +
geom_text(aes(label = Reviewed, x = 0.45,
y = stat(..count../sum(..count..))), stat = 'count',
position = position_stack(0.5),
hjust = 0,
angle = 45) +
geom_hline(yintercept = 0.5) +
scale_y_continuous(labels = scales::percent) +
coord_flip(xlim = c(0.555, 1.4), clip = 'off',expand = F) +
theme(plot.margin = margin(0, 0, 20, 10),
legend.position = 'none')
Add a dot in at the midpoint of the stack for the value of company “e”.
This is pretty hack-y. Using the same y values as in geom_text, use geom_point to plot a point for every value of Reviewed, then use position_stack(0.5) to nudge them to the center of the bar. Then use scale_color_manual to only color "Weekly" values (which is the corresponding value of Reviewed for Company "e"). I'm sure there's a way to do this more programmatically.
ggplot(Data, aes(x="Question", fill=Reviewed)) +
geom_bar(position = 'fill') +
geom_text(aes(label = Reviewed, x = 0.45,
y = stat(..count../sum(..count..))), stat = 'count',
position = position_stack(0.5),
hjust = 0,
angle = 45) +
geom_hline(yintercept = 0.5) +
geom_point(aes(y = stat(..count../sum(..count..)),
color = Reviewed), stat = 'count',
position = position_stack(0.5), size = 5) +
scale_color_manual(values = 'black', limits = 'Weekly') +
scale_y_continuous(labels = scales::percent) +
coord_flip(xlim = c(0.555, 1.4), clip = 'off',expand = F) +
theme(plot.margin = margin(0, 0, 20, 10),
legend.position = 'none')
This is what I'm hoping the finished graph will look like.
Prettying things up:
ggplot(Data, aes(x="Question", fill = Reviewed)) +
geom_bar(position = 'fill') +
geom_text(aes(label = Reviewed, x = 0.45,
y = stat(..count../sum(..count..))), stat = 'count',
position = position_stack(0.5),
hjust = 0,
angle = 45) +
geom_hline(yintercept = 0.5) +
geom_point(aes(y = stat(..count../sum(..count..)),
color = Reviewed), stat = 'count',
position = position_stack(0.5), size = 5) +
scale_color_manual(values = 'black', limits = 'Weekly') +
scale_y_continuous(labels = scales::percent) +
coord_flip(xlim = c(0.555, 1.4), clip = 'off', expand = F) +
labs(x = NULL, y = NULL) +
theme_minimal() +
theme(plot.margin = margin(0, 0, 35, 10),
legend.position = 'none')
I'm having a hard time dealing with this plot.
The height of values in ANI>96 making it hard to read the red and blue percentage text.
I failed to break the y-axis by looking at answers from other posts in StackOverflow.
Any suggestions?
Thanks.
library(data.table)
library(ggplot2)
dt <- data.table("ANI"= sort(c(seq(79,99),seq(79,99))), "n_pairs" = c(5, 55, 13, 4366, 6692, 59568, 382873, 397996, 1104955, 282915,
759579, 261170, 312989, 48423, 120574, 187685, 353819, 79468, 218039, 66314, 41826, 57668, 112960, 81652, 28613,
64656, 21939, 113656, 170578, 238967, 610234, 231853, 1412303, 5567, 4607268, 5, 14631942, 0, 17054678, 0, 3503846, 0),
"same/diff" = rep(c("yes","no"), 21))
for (i in 1:nrow(dt)) {
if (i%%2==0) {
next
}
total <- dt$n_pairs[i] + dt$n_pairs[i+1]
dt$total[i] <- total
dt$percent[i] <- paste0(round(dt$n_pairs[i]/total *100,2), "%")
dt$total[i+1] <- total
dt$percent[i+1] <- paste0(round(dt$n_pairs[i+1]/total *100,2), "%")
}
ggplot(data=dt, aes(x=ANI, y=n_pairs, fill=`same/diff`)) +
geom_text(aes(label=percent), position=position_dodge(width=0.9), hjust=0.75, vjust=-0.25) +
geom_bar(stat="identity") + scale_x_continuous(breaks = dt$ANI) +
labs(x ="ANI", y = "Number of pairs", fill = "Share one common species taxonomy?") +
theme_classic() + theme(legend.position="bottom")
Here is the list of major changes I made:
I reduced the y axis by zooming into the chart with coord_cartesian (which is called by coord_flip).
coord_flip shouuld also improve the readability of the chart by switching x and y. I don't know if the switch is a desirable output for you.
Also now position_dodge, works as expected: two bars next to each other with the labels on top (on the left in this case).
I set geom_bar before geom_text so that the text is always in front of the bars in the chart.
I set scale_y_continuous to change the labels of the y axis (in the chart the x axis because of the switch) to improve the readability of the zeros.
ggplot(data=dt, aes(x = ANI, y = n_pairs, fill = `same/diff`)) +
geom_bar(stat = "identity", position = position_dodge2(width = 1), width = 0.8) +
geom_text(aes(label = percent), position = position_dodge2(width = 1), hjust = 0, size = 3) +
scale_x_continuous(breaks = dt$ANI) +
scale_y_continuous(labels = scales::comma) +
labs(x ="ANI", y = "Number of pairs", fill = "Share one common species taxonomy?") +
theme_classic() +
theme(legend.position = "bottom") +
coord_flip(ylim = c(0, 2e6))
EDIT
Like this columns and labels are stacked but labels never overlap.
ggplot(data=dt, aes(x = ANI, y = n_pairs, fill = `same/diff`)) +
geom_bar(stat = "identity", width = 0.8) +
geom_text(aes(label = percent,
hjust = ifelse(`same/diff` == "yes", 1, 0)),
position = "stack", size = 3) +
scale_x_continuous(breaks = dt$ANI) +
scale_y_continuous(labels = scales::comma) +
labs(x ="ANI", y = "Number of pairs", fill = "Share one common species taxonomy?") +
theme_classic() +
theme(legend.position = "bottom") +
coord_flip(ylim = c(0, 2e6))
Alternatively, you can avoid labels overlapping with check_overlap = TRUE, but sometimes one of the labels will not be shown.
ggplot(data=dt, aes(x = ANI, y = n_pairs, fill = `same/diff`)) +
geom_bar(stat = "identity", width = 0.8) +
geom_text(aes(label = percent), hjust = 1, position = "stack", size = 3, check_overlap = TRUE) +
scale_x_continuous(breaks = dt$ANI) +
scale_y_continuous(labels = scales::comma) +
labs(x ="ANI", y = "Number of pairs", fill = "Share one common species taxonomy?") +
theme_classic() +
theme(legend.position = "bottom") +
coord_flip(ylim = c(0, 2e6))
I have a graph made in ggplot that looks like this:
I wish to have the numeric labels at each of the bars to be grounded/glued to the x axis where y <= 0.
This is the code to generate the graph as such:
ggplot(data=df) +
geom_bar(aes(x=row, y=numofpics, fill = crop, group = 1), stat='identity') +
geom_point(data=df, aes(x = df$row, y=df$numofparcels*50, group = 2), alpha = 0.25) +
geom_line(data=df, aes(x = df$row, y=df$numofparcels*50, group = 2), alpha = 0.25) +
geom_text(aes(x=row, y=numofpics, label=bbch)) +
geom_hline(yintercept=300, linetype="dashed", color = "red", size=1) +
scale_y_continuous(sec.axis= sec_axis(~./50, name="Number of Parcels")) +
scale_x_discrete(name = c(),breaks = unique(df$crop), labels = as.character(unique(df$crop)))+
labs(x=c(), y="Number of Pictures")
I've tried vjust and experimenting with position_nudge for the geom_text element, but every solution I can find changes the position of each element of the geom_text respective to its current position. As such everything I try results in situation like this one:
How can I make ggplot ground the text to the bottom of the x axis where y <= 0, possibly with the possibility to also introduce a angle = 45?
Link to dataframe = https://drive.google.com/file/d/1b-5AfBECap3TZjlpLhl1m3v74Lept2em/view?usp=sharing
As I said in the comments, just set the y-coordinate of the text to 0 or below, and specify the angle : geom_text(aes(x=row, y=-100, label=bbch), angle=45)
I'm behind a proxy server that blocks connections to google drive so I can't access your data. I'm not able to test this, but I would introduce a new label field in my dataset that sets y to be 0 if y<0:
df <- df %>%
mutate(labelField = if_else(numofpics<0, 0, numofpics)
I would then use this label field in my geom_text call:
geom_text(aes(x=row, y=labelField, label=bbch), angle = 45)
Hope that helps.
You can simply define the y-value in geom_text (e.g. -50)
ggplot(data=df) +
geom_bar(aes(x=row, y=numofpics, fill = crop, group = 1), stat='identity') +
geom_point(data=df, aes(x = df$row, y=df$numofparcels*50, group = 2), alpha = 0.25) +
geom_line(data=df, aes(x = df$row, y=df$numofparcels*50, group = 2), alpha = 0.25) +
geom_text(aes(x=row, y=-50, label=bbch)) +
geom_hline(yintercept=300, linetype="dashed", color = "red", size=1) +
scale_y_continuous(sec.axis= sec_axis(~./50, name="Number of Parcels")) +
scale_x_discrete(name = c(),breaks = unique(df$crop), labels =
as.character(unique(df$crop)))+
labs(x=c(), y="Number of Pictures")
I have a test dataset like this:
df_test <- data.frame(
proj_manager = c('Emma','Emma','Emma','Emma','Emma','Alice','Alice'),
proj_ID = c(1, 2, 3, 4, 5, 6, 7),
stage = c('B','B','B','A','C','A','C'),
value = c(15,15,20,20,20,70,5)
)
Preparation for viz:
input <- select(df_test, proj_manager, proj_ID, stage, value) %>%
filter(proj_manager=='Emma') %>%
do({
proj_value_by_manager = sum(distinct(., proj_ID, value)$value);
mutate(., proj_value_by_manager = proj_value_by_manager)
}) %>%
group_by(stage) %>%
do({
sum_value_byStage = sum(distinct(.,proj_ID,value)$value);
mutate(.,sum_value_byStage= sum_value_byStage)
}) %>%
mutate(count_proj = length(unique(proj_ID)))
commapos <- function(x, ...) {
format(abs(x), big.mark = ",", trim = TRUE,
scientific = FALSE, ...) }
Visualization:
ggplot (input, aes(x=stage, y = count_proj)) +
geom_bar(stat = 'identity')+
geom_bar(aes(y=-proj_value_by_manager),
stat = "identity", fill = "Blue") +
scale_y_continuous(labels = commapos)+
coord_flip() +
ylab('') +
geom_text(aes(label= sum_value_byStage), hjust = 5) +
geom_text(aes(label= count_proj), hjust = -1) +
labs(title = "Emma: 4 projects| $90M Values \n \n Commitment|Projects") +
theme(plot.title = element_text(hjust = 0.5)) +
geom_hline(yintercept = 0, linetype =1)
My questions are:
Why is the y-values not showing up right? e.g. C is labeled 20, but nearing hitting 100 on the scale.
How to adjust the position of labels so that it sits on the top of its bar?
How to re-scale the y axis so that both the very short bar of 'count of project' and long bar of 'Project value' can be well displayed?
Thank you all for the help!
I think your issues are coming from the fact that:
(1) Your dataset has duplicated values. This causes geom_bar to add all of them together. For example there are 3 obs for B where proj_value_by_manager = 90 which is why the blue bar extends to 270 for that group (they all get added).
(2) in your second geom_bar you use y = -proj_value_by_manager but in the geom_text to label this you use sum_value_byStage. That's why the blue bar for A is extending to 90 (since proj_value_by_manager is 90) but the label reads 20.
To get you what I believe the chart you want is you could do:
#Q1: No dupe dataset so it doesnt erroneous add columns
input2 <- input[!duplicated(input[,-c(2,4)]),]
ggplot (input2, aes(x=stage, y = count_proj)) +
geom_bar(stat = 'identity')+
geom_bar(aes(y=-sum_value_byStage), #Q1: changed so this y-value matches your label
stat = "identity", fill = "Blue") +
scale_y_continuous(labels = commapos)+
coord_flip() +
ylab('') +
geom_text(aes(label= sum_value_byStage, y = -sum_value_byStage), hjust = 1) + #Q2: Added in y-value for label and hjust so it will be on top
geom_text(aes(label= count_proj), hjust = -1) +
labs(title = "Emma: 4 projects| $90M Values \n \n Commitment|Projects") +
theme(plot.title = element_text(hjust = 0.5)) +
geom_hline(yintercept = 0, linetype =1)
For your last question, there is no good way to display both of these. One option would be to rescale the small data and still label it with a 1 or 3. However, I didn't do this because once you scale down the blue bars the other bars look OK to me.