| Title: | Visualizes Preference Data via Ternary Plots in Two and Higher Dimensions |
| Version: | 0.1.1 |
| Description: | Visualises preference and ranking data by extending traditional ternary plots to support high-dimensional simplexes. The package provides methods to transform compositional data into coordinates suitable for 2D and high-dimensional ternary plots (see Cook & Laa (2024) https://dicook.github.io/mulgar_book/). Compatibility with interactive visualization packages such as 'plotly' or 'detourr' allows users to explore high-dimensional preference structures dynamically. |
| License: | GPL (≥ 3) |
| Encoding: | UTF-8 |
| RoxygenNote: | 7.3.3 |
| Imports: | dplyr, prefio, tibble, tidyr, rlang, ggplot2, tidyselect, geozoo |
| Suggests: | knitr, rmarkdown, tourr, kableExtra, ggthemes, testthat (≥ 3.0.0) |
| Config/testthat/edition: | 3 |
| Depends: | R (≥ 4.1.0) |
| LazyData: | true |
| VignetteBuilder: | knitr |
| URL: | https://numbats.github.io/prefviz/ |
| BugReports: | https://github.com/numbats/prefviz/issues |
| NeedsCompilation: | no |
| Packaged: | 2026-04-07 12:57:15 UTC; ADMIN |
| Author: | Linh Ngo [aut, cre],
Dianne Cook |
| Maintainer: | Linh Ngo <linhngo66.work@gmail.com> |
| Repository: | CRAN |
| Date/Publication: | 2026-04-13 11:50:02 UTC |
prefviz: Visualizes Preference Data via Ternary Plots in Two and Higher dimensions
Description
Visualises preference and ranking data by extending traditional ternary plots to support high-dimensional simplexes. The package provides methods to transform compositional data into coordinates suitable for 2D and high-dimensional ternary plots (see Cook & Laa (2024) https://dicook.github.io/mulgar_book/). Compatibility with interactive visualization packages such as 'plotly' or 'detourr' allows users to explore high-dimensional preference structures dynamically.
Author(s)
Maintainer: Linh Ngo Linh.Ngo2@monash.edu
Authors:
Dianne Cook DiCook@monash.edu (ORCID)
Damjan Vukcevic damjan.vukcevic@monash.edu (ORCID)
See Also
Useful links:
Add data edges
Description
Internal helper to create paths/edges between observations in a ternary plot.
Used by new_ternable() to create the data_edges component.
Usage
add_data_edges(data, group_col_chr)
Arguments
data |
A data frame input from |
group_col_chr |
Character vector of group column names |
Draw the 2D ternary simplex
Description
Draws the boundary of a 2D ternary simplex as an equilateral triangle
Usage
add_ternary_base(...)
Arguments
... |
Arguments passed to |
Value
A ggplot object
Examples
library(ggplot2)
# Basic simplex
ggplot() + add_ternary_base()
# Customize appearance
ggplot() + add_ternary_base(colour = "blue", linewidth = 1.5)
Add vertex labels to ternary plot
Description
Adds text labels at the vertices of a ternary simplex with automatic positioning adjustments.
Usage
add_vertex_labels(
vertex_labels_df,
nudge_x = c(-0.02, 0.02, 0),
nudge_y = c(-0.05, -0.05, 0.05),
...
)
Arguments
vertex_labels_df |
A data frame containing vertex coordinates and labels.
Should have columns |
nudge_x |
Numeric vector of length 3 specifying horizontal nudges for each vertex label. |
nudge_y |
Numeric vector of length 3 specifying vertical nudges for each vertex label. |
... |
Arguments passed to |
Value
A ggplot object
Examples
library(ggplot2)
# Create a ternable object
tern <- as_ternable(prefviz::aecdop22_transformed, ALP:Other)
ggplot() +
add_ternary_base() +
add_vertex_labels(tern$simplex_vertices, size = 5, fontface = "bold")
Distribution of preferences by candidate by division in the Australian Federal Election (2022 and 2025)
Description
Provides details on how votes are distributed and transferred among candidates in all count stages of the preferential voting system. All electoral divisions in the Australian Federal Election are included.
Usage
aecdop_2022
aecdop_2025
Format
A tibble of 14 columns:
- StateAb
State or territory abbreviation (e.g., "ACT", "NSW", "VIC")
- DivisionID
Numeric identifier for the electoral division
- DivisionNm
Name of the electoral division (e.g., "Bean", "Canberra")
- CountNumber
Round in the counting procedure, starting from 0 (first preference)
- BallotPosition
Position of the candidate on the ballot paper
- CandidateID
Unique numeric identifier for the candidate
- Surname
Candidate's surname
- GivenNm
Candidate's given name(s)
- PartyAb
Party abbreviation (e.g., "UAPP", "ALP", "LP")
- PartyNm
Full party name (e.g., "United Australia Party", "Australian Labor Party")
- Elected
Whether the candidate was elected: "Y" (yes) or "N" (no)
- HistoricElected
Whether the candidate was elected in the previous election: "Y" (yes) or "N" (no)
- CalculationType
Type of calculation:
- Preference Count
Number of votes received
- Preference Percent
Percentage of votes received
- Transfer Count
Number of votes transferred from other candidates
- Transfer Percent
Percentage of votes transferred from other candidates
- CalculationValue
Numeric value for the calculation type (votes or percentage)
An object of class spec_tbl_df (inherits from tbl_df, tbl, data.frame) with 35096 rows and 14 columns.
An object of class spec_tbl_df (inherits from tbl_df, tbl, data.frame) with 30888 rows and 14 columns.
Details
Two datasets are provided:
-
aecdop_2022: 2022 Federal Election (35,096 rows) -
aecdop_2025: 2025 Federal Election (30,888 rows)
Source
Australian Electoral Commission (AEC) Distribution of Preferences 2022 Distribution of Preferences 2025
Examples
# Load the datasets
data(aecdop_2022)
data(aecdop_2025)
# First preferences for Bean division in 2022
aecdop_2022 |>
dplyr::filter(DivisionNm == "Bean",
CountNumber == 0,
CalculationType == "Preference Count")
Distribution of preferences in wide form for selected parties (2022 and 2025)
Description
Wide-form versions of the Australian Federal Election distribution-of-
preferences data, aggregated to selected parties within each electoral
division and couting round. Each row gives the vote share for a set of
parties and an "Other" category at a given count stage in a given
division. These datasets are derived from aecdop_2022 and
aecdop_2025 for ease of analysis and visualisation.
Usage
aecdop22_transformed
aecdop25_transformed
Format
For aecdop22_transformed, a tibble with 1,052 rows and 6 columns:
- DivisionNm
Name of the electoral division (e.g., "Adelaide").
- CountNumber
Round in the counting procedure, starting from 0 (first preference).
- ElectedParty
Party abbreviation of the candidate ultimately elected in the division (e.g., "ALP", "LNP").
- ALP
Proportion of votes for the Australian Labor Party at this count, between 0 and 1.
- LNP
Proportion of votes for the Coalition grouping at this count, between 0 and 1.
- Other
Proportion of votes for all other parties and candidates combined at this count, between 0 and 1.
For aecdop25_transformed, a tibble with 976 rows and 8 columns:
- DivisionNm
Name of the electoral division (e.g., "Adelaide").
- CountNumber
Round in the counting procedure, starting from 0 (first preference).
- ElectedParty
Party abbreviation of the candidate ultimately elected in the division (e.g., "ALP", "GRN", "LNP", "IND").
- ALP
Proportion of votes for the Australian Labor Party at this count, between 0 and 1.
- GRN
Proportion of votes for the Australian Greens at this count, between 0 and 1.
- LNP
Proportion of votes for the Coalition grouping at this count, between 0 and 1.
- Other
Proportion of votes for all other parties and candidates combined at this count, between 0 and 1.
- IND
Proportion of votes for independent candidates at this count, between 0 and 1.
An object of class tbl_df (inherits from tbl, data.frame) with 1052 rows and 6 columns.
An object of class tbl_df (inherits from tbl, data.frame) with 976 rows and 8 columns.
Details
Two datasets are provided:
-
aecdop22_transformed: 2022 Federal Election (1,052 rows), aggregated to Labor (ALP), Coalition (LNP), and Other (Other) -
aecdop25_transformed: 2025 Federal Election (976 rows), aggregated to Labor (ALP), Coalition (LNP), Greens (GRN), Independent (IND) and Other (Other)
Within each row, the party columns represent proportions that sum to 1 (up to rounding), giving a compositional view of the distribution of preferences at each count stage. These structures are designed for use in simplex-based visualisations and related methods.
See Also
Examples
data(aecdop22_transformed)
data(aecdop25_transformed)
# Proportions for Adelaide over the count in 2022
aecdop22_transformed |>
dplyr::filter(DivisionNm == "Adelaide")
Create a ternable object
Description
Creates a ternable object, which contains observation coordinates, simplex vertices, and edges necessary for building a ternary plot in both two and higher dimensions.
Usage
as_ternable(
data,
items = dplyr::everything(),
group = NULL,
order_by = NULL,
decreasing = FALSE,
na_method = c("drop_na", "drop_group"),
...
)
Arguments
data |
A data frame containing the item (alternative) columns used to construct the ternary plot. |
items |
< |
group |
Optional column name indicating the grouping variable. If specified, the data will be grouped by this variable. This is useful for creating paths between observations within each group. |
order_by |
Optional column name indicating the order variable. If specified, the data will be ordered by this variable. This is useful for creating paths between observations within each group. |
decreasing |
Logical. If |
na_method |
Character string specifying how to handle missing values in
|
... |
Additional arguments (currently unused, reserved for future extensions). |
Value
A ternable object (S3 class) containing:
data |
: The validated and normalized data frame |
data_coord |
: Transformed coordinates for all observations |
data_edges |
: Edge connections for drawing paths between observations |
simplex_vertices |
: Vertex coordinates and labels for the simplex |
simplex_edges |
: Edge connections for drawing the simplex boundary |
vertex_labels |
: Labels of the vertices, same as names of the selected item columns |
Examples
# Load and transform the dataset
prefviz::aecdop25_transformed
# Create the ternable object
tern <- as_ternable(prefviz::aecdop25_transformed, items = ALP:IND)
tern
Get full distribution of preferences in each instant runoff voting round as percentage
Description
Compute the preference in each round of instant runoff voting from input data, transforming the results into a tidy format for visualization. Each row represents one round, with columns for each candidate's preference percentage and the election winner.
Usage
dop_irv(x, value_type = c("percentage", "count"), ...)
Arguments
x |
Input data. Accepts the same formats as
|
value_type |
Character string specifying the output format. Either:
|
... |
Additional arguments passed to
|
Value
A tibble with the following structure:
-
round: Integer, the round number (1 to n) One column per candidate: Numeric, the percentage of votes (0-1) that candidate received in that round. NA values are replaced with 0 for eliminated candidates.
-
winner: Character, the name of the eventual IRV winner (same for all rows)
Examples
# Example 1: From preference vector
votes <- prefio::preferences(c("A > B > C", "B > A > C", "C > B > A", "A > B > C"))
dop_irv(votes, value_type = "count")
# Example 2: From data frame with custom column names
vote_data <- tibble::tibble(
prefs = prefio::preferences(c("A > B > C", "B > C > A", "C > A > B")),
counts = c(100, 75, 25)
)
dop_irv(vote_data, value_type = "percentage",
preferences_col = prefs,
frequency_col = counts)
Transform AEC distribution of preferences from long to wide format
Description
Transform AEC distribution of preferences from long to wide format, with optional scaling and normalization. This function is useful for converting all distribution of preference data with similar format into format ready for ternary plots.
Usage
dop_transform(
data,
key_cols,
value_col,
item_col,
normalize = TRUE,
scale = 1,
fill_value = 0,
winner_col = NULL,
winner_identifier = "Y"
)
Arguments
data |
A data frame containing preference or vote distribution data, with format similar to AEC Distribution of Preferences 2022 |
key_cols |
Columns that identify unique observations, e.g., DivisionNm, CountNumber |
value_col |
Numeric and non-negative. Column containing the numeric values to aggregate, e.g., CalculationValue, Votes. |
item_col |
Column name containing the items (candidates/parties) of the election, e.g., Party, Candidate. This column will become column names in the output wide format. |
normalize |
Logical. If |
scale |
Numeric. If |
fill_value |
Numeric. Value to use for missing combinations after pivoting. Default is 0. |
winner_col |
Optional character string specifying a column that indicates
the winner/elected party. If provided, this column will be joined back to
the output based on key columns. Useful for preserving election outcome
information. Default is |
winner_identifier |
Optional character string specifying the value in
|
Value
A data frame in wide format with:
Key columns identifying each observation
Columns for each item (candidate/party) containing aggregated/normalized values
Winner column (if
winner_colwas specified)
Examples
library(dplyr)
# Convert AEC 2025 Distribution of Preference data to wide format
data(aecdop_2025)
# We are interested in the preferences of Labor, Coalition, Greens and Independent.
# The rest of the parties are aggregated as Other.
aecdop_2025 <- aecdop_2025 |>
filter(CalculationType == "Preference Percent") |>
mutate(Party = case_when(
!(PartyAb %in% c("LP", "ALP", "NP", "LNP", "LNQ")) ~ "Other",
PartyAb %in% c("LP", "NP", "LNP", "LNQ") ~ "LNP",
TRUE ~ PartyAb))
dop_transform(
data = aecdop_2025,
key_cols = c(DivisionNm, CountNumber),
value_col = CalculationValue,
item_col = Party,
winner_col = Elected
)
Centroids of electoral divisions in the 2025 Australian Federal Election
Description
Provides the centroids of all electorates in the 2025 Australian Federal Election. The dataset is computed from 2025 Electoral Boundaries data.
Usage
elb_centroid
Format
A tibble of 5 columns:
- id
Unique identifier for electorate
- elect_div
Electoral division name
- area_sqkm
Area of the electorate in square kilometres
- long
Longitude of the electoratecentroid
- lat
Latitude of the electorate centroid
Source
Australian Electoral Commission (AEC) https://www.aec.gov.au/electorates/maps.htm
Examples
library(ggplot2)
library(ggthemes)
# Load the dataset
data(elb_centroid)
# Plot the centroids on top of the electoral boundaries
ggplot(elb_map) +
geom_polygon(
aes(x = long, y = lat, group = group),
fill = "grey90", color = "white") +
geom_point(
data = elb_centroid,
aes(x = long, y = lat),
size = 1, alpha = 0.8
) +
theme_map()
Electoral boundaries map for the 2025 Australian Federal Election
Description
Provides the points that make up the boundaries of each electoral division in the 2025 Australian Federal Election.
Usage
elb_map
Format
A tibble of 8 columns:
- long
Longitude of point in polygon
- lat
Latitude of point in polygon
- hole
Whether the polygon has a hole
- piece
Polygon piece number
- group
Polygon group number
- order
Order of polygon within group
- id
Unique identifier for polygon
- elect_div
Electoral division name
Source
Australian Electoral Commission (AEC) https://www.aec.gov.au/electorates/maps.htm
Examples
library(ggplot2)
library(ggthemes)
# Load the dataset
data(elb_map)
# Plot the map
ggplot(elb_map) +
geom_polygon(
aes(x = long, y = lat, group = group),
fill = "grey90", color = "white") +
theme_map()
Create polygonal regions in a ternary plot based on a reference point
Description
geom_ternary_region() and stat_ternary_region() divide the ternary triangle
into three polygonal regions centered around a specific reference point.
Geometrically, lines are drawn from the reference point perpendicular to the three edges of the triangle. These lines partition the simplex into three zones, where each zone is associated with the closest vertex (item). This is often used to visualize "winning regions" or catchment areas for each item.
Usage
geom_ternary_region(
mapping = NULL,
position = "identity",
show.legend = NA,
inherit.aes = FALSE,
x1 = 1/3,
x2 = 1/3,
x3 = 1/3,
vertex_labels = NULL,
...
)
stat_ternary_region(
mapping = NULL,
data = NULL,
geom = "polygon",
position = "identity",
show.legend = NA,
inherit.aes = FALSE,
x1 = 1/3,
x2 = 1/3,
x3 = 1/3,
vertex_labels = NULL,
...
)
StatTernaryRegion
Arguments
mapping |
Set of aesthetic mappings created by |
position |
A position adjustment to use on the data for this layer. This
can be used in various ways, including to prevent overplotting and
improving the display. The
|
show.legend |
Logical. Should this layer be included in the legends?
|
inherit.aes |
If |
x1, x2, x3 |
Numeric values defining the reference point in ternary coordinates
(proportions). Must sum to 1 (or will be normalized). Default is |
vertex_labels |
Character vector of length 3 providing names for the regions.
The order must correspond to the three vertices of the ternary plot.
If |
... |
Other arguments passed on to
|
data |
The data to be displayed in this layer. There are three options: If A A |
geom |
The geometric object to use to display the data. Default is |
Format
An object of class StatTernaryRegion (inherits from Stat, ggproto, gg) of length 3.
Value
A ggplot object
Computed variables
stat_ternary_region() calculates the following variables which can be accessed
with after_stat():
x,yCartesian coordinates defining the polygon shapes.
idNumeric identifier for the specific geometric points used to build the polygons:
1-3: The main vertices of the ternary triangle.
4: The reference point (center).
5-7: The projection points on the edges.
groupInteger (1, 2, or 3) identifying which region the polygon belongs to.
vertex_labelsThe label assigned to the region (derived from the
vertex_labelsparameter).
Examples
library(ggplot2)
# Get ternable
tern22 <- as_ternable(prefviz::aecdop22_transformed, ALP:Other)
# Draw the ternary plot
ggplot(get_tern_data(tern22, plot_type = "2D"), aes(x = x1, y = x2)) +
add_ternary_base() +
geom_ternary_region(
vertex_labels = tern22$vertex_labels,
aes(fill = after_stat(vertex_labels)),
alpha = 0.3, color = "grey50",
show.legend = FALSE
)
Transform compositional data using Helmert matrix
Description
Transform n-dimension compositional data (all values sum to 1) into an (n-1)-dimensional Euclidean space using the Helmert matrix. This dimension reduction is the geometric basis for plotting points within the simplex.
Usage
helmert_transform(data, items = dplyr::everything(), append = FALSE)
Arguments
data |
A data frame or matrix containing the compositional data. |
items |
< |
append |
(Optional) A logical value indicating whether the transformed data should be appended to the original data frame.
Default is |
Value
A data frame containing the Helmert-transformed coordinates, named
x1, x2, ..., x(n-1), where n is the number of items. If append = TRUE,
these columns are added to the input data.
Examples
# Example 1: Transform a matrix (all columns)
comp_mat <- matrix(c(0.5, 0.3, 0.2,
0.4, 0.4, 0.2,
0.6, 0.2, 0.2),
ncol = 3, byrow = TRUE)
helmert_transform(comp_mat)
# Example 2: Transform specific columns in a data frame
df <- data.frame(
electorate = c("A", "B", "C"),
ALP = c(0.5, 0.4, 0.6),
LNP = c(0.3, 0.4, 0.2),
Other = c(0.2, 0.2, 0.2)
)
helmert_transform(df, items = c(ALP, LNP, Other))
Validate and order path data
Description
Internal helper to validate and reorder data
within each group according to the order_by aesthetic.
Used by stat_ordered_path().
Usage
ordered_path_df(
data,
group,
order_by,
decreasing = FALSE,
na_method = c("drop_na", "drop_group")
)
Arguments
data |
A data frame containing at least an |
decreasing |
Logical. If |
na_method |
Character string indicating how to handle missing values in
|
Value
A data frame with the same columns as data, but potentially fewer
rows (after dropping rows or groups) and with rows reordered within each
group.
Print method for ternable objects
Description
Print method for ternable objects
Usage
## S3 method for class 'ternable'
print(x, ...)
Arguments
x |
A ternable object |
... |
Additional arguments passed to print methods |
Value
The object, invisibly
Reorder observations for a path geom
Description
stat_ordered_path() reorders observations along each path using a user-supplied
ordering aesthetic (order_by) before drawing the path. The statistic can be
used to ensure that paths are drawn in a consistent order even when the input
data are not pre-sorted. This is equivalent to reordering the data before
passing it to geom_path().
Usage
stat_ordered_path(
mapping = NULL,
data = NULL,
geom = "path",
position = "identity",
show.legend = NA,
inherit.aes = TRUE,
decreasing = TRUE,
na_method = c("drop_na", "drop_group"),
...
)
StatOrderedPath
Arguments
mapping |
Set of aesthetic mappings created by |
data |
Data frame to be used for this layer. If |
geom |
The geometric object to use to draw the paths. Defaults to |
position |
A position adjustment to use on the data for this layer. This
can be used in various ways, including to prevent overplotting and
improving the display. The
|
show.legend |
Logical or |
inherit.aes |
If |
decreasing |
Logical. If |
na_method |
Character string specifying how to handle missing values in
|
... |
Additional parameters passed on to the underlying geom. |
Format
An object of class StatOrdered (inherits from Stat, ggproto, gg) of length 4.
Details
The statistic expects an order_by aesthetic that supplies the variable used
to order observations within each group.
Missing values order_by are handled according to na_method.
If na_method is "drop_na", missing values are dropped from the data, and the path is
still drawn, but might skipping steps due to missing values. If na_method is "drop_group",
the entire group whose missing values belong is dropped from the data, and the path is not drawn.
Ties in order_by are allowed but trigger a warning and preserve the original
row order for tied values.
Duplicates in order_by are dropped and a warning is issued.
Grouping is controlled via the usual group aesthetic. If a group column
is present in the data, reordering is performed independently within each
group; otherwise, the entire data is treated as a single path.
Value
A ggplot object
A ggplot2 layer that can be added to a plot object.
Aesthetics
stat_ordered_path() understands the following aesthetics (required are in
bold).
-
x
-
y
-
order_by
group
alpha, colour, linewidth, linetype, etc. (inherited from
ggplot2::geom_path())
Examples
library(ggplot2)
library(dplyr)
# Data prep
input_df <- prefviz::aecdop22_transformed |>
filter(DivisionNm %in% c("Higgins", "Monash"))
tern22 <- as_ternable(input_df, ALP:Other)
# Base plot
p <- get_tern_data(tern22, plot_type = "2D") |>
ggplot(aes(x = x1, y = x2)) +
add_ternary_base() +
geom_ternary_region(
aes(fill = after_stat(vertex_labels)),
vertex_labels = tern22$vertex_labels,
alpha = 0.3, color = "grey50",
show.legend = FALSE
) +
geom_point(aes(color = ElectedParty)) +
add_vertex_labels(tern22$simplex_vertices) +
scale_color_manual(
values = c("ALP" = "red", "LNP" = "blue", "Other" = "grey70"),
aesthetics = c("fill", "colour")
)
# Add ordered paths
p +
stat_ordered_path(
aes(group = DivisionNm, order_by = CountNumber, color = ElectedParty))
Getter functions to extract components from ternable object for ternary plots
Description
Performs additional transformations on ternable object components, making it
ready for both 2D ternary plot with ggplot2 and
high-dimensional ternary plots with tourr.
Usage
get_tern_data(ternable, plot_type = c("2D", "HD"))
get_tern_edges(ternable, include_data = FALSE)
get_tern_labels(ternable)
Arguments
ternable |
A ternable object created by |
plot_type |
Only in |
include_data |
Logical. Only in |
Details
These functions are designed to work together for creating animated tours of high-dimensional ternary data:
-
get_tern_data()provides the point coordinates -
get_tern_edges()provides the simplex structure -
get_tern_labels()provides labels that align with the data rows
Value
-
get_tern_data(): A data frame as input forggplot2ortourr.If
plot_type = "2D", the data frame augments the original data, with its ternary coordinates. Used as input data forggplot2.If
plot_type = "HD", the data frame combines ternary coordinates of original data with those of simplex vertices (without vertex labels). Used as input data fortourr.
-
get_tern_edges(): A matrix of simplex edge connections for drawing the simplex boundary.If
include_data = FALSE, the matrix contains only the simplex edges. Equivalent toternable$simplex_edges.If
include_data = TRUE, the matrix combines the simplex edges with the data edges. Used when you want to draw lines between the data points.
-
get_tern_labels(): A character vector containing vertex labels. Used as vertex labels fortourr, via argumentvertex_labels.
See Also
as_ternable() for creating ternable objects
Examples
library(ggplot2)
# Create a ternable object
tern <- as_ternable(prefviz::aecdop22_transformed, ALP:Other)
# Use with tourr (example)
tourr::animate_xy(
get_tern_data(tern, plot_type = "HD"),
edges = get_tern_edges(tern),
obs_labels = get_tern_labels(tern),
axes = "bottomleft")
# Use with ggplot2 (example)
ggplot(get_tern_data(tern, plot_type = "2D"), aes(x = x1, y = x2)) +
add_ternary_base() +
geom_point(aes(color = ElectedParty))