ggplot2: independent axes for grid facet - r

I have xy data on a grid with some categorical variables. Here is some dummy data.
fn <- function(x,group,type) {
y <- expand.grid(x=seq(1,x),y=seq(1,x))
y$group <- group
y$type <- type
return(y)
}
dfr <- rbind(
fn(5,"a","1"),
fn(5,"b","1"),
fn(20,"c","1"),
fn(10,"a","2"),
fn(50,"c","2")
)
head(dfr)
x y group type
1 1 1 a 1
2 2 1 a 1
3 3 1 a 1
4 4 1 a 1
5 5 1 a 1
6 1 2 a 1
I need them on a grid layout due to the grouping variables.
Default grid
Good: Keeps grid layout, Gaps between points fixed, aspect ratio fixed
Bad: Data does not fill facet
ggplot(dfr,aes(x,y))+
geom_point()+
facet_grid(group~type)+
theme(aspect.ratio=1)
Grid free scales
Good: Keeps grid layout, aspect ratio fixed.
Bad: Data does not fill facet, Gaps between points differ
ggplot(dfr,aes(x,y))+
geom_point()+
facet_grid(group~type,scales="free")+
theme(aspect.ratio=1)
Grid free scales + free space
Good: Keeps grid layout, Gaps between points fixed
Bad: Data does not fill facet, Aspect ratio cannot be set
ggplot(dfr,aes(x,y))+
geom_point()+
facet_grid(group~type,scales="free",space="free")
Wrap free scales
Good: Data fills facet, aspect ratio fixed
Bad: Loses grid layout, gap between points not equal
ggplot(dfr,aes(x,y))+
geom_point()+
facet_wrap(group~type,scales="free")+
theme(aspect.ratio=1)
I would like all of these combined.
Keeps grid layout
Gaps between points fixed
Aspect ratio fixed
Data fills the facet (ie; variable size facets)
I think this is only possible using grid layout with independent axes. Is there some option I am missing or do I have to build this manually?
Update: 1
Expected end result.
Update 2:
I think to be able to achieve what I want, the facets need to have free scales, independent axes, fixed aspect ratio and variable sized facets/free space. The first three together are possible using gghx.
library(gghx)
ggplot(dfr,aes(x,y))+
geom_point()+
facet_grid2(group~type,scales="free",independent="all")+
theme(aspect.ratio=1)

Related

Preventing incosistent spacing/bar widths in geom_bar with many bars

In a bar plot with lots of bars the problem occurs that the spacing between bars and/or the widths of the bars becomes incosistent, also changing with changing the width of the plot.
set.seed(23511)
dat <- data.frame(x = 1:540, y = rnorm(540))
library(ggplot2)
ggplot(dat) +
geom_bar(aes(x = x, y = y), stat = "identity")
Is there a way to solve this? I tried playing with width and the overall plot size to no avail.
In response to alistaire's comment here's a screenshot of the first few bars from RStudio. Looking at the first 10 values..
x y
1 1 0.9450960
2 2 0.9277378
3 3 0.4371033
4 4 -1.0333073
5 5 2.0473397
6 6 0.8174123
7 7 0.4277842
8 8 -0.4336887
9 9 0.2156801
10 10 0.4918345
.. to me it clearly looks like for the first 3 positive values there's space between the bars/the bars are narrower than for the second set of 3 positive values where there's no space between the bars/the bars are wider.
I think this is a pixel issue. If the x of a bar goes from 1.5 to 2.7 pixels, it will be one pixel wide, if it goes from 1.9 to 3.1 (same width) it will be 2 pixels wide.
You could do lines instead of bars.
ggplot(data=dat, aes(x=x, y=y)) +
geom_segment(aes(xend=x, yend=0), size = 0.6)
I think you still sometimes run into pixel issues, but it's maybe easier to control with size.

ggplot grid with constant x-axis scale but varying axis limits

