Hot questions for Using Ggplot2 in ggtern

Question:

I'm using ggtern to plot a large dataset in a form of tertiary plot (see below an example).

Till a certain data-size everything was perfect, as I was using geom_density_tern(). As I want to visualize a far more complicated dataset loading all of it and rendering with ggplot becomes impossible (limitation on the memory side). I thought that maybe there could be a workaround by imputing the result of kde2d matrix calculated seperately. And that's where I'm stuck. I would like to know if it is possible to do it anyhow in ggtern?

In any case I add a minimal case of the data structure and plotting that I use at this moment.

require(ggplot2)
require(ggtern) 

set.seed(1) 

mydata <- data.frame(
        x = runif(100, min = 0.25, max = 0.5),
        y = runif(100, min = 0.1, max = 0.4),
        z = runif(100, min = 0.5, max = 0.7))   

plot <- ggtern() + 
        theme_bw() +
        theme_hidetitles() +
        geom_density_tern(data = mydata,
            aes(x = x, y = y, z = z, alpha = ..level.. ), 
            size = 0.1, linetype = "solid", fill = "blue")+
        geom_point(data = mydata, 
            aes(x = x, y = y, z = z), alpha = 0.8, size = 1)
plot

Those extra lines reproduce the density plot in the ternary coordination system:

library(MASS)
dataTern = transform_tern_to_cart(mydata$x,mydata$y,mydata$z)
dataTernDensity <- kde2d(x=dataTern$x, y=dataTern$y, lims = c(range(0,1), range(0,1)), n = 400) 

image(dataTernDensity$x, dataTernDensity$y, dataTernDensity$z)
points(dataTern$x, dataTern$y, pch = 20, cex = 0.1)
segments(x0 = 0, y0 = 0, x1 = 0.5, y1 = 1, col= "white")
segments(x0 = 0, y0 = 0, x1 = 1, y1 = 0, col= "white")
segments(x0 = 0.5, y0 = 1, x1 = 1, y1 = 0, col= "white")

And obtaining this graph:

Thanks in advance for any help!


Answer:

We can solve this using the code which is usually used behind the scenes in the Stat. Having just released ggtern 2.0.1, published on CRAN a couple of days ago after completely re-writing the package to be compatible with ggplot2 2.0.0, I am familiar with an approach that may suit your needs. Incidentally, for you interest, a summary of the new functionality in ggtern 2.0.X can be found here:

Below please find a solution and working code for your problem, which is a density estimate calculated on isometric log-ratio space.

#Required Libraries
library(ggtern)
library(ggplot2)
library(compositions)
library(MASS)
library(scales)

set.seed(1) #For Reproduceability
mydata <- data.frame(
  x = runif(100, min = 0.25, max = 0.5),
  y = runif(100, min = 0.1, max = 0.4),
  z = runif(100, min = 0.5, max = 0.7)) 

#VARIABLES
nlevels  = 7
npoints  = 200
expand   = 0.5

#Prepare the data, put on isometric logratio basis
df     = data.frame(acomp(mydata)); colnames(df) = colnames(mydata)
data   = data.frame(ilr(df)); colnames(data) = c('x','y')

#Prepare the Density Estimate Data
h.est  = c(MASS::bandwidth.nrd(data$x), MASS::bandwidth.nrd(data$y))
lims   = c(expand_range(range(data$x),expand),expand_range(range(data$y),expand))
dens   = MASS::kde2d(data$x,data$y,h=h.est,n=npoints,lims=lims)

#-------------------------------------------------------------
#<<<<< Presumably OP has data at this point, 
#      and so the following should achieve solution
#-------------------------------------------------------------

#Generate the contours via ggplot2's non-exported function
lines  = ggplot2:::contour_lines(data.frame(expand.grid(x = dens$x, y = dens$y),
                                            z=as.vector(dens$z),group=1),
                                 breaks=pretty(dens$z,n=nlevels))

#Transform back to ternary space
lines[,names(mydata)] = data.frame(ilrInv(lines[,names(data)]))

#Render the plot
ggtern(data=lines,aes(x,y,z)) +
  theme_dark() + 
  theme_legend_position('topleft') + 
  geom_polygon(aes(group=group,fill=level),colour='grey50') +
  scale_fill_gradient(low='green',high='red') + 
  labs(fill  = "Density",
       title = "Example Manual Contours from Density Estimate Data")

Question:

I have an issue where geom_hex_tern works perfectly with single plots but the hex bin size and shape gets distorted when I make facets.

library(tidyverse)
library(ggtern)

