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:

scatterplot with density2d

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:

enter image description here

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.


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.


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:


  • 6
    $\begingroup$ 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. $\endgroup$
    – Andy W
    Feb 14, 2012 at 17:14
  • $\begingroup$ 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. $\endgroup$
    – Fr.
    Feb 14, 2012 at 17:55
  • $\begingroup$ Could you add your R code to question? $\endgroup$ Feb 14, 2012 at 20:07
  • $\begingroup$ 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. $\endgroup$
    – Wayne
    Feb 22, 2012 at 14:49
  • $\begingroup$ @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). $\endgroup$
    – Andy W
    Feb 23, 2012 at 16:27

3 Answers 3


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.

enter image description here

work <- "E:\\Forum_Post_Stuff\\convex_hull_ggplot2"

#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")
  • $\begingroup$ Thanks, I'll revise the code accordingly. Unfortunately, your image file does not seem to load here, but the code is there. $\endgroup$
    – Fr.
    Feb 15, 2012 at 10:52
  • $\begingroup$ @Fr. , What exactly is the problem? $\endgroup$
    – Andy W
    Feb 15, 2012 at 12:46
  • $\begingroup$ @AndyW Unfortunately, the code does not support missing values, and I did not find a way to tweak it to do so. $\endgroup$
    – Fr.
    Feb 15, 2012 at 18:02
  • $\begingroup$ @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. $\endgroup$
    – Andy W
    Feb 15, 2012 at 18:07
  • $\begingroup$ @AndyW I mean that the NA kill the chull function. I would expect it to just ignore it, but it fails at doing so and I did not find a way to use na.omit() to make it work. I'm sure it's possible, I just don't have the hackery skills to go beyond the previous solution. $\endgroup$
    – Fr.
    Feb 15, 2012 at 20:07

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 turn X into a data.frame with variables x and y. Then:

X <- as.data.frame(X)
hull <- chull(X)
hull <- c(hull, hull[1])
ggplot(X, aes(x=x, y=y)) + geom_polygon(data=X[hull,], fill="red") + geom_point()

Note that the geom_point is using the data (X) 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.

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

As of this afternoon, I've wrapped the chull function inside an R package as a geom_convexhull function.

Once the package is loaded, it can be used as any other geom, in your case it should be something like :

ggplot(d, aes(man, eff, colour=issue, fill=issue)) + 
  geom_convexhull(alpha=.5) + 
  geom_point() + 
  labs(x = "Efficiency", y = "Mandate"))

The package is available on github : https://github.com/cmartin/ggConvexHull

  • $\begingroup$ Thanks a lot for this! I was getting frustrated from undesired output when trying to apply chull across a grouping factor until I found this. $\endgroup$
    – jogall
    Feb 10, 2018 at 16:03

Not the answer you're looking for? Browse other questions tagged or ask your own question.