Take a look at the following plotting code:
library(ggplot2)
library(cowplot)
a <- data.frame(a1=1:10, a2=1:10)
b <- data.frame(b1=1:5, b2=2*(1:5))
aplot <- ggplot(a, aes(x=a1, ymin=0, ymax=12)) +
geom_line(aes(y=a2))
bplot <- ggplot(b, aes(x=b1, ymin=0, ymax=12)) +
geom_line(aes(y=b2))
plot_grid(aplot,bplot, ncol=2)
It yields two side-by-side plots of identical dimensions showing similar lines. But the x-axis scales are rather different. In fact, the second line has twice the slope of the first.
I am looking for a way to plot this figure so that the width of a plot is scaled by the limits of its x-axis, so that the slopes can be compared visually. The real plots I am interested in visualizing are five in number and will lack y-axis labels except for the leftmost. I can use grid.arrange() to plot them all in a row with whatever widths I want, but the problem is that I don't know what width to assign to each panel to make sure they come out right (the panel width has to be large enough to accommodate the plot margins, the y-axis tick marks, and the y-axis text). I can set the margins myself and account for them in my panel widths, but I cannot find a good way to figure out how wide (e.g. in cm) the y-axis text is.
You can use the rel_widths option in plot_grid to achieve this. You need to calculate the relative size you want each plot to be using the ratio of the ranges xmax-xmin of each panel. But, there is an extra catch. rel_widths sets the relative width of the whole panel, including the margins. So we also need to account for the margins in calculating the relative size. In the following code, adding an offset value of 2 to the numerator and denominator of relative.size works for this. But note that this offset value may change if you alter the size of the margins.
aplot <- ggplot(a, aes(x=a1, ymin=0, ymax=12, xmin=0, xmax=max(a$a1))) +
geom_line(aes(y=a2))
bplot <- ggplot(b, aes(x=b1, ymin=0, ymax=12, xmin=0, xmax=max(b$b1))) +
geom_line(aes(y=b2))
relative.size <- (2+max(b$b1)) / (2+max(a$a1)) # the addition of 2 here is to account for the plot margins
plot_grid(aplot,bplot, ncol=2, rel_widths=c(1,relative.size), align = "h")
gives