# My data
dat <- structure(list(Fact2 = c(0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 
  0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 
  0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 
  0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 0.24, 
  0.24, 0.28, 0.28, 0.28, 0.28, 0.28), x = c(0.05, 0.1, 0.1, 0.1, 
    0.15, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.25, 0.25, 0.25, 0.25, 
    0.3, 0.3, 0.35, 0.35, 0.4, 0.4, 0.4, 0.45, 0.45, 0.45, 0.45, 
    0.5, 0.5, 0.5, 0.5, 0.55, 0.55, 0.55, 0.6, 0.6, 0.6, 0.65, 0.7, 
    0.75, 0.05, 0.1, 0.2, 0.3, 0.45), y = c(0.6, 0.5, 0.6, 0.7, 0.55, 
      0.1, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.35, 0.4, 0.45, 0.5, 0.3, 
      0.4, 0.25, 0.4, 0.3, 0.35, 0.4, 0.2, 0.25, 0.35, 0.45, 0.05, 
      0.15, 0.2, 0.25, 0.1, 0.2, 0.3, 0.05, 0.1, 0.25, 0.1, 0.05, 0.05, 
      0.55, 0.5, 0.55, 0.2, 0.25), z = c(0.35, 0.4, 0.3, 0.2, 0.3, 
        0.7, 0.45, 0.4, 0.35, 0.3, 0.25, 0.2, 0.4, 0.35, 0.3, 0.25, 0.4, 
        0.3, 0.4, 0.25, 0.3, 0.25, 0.2, 0.35, 0.3, 0.2, 0.1, 0.45, 0.35, 
        0.3, 0.25, 0.35, 0.25, 0.15, 0.35, 0.3, 0.15, 0.25, 0.25, 0.2, 
        0.4, 0.4, 0.25, 0.5, 0.3), wt = c(0.027, 0.02, 0.016, 0.017, 
          0.043, 0.018, 0.02, 0.023, 0.037, 0.02, 0.018, 0.02, 0.015, 0.043, 
          0.031, 0.033, 0.036, 0.029, 0.015, 0.022, 0.036, 0.022, 0.017, 
          0.02, 0.022, 0.018, 0.019, 0.023, 0.02, 0.065, 0.038, 0.043, 
          0.02, 0.023, 0.063, 0.02, 0.018, 0.025, 0.042, 0.016, 0.015, 
          0.019, 0.017, 0.018, 0.039)), row.names = c(NA, -45L), class = c("tbl_df", 
            "tbl", "data.frame"))


# PLot Fact2 == 0.24 - OK
filter(dat, Fact2 == 0.24) %>%
  ggtern(aes(x = x, y = y, z = z)) + 
  geom_hex_tern(binwidth = 0.05, colour = "black",  aes(value = wt)) 

# PLot Fact2 == 0.28 - OK
filter(dat, Fact2 == 0.28) %>%
ggtern(aes(x = x, y = y, z = z)) + 
  geom_hex_tern(binwidth = 0.05, colour = "black", aes(value = wt)) 

# plot both together - weird hex bin size/shape 
ggtern(dat, aes(x = x, y = y, z = z)) + 
  geom_hex_tern(binwidth = 0.05, colour = "black", aes(value = wt)) +
  facet_wrap(~Fact2) 

The first two plots look good, but the bins are messed up when plotted together via faceting, this only seems to happen when I plot sparse data (few bins) faceting works fine when I have lots of points on each plot. Any advice how I can get the faceted plots looking normal would be much appreciated.


Answer:

I have a working solution, though I can't help thinking I've done it the hard way.

Initially, since you pointed out that the problem goes away when there are lots of bins to be plotted, I experimented with trying to draw lots of extra invisible hexagons with an added dummy variable which controlled the alpha (transparency). Unfortunately, this doesn't work when you are using binned data.

I also tried creating invisible hexagons in a different layer. This is possible, but having the invisible hexagons in a different layer means they no longer coerce the hexagons in the visible layer to the correct shape.

The other thought that occurred was to try a 2 x 2 facet, as I assumed this would normalize the hexagons' shapes. It doesn't.

In the end I decided to just "crack open" the ggplot, get the hex grobs and change their vertices arithmetically. The mathematical stretching itself is straightforward, since the hex grobs are already centred correctly and are exactly half their desired height; we therefore just take the y co-ordinates and subtract the mean of their range from double their value.

The tricky part is getting the grobs in the first place. First you need to convert the ggplot to a table of grobs (ggtern has its own functions to do this). This is simple enough, but the gTable is a deeply nested S3 object, so finding a general solution to the problem of extracting the correct elements was tricky. Putting them back in place in the correct format was complex, requiring nested mapply functions.

However, now that this is done, the logic can all be contained within a function that takes only the ggplot as input and then plots the version with stretched hex grobs (while also returning a gTable silently in case you want to do anything else with it)

fix_hexes <- function(plot_object)
{
  # Define all the helper functions used in the mapply and lapply calls
  cmapply     <-  function(...)    mapply(..., SIMPLIFY = FALSE)
  get_hexes   <-  function(x)      x$children[grep("hex", names(x$children))]
  write_kids  <-  function(x, y) { x[[1]]$children <- y; return(x)}
  write_y     <-  function(x, y) { x$y <- y; return(x)}
  write_all_y <-  function(x, y) { gList <- mapply(write_y, x, y, SIMPLIFY = F)
                                   class(gList) <- "gList"; return(gList) }
  write_hex   <-  function(x, y) { x$children[grep("hex", names(x$children))] <- y; x; }
  fix_each    <-  function(y) {    yval <- y$y
                                   att  <- attributes(yval)
                                   yval <- as.numeric(yval)
                                   yval <- 2 * yval - mean(range(yval))
                                   att  -> attributes(yval)
                                   return(yval)}

  # Extract and fix the grobs
  g_table     <- ggtern::ggplot_gtable(ggtern::ggplot_build(plot_object))
  panels      <- which(sapply(g_table$grobs, function(x) length(names(x)) == 5))
  hexgrobs    <- lapply(g_table$grobs[panels], get_hexes)
  all_hexes   <- lapply(hexgrobs, function(x) x[[1]]$children)
  fixed_yvals <- lapply(all_hexes, lapply, fix_each)

  # Reinsert the fixed grobs
  fixed_hexes            <- cmapply(write_all_y, all_hexes, fixed_yvals)
  fixed_grobs            <- cmapply(write_kids, hexgrobs, fixed_hexes)  
  g_table$grobs[panels]  <- cmapply(write_hex, g_table$grobs[panels], fixed_grobs)

  # Draw the plot on a fresh page and silently return the gTable
  grid::grid.newpage()
  grid::grid.draw(g_table)
  invisible(g_table)
}

