
ggcircular is a ggplot2 extension for
circular, axial and directional data. It provides layers, scales,
coordinate helpers, summaries and diagnostics for angles measured on a
periodic scale.
The package is designed for exploratory graphics, teaching examples and reproducible statistical workflows involving directions, bearings, orientations, times of day, turn angles and other circular measurements.
ggcircular is not on CRAN yet. Install it from GitHub
while the API is being stabilized for a first CRAN submission.
Install the development release from GitHub:
install.packages("remotes")
remotes::install_github("AurelienNicosiaULaval/ggcircular")Or clone with SSH and install locally:
git clone git@github.com:AurelienNicosiaULaval/ggcircular.git
cd ggcircular
R -q -e 'devtools::install(upgrade = "never")'library(ggplot2)
library(dplyr)
library(ggcircular)wind_directions |>
filter(season == "winter") |>
ggplot(aes(x = direction)) +
geom_rose(aes(y = after_stat(density), fill = after_stat(density)), bins = 24, alpha = 0.78) +
geom_circular_density(linewidth = 1.1, colour = "#123C4A") +
geom_mean_direction(length = "resultant", colour = "#E4572E", linewidth = 1.1) +
scale_x_circular_compass() +
coord_circular(zero = "north", direction = "clockwise") +
labs(fill = "density", title = "Winter wind directions") +
theme_circular()
| Workflow | Main helpers |
|---|---|
| Rose diagrams and circular histograms | geom_rose(), stat_rose() |
| Circular density estimation | geom_circular_density(),
stat_circular_density() |
| Mean direction and concentration | geom_mean_direction(), circular_summary(),
estimate_kappa() |
| Circular confidence intervals and tests | circular_mean_ci(), rayleigh_test(),
watson_williams_test(),
stat_circular_test() |
| Axial orientations modulo pi | axial = TRUE in summaries and layers |
| Theoretical circular distributions | stat_vonmises(), stat_wrapped_normal(),
stat_uniform_circular() |
| Mixtures of von Mises components | fit_vonmises_mixture(),
stat_vonmises_mixture() |
| Movement and state-angle graphics | mutate_directional_features(),
geom_direction_arrow(),
plot_state_angles() |
| Angular model diagnostics | circular_residuals(),
circular_model_diagnostics(), autoplot()
methods |
| Spherical and posterior helpers | spherical_summary(), as_circular_draws(),
summarise_circular_draws() |
2 * pi.pi through
axial = TRUE.ggplot objects, tibbles or
familiar test objects.The default mathematical convention is zero = "east"
with angles increasing counterclockwise. This matches the usual unit
circle.
Compass bearings use zero = "north" with angles
increasing clockwise. Use scale_x_circular_compass()
together with
coord_circular(zero = "north", direction = "clockwise") for
bearing-like data such as wind direction or movement headings.
Axial data, such as unoriented lines, are different again:
0 and pi represent the same orientation. Use
axial = TRUE in summaries and layers for these data.
circular_summary() respects existing dplyr
groups and returns mean direction, resultant length, circular variance,
circular standard deviation and an estimated von Mises concentration
parameter. estimate_kappa() is a descriptive piecewise
approximation from the sample resultant length, not a full inferential
fit.
wind_directions |>
circular_summary(direction, season) |>
mutate(
mean_degrees = round(rad_to_deg(mean), 1),
Rbar = round(Rbar, 3),
kappa = round(kappa, 2)
) |>
select(season, n, mean_degrees, Rbar, kappa)
#> # A tibble: 4 × 5
#> season n mean_degrees Rbar kappa
#> <chr> <int> <dbl> <dbl> <dbl>
#> 1 fall 131 310. 0.811 3
#> 2 spring 115 135. 0.802 2.89
#> 3 summer 138 223. 0.87 4.15
#> 4 winter 116 48.2 0.904 5.52Axial observations identify opposite directions. For example, an
orientation of 0 radians is equivalent to an orientation of pi radians.
Use axial = TRUE to compute and display these data modulo
pi.
ggplot(axial_orientations, aes(x = orientation, fill = group)) +
geom_rose(bins = 18, axial = TRUE, alpha = 0.72) +
geom_mean_direction(axial = TRUE, colour = "#123C4A", linewidth = 1) +
scale_x_circular_degrees(limits = c(0, pi)) +
coord_circular() +
facet_wrap(~ group) +
theme_circular()
ggcircular includes helpers for bearings, turn angles
and state-specific angular distributions.
animal_steps |>
filter(!is.na(turn_angle)) |>
ggplot(aes(x = turn_angle, fill = state)) +
geom_rose(bins = 24, alpha = 0.72) +
geom_mean_direction(colour = "#123C4A", linewidth = 1) +
scale_x_circular_degrees(
breaks = deg_to_rad(c(0, 90, 180, 270)),
labels = c("0", "90", "180", "270")
) +
coord_circular() +
facet_wrap(~ state) +
theme_circular()
Finite mixtures are fitted with an expectation-maximization routine
and can be drawn directly on top of empirical rose diagrams. These fits
are descriptive and depend on initialization, so use seed,
nstart and diagnostic output when the mixture is
substantively important.
set.seed(2026)
fit_mix <- fit_vonmises_mixture(
wind_directions$direction,
k = 2,
init = "spaced",
nstart = 3,
seed = 2026
)
ggplot(wind_directions, aes(x = direction)) +
geom_rose(aes(y = after_stat(density)), bins = 24, alpha = 0.42) +
stat_vonmises_mixture(fit = fit_mix, linewidth = 1.2, colour = "#123C4A") +
scale_x_circular_degrees() +
coord_circular() +
theme_circular()
tidy_circular(fit_mix) |>
mutate(
mu_degrees = round(rad_to_deg(mu), 1),
kappa = round(kappa, 2),
proportion = round(proportion, 3)
) |>
select(component, proportion, mu_degrees, kappa)
#> # A tibble: 2 × 4
#> component proportion mu_degrees kappa
#> <int> <dbl> <dbl> <dbl>
#> 1 1 0.328 51.6 1.45
#> 2 2 0.672 232. 0.77circular_mean_ci(
wind_directions$direction,
method = "bootstrap",
R = 399,
seed = 2026
) |>
mutate(across(c(mean, lower, upper), rad_to_deg))
#> # A tibble: 1 × 7
#> mean lower upper level method n Rbar
#> <dbl> <dbl> <dbl> <dbl> <chr> <int> <dbl>
#> 1 235. 139. 354. 0.95 bootstrap 500 0.0494rayleigh <- rayleigh_test(wind_directions$direction)
tibble::tibble(
statistic = unname(rayleigh$statistic),
n = unname(rayleigh$parameter),
p_value = rayleigh$p.value,
method = rayleigh$method
)
#> # A tibble: 1 × 4
#> statistic n p_value method
#> <dbl> <int> <dbl> <chr>
#> 1 1.22 500 0.295 Rayleigh test of circular uniformityThe package keeps heavier modeling ecosystems in
Suggests. When available, these integrations add
diagnostics without making them hard dependencies.
fit <- CircularRegression::consensus(direction ~ speed, data = wind_directions)
circular_model_diagnostics(fit)
autoplot(fit, type = "residuals_density")
autoplot(fit, type = "fitted_observed")Optional helpers currently target:
CircularRegression-style angular, consensus and
two-step objects through S3 class support.momentuHMM state probabilities and Viterbi states.posterior draw objects through
posterior::as_draws_df().circular tests when classical circular test
implementations are available.The following pieces are intentionally available but still experimental:
momentuHMM state-angle adapters;Experimental functions are documented and tested, but their return columns may still be refined before a CRAN release if validation reveals a better public contract.
ggcircular is primarily a visualization and diagnostics
package. It does not replace specialist inference workflows for circular
statistics.
circular_mean_ci() is unreliable when the mean
resultant length is close to zero because the mean direction is weakly
identified.rayleigh_test() is mainly sensitive to unimodal
departures from uniformity.watson_williams_test() relies on strong assumptions
about group concentration and uses the optional circular
implementation.The package is being prepared for a first CRAN submission. The release checklist currently includes:
R CMD check --as-cran on the final source tarball;--run-donttest checks;_R_CHECK_FORCE_SUGGESTS_=false;Longer articles are built for pkgdown and excluded from the CRAN tarball.
Start with:
vignette("ggcircular", package = "ggcircular")Then see the pkgdown articles:
Contributions are welcome through focused GitHub issues and pull
requests. See CONTRIBUTING.md,
SUPPORT.md
and CODE_OF_CONDUCT.md
for contribution, support and conduct guidelines.
ggcircular is currently experimental. The public API is
usable, tested and documented, but may still evolve as more angular
model classes and validation cases are added.
Current checks:
devtools::test() passes.devtools::check(document = FALSE, args = "--as-cran", build_args = "--no-manual")
is used before release commits._R_CHECK_FORCE_SUGGESTS_=false and full-suggests checks
when optional packages are available.pkgdown builds and publishes the website from
main.