distance between bars (position dodge doesn't work)

problem: I want to keep the column with the width I define (0.20); however, I would like to put the columns closer. I am trying to use position_dodge to do it but it doesn't seem to work (I change the width of the position_dodge but I see no change in the output).
data:
SITE N CHL sd se ci
SITE1 20 0.01037453 0.004217003 0.0009429505 0.001973618
SITE2 20 0.01704401 0.006188691 0.0013838333 0.002896396
SITE3 19 0.01191036 0.004188927 0.0009610057 0.002018998
script:
dodge <- position_dodge(width = 0.25)#this is the width I try to change to get the columns closer, but it doesn't do it
ggplot() + geom_bar(data=s_site2chl, aes(x=SITE, y=CHL, fill=SITE),
stat="identity", colour="black", width = .20, position = dodge)
To use position_dodge() does not help, because there is nothing in your plot. The position only matters, if you have several bars with the same x-value. Then, you need to choose, whether you want to stack them on top of each other ("stack"), next to each other ("dodge"), or let them overplot each other ("identity"). But you have only one bar per x-value and thus position is irrelevant.
You can bring the bars closer together by making them wider, as others have suggested. If you set width = 1, the bars will touch, if you choose a smaller value, there will still be a gap:
ggplot() + geom_bar(data=s_site2chl, aes(x=SITE, y=CHL, fill=SITE),
stat="identity", colour="black", width = .8)
You say in one of your comments, that you only want to bring the bars closer together, but not make them wider. I can see at least two ways to achieve this. One would be to just save the plot with a different resolution. The figure above is 590 pixels wide and 380 pixels high. If I store it with half the width, I get this:
The second possibility is that you expand the x-axis more on both sides. You can use scale_x_discrete() as follows:
ggplot() + geom_bar(data=s_site2chl, aes(x=SITE, y=CHL, fill=SITE),
stat="identity", colour="black", width = .8) +
scale_x_discrete(expand = c(0, 2))
expand takes a vector of two with the following meaning (from ?discrete_scale):
a numeric vector of length two, giving a multiplicative and additive constant used to expand the range of the scales so that there is a small gap between the data and the axes. The defaults are (0,0.6) for discrete scales and (0.05,0) for continuous scales.
I set the additive value to 2 (instead of the default 0.6), which adds more space on both sides of the bars and as a result makes them thinner.

re-sizing ggplot geom_dotplot

I'm having trouble creating a figure with ggplot2. I am using geom_dotplot with center stacking to display my data which are discrete values for 4 categories.
For aesthetic reasons I want to customize the positions of the dots so that
reduce the empty space between dots along the y axis, (ie the dots are 1 value large)
The distributions fit and don't overlap
I've adjusted the bin and dotsize to achieve aesthetic goal 1, but that requires me to fiddle with the ylim() parameter to make sure that the groups fit in the plot. This results in a plot with more whitw space and few numbers on the y axis.
Question: Can anyone explain a way to resize the empty space on this plot?
My code is below:.
plot <- ggplot(figdata, aes(y=Counts, x=category, col=strain)) +
geom_dotplot(aes(fill=strain), dotsize=1, binwidth=.7,
binaxis= "y",stackdir ="centerwhole", stackratio=.7) +
ylim(18,59)
plot + scale_color_manual(values=c("#E69F00", "#56B4E9")) +
geom_errorbar(stat="hline", yintercept="mean",
aes( ymax=..y..,ymin=..y.., group = category, width = 0.5),
color="black")
Which produces:
EDIT: Incorporating jitter will allow the all the data to fit, but I don't want to add noise to this data and would prefer to show it as discreet data.
adjusting the binwidth and dotsize to 0.3 as suggested below also fits all the data, however it leaves too much white space.
I think that I might have to transform my data so that the values are steps smaller than 1, in order to get everything to fit horizontally and dot sizes to big large enough to reduce white space.
I think the easiest way is using coord_cartesian:
plot + scale_color_manual(values=c("#E69F00", "#56B4E9")) +
geom_errorbar(stat="hline", yintercept="mean",
aes( ymax=..y..,ymin=..y.., group = category, width = 0.5),
color="black") +
coord_cartesian(ylim=c(17,40))
Which gives me this plot (with fake data that are not as neatly distributed as yours):

Combine ggplots but fix the size/ratio of the plots

I have two plots that I combine. arrangeGrob() squeezes them so that the size of the new image is the same as one alone. How can I arrange them while preserving the ratio/size?
require(ggplot2)
require(gridExtra)
dat <- read.csv("http://www.ats.ucla.edu/stat/data/fish.csv")
frqncy <- as.data.table(table(dat$child))#
frqncy$V1 <- as.numeric(frqncy$V1)
plot1 <- ggplot(frqncy, aes(x=V1, y= N)) +
geom_histogram(stat="identity", binwidth = 2.5)
plot2 <- ggplot(frqncy, aes(x=V1, y= N)) +
geom_density(stat="identity")
plot <- arrangeGrob(plot1, plot2)
Plot looks like
I have not found any parameter in ggplot() or arrangeGrob() that fixes the ratio of the input.
Edit: Additional complications arise from the definition of axis labels in arrangeGrob(), i.e.
plot <- arrangeGrob(plot1, plot2, left="LHS label")
Then the new file will not automaticall shrink to the minimum height/width combination of plot1 and plot2.
there are several other options, depending on what you want*
library(ggplot2)
p = qplot(1, 1)
grid.arrange(p, p, respect=TRUE) # both viewports are square
grid.arrange(p, p, respect=TRUE, heights=c(1,2)) # relative heights
p1 = p + theme(aspect.ratio=3)
grid.arrange(p,p1, respect=TRUE) # one is square, the other thinner
*: the aspect ratio is often not a well-defined property of plots (unless set manually), because the default is to extend the plot to the available space defined by the plot window/device/viewport.
You can control this when you output to a device. For example, a PDF file:
pdf("plot.pdf", width=5,height=8)
plot
dev.off()
Another option is to set a fixed ratio between the x and y coordinates in the plot itself using coord_fixed(ratio=n), where n is the y/x ratio. This will set the relative physical length of the x and y axes based on the nominal value range for each axis. If you use coord_fixed() the graph will always maintain the desired aspect ratio no matter what device size you use for your output.
For example, in your case both graphs have x-range 0 to 3 and y-range 0 to 132. If you set coord_fixed(ratio=1), your graphs will be tall and super skinny because the x-axis length will be 3/132 times the y-axis length (or to put it another way, 1 x-unit will take up the same physical length and 1 y-unit, but there are only 3 x-units and 132 y-units). Play around with the value of ratio to see how it works. A ratio of somewhere around 0.02 is probably about right for your graphs.
As an example, try the following code. Here I've set the ratio to 0.1, so now 1 x-unit takes up 10 times the physical length of each y-unit (that is, 0 to 3 on the x-axis has the same physical length as 0 to 30 on the y-axis).
plot1 <-ggplot(frqncy, aes(x=V1, y= N)) +
geom_histogram(stat="identity", binwidth = 2.5) +
coord_fixed(ratio=0.1)
plot2 <- ggplot(frqncy, aes(x=V1, y= N)) +
geom_density(stat="identity") +
coord_fixed(ratio=0.1)
plot <- arrangeGrob(plot1, plot2)
pdf("plot.pdf", 5,8)
plot
dev.off()

Resources