--- title: "Advanced Circumplex Visualization" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Advanced Circumplex Visualization} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5, out.width = "100%" ) set.seed(12345) library(ggplot2) ``` ```{r setup} library(circumplex) ``` ## Beyond the built-in plots The `ssm_plot_circle()`, `ssm_plot_curve()`, and `ssm_plot_contrast()` functions cover the most common circumplex figures, but they each produce a finished plot with a fixed set of layers. Sometimes you want more control: to overlay individual respondents on a group profile, to annotate a specific region of the circle, to restyle the points, or to place several circumplex panels side by side. To make that possible, `circumplex` exposes the building blocks that the built-in plots are themselves made of. These are ordinary [`ggplot2`](https://ggplot2.tidyverse.org/) components, so you compose them with `+` and combine them freely with any other `ggplot2` layers, scales, and themes: - `ggcircumplex()` draws the empty circular **canvas** (amplitude rings, displacement spokes, and scale labels). - `geom_ssm_point()` and `geom_ssm_arc()` are polar-native **layers** that place profile points and their confidence regions in the circle, taking amplitude and displacement directly as aesthetics. - `scale_x_circumplex()` is a **scale** for the angle axis of linear circumplex plots (such as the score-by-angle curve). This vignette works through each of these and then combines them. ## The circular canvas `ggcircumplex()` returns a `ggplot2` object containing just the circular backdrop, with no data drawn on it yet. By default it uses octant scales labeled by their angular position in degrees: ```{r canvas-default} ggcircumplex() ``` You can label the scales however you like. Passing a character vector labels the spokes in the order of the angles: ```{r canvas-labels} ggcircumplex(octants(), labels = PANO()) ``` If you are working with one of the instruments bundled with the package, you can pass it directly and the scale angles and abbreviations are taken from the instrument: ```{r canvas-instrument} ggcircumplex(instrument = csip) ``` The `amax` argument sets the amplitude represented by the outer ring; it also determines the amplitude gridline labels. It matters because it is the shared reference that the data layers below must agree with, as we will see next. ## Placing SSM results in the circle Let's estimate a Structural Summary Method (SSM) profile for two measures and draw it on the canvas ourselves, rather than calling `ssm_plot_circle()`. ```{r analyze} results <- ssm_analyze( jz2017, scales = PANO(), measures = c("NARPD", "ASPD") ) results$results[, c("Label", "a_est", "d_est", "a_lci", "a_uci")] ``` `geom_ssm_point()` places a point for each profile at its amplitude (`a_est`) and displacement (`d_est`), and `geom_ssm_arc()` draws the wedge spanning each profile's amplitude confidence interval radially and its displacement confidence interval angularly. Both take the SSM parameters directly as aesthetics and handle the conversion into circular coordinates internally, including wrap-around when a displacement interval crosses the 0/360 degree boundary. The one rule to remember is that **`amax` must be the same** for the canvas and for every data layer, so that amplitudes map onto the same radius. Here we set it once and reuse it: ```{r results-plot} amax <- 1 ggcircumplex(octants(), labels = PANO(), amax = amax) + geom_ssm_arc( data = results$results, mapping = aes( amplitude_min = a_lci, amplitude_max = a_uci, displacement_min = d_lci, displacement_max = d_uci, fill = Label ), amax = amax, alpha = 0.4, color = NA ) + geom_ssm_point( data = results$results, mapping = aes(amplitude = a_est, displacement = d_est, fill = Label), amax = amax ) ``` Each arc displays two separate confidence intervals for one profile at once: its radial extent is the amplitude interval and its angular extent is the displacement interval. It is a convenient way to show both intervals together, not a single joint confidence region with its own coverage level, and not a hypothesis test. The angular extent in particular is a range of plausible *directions*: because zero degrees is an arbitrary reference direction rather than a null value, it should not be read as a significance test the way a confidence interval for a linear parameter (such as elevation) can be. Displacement is only worth interpreting at all when the amplitude interval is clearly above zero and the model fits reasonably well (see the "Introduction to SSM Analysis" vignette and `?ssm_analyze`). ## Composing custom layers Because the canvas and geoms are ordinary `ggplot2` objects, you can add anything else to them. A common request is to show where individual respondents fall relative to a summary. We can compute each person's own amplitude and displacement with `ssm_score()` and draw them as a faint cloud behind a group-level point. ```{r individuals, warning = FALSE} # Per-person SSM parameters for a subset of the sample. A respondent whose # scores are flat has no displacement and is returned as NA (with a warning), # so we keep only the well-defined profiles. people <- ssm_score( jz2017[1:100, ], scales = PANO(), append = FALSE ) people <- people[!is.na(people$Disp), ] # Group-level profile for the same subset group <- ssm_analyze(jz2017[1:100, ], scales = PANO()) amax <- 3 ggcircumplex(octants(), labels = PANO(), amax = amax) + geom_ssm_point( data = people, mapping = aes(amplitude = Ampl, displacement = Disp), amax = amax, fill = "grey70", size = 1.5, alpha = 0.6 ) + geom_ssm_point( data = group$results, mapping = aes(amplitude = a_est, displacement = d_est), amax = amax, fill = "#0072B2", size = 4 ) ``` The individual points spread around the circle while the group summary sits near their center of mass, a picture that none of the built-in functions produce directly. Any other `ggplot2` layer --- text annotations, additional geoms, faceting --- can be added the same way. ## The angle axis for linear plots Not every circumplex figure is circular. The score-by-angle curve drawn by `ssm_plot_curve()` is a linear plot whose x-axis runs through the scale angles. `scale_x_circumplex()` labels that axis consistently with the circular canvas: by default with the angle in degrees, or with custom labels or an instrument's abbreviations. ```{r curve-axis} angles <- octants() curve <- data.frame( angle = angles, score = 1 + 0.8 * cos((angles - 135) * pi / 180) ) ggplot(curve, aes(x = angle, y = score)) + geom_line() + geom_point(size = 2) + scale_x_circumplex(angles, labels = PANO()) + labs(x = "Scale", y = "Score") + theme_bw() ``` Passing the same `labels` (or the same `instrument`) to both `ggcircumplex()` and `scale_x_circumplex()` guarantees that a circular figure and a linear one label their scales identically. ## Relationship to the built-in plots The built-in plotting functions are implemented on exactly these components: `ssm_plot_circle()` is `ggcircumplex()` plus `geom_ssm_arc()` and `geom_ssm_point()`, and `ssm_plot_curve()` uses `scale_x_circumplex()` for its angle axis. So you can always start from a built-in plot and add to it, or rebuild it from the pieces when you need finer control. Whichever route you take, the coordinates are computed the same way, so the results line up. ## References * Gurtman, M. B. (1992). Construct validity of interpersonal personality measures: The interpersonal circumplex as a nomological net. _Journal of Personality and Social Psychology, 63_(1), 105--118. * Wright, A. G. C., Pincus, A. L., Conroy, D. E., & Hilsenroth, M. J. (2009). Integrating methods to optimize circumplex description and comparison of groups. _Journal of Personality Assessment, 91_(4), 311--322. * Zimmermann, J., & Wright, A. G. C. (2017). Beyond description in interpersonal construct validation: Methodological advances in the circumplex Structural Summary Approach. _Assessment, 24_(1), 3--23.