So let's see the original plot:

gg <- ggtern(dat, aes(x = x, y = y, z = z)) + 
       geom_hex_tern(binwidth = 0.05, colour = "black", aes(value = wt)) +
       facet_wrap(~Fact2)

plot(gg)

And we can fix it now by simply doing:

fix_hexes(gg)

Question:

I am using ggtern in R to make a ternary plot, and would like to have the axis labels and breaks on my ggtern plot, the same as the original data. In the case of the generated data in the code below, each axis would go to a max of 12, 10 and 4.

Following a previous post I have attempted to use breaks and labels to do this, but each axis is still on the 0-1 scale, labels are missing (due to them being over 1) and the axis lines with labels do not intersect the points on the plot. (How to change labels of a ternary plot made by ggtern?)

library(ggtern)
labFnc <- function(x,digits=2) format(round(unique(x),digits),digits=digits)

mydata <- data.frame(
  x = runif(50, min = 0.25, max = 12),
  y = runif(50, min = 0.1, max = 10),
  z = runif(50, min = 0.5, max = 4),
  value = runif(50, min = 10000, max = 20000))

ggtern(data = mydata,aes(x = x, y = y, z = z,col=value)) + 
  theme_bw() +
  geom_point(alpha = 0.8, size = 3) +
  theme_showarrows() +
  scale_T_continuous(breaks=unique(mydata$x),labels=labFnc(mydata$x))+ 
  scale_L_continuous(breaks=unique(mydata$y),labels=labFnc(mydata$y))+ 
  scale_R_continuous(breaks=unique(mydata$z),labels=labFnc(mydata$z))

Is there a way to do this? Any help would be greatly appreciated.

Edit: I have also tried adding in the tern_limits argument. Whilst this looks to proportionally expand the plot, the data is in the wrong place. and I cant add in my unique breaks as before.

ggtern(data = mydata,aes(x = x, y = y, z = z,col=value)) + 
  theme_bw() +
  geom_point(alpha = 0.8, size = 3) +
  theme_showarrows() +
  tern_limits(T=12, L=10, R=4)


Answer:

The solution you have provided is along the right path, however, all arguments to the limit_term(...) function (or aliases) are expected to be in the range [0,1] which corresponds to [0,100%]. Values can be supplied outside of this range, however, this will serve to resolve limits which will contain values greater than 100% and less than 0%.

In summary, use of the following:

tern_limits(T=12, L=10, R=4)

is actually asking for the ternary limits to be bound by 1200%, 1000% and 400% maxima respectively, which is exactly as your attempted result has been rendered.

Anyway, here are some examples of the limits_tern and zoom functionality.

library(ggtern)

n  = 100
df = data.frame(id=1:n,
                x=runif(n),
                y=runif(n),
                z=runif(n))
base = ggtern(df,aes(x,y,z,color=id)) + geom_point(size=3)
base

#Top Corner
base + limit_tern(1.0,0.5,0.5)

#Left Corner
base + limit_tern(0.5,1.0,0.5)

#Right Corner
base + limit_tern(0.5,0.5,1.0)

#Center Zoom Convenience Function
base + theme_zoom_center(0.4) # Zoom In
base + theme_zoom_center(0.6) # Zoom In
base + theme_zoom_center(0.8) # Zoom In
base + theme_zoom_center(1.0) ##Default as per no zoom
base + theme_zoom_center(1.2) # Zoom Out
base + theme_zoom_center(1.4) # Zoom Out
base + theme_zoom_center(1.6) # Zoom Out
base + theme_zoom_center(1.8) # Zoom Out
base + theme_zoom_center(2.0) # Zoom Out

#Left Zoom Convenience Function 
#   (try theme_zoom_R and theme_zoom_T for Right and Top respectively)
base + theme_zoom_L(0.4) # Zoom In
base + theme_zoom_L(0.6) # Zoom In
base + theme_zoom_L(0.8) # Zoom In
base + theme_zoom_L(1.0) ##Default as per no zoom
base + theme_zoom_L(1.2) # Zoom Out
base + theme_zoom_L(1.4) # Zoom Out
base + theme_zoom_L(1.6) # Zoom Out
base + theme_zoom_L(1.8) # Zoom Out
base + theme_zoom_L(2.0) # Zoom Out

Note: that these are all convenience functions to make zooming easier than controlling the limits independently (which is valid) via scale_X_continuous(...) [X = T,L,R]. Unlike a purely cartesian coordinate system where x and y are independent, in the ternary system, the limits must make sense so that the three apex points satisfy conditions of the simplex.

If you have to control each axis independently, here below is an example where each limits is defined, the axis breaks and the axis labels, for the T, L and R axis respectively. If the limits are nonsensical in terms of the simplex conditions, an error will be thrown.

ggtern() + 
  scale_T_continuous(limits=c(0.5,1.0),
                     breaks=seq(0,1,by=0.1),
                     labels=LETTERS[1:11]) + 
  scale_L_continuous(limits=c(0.0,0.5),
                     breaks=seq(0,1,by=0.1),
                     labels=LETTERS[1:11]) +
  scale_R_continuous(limits=c(0.0,0.5),
                     breaks=seq(0,1,by=0.1),
                     labels=LETTERS[1:11])

