# How to draw neat polygons around scatterplot regions in ggplot2

How do I add a neat polygon around a group of points on a scatterplot? I am using ggplot2 but am disappointed with the results of geom_polygon.

The dataset is over there, as a tab-delimited text file. The graph below shows two measures of attitudes towards health and unemployment in a bunch of countries:

I would like to switch from geom_density2d to the less fancy but empirically more correct geom_polygon. The result on unsorted data is unhelpful:

How do I draw 'neat' polygons that behave as contour paths around the min-max y-x values? I tried sorting the data to no avail.

Code:

print(fig2 <- ggplot(d, aes(man, eff, colour=issue, fill=issue)) +
geom_point() + geom_density2d(alpha=.5) + labs(x = "Efficiency", y = "Mandate"))


The d object is obtained with this CSV file.

Solution:

Thanks to Wayne, Andy W and others for their pointers! The data, code and graphs have been posted to GitHub. The result looks like this:

-
The term you might be looking for is the convex hull of the points (or potentially the alpha hull). You should be able to find an R function to calculate these and then be able to add them as layers to the plot. –  Andy W Feb 14 '12 at 17:14
Thanks for pointing out the correct terminology! I have failed to use ?chull with ggplot2 so far. I am not sure that I am coding it right, and hope that someone has done it already. –  Fr. Feb 14 '12 at 17:55
Could you add your R code to question? –  Yuriy Petrovskiy Feb 14 '12 at 20:07
One thing to note: what you're displaying are the maxima, which may be "outliers". I believe the R package alphahull works similar to finding the convex hull, but allows you to adjust it inwards/outwards to try to do something like confidence intervals. –  Wayne Feb 22 '12 at 14:49
@Wayne, an alpha hull is not a confidence interval (in any way imaginable). See this gis.se question for a brief description and some references of what an alpha hull is. Perhaps your thinking of bivariate confidence ellipses, or maybe even bagplots (bi-variate boxplots for identifying outliers). –  Andy W Feb 23 '12 at 16:27
show 3 more comments

With some googling I came across the website of Gota Morota who has an example of doing this already on her website. Below is that example extended to your data.

library(ggplot2)
work <- "E:\\Forum_Post_Stuff\\convex_hull_ggplot2"
setwd(work)

#note you have some missing data
mydata <- read.table(file = "emD71JT5.txt",header = TRUE, fill = TRUE)
nomissing <- na.omit(mydata) #chull function does not work with missing data

#getting the convex hull of each unique point set
df <- nomissing
find_hull <- function(df) df[chull(df$eff, df$man), ]
hulls <- ddply(df, "issue", find_hull)

plot <- ggplot(data = nomissing, aes(x = eff, y = man, colour=issue, fill = issue)) +
geom_point() +
geom_polygon(data = hulls, alpha = 0.5) +
labs(x = "Efficiency", y = "Mandate")
plot

-
(+1) This link doesn't refer to Hadley's website. –  chl Feb 15 '12 at 10:50
Thanks, I'll revise the code accordingly. Unfortunately, your image file does not seem to load here, but the code is there. –  Fr. Feb 15 '12 at 10:52
@Fr. , What exactly is the problem? chl - Thanks for the note! I have updated the post accordingly. –  Andy W Feb 15 '12 at 12:46
@AndyW Unfortunately, the code does not support missing values, and I did not find a way to tweak it to do so. –  Fr. Feb 15 '12 at 18:02
@Fr., How exactly do you want missing data values to be handled besides eliminating those observations? Any reasonable imputation technique would result in the points being inside the convex hulls of the non-missing observations. –  Andy W Feb 15 '12 at 18:07
show 4 more comments

If I understand your problem, you're looking for the convex hull of health and of unemployment. There are probably several packages to do this in R, one of which is package geometry. I'd imagine that the points are sorted in order around the perimeter, but you'd have to check that.

EDIT: Here's an example, which doesn't use ggplot, but I hope it's useful. The example in the chull documentation seems to be wrong, which might be throwing you off:

X <- matrix(rnorm(2000), ncol = 2)
X.chull <- chull (X)
X.chull <- c(X.chull, X.chull[1])
plot (X)
lines (X[X.chull,])


EDIT 2: OK, here is something using ggplot2. We have a data.frame called foo, which has variables foo\$x and foo\$y. Then:

hull <- chull (foo)
hull <- c(hull, hull[1])
bar <- data.frame (x=foo$x[hull], y=foo$y[hull])
# Or bar <- foo[hull,] since we're using the same variable names, x and y

ggplot (foo, aes (x=x, y=y)) + geom_polygon (data=bar, fill="red") + geom_point ()


which is tricky. Note that the geom_point is using the data (foo) and aes from the ggplot, while I'm overriding it in the geom_polygon.

TO get it fully, you'd need to put the x and y for the hull for both issues into bar, using a third column issue to differentiate them, just as for your original foo.

-
Correct about the convex hull. I have tried to use chull to generate the convex hull but failed to use the results with ggplot2. –  Fr. Feb 14 '12 at 17:55
@Fr.: I've done a quick edit of my answer. See if that puts you on the right track. –  Wayne Feb 14 '12 at 18:09
I can see how it works on its own, but I wonder how to obtain that last line with ggplot2. –  Fr. Feb 14 '12 at 22:40
@Fr.: OK, how about now? –  Wayne Feb 14 '12 at 23:15
It worked! Thanks. I had to add na.omit to get rid of NA that stop chull from working. Thanks again. –  Fr. Feb 15 '12 at 10:36