--- title: "Fourfold Displays for 'ggplot2'" author: "Michael Friendly and Gavin Klorfine" date: "`r Sys.Date()`" output: rmarkdown::html_vignette bibliography: refs.bib csl: apa.csl vignette: > %\VignetteIndexEntry{Fourfold Displays for 'ggplot2'} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>" ) ``` ```{r setup} library(ggfourfold) library(ggplot2) ``` **ggfourfold** provides `geom_fourfold()` and `theme_fourfold()` for drawing fourfold displays of $2 \times 2$ and $2 \times 2 \times k$ tables with **ggplot2** to show the direction and strength of association between two binary variables, and its pattern of association across $k$ strata. For example, in the classic case we use here, one could ask if there is an association between gender and admission to graduate school, and whether this association varies across the departments applied to. This introduction follows the treatment in @FriendlyMeyer:2016:DDAR [Section 4.4]. ## Fourfold displays The fourfold display is a special case of a radial diagram, or "polar area chart", designed for $2 \times 2$ (or $2 \times 2 \times k$) tables [@Fienberg:75; @Friendly:94:TR217; @Friendly:94c]. The frequency $n_{ij}$ in each cell is shown by a quarter circle whose radius is proportional to $\sqrt{n_{ij}}$, so that its area is proportional to the cell count. Like a pie chart, it uses segments of a circle to show frequencies; unlike a pie chart, it keeps the angles of the segments constant and varies the radius. This is the $2 \times 2$ cousin of the graphic form used by Florence Nightingale [@Nightingale:1858]. @FriendlyAndrews:2021 describe the rich history of radial diagrams. The main purpose of the display is to show the sample odds ratio ($\hat{\theta}$). This gives a number that compares the odds of an event (accepted!) happening in one group (men) to the odds of it happening in another group (women). $$ \hat{\theta} = \frac{n_{11} / n_{12}}{n_{21} / n_{22}} = \frac{n_{11} \, n_{22}}{n_{12} \, n_{21}} . $$ An association between the two variables ($\theta \neq 1$) appears as a tendency for one pair of diagonally opposite cells to be larger than the other pair. The fourfold plot shows the association and the odds ratio visually as follows: * Color and a diagonal line show the direction of association: the relatively larger diagonal pair is drawn in blue (more than expected under independence) with a line through the center along that diagonal, and the other pair in red. * Confidence rings around each quadrant give a visual test of independence, $H_0: \theta = 1$: in a standardized display, the rings for adjacent quadrants overlap if and only if the observed counts are consistent with the null hypothesis. ## Example: Berkeley admissions The classic example, `UCBAdmissions` gives applicants to the six largest graduate departments at Berkeley in 1973, classified by admission and gender [@Bickel-etal:75]. `geom_fourfold()` currently works on data in *long form*: map the two-level variables to `x` (drawn left to right) and `y` (drawn top to bottom), and the cell frequencies to `weight`. Without faceting, rows for the same cell are summed, so this pools over departments: ```{r berkeley} ucb <- as.data.frame(UCBAdmissions) berkeley <- ggplot(ucb, aes(x = Admit, y = Gender, weight = Freq)) + theme_fourfold() ``` The unstandardized display (`std = "ind.max"`) shows the raw frequencies, with the largest cell drawn at full size. The sample odds ratio, Odds(Admit | Male) / Odds(Admit | Female), is $(1198 / 1493) / (557 / 1278) = 1.84$: male applicants were nearly twice as likely to be admitted. ```{r berkeley-raw, fig.width = 4, fig.height = 4} berkeley + geom_fourfold(std = "ind.max") ``` These comparisons are difficult because there are more male applicants than female, and because more applicants were rejected than admitted. In the unstandardized display the confidence rings have no interpretation as a test of $H_0: \theta = 1$. The data can be standardized to make the comparison easier while preserving the odds ratio. With `margin = 1` the rows (here, `Gender`) are equated, so each quadrant shows the proportion of men or women who were admitted or rejected: 44.5% of male applicants were admitted, compared with 30.4% of female applicants. Now the rings for each upper quadrant overlap those of the quadrant below it if the odds ratio does not differ from 1. ```{r berkeley-rows, fig.width = 4, fig.height = 4} berkeley + geom_fourfold(margin = 1) ``` The default, `margin = c(1, 2)`, standardizes *both* margins. The four quadrants then align horizontally and vertically when the odds ratio is 1, regardless of the marginal frequencies, so the association between admission and gender can be seen without the influence of the overall admission rate or of the numbers of men and women who applied. This fully standardized display is usually the most useful form. ```{r berkeley-std, fig.width = 4, fig.height = 4} berkeley + geom_fourfold() ``` The quadrants do not align, and the 95% confidence rings, computed from the interval $1.62 \le \theta \le 2.09$, do not overlap: the odds ratio differs significantly from 1, apparent evidence of gender bias in favor of men. ## Stratified $2 \times 2 \times k$ tables In a $2 \times 2 \times k$ table the third variable often defines strata, and the question is whether the association between the first two is the same in each stratum. Make one fourfold display per stratum with `facet_wrap()` or `facet_grid()`; standardizing the margins within each panel allows the pattern of association to be compared across strata even when their marginal frequencies differ. ```{r berkeley-dept, fig.width = 7, fig.height = 5} berkeley + geom_fourfold() + facet_wrap(vars(Dept), ncol = 3, labeller = label_both) ``` Surprisingly, for five of the six departments the odds of admission are about the same for men and women. Only Department A differs, and in the opposite direction: there women were about 2.86 times as likely as men to be admitted ($1 / 0.349$). The more intense colors mark the one panel whose odds ratio differs significantly from 1. With several strata, `geom_fourfold()` adjusts the p-values that control this emphasis for multiple testing, by default using Holm's method via `stats::p.adjust()`; the confidence rings themselves are not adjusted. This reversal of the aggregate result is an example of Simpson's paradox, and it shows why an analysis of a table collapsed over a third variable can mislead. Men and women applied to departments in different proportions, and women applied in greater numbers to departments with low admission rates. Because a fourfold display keeps the angle of each cell fixed, corresponding cells are always in the same position in every panel. An array of fourfold displays therefore supports *controlled comparison*, against a standard with other things held constant, much better than an array of pie charts. Standardizing the margins adds a further standard for comparison while preserving the odds ratio, the quantity the display is designed to show. ## Confidence rings Each quadrant of a fourfold display is drawn with a pair of thin rings, one just inside and one just outside its edge. Together they show the range of tables that are consistent with the data at the confidence level given by `conf_level` (0.95 by default). They are constructed in three steps. **1. An interval for the odds ratio.** Inference is simplest on the log scale. The log odds ratio, $\hat{\psi} = \log \hat{\theta}$, has an approximate standard error $$ \hat{s}(\hat{\psi}) = \sqrt{\frac{1}{n_{11}} + \frac{1}{n_{12}} + \frac{1}{n_{21}} + \frac{1}{n_{22}}} , $$ so an approximate $1 - \alpha$ confidence interval for $\psi$ is $\hat{\psi} \pm z_{1 - \alpha/2} \, \hat{s}(\hat{\psi})$. Exponentiating its end points, $\hat{\psi}_l$ and $\hat{\psi}_u$, gives the interval $\{\exp(\hat{\psi}_l), \exp(\hat{\psi}_u)\}$ for $\theta$. (If any cell is zero, 0.5 is first added to every cell, as in `vcd::fourfold()`.) For the pooled Berkeley data, $\hat{\psi} = 0.610$ and $\hat{s} = 0.0639$, so the 95% interval for the odds ratio runs from 1.624 to 2.087. **2. A table for each end of the interval.** Each limit is turned into the $2 \times 2$ table that has that odds ratio. When both margins are equated, such a table has the symmetric form $$ \begin{bmatrix} p & 1 - p \\ 1 - p & p \end{bmatrix} , $$ whose odds ratio is $\theta = p^2 / (1 - p)^2$. Solving for $p$ gives $p = \sqrt{\theta} / (1 + \sqrt{\theta})$. More generally, the table can be found that keeps the observed row and column totals and has the required odds ratio; with the margins fixed, the whole table follows from a single cell, which is the root of a quadratic equation. **3. Draw the tables like the data.** Each limiting table is standardized in the same way as the observed table (`std`, `margin`) and drawn as outlines over the quadrants. For the Berkeley data this gives: | | Odds ratio | $p$ | Male, admitted | Male, rejected | Female, admitted | Female, rejected | |:----------------|-----------:|------:|---------------:|---------------:|-----------------:|-----------------:| | Lower 95% limit | 1.624 | 0.560 | 1167.1 | 1523.9 | 587.9 | 1247.1 | | Observed | 1.841 | 0.576 | 1198 | 1493 | 557 | 1278 | | Upper 95% limit | 2.087 | 0.591 | 1228.4 | 1462.6 | 526.6 | 1308.4 | Every row has the observed margins, 2691 male and 1835 female applicants, of whom 1755 were admitted. Only the balance among the cells changes. ### Reading the rings The distance between the rings of a quadrant shows the precision of the estimate: large samples give narrow rings, like those for the pooled Berkeley data above, and small samples give wide ones. More usefully, the rings give a visual test of independence, $H_0: \theta = 1$. When both margins are equated, independence corresponds to $p = 1/2$, so all four quadrants would have the same radius. If the confidence interval for $\theta$ includes 1, that table of equal quadrants lies between the rings, and the rings of each pair of adjacent quadrants overlap. If the interval excludes 1, the rings of adjacent quadrants are separated. For the pooled Berkeley data, they are clearly separated. How much of this reading holds depends on the standardization: - With both margins equated (the default), the test applies in both directions, between horizontally and vertically adjacent quadrants. - With `margin = 1`, only the rows are equated. The rings of each upper quadrant and the quadrant below it overlap if and only if the interval includes 1; nothing can be said about quadrants side by side, because the column totals have not been equated. - In an unstandardized display (`std = "ind.max"` or `"all.max"`), the rings still show the precision of each cell, but their overlap is not a test of $H_0$. With several strata, each panel's rings show that stratum's own interval at `conf_level`. They are not adjusted for multiple comparisons, as is also the case in `vcd::fourfold()`. Only the *p*-values that decide which panels are drawn in the more intense colors are adjusted (Holm's method, by default). A stratum whose rings only just separate can therefore still be drawn in the paler colors. Set `conf_level = 0` to omit the rings. ## Customizing the display ### Square cells **ggfourfold** allows for four squares to be drawn instead of quarter-circles via the `shape` argument of `geom_fourfold()`. Squares are drawn with area equal to the quarter-circle they replace (side $r\sqrt{\pi}/2$ for radius $r$), and confidence rings become square outlines that are read in the same way. ```{r berkeley-square, fig.width = 7, fig.height = 5} berkeley + geom_fourfold(shape = "square") + facet_wrap(vars(Dept), ncol = 3, labeller = label_both) ``` ### Cell counts By default, raw cell counts are pictured in the inside corners of each display's frame; however, if the drawing of quarter-circles (or squares) comes close to overlapping these counts, they are displayed just outside the frame of each display. To fix the placement, supply `counts = "inside"` or `counts = "outside"` to `geom_fourfold()`. `counts = "none"` hides them, while the default, automatic placement uses `counts = "auto"`. ```{r berkeley-square-outside, fig.width = 7, fig.height = 5} berkeley + geom_fourfold(shape = "square", counts = "outside") + facet_wrap(vars(Dept), ncol = 3, labeller = label_both) ``` ### Colors Displays are *extended* by default: each stratum is shaded by the significance of its association, as in the Berkeley examples above, and a diagonal line is drawn through the center, along the diagonal with more cases than expected. If `extended = FALSE` is supplied to `geom_fourfold()`, every panel is shaded alike, with the larger diagonal being darker. Significance is not shown and the diagonal line is omitted, giving a simpler display. ```{r berkeley-simple, fig.width = 7, fig.height = 5} berkeley + geom_fourfold(extended = FALSE) + facet_wrap(vars(Dept), ncol = 3, labeller = label_both) ``` Since colors carry the display's meaning, they are drawn directly through the `geom` rather than mapped through a fill scale. `scale_fill_manual()` and similar functions therefore have no effect. Supply them through the `palette` argument instead, as a vector of six colors in the same order used by `fourfold_palette()`. ```{r ff-palette} fourfold_palette() ``` The six colors form three pairs: - Colors 1–2 are used when `extended = FALSE` or `conf_level = 0` - Colors 3–4 are used for a stratum whose association is not significant after adjustment - Colors 5–6 are used for a stratum whose association is significant after adjustment Within each pair, the first color fills the diagonal with *fewer* cases than expected under independence and the second fills the diagonal with *more*. For example, we can construct a palette based on the Okabe-Ito colors [@OkabeIto:2008], which remain distinguishable for readers with common forms of color-deficient vision. Lightened orange and sky blue mark strata whose association is not significant, while vermilion and blue mark those whose association is significant. ```{r berkeley-okabe-ito, fig.width = 7, fig.height = 5} okabe_ito <- c( "#56B4E9", "#0072B2", # extended = FALSE: sky blue, blue "#F2CF7F", "#AAD9F3", # not significant: orange, sky blue (lightened) "#D55E00", "#0072B2" # significant: vermilion, blue ) berkeley + geom_fourfold(palette = okabe_ito) + facet_wrap(vars(Dept), ncol = 3, labeller = label_both) ``` This palette is also built into **ggfourfold**, so the same display can be drawn with `fourfold_palette("okabe-ito")`. ```{r berkeley-okabe-ito-builtin, eval = FALSE} berkeley + geom_fourfold(palette = fourfold_palette("okabe-ito")) + facet_wrap(vars(Dept), ncol = 3, labeller = label_both) ``` ### Diagonal line The diagonal line marking the direction of association can be omitted by supplying `diagonal = FALSE` to `geom_fourfold()`. The length, interior color, and width of the line can be changed with arguments `diagonal.length`, `diagonal.fill`, and `diagonal.width`. The line's outline follows the `color` and `linewidth` of the layer, like the cell outlines, confidence rings, axes, and frame. `color` also applies to the text. ```{r berkeley-diagonal, fig.width = 5, fig.height = 5} berkeley + geom_fourfold( diagonal.length = 0.05, diagonal.fill = "limegreen", diagonal.width = 1.5, color = "grey30" ) ``` ### Working with 'ggplot2' A fourfold display is an ordinary ggplot, so the usual tools for finishing a figure all apply. Below, `ggplot2::labs()` adds a title that states the finding and adds a caption giving the source of the data. Additionally, the facet labeller adds the number of applicants to each panel, which makes the departments easy to compare without needing to add up their cell counts. The text drawn by `geom_fourfold()` itself, such as the category labels and cell counts, follows the `base_size` and `base_family` of `theme_fourfold()`, meaning that a single argument changes the font of the whole figure. If more control is needed, other theme settings can be passed through the `...` of `theme_fourfold()` or added with `ggplot2::theme()` as usual. ```{r berkeley-finished, fig.width = 7, fig.height = 5.8} applicants <- margin.table(UCBAdmissions, 3) dept_labels <- sprintf("%s (n = %d)", names(applicants), applicants) names(dept_labels) <- names(applicants) ggplot(ucb, aes(x = Admit, y = Gender, weight = Freq)) + geom_fourfold() + facet_wrap(vars(Dept), ncol = 3, labeller = labeller(Dept = dept_labels)) + labs( title = "UC Berkeley Admissions, 1973", subtitle = "Only department A shows a significant association", caption = "Data from Bickel et al. (1975)" ) + theme_fourfold(base_family = "serif") + theme( plot.title.position = "plot", plot.title = element_text(hjust = 0), plot.subtitle = element_text( hjust = 0, color = "grey30", margin = margin(b = 10) ), plot.caption = element_text( hjust = 1, color = "grey30", size = rel(0.8), margin = margin(t = 10) ) ) ``` ## References