Question:

I have this data file that has enough data points for me to plot a "heatmap" in ternary plot. (It is not really heat map, just a scatter plot with enough data points)

library(ggtern)
library(reshape2)

N=90
trans.prob = as.matrix(read.table("./N90_p_0.350_eta_90_W12.dat",fill=TRUE))
colnames(trans.prob) = NULL

# flatten trans.prob for ternary plot
flattened.tb = melt(trans.prob,varnames = c("x","y"),value.name = "W12")
# delete rows with NA
flattened.tb = flattened.tb[complete.cases(flattened.tb),]
flattened.tb$x = (flattened.tb$x-1)/N
flattened.tb$y = (flattened.tb$y-1)/N
flattened.tb$z = 1 - flattened.tb$x - flattened.tb$y

ggtern(data = flattened.tb, aes(x=x,y=y,z=z)) +
  geom_point(size=1, aes(color=W12)) +
  theme_bw() +
  scale_color_gradient2(low = "green", mid = "yellow", high = "red")

Here is what I got:

I want to get something like the following using ggtern:

My question is: How can I get something like the second figure using ggtern?

Edit 1: Sorry for the typo in the file name. I fixed the filename. The data file contains too much data points for me to directly paste them here.

The 2nd figure was produced by a 3rd-party Matlab package ternplot. I want a ternary contour plot that has discrete lines rather than the heatmap in my first figure. To be more specific, I want to specify a list of contour lines such as W12=0.05,0.1,0.15,.... I have played with geom_density_tern and geom_interpolate_tern for hours but still have no clue how to get what I want.

The MATLAB code is:

[HCl, Hha, cax] = terncontour(X,Y,1-X-Y,data,[0.01,0.1,0.2,0.3,0.4,0.5]); 

where X,Y,1-X-Y specify the coordinate on the plot, data stores the values and the vector specifies the values of the contours.


Answer:

WDG, I have made a few small changes to ggtern, for better handling this type of modelling, which has just been submitted to CRAN, so should be available over the next day or so. In the interim, you can download from source from my BitBucket account: https://bitbucket.org/nicholasehamilton/ggtern

Anyway, here is the source, which will work from ggtern version 2.1.2.

I have included the points underneath (with a mild alpha value) so one can observe how representative the interpolation geometry has been:

library(ggtern)
library(reshape2)

N=90
trans.prob = as.matrix(read.table("~/Downloads/N90_p_0.350_eta_90_W12.dat",fill=TRUE))
colnames(trans.prob) = NULL

# flatten trans.prob for ternary plot
flattened.tb = melt(trans.prob,varnames = c("x","y"),value.name = "W12")
# delete rows with NA
flattened.tb   = flattened.tb[complete.cases(flattened.tb),]
flattened.tb$x = (flattened.tb$x-1)/N
flattened.tb$y = (flattened.tb$y-1)/N
flattened.tb$z = 1 - flattened.tb$x - flattened.tb$y

############### MODIFIED CODE BELOW ###############

#Remove the (trivially) Negative Concentrations
flattened.tb = subset(flattened.tb,z >= 0)

#Plot a series of plots in increasing polynomial degree
plots = lapply(seq(3,18,by=3),function(x){
  degree = x
  breaks = seq(0.025,0.575,length.out = 10)
  base   = ggtern(data = flattened.tb, aes(x=x,y=y,z=z)) +
    geom_point(size=1, aes(color=W12),alpha=0.05) +
    geom_interpolate_tern(aes(value=W12,color=..level..),
                          base = 'identity',method = glm,
                          formula = value ~ polym(x,y,degree = degree,raw=T),
                          n = 150, breaks = breaks) + 
    theme_bw() +
    theme_legend_position('topleft') + 
    scale_color_gradient2(low = "green", mid = "yellow", high = "red",
                          midpoint = mean(range(flattened.tb$W12)))+
    labs(title=sprintf("Polynomial Degree %s",degree))
  base
})

#Arrange the plots using grid.arrange
png("~/Desktop/output.png",width=700,height=900)
  grid.arrange(grobs = plots,ncol=2)
garbage <- dev.off()

This produces the following output:

For the sake of producing a diagram closer to the colours and orientation as the sample matlab contour plot, try the following:

plots = lapply(seq(3,18,by=3),function(x){
  degree = x
  breaks = seq(0.025,0.575,length.out = 10)
  base   = ggtern(data = flattened.tb, aes(x=z,y=y,z=x)) +
    geom_point(size=1, aes(color=W12),alpha=0.05) +
    geom_interpolate_tern(aes(value=W12,color=..level..),
                          base = 'identity',method = glm,
                          formula = value ~ polym(x,y,degree = degree,raw=T),
                          n = 150, breaks = breaks) + 
    theme_bw() +
    theme_legend_position('topleft') + 
    scale_color_gradient2(low = "darkblue", mid = "green", high = "darkred",
                          midpoint = mean(range(flattened.tb$W12)))+
    labs(title=sprintf("Polynomial Degree %s",degree))
  base
})
png("~/Desktop/output2.png",width=700,height=900)
  grid.arrange(grobs = plots,ncol=2)
garbage <- dev.off()

This produces the following output:

Question:

Here is the code which I am using to create boundaries in my ternary diagram:

library(ggtern)
DATA <- data.frame(x = c(0,0,0.04),
               y = c(1,0.6,0.575),
               z = c(0,0.4,0.385),
               xend = c(0.4,0.21,0.1),
               yend = c(0.0,0.475,0),
               zend = c(0.6,0.315,0.9),
               Series = c("yz","xz","xy"))
