circumplex 2.0.0
This is a major release. Its flagship addition is cpm_fit(), a native reimplementation of Browne’s (1992) circular stochastic process model for the correlational structure of circumplex scales — filling the gap left by the archived CircE package, the previous R implementation. Alongside it come four other new analysis families: latent-variable SSM analysis with ssm_sem(), repeated-measures (longitudinal) SSM analysis, fit_structure() for exploratory circumplex-structure tests, axes_reliability() for the reliability of the circumplex axes (Strack et al., 2013), and ssm_ci_accuracy(), a diagnostic for whether an ssm_analyze() result’s confidence intervals can be trusted at your sample size and profile (Zimmermann & Wright, 2017). The plotting layer has been rebuilt on a real ggplot2 coordinate system.
Breaking changes and changed behavior
The component standard errors reported by
axes_reliability()are now calibrated. Previously they were computed as if the item correlation matrix were a covariance matrix — the source paper’s own practice, documented as approximate — which for strong-axes instruments overstated the standard error of the axes variance by 25–45%, and for weak-axes, strong-general instruments could understate it slightly. Because the error changed sign across the range of instruments the function accepts, no fixed caveat could state it honestly. Point estimates, reliabilities, SEm, degrees of freedom, and SRMR are all unchanged; the remaining fit statistics are corrected separately, below. Corrected standard errors are typically smaller than those printed in Strack et al. (2013), whose LISREL values carry the same uncorrected approximation. The uncorrected values remain available indetails$se_uncorrected.The global fit statistics reported by
axes_reliability()are now calibrated to the correlation metric.chisq,pvalue,rmseaandcfiare Satorra-Bentler-type scaled values, computed by dividing the chi-square by a factor evaluated at the fitted matrix (withcfialso scaling its baseline model);dfandsrmrare unchanged. Previously these carried the same correlation-as-covariance mismatch as the standard errors did, running the other way: sample correlations vary less than covariances, so the test statistic came out too small and fit was flattered — by roughly 4% at one reference population, which the documentation had reported as though it were a constant. It is not a constant, and the scaling factor is now recomputed for every fit. All three input paths scale, includingmissing = "fiml". Expect slightly larger chi-squares, smaller p-values, slightly higher RMSEA, and slightly lower CFI than previous versions reported on the same data. The unscaled values remain available indetails$fit_uncorrected, with the factors indetails$scaling_factor. If the factor cannot be computed, the four areNAwith the reason indetails$fit_scaling_failedrather than falling back to the unscaled values. The correction is calibrated in mean and its test is asymptotically exact, but at small samples relative to the item count it over-rejects: see?axes_reliabilityfor the measured rates and the sample sizes they were measured at. Note the direction — the scaled test over-flags misfit, where the uncorrected one flattered it. These follow the definitions lavaan callschisq.scaled,pvalue.scaled,rmsea.scaledandcfi.scaled, not its*.robustforms. Since the fit itself is estimated with plain ML,fitMeasures()on an equivalent fit reports the unscaled values under the bare names and no*.scaledor*.robustmeasure at all, so a cross-check against lavaan’s barecfiwill differ and a request forcfi.robustwill come back empty.axes_reliability()now refuses a degenerate fitted covariance matrix under a single stated criterion, evaluated in the metric every reported number is computed in: when the smallest eigenvalue ofcov2cor()of the fitted matrix, relative to its largest, falls at or belowsqrt(p * .Machine$double.eps / 1e-6)— an indefinite, singular, or severely ill-conditioned correlation structure; the1e-6is a stated accuracy target, past whose floor the corrected standard errors could carry relative error above it — both surfaces refuse with reason"ill_conditioned", the component standard errors and the four scaled statistics (chisq,pvalue,rmsea,cfi) areNAtogether, and each surface’s warning names that shared reason;dfandsrmrstill report. The standard-error surface additionally applies the same criterion to the raw fitted matrix, which one internal arm of its computation — the uncorrected normal-theory pricing kept only as a diagnostic tie to lavaan’s own standard errors — inverts. A matrix degenerate only in the raw metric (wildly unequal fitted variances over a well-conditioned correlation structure) refuses that internal arm alone: the reported standard errors and scaled fit statistics all compute, with no warning, and the internal refusal is recorded silently indetails$naive_reason(a newdetailsfield,NULLwhenever that arm computed) under the same reason vocabulary. Under the shared criterion the two surfaces’ user-facing refusals therefore agree exactly, and on a unit-diagonal fitted matrix the two metrics coincide; each surface retains its own refusals outside the criterion (such as the saturated-model door, which touches only the fit statistics). Previously the two surfaces disagreed at the numerical margin — whichever internalsolve()failed first refused with an incidental label — so a sufficiently degenerate fitted matrix could yieldNAcorrected standard errors beside silently scaled fit statistics derived from the same matrix. The failure-reason vocabulary is now shared across both surfaces, and the reported literals change as follows. Ondetails$se_correction_failedalone: a nonpositive fitted variance reports"singular"where it previously reported"nonpositive_diagonal", and a positive-infinite one reports"infinite_diagonal"rather than"unidentified". Ondetails$se_correction_failedanddetails$fit_scaling_failedalike: an exactly singular fitted matrix reports"ill_conditioned"where both previously reported"singular", and an indefinite one reports"indefinite"deliberately or"ill_conditioned"at the numerical margin, per the refusal-vocabulary split in the next entry. Code that branches on any of these reason strings needs updating.axes_reliability()‘s refusal reasons now say which degeneracy happened. Within the degeneracy criterion’s refusal region, a fitted correlation structure whose smallest eigenvalue is decisively negative — beyond the fit’s own numerical noise band — reports"indefinite", a statement about the model; roundoff-level negativity, exact singularity, and mere ill-conditioning report"ill_conditioned", a numerical caution. A saturated model (zero degrees of freedom) is refused as"saturated"before any scaling arithmetic runs, where it previously surfaced as"indefinite"through an internal division by zero; and the final nonpositive-scaling-factor backstop likewise reports"ill_conditioned"rather than"indefinite", an indefiniteness it cannot diagnose. When the standard-error surface’s two internal arms would label one matrix differently, the reported literal is the correlation-metric arm’s — the same arm the fit-scaling surface prices — so the two surfaces never name the same matrix differently. Two of these changes are visible today only at the internal helpers’ documented contract boundary, not through anyaxes_reliability()call:"saturated"needs a three-item map, whichaxes_reliability()refuses, and the backstop’s relabel has not been observed to fire (a 30,000-draw search found no reaching input — recorded as not-reached, not as unreachable); they are documented for code that branches on thedetailsreason fields.axes_reliability()objects now reportdetails$n_moments, the number of distinct analyzed moments p* = p(p+1)/2, anddetails$baseline, the independence model’s unscaled chi-square and degrees of freedom.details$nis documented now as the sample size the fit was priced at, as distinct fromn_totalandn_complete. Togethern_momentsandnlet you locate a fit on the calibration table invignette("axes-reliability");baseline, withfit$chisq,fit$dfand thebaselineelement ofdetails$scaling_factor, lets you reproduce the reportedcfi.The displacement-interpretability guardrail in
print()andsummary()now uses a scale-free rule: a profile’s displacement is certified as interpretable only when the amplitude confidence interval’s lower bound sits at least 0.35 interval-widths above zero. This replaces the rule introduced in 1.2.0, which certified whenever the lower bound rounded above zero at the display precision — a threshold that moved with the printdigitsand meant different things on different score metrics, and that (as the newssm_ci_accuracy()diagnostic makes visible) certified a genuinely zero amplitude almost every time. The new rule holds false-certification near the interval’s one-sided error rate regardless of scale or display settings. As a result, some near-zero-amplitude profiles that were previously certified are now flagged uninterpretable. The threshold is calibrated for the default 95% confidence interval.Displacement and angle confidence-interval endpoints that land exactly on the 0/360 pole are now reported as 360, never 0, matching how the package labels that pole everywhere else (LM = 360):
ssm_analyze()bootstrap displacement CIs andcpm_fit()bootstrap angle CIs both use the shared circular-quantile machinery that now applies this labeling.cpm_fit()’s reportedAnglecolumn likewise labels the pole 360 — a reference scale with a theory angle of 360 previously printedAngle = 0with a degenerate CI of[0, 0], and now prints 360 throughout. An exact-pole endpoint is a measure-zero floating-point corner for real data, so numeric results are otherwise unchanged.norm_standardize()now refuses a normative sample whose mean scores fall outside the instrument’s own response range, instead of returning z-scores computed from it. Such a sample cannot be on the same metric as the scores being standardized, so the values it produced were wrong in an undefined unit, with nothing in the output to indicate it. One shipped sample is affected: the CAIS adult sample (sample = 2), three of whose octant means exceed the CAIS’s 1–5 response range. The CAIS child sample (sample = 1) and every other instrument are unaffected. See?cais; the source of those values is under query with its authors, and the sample will be corrected or withdrawn once that is settled.norm_standardize()now reports which normative sample it used. Every successful call prints the sample number, its size, its description and its reference kind — for example, “Standardized against IIP-SC normative sample 1: N = 872, American college students. Reference kind: identified published source.” — and, where the instrument carries more than one sample, says how many others are available. Which sample you standardize against is a result-determining choice rather than a technicality: across the shipped instruments, different samples of the same instrument move a respondent’s z-scores by roughly half a standard deviation on average, and by nearly twice that at the extreme. Pass the newquiet = TRUEto suppress the message in loops and knitted documents.Every normative sample now records what kind of reference distribution it is, in a new
Kindcolumn readable atinstrument$Norms[[2]]. Six of the 24 shipped samples — the IIP-32’s and IIP-64’s — were drawn to represent a defined population, so their means and standard deviations estimate that population’s; 16 are described in an identified published source and describe that group of people and no wider frame; and two appear in no source that has been identified at all.norms()prints the kind for each sample it lists, andnorm_standardize()names it in both its message and its attribute, so the distinction is available where you choose a sample and where you use one. See?normsfor what each kind means.Every data frame returned by
norm_standardize()now carries a"norm_sample"attribute recording the instrument, the sample number, its size, its description and its reference kind, so a script that never sees the console can still report what its z-scores are relative to. Retrieve it withattr(x, "norm_sample"). It is attached whether or notquietis set, and on both theappend = TRUEandappend = FALSEreturn paths.The package now requires ggplot2 (>= 4.0.0), and
ggforceis no longer a dependency. The declared R requirement moves to R (>= 4.1) to match the floor ggplot2 already imposes; no installation that worked before is affected.ssm_score()’s extra arguments passed through...must now be named (e.g.prefix = "IIP_") and must be single strings; an unnamed or non-scalar argument is now an error rather than being silently ignored (previously it could yield unlabeled or garbled output columns). Rows whose profile has undefined displacement now produce a single warning reporting how many such rows there are, rather than one warning per row.Count-valued arguments (e.g.
boots,reps,ncpus,digits, and the sample sizen) acrossssm_analyze(),ssm_ci_accuracy(),cpm_fit(),cpm_simulate(), andssm_sem()are now uniformly validated as a single non-negative whole number. A few of these previously accepted a length-greater-than-one vector without complaint; such input now raises a clear error.ssm_plot_circle()now warns and names any profile it cannot place on the circle because its displacement is undefined (a flat or zero-amplitude profile), instead of dropping it from the figure without notice.
Circumplex structure and model fitting
New
cpm_fit()function estimates Browne’s (1992) circular stochastic process model for the correlational structure of circumplex scales or items, a native replacement for the archived CircE package. It accepts either raw data or a correlation matrix, estimates item angles and communality indices (with four model variants), and reports the usual covariance-structure fit indices (chi-square, RMSEA with a 90% confidence interval, SRMR, CFI, TLI, AIC, BIC). The returnedcircumplex_cpmobject hasprint()andsummary()methods. On the raw-data path, confidence intervals are estimated by a nonparametric bootstrap by default (resampling rows and refitting the model, with percentile intervals; angle intervals use the package’s circular quantile machinery, so an interval straddling the 0/360 degree boundary is reported wrapped, as with displacement intervals). Resamples that are degenerate or fail the convergence criterion are excluded with a warning and counted in the output. Only the bootstrap consumes R’s random number stream: callset.seed()immediately beforecpm_fit()for reproducible intervals (point estimates are deterministic). On the correlation-matrix path, intervals are analytic (Wald) — there is no raw data to resample — andsummary()cautions when the sample size is small enough that these may mis-cover. Ascalingargument selects the covariance-scaling family:"unit"(the default) fits the correlation structure, while"free"fits Browne’s covariance structure withpfree variance scales — the parameterization CIRCUM and CircE use — socpm_fit()can reproduce their published output exactly. Free scaling addspparameters without changing the degrees of freedom, and reports the fitted variance ratios in aVarRatiocolumn (without confidence intervals). With correlation input the two families’ model-test statistics are calibration-indistinguishable (paired simulation at sample sizes 250–50,000), so the default remains the recommended family for routine inference; usescaling = "free"when the goal is reproducing published CIRCUM/CircE output.cpm_fit(scaling = "free")also starts its optimizer from the unit-scaling solution, so the free family’s fit statistic can never exceed the default family’s on the same input beyond numerical tolerance (the free family mathematically nests the default).The
cpm_fit()estimator has been validated against the published CIRCUM/CircE literature (Grassi, Luccio, & Di Blas, 2010, reanalyzing Browne’s 1992 vocational-interest example) and against independent OpenMx and lavaan implementations of the same model (both now in Suggests as test oracles only). CIRCUM and CircE fit Browne’s covariance parameterization with free variance scalings;cpm_fit(scaling = "free")fits that same family and reproduces their published estimates, chi-square, and fit indices to printed precision, while the default correlation-structure fit differs from them slightly in finite samples (same degrees of freedom, asymptotically equivalent); see the package’s design notes for details. A large seeded simulation study measured the coverage of both interval methods: based on its results,summary()now also cautions about analytic intervals at any sample size below 50,000 when the fitted solution is near a parameter boundary or weakly identified (Heywood case, removed harmonic, very small correlation-function weight, ill-conditioning, or competing near-tied optima — the caution names which), the regime where they measurably mis-covered. Percentile bootstrap intervals were confirmed as the better default but are themselves conservative-liberal in spots (notably for near-boundary correlation-function weights); improving them is planned follow-up work. On the bootstrap path,summary()also lists any fired markers in a descriptive note at every sample size, stating that what has been measured about the markers covers analytic intervals only — not every marker was measured, and none is validated as a predictor of the bootstrap intervals shown.New
fit_structure()function evaluates whether a set of scales forms a circumplex using the exploratory criteria of Acton & Revelle (2004). Four criteria are computed from the first two unrotated principal-axis factors of the scales’ correlations — the Fisher Test of equal axes, the Gap Test of equal spacing, and the Variance (VT2) and Rotation tests of interstitiality — and a fifth, the RANDALL correspondence index (Hubert & Arabie, 1987; Tracey, 1997), tests the hypothesized circular order of the scales with a randomization test that yields an exact p-value. The factor-analytic statistics are classified against interpretive cutoffs that were re-derived by simulation under Acton & Revelle’s own generating model for eight (octant) scales — their published cutoffs were calibrated on far more variables and do not transfer — and that are keyed to the scoring, since these criteria work best with a general factor removed.fit_structure()deviation-scores (ipsatizes) by default for that reason, with a raw opt-out. Missing values are handled by listwise deletion by default (alistwiseargument, matchingssm_analyze()), so all five tests share one complete-case correlation matrix — the metric the cutoffs were calibrated on. The returnedcircumplex_structureobject hasprint(),summary(), andplot()methods; interpretations are presented as the heuristic likelihood classifications they are, never as significance tests.New
axes_reliability()function estimates the reliability (and standard error of measurement) of the two circumplex axes with the item-level restricted tau-equivalent CFA of Strack, Jacobs, and Grosse Holtforth (2013). The model decomposes each item’s variance into a general factor, the two circumplex axes, scale specificity, and item specificity, and reads the axes’ reliability off the isolated axes-variance component with the Spearman-Brown formula — a confirmatory, item-level complement tofit_structure()‘s exploratory scale-level criteria. The Nunnally-Bernstein axis reliability is reported alongside for comparison (it overestimates when scale specificity is large). Items are supplied through acircumplex_instrumentor an explicit angle-and-item map. Any equally spaced set of scale angles is accepted, at any rotation and any count from four scales up — the canonical octants, an interstitial set rotated off the axes, or a six- or twelve-scale circumplex. Unequally spaced (quasi-circumplex) angles are refused rather than approximated, and three scales are refused because the variance components are not separately identified at that count. Scales may carry a single item each, as Strack’s single-item circumplex types do: with one item at every position no two items share a scale, so the scale-specificity component is not identified and is dropped from the model rather than estimated, leaving a three-row components table. A mixed instrument carrying at least one multi-item scale still estimates it. Because coefficient alpha is undefined for a one-item scale, the Nunnally-Bernstein comparison is reported asNAwith a stated reason whenever any scale has fewer than two items — as Strack et al. themselves do, leaving it blank for such instruments. Blockwise instruments — those administering items in blocks cutting across the scales — are supported through ablocksargument taking a list of item columns, one element per block, which adds Strack’s block-specificity component to the model and azeta2row to the components table. Blocks that carry no information the model lacks (blocks that coincide with the scales, one block for everything, or one block per item) leave the component unidentified, and it is dropped withdetails$zeta2_fittedrecording that, as scale specificity is on a single-item instrument. Whether ignoring real blocks matters depends on their geometry: the general factor is inflated under most layouts and never deflated, while the axes variance — and so the reliability — moves only when block membership carries information about the angular distance between items. When each block draws exactly one item from every scale it carries none, and the reliability is unaffected; other layouts bias it in either direction, and being evenly spread around the circle is not sufficient for safety. Estimation works either from raw item data or, throughcormatandn, from a published item correlation matrix alone, for reanalyzing a matrix whose raw data is not available; on that path the Nunnally-Bernstein comparison is reported asNAandsd = "raw"is refused, since both need the respondents’ own item scores. Missing data are handled by listwise deletion by default, and amissing = "fiml"setting estimates from every respondent who answered at least one item by full-information maximum likelihood instead. FIML assumes the data are missing at random and multivariate normal, both stronger assumptions than listwise deletion needs: under MCAR listwise deletion is already consistent and merely inefficient, so the gain there is precision rather than correctness, while under MAR listwise deletion is biased and FIML is not. The items are standardized by the saturated model’s own FIML moments rather than by whichever cells happen to be observed, and those columns feed a single FIML fit; the reported standard errors are observed-information standard errors on that standardized metric, conditional on the standardization constants. Undermissing = "fiml"the Nunnally-Bernstein comparison isNAandsd = "raw"is refused, both needing items observed by every respondent. Pairwise-deletion correlations are never used on either setting. Strack et al. report no missing-data analyses, so the FIML variant is certified against the package’s own synthetic oracle rather than against their results. A boundary fit returnsNAreliability rather than a clipped value; and the returnedcircumplex_axes_reliabilityobject hasprint()andsummary()methods. A bundled simulated dataset,simulated_items, is included for the examples.New
cpm_simulate()function draws standardized observations from a fittedcpm_fit()model’s implied correlation matrix, using the model’s exact positive-semidefinite factor representation. It returns a numeric matrix with one column per scale (in fitted order, named), whose population correlation matrix is the fittedPhat. Callset.seed()immediately before it for reproducible draws.
Latent-variable (SEM) analysis
New SEM-based (latent-variable) SSM analysis:
ssm_sem()estimates the Structural Summary Method profile of one or more external measures against the latent circumplex content of the scales — the disattenuated analog of the correlation-basedssm_analyze()— from a fixed-theoretical-angle measurement model fitted with lavaan (now a runtime Suggests dependency for this feature family; everything else works without it). Confidence intervals for all parameters are built in-package by propagating draws of the model’s free parameters (multivariate-normal by default, or a full lavaan bootstrap viaci_method = "boot") through the profile and SSM transforms and the same circular-quantile machinery asssm_analyze()— never lavaan’s delta-method or percentile intervals, which ignore the angular branch cut. The default draws propagate lavaan’s robust (sandwich) covariance, which the package’s coverage validation found necessary to keep the intervals calibrated when the fixed-angle model only approximates the data; the default estimator is MLR, so the global fit indicesprint()reports are likewise the robust/scaled versions (circumplex scale scores are typically skewed). Includesssm_sem_syntax()(an inspectable lavaan model-syntax generator that works without lavaan installed) andssm_sem_parameters()(estimate from a lavaan fit you have modified or fitted yourself). Results arecircumplex_ssmobjects, sossm_table()and thessm_plot_*functions work on them unchanged. With agroupingvariable,ssm_sem()fits the measurement model across groups and gates a latent group contrast on measurement invariance: it tests a configural-metric-scalar ladder using lavaan’s own nested-model test (the scaled difference test under robust estimators) at theinvariancerung (defaulting to each path’s required level) and theinvariance_alphalevel, and computes the disattenuated contrast only when the required rung is retained. When invariance is rejected it reports an honest non-comparison — the verdict plus each group’s separate configural profile — rather than a contrast that would confound structural difference with measurement non-invariance. Supplyinggroupingwithoutmeasuresanalyzes the latent mean path (each group’s model-implied latent mean profile). The observed-score group contrast inssm_analyze()remains the right tool when invariance cannot be assumed; it answers its own, different question.The invariance ladder that
print()reports now also carriesdcfi, the change in CFI from the previous fitted rung, as a labeled secondary criterion: Cheung and Rensvold’s (2002) general rule rejects an invariance step when CFI falls by more than .01. (That direction is taken from the article’s own Table 5, whose critical values are the 1% lower tails of the simulated null distributions; the sentence stating the rule on its p. 251 reads backwards relative to that table, and the package follows the simulation.) It is reported and never gates — the nested test alone decides comparability, the verdict, and which fit the estimates come from — and the two criteria can legitimately disagree, since a change in CFI is insensitive to sample size where the nested test is not. The retain/reject label prints only inside the envelope their simulation covers, which is narrow: they simulated two groups, ML estimation, and multivariate normal data, and examined Type I error only — not power — and robust CFI variants were not part of their study. So the label appears only for a two-group fit estimated by ML whose CFI is the plain, non-robust one.estimator = "ML"is necessary but not sufficient:missing = "fiml"also makes lavaan report a robust CFI, as do the"MLR"default and"MLM". Under a robust CFI from any of those routes, a non-ML estimator such as"GLS", or more than two groups, thedcfivalue still prints, marked as not validated for that configuration, with a note naming which condition applies and carrying no verdict — extending the cutoff there would require simulation that has not been done.
Repeated-measures and longitudinal analysis
New repeated-measures (longitudinal) SSM analyses:
ssm_analyze()gains anoccasionsargument taking a named list of column blocks, one per occasion, each selecting the same circumplex scales measured at that occasion (wide data, one row per person). Every occasion yields its own profile row, occasions cross withgrouping, andcontrast = TRUEwith exactly two occasions (single group) estimates the paired within-person contrast — second listed occasion minus first — through both engines: the bootstrap resamples persons (preserving within-person dependence nonparametrically) and the Monte Carlo engine draws the stacked occasion mean vectors jointly. Cross-occasion column alignment is validated by stem matching (a reordered occasion block errors instead of silently rotating displacement). Occasions analyses are listwise-only across waves, with the dropped-person count messaged and a selection caution documented. Results from occasions analyses carry a newOccasioncolumn that is present only for such analyses — downstream code should test for the column by name. Coverage of the paired contrasts was validated by simulation at nominal rate across boundary cells (displacement changes near 0 and 180 degrees, CIs straddling the 0/360 pole, small samples, three occasions); note that paired contrasts are not unconditionally more efficient than independent-groups designs (see the new Occasions section in?ssm_analyze).New
ssm_analyze_long()provides a long-format (one row per person per occasion) interface to the repeated-measures occasions analysis. It reshapes the data to the wide layoutssm_analyze()expects and delegates to it, so the estimation, paired within-person contrasts, and listwise missing-wave handling are unchanged. Occasion order is taken from the factor levels (or first appearance) of theoccasioncolumn and is never sorted alphabetically, so aT10/T2pair keeps its temporal order.New per-person (intraindividual) SSM scoring:
ssm_parameters_id()scores each person’s own circumplex profile through the closed-form SSM transform and returns a per-person parameter table — one row per person, with anidargument that first averages a person’s rows (e.g., occasions of intensive longitudinal data) within person before scoring. Degenerate profiles keep their row withNAparameters (never a silent drop), and anna_ratecolumn exposes each person’s share of missing scale cells. Asummary()method aggregates the table at the group level using circular statistics for displacement (circular mean and mean resultant length, never arithmetic means of angles), reporting how many undefined displacements were excluded. Two documented caveats: the circular mean of per-person displacements (equal weight per person) is a different quantity from the displacement of the group mean profile (amplitude-weighted), and by the triangle inequality the group profile’s amplitude is at most the mean per-person amplitude, strictly smaller when directions disperse.New Bayesian draws adapter:
ssm_draws()converts posterior draws from a user-fitted Bayesian model (e.g., a brms cosine regression) into SSM parameter draws and summarizes them with the package’s circular-statistics machinery — circular quantiles for displacement (credible intervals that straddle 0/360 wrap instead of inverting), posterior medians for the linear parameters (the amplitude posterior is right-skewed), and the circular mean for displacement, with the marginal-coherence caveat documented. Two draw shapes are accepted and never guessed: (e, x, y) parameter draws (type = "parameters", required because a 3-column matrix is ambiguous) and profile draws (one column per scale, withangles). Draws with undefined displacement are excluded from the displacement summaries only, with an honest warning that says “posterior draws” and “credible interval”.ssm_draws()objects also apply the package’s displacement-certification rule to the amplitude credible interval: when the interval’s lower bound sits under 0.35 interval-widths above zero, printing notes that the displacement is not interpretable, and the verdict is stored in$details$certified.New growth-model support for repeated-measures SSM analysis, documented in a new vignette (“Growth Models on SSM Parameters”): fit a joint mixed model to the per-person Cartesian coordinates from
ssm_parameters_id()(the reference recipe uses glmmTMB, now in Suggests; fitting the coordinates with separate univariate models silently zeroes their cross-covariance and produces wrong displacement intervals), then convert fixed-effect draws to amplitude/displacement trajectories withssm_draws(). The recipe was validated by simulation: pointwise displacement coverage is nominal in a pole-crossing design, and the univariate shortcut demonstrably fails coverage under correlated person effects.New
angle_unwrap()helper unwraps a temporally ordered sequence of angles onto a continuous branch (350, 10, 30 becomes 350, 370, 390), supporting the vignette’s alternative unwrap-then-model recipe. Inputs are wrapped to [0, 360) first; an exact 180-degree step ascends (the package’s half-turn convention);NAmakes later waves branch-ambiguous and so propagates onward.
Interval methods and diagnostics
New
ssm_ci_accuracy()function assesses, by simulation, whether the confidence intervals of anssm_analyze()result would cover the true SSM parameters at their nominal rate if the population looked like the fitted estimates, at the observed sample size(s) — the CI-trustworthiness diagnostic of Zimmermann & Wright (2017), generalized to the user’s own configuration (grouping, contrasts, measures, engine, resample count, and interval level). The population’s scale structure is characterized by acpm_fit()model (or, optionally, the observed correlations); each simulated dataset replays the object’s own interval procedure. Coverage is reported per profile row, parameter, and amplitude condition — a ladder of populations with the amplitude scaled toward zero, where percentile amplitude intervals are theoretically weakest — along with one-sided miss rates, interval widths, the certification rate of the printed displacement-interpretability guardrail, and displacement coverage conditional on certification. For a contrast row — a signed difference thatprint.circumplex_ssm()never certification-gates — the displacement verdict and printed coverage are reported unconditionally, matching that profiles-only stance (its conditional coverage is retained in the object as a descriptive). Coverage at the as-estimated condition is classified against Bradley’s (1978) liberal robustness band using 95% Wilson score intervals, andprint()/summary()translate the classifications into a plain-language verdict, including a line reporting how often the guardrail would certify displacement if the true amplitude were zero (the scale-free rule holds this near the interval’s one-sided error rate, and a caution is raised only if it materially exceeds that).summary()also annotates the realism of the simulated population (structural-model convergence and fit, against conventional RMSEA/SRMR benchmarks with citations) and, when an amplitude estimate is itself below half its CI width, notes that the analysis already sits in the near-zero regime and adds a ladder rung at the certification margin. Aplot()method draws coverage across the amplitude ladder with the Bradley band shaded. Simulation replicates can be parallelized (parallel/ncpus) with seed-identical results, and the caller’s random-number state is restored on exit. To support the diagnostic,ssm_analyze()now stores per-group sufficient statistics (sizes, scale SDs, and correlation matrices) in its output; objects created by earlier versions can be assessed by re-supplying the original data viassm_ci_accuracy(..., data = ), which is checked for consistency against the stored profiles.ssm_ci_accuracy()also assesses repeated-measures occasions analyses. Its plug-in population is a multivariate normal with the observed stacked cross-occasion covariance, so the within-person dependence across occasions is carried into the simulation (rather than ignored); it reports CI trustworthiness per occasion and for the paired within-person contrast. A flat occasion is refused by name, a rank-deficient stacked covariance is flagged (the fit-statistic pass rate becomes descriptive), and because the occasions population is the observed covariance thestructure/cpmarguments are not accepted on that path.ssm_analyze()gains amethodargument offering a Monte Carlo alternative to the bootstrap (method = "montecarlo"): SSM parameter replicates are drawn from the asymptotic sampling distribution of the group mean vector or measure-scale correlation vector (a multivariate normal with empirically estimated covariance; correlations are drawn jointly across measures on the Fisher z scale) and propagated through the SSM transformation. It produces intervals closely matching the bootstrap on large samples while running in a fraction of the time, but relies on asymptotic normality and requires listwise-complete data, so the bootstrap remains the default and the recommended choice for small samples.summary()reports which method produced the intervals.ssm_analyze()gainsparallelandncpusarguments (passed toboot::boot()) to distribute the bootstrap computation across multiple CPU cores. Because the resample indices are drawn in the main R process before any work is distributed, results for a givenset.seed()are identical regardless of these settings, so parallelizing never changes your estimates or confidence intervals.The Monte Carlo interval engine (
ssm_analyze(method = "montecarlo")) is faster on correlation-based analyses: the influence-function covariance is built in one vectorized pass and all profile rows are propagated through the SSM transformation in a single compiled call. Results are unchanged (byte-identical for a fixed seed).ssm_score()is now vectorized internally (one compiled call instead of a row-wise loop), making it much faster on large data sets. Results are unchanged.
Visualization
Circumplex figures are now built on a real ggplot2 coordinate system. The new
coord_circumplex()owns the amplitude-to-radius scaling and the displacement-to-angle transform in one place, so a canvas and its data layers can no longer disagree about the outer-ring amplitude. It adds a configurable amplitude center (the rings relabel and the amplitudes remap together) and a theme-responsive canvas: the rings, spokes, and labels drawn byggcircumplex()now restyle through+ theme_*(). It always draws an amplitude ring atamax, so every circumplex canvas closes at its rim and no point is drawn past the last visible ring; that rim ring is unlabeled unlessamaxis itself one of the axis breaks. A non-finiteamaxorcenteris rejected with a message naming the argument.ggcircumplex(),geom_ssm_point(),geom_ssm_arc(), andssm_plot_circle()keep their signatures and correct output. The per-layeramaxargument (andgeom_ssm_arc()’sn) are no longer needed and are ignored with a one-time note.New
ggcircumplex()function builds an empty circumplex plotting canvas (amplitude rings, displacement spokes, and scale labels) as a ggplot2 object that you can add layers to with+. It accepts a set of scaleanglesandlabels, or acircumplex_instrumentobject to derive both automatically. The package’s ownssm_plot_circle()draws on the same canvas.ggcircumplex()andscale_x_circumplex()label and place circumplex scales at their exact angles, including non-integer angles (for example, the 22.5-degree spacing of a 16-scale instrument), instead of rounding them to whole degrees.New
geom_ssm_point()andgeom_ssm_arc()layers draw SSM profile points and their confidence-region arcs directly in circumplex space on aggcircumplex()canvas, taking amplitude and displacement as aesthetics and handling the polar transform (including wrap-around at the 0/360 degree boundary) internally. These make it possible to build custom circumplex figures by composing ggplot2 layers.New
scale_x_circumplex()provides an angle-labeled x-axis scale for linear circumplex plots (such as the score-by-angle curve). It labels axis breaks with their angle in degrees by default, or with custom labels or acircumplex_instrument’s scale abbreviations, using the same conventions asggcircumplex().The circumplex ggplot2 layers are extensible and ergonomic. The
GeomSsmPoint,GeomSsmArc, andCoordCircumplexggproto generators are exported so downstream packages can subclass them. The amplitude (radial) axis and its labels are drawn in the widest gap between the displacement spokes, so they no longer overlap a spoke label;coord_circumplex()gains anr_axis_angleargument to place it manually. The canvas theme is exported astheme_circumplex().geom_ssm_point()andgeom_ssm_arc()follow the ggplot2na.rmconvention: withna.rm = FALSEthey warn (with the count) before dropping profiles that cannot be placed, while the defaultna.rm = TRUEdrops them silently.ssm_plot_circle(repel = TRUE)now gives a clear error when the suggestedggrepelpackage is not installed.The new
geom_ssm_path()layer draws a profile’s movement across occasions as a path on the circumplex canvas, so change in amplitude and displacement reads as motion in circumplex space rather than only as separate parameter panels. Each segment is curved along the circle bycoord_circumplex(). Consecutive occasions are joined the short way around the 0/360 boundary, so a step from 350 to 10 degrees is drawn as a 20 degree arc across the pole rather than a 340 degree sweep the long way round. Occasions are connected in data order, withgroupseparating one series from another and an optionalorderaesthetic to sort within a series; an optionalarrowmarks the direction of time. An occasion with no defined displacement (a flat or zero-amplitude profile) breaks the path rather than being interpolated through, and the segment after the gap is still drawn on the correct branch.ssm_plot_circle()gains apathargument that adds this movement path to its usual points and confidence wedges, for results fromssm_analyze(occasions = )andssm_analyze_long(). Occasions are connected in the order they were supplied, never alphabetically.The new
ssm_plot_trajectory()plots how each SSM parameter changes across occasions, one panel per parameter with its confidence interval as a band, for results fromssm_analyze(occasions = )andssm_analyze_long(). The displacement panel is drawn on an unwrapped branch, so a profile whose displacement crosses the 0/360 boundary is shown as one continuous path instead of jumping a full turn, and each confidence bound is placed on its own estimate’s branch. Occasions appear in the order they were supplied, never alphabetically. An occasion whose amplitude is too close to zero for its displacement to be interpretable is marked with a hollow point, and a profile with no defined displacement leaves a gap rather than a spurious segment.ssm_plot_trajectory()also accepts a trajectory table: a data frame with one row per time point, a numeric time column named by the newtimeargument, anda_est/a_lci/a_uciandd_est/d_lci/d_ucicolumns (optionally thee_*,x_*, andy_*triples and a logicalcertifiedcolumn). This is the shape a model-based workflow assembles by evaluating a fitted growth model at each time point and passing the draws throughssm_draws(), and it is plotted on a continuous time axis, so unequally spaced time points are drawn at their actual spacing. Only the panels the table can fill are drawn. The displacement unwrap, the interval placement, and the hollow marking of uninterpretable time points are shared with the occasions path; when nocertifiedcolumn is supplied, the figure makes no interpretability claim rather than asserting one.New
plot()method forcircumplex_cpmobjects draws the estimated item configuration on theggcircumplex()canvas: each scale appears at its estimated angle and at a radius given by its communality, with a wedge spanning its angle and communality confidence intervals where these are estimable (scales with an inestimable interval are drawn as a point only and named).The amplitude axis labels are now drawn over a translucent backdrop, so they stay readable where a data layer falls behind them. The amplitude axis is drawn on top of the plotted data, which kept the labels visible but not legible: a label crossing a dark marker, an arrowhead, or a dense scatter had too little contrast against it to read. The backdrop is deliberately translucent rather than opaque, so it restores contrast without hiding the data it covers.
Instrument data
- The normative data shipped with
csie,csig,csip,csivandiitchas been re-verified against its published sources, value by value. Every mean, standard deviation and sample size was confirmed correct, as was every item-to-scale assignment its source publishes; no norm value changed. - The source recorded for two of those instruments did change.
csivnow reports its norms as unpublished data from the instrument’s author rather than attributing them to Locke (2000), whose article reports a different sample and publishes no octant statistics. TheURLrecorded forcsieandcsivnow points at the author’s current norms tables; the previous addresses had been redirected to a site homepage. Usenorms()to see the provenance recorded for any instrument. -
?normsnow states that the population shown for a normative sample is a short standardized label chosen by this package, deliberately broader than the description the original source gives. -
norm_standardize()now works withiei. The sample column of the IEI’s normative data had been built so that its two samples were interleaved rather than stacked, which left each sample holding four octants twice and four not at all; standardizing against either IEI sample failed with an error about duplicate angles. No normative value was wrong, and no other instrument was affected. - The reference recorded for the
ieinorms misspelled the second author’s name and now reads Horner, Locke, & Hulsey (2024). - The normative data shipped with
iis32,iis64,ipipipcandischas now been re-verified the same way. Foriis64andisc, every mean, standard deviation and sample size was confirmed correct against the published source, as was every item-to-scale assignment the source publishes. - For
iis32andipipipc, it could not be. Neither instrument’s cited article publishes the octant means and standard deviations the package ships: Hatcher and Rogers (2012) reports no descriptive statistics at all, and Markey and Markey (2009) reports them only for a sample other than the one the package names. No other source for them has been identified. The values ship unchanged, since nothing establishes they are wrong either, but theReferencerecorded for each now says the norms source is unconfirmed instead of crediting an article that does not carry them, and?iis32and?ipipipcsay the same. Standardized scores from these two instruments should be treated as resting on unverified norms. - Four item texts were wrong against their sources and are corrected. In
iis64, item 5 had been truncated to “I realize” and now reads “I realize that I don’t have to be friends with everyone”, and item 7 read “not agreeable with others” where the source prints “not agreeable to others”. Iniis32, item 28 read “I’m ok with not being included in all activities” where its own source prints “okay” (the wording differs between the two IIS articles). Inipipipc, item 16 read “Don’t fall for sob-stories” where the source prints “sob stories”. - The sources cited for
iitcandieiwere recorded as “in press” and now give the published citations. -
caisscores change. The CAIS item-to-scale key was wrong and is corrected. The instrument’s 37 items are not distributed evenly across the eight octants — its source assigns five items each to PA, BC, DE, HI, LM and NO, four to FG and three to JK — but the key shipped four items per octant, which put one Warm-Agreeable item into Unassuming-Ingenuous, one Gregarious-Extraverted item into Warm-Agreeable and one Assured-Dominant item into Gregarious-Extraverted, and left the last five items scored into no scale at all.score()and anything downstream of it therefore returned different values than the instrument’s authors defined, andnorm_standardize()compared those values against norms computed the correct way. Seven of the eight octants change under the correction: PA gains the item that had been scored as Gregarious-Extraverted, BC, DE and HI each gain one of the previously unscored items, JK loses an item, and LM and NO each lose one item and gain two. Only FG (items 4, 12, 20 and 28) is unchanged. Re-run any CAIS analysis. The item text and its ordering were correct throughout. - The normative data shipped with
cais,iei,igicrandiipschas now been re-verified against its published sources the same way as the other nine. Every mean and standard deviation of all nine normative samples was confirmed correct, as was every scale angle the sources publish. Four shipped values did not match their source and are corrected: thecaisitem-to-scale key above, and the three provenance records below. - Three provenance records changed with that verification. The
caissample size for the child sample now reports 204, the sample size printed on the table its means and standard deviations come from, rather than the 213 given for the child sample elsewhere in the same article. Theiipsccollege-student norms were credited to a 2011 publication and now name Hopwood, Pincus, DeMoor, & Koonce (2008), the article that publishes them, which is also the DOI the instrument already recorded;?iipscnow cites both of its normative sources rather than only one. TheieiURLpointed at the study’s data repository, which publishes neither of its normative tables, and now gives one address per sample: the author’s IEI norms page for the undergraduate sample and the article for the community sample. - Every bundled instrument’s item-to-scale key is now checked to cover each of the instrument’s items exactly once, so a key that silently drops or double-counts an item — the
caisfailure above — cannot ship again. - The normative data shipped with
iip32andiip64has now been re-verified against the IIP professional manual, which completes the sweep: every one of the fifteen bundled instruments has now been checked against a published source, though foriis32andipipipcthat check is what established that no source publishes their values. All 96 means and standard deviations of the six IIP normative samples were confirmed correct, as were all 96 item-to-scale assignments and the three IIP-64 sample sizes. No value changed. The manual prints no sample sizes for the IIP-32 specifically, so its 800/400/400 are carried over from the same standardization sample the manual describes for the longer form, which is what the shorter form was scored from. -
?iip32and?iip64now cite the third edition of the manual (Horowitz, Alden, Wiggins, & Pincus, 2003, Mind Garden), the edition the shipped values were verified against, rather than the earlier edition from a different publisher; both help pages also carry the credit line the publisher’s reproduction permission requires for the normative statistics. - The
Populationrecorded for both IIP instruments’ normative samples now describes them as a national standardization sample rather than as community adults, which is what the manual reports: 800 adults sampled to be representative of the U.S. adult population, with separate norms for women and men.
Documentation
The “Evaluating Circumplex Structure” vignette gains a section, When a fit sits at a boundary, on reading a
cpm_fit()solution that sits at or near a parameter boundary — the regime the vignette’s own worked example turns out to occupy. It glosses each of the five boundary and weak-identification markerscpm_fit()records, reads the displayed fit’s Heywood case and its zero-width communality interval, shows a fit whosesummary()prints the fired-marker list, separates what the package’s validation simulations measured from what they did not, and gives four concrete next steps.?summary.circumplex_cpmpoints at it. The section on reading the estimated angles is also corrected: it now says that one scale is held fixed to identify the configuration, and describes the spacing the printed table actually shows rather than calling the departures minor.The “Using Circumplex Instruments” vignette and
?normsnow say precisely what the bundled normative statistics are, instead of implying that a normative sample stands in for a population. The standardizing section characterizes the shipped samples from the instrument objects themselves — the counts are computed in the vignette rather than written down — noting that many are single-study samples of college students, that the IIP-32 and IIP-64 national standardization samples are the exception at one end, and that two of the tables are published in no identified source at the other. It drops a claim that some instruments offer samples matched on nationality (none do; the matched sets are by gender and by age), and it resolves the choice between samples on which group your participants resemble rather than on which sample is larger.?normsnow adds that thePopulationlabel names the group a sample was drawn from rather than a population it was drawn to represent, and points at the vignette.New vignette, “Evaluating Circumplex Structure”: how to test whether an instrument fits a circumplex in your sample with
cpm_fit()(reading and benchmarking the fit indices, comparing the constrained model variants, and the boundary-solution/chi-square cautions from the package’s validation simulations), and how to check whether SSM confidence intervals can be trusted at your sample size and profile withssm_ci_accuracy(). Summarizes Zimmermann & Wright’s (2017) simulation findings as cited context (transcribed from the published article), reproduces their Study 5 analyses on the bundledjz2017data, and adds guidance on when to trust each SSM parameter and on what ipsatizing octant scores costs an SSM analysis. The diagnostic itself was validated against the article: configured to transcribed Zimmermann & Wright simulation conditions, it reproduces their published accuracy classifications (validation scripts and results are recorded in the package’s development repository).New vignette, “SEM-Based SSM Analysis,” teaching the latent SSM: the disattenuated estimand and how it differs from the observed profile, why amplitude and displacement intervals are built in-package rather than by lavaan, the two group-difference estimands (observed vs. invariance-gated latent) side by side, and the model-conditional assumptions that make the latent parameters interpretable.
New precomputed vignette, “Bayesian SSM Analysis,” derives the cosine-regression mapping (pinning the atan2 argument order with an executable known-direction check), walks a brms random-intercept example whose posterior draws ship with the package, and exhibits the Rayleigh-shaped prior that independent (x, y) priors induce on amplitude (brms is a new optional
Suggestsdependency used only by that vignette’s frozen model-fitting chunk).New vignette, “Advanced Circumplex Visualization,” teaches the plotting API:
coord_circumplex()as the owner of the amplitude-to-radius mapping, the configurable circle center and amplitude-axis placement, restyling the canvas throughtheme_circumplex()and ordinarytheme()calls, subclassing the exportedGeomSsmPoint/GeomSsmArcobjects to build reusable layers, and plotting a trajectory across occasions.The reference index now groups the plotting API into “Complete Plots” and “Building Blocks”. The
ssm_plot_*functions cross-link to each other, sossm_plot_trajectory()is reachable from its siblings’ help pages, and the composable layers (ggcircumplex(),coord_circumplex(), thegeom_ssm_*()layers,scale_x_circumplex(), andtheme_circumplex()) likewise cross-link to each other.Clarified in the documentation of
ssm_parameters(),ssm_score(), andssm_analyze()that the reported model fit is a bounded R-squared in[0, 1]for equally spacedangles(more generally, for any angle set satisfying first- and second-harmonic balance); for angle sets violating that balance the closed-form estimator is not a least-squares fit and the reported fit can fall below 0.
Bug fixes
axes_reliability(missing = "fiml")no longer refuses, on Windows only, data it estimates on other platforms. The saturated-stage EM that estimates the standardizing moments now always runs unaccelerated: lavaan 0.7 defaults that stage to SQUAREM acceleration, whose convergence on items with very few observed responses proved platform-sensitive — an item observed 20 times out of 300 estimated cleanly on macOS and Linux but stalled at any iteration cap on Windows, so the same data raised “The saturated (EM) stage did not converge” on one platform and not the others. The package’s iteration cap was calibrated under the unaccelerated EM, so this restores the measured regime rather than adding a new one. Estimates on data with such thinly-observed items may shift within the EM’s own convergence tolerance (differences on the order of 1e-3 in the estimated moments); healthy data is unaffected in both value and speed.norm_standardize()’s refusal of an off-metric normative sample now names the offending scales for every instrument. On the seven instruments whose normative data labels its scale columnAbbrevrather thanScale, the message previously named no scale at all.Asking
norm_standardize()for a normative sample an instrument does not carry now produces an error naming that argument and listing the sample numbers the instrument does carry. Previously the call fell through to an unrelated check and failed with a message aboutscalesnot matching the normative data, which named neither the argument at fault nor a valid value.Fixed a bug where a bootstrap resample under pairwise deletion (
listwise = FALSE) could crashssm_analyze()withmean(): object has no elementswhen the resample happened to draw only missing values for one scale. Such a scale now yields anNAmean (matching the correlation path), and the affected resample is excluded from the confidence intervals as a degenerate profile, consistent with the existing degeneracy handling.Fixed a bug where the displacement of a group contrast between two exactly opposed profiles (a half-turn apart) was reported as
-180degrees instead of+180, inconsistent with the documented(-180, 180]convention for contrasts. Such a contrast is now reported as+180.
Other
-
instruments()now derives its listing from the bundled instrument data rather than a hardcoded table, so it always reflects the instruments actually shipped. As part of this, the listed name for the IIP-SC now reads “Inventory of Interpersonal Problems Short Circumplex” (matching its stored metadata).
circumplex 1.2.0
CRAN release: 2026-07-02
- The SSM plotting functions (
ssm_plot_circle(),ssm_plot_curve(),ssm_plot_contrast()) now warn when given an unrecognized argument (e.g., a misspelled parameter name) instead of silently ignoring it. - Matrix input now works wherever it is documented.
ssm_analyze(),ssm_score(),ipsatize(),score(),norm_standardize(), andself_standardize()previously errored when given a matrix despite advertising matrix support; they now coerce it to a data frame internally. -
ssm_score()now accepts numeric column indexes forscales(e.g.,scales = 1:8), consistent with its documentation and withssm_analyze(); it previously required character names. - Printing an SSM object (via
print()orsummary()) now adds a note under any profile whose model fit is inadequate (R-squared < .70; interpret only elevation) or whose amplitude confidence interval includes zero (the displacement is not interpretable). The notes apply to profiles only, not to contrast rows. - Contrast displacement estimates and their confidence intervals are now always reported on the same angular branch. Previously, for contrasts near ±180 degrees, the estimate (reported in (-180, 180]) could fall numerically outside a confidence interval it was geometrically inside, because the interval was centered on the bootstrap circular mean’s own branch. The interval is now shifted by a full circle when needed (its width and meaning are unchanged; results away from the boundary are identical), so interval endpoints may exceed ±180 degrees when the contrast straddles the boundary.
-
norm_standardize()now matches each scale to its normative data by angular position rather than exact numeric equality, so 0 and 360 degrees are treated as the same angle (previously passing 0 for a scale stored at 360 failed with a cryptic error). An angle with no matching normative row, or with more than one, now produces an informative error naming the available angles. - Degenerate profiles are now handled explicitly instead of returning numerical noise. A flat (zero-variance) profile returns
NAdisplacement and fit with a warning (previously an arbitrary angle and-Inf); a profile with real variance but zero amplitude returnsNAdisplacement and a fit of 0. Bootstrap resamples that produce degenerate profiles (e.g., a resampled measure with zero variance) no longer crashssm_analyze(); they are excluded from the confidence intervals with a warning reporting the count. Genuinely small amplitudes are unaffected — the degeneracy test operates at machine-noise scale only. - Fixed a bug where a missing (
NA) value in thegroupingvariable ofssm_analyze()crashed with a cryptic error under pairwise deletion (listwise = FALSE). Such observations are now dropped before analysis with a message reporting how many were removed, in both deletion modes; if no observations remain, a clear error is given. - Fixed a bug where length requirements on character arguments were never enforced (
is_null_or_char()dropped itsnargument).ssm_analyze()now errors ifmeasures_labelsdoes not match the number ofmeasures(or is given withoutmeasures),ssm_plot_circle()/ssm_plot_curve()now error ifangle_labelsdoes not match the number of angles (previously mismatched labels could be silently recycled onto the wrong scales), andssm_table()/html_render()now requirecaptionto be a single string. - Fixed a bug where
ssm_score()silently ignored itsanglesargument and always usedoctants(): custom angle sets of the same length produced incorrect results without warning, and angle sets of a different length (e.g.,poles()with four scales) errored. Results fromssm_score()with the defaultangles = octants()are unaffected. (found in 2026-07 audit)
circumplex 1.0.1
CRAN release: 2025-07-28
New features
- Add the
self_standardize()function for standardizing variables using sample means and SDs
circumplex 1.0.0
CRAN release: 2024-10-28
Breaking changes
Nearly all code rewritten/refactored to streamline and reduce dependencies.
Removed support for non-standard evaluation
The
contrastargument tossm_analyze()is now TRUE or FALSE instead of “none”, “model”, or “test”. Model contrasts were removed and TRUE yields test contrasts.Many arguments renamed (e.g.,
.datatodata,.ssm_objecttossm_object,xytodrop_xy)Removed
ssm_plot()function in favor ofssm_plot_circle(),ssm_plot_curve(), andssm_plot_contrast().Renamed
standardize()function tonorm_standardize()
New features
Added
ssm_plot_curve()Added CAIS and IEI instrument data
Added profile scores, results, and plotting to models with contrasts
Added
PANO()function for conveniently creating scale namesAll internal and external data are now data frames instead of tibbles
Rewrote all vignettes to use the updated functions, arguments, etc.
Minor improvements and fixes
Harmonized the
resultsandscoresfields in the output ofssm_analyze()Added many unit tests, increasing the package to 100% code coverage
Added many assertions to check for invalid input arguments
Harmonized the tidying function arguments (e.g.,
prefix,suffix,append)Added print methods for degree and radian classes
Replace internal non-standard evaluation with
.datareferencesMinor visual improvements to print and summary methods for ssm_objects
circumplex 0.3.9
CRAN release: 2023-02-14
Minor improvements and fixes
Fixed a bug related to
NaNvalues anddplyr::na_if()Updated package website using new version of {pkgdown}
circumplex 0.3.7
CRAN release: 2021-05-17
New features
Add
angle_labelsargument tossm_plot()to allow users to customize the angle labels around a circular plotAdd
paletteargument tossm_plot()to allow users to customize the color palette (from {RColorBrewer}) of a circular plotReplaced the
font_sizeargument tossm_plot()with thelegend_font_sizeandscale_font_sizearguments to allow users to customize the font size of different elements of a circular plot
Minor improvements and fixes
Update
ggsave()documentation for future compatibilityUpdate {Rcpp} code for future compatibility
Added a black border to the points in a circular plot to greater distinguish them visually
Change CI notation from [] to () to play nice with pandoc
Update to {testthat} 3E and add
ssm_plot()tests using {vdiffr}Recompile vignettes with new version of {roxygen2}
Replace TravisCI with GitHub Actions
circumplex 0.3.4
CRAN release: 2019-12-05
Minor improvements and fixes
Adjust the test of
quantile.radian()to account for changes to%%starting in R 3.6.1 PatchedAdd the name of the package to the S3 class names (e.g.,
circumplex_radianinstead ofradian) to minimize the risk of overlapping classes between packagesAdd some supplementary files to the R build ignore list to avoid notes during CRAN check
circumplex 0.3.2
CRAN release: 2019-08-21
circumplex 0.3.0
CRAN release: 2019-04-26
New features
New
ssm_parameters()calculates SSM parameters (without confidence intervals) from a vector of scores.New
ssm_score()calculates SSM parameters by row.Added support for older versions of R (3.3.x).
Minor improvements and fixes
Updated the “Introduction to SSM” vignette’s figures.
Replaced use of
dplyr::funs()as this function is being deprecated.Fixed a bug in the normative data for
ipipipcthat prevented standardization.Fixed a bug caused by changes in how random numbers are generated in R 3.6.x.
Fixed several broken links by running package through new version of
usethis.Fixed warnings related to documentation inherited from other packages.
circumplex 0.2.1
CRAN release: 2018-11-29
New features
iis32now has normative data.Added open-access (i.e., full item text) to the
iis32andiis64.
Minor improvements and fixes
iis32item ordering and scoring now match the author’s version.iis32response anchors now range from 1 to 6 and match norms.Changed use of
tibblefunctions to avoid problems when new version releases.Removed dependency on
MASSpackage (until it is used by exported functions).
circumplex 0.2.0
CRAN release: 2018-10-26
New features
Added functions and documentation for numerous circumplex instruments.
Added functions for ipsatizing and scoring item-level data.
Added function for standardizing scale-level data using normative data.
Minor improvements and fixes
Changed OpenMP flags in Makevars to fix a compile problem on Debian machines.
Fixed a bug related to calculating angular medians in the presence of NAs.
Changed the default to plot profiles with low fit (but with dashed borders).
Import and export functions from rlang tidy evaluation.
Added unit testing of various functions to increase code coverage.
Redesigned package website to be more attractive and clear.
Updated the SSM vignette to use the
standardize()function.
circumplex 0.1.2
CRAN release: 2018-08-06
