Changelog
All notable changes to openpls-engine. The format follows Keep a Changelog and the project adheres to Semantic Versioning.
The public API is stable as of 1.0.0. Tagged releases trigger a GitHub Actions workflow that builds the package and publishes it to PyPI via OIDC trusted publishing.
Unreleased
1.5.0
Released 2026-06-10.
Adds the canonical pre-MGA measurement-invariance check so engine users can verify that composite constructs are comparable across two groups before interpreting group differences. Single additive feature, no existing behaviour changes.
Added
-
MICOM â Measurement Invariance of Composite Models (Henseler, Ringle and Sarstedt 2016) via
Plspm.micom(data, grouping_column, group_a, group_b, iterations=1000, seed=42). Three-step procedure:- Step 1 â Configural invariance is guaranteed by construction: the same
Configis reused for both groups (audit trail viaMICOM.group_sizes()). - Step 2 â Compositional invariance. Per construct, computes
c = w_A' ÎŁ w_B / sqrt((w_A' ÎŁ w_A)(w_B' ÎŁ w_B))between the group-A and group-B weight vectors evaluated on the pooled indicator covariance, then testsH_0: c = 1with a one-sided lower-tail permutation test (the permutation distribution clusters near1under the null; an observedcdeep in the lower tail rejects invariance). Sign indeterminacy is handled by aligning each permutationâs weight direction before computingc. - Step 3 â Equality of composite means and variances. Applies pooled-fit weights to standardized indicators to produce common-scale composite scores; mean differences and
log(var_A / var_B)are then tested with two-sided label-shuffling permutations. Step 3 reuses the pooled weights inside each iteration instead of refitting PLS, so it is far cheaper than Step 2. MICOM.summary()collapses the three steps into a per-construct verdict â"full"(Step 2 + Step 3 pass),"partial"(Step 2 passes but mean or variance differs), or"none"(Step 2 fails â composites are not comparable and MGA results would be uninterpretable). Step 2 and Step 3 are also exposed individually viastep2()andstep3().
Closes a longstanding gap: prior releases supported
Plspm.mga(...)but provided no in-engine way to verify the invariance prerequisite MGA assumes. - Step 1 â Configural invariance is guaranteed by construction: the same
1.4.0
Released 2026-06-09.
Five seminr-aligned additions covering one-call reporting, predictive accuracy, structural effect sizes, discriminant validity, and mediation decomposition. All APIs are additive â no existing behaviour or signatures changed â so this is a minor bump.
Added
- Publication-ready summary report via
Plspm.report(include_rho_a=True, include_htmt2=True). Bundles the engineâs individual diagnostics â reliability (Cronbach alpha, rho_A, rho_C, AVE), discriminant validity (HTMT, HTMT2, Fornell-Larcker), structural paths with f² effect sizes and p-values, per-LV R² / adjusted R² / BIC, fit indices (SRMR, d_ULS, GoF), and outer/inner VIF â into a singleReportobject covering the standard PLS-SEM research-report panels expected by Hair, Hult, Ringle and Sarstedt (2022, A Primer on PLS-SEM, 3rd ed.).Report.to_dict()returns every section in one dictionary, ready for JSON export. Pure orchestration: every value comes from an existing lazy-cached method onPlspm, so callingreport()repeatedly is cheap. The two flags trade speed for completeness: turn offinclude_rho_ato skip PLSc; turn offinclude_htmt2to skip the geometric-mean HTMT refinement. - PLSpredict full panel (Shmueli et al. 2019) via the existing
Plspm.predict(...)method.metrics()now reports per-indicator MAPE for PLS and LM (proportion-form,mean(|err / actual|), matching sklearnâs convention) alongside RMSE and MAE, and adds the in-sample counterpartsrmse_pls_in,mae_pls_in,mape_pls_in,rmse_lm_in,mae_lm_in,mape_lm_incomputed from a single fit on the full data. The complete out-of-sample / in-sample comparison is what reviewers expect to see published (Shmueli, Sarstedt, Hair, Cheah, Ting, Vaithilingam and Ringle 2019, Table 6). MAPE rows where the actual value is zero are excluded from MAPE only; all other metrics still see them. Existing columns andsummary()are unchanged. - Cohenâs f² effect size via
Plspm.f_squared(). For each structural-model edgepredictor -> endogenous, refits the endogenous LVâs OLS without the predictor and reportsf² = (R²_full - R²_reduced) / (1 - R²_full)(Cohen 1988; Hair, Hult, Ringle & Sarstedt 2022). ReturnedFSquaredinstance exposes a long-formattable()with conventional effect-size labels (none/small/medium/large, thresholds 0.02 / 0.15 / 0.35) and a squarematrix()mirroring the path matrix. Computed lazily and cached. - Fornell-Larcker discriminant-validity criterion via
Plspm.fornell_larcker(). Returns a square matrix withsqrt(AVE)on the diagonal and inter-construct correlations off-diagonal (Fornell and Larcker 1981).summary()produces a per-LVpassesverdict (Truewhensqrt(AVE)exceeds every absolute off-diagonal entry in its row). Formative (Mode B) and single-indicator LVs receiveNaNon the diagonal because AVE is undefined for them, andsummary()flags them as non-applicable. The modern recommendation (Henseler, Ringle and Sarstedt 2015) is to prefer HTMT for discriminant validity; Fornell-Larcker is provided alongside, not in place of, HTMT. Computed lazily and cached. - Specific indirect effects for mediation analysis via
Plspm.specific_indirect_effects(source, target, through=None)for point estimates andBootstrap.specific_indirect_effects(source, target, through=None, alpha=0.05)for bootstrap percentile CIs. Implements the chain-product procedure of Zhao, Lynch and Chen (2010) and Nitzl, Roldan and Cepeda (2016): each mediation chainsource -> M1 -> ... -> targetcarries an effect equal to the product of its path coefficients, and the per-iteration distribution of that product gives the inferential statistics. Withthrough=Nonethe structural-model DAG is searched for every simple chain fromsourcetotarget; withthroughset, only that single chain is evaluated. Aligns the engine withseminr::specific_effect_significance().
1.3.0
Released 2026-06-09.
Disjoint two-stage higher-order construct (HOC) workflow as a first-class API. The legacy Config.add_higher_order (repeated-indicators / embedded two-stage) stays untouched for backward compatibility, so this is a minor bump.
Added
-
Disjoint two-stage higher-order constructs (HOC) via
Plspm.higher_order(name, first_order, mode, structure, ...). Implements the workflow recommended in Sarstedt, Hair, Cheah, Becker and Ringle (2019) and Hair, Hult, Ringle and Sarstedt (2022, A Primer on PLS-SEM, 3rd ed., Chapter 8). The fittedPlspmbecomes stage 1; its first-order LV scores are appended to the data as indicators of the new second-order construct, and a stage-2Plspmis fit with the HOC in place of its first-order constituents in the structural part of the model. All four canonical HOC types are covered by combining the first-order LV modes (set on the base config) with the HOCmode:- Type I (R-R) â first-order Mode A, HOC Mode A.
- Type II (R-F) â first-order Mode A, HOC Mode B.
- Type III (F-R) â first-order Mode B, HOC Mode A.
- Type IV (F-F) â first-order Mode B, HOC Mode B.
The returned
HigherOrderinstance exposes the stage-1 fit (base()), the stage-2 fit (refit()), the HOCâs measurement model (loadings()), the stage-2 structural path coefficients (path_coefficients()), and a per-first-order summary table. Nested HOCs work by callinghigher_order()again on the stage-2 refit.
1.2.0
Released 2026-06-09.
Three seminr-aligned diagnostics for measurement-error correction, discriminant validity, and structural-equation endogeneity. All three APIs are additive â no existing behaviour or signatures changed â so this is a minor bump.
Added
- Gaussian-copula endogeneity test via
Plspm.copula(endogenous, suspected=None, n_boot=500, seed=42). Park and Gupta (2012) / Hult, Hair, Proksch, Sarstedt, Pinkwart and Ringle (2018) procedure for detecting endogeneity in PLS-SEM structural equations. For the structural equation ofendogenous, each suspected predecessor LV is augmented with a copula termP_k = ÎŚâťÂš(F_n(X_k))(F_n(x) = rank(x) / (n+1)), the augmented OLS regression is refit on the latent-variable scores, and each copula coefficientÎł_kis tested by a non-parametric row bootstrap (sameSE/t/pconvention asLongBootstrap). Each suspected predictor is screened with a CramĂŠr-von Mises normality test against its sample-fitted normal;summary()marks normal predictors ascopula not admissible (normal)because under normality the copula term degenerates and the test cannot tell endogeneity from a Gaussian regressor. Returns per-predictorgamma,boot_se,t,p_value,cvm_p_nonnormal, the endogeneity-correctedaugmented_paths, and a verdict column. - HTMT2 via
Plspm.htmt2(). Geometric-mean refinement of the Heterotrait-Monotrait Ratio of Correlations (Roemer, Schuberth and Henseler 2021). Replaces the two arithmetic means in the original Henseler/Ringle/Sarstedt 2015 HTMT with geometric means (exp(mean(log(¡)))), removing the bias HTMT shows when indicator loadings within a block are unequal. HTMT2 is consistent under the tau-equivalent / congeneric measurement model. Same API surface asHTMTâmatrix()returns the symmetric matrix andpairs()returns the long-format view. Pairs involving a single-indicator construct or any zero indicator correlation are returned asNaN(the geometric mean is undefined in those cases). The same conservative discriminant-validity thresholds apply (HTMT2 < 0.85/< 0.90). - Consistent PLS (PLSc) via
Plspm.plsc(). Applies the Dijkstra and Henseler (2015) bias correction for reflective (Mode A) measurement: each construct receives a closed-formrho_Areliability(w'w)² ¡ w'Sw / w'(ww' â diag) w, off-diagonal construct correlations are dis-attenuated bysqrt(rho_A_i ¡ rho_A_j), and path coefficients are re-estimated by OLS on the adjusted correlation matrix. CorrectedR², adjustedR², and outer loadings (Îť_k = w_k ¡ sqrt(rho_A) / (w'w)) are returned side by side with the composite-model originals. Formative (Mode B) and single-indicator constructs receiverho_A = 1by convention and are not adjusted. Aligns withseminr::PLSc().
1.1.0
Released 2026-06-09.
Two seminr-aligned outer-model diagnostics. Both APIs are additive â no existing behaviour or signatures changed â so this is a minor bump.
Added
- Variance Inflation Factor (VIF) diagnostics via
Plspm.vif(). Two views:items()returns per-indicator VIF within each construct block (collinearity diagnostic primarily for Mode B / formative blocks; for each indicatorx_jin a block with two or more indicators,x_jis regressed on the remaining indicators andVIF_j = 1 / (1 - R²_j)), andinner()returns per-predictor VIF for each endogenous LV (structural collinearity among antecedents â each predictorâs score is regressed on the other predictorsâ scores). Single-indicator blocks and single-predictor endogenous LVs are omitted (VIF undefined or trivially1). Aligns the engine withseminr::vif_items(). - Confirmatory Tetrad Analysis (CTA-PLS) via
Plspm.cta(n_boot=500, alpha=0.05, seed=42). Outer-model diagnostic that tests whether reflective (Mode A) specification is consistent with the data, per block of four or more indicators, using Bollen and Tingâs (1993) vanishing-tetrad theorem and the bootstrap procedure of Gudergan, Ringle, Wende and Will (2008). One canonical tetrad per indicator 4-tuple givesC(p, 4)non-redundant tetrads per block; each is bootstrapped to obtain a two-sided percentile p-value underHâ: Ď = 0(after centering the bootstrap distribution on zero), then reduced with a within-block Holm step-down correction atalpha.tetrads()returns the per-tetrad table;summary()returns the per-block verdict ("reflective supported"vs"reflective rejected"). Mode B blocks and reflective blocks with fewer than four indicators are omitted.
1.0.2
Released 2026-06-01.
Test-suite hardening release. No API changes, no runtime behaviour changes â 1.0.2 is binary-identical to 1.0.1 at runtime.
Added
- Scheme-equivalence regression test (
tests/test_scheme_equivalence_two_lv.py). On any two-LV model each LV has exactly one neighbour, so the inner-weighting update degenerates and PATH, CENTROID, FACTORIAL, PCA, and NEWTON must produce identical path coefficients, weights, loadings, and R². The test locks this invariant down to< 1e-6. - Redundancy-analysis regression test (
tests/test_redundancy_analysis_mode_b.py). A Mode B (formative) driver block predicting a single-item global rating LV. Asserts path recovery is positive and within a sampling band across seeds, R² lies in the expected attenuated range, and the single-indicator loading is exactly1.0. Also parametrized across inner schemes for the degenerate two-LV case. - Path-recovery regression test (
tests/test_path_recovery_synthetic.py). Three-LV mediation chainX â M â Ywith known structural coefficients. Asserts the engine recovers direct paths, indirect / direct / total effects, and the population R² onYwithin sampling tolerance over multiple seeds.
Changed
- Internal docstring and comment phrasing in
openpls/config.py,openpls/fit.py,tests/test_fit.py, andtests/test_sign_convention.pynow reference the underlying methodological convention (Henseler et al. 2014 §5.3, Hair et al., Wold) directly. No code behaviour change.
1.0.1
Released 2026-06-01.
Two SmartPLS-parity fixes discovered while validating 1.0.0 against 14 reference cases. No API changes.
Fixed
- Per-LV sign vote in
_MetricWeights.calculate(). The previous implementation computednp.sign(cor * odm)first and applied the membership mask after. Becausenp.sign(0) == 0butmath.copysign(1.0, 0) == +1.0for the per-cell variant, every non-belonging indicator contributed a phantom+1to the LVâs sign vote. A small LV (e.g. 3 indicators) embedded in a much larger model could therefore be out-voted by the phantom contributions of the larger LV, leaving it on the wrong sign even when every one of its own indicators correlated negatively with the latent direction. The sign is now computed first and multiplied by the membership mask, so non-belonging cells contribute0rather than+1. Empirical impact: the OI validation cases now match SmartPLS onOrg_Ident â AC_Love(β was+0.41versus SmartPLSâ0.41). - Saturated-model SRMR and d_ULS exclude within-LV pairs for Mode B (formative) constructs. The implied indicator-correlation matrix
ÎŁĚ = Î ÎŚ Îáľonly constrains common-factor (Mode A) measurement. Mode B indicators are exogenous causes of the composite, so their pairwise correlation is empirical, not implied byÎᾢ Îâąź. Including those pairs in the SRMR and d_ULS sums inflated both metrics purely as a measurement-model artifact (Henseler et al. 2014 §5.3, SmartPLS convention). Fit now builds an inclusion mask that excludes within-Mode-B-LV blocks and aggregates over the kept pairs only. Models without Mode B LVs are unaffected. Empirical impact: the Corporate Reputation Advanced d_ULS gap closes from+0.5104toâ0.0004.
Added
- Regression test
tests/test_sign_convention.pyconstructing a 14-indicator LV alongside a 3-indicator LV whose indicators are all inverted; pins the sign-vote behaviour against the old phantom-vote bug. - Regression test
tests/test_fit.py::test_mode_b_within_lv_pairs_excluded_from_fitasserting that the within-Mode-B residual block is masked out of both SRMR and d_ULS sums by exact arithmetic identity.
1.0.0
Released 2026-06-01.
First stable release. The API surface (Plspm, Config, Mode, Scheme, IPMA, PLSPredict, Moderation, FIMIX) now follows semver: breaking changes require a major version bump.
Changed
- Namespace renamed
plspmâopenpls. All imports change shape:from plspm import Plspmbecomesfrom openpls import Plspm,import plspm.config as cbecomesimport openpls.config as c, and similarly foropenpls.mode,openpls.scheme,openpls.mga,openpls.fimix,openpls.ipma,openpls.moderation,openpls.predict,openpls.long_bootstrap. The distribution name on PyPI (openpls-engine) is unchanged. Consumers upgrading from0.7.xmust rewrite their imports. - Column-wise standardization. Indicators are now standardized per column with Bessel-corrected variance (ddof=1), matching SmartPLS 4 conventions. The previous pooled-stack standardization is gone. This shifts numerical alignment closer to SmartPLS 4 for mixed-scale indicator blocks. Path coefficients and quality criteria can move by a few percent on existing models.
setup.pyshim removed. Project metadata is fully driven by PEP 621pyproject.toml. Source installs should use a modernpip(pip install -e .continues to work).
Fixed
- LV name may equal an indicator column name.
add_lv()no longer rejects configurations where a latent variable shares its name with one of its manifest variables (the ECSICUSCOsingle-item LV pattern). Internal LV and MV namespaces are distinct, so the collision was a false positive.
0.7.0a3
Released 2026-05-30.
Second feature release. Ships four advanced PLS-SEM analyses (IPMA, PLSpredict, two-stage moderation, FIMIX-PLS) and two additional inner-weighting schemes (Newton/BFGS and LohmĂśllerâs PCA), filling the gap between the original plspm-python API and mainstream commercial PLS-SEM tools.
Added
Scheme.PCA: LohmĂśllerâs PCA inner-weighting scheme (LohmĂśller 1989, Section 2.4.2). For each LV, the inner weights are the components of the first principal direction of its neighbor-score matrix, sign-flipped to correlate positively with the LV. Treats neighbor weights as a joint multivariate direction rather than as pairwise quantities.Scheme.NEWTON: quasi-Newton (BFGS) inner-weighting scheme. For each latent variable, jointly fits inner weights over all neighbors (predecessors and successors together) via BFGS minimization of a least-squares objective, in contrast to the classical PATH scheme, which mixes OLS coefficients for predecessors with bare correlations for successors. Initialized from the analytical OLS solution; usesscipy.optimizefor the second-order Hessian-secant update.openpls.fimix.FIMIX: Finite Mixture PLS (Hahn et al. 2002) for latent class segmentation. EM algorithm with multiple random restarts detects K subgroups sharing the measurement model but with distinct structural paths. Reports per-class path coefficients, posterior memberships, hard assignments, and information criteria (AIC, AIC3, AIC4, BIC, CAIC, MDL5, normalized entropy EN). Exposed asPlspm.fimix(n_classes).openpls.ipma.IPMA: Importance-Performance Map Analysis. For a chosen target endogenous LV, returns each predecessorâs importance (total effect) and performance (mean of 0-100-rescaled LV score), plus an indicator-level breakdown with rescaled-mean performance and normalized weights. Exposed asPlspm.ipma(target).openpls.moderation.Moderation: two-stage moderation (Henseler and Chin 2010). Fits a base model, multiplies the standardized LV scores for predictor and moderator into a product column, and refits with that product as a single-indicator construct pointing at the target. Exposesbase(),refit(), andinteraction_effect().openpls.predict.PLSPredict: PLSpredict via k-fold cross-validation. Per-indicator RMSE/MAE for PLS and a linear-regression benchmark, plus Q squared predict against the indicator-average baseline (Shmueli et al. 2019). Exposed asPlspm.predict(k=10, repeats=1, seed=42);summary()returns the per-indicator PLS-vs-LM verdict.
0.7.0a2
Released 2026-05-30.
First release published to PyPI. Identical code to 0.7.0a1; bumped only to validate the trusted-publisher pipeline end to end. The previous v0.7.0a1 GitHub release stays available as a download but was never uploaded to PyPI.
0.7.0a1
Released 2026-05-30.
All planned ports from the OpenPLS web app are now in. This is the first feature-complete pre-release.
Added
openpls.long_bootstrap.LongBootstrap: serial bootstrap with progress callback, sign-flipping, BCa percentile CIs, normal-approximation p-values, and a configurable success-rate floor. Suited for long-running, progress-streaming workloads.openpls.mga.MGAandopenpls.mga.GroupSpec: Multi-Group Analysis via Henseler permutation, with categorical and numeric-range group definitions, pairwise comparisons across 2+ groups, two-sided permutation p-values with Phipson-Smyth add-one smoothing.Plspm(..., missing_strategy="mean"): mean replacement for NaN cells in indicator columns. Default"casewise"preserves upstream behavior.openpls.q_squared.QSquared: Stone-Geisser Q squared via blindfolding with configurable omission distance D. Exposed asPlspm.q_squared().openpls.htmt.HTMT: Heterotrait-Monotrait ratio of correlations.openpls.fit.ModelFit: SRMR (Standardized Root Mean Square Residual) and d_ULS (unweighted least-squares discrepancy).- BIC for endogenous LVs in
openpls.inner_summary. - Listwise-deletion fallback for Cronbach alpha and Dijkstra-Henseler rho when an LVâs indicator block contains NaN.
openpls.__version__reports the installed package version at runtime.
Changed
- Project metadata moved from
setup.pyto PEP 621pyproject.toml. - Lint pipeline (ruff) and test pipeline (pytest) run on Python 3.10 through 3.13 in CI.
0.6.0a1
Released 2026-05-30.
Initial OpenPLS rebrand of the plspm-python 0.5.7 baseline.
Added
- Forked
plspm-python0.5.7 with attribution preserved. pyproject.toml, ruff config, GitHub Actions CI matrix (Py 3.10 to 3.13).