| Title: | Dyad Ratios Algorithm for Latent Variable Estimation |
|---|---|
| Description: | Implements the Dyad Ratios algorithm for estimating latent variables from time-series survey data. The algorithm estimates a latent mood dimension (or two dimensions) from a set of issue opinion series. Supports annual, quarterly, monthly, and daily aggregation intervals, optional exponential smoothing, and up to two latent dimensions. Input data can be provided as a data frame or read from delimited text files. Based on Stimson's 'MCalc' C++ program. See Stimson (2018) <doi:10.1177/0759106318761614> for more details. |
| Authors: | James Stimson [aut] (Original C++ implementation), Dave Armstrong [cre, aut] |
| Maintainer: | Dave Armstrong <[email protected]> |
| License: | GPL-3 |
| Version: | 2.1 |
| Built: | 2026-09-24 11:27:35 UTC |
| Source: | https://github.com/cran/DyadRatios |
Implements the Dyad Ratios algorithm (Stimson 1991) for estimating latent public mood from a collection of time-series survey marginals. The computationally intensive estimation loop is written in C++ (via Rcpp) and is a faithful translation of James Stimson's original 'MCalc' program. The R layer handles data ingestion, temporal aggregation, result formatting, and visualisation.
extractRun the algorithm on a data frame.
The primary input is a data frame where each row is one survey marginal:
A column identifying the opinion variable (issue series).
A date column (any format coercible by as.Date).
An index column: the survey proportion or percentage.
An optional n column: number of respondents (used as weight; defaults to 1000 if omitted).
Maintainer: Dave Armstrong [email protected]
Authors:
James Stimson (Original C++ implementation)
Stimson, J. A. (1991). Public Opinion in America: Moods, Cycles, and Swings. Boulder, CO: Westview Press.
Stimson, J. A. (1999). Public Opinion in America, 2nd ed. Westview.
The bootstrapping algorithm was added to the package and code by Dave Armstrong. It is worth noting that this was not something Stimson had originally implemented nor even something that he necessarily even endorsed. While bootstrapping is a common method for estimating uncertainty in complex estimators, like this one, use these results at your own risk.
boot_dr( obj, data, R = 200L, level = 0.95, pw = FALSE, seed = NULL, parallel = FALSE )boot_dr( obj, data, R = 200L, level = 0.95, pw = FALSE, seed = NULL, parallel = FALSE )
obj |
An object of class |
data |
The original data frame that was passed to |
R |
Integer. Number of bootstrap replications. Default |
level |
Numeric in (0, 1). Confidence level for the interval.
Default |
pw |
Logical. Whether to calculate pairwise differences between times |
seed |
Integer or |
parallel |
Logical. Parallelise the outer loop using parallel if
available? Useful for large |
Generates a sampling distribution around the latent mood trajectory by
repeatedly drawing synthetic survey marginals from a binomial model and
re-running extract. The original (unperturbed) estimate is
used as the point estimate; the bootstrap draws characterise uncertainty
around it.
All model parameters (aggregation interval, column names, smoothing, etc.)
are taken directly from the stored call inside obj, so there is no
risk of the bootstrap replications being run with different settings than
the original.
Each replication draws where
is the observed proportion and is the sample size, then
replaces the index column with (rescaled to the same 0–100
vs 0–1 convention as the original) and re-runs extract using
the exact call stored in obj. Replications that error (e.g. due to
a degenerate draw) are silently discarded; attr(result, "R") reports
how many succeeded.
A list of class "boot_dr" with two or four components
depending on whether the original obj used one or two dimensions:
estimatesA data.frame with one row per time period
and columns period, year, month, quarter,
mood (original point estimate), lower, and upper
(confidence bounds at (1-level)/2 and 1-(1-level)/2).
When n_dim = 2, three additional columns are appended:
mood_dim2, lower_dim2, and upper_dim2.
samplesAn n_periods × R matrix of raw
bootstrap dimension-1 trajectories.
samples_dim2(n_dim = 2 only) An n_periods
× R matrix of raw bootstrap dimension-2 trajectories.
The list also carries attributes R (number of successful
replications), level, agg_interval, and n_dim.
# Build a small synthetic dataset: 4 items measured annually over 20 years set.seed(42) n_years <- 20 years <- seq(1980, length.out = n_years) items <- c("item_a", "item_b", "item_c", "item_d") dat <- do.call(rbind, lapply(items, function(item) { data.frame( varname = item, date = as.Date(paste0(years, "-07-01")), index = 50 + cumsum(rnorm(n_years, 0, 1.5)) + rnorm(n_years, 0, 2), n = sample(800:1200, n_years, replace = TRUE) ) })) # Run the original estimate first res <- extract(dat, n_col = "n", smoothing = FALSE) # Bootstrap with 100 replications (use more in practice) boot <- boot_dr(res, dat, R = 100, seed = 1) # estimates is the summary data frame head(boot$estimates) # mood is the original point estimate; lower/upper are the 95% CI boot$estimates[, c("year", "mood", "lower", "upper")] # samples is the raw n_periods x R matrix of bootstrap trajectories dim(boot$samples) # Plot the trajectory with uncertainty ribbon plot(boot)# Build a small synthetic dataset: 4 items measured annually over 20 years set.seed(42) n_years <- 20 years <- seq(1980, length.out = n_years) items <- c("item_a", "item_b", "item_c", "item_d") dat <- do.call(rbind, lapply(items, function(item) { data.frame( varname = item, date = as.Date(paste0(years, "-07-01")), index = 50 + cumsum(rnorm(n_years, 0, 1.5)) + rnorm(n_years, 0, 2), n = sample(800:1200, n_years, replace = TRUE) ) })) # Run the original estimate first res <- extract(dat, n_col = "n", smoothing = FALSE) # Bootstrap with 100 replications (use more in practice) boot <- boot_dr(res, dat, R = 100, seed = 1) # estimates is the summary data frame head(boot$estimates) # mood is the original point estimate; lower/upper are the 95% CI boot$estimates[, c("year", "mood", "lower", "upper")] # samples is the raw n_periods x R matrix of bootstrap trajectories dim(boot$samples) # Plot the trajectory with uncertainty ribbon plot(boot)
Estimates one or two latent opinion dimensions from a collection of
time-series issue variables using the Dyad Ratios algorithm developed by
James Stimson (Stimson 1991, 1999). This function accepts already-loaded
data as a data.frame, handles aggregation to the requested interval,
standardises the issue matrix, passes it to the compiled C++ core, and
returns a richly-annotated result object.
extract( data, varname_col = "varname", date_col = "date", index_col = "index", n_col = NULL, agg_interval = c("annual", "quarterly", "monthly", "daily", "multi_year"), multiple = 1L, start_date = NULL, end_date = NULL, n_dim = 1L, smoothing = TRUE, tol = 0.001, fiscal_year_end = 12L, smooth_seed = 1L )extract( data, varname_col = "varname", date_col = "date", index_col = "index", n_col = NULL, agg_interval = c("annual", "quarterly", "monthly", "daily", "multi_year"), multiple = 1L, start_date = NULL, end_date = NULL, n_dim = 1L, smoothing = TRUE, tol = 0.001, fiscal_year_end = 12L, smooth_seed = 1L )
data |
A |
varname_col |
Character. Name of the column that identifies the
opinion variable (issue series). Default |
date_col |
Character. Name of the column containing the observation
date. Must be coercible to |
index_col |
Character. Name of the column containing the survey
marginal value (e.g. proportion liberal, per cent approving, etc.).
Default |
n_col |
Character or |
agg_interval |
Character. Temporal aggregation level. One of
|
multiple |
Integer. Number of years per period when
|
start_date |
Optional |
end_date |
Optional |
n_dim |
Integer 1 or 2. Number of latent dimensions to extract.
Default |
smoothing |
Logical. Apply optimal exponential smoothing to each
forward and backward pass. Default |
tol |
Numeric. Convergence tolerance (maximum weighted change in
item-mood correlations between iterations). Default |
fiscal_year_end |
Integer 1–12. Final month of the fiscal / policy
year; use 12 (default) for calendar years. When set to a value < 12
observations after this month are rolled forward into the next year,
consistent with the original program's |
smooth_seed |
Integer. Seed for the C random number generator used
inside the exponential-smoothing optimiser ( |
The algorithm iterates between a forward pass and a backward pass. In each
pass every period's latent score is estimated as the weighted average of
ratios of issue values relative to all other periods for which both values
are non-missing. The weights are the squared correlations of each issue
with the current mood estimate. Convergence is declared when the maximum
weighted change in these correlations falls below tol.
Issues are standardised to a mean of 100 and a standard deviation of 10 before estimation, then the final mood series is rescaled to have the weighted mean and standard deviation of the raw issue series.
An object of class "extract", which is a named list
containing:
moodNumeric vector of estimated public mood, one value
per period. When n_dim = 2 this is the first dimension.
mood_dim2Numeric vector of the second dimension, or
NA when n_dim = 1.
periodsA data.frame describing each period
(year, month/quarter where applicable) matching the length of
mood.
loadingsA data.frame of variable names with
loading on each extracted dimension.
iterationsA data.frame of the iteration history
(convergence, reliability, smoothing alphas per iteration).
n_seriesInteger. Number of opinion series retained after dropping constant series.
n_periodsInteger. Number of time periods.
n_obsInteger. Total number of input observations used.
alpha_FFinal forward-pass smoothing parameter.
alpha_BFinal backward-pass smoothing parameter.
eigenvalueEigenvalue estimate for the first dimension.
variance_explainedProportion of total variance accounted for by the first dimension.
variance_explained_dim2(n_dim = 2 only) Additional
proportion of total variance accounted for by the second dimension (i.e.
its share of the variance left unexplained by dimension 1). NA
for single-dimension solutions.
callThe matched call.
settingsList of all parameter values used.
Stimson, J. A. (1991). Public Opinion in America: Moods, Cycles, and Swings. Westview Press.
Stimson, J. A. (1999). Public Opinion in America, 2nd ed. Westview Press.
# Minimal synthetic example set.seed(42) n <- 60 dates <- seq(as.Date("1980-01-01"), by = "year", length.out = n / 3) dat <- data.frame( varname = rep(c("item_a", "item_b", "item_c"), each = length(dates)), date = rep(dates, 3), index = c(seq(40, 65, length.out = length(dates)) + rnorm(length(dates), 0, 2), seq(55, 35, length.out = length(dates)) + rnorm(length(dates), 0, 2), seq(45, 70, length.out = length(dates)) + rnorm(length(dates), 0, 3)), n = 1000 ) result <- extract(dat, n_col = "n", smoothing = FALSE) print(result)# Minimal synthetic example set.seed(42) n <- 60 dates <- seq(as.Date("1980-01-01"), by = "year", length.out = n / 3) dat <- data.frame( varname = rep(c("item_a", "item_b", "item_c"), each = length(dates)), date = rep(dates, 3), index = c(seq(40, 65, length.out = length(dates)) + rnorm(length(dates), 0, 2), seq(55, 35, length.out = length(dates)) + rnorm(length(dates), 0, 2), seq(45, 70, length.out = length(dates)) + rnorm(length(dates), 0, 3)), n = 1000 ) result <- extract(dat, n_col = "n", smoothing = FALSE) print(result)
Combines the period descriptor table from an extract result
with the estimated mood trajectory, returning a plain data.frame
that is convenient for further analysis or export.
get_mood(obj, ...)get_mood(obj, ...)
obj |
An object of class |
... |
Ignored; included for potential future use. |
A data.frame with one row per time period. Columns
period, year, month, and quarter are
inherited from the period table inside obj. When a single
dimension was estimated an additional column mood is appended.
When two dimensions were estimated, columns mood_dim1 and
mood_dim2 are appended instead.
set.seed(1) dat <- data.frame( varname = rep(c("a", "b", "c"), each = 20), date = rep(seq(as.Date("1980-01-01"), by = "year", length.out = 20), 3), index = 50 + rnorm(60, 0, 5), n = 1000L ) res <- extract(dat, n_col = "n", smoothing = FALSE) mood_df <- get_mood(res) head(mood_df)set.seed(1) dat <- data.frame( varname = rep(c("a", "b", "c"), each = 20), date = rep(seq(as.Date("1980-01-01"), by = "year", length.out = 20), 3), index = 50 + rnorm(60, 0, 5), n = 1000L ) res <- extract(dat, n_col = "n", smoothing = FALSE) mood_df <- get_mood(res) head(mood_df)
A dataset of survey marginals from the British Social Attitudes (BSA) survey, measuring public trust in government. These marginals are commonly used as input to the Dyad Ratios Algorithm for constructing latent time series. We replaced missing sample sizes with a value of 850, which is roughly the minimum sample size observed in the data.
A data frame with 4 variables and 'r nrow(jennings)' rows:
Character string identifying the survey question or series.
Date the survey was fielded.
percentage of people indicating distrust in the government.
Sample size for the survey wave.
Jennings, W. N. Clarke, J. Moss and G. Stoker (2017). "The Decline in Diffuse Support for National Politics: The Long View on Political Discontent in Britain" In *Public Opinion Quarterly*, 81(3), 748-758. doi:10.1093/poq/nfx020
data(jennings) head(jennings)data(jennings) head(jennings)
Draws a ribbon plot of the bootstrap confidence interval around the original mood estimate. Requires ggplot2; falls back to base R if unavailable.
## S3 method for class 'boot_dr' plot(x, dim = 1L, title = "Estimated Public Mood", ylab = "Mood", ...)## S3 method for class 'boot_dr' plot(x, dim = 1L, title = "Estimated Public Mood", ylab = "Mood", ...)
x |
A |
dim |
Integer. Which dimension to plot ( |
title |
Character. Plot title. |
ylab |
Character. Y-axis label. |
... |
Ignored. |
Invisibly, the ggplot2 object or NULL (base R).
Produces a time-series plot of the estimated latent mood. Requires ggplot2. If ggplot2 is not installed, falls back to base R graphics.
## S3 method for class 'extract' plot(x, dim = 1L, title = "Estimated Public Mood", ylab = "Mood", ...)## S3 method for class 'extract' plot(x, dim = 1L, title = "Estimated Public Mood", ylab = "Mood", ...)
x |
A |
dim |
Integer. Which dimension to plot (1 or 2). Default |
title |
Character. Plot title. Default |
ylab |
Character. Y-axis label. Default |
... |
Additional arguments passed to the plotting function. |
Invisibly, the plot object (ggplot2) or NULL (base R).
Print method for extract objects
## S3 method for class 'extract' print(x, ...)## S3 method for class 'extract' print(x, ...)
x |
A |
... |
Ignored. |
Prints a detailed report similar to the log file produced by the original 'MCalc' program, including the iteration history, variable loadings, and variance accounting.
## S3 method for class 'extract' summary(object, ...)## S3 method for class 'extract' summary(object, ...)
object |
A |
... |
Ignored. |
Invisibly returns object.