ggplot in R: barchart with log scale label misplacement - r

So I have a bar chart to make, and a log plot for y axis is warranted because of the data range. So the problem is I have the value of 0.5, which in log10 is -0.3.
Since the bar goes into negative, the "top" of the bar, which is used for placing the labels is actually the "bottom" and so my text label is "just above" the bottom, which means in the middle of the bar.
I figure I am probably not the first person with this issue, but searching for related fixes has not helped. Most notably, I tried using dodge, but this does not change that the "top" of the bar is really the "bottom".
So two questions:
Can I fix this label mishap?
This is just ugly: can I move the x axis up to y=1 to give more context to the negative value without moving the x axis labels?
.
alpha=c('A','B','C','D')
value=c(0.5,10,40,1100)
table<-as.data.frame(alpha)
table<-cbind(table, value)
library(ggplot2)
graph <- ggplot(table, aes(x=alpha)) +
geom_bar(stat="identity",aes(y=value),width=0.5) +
geom_text(aes(y=value,label=value),vjust=-0.5) +
scale_y_continuous(trans="log10",limits=c(0.5,1400))
graph + theme_classic()

A little trick modifying the y coordinate of the data labels (use ifelse() to set the value of y to 1 if the value is less than one). As for the axis, simply hide the X axis (setting it to element_blank()) and draw a new horizontal line:
graph <- ggplot(table, aes(x=alpha)) +
geom_bar(stat="identity",aes(y=value),width=0.5) +
# Modify the placing of the label using 'ifelse()':
geom_text(aes(y=ifelse(value < 1, 1, value),label=value),vjust=-0.5) +
scale_y_continuous(trans="log10",limits=c(0.5,1400)) +
theme_classic() +
# Hide the X axis:
theme(axis.line.x = element_blank()) +
# Draw the new axis
geom_hline()
print(graph)
The output:

Related

X axis labels tied to histogram bars instead of following separate rules

When using a histogram with x as a POSIXct value, I'm not sure how you're supposed to line the ticks up with the binsize of the graph.
Setting the tick size to the same as the binsize makes it line a bit off, but the offset adds onto each other until its no longer accurate.
bymonth <- ggplot() +
scale_x_datetime("", breaks = date_breaks("60 days"), labels = date_format("%m-%y")) +
...
lots of geom_rects for background colors
...
theme(legend.title = element_blank()) +
geom_histogram(data=dat, aes(x = iso, fill = name), binwidth = 30*24*60*60, position = 'dodge')
I tried using annotate() as well as experimenting with the spacing of the tick but I think my approach here might be wrong in its own accord
This leads to a graph looking something like this
Which is quite annoying

prevent custom text to increase plot size

I am writing some text on my plot, which works perfectly fine. Now I realized that if I put the text further down the y-axis, the plot space somehow becomes larger. This is not what I want. The following 2 plots illustrate the issue. The first one puts the text at y = 0, whereas the second one puts it at y = the min-0.25, so roughly where the plot space begins vertically. Is it possible to keep the plot space as in the first plot, but still to write at the bottom? If there is no intended way to adjust that, I would also be happy with a workaround.
set.seed(12)
test <- data.table(x = rnorm(29*2),var=c(rep("x1",29),rep("x2",29)),
time=rep(seq(as.Date("1983/12/31"),as.Date("2011/12/31"), "year"),2))
library(ggplot2);library(scales)
ggplot(data=test,aes(x=time, y=x, colour=var)) +
geom_line() + scale_x_date(date_labels="%Y",date_breaks = "3 years") +
geom_text(aes(x=as.Date("1988-04-30"), label="Text which does not increases space", y=0,
fontface="plain"), angle=60, colour="black",vjust=0,hjust=0,size = 4)
ggplot(data=test,aes(x=time, y=x, colour=var)) +
geom_line() + scale_x_date(date_labels="%Y",date_breaks = "3 years") +
geom_text(aes(x=as.Date("1988-04-30"), label="Text which increases space", y=min(test[,x])-0.25,
fontface="plain"), angle=60, colour="black",vjust=0,hjust=0,size = 4)
As any other geom, geom_text expands the plotting area in cases like this. Then it's necessary to manually restrict the y axis with ylim(range(test$x)). However, that's not enough, as then the text wouldn't be fully visible and, as a result, ggplot completely drops it. To fix this, we need clip = "off". Thus, adding
coord_cartesian(clip = "off", ylim = range(test$x))
gives
where now axes are unchanged.