ggtern(data=DATA,aes(x,y,z,xend=xend,yend=yend,zend=zend)) + 
geom_segment(aes(color=Series),size=1) +
scale_color_manual(values=c("darkgreen","darkblue","darkred")) +
theme_bw() + theme_nogrid() + 
theme(legend.position=c(0,1),legend.justification=c(0,1)) + 
labs(title = "Sample Midpoint Segments")

And this code produces the following diagram.

I want to fill different colours in each section. This figure is divided into 4 sections. Could you kindly tell me how to fill different colours in each sections using geom_polygon function or any other function?


Answer:

Try this:

g <- data.frame(y=c(1,0,0),
                x=c(0,1,.4),
                z=c(0,0,.6),         Series="Green")

p <- data.frame(y=c(1,0.475,0.6),
                x=c(0,0.210,0),
                z=c(0,0.315,.4),         Series="Red")

q <- data.frame(y=c(0.575,0.475,0.0,0.0),
                x=c(0.040,0.210,0.4,0.1),
                z=c(0.385,0.315,0.6,0.9),         Series="Yellow")

f <- data.frame(y=c(0.6,0.575,0.0,0.0),
                x=c(0.0,0.040,0.1,0.0),
                z=c(0.4,0.385,0.9,1.0),         Series="Blue")

DATA = rbind(g, p, q, f)
ggtern(data=DATA,aes(x,y,z)) + 
  geom_polygon(aes(fill=Series),alpha=.5,color="black",size=0.25) +
  scale_fill_manual(values=as.character(unique(DATA$Series))) +
  theme(legend.position=c(0,1),legend.justification=c(0,1)) + 
  labs(fill="Region",title="Sample Filled Regions")

Question:

The ternary diagram is shown in the following image. I want to add the labels of Z=60, Z=90 and Y=60 using ggtern package in R.

The R code link is the R code of ternary diagram


Answer:

This is not the perfect answer, but I tried to achieve your wanted result with annotate like this:

ggtern(data=DATA,aes(x,y,z)) + 
  geom_polygon(aes(fill=Series),alpha=.5,color="black",size=0.25) +
  scale_fill_manual(values=as.character(unique(DATA$Series))) +
  theme(legend.position=c(0,1),legend.justification=c(0,1)) + 
  labs(fill="Region",title="Sample Filled Regions") +
  annotate(geom  = 'text',
           x     = c(0.1, 1/3, 0.0),
           y     = c(0.0, 0.0, 1.5),
           z     = c(0.5, 1/3, 1.0),
           angle = c(0, 0, 0),
           vjust = c(2.5, 2.5, -1.5),
           hjust = c(0.0, -0.2, 0.0),
           label = c("Z=90","Z=60","Y=60"),
           color = c("black","gray",'orange')) + # for inspection
  theme_nomask()   # allows drawing beyond the borders 

This yields the following picture:

Question:

I'm trying to use the ggtern package to plot some plots. It used to run on ggplot2, but now it doesn't work and breaks ggplot2.

When I run the following:

library(ggplot2)
qplot(1,2)
library(ggtern)
qplot(3,4)

df = data.frame(x = runif(50),
                y = runif(50),
                z = runif(50),
                Value = runif(50,1,10),
                Group = as.factor(round(runif(50,1,2))))
ggtern(data=df,aes(x,y,z,color=Group)) +
  theme_rgbw() +
  geom_point() + geom_path() +
  labs(x="X",y="Y",z="Z",title="Title")

qplot(1,2) works just fine, library(ggtern) gives me this (which may or may not be related to the problem):

Attaching package: ‘ggtern’

The following objects are masked from ‘package:ggplot2’:

    %+%, %+replace%, aes, calc_element, Geom, geom_segment, ggplot_build,
    ggplot_gtable, ggsave, theme, theme_bw, theme_classic, theme_get, theme_gray,
    theme_grey, theme_minimal, theme_set, theme_update

qplot(3,4) fails with this:

Error in FUN(X[[i]], ...) : attempt to apply non-function

