I'm doing quantitative image analysis, and visualizing the results with ggplot2. The output contains one datapoint for each pixel in the original image.
geom_raster() nicely visualizes my data in R. But it would be nice to output a raster image corresponding to the results. That way, I could flip through several derived images using a lightweight image viewer (e.g., feh), and the pixels would line up perfectly.
Is there an easy way to output the pixels, and only the pixels, to an image file? No legend, no axes, nothing but the pixels. Assume my data.frame has columns for row and col, and the desired output resolution is also known.
Here's one way:
library(ggplot2)
library(reshape2) # for melt(...)
n <- 100
set.seed(1) # for reproducible example
img <- matrix(rnorm(n^2,30,3),nc=n)
gg <- melt(data.frame(x=1:n,img),id="x")
ggplot(gg) + geom_raster(aes(x=x,y=variable,fill=value))+
scale_x_continuous(expand=c(0,0))+ # get rid of extra space on x-axis
guides(fill=FALSE)+ # turn off color legend
theme(axis.text=element_blank(), # turn off the axis annotations
axis.ticks=element_blank(),
axis.title=element_blank())
Thanks to jlhoward for pointing me in the right direction. There are a few more missing ingredients -- for instance, without labs(x=NULL, y=NULL), the output PNG will have white borders on the bottom and left.
I decided my solution should have two parts:
Craft a ggplot object to visualize my data. (This step is the same as usual.)
Call a general-purpose function to take care of all the annoying details which are necessary to output that plot as a pixel-perfect PNG.
Here is one such function.
BorderlessPlotPng <- function(plot, ...) {
# Write a ggplot2 plot to an image file with no borders.
#
# Args:
# plot: A ggplot2 plot object.
# ...: Arguments passed to the png() function.
require(grid)
png(type='cairo', antialias=NULL, units='px', ...)
print(plot
+ theme(plot.margin=unit(c(0, 0, -0.5, -0.5), 'line'),
axis.text=element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank(),
legend.position='none')
+ scale_x_continuous(expand=c(0, 0))
+ scale_y_continuous(expand=c(0, 0))
+ labs(x=NULL, y=NULL)
)
dev.off()
}
To see it in action, here's a plot of some synthetic data. (I made each output pixel 10 pixels wide for demonstration purposes.)
# Synthetic data.
width <- 64
height <- 48
d <- data.frame(row=rep(1:height, each=width),
col=rep(1:width, height),
x=rnorm(n=width * height))
# Construct and print the plot.
library(ggplot2)
plot <- (ggplot(data=d, aes(x=col, y=height + 1 - row, fill=x))
+ geom_raster()
+ scale_fill_gradient2()
)
pixel_size <- 10
BorderlessPlotPng(plot,
filename='test.png',
width=width * pixel_size,
height=height * pixel_size)
Output:
Of course, running with pixel_size <- 1 would give you a 1:1 image, which you could compare to the original image by flipping back and forth.
Related
I have an excel table with the data of the Odds Ratios of different diseases for my study. I want to make a forestplot with the R package ggplot2. I have used this script:
library(ggplot2)
df <- excel.xlsx
fp <- ggplot(data=df, aes(x=Disease, y=OR, ymin=Lower, ymax=Upper)) +
geom_pointrange() +
geom_hline(yintercept=1, lty=2) + # add a dotted line at x=1 after flip
coord_flip() + # flip coordinates (puts labels on y axis)
xlab("Disease") + ylab("OR (95% CI)") +
theme_bw() # use a white background
print(fp)
This makes round black spots for all diseases.I would like to change the shape of the dots on the graph to squares or other different form, but only to some diseases. I would like to change the shape of the points on the graph corresponding to rows 6, 8, 14 and 16 and the rest of the points leave them as they are now.
Thank you in advanced.
I have tried this script but it makes only black spots.
the example code is not reproducible when I'm writing this answer, but I think you just need to specify shape in the aes
This question includes a complete example with multiple shapes
I am trying to create a graph panel with 8 graphs in total ( 4 x 4). Each graph corresponds to a different gene, whereby there are three lines ( one for control, one for UC disease and one for Crohns), representing the average change in expression comparing a first measurement and a second.
The code I am using to run each of the plots is;
s <- ggplot(X876, aes(x=Timepoint, y=value, group=Group)) +
geom_line(aes(color=Group), size=1)+
geom_point(aes(color=Group), size=2.5) +
labs(y="X876") + ylim(0.35, 0.55) +
theme_classic() +
scale_color_manual(values=c("darkmagenta", "deepskyblue4", "dimgrey"))
Using grid.arrange(l, m, n, o, p, q, r, s, nrow=4, nrow=4), creates a graph panel where the y axes names overlap.
I have seen on here about changing the plot margins via,
pl = replicate(3, ggplot(), FALSE)
grid.arrange(grobs = pl)
margin = theme(plot.margin = unit(c(2,2,2,2), "cm"))
grid.arrange(grobs = lapply(pl, "+", margin))
However, I am unsure how this can be applied to increase the vertical height between the plots on the top and bottom rows. For each of the graphs l, m, n, o, p, q, r, s do I need to include
+ theme(plot.margin=unit(c(t,r,b,l),"cm"))
and then run the grid.arrange(l, m, n, o, p, q, r, s, nrow=4, ncol=4)
Please could somebody suggest which values do I need to include for top (t), right(r), bottom (b), left(l) to only increase the distance (by about 3cms) between the top and bottom row? I am trying different values and I'm not getting a decent graph panel yet.
Thank-you
Probably the easiest way is to create your own theme based on the theme_classic theme and then modify the plotting margins (and anything else) the way that you prefer.
theme_new <- theme_classic() +
theme(plot.margin=unit(c(1,0,1,0), "cm")) # t,r,b,l
Then set the theme (will revert back to the default on starting a new R session).
theme_set(theme_new)
The alternative is to use grid.arrange and modify the margins using the grobs as you've already mentioned.
Once the panels have been arranged, you can then modify the top and bottom margins (or left and right) by specifying the vp argument of grid.arrange, which allows you to modify the viewport of multiple grobs on a single page. You can specify the height and width using the viewport function from the grid package.
For example, if you have a list of ggplot() grobs called g.list that contain your individual plots (l,m,n,o,p,q,r,s), then the following would reduce the height of the viewport by 90%, which effectively increases the top and bottom margins equally by 5%.
library(grid)
library(gridExtra)
grid.arrange(grobs = g.list, vp=viewport(height=0.9))
Without your data, I can't test it, especially to see if the y-axes labels overlap. And I don't know why you think increasing the top and bottom margins can solve that problem since the y-axes are, by default, on the left-hand side of the graph.
Anyway, I'll use the txhousing dataset from the ggplot2 package to see if I can reproduce your problem.
library(ggplot2)
data(txhousing)
theme_new <- theme_classic() +
theme(plot.margin=unit(c(0.1,0.1,0.1,0.1), "cm"), text=element_text(size=8))
theme_set(theme_new)
tx.list <- split(txhousing, txhousing$year)
g.list <- lapply(tx.list, function(data)
{
ggplot(data, aes(x=listings, y=sales)) +
geom_point(size=0.5)
} )
grid.arrange(grobs = g.list, vp=viewport(height=0.9))
I don't see any overlapping. And I don't see why increasing the top and bottom margins would make much difference.
The question was asked a couple of years ago, but I bumped into it only now and thought that I might share a quick and dirty tip for this, which works good enough in many cases.
In some situations the theme is already so complex that this trick might be the easiest way: adding a few \n's (newlines) to the x and y axis names, as this will affect the distances between the plots in the panel. I've learned this trick for a slightly different purpose from here (originally from here).
I'll use the same logic for the example dataset (in this case: Orange from R built-in data sets) as in the excellent code by the previous answerer.
library(ggplot2)
library(gridExtra)
or.list <- split(Orange, Orange$Tree)
g.list <- lapply(or.list, function(data)
{
ggplot(data, aes(x=age, y=circumference)) +
theme_classic() +
geom_point(size=0.5) +
scale_x_continuous(name = "Age\n\n") +
scale_y_continuous(name = "\n\n\nCircumference")
} )
grid.arrange(grobs = g.list)
When creating PNG files for a document using ggplot2 and geom_tile, is there a way to set the size of the tiles to some absolute unit (cm, pt)? I have multiple plots that, when inserted into a document, do not have the exact same tile sizes. Of course I could change the width and height of the plots manually, so the tiles almost match each other, but that's cheating...
The variable names on the x- and y-axis have different lengths. So rather than setting the dimensions of the whole plot (including all the labels), I would like to set the dimensions of only the plot area to some fixed value, maybe through scale_x/y_discrete? Or maybe the individual plots can be extracted after they have been resized with gridExtra or gtable?
# Create some example data.
set.seed(1)
dt1 <- data.table(x=letters[1:5], y=rep(c("aaaaaaaaa", letters[2:3]), rep(5,3)),
value=runif(5*3))
dt2 <- data.table(x=letters[1:4], y=rep(letters[1:4], rep(4,4)),
value=runif(4*4))
# Make two tile plots.
p1 <- ggplot(dt1, aes(x, y, fill=value)) +
geom_tile() +
coord_equal(); p1
p2 <- ggplot(dt2, aes(x, y, fill=value)) +
geom_tile() +
coord_equal(); p2
# Save tile plots to drive.
# ggsave("p1.png", p1, width=5)
# ggsave("p2.png", p2, width=5)
Here's a screenshot from a document with PNG images inserted, note that the labels have the same size:
And this is what I'm after: the absolute distance between the red arrows is identical in both images, without resizing the labels.
Any suggestions greatly appreciated!
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()
I am trying to make a map with two legends denoting shape and colour ("Type" and "Org" in the example below), and have the legends inset. I can place the legends, but I would like them to be left justified so that their left edges line up. I can't make them anything other than centred with respect to each other:
require(ggplot2)
require(ggmap)
require(grid)
require(mapproj)
data <- data.frame(Org=rep(c("ABCDEFG","HIJKLMNOP","QRSTUVWX"),4)
, Type=rep(c("Y","Z"),6), Lat=runif(12,48,54.5)
, Long=runif(12,-133.5,-122.5))
osmMap <- get_map(location=c(-134,47.5,-122,55), source = 'osm')
points <- geom_jitter(data=data, aes(Long, Lat, shape=Type
, colour=Org))
legend <- theme(legend.justification=c(0,0), legend.position=c(0,0)
, legend.margin=unit(0,"lines"), legend.box="vertical"
, legend.key.size=unit(1,"lines"), legend.text.align=0
, legend.title.align=0)
ggmap(osmMap) + points + legend
This option is now available in ggplot2 0.9.3.1, use
ggmap(osmMap) + points + legend + theme(legend.box.just = "left")
Old, manual solution:
Here is a solution:
require(gtable)
require(ggplot2)
require(ggmap)
require(grid)
require(mapproj)
# Original data
data <- data.frame(Org=rep(c("ABCDEFG","HIJKLMNOP","QRSTUVWX"),4),
Type=rep(c("Y","Z"),6), Lat=runif(12,48,54.5),
Long=runif(12,-133.5,-122.5))
osmMap <- get_map(location=c(-134,47.5,-122,55), source = 'google')
points <- geom_jitter(data=data, aes(Long, Lat, shape=Type, colour=Org))
legend <- theme(legend.justification=c(0,0), legend.position=c(0,0),
legend.margin=unit(0,"lines"), legend.box="vertical",
legend.key.size=unit(1,"lines"), legend.text.align=0,
legend.title.align=0)
# Data transformation
p <- ggmap(osmMap) + points + legend
data <- ggplot_build(p)
gtable <- ggplot_gtable(data)
# Determining index of legends table
lbox <- which(sapply(gtable$grobs, paste) == "gtable[guide-box]")
# Each legend has several parts, wdth contains total widths for each legend
wdth <- with(gtable$grobs[[lbox]], c(sum(as.vector(grobs[[1]]$widths)),
sum(as.vector(grobs[[2]]$widths))))
# Determining narrower legend
id <- which.min(wdth)
# Adding a new empty column of abs(diff(wdth)) mm width on the right of
# the smaller legend box
gtable$grobs[[lbox]]$grobs[[id]] <- gtable_add_cols(
gtable$grobs[[lbox]]$grobs[[id]],
unit(abs(diff(wdth)), "mm"))
# Plotting
grid.draw(gtable)
This does not depend on Type or Org. However, this would not be enough having more than two legends. Also, in case you do some changes so that list of grobs (graphical objects) is altered, you might need to change grobs[[8]] to grobs[[i]] where i is the position of your legends, see gtable$grobs and look for TableGrob (5 x 3) "guide-box": 2 grobs.
Edit: 1. Automatically detecting which grob is legends table, i.e. no need to change anything after modifying other parts of plot. 2. Changed calculation of width differences, now code should work when having any two legends, i.e. in more complex cases as well, for example: