---
title: "The GGE biplot: every view with every scaling"
author: "Muhammad Yaseen"
date: last-modified
format:
  html:
    self-contained: true
    number-sections: true
    toc: true
    toc-depth: 3
    highlight-style: pygments
    code-tools: true
    html-math-method: mathjax
vignette: >
  %\VignetteIndexEntry{The GGE biplot: every view with every scaling}
  %\VignetteEngine{quarto::html}
  %\VignetteEncoding{UTF-8}
execute:
  echo: true
  warning: false
  message: false
---

{{< include _stability-macros.txt >}}

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = FALSE, comment = "", fig.width = 7, fig.height = 6.2,
                      dev = if (knitr::is_latex_output()) "cairo_pdf" else
                        if (requireNamespace("svglite", quietly = TRUE)) "svglite" else "png")
if (requireNamespace("stability", quietly = TRUE)) library(stability) else
  pkgload::load_all(export_all = FALSE, helpers = FALSE, quiet = TRUE)
```

```{r facts, include = FALSE}
# the numbers quoted in the text: the biplot data of every scaling
views <- c("basic", "which-won-where", "mean-stability", "discrimination")
svps  <- c("genotype", "symmetric", "environment")
gg <-
  lapply(
      X   = stats::setNames(object = svps, nm = svps)
    , FUN = function(s)
        attr(
            x     = gge_biplot(
                .data  = ge_data
              , .y     = Yield
              , .gen   = Gen
              , .env   = Env
              , .which = "basic"
              , .svp   = s
              )
          , which = "gge"
          )
    )
pct    <- sum(gg$genotype$percent)
pc     <- gg$genotype$percent
nG     <- nrow(gg$genotype$gen)
nE     <- nrow(gg$genotype$env)
same_w <- identical(x = gg$genotype$winners, y = gg$symmetric$winners) && identical(
    x = gg$genotype$winners
  , y = gg$environment$winners
  )
win    <- unique(gg$genotype$winners$Winner)
vtx    <- lapply(X = gg, FUN = function(g) sort(g$vertex))
same_v <- identical(x = vtx$genotype, y = vtx$symmetric) && identical(
    x = vtx$genotype
  , y = vtx$environment
  )
same_a <- identical(x = gg$genotype$atc$Gen, y = gg$environment$atc$Gen)
rc <- function(a, b, key, v) round(
    x      = stats::cor(x = a[[v]], y = b[[v]][match(x = a[[key]], table = b[[key]])], method = "spearman")
  , digits = 2
  )
ri_gs <- rc(gg$genotype$atc, gg$symmetric$atc, "Gen", "Instability")
ri_ge <- rc(gg$genotype$atc, gg$environment$atc, "Gen", "Instability")
rl_es <- rc(gg$environment$envs, gg$symmetric$envs, "Env", "Length")
rl_eg <- rc(gg$environment$envs, gg$genotype$envs, "Env", "Length")
ra_es <- rc(gg$environment$envs, gg$symmetric$envs, "Env", "Angle")
ra_eg <- rc(gg$environment$envs, gg$genotype$envs, "Env", "Angle")
lam <- sqrt(pc[1] / pc[2])
lst <- function(x) if (length(x) < 2L) x else paste0(paste(x[-length(x)], collapse = ", "), " and ", x[length(x)])
```

# Abstract

`gge_biplot()` has four views (`.which`) and three scalings (`.svp`), twelve plots in all. This vignette draws every
one of them for the wheat trial `ge_data` (`r nG` genotypes in `r nE` environments), interprets each with
`stab_interpret()`, and says which combination answers which question. The short version: a view asks the question
and the scaling decides whether the distances in the plot measure what the view needs. Which won where is drawn with
symmetric scaling, mean against stability with genotype-focused scaling, and discrimination and representativeness with
environment-focused scaling. Some readings (the winner of each environment, the ranking of genotypes on the average
environment axis) are the same under every scaling; others (the polygon, the instability of genotypes, the lengths
and angles of environment vectors) are not.

# The biplot and its scalings

The GGE biplot (Yan et al. 2000) displays the table of means centred on the environment means,
$$\mX = \left(\ybar_{ij} - \ybar_{\cdot j}\right), \qquad i = 1, \ldots, G, \quad j = 1, \ldots, E,$$
which holds the genotype main effect G and the interaction GE together. With the singular value decomposition
$\mX = \mU\mD\mV^\top$ and its first two components, genotype $i$ is plotted at the point $\vgs_i$ and environment $j$
at the end of the vector $\ves_j$, the rows of
$$\mathbf{G}_P = \mU_2\mD_2^{P}, \qquad \mathbf{E}_P = \mV_2\mD_2^{1-P},$$
where $P$ is the singular value partition:

| `.svp`          | $P$ | the singular values go to | the plot preserves                                               |
|:----------------|:---:|:--------------------------|:-----------------------------------------------------------------|
| `"genotype"`    | 1   | the genotypes             | distances between genotypes (genotype metric)                    |
| `"symmetric"`   | 0.5 | both, equally             | neither metric exactly; a balanced picture                       |
| `"environment"` | 0   | the environments          | lengths of environment vectors and the angles between them       |

For every $P$ the inner product is the same,
$$\vgs_i^\top\ves_j = \sum_{n=1}^{2} u_{in}\lambda_n^{P}\lambda_n^{1-P}v_{jn} = \hat{x}_{ij},$$
the two-component fit of genotype $i$ in environment $j$. Everything that rests on inner products alone is
therefore *invariant to the scaling*:

* **the winner of each environment** (the largest $\hat{x}_{ij}$ in column $j$). In these data the winners
  `r if (same_w) "are indeed identical" else "differ"` under the three scalings;
* **the ranking of genotypes on the average environment axis**. The axis has the direction
  $\va \propto \bar{\ves} = \mathbf{E}_P^\top\mathbf{1}/E$, and the projection
  $\vgs_i^\top\bar{\ves} = \sum_j\hat{x}_{ij}/E$ is the two-component mean of genotype $i$ whatever $P$ is. In these
  data the order on the axis `r if (same_a) "is identical" else "differs"` under the three scalings.

What depends on the scaling are distances and angles:

* **the polygon** of which won where. A genotype that wins somewhere is always a vertex, but non-winning vertices can
  come or go. Here the vertices `r if (same_v) "happen to be the same" else "change"` under the three scalings;
* **instability**, the distance of a genotype from the average environment axis. Its rank correlation between
  genotype-focused and symmetric scaling is `r ri_gs`, and between genotype-focused and environment-focused scaling
  `r ri_ge`;
* **discrimination**, the length of an environment vector. Under environment-focused scaling it equals the
  standard deviation of the fitted genotype differences in that environment multiplied by $\sqrt{G-1}$, because
  $\lVert\ves_j\rVert^2 = \sum_n\lambda_n^2v_{jn}^2 = \sum_i\hat{x}_{ij}^2$. The rank correlation of the lengths
  between environment-focused and symmetric scaling is `r rl_es`, and between environment-focused and
  genotype-focused scaling `r rl_eg`;
* **representativeness**, the angle of an environment with the average environment axis. Under environment-focused
  scaling the cosine of the angle between two vectors is the correlation of the two environments in the
  two-component fit. Rank correlations of the angles with the environment-focused angles: `r ra_es` (symmetric) and
  `r ra_eg` (genotype-focused).

How large the distortion is depends on the ratio of the singular values: changing $P$ by one stretches PC1
relative to PC2 by the factor $\lambda_1/\lambda_2$. In these data the two components hold
`r sprintf("%.1f%%", pct)` of G + GE (PC1 `r sprintf("%.1f%%", pc[1])`, PC2 `r sprintf("%.1f%%", pc[2])`), so
$\lambda_1/\lambda_2 = `r sprintf("%.2f", lam)`$, a mild ratio: the rankings above change little, although the
distances themselves do change. In trials where PC1 dominates more, the choice of scaling matters more. Two components holding less than half of G + GE is a modest share, so every view below is an approximation; `stab_interpret()`
adds a caution whenever the share is below 60 percent.

For display, `gge_biplot()` multiplies the less spread set of points (environments under genotype-focused scaling,
genotypes under environment-focused scaling) by one constant so that both sets fill the plot. A common constant
changes neither directions nor rankings, so the readings below are unaffected.

# Choosing the view and the scaling

| question                                                                      | `.which`            | `.svp`          |
|:-----------------------------------------------|:------------------------------|:--------------------|
| first look at the data; which genotypes and environments go together           | `"basic"`           | `"symmetric"`   |
| how similar are the genotypes (clustering, parents for crossing)               | `"basic"`           | `"genotype"`    |
| how similar are the environments (correlations, redundant sites)               | `"basic"`           | `"environment"` |
| which genotype wins where; are there mega-environments                         | `"which-won-where"` | `"symmetric"`   |
| which genotypes are high-yielding and stable (wide recommendation)             | `"mean-stability"`  | `"genotype"`    |
| which environments are good test sites; which are redundant or contrary        | `"discrimination"`  | `"environment"` |

The other combinations are not wrong, but they show the question in a distorted metric. They are shown below so that
the differences can be seen, and `stab_interpret()` names the scaling that suits a view when another one is used.

# Basic biplot

The basic view shows the genotype points and the environment vectors without any construction. It answers the
questions that rest on inner products directly: a genotype does well, relative to the environment mean, in an
environment whose vector points towards it. The farther it projects along the vector, the better it does there; a
projection on the opposite side means a yield below the environment mean.

## Genotype-focused scaling

```{r basic-g}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "basic"
    , .svp   = "genotype"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The distance between two genotype points approximates the distance between their rows of $\mX$, that
is, how differently the two genotypes perform over all environments. Genotypes close together are similar, and
genotypes far from the origin are the most responsive. The environment vectors keep their directions, but their
lengths are not comparable.

**When to use.** To compare genotypes with each other: grouping genotypes, choosing dissimilar parents, or checking
that two entries are duplicates.

## Symmetric scaling

```{r basic-s}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "basic"
    , .svp   = "symmetric"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** Genotypes and environments spread alike, so neither set crowds near the origin. Neither distances
between genotypes nor angles between environments are exact, but the picture is balanced.

**When to use.** For the first look at a trial, for presentations, and as the base of which won where.

## Environment-focused scaling

```{r basic-e}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "basic"
    , .svp   = "environment"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The length of an environment vector is proportional to the spread of the genotypes in that environment,
and the cosine of the angle between two vectors approximates the correlation between the two environments. An acute
angle means the two environments rank genotypes alike, a right angle means no relation, and an obtuse angle means
opposite rankings (crossover interaction).

**When to use.** To compare environments: finding groups of correlated sites, redundant sites, or sites that rank the
genotypes against the rest.

# Which won where

The convex hull of the genotype points is drawn, with lines (rays) from the origin perpendicular to its sides. The
rays divide the plane into sectors. The vertex genotype of a sector has the largest inner product with every
environment that falls in it, so it is the winner there. Sectors that hold environments define candidate
mega-environments. A vertex whose sector holds no environment wins nowhere and is among the poorest in some
environments. Genotypes inside the polygon, near the origin, respond little to the environments.

In this trial the winners are `r lst(win)`, under every scaling.

## Genotype-focused scaling

```{r www-g}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "which-won-where"
    , .svp   = "genotype"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The winners and their environments are those of the symmetric plot. The polygon is stretched along PC1,
because PC1 carries $\lambda_1$ rather than $\sqrt{\lambda_1}$, so the sectors of genotypes that differ mainly on PC2
become narrow and their environments are hard to see.

**When to use.** When which won where is wanted on the same plot as distances between genotypes. Otherwise prefer
symmetric scaling.

## Symmetric scaling

```{r www-s}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "which-won-where"
    , .svp   = "symmetric"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** This is the standard which-won-where plot (Yan and Tinker 2006). Read every sector with its vertex
genotype and its environments; each group of environments with one winner is a candidate mega-environment.

**When to use.** To investigate mega-environments and specific adaptation, and to decide whether one recommendation
covers the region (one winner) or the region needs to be split (several winners). The grouping must recur over years
before it guides recommendations.

## Environment-focused scaling

```{r www-e}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "which-won-where"
    , .svp   = "environment"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The winners are again unchanged. The genotype points crowd along the second axis and the polygon
becomes flatter. The environment vectors now show correlations, so one plot shows both the sectors and how strongly
the environments of a sector agree.

**When to use.** When the grouping of environments is the main interest and the polygon is secondary.

# Mean against stability

The average environment axis (the arrow) runs through the origin and the average of the environment points. The
projection of a genotype on it estimates the genotype's mean, and the arrow points to higher means. The dotted line
from a genotype to the axis measures instability: its contribution to the interaction beyond the average response.
The ideal genotype sits far along the arrow and on the axis.

## Genotype-focused scaling

```{r ms-g}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "mean-stability"
    , .svp   = "genotype"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
head(x = attr(x = p, which = "gge")$atc, n = 8)
```

**Reading.** The genotype metric makes the dotted distances comparable across genotypes. A genotype with a long
projection along the arrow and a short dotted line is high-yielding and stable. A long dotted line marks specific
adaptation; the which-won-where plot shows where.

**When to use.** To select genotypes for wide recommendation, combining mean and stability (Yan and Kang 2003). This
is the scaling for this view.

## Symmetric scaling

```{r ms-s}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "mean-stability"
    , .svp   = "symmetric"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The order of the projections on the arrow is that of the genotype-focused plot, so the ranking of the
means can still be read. The dotted distances are no longer in the genotype metric: PC2 is relatively enlarged, and
genotypes that differ on PC2 look less stable than they are. In these data the rank correlation of instability with
the genotype-focused plot is `r ri_gs`.

**When to use.** Only when one plot has to carry several views. For selection, redraw with `.svp = "genotype"`.

## Environment-focused scaling

```{r ms-e}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "mean-stability"
    , .svp   = "environment"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The ranking on the arrow still holds. The genotype points carry no singular values, so both axes count
equally and the instability is measured in the wrong metric: a genotype that deviates on PC2 is penalized as much as
one that deviates equally on PC1, although PC1 carries `r sprintf("%.2f", lam)` times as much variation. In these
data the rank correlation of instability with the genotype-focused plot is still `r ri_ge`, because the ratio of the
singular values is mild; with a dominant PC1 it can be much lower.

**When to use.** Not for judging stability. It is shown here to make the point that the scaling matters for this
view.

# Discriminativeness and representativeness

The environment vectors are drawn from the origin, with the average environment axis. A long vector discriminates
well between genotypes. A small angle with the axis marks a representative environment. The ideal test environment
is long and on the axis.

Four kinds of environment can be told apart:

* long and representative: the best test sites, for selecting superior genotypes;
* long but at a wide angle: useful for culling unstable genotypes, not for selecting the best ones;
* short: little information, and candidates for dropping if they stay short over years;
* at more than 90 degrees from the axis: they rank the genotypes against the other environments, a sign of crossover
  or of a different mega-environment.

## Genotype-focused scaling

```{r dis-g}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "discrimination"
    , .svp   = "genotype"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** The environment vectors carry no singular values, so their lengths reflect only how each environment
loads on the two components, not how strongly it separates the genotypes, and the angles no longer give
correlations. In these data the ranks of lengths and angles still agree closely with the environment-focused plot
(rank correlations `r rl_eg` and `r ra_eg`), because the ratio of the singular values is mild; this cannot be
counted on in general.

**When to use.** Not for evaluating test environments.

## Symmetric scaling

```{r dis-s}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "discrimination"
    , .svp   = "symmetric"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
```

**Reading.** This is a compromise: the lengths and angles are between those of the genotype-focused and the
environment-focused plots. In these data their ranks agree with the environment-focused plot (rank correlations
`r rl_es` and `r ra_es`), so the same environments come out as most discriminating and most representative; the
cosines are still not correlations.

