Consider points of the form \((x, y, z)\) where \(x, y, z \geq 0\).
Here are some points
# Generate some points
rbind(
expand.grid(x=1:10, y=1:10, z=1:10) %>% mutate(set="10x10x10", ID=NA),
expand.grid(x=c(1,10), y=c(1,10), z=c(1,10)) %>% mutate(set="extremes", ID=LETTERS[row_number()])
) %>%
as_tibble() %>%
mutate(
# Generate the length from the origin
length = sqrt(x^2+y^2+z^2),
# Generate their closure
sum=x+y+z,
x.clo=x/sum,
y.clo=y/sum,
z.clo=z/sum,
# Generate their clr
x.log=log10(x),
y.log=log10(y),
z.log=log10(z),
gm=(x.log + y.log + z.log)/3,
x.clr=x.log-gm,
y.clr=y.log-gm,
z.clr=z.log-gm,
# Generate a projection of the 3D simplex onto the 2D plane
# See https://mathworld.wolfram.com/TernaryDiagram.html
# I've centred this so that (1,1,1) maps to (0,0,0)
t1 = (2*y + z)/(2*sum) - 0.5,
t2 = (sqrt(3) * z)/(2*sum) - sqrt(3)/6,
# Generate a projection of the 3D simplex onto ilr coordinates
# This is the coordinates from Eq 5.1 of Compositional Data Analysis, Theory and Practice
ilr1=-sqrt(1/2)*log10(x/y),
ilr2=-sqrt(1/6)*log10(x*y/z^2),
# Do the inverse ilr transform
x.inv = 1,
y.inv = 10^(ilr1*sqrt(2)),
z.inv = 10^(ilr1/sqrt(2) + ilr2*sqrt(3/2)),
sum.inv= x.inv + y.inv + z.inv,
x.inv = x.inv/sum.inv,
y.inv = y.inv/sum.inv,
z.inv = z.inv/sum.inv
) -> X
# Create these handy subsets
Points <- subset(X, set=="10x10x10")
Extremes <- subset(X, set=="extremes")
select(Points, x.clo, y.clo, z.clo, ilr1, ilr2, x.inv, y.inv, z.inv)
## # A tibble: 1,000 x 8
## x.clo y.clo z.clo ilr1 ilr2 x.inv y.inv z.inv
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 0.333 0.333 0.333 0 0 0.333 0.333 0.333
## 2 0.5 0.25 0.25 -0.213 -0.123 0.5 0.250 0.25
## 3 0.6 0.2 0.2 -0.337 -0.195 0.6 0.2 0.2
## 4 0.667 0.167 0.167 -0.426 -0.246 0.667 0.167 0.167
## 5 0.714 0.143 0.143 -0.494 -0.285 0.714 0.143 0.143
## 6 0.75 0.125 0.125 -0.550 -0.318 0.75 0.125 0.125
## 7 0.778 0.111 0.111 -0.598 -0.345 0.778 0.111 0.111
## 8 0.8 0.1 0.1 -0.639 -0.369 0.8 0.100 0.1
## 9 0.818 0.0909 0.0909 -0.675 -0.390 0.818 0.0909 0.0909
## 10 0.833 0.0833 0.0833 -0.707 -0.408 0.833 0.0833 0.0833
## # ... with 990 more rows
Here are the points in 3D
marker.RdBu <- list(color=~length, colorscale="RdBu", size=2, showscale=FALSE)
marker.extremes <- list(color="red", size=3, showscale=FALSE, opacity=1)
text.red <- list(color="red", size = 20)
plot_ly(
data=Points,
type="scatter3d",
name="points",
mode="markers",
x=~x, y=~y, z=~z,
marker=marker.RdBu
) %>%
add_trace(
data=Extremes,
name="label",
mode="markers+text",
x=~x, y=~y, z=~z, text=~ID,
marker=marker.extremes,
textfont = text.red
)
Points are transformed to the simplex \(\mathcal{S}^3\) by dividing them by their sum \(x+y+z\). This operation is called closure in compositional data analysis. Here are the points on the simplex:
plot_ly(
data=Points,
type="scatter3d",
name="points",
mode="markers",
x=~x.clo, y=~y.clo, z=~z.clo,
marker=marker.RdBu
) %>%
add_trace(
data=Extremes,
name="label",
mode="markers+text",
x=~x.clo, y=~y.clo, z=~z.clo, text=~ID,
marker=marker.extremes,
textfont = text.red
)
Here is a projection of the simplex onto the 2D plane (see https://mathworld.wolfram.com/TernaryDiagram.html):
ggplot(
data=Points, aes(x=t1, y=t2)) +
geom_point(aes(color=length)) +
scale_color_gradient2(
low = ("blue"),
midpoint=median(Points$length),
high = ("red")) +
coord_equal() +
geom_point(data=Extremes, color="red") +
geom_text(
data=Extremes,
aes(x=1.1*t1, y=1.1*t2, label=ID), color="red"
) +
xlab(expression(ternary[1])) + ylab(expression(ternary[2])) +
theme(legend.position="none")
Here is their centred logratio transform
plot_ly(
data=Points,
type="scatter3d",
name="points",
mode="markers",
x=~x.clr, y=~y.clr, z=~z.clr,
marker=marker.RdBu
) %>%
add_trace(
data=Extremes,
name="label",
mode="markers+text",
x=~x.clr, y=~y.clr, z=~z.clr, text=~ID,
marker=marker.extremes,
textfont = text.red
)
Here is (an) isometric logratio transform (there are infinitely many) onto the 2D plane:
ggplot(
data=Points, aes(x=ilr1, y=ilr2)) +
geom_point(aes(color=length)) +
scale_color_gradient2(
low = ("blue"),
midpoint=median(Points$length),
high = ("red")) +
coord_equal() +
geom_point(data=Extremes, color="red") +
geom_text(
data=Extremes,
aes(x=1.1*ilr1, y=1.1*ilr2, label=ID), color="red"
) +
xlab(expression(ilr[1])) + ylab(expression(ilr[2])) +
theme(legend.position="none")
There are infinitely many isometric logratio transformations that map the simplex \(\mathcal{S}^D\) to \(\mathbb{R}^{D-1}\). Here’s one from (Pawlowsky-Glahn and Buccianti 2011, 35) which maps a point \(\mathbf{x} = (x_1, x_2, \dots, x_D)\) on the \(D\)-dimensional simplex onto a point \(\mathbf{y} = (y_1, y_2, \dots, y_{D-1})\) on the \(D-1\)-dimensional Euclidean plane: \[ y_i = \frac{1}{\sqrt{i(i+1)}} \ln \left[\frac{x_1 x_2 \cdots x_i}{(x_{i+1})^i} \right], \qquad i = 1, 2, \dots D - 1. \] Let’s see what that looks like for a few values of \(i\): \[ \begin{align} y_1 &= \frac{1}{\sqrt{2}} \ln\frac{x_1}{x_2} \\ &= \frac{1}{\sqrt{2}} (\ln x_1 - \ln x_2)\\ y_2 &= \frac{1}{\sqrt{6}} \ln\frac{x_1 x_2}{x_3^2} \\ &= \frac{1}{\sqrt{2}} (\ln x_1 + \ln x_2 - 2\ln x_3) \end{align} \]
We can use this system of equations to show that \[ \begin{align} \ln x_2 &= \ln x_1 - \sqrt{2} y_1\\ \ln x_3 &= \ln x_1 - \frac{\sqrt{2}}{2} y_1 - \frac{\sqrt{6}}{2} y_2\\ \end{align} \] so that we can write the inverse transform of \((y_1, y_2)\) from the \(D-1\)-dimensional Euclidean plane back to the \(D\)-dimensional simplex as \[ \mathcal{C}(\exp(1, -\sqrt{2}y_i, -\frac{\sqrt{2}}{2} y_1 - \frac{\sqrt{6}}{2} y_2)) \] where \(\mathcal{C}\) is the closure operator.
Here we map an ellipse from an isometric logratio plane in \(\mathbb{R}^{2}\) to the probability simplex in \(\mathcal{S}^D\)
First, we make a function to generate \(n\) points on an ellipse centred on \((x_c, y_c)\) with major axis length \(a\), minor axis length \(b\) and angle to the \(x\)-axis \(\phi\):
# Adapted from https://stackoverflow.com/questions/41820683/how-to-plot-ellipse-given-a-general-equation-in-r
ellipse <- function(xc=0.1, yc=-0.2, a=1, b=0.5, phi=pi/3, n=100){
t <- seq(0, 2*pi, 0.01)
tibble(
x = xc + a*cos(t)*cos(phi) - b*sin(t)*sin(phi),
y = yc + a*cos(t)*cos(phi) + b*sin(t)*cos(phi)
)
}
Then we treat the points of that ellipse as though they are ilr coordinates \((\mathrm{ilr}_1, \mathrm{ilr}_2)\), then apply an inverse ilr mapping to \((x_\mathrm{inv}, y_\mathrm{inv}, z_\mathrm{inv})\), and project that onto a 2-D ternary diagram with points \((t_1, t_2)\)
ellipse() %>%
rename(ilr1=x, ilr2=y) %>%
mutate(
x.inv = 1,
y.inv = 10^(ilr1*sqrt(2)),
z.inv = 10^(ilr1/sqrt(2) + ilr2*sqrt(3/2)),
sum.inv= x.inv + y.inv + z.inv,
x.inv = x.inv/sum.inv,
y.inv = y.inv/sum.inv,
z.inv = z.inv/sum.inv,
t1 = (2*y.inv + z.inv)/2 - 0.5,
t2 = (sqrt(3) * z.inv)/2 - sqrt(3)/6
) -> ilr.ellipse
Here is the view in ilr coordinates, with the points from our cube of counts for reference:
ggplot(
data=Points, aes(x=ilr1, y=ilr2)) +
geom_point(aes(color=length)) +
scale_color_gradient2(
low = ("blue"),
midpoint=median(Points$length),
high = ("red")) +
coord_equal() +
geom_point(data=Extremes, color="red") +
geom_text(
data=Extremes,
aes(x=1.1*ilr1, y=1.1*ilr2, label=ID), color="red"
) +
geom_path(data=ilr.ellipse) +
xlab(expression(ilr[1])) + ylab(expression(ilr[2])) +
theme(legend.position="none")
And here is the view from the simplex
tribble(
~x.inv, ~y.inv, ~z.inv, ~ID, ~angle,
1, 0, 0, "x = 1", 30,
1/2, 1/2, 0, "(1/2, 1/2, 0)", 0,
0, 1, 0, "y = 1", -30,
0, 1/2, 1/2, "(0, 1/2, 1/2)", -60,
0, 0, 1, "z = 1", 90,
1/2, 0, 1/2, "(1/2, 0, 1/2)", 60,
1, 0, 0, NA, 0
) %>%
mutate(
t1 = (2*y.inv + z.inv)/2 - 0.5,
t2 = (sqrt(3) * z.inv)/2 - sqrt(3)/6
) -> S3
ggplot(
data=Points, aes(x=t1, y=t2)) +
# The simplex boundary
geom_path(data=S3, color="grey") +
geom_text(
data=filter(S3, !is.na(ID)), color="grey",size=3, hjust=0.5, vjust=0.5,
aes(x=1.1*t1, y=1.1*t2, label=ID, angle=angle)
) +
# The reference points
geom_point(aes(color=length)) +
scale_color_gradient2(
low = ("blue"),
midpoint=median(Points$length),
high = ("red")) +
# The extremes
geom_point(data=Extremes, color="red") +
geom_text(
data=Extremes,
aes(x=1.1*t1, y=1.1*t2, label=ID), color="red"
) +
# The transformed ilr ellipse
geom_path(data=ilr.ellipse) +
coord_equal() +
xlab(expression(t[1])) + ylab(expression(t[2])) +
theme(legend.position="none")
Pawlowsky-Glahn, Vera, and Antonella Buccianti, eds. 2011. Compositional Data Analysis: Theory and Applications. http://onlinelibrary.wiley.com/book/10.1002/9781119976462.