--- title: "Exploring data" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Exploring data} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = FALSE, comment = "", fig.width = 7, fig.height = 4.5, dpi = 96, dev.args = list(bg = "transparent")) # Console colour carries no meaning on a rendered page. pkgdown turns it on for # its own build, and the escape sequences then reach the reader as literal text, # so colour is switched off here for a plain vignette render and a site build # alike. The fixed width keeps printed output inside the documentation column. options(cli.num_colors = 1, cli.hyperlink = FALSE, crayon.enabled = FALSE, width = 80) # Figures on the package website sit on a warm off-white page in light mode and # are inverted by pkgdown in dark mode, so an opaque background would read as a # pale slab one way and a black plate the other. Two things paint one. The # device canvas is made transparent by `dev.args` above, and theme_depictr() # then inherits theme_minimal()'s white plot.background, which is drawn over # that canvas, so it is cleared as each figure is printed. This is deliberately # a vignette-level choice: theme_depictr() keeps its opaque background, which # is what a figure saved for a paper wants. transparent_bg <- ggplot2::theme( plot.background = ggplot2::element_rect(fill = NA, colour = NA), panel.background = ggplot2::element_rect(fill = NA, colour = NA) ) knit_print.ggplot <- function(x, ...) knitr::normal_print(x + transparent_bg) knit_print.patchwork <- function(x, ...) knitr::normal_print(x & transparent_bg) library(depictr) ``` depictr provides a coherent set of exploratory plots, estimation plots and a descriptive table, all sharing the package theme and palette. Column names can be given quoted or unquoted. The examples use the bundled `lexical_decision`, `wellbeing_survey` and `crop_yield` datasets. A closing section shows how to customise any of these plots with ordinary ggplot2 code. ## One variable `explore_distribution()` for a numeric variable, `explore_categorical()` for a categorical one. A unimodal density leaves its upper corners empty, so `legend_inside = TRUE` tucks the colour legend into the top-right rather than spending a right-hand margin on it. Several plots take this argument, and it is off by default because the empty corner depends on the data. ```{r} explore_distribution(lexical_decision, RT, group = condition, type = "density", legend_inside = TRUE) ``` An overlay gets crowded once there are several groups, so `facet = TRUE` gives each group its own panel: ```{r} explore_distribution(wellbeing_survey, life_satisfaction, group = region, type = "both", facet = TRUE) ``` `ecdf_plot()` is a bin-free alternative: the empirical cumulative distribution lets you read medians and quantiles straight off the curve and makes a shift between groups obvious (here the related condition is faster throughout). ```{r} ecdf_plot(lexical_decision, RT, group = condition, reference_quantiles = c(0.25, 0.5, 0.75), legend_inside = TRUE) ``` ```{r} explore_categorical(wellbeing_survey, education, group = region, proportion = TRUE, position = "dodge") ``` ## Two variables, any types `explore_bivariate()` selects the appropriate plot automatically: a scatter plot for two numeric variables, box plots for a numeric variable against a categorical one, and a filled bar chart for two categorical variables. ```{r} explore_bivariate(lexical_decision, condition, RT) ``` For a focused scatter with a fitted trend, use `scatter_trend()`. The crop-yield trial has a real fertiliser-by-treatment interaction, visible here as two diverging slopes. ```{r} scatter_trend(crop_yield, fertiliser, yield, group = treatment) ``` ## Many variables at once `explore_pairs()` is a scatter-plot matrix, and `correlation_heatmap()` condenses the same information into a single coloured grid. ```{r, fig.height = 6} explore_pairs(crop_yield, cols = c("rainfall", "fertiliser", "soil_ph", "yield")) ``` ```{r, fig.height = 5} correlation_heatmap(wellbeing_survey) ``` `reorder = TRUE` orders the variables by hierarchical clustering, so blocks of mutually correlated variables sit together and the structure is easier to read: ```{r, fig.height = 5} correlation_heatmap(wellbeing_survey, reorder = TRUE) ``` ## Distributions across groups `raincloud_plot()` shows the density, the box summary and the raw points together, and `group_comparison_plot()` adds the group means with confidence intervals over the raw data. By showing the estimate alongside its uncertainty, the latter conveys whether the groups differ more faithfully than a bar chart. ```{r} raincloud_plot(lexical_decision, RT, group = condition) ``` ```{r} group_comparison_plot(lexical_decision, RT, condition) ``` To compare the *shape* of a distribution across several groups at once, `ridgeline_plot()` stacks one partially overlapping density per group: ```{r, fig.height = 4} ridgeline_plot(wellbeing_survey, life_satisfaction, region) ``` ## Estimation plots: effect size rather than a p-value An *estimation plot* puts the effect size and its uncertainty at the centre of the comparison. `estimation_plot()` draws the classic Gardner-Altman two-group plot: the raw data with group means on top, and the mean difference with a bootstrap confidence interval beneath, aligned so a difference of zero sits under the reference group's mean. With two groups it also annotates a standardised effect size (Hedges' *g* by default). ```{r, fig.height = 5} set.seed(1) estimation_plot(lexical_decision, RT, condition, title = "RT difference: unrelated vs. related priming") ``` `group_comparison_plot(differences = TRUE)` is the same idea reached from the group-means plot: it appends the difference panel, turning a means comparison into a full estimation plot. With more than two groups every other group is compared against a chosen reference (a Cumming plot). ```{r, fig.height = 5} set.seed(1) group_comparison_plot(crop_yield, yield, treatment, differences = TRUE, title = "Yield difference: enhanced vs. standard") ``` ## Comparing two groups across categories `dumbbell_plot()` compares one value between two groups across a set of categories: the two group values per category are joined by a segment, so the size and direction of each gap can be read off directly. Here it contrasts younger and older respondents' life satisfaction by region. ```{r} wb <- wellbeing_survey wb$age_group <- ifelse(wb$age < median(wb$age), "younger", "older") dumbbell_plot(wb, region, life_satisfaction, age_group, legend_inside = TRUE) ``` ## Data quality: outliers and missingness `outlier_plot()` draws the distribution as a box or violin plot and highlights the points beyond the 1.5 * IQR fences, so unusual values are easy to see before any modelling begins. ```{r} outlier_plot(crop_yield, yield) ``` `wellbeing_survey` has *informative* missingness (income is missing more often at higher stress), so the missingness map is worth a look before modelling. ```{r, fig.height = 5} missingness_map(wellbeing_survey, legend_inside = TRUE) ``` ## A descriptive summary table `summary_table()` builds a 'Table 1': mean (SD) for numeric variables, counts and percentages for categorical ones, optionally by group. The first row always reports the sample size (`N`), and any variable with missing values gets a `Missing, n (%)` row, both visible here for the wellbeing survey. It returns a plain data frame, ready for `knitr::kable()`. ```{r} tab <- summary_table( wellbeing_survey, vars = c("life_satisfaction", "income", "stress", "education"), group = "region" ) knitr::kable(tab) ``` ## Customising and extending the plots Every depictr function returns a plain `ggplot2` object (composite figures return a `patchwork`), so you can keep adding layers, scales, labels and theme tweaks with the usual `+`: ```{r} library(ggplot2) scatter_trend(crop_yield, fertiliser, yield, group = treatment) + labs(title = "Yield rises with fertiliser", subtitle = "More steeply under the enhanced treatment") + theme(legend.position = "bottom") ``` ### Tidying the legend depictr centres a legend title over its keys by default. When the levels speak for themselves, though, the title is just clutter, so drop it by mapping it to `NULL`. Reversing a discrete colour legend at the same time makes it read top-to-bottom in the order the curves are stacked: ```{r} ecdf_plot(lexical_decision, RT, group = condition) + labs(colour = NULL) + guides(colour = guide_legend(reverse = TRUE)) ``` ### Moving the legend into the plot Several plots take `legend_inside = TRUE` to tuck the legend into a corner they usually leave empty (see e.g. `?ecdf_plot`). For any plot that does not, the same move is one `theme()` call. A dodged bar chart, for instance, leaves the top-right clear when the right-most category is short, so the legend fits there. Because the regions are self-evident, we drop the title too (with `element_blank()`, since this plot sets the legend name on its fill scale): ```{r} explore_categorical(wellbeing_survey, education, group = region, proportion = TRUE, position = "dodge") + theme(legend.position = "inside", legend.position.inside = c(0.98, 0.98), legend.justification = c(1, 1), legend.title = element_blank()) ``` ### Built-in layout controls Many functions also expose layout controls directly. `explore_distribution(facet = TRUE)` and `ridgeline_plot()` separate groups, `correlation_heatmap(reorder = TRUE)` clusters variables, and the model-estimate plots take `facet` and `standardise` to keep coefficients on very different scales legible (see `vignette("model-estimates")`).