Level: Advanced. Read “Evaluating Circumplex Structure” first.
This vignette explains what a boundary solution in a circular process
model (CPM) fit is, and what to do when one appears. Section 2, “When a
fit sits at a boundary”, refits the jz2017 model that
“Evaluating Circumplex Structure” fits and reads its boundary note. It
then glosses the five markers that cpm_fit() records and
states what the package’s validation measured about them. It ends with
four steps to take when one fires. The Wrap-up lists what the page
covered and names related pages.
This page starts from the fit of the jz2017 octants that
“Evaluating Circumplex Structure” makes. The same call refits it here,
with the same seed and the same two warnings.
data("jz2017")
set.seed(12345)
cpm <- cpm_fit(jz2017, scales = PANO(), angles = octants(), boots = 500)
#> Warning: CPM Hessian is ill-conditioned (condition number 1.83e+14): angles
#> may be clustered or parameters weakly determined.
#> Warning: 11 of 500 bootstrap resamples were excluded (0 with a degenerate or
#> non-positive-definite correlation matrix, 11 failing the convergence
#> acceptance criterion); the confidence intervals are based on the remaining
#> 489 replicates and are conditional on estimability.
summary(cpm)
#>
#> Circular Process Model (Browne, 1992)
#> Model: quasi-circumplex
#> Harmonics (m): 3
#> Sample size (N): 1166
#> Reference scale: PA
#> CI method: bootstrap
#> Confidence level: 0.95
#>
#> # Estimated angles and communality indices
#>
#> Scale Theory Angle lci uci Zeta lci uci
#> PA 90 90.000 90.000 90.000 0.767 0.684 0.860
#> BC 135 125.074 113.548 137.069 0.931 0.872 1.000
#> DE 180 170.353 156.943 185.174 0.780 0.733 0.826
#> FG 225 195.425 185.811 206.523 0.861 0.822 0.910
#> HI 270 250.721 244.660 258.758 0.956 0.935 0.977
#> JK 315 269.491 261.813 279.412 0.942 0.929 0.957
#> LM 360 294.230 286.626 303.019 0.806 0.761 0.850
#> NO 45 11.305 2.426 20.913 1.000 1.000 1.000
#>
#> # Correlation-function weights
#>
#> k Beta Beta_lci Beta_uci
#> 0 0.450 0.418 0.481
#> 1 0.440 0.411 0.466
#> 2 0.074 0.065 0.087
#> 3 0.036 0.015 0.057
#>
#> # Fit indices
#>
#> χ²(10) = 81.169, p = <1e-04
#> RMSEA = 0.078 [0.063, 0.094] (90% CI)
#> SRMR = 0.042
#> CFI = 0.984 TLI = 0.956
#> AIC = 117.169 BIC = 208.273
#>
#> # Residuals
#>
#> Largest absolute residual: 0.134 (PA – HI)
#>
#> # Diagnostics
#>
#> Note: a communality index reached its upper boundary (ζ > 0.995, a
#> Heywood-type solution).
#> Note: 11 of 500 bootstrap resamples were excluded (0 degenerate, 11
#> non-convergent); the intervals are based on 489 replicates and are
#> conditional on estimability.
#>
#> Note: boundary/weak-identification markers fired: Heywood communality;
#> small correlation-function weight; ill-conditioned Hessian.
#> What has been measured about these markers covers analytic intervals only,
#> and not every marker was measured; they are not validated as predictors of
#> the bootstrap intervals shown here (see the vignette section 'When a fit
#> sits at a boundary').The fit above sits at a boundary, and it says so. Its
# Diagnostics block prints a note that a communality index
reached its upper boundary. The results table shows which one: the NO
scale’s communality index is 1.000, with a confidence interval of
[1.000, 1.000]. A zero-width interval is not a precise estimate. It is
an absent one. Both endpoints equal the estimate. So at least the middle
95% of the retained bootstrap resamples also sat on the boundary. That
left the percentile interval no width to report. The same chunk also
emits a warning that the fit’s Hessian is ill-conditioned, a second
signal from the same regime. When octant scales share a strong general
factor, as interpersonal problem scales do, fitted solutions frequently
sit at or near a parameter boundary. This is the estimator meeting real
data at finite \(n\), not a data-entry
error.
cpm_fit() records five such markers, and
summary() names the ones that fired:
summary() prints that list when markers fire, but on the
analytic path it prints the list only within a sample-size window. On
the bootstrap path, as in the fit displayed above, the list prints as a
descriptive note at every sample size. Beside the note, a statement says
that what has been measured about the markers covers analytic intervals
only. It also says that not every marker was measured. What a fired
marker does and does not tell you, below, has the particulars. So
the markers are not validated as predictors of the bootstrap intervals
they accompany. When the intervals are analytic, the list instead
arrives inside a coverage caution. That caution prints when a marker
fires and \(N\) is at least 2000 and
below 50000. Below 2000, an unconditional caution prints without the
list. At 50000 and above, neither prints. To see that caution-embedded
form, we simulate a larger sample from the structure just estimated with
cpm_simulate(). Then we refit it on the correlation-matrix
path, where the intervals are analytic.
set.seed(2026)
sim <- cpm_simulate(cpm, n = 2500) # draws from the structure estimated above
demo <- cpm_fit(
cormat = cor(sim), scales = colnames(sim), angles = octants(), n = 2500
)
summary(demo)
#>
#> Circular Process Model (Browne, 1992)
#> Model: quasi-circumplex
#> Harmonics (m): 3
#> Sample size (N): 2500
#> Reference scale: PA
#> CI method: analytic
#> Confidence level: 0.95
#>
#> # Estimated angles and communality indices
#>
#> Scale Theory Angle lci uci Zeta lci uci
#> PA 90 90.000 90.000 90.000 0.785 0.748 0.823
#> BC 135 125.057 120.571 129.542 0.881 0.852 0.911
#> DE 180 167.802 161.988 173.616 0.799 0.777 0.820
#> FG 225 193.344 187.711 198.976 0.878 0.854 0.901
#> HI 270 250.958 245.112 256.805 0.958 0.944 0.971
#> JK 315 269.528 263.351 275.706 0.948 0.938 0.958
#> LM 360 293.619 286.622 300.617 0.803 0.778 0.829
#> NO 45 12.673 7.017 18.329 0.929 0.836 1.023
#>
#> # Correlation-function weights
#>
#> k Beta Beta_lci Beta_uci
#> 0 0.442 0.422 0.462
#> 1 0.453 0.436 0.470
#> 2 0.068 0.058 0.078
#> 3 0.037 0.030 0.044
#>
#> # Fit indices
#>
#> χ²(10) = 2.229, p = 0.994
#> RMSEA = 0 [0, 0] (90% CI)
#> SRMR = 0.005
#> CFI = 1 TLI = 1.002
#> AIC = 38.229 BIC = 143.062
#>
#> # Residuals
#>
#> Largest absolute residual: 0.012 (FG – NO)
#>
#> Note: this solution is near a parameter boundary or weakly identified
#> (small correlation-function weight); analytic (Wald) confidence intervals
#> mis-covered for such fits in validation even at N in the tens of thousands.
#> Interpret them with caution and prefer the bootstrap on the raw-data path
#> when available.One marker fires here, a small correlation-function
weight, and summary() names it. Notice what did
not happen. The population that this sample was drawn from
carries NO’s communality at the boundary, yet this draw produced no
Heywood case. A boundary in the population does not guarantee a boundary
in every sample from it. In the same way, a quiet fit does not guarantee
that there is no boundary behind the data.
A lower discrepancy beside the fit.
cpm_fit() reports the lowest discrepancy that its starts
from the theoretical angles reach (the cpm_fit() help
section “Which solution is reported”). It then runs a fixed set of extra
starts, most of them from that solution. If one reaches a discrepancy
lower by more than 0.01 on the \(\chi^2\) scale, print() and
summary() add a note that begins “extra starts reached a
solution whose \(\chi^2\) is lower by”.
The note is not one of the five markers, and the analytic caution does
not read it. It changes nothing that the fit reports: the estimates, the
fit indices and the intervals stay those of the reported solution. What
it tells you is that the reported \(\chi^2\) is larger than at the lower
solution, so at the same degrees of freedom the \(p\)-value is smaller and RMSEA and CFI are
no better than they would be there. It also says whether the lower
solution keeps the scales’ order around the circle, and how many more or
fewer communality indices it has above 0.995. The package’s simulations
covered normal data at two truths of eight octant scales with three
harmonics. There, such lower solutions estimated the true angles and
weights worse on average than the reported one, though not on every fit.
Neither fit above prints the note. A fit with no note can still have a
lower discrepancy, because the extra starts do not find every one. The
check runs only for the default "quasi-circumplex" model
with unit scaling, and check_lower = FALSE turns it
off.
What a fired marker does and does not tell you. The package’s validation simulations measured what these markers predict, and the measurement is narrower than the markers are. It covered analytic (Wald) intervals fitted from a correlation matrix. That marker study measured interval coverage (how often an interval contained the truth), not bias in the point estimates.
The record does not support four things, and no reading of these markers should assume them. First, the marker study measured nothing about bias in the point estimates. Second, the marker study fitted analytic intervals only. So the markers are not validated as predictors on the bootstrap path that this vignette uses by default. Third, the boundary harmonic removed marker showed no evidence of predicting mis-coverage at all. Fits carrying it covered as well as fits without it. It is kept because it names a real feature of the solution, not because it forecasts trouble. Fourth, the numbers behind Heywood communality, ill-conditioned Hessian and competing near-tied optima come almost entirely from one simulated configuration, chosen deliberately to provoke them. In that configuration, those markers fire often enough to measure. Even there, near-tied optima fired only rarely. Read them as what happens where those markers fire, not as how often they fire.
What to do when one fires.
"quasi-circumplex" model is the least constrained of the
variants that “Evaluating Circumplex Structure” compares. So a boundary
that appears only under a constrained variant was put there by the
constraint. A boundary already present in the default fit belongs to the
data and the model family, not to an assumption you added. If you fitted
from a correlation matrix, refitting from raw data also buys you
bootstrap intervals in place of analytic ones.RMSEA = 0 [0, 0], which is
not that failure. It is a fit statistic at its own floor, because the
refit recovers almost exactly the structure it was simulated from.)A boundary solution is the estimator meeting real data, and a fired marker is a finding to locate, refit, report and keep in view. No page follows this one. “Evaluating Circumplex Structure” compares the model variants that a refit chooses among. “Confidence Interval Accuracy” asks whether the intervals of an SSM analysis can be trusted at your sample size.