And if I comment it out and it reaches the ggtern(... I get this:

Error in coord_tern() : could not find function "coord"

Obviously, something is wrong. Where do I start looking for what's wrong? What's this function coord and why is it gone?


Answer:

Yep, ggtern 2.0.1 is now available, published on CRAN a couple of days ago after completely re-writing the package to be compatible withggplot2 2.0.0. A summary of the new functionality in ggtern 2.0.X can be found here:

Here is what your code produces under the new package:

Question:

Here's a problem: What's the best way to plot values for threefold combinations of a categorical variable?

This is as far as I got in R:

library(tidyverse)
library(ggtern)

df_person <- tibble( name = c( 'Alice', 'Bob', 'Carla', 'Dave', 'Eve' ) ) %>%
  rowid_to_column( 'id_person' )

# generate all trios of persons (5 choose 3)  
df <- df_person %>% select( name ) %>%
  map_df( function(x) { combn(x, 3, paste, collapse = '_') } ) %>%
  separate( name, c('person1', 'person2', 'person3') ) %>%
  mutate_all(~ as.factor(.) )
# assign a value to each trio
df$val <- runif( nrow(df) )

# generate ticks and labels for axes
axis <- df_person %>% mutate( fct = as.factor(name) ) %>%
  mutate( tick = as.numeric(fct) / 5 )

ggtern( df, aes(x = as.numeric(person1),
                y = as.numeric(person2),
                z = as.numeric(person3),
                color = val) ) +
  geom_point() +
  scale_T_continuous( breaks = axis$tick, labels = axis$name ) +
  scale_L_continuous( breaks = axis$tick, labels = axis$name ) +
  scale_R_continuous( breaks = axis$tick, labels = axis$name ) +
  labs( x = 'person1', y = 'person2', z = 'person3' )

Which gives a rather odd result:

I would expect ten points which are located where the grid lines meet (since these are categorical variables).

Ideally, I would like to generate a heatmap-like plot, i.e. triangular tiles instead of points.

Any help is highly appreciated!


Answer:

Ok, after some research into ternary plots I now understand that this is not how they are used.

This kind of plot makes sense in situations where different contributions of three variables, which always sum up to the same value, are considered.

For my particular use case, I am better off with a faceted bar plot:

This is still not perfect since there are are some combinations in the plot that never occur in the data (e.g. (Alice, Carla, Carla)), but it does the job.

If anybody knows a better visualization for this use case I would be very much interested.

Question:

Trying to switch the theme to theme_bw() is not working, theme_grey() seems to be prioritized. Any ideas why?

ggplot(data=mpg,aes(y=year,x=cyl)) + geom_point() + theme_bw()

EDIT:

As suggested in the comments, this works when run in a clean R session. But when I implement the code in a session with ggtern loaded, the problem crops up.

library(ggplot2)
#Warning message:
#package ‘ggplot2’ was built under R version 3.2.5 
ggplot(data=mpg,aes(y=year,x=cyl)) + geom_point() + theme_bw()

# sessionInfo()
# R version 3.2.2 (2015-08-14)
# Platform: x86_64-w64-mingw32/x64 (64-bit)
# Running under: Windows 8 x64 (build 9200)
# 
# locale:
#   [1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252   
# [3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
# [5] LC_TIME=English_United States.1252    
# 
# attached base packages:
#   [1] stats     graphics  grDevices utils     datasets  methods   base     
# 
# other attached packages:
#   [1] ggplot2_2.1.0
# 
# loaded via a namespace (and not attached):
#   [1] labeling_0.3     colorspace_1.2-6 scales_0.4.0     plyr_1.8.3       tools_3.2.2      gtable_0.1.2    
# [7] Rcpp_0.12.2      grid_3.2.2       munsell_0.4.2 

library(ggtern)
#Loading required package: ggplot2

#Attaching package: ‘ggtern’

#The following objects are masked from ‘package:ggplot2’:

    #aes, calc_element, ggplot, ggplot_build, ggplot_gtable, ggplotGrob, ggsave, is.ggplot, layer_data,
    #layer_grob, layer_scales, theme, theme_bw, theme_classic, theme_dark, theme_get, theme_gray,
    #theme_light, theme_linedraw, theme_minimal, theme_set, theme_void

#Warning messages:
#1: package ‘ggtern’ was built under R version 3.2.5 
#2: package ‘ggplot2’ was built under R version 3.2.5 
ggplot(data=mpg,aes(y=year,x=cyl)) + geom_point() + theme_bw()

# sessionInfo()
# R version 3.2.2 (2015-08-14)
# Platform: x86_64-w64-mingw32/x64 (64-bit)
# Running under: Windows 8 x64 (build 9200)
# 
# locale:
#   [1] LC_COLLATE=English_United States.1252  LC_CTYPE=English_United States.1252   
# [3] LC_MONETARY=English_United States.1252 LC_NUMERIC=C                          
# [5] LC_TIME=English_United States.1252    
# 
# attached base packages:
#   [1] stats     graphics  grDevices utils     datasets  methods   base     
# 
# other attached packages:
#   [1] ggtern_2.1.1  ggplot2_2.1.0
# 
# loaded via a namespace (and not attached):
#   [1] Rcpp_0.12.2         lattice_0.20-33     MASS_7.3-45         grid_3.2.2          plyr_1.8.3         
# [6] bayesm_3.0-2        gtable_0.1.2        magrittr_1.5        scales_0.4.0        stringi_1.0-1      
# [11] compositions_1.40-1 robustbase_0.92-5   latex2exp_0.4.0     boot_1.3-17         labeling_0.3       
# [16] proto_0.3-10        tools_3.2.2         stringr_1.0.0       energy_1.6.2        DEoptimR_1.0-4     
# [21] munsell_0.4.2       colorspace_1.2-6    tensorA_0.36        gridExtra_2.0.0    

Looks like ggtern masks a lot of the themes from ggplot2.


Answer:

ggtern masks EVERY default theme from ggplot2, and this is because when writing this software, some ~60 new theme elements have been created, which exist in order to make ggtern render correctly. For a comprehensive list of the new theme elements, run the following command:

library(ggtern)
?theme_elements

Having said the above, I was aware of some clashes, as you have identified, due to ggtern also modifying some of the base theme elements, and since about version 2.1.2 - 2.1.3, I have completely re-worked the themes so that ggtern no longer modifies any of the base elements -- hopefully this annoying behaviour has now gone away for good!

I am in the process of producing a publication on the package, and have been refining many many many long-tern annoyances, so please download and install the most recent version (2.1.4) from my (Bitbucket Repository). Embarrassingly, even the last 2.1.3 version on CRAN, I picked up on a pretty significant bug when running two plots in a grid.arrange type situation. This is an issue with the clipping mask, and something which has been resolved in 2.1.4 yet sent to CRAN, something I plan to do imminently.

Anyway, to answer your question, please upgrade your version, you are running version 2.1.0, which was a version released not long after a HUGE revision in ggplot2 -- in fact, not long earlier, prior versions were so heavily broken, requiring almost the entire re-writing of ggtern, so having perfect themes was the least of my priorities. In recent versions, since I have had time to refine and identify issues, this behaviour should have been resolved -- demonstrated by the following two (2) commands run (in this order, from a clean session), which, with the exception of the obvious difference in the titles, should yield identical results:

library(ggplot2)
ggplot(data=mpg,aes(y=year,x=cyl)) + 
    geom_point() + theme_bw() + 
    labs(title='From ggplot2')

library(ggtern)
ggplot(data=mpg,aes(y=year,x=cyl)) + 
    geom_point() + theme_bw() + 
    labs(title='From ggtern')

If I can just say, this may seem a little off-topic, but this whole problem actually originally stemmed, by a couple of degrees of separation, from one thing. ggplot2 was designed without clipping masks, it uses the grid viewport as essentially a pseudo clipping mask, because all of its plots (with the exception of the polar transformation), are rendered on a rectangular region. Data mappings that lie outside of the rectangular region are subsequently discarded by the viewport. ggtern on the other hand needs one, because it needs to render within a triangular shaped polygon region housed within a rectangular viewport -- and as everyone knows, 'triangles don't fit in square holes' or however the saying goes! When the axis limits are reduced, data mappings can, and often do, lie outside of the triangular region, and these need to be either deleted or masked (or shown if the user so wishes). Deleting is not an option, since it would then affect certain plots like polygon, path, density or contour plots (I think probably point geometries are the only ones which are not potentially affected), which leaves only one option -- implementation of clipping mask. In some of the earlier versions, a clipping mask wasn't implemented (in favour of subsetting the data) thus requiring modifications of base theme elements. If anything, perhaps an initial 'poor design', has flowed on many generations later to a frustrating behaviour.

Question:

Polynators convex hull Area

Consider the following data.frame

DGChi <- structure(list(Sucrose = c(42, 40, 15, 19, 33, 49, 35, 31, 22, 
25, 37, 28, 31, 41, 27, 28, 33, 43, 21, 37, 14, 41, 30, 34, 38, 
40, 40, 33, 33), Fructose = c(27, 29, 41, 35, 29, 23, 27, 33, 
38, 38, 28, 31, 29, 26, 32, 34, 31, 28, 40, 30, 39, 27, 32, 31, 
29, 28, 28, 32, 29), Glucose = c(31, 31, 44, 46, 38, 28, 38, 
36, 40, 37, 35, 41, 40, 33, 41, 38, 36, 30, 39, 33, 47, 32, 38, 
35, 33, 32, 32, 35, 38), Sindrome = c("Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily", "Chiropterophily", 
"Chiropterophily", "Chiropterophily", "Chiropterophily")), .Names = c("Sucrose", 
"Fructose", "Glucose", "Sindrome"), row.names = c(NA, -29L), class = c("tbl_df", 
"tbl", "data.frame"))
First try

I am trying to make a ternary plot and add a convex hull around the points, my first try was using geom_encircle of the ggalt package:

library(ggtern)
library(ggalt)

ggtern(data = DGChi, aes(x = Fructose, y = Sucrose, z = Glucose, fill = Sindrome)) +
theme_bw() +
geom_encircle(alpha=0.2,size=1, spread = 0.5) +
geom_point() +
theme(legend.position="bottom") 

With this result

Which encircles the points, but is not a convex hull

Second try using the geometry package

Trying to follow my own answer for rgl, I tried this:

library(geometry)
DGChiMin <- as.data.frame(convhulln(matrix(c(DGChi$Fructose, DGChi$Sucrose, DGChi$Glucose), ncol = 3)))
colnames(DGChiMin) <- c("Fructose", "Sucrose", "Glucose")

and then this for the plot:

ggtern(data = DGChi, aes(x = Fructose, y = Sucrose, z = Glucose)) +
  theme_bw() +
  geom_polygon(data = DGChiMin,aes(x = Fructose, y = Sucrose, z = Glucose)) +
  geom_point() +
  theme(legend.position="bottom")

But got this super weird polygon:

Can someone help me get the convex hull plot?


Answer:

Think you will find that geom_encircle uses chull internally. Set the expand parameter to 0.

library(ggalt)
library(ggtern)
ggtern(data = DGChi, aes(x = Fructose, y = Sucrose, z = Glucose, fill = Sindrome)) +
  theme_bw() +
  geom_encircle(alpha=0.2,size=1, expand=0) + ##<<<<<< expand = 0
  geom_point() +
  theme(legend.position="bottom") 

Question:

I have long axis title names in a ternary plot coded in ggtern. I cannot get the R and L (baseline) titles to move in (from where they are cut off). The top title looks great, but the other two do not.

I have tried to use axis.title.x = element_text(margin = margin(t = 0, r = 0, b = 0, l = 0)) and changed the positioning variables, but that didn't seem to work. So I'm at a loss as to what to do.

#library(tidyverse)
#library(ggtern)

confidencebreaks <- c(0.95)
x  <- runif(1000,min = 0, max = 1)
y  <- runif(1000,min = 0, max = 1)
z  <- runif(1000,min = 0, max = 1)
df <- tibble(x,y,z)

tern1 <- 
  ggtern(data = df,
         mapping = aes(x = z, y = y, z = z)
  ) +
  labs(title = "A title", 
       subtitle = "A subtitle",
       x = expression(paste(atop("Title 2", 
                                 "A long line 2 that goes on and on"))), 
       y = expression(paste(atop("Title 0", 
                                 "A long line 2 that goes on and on"))),
       z = expression(paste(atop("Title 1", 
                                 "A long line 2 that goes on and on")))
  ) + 
  theme(axis.title = element_text(size=10)) 
print(tern1)

The code above reproduces the problem with the cut off and long axis titles. I would like to be able to shift the long axis titles for "Title 2" and "Title 1" inwards, but have not managed to do so.


Answer:

So to adjust the labels on ggtern, you should use tern.axis.title and the specification for which side which is R for right, L for left, and T for Top. In your question, you can adjust the labels as follows then

    library(tidyverse)
    library(ggtern)

    confidencebreaks <- c(0.95)
    x  <- runif(1000,min = 0, max = 1)
    y  <- runif(1000,min = 0, max = 1)
    z  <- runif(1000,min = 0, max = 1)
    df <- tibble(x,y,z)

    df

    ggtern(data = df,
             mapping = aes(x = z, y = y, z = z)
      ) +
      labs(title = "A title", 
           subtitle = "A subtitle")+
           xlab("Title 2 \n A long line 2 that goes on and on")+ 
           ylab("Title 0 \n A long line 2 that goes on and on")+
           zlab("Title 1 \n A long line 2 that goes on and on")+ 
      theme(tern.axis.title.L = element_text(hjust = 0),
            tern.axis.title.R = element_text(hjust = 1))

This will result in a plot like this

Question:

I am using the ggtern package in R for plotting the ternary diagrams. I have trouble customizing the legend of the 'fill'. Here is my code:

library(ggtern)
setwd("~/R/data")
library(XLConnect)
df <- readWorksheetFromFile("ternary_two_wells.xlsx",sheet=1,startRow = 1, endCol=7)
# Feldspar = Feldspar[with(Feldspar, order(-P.Gpa)), ]
df = df[with(df,order(-ReTOC)), ]
# Build and Render the Plot
ggtern(data = df, aes(x = Silicate, y = Carbonate, z = Clay)) +
  #the layer
  geom_point(aes(fill = Permeability,
                 size = ReTOC,
                 shape = Well)) +
  #scales
  scale_shape_manual(values = c(21, 24)) +
  scale_size_continuous(range = c(2.5, 7.5)) +
  scale_fill_gradient(low = 'green', high = 'red') +

  #theme tweaks
  theme_bw()  +
  theme(legend.position      = c(0, 1),
        legend.justification = c(0, 1),
        legend.box.just      = 'left') +
  #tweak guides
  guides(shape= guide_legend(order   =1,
                             override.aes=list(size=5)),
         size = guide_legend(order   =2),
         fill = guide_colourbar(order=3)) +

  #labels and title
  labs(size = 'ReTOC/%BV',
       fill = 'Permeability/nD') +
  ggtitle('Two Encana Wells')

Here is how the resulting ternary diagram looks like: See the 'Permeability/nD' legend? That is in scientific notation. How do I get it to show values in decimal instead?


Answer:

You can use package scales and the labels argument in scale_fill_gradient to use commas instead of scientific notation.

library(scales)

scale_fill_gradient(low = 'green', high = 'red', labels = comma) 

Question:

I am trying to replicate a chart I found on FiveThirtyEight at https://fivethirtyeight.com/features/how-cable-news-reacted-to-the-cohen-hearing/. This shows a ternery plot where the location of the words in the 3 axis shows the proportion quoted that by that respective network.

I am currently using R, ggplot2 and more importantly ggtern (which I use extensively for ternery plots). However I have never found a way to make data labels on points not overlap. I have always hoped that ggtern would interact with ggrepel but sadly it doesn't (to my knowledge). Is there any way to force these to interact, or find another way of doing this?

Chart shown in link for clarity of what I'm after:

Example of my chart with words overlapping and looking bad:

EDIT Code to create my hideous chart:

    data <- data.frame(word = c("A","random","set","of","words","that","can","hopefully","help","someone","solve","my","issue","of","overlapping","labels","and","make","my","chart","readable","and","a","good","visualization"),
               axis1 = sample(1:100),
               axis2 = sample(1:100),
               axis3 = sample(1:100))

    ggtern(data = data,
        aes(x = axis1, y = axis2, z = axis3, colour = word, label = word)) +
      geom_point(size = 1) +
      geom_text()

Answer:

Ok, so you want the functionality as in ggrepel package. While ggrepel wont work here you can use position_nudge_tern and check_overlap:

word = c("A","random","set","of","words","that","can","hopefully","help","someone","solve","my","issue","of","overlapping","labels","and","make","my","chart","readable","and","a","good","visualization")
col = c("red", "blue", "green", "red", "blue", "green","red", "blue", "green", "red", "blue", "green","red", "blue", "green", "red", "blue", "green","red", "blue", "green", "red", "blue", "green","red")

n  = 25   #Number of Data Points
nv = 0.1  #Vertical Adjustment
pn = position_nudge_tern(y=nv,x=-nv/2,z=-nv/2)

data <- data.frame(x = sample(1:25),
               y = sample(1:25),
               z = sample(1:25), 
               label=word)

ggtern(data = data, aes(x = x, y = y, z = z, colour = col, label = word)) +
  geom_point(size = 1) +
  theme_nomask() + #Allow Labels to Spool Over Edges
  geom_text(position=pn,aes(label=word),check_overlap=T, size=5)

Which will give you non-overlapping labels: