--- title: "Getting started with ggcircular" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Getting started with ggcircular} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>", fig.width = 7, fig.height = 5) ``` # Overview `ggcircular` extends `ggplot2` for circular, axial and directional data. This vignette gives a complete first tour of the package: angle conventions, rose diagrams, circular densities, mean directions, uncertainty, axial data, movement data, mixtures of von Mises distributions and model diagnostics. # Not on CRAN yet `ggcircular` is not on CRAN yet. The package is being stabilized for a first CRAN submission; install the development version from GitHub for now. ```{r} library(ggplot2) library(dplyr) library(ggcircular) ``` # Why circular data are different Circular observations live on a periodic scale. In radians, `0` and `2 * pi` represent the same direction. This means that linear tools can fail near the boundary. ```{r} boundary_angles <- tibble( theta = c(0.05, 0.10, 2 * pi - 0.10, 2 * pi - 0.05) ) boundary_angles |> summarise( arithmetic_mean = mean(theta), circular_mean = mean_direction(theta), Rbar = mean_resultant_length(theta) ) ``` The arithmetic mean is near `pi`, even though the observations are concentrated near zero. The circular mean uses sine and cosine components, so it respects the periodic scale. # Data included in the package The package ships with four simulated datasets. They are small enough for examples and large enough to show realistic grouped workflows. ```{r} glimpse(wind_directions) glimpse(animal_steps) glimpse(hourly_activity) glimpse(axial_orientations) ``` ```{r} wind_directions |> count(season, station) ``` # Directional versus axial data Directional data have an arrow. For example, a bearing of north and a bearing of south are different directions. Axial data have an orientation but no arrow, so an angle and the angle plus `pi` are equivalent. ```{r} directional <- c(0, pi) axial <- c(0, pi) tibble( case = c("directional", "axial"), Rbar = c( mean_resultant_length(directional), mean_resultant_length(axial, axial = TRUE) ), mean = c( mean_direction(directional), mean_direction(axial, axial = TRUE) ) ) ``` For axial calculations, `ggcircular` doubles the angles internally, computes the directional statistic, then transforms the answer back to the original scale. # Conventions for directions and bearings The internal default unit is radians. Helpers are provided for degrees, hours and compass labels. ```{r} tibble( degrees = c(0, 90, 180, 270), radians = deg_to_rad(degrees), hours = rad_to_hour(radians), compass = rad_to_compass(radians) ) ``` Compass labels use the bearing convention: zero points north and angles increase clockwise. Use this with `coord_circular(zero = "north", direction = "clockwise")`. For mathematical plots, the default coordinate convention is zero at east and positive angles rotating counterclockwise. For axial data, set `axial = TRUE` because `theta` and `theta + pi` represent the same orientation. # First rose diagram A rose diagram is a circular histogram. The first and last bins are adjacent on the circle. ```{r} ggplot(wind_directions, aes(x = direction)) + geom_rose(bins = 16) + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` # Counts, densities and proportions `geom_rose()` exposes computed variables such as `count`, `density` and `proportion`. These are available with `after_stat()`. ```{r} ggplot(wind_directions, aes(x = direction)) + geom_rose( aes(fill = after_stat(proportion)), bins = 16, normalize = "proportion" ) + scale_x_circular_degrees() + coord_circular() + theme_rose() ``` # Groups and facets Because the layers follow the `ggplot2` grammar, standard grouping, colouring and faceting workflows work naturally. ```{r} ggplot(wind_directions, aes(x = direction, fill = season)) + geom_rose(bins = 16, alpha = 0.75) + facet_wrap(~ season) + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` # Circular density `geom_circular_density()` estimates a smooth density on the circle using a von Mises kernel. The estimate wraps around the origin. ```{r} ggplot(wind_directions, aes(x = direction)) + geom_rose(aes(y = after_stat(density)), bins = 24, alpha = 0.35) + geom_circular_density(linewidth = 1) + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` The bandwidth can be adjusted. Smaller values show more local variation. ```{r} ggplot(wind_directions, aes(x = direction)) + geom_circular_density(bw = 0.25, linewidth = 1) + geom_circular_density(bw = 0.75, linetype = 2) + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` # Mean direction and resultant length The mean resultant length `Rbar` measures concentration. Values close to one indicate strong concentration; values close to zero indicate weak or cancelling directionality. ```{r} wind_directions |> group_by(season) |> circular_summary(direction) ``` ```{r} ggplot(wind_directions, aes(x = direction, colour = season)) + geom_circular_density(linewidth = 1) + geom_mean_direction(length = "resultant") + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` # Uncertainty and circular tests `circular_mean_ci()` computes large-sample or bootstrap intervals for the mean direction. `rayleigh_test()` provides a basic test against circular uniformity. ```{r} circular_mean_ci(wind_directions$direction, method = "large_sample") rayleigh_test(wind_directions$direction) ``` ```{r} ggplot(wind_directions, aes(x = direction)) + geom_rose(bins = 16, alpha = 0.8) + stat_circular_test(test = "rayleigh", y = 1.1, size = 3) + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` # Compass display For bearing-like data, compass labels and geographic orientation are usually easier to read. ```{r} ggplot(wind_directions, aes(x = direction)) + geom_rose(bins = 16, aes(fill = after_stat(count))) + geom_mean_direction() + scale_x_circular_compass() + coord_circular(zero = "north", direction = "clockwise") + theme_compass() ``` # Axial data Set `axial = TRUE` when orientations are modulo `pi`. ```{r} axial_orientations |> group_by(group) |> circular_summary(orientation, axial = TRUE) ``` ```{r} ggplot(axial_orientations, aes(x = orientation, fill = group)) + geom_rose(bins = 18, axial = TRUE, alpha = 0.7) + geom_mean_direction(axial = TRUE) + scale_x_circular_degrees(limits = c(0, pi)) + coord_circular() + theme_circular() ``` # Movement data Movement tracks naturally produce step lengths, bearings and turn angles. ```{r} animal_steps |> group_by(state) |> summarise( mean_step = mean(step_length, na.rm = TRUE), median_step = median(step_length, na.rm = TRUE), .groups = "drop" ) ``` ```{r} ggplot(animal_steps, aes(x = x, y = y, group = id, colour = id)) + geom_path(alpha = 0.7) + coord_equal() + theme_minimal() ``` ```{r} plot_state_angles(animal_steps, angle = turn_angle, state = state, type = "rose") ``` ```{r} plot_state_angles(animal_steps, angle = turn_angle, state = state, type = "density") ``` # Mixtures of von Mises distributions Mixtures provide a descriptive way to represent multimodal circular distributions. The EM fit can depend on initialization, so use `seed`, `nstart` and `glance_circular()` when reproducibility or convergence matters. ```{r} fit_mix <- fit_vonmises_mixture( wind_directions$direction, k = 2, nstart = 3, seed = 2026, max_iter = 200 ) tidy_circular(fit_mix) glance_circular(fit_mix) ``` ```{r} ggplot(wind_directions, aes(x = direction)) + geom_rose(aes(y = after_stat(density)), bins = 24, alpha = 0.35) + stat_vonmises_mixture(fit = fit_mix, linewidth = 1) + scale_x_circular_degrees() + coord_circular() + theme_circular() ``` # Angular model diagnostics `ggcircular` provides basic methods for angular model summaries and residual diagnostics. The example below uses a small mock object with the same observed and fitted angle fields expected from supported angular model classes. ```{r} fit <- structure( list( y = wind_directions$direction[1:50], mui = normalize_angle(wind_directions$direction[1:50] + rnorm(50, 0, 0.15)), term_labels = c("intercept", "speed") ), class = "angular" ) tidy_circular(fit) glance_circular(fit) circular_model_diagnostics(fit) ``` ```{r} autoplot(fit, type = "residuals_density") ``` # Circular posterior draws When the optional `posterior` package is installed, circular posterior draws can be converted to a long format and summarized with circular statistics. ```{r} if (requireNamespace("posterior", quietly = TRUE)) { set.seed(1) draws <- posterior::draws_df( theta = rnorm(400, mean = pi / 3, sd = 0.25), phi = rnorm(400, mean = pi, sd = 0.35) ) circular_draws <- as_circular_draws(draws, variables = c("theta", "phi")) summarise_circular_draws(circular_draws) } ``` ```{r} if (requireNamespace("posterior", quietly = TRUE)) { autoplot_circular_draws(circular_draws) } ``` # Experimental features The optional model integrations are experimental. They are intended to make diagnostics easier for workflows built with `CircularRegression`, `momentuHMM` and `posterior`, while keeping those packages in `Suggests`. The functions give explicit errors when an optional package is required but not installed. # Statistical limitations Circular graphics are descriptive and should be read with the data-generating context in mind. 1. A rose diagram can change visually with the number of bins. 2. A circular mean is unstable when `Rbar` is close to zero. 3. Directional and axial data require different summaries. 4. Compass bearings and mathematical angles use different zero directions. 5. Multimodal data should not be summarized only by one mean direction. 6. The automatic circular density bandwidth is a heuristic. 7. `estimate_kappa()` is a descriptive approximation, not a full uncertainty analysis. 8. Rayleigh and Watson-Williams tests have classical assumptions that should be checked before confirmatory use. # Next steps The focused vignettes go deeper into rose diagrams, circular density, mean direction, axial data, movement data, theoretical distributions, angular model diagnostics, and spherical or posterior workflows.