Realigning x-axis on geom_histogram in ggplot2

When creating a geom_histogram in ggplot, the bin labels appear directly underneath the bars. How can I make it so that they appear on either side of the bin, so that they describe the range of each bin (so that the bin that includes cases from 0 to 10 will appear between the 0 and 10 labels)?
I tried using
geom_histogram(position=position_nudge(5))
However, the histogram I'm using is stacked (to differentiate categories within each bin), and this effect is ruined when I add this position. Is there another way of doing it? Maybe moving the axis labels themselves instead of the bars?
Reproducible code:
dd<-data.frame(nums=c(1:20,15:30,40:55),cats=c(rep("a",20),rep("b",30),rep("c",2)))
ggplot(dd, aes(nums))+geom_histogram(aes(nums,fill=cats),dd,binwidth = 10)
results in this:
I want the bars to be shifted to the right by 5, so that the 0 aligns with the left-hand side of the histogram
You can try to define breaks and labels
n <- 10
ggplot(dd, aes(nums, fill=cats)) +
geom_histogram(binwidth = n, boundary = 0) +
scale_x_continuous(breaks = seq(0,55,n), labels = seq(0,55, n))
The following moves the labels of the axis. I wasn't sure how to move the ticks on the x axis so I removed them.
ggplot(dd, aes(nums))+geom_histogram(aes(nums),dd,binwidth = 10)+
theme(axis.text.x = element_text(hjust = 5),
axis.ticks.x = element_blank())

ggplot2 bar plot: hjust depending on bar and label size