**When to use.** For a quick look at the test sites when one symmetric plot is used for all views.

## Environment-focused scaling

```{r dis-e}
p <-
  gge_biplot(
      .data  = ge_data
    , .y     = Yield
    , .rep   = Rep
    , .gen   = Gen
    , .env   = Env
    , .which = "discrimination"
    , .svp   = "environment"
    )
p
stab_interpret(
    x             = p
  , higher_better = TRUE
  , top           = 5
  )
head(x = attr(x = p, which = "gge")$envs[order(-Length)], n = 6)
```

**Reading.** The vector length is the standard deviation of the fitted genotype differences in the environment,
times $\sqrt{G-1}$, and the cosine of the angle between two environments is their correlation in the two-component
fit. Read the long vectors close to the arrow as the best test sites, and the obtuse angles as negative
correlations.

**When to use.** To evaluate test environments: choose sites for future trials, find redundant sites, and find sites
that disagree with the rest (Yan et al. 2007). This is the scaling for this view.

# All twelve at a glance

```{r grid, fig.width = 11, fig.height = 13, echo = FALSE}
pl <- list()
for (w in views) for (s in svps) {
  pl[[paste(w, s)]] <-
    gge_biplot(
        .data  = ge_data
      , .y     = Yield
      , .gen   = Gen
      , .env   = Env
      , .which = w
      , .svp   = s
      ) +
    ggplot2::labs(title = w, subtitle = paste(s, "scaling"), x = NULL, y = NULL) +
    ggplot2::theme(legend.position = "none", plot.title = ggplot2::element_text(size = 9),
                   plot.subtitle = ggplot2::element_text(size = 8),
                   axis.text = ggplot2::element_text(size = 6))
}
pl <-
  lapply(
      X   = pl
    , FUN = function(q) { q$layers <- Filter(
          f = function(l) !inherits(x = l$geom, what = "GeomTextRepel")
        , x = q$layers
        )
      q }
    )
grid::grid.newpage()
grid::pushViewport(grid::viewport(layout = grid::grid.layout(nrow = 4, ncol = 3)))
for (k in seq_along(pl)) {
  print(
      pl[[k]]
    , vp = grid::viewport(layout.pos.row = (k - 1) %/% 3 + 1, layout.pos.col = (k - 1) %% 3 + 1)
    )
}
```

The rows are the views and the columns the scalings, with the labels removed for size. Down a column the points do
not move; across a row the same question is shown in three metrics.

# Summary

* The view (`.which`) chooses the question, and the scaling (`.svp`) chooses the metric in which it is answered.
* Winners and the ranking on the average environment axis are invariant to the scaling. Polygons, instability, and
  the lengths and angles of environment vectors are not.
* Use symmetric scaling for which won where, genotype-focused scaling for mean against stability, and
  environment-focused scaling for discrimination and representativeness.
* `stab_interpret()` writes the findings of the view that was drawn and warns when the scaling does not suit it.
* With `r sprintf("%.1f%%", pct)` of G + GE on two components, these data call for caution. Check the conclusions
  with `ammi()`, `stab_eskridge()` and the other stability statistics, and confirm mega-environments over years.

# References

Yan, W., Hunt, L. A., Sheng, Q. and Szlavnics, Z. (2000). Cultivar evaluation and mega-environment investigation based on the GGE biplot. *Crop Science*, 40(3), 597--605.

Yan, W. and Kang, M. S. (2003). *GGE Biplot Analysis: A Graphical Tool for Breeders, Geneticists, and Agronomists*. CRC Press, Boca Raton.

Yan, W. and Tinker, N. A. (2006). Biplot analysis of multi-environment trial data: principles and applications. *Canadian Journal of Plant Science*, 86(3), 623--645.

Yan, W., Kang, M. S., Ma, B., Woods, S. and Cornelius, P. L. (2007). GGE biplot vs. AMMI analysis of genotype-by-environment data. *Crop Science*, 47(2), 643--653.