Being new to R, I produced very simple horizontal bar plots using ggplot2 and coord_flip().
Notably, I insert the values of the x variable at the left side of the bar by default (or at the right side if the label does not fit) using the following command:
geom_text(aes(x=TYPE, y=COUNT, ymax=COUNT, label=COUNT,
hjust=ifelse(COUNT>1000, 1.5, -0.3)),
size=3.5, position = position_dodge(width=0.8))
The problem is that, depending on the data-sets, the x values can vary significantly (e.g. dataset_1 x values can be between 1 to 200; dataset_2 x values can be between 10,000 to 100,000; ...), which causes the label of the shortest bar to be misplaced with the ifelse statement I am using (see brown bar in figure A below).
In this case I cannot just use a constant COUNT>1000 condition for all the datasets.
Figure A:
I could modify manually the value of the hjust=ifelse(COUNT>1000,...statement for each dataset.
But I was wondering if it is possible to automatically move the label outs of the bar if it does not fit between the axis and the top of the bar without modifying the value of the ifelse condition for each dataset, like in figure B below.
Figure B :
EDIT
Workaround (not perfect but better):
Placing the label at the right of the bar if the value is less than 5% of the maximum value
MAXI <- max(data[,2])
geom_text(aes(x=TYPE, y=COUNT, ymax=COUNT, label=COUNT,
hjust=ifelse((COUNT/MAXI)<0.05, -0.3, 1.3)))
Having some labels outside the bars and some inside can distort the visual encoding of magnitude as the length of the bar. Another option is to put the values in the middle of the bar but set geom_text to skip values that are small relative to the maximum bar. Or, if you want to include text for all the bar values added, you can put them below the bars in order to keep a clean visual pattern for the bar lengths. Examples of both options are below:
# Fake data
dat = data.frame(x = LETTERS[1:5], y=c(432, 1349, 10819, 5489, 12123))
ggplot(dat, aes(x, y, fill=x)) +
geom_bar(stat="identity") +
geom_text(aes(label=ifelse(y < 0.05*max(dat$y), "", format(y, big.mark=",")), y=0.5*y),
colour="white") +
coord_flip(xlim=c(0.4,5.6), ylim=c(0, 1.03*max(dat$y)), expand=FALSE) +
guides(fill=FALSE)
ggplot(dat, aes(x, y, fill=x)) +
geom_hline(yintercept=0, lwd=0.3, colour="grey40") +
geom_bar(stat="identity") +
geom_text(aes(label=format(y, big.mark=","), y=-0.01*max(dat$y)),
size=3.5, hjust=1) +
coord_flip(ylim = c(-0.04*max(dat$y), max(dat$y))) +
guides(fill=FALSE)

ggplot2: How to swap y-axis and right facet strip and how to order facet values

With the following code:
library(ggplot2)
set.seed(6809)
diamonds <- diamonds[sample(nrow(diamonds), 1000), ]
diamonds$cut <- factor(diamonds$cut,
levels = c("Ideal", "Very Good", "Fair", "Good", "Premium"))
# Repeat first example with new order
p <- ggplot(diamonds, aes(carat, ..density..)) +
geom_histogram(binwidth = 1)
p + facet_grid(color ~ cut)
I can create the following figure:
My questions are:
How can I sort the right strip according to my desired order
e.g. (G, F, D, E, I, J, H)?
How can I swap the right strip to with the y-axis (density)?
Update for ggplot2 2.2.1
With ggplot2 version 2, you can switch the positions of the axis labels and facet labels. So here's updated code that takes advantage of these features:
# Reorder factor levels
diamonds$color = factor(diamonds$color, levels=c("G","F","D","E","I","J","H"))
ggplot(diamonds, aes(carat, ..density..)) +
geom_histogram(binwidth=1) +
facet_grid(color ~ cut, switch="y") + # Put the y facet strips on the left
scale_y_continuous("density", position="right") + # Put the y-axis labels on the right
theme(strip.text.y=element_text(angle=180))
Original Answer
As #joran said, you have to modify the grid object if you want full control over what goes where. That's painful.
Here's another approach that's still a hassle, but easier (for me at least) than modifying the grid object. The basic idea is that we orient the various facet and axis labels so that we can rotate the plot 90 degrees counter-clockwise (to get the facet labels on the left side) while still having all the labels oriented properly.
To make this work, you need to modify the graph in several ways: Note my addition of coord_flip, all the theme stuff, and scale_x_reverse. Note also that I've switched the order of the facet variables, so that color starts out on top (it will be on the left after we rotate the graph).
# Reorder factor levels
diamonds$color = factor(diamonds$color, levels=rev(c("G","F","D","E","I","J","H")))
p <- ggplot(diamonds, aes(carat, ..density..)) +
geom_histogram(binwidth = 1) +
facet_grid(cut ~ color) + coord_flip() +
theme(strip.text.x=element_text(angle=-90),
axis.text.y=element_text(angle=-90, vjust=0.5, hjust=0.5),
axis.text.x=element_text(angle=-90, vjust=0.5, hjust=0),
axis.title.x=element_text(angle=180),
axis.title.y=element_text(angle=-90)) +
scale_x_reverse()
One option is to save the graph and then rotate it in another program (such as Preview, if you're on a Mac). However, with the help of this SO answer, I was able to rotate the plot within R. It required some trial and error (with my limited knowledge of how to manipulate grid objects) to get the right size for the viewport. I saved it as a PNG for posting on SO, but you can of course save it as a PDF, which will look nicer.
png("example.png", 500,600)
pushViewport(viewport(width = unit(8, "inches"), height = unit(7, "inches")))
print(p, vp=viewport(angle=90))
dev.off()
And here's the result:
facet_grid has the attribute "switch".
switch: By default, the labels are displayed on the top and right of the plot. If "x", the top labels will be displayed to the bottom. If "y", the right-hand side labels will be displayed to the left. Can also be set to "both".
manual here
facet_grid(cut ~ color, switch = "y")

Resources