Polytomous Variable Latent Class Analysis in C++ with Python bindings
Project description
pypoLCA
Polytomous variable latent class analysis (LCA) for Python, powered by a C++17 backend. pypoLCA is a translation of R's poLCA package by Drew Linzer and Jeffrey Lewis.
What is latent class analysis?
LCA is a statistical method that discovers latent (unobserved) categorical variables from a set of nominal responses. The core assumption is that observed responses are mutually independent conditionally on the latent variable — all dependencies between responses flow through the latent structure.
The model identifies two things:
- The underlying latent classes (e.g., "high-risk" vs. "low-risk" respondents), and
- The conditional probabilities of each observed response given each class.
LCA's latent variables are categorical (e.g., class 1 = "non-cheaters", class 2 = "chronic cheaters"). This makes LCA the categorical-data analogue of Gaussian Mixture Models (GMM): GMM assumes normally-distributed responses, LCA assumes multinomial responses. Both fit parameters via Expectation-Maximisation (EM). A good tutorial extending EM beyond standard GMM applications is Gao (2022).
Applications
- Diagnostic agreement — LCA estimates rater accuracy without a gold-standard reference. Applied to carcinoma diagnoses by seven pathologists (Uebersax & Grove, 1990; dataset:
carcinoma). - Political typology — LCA identifies voter segments from candidate trait ratings. Applied to 2000 ANES survey data (dataset:
election). - Academic dishonesty — Latent classes of cheating behavior among students, regressed on GPA covariates (dataset:
cheating). - Survey attitude clustering — Uncovering latent opinion groups from social survey responses (McCutcheon, 1987; dataset:
gss82).
Latent class regression
LCA can be extended with covariates that predict class membership. pypoLCA fits latent class regression using the same hybrid EM / Newton-Raphson algorithm as R's poLCA. The EM loop alternates expected-posterior and maximisation steps. Response probabilities have a closed-form M-step, which guarantees the standard EM ascent property. Covariate coefficients lack a closed form and are updated within each M-step via Newton-Raphson (NR). Unlike pure EM, the NR step can overshoot and cause a likelihood drop. The implementation detects this and restarts with perturbed starting values (max_restarts). In any case, the algorithm finds only a local maximum, so multiple random starts (nrep) are recommended.
Standard errors are provided for all parameter estimates (i.e. conditional response probabilities, prior class probabilities, and (when covariates are present) regression coefficients). SEs are computed from the observed information matrix via the outer product of the individual score contributions, then transformed to probability space via the delta method. This matches the approach used by R's poLCA.
Install
pip install pypolca
Until published on PyPI, install from source:
git clone https://github.com/.../pypoLCA.git cd pypoLCA uv pip install -e ".[dev]"
Quick start
import pypolca as lca
# Load a built-in dataset (a Polars DataFrame)
df = lca.load_dataset("carcinoma")
# Fit a 2-class model — seven pathologists rating 118 slides
result = lca.fit("cbind(A, B, C, D, E, F, G) ~ 1", data=df, nclass=2, nrep=5)
# Inspect results
print(f"Log-likelihood: {result.loglik:.2f}")
print(f"AIC: {result.aic:.2f}")
print(f"Iterations: {result.iterations}")
# Class-conditional probabilities for the first item
print(result.probs[0]) # shape (nclass, n_categories)
# Posterior class membership (N × R)
print(result.posterior[:5])
# Predicted class for each observation (1-based)
print(result.predclass[:5]) # 1-based (matching R poLCA convention)
# Standard errors
print(result.probs_se[0]) # SEs for first item
print(result.P_se) # SEs for class priors
With covariates (latent class regression):
df = lca.load_dataset("cheating")
# Cheating behaviours ~ GPA
result = lca.fit(
"cbind(LIEEXAM, LIEPAPER, FRAUD, COPYEXAM) ~ GPA",
data=df,
nclass=2,
nrep=10,
)
print(result.coeff) # Regression coefficients (covariates × (classes − 1))
print(result.coeff_se) # Standard errors
print(result.P) # Population class shares
The formula syntax uses cbind(var1, var2, ...) on the left-hand side, or equivalently Python-style var1 + var2 + .... The right-hand side is ~ 1 for intercept-only or ~ cov1 + cov2 for latent class regression. Parsing is handled by a lightweight custom parser (no external formula library).
Backend
The EM engine and standard error computation are written in C++17 (Eigen for linear algebra), exposed to Python via pybind11. The build system is CMake + scikit-build-core, managed by uv. Incremental C++ rebuilds take ~1–2 s with ./rebuild.sh.
Benchmarks
Comparison of pypolca (C++, with/without SE) vs R's poLCA on the cheating dataset (N=319, 4 binary items, 2 classes). Timings are means over 20 runs.
| N | Items | Classes | R poLCA | pypolca (with SE) | Speed-up | pypolca (no SE) | Speed-up |
|---|---|---|---|---|---|---|---|
| 319 | 4 | 2 | — | — | — | — | — |
| 500 | 5 | 2 | — | — | — | — | — |
| 2,000 | 5 | 2 | — | — | — | — | — |
| 10,000 | 5 | 2 | — | — | — | — | — |
Results TBD — run
python scripts/benchmark.pyandpython scripts/benchmark_scaling.pyto populate with fresh numbers (requires R withpoLCAandjsonliteinstalled). Speed-up is relative to RpoLCA.
Datasets
| Dataset | N | Manifest items | Covariates | Source |
|---|---|---|---|---|
| carcinoma | 118 | A–G (7 binary: no carcinoma / carcinoma) | — | Agresti (2002) |
| cheating | 319 | LIEEXAM, LIEPAPER, FRAUD, COPYEXAM (4 binary) | GPA | R poLCA |
| election | 1,785 | MORALG–INTELB (12 ordinal: 4-point trait ratings) | VOTE3, AGE, EDUC, etc. | 2000 ANES |
| gss82 | 1,202 | PURPOSE, ACCURACY, UNDERSTA, COOPERAT (2–3 categories) | — | McCutcheon (1987) |
| values | 216 | A–D (4 binary: universalistic / particularistic) | — | R poLCA |
Credits
pypoLCA is a translation of Drew A. Linzer and Jeffrey B. Lewis's R package:
Linzer, D. A., & Lewis, J. B. (2011). poLCA: An R Package for Polytomous Variable Latent Class Analysis. Journal of Statistical Software, 42(10), 1–29. doi:10.18637/jss.v042.i10
Built-in datasets and the EM / Newton-Raphson algorithm are taken from the poLCA R package, licensed GPL-2.0-or-later.
C++ bindings powered by pybind11. Linear algebra via Eigen.
Contributing
Contributions are welcome and appreciated. Please keep submissions tight and purposeful. The goal is to keep pypoLCA a focused, maintainable package. AI-assisted contributions are fine, but AI use doesn't excuse sloppy or verbose work; review your output before submitting a PR.
License
GPL-2.0-or-later
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polca-0.4.0.tar.gz.
File metadata
- Download URL: polca-0.4.0.tar.gz
- Upload date:
- Size: 62.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43d72d3a6f29601f87263c1d8b874be66d0b5b2a38f967abf466c413e17dddce
|
|
| MD5 |
05e1d6b88fa706218c6f2ca9394535e8
|
|
| BLAKE2b-256 |
7c43f73fbae960d9bddd0e45da91d37ca072e2fdcc3c170edd0d6007a5c810ee
|
Provenance
The following attestation bundles were made for polca-0.4.0.tar.gz:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0.tar.gz -
Subject digest:
43d72d3a6f29601f87263c1d8b874be66d0b5b2a38f967abf466c413e17dddce - Sigstore transparency entry: 1733128158
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type:
File details
Details for the file polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 3.1 MB
- Tags: CPython 3.13, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b39252aa2319569040c243ece813dbac127c5c62e78cb9b6a9bfa11bb95f289
|
|
| MD5 |
e124c25328e9bea014313fc8754a2083
|
|
| BLAKE2b-256 |
794c15c4ef7a031ea6a63ee6422f6e67404b4926fcd809c9704cde448376eb5e
|
Provenance
The following attestation bundles were made for polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl -
Subject digest:
1b39252aa2319569040c243ece813dbac127c5c62e78cb9b6a9bfa11bb95f289 - Sigstore transparency entry: 1733128197
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type:
File details
Details for the file polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d3ac9e12ccf0b4743d895998cb5905d1a2a489f74356861dfae939b4af0965a
|
|
| MD5 |
95b374743f3ec71950632e3999c4244b
|
|
| BLAKE2b-256 |
50f5d892cec07492df5c288002e3dc8b011c68606a9c23ebeb0ad689b8ab9978
|
Provenance
The following attestation bundles were made for polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
9d3ac9e12ccf0b4743d895998cb5905d1a2a489f74356861dfae939b4af0965a - Sigstore transparency entry: 1733128217
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type:
File details
Details for the file polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
92419f3cfd34383cfc6d9150c656d3f7cbcae2d0aa822f3d3cc20e9ffba0eb67
|
|
| MD5 |
3de96a9a2aa4b82b65359728e1fac19f
|
|
| BLAKE2b-256 |
7686eed75ae77b090175ab4c10c8012b9d0fdde431a8550d859af87e27798941
|
Provenance
The following attestation bundles were made for polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl -
Subject digest:
92419f3cfd34383cfc6d9150c656d3f7cbcae2d0aa822f3d3cc20e9ffba0eb67 - Sigstore transparency entry: 1733128235
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type:
File details
Details for the file polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 3.1 MB
- Tags: CPython 3.12, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e9ec2f0496846a4234c8e862abb944ab1d2f61744d0ae54c478162091664206
|
|
| MD5 |
205ec4fbcac45f88043aa5d0f3cc03d6
|
|
| BLAKE2b-256 |
742ed1a2aeeb8c63c69b1fcebed2f6412fc12d7fe7cbf99738e93f6ba15943c9
|
Provenance
The following attestation bundles were made for polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl -
Subject digest:
3e9ec2f0496846a4234c8e862abb944ab1d2f61744d0ae54c478162091664206 - Sigstore transparency entry: 1733128255
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type:
File details
Details for the file polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.12, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
99f6963366ec8de7ce518b85d10346663768c67e478e1a19ceeffcec5cc5172b
|
|
| MD5 |
99b45c77110cd816aa55805e025e7d60
|
|
| BLAKE2b-256 |
cd420fb6f2b5283020c4b8918b08ee00e5166962f32a6745cccb9acfe6b064d2
|
Provenance
The following attestation bundles were made for polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
99f6963366ec8de7ce518b85d10346663768c67e478e1a19ceeffcec5cc5172b - Sigstore transparency entry: 1733128271
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type:
File details
Details for the file polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl.
File metadata
- Download URL: polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.12, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
10e38f849803d9e1245de77808af652f687e47c72e637dbbb187516a2b9b7d7d
|
|
| MD5 |
a30d2d076c227a8cd0d41a9ec65b849c
|
|
| BLAKE2b-256 |
2c186d20141490a1441a077f9db50c5c4d63c8cc24d61b196f04a280f6d68606
|
Provenance
The following attestation bundles were made for polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl:
Publisher:
wheels.yml on marcandre259/pypolca
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl -
Subject digest:
10e38f849803d9e1245de77808af652f687e47c72e637dbbb187516a2b9b7d7d - Sigstore transparency entry: 1733128176
- Sigstore integration time:
-
Permalink:
marcandre259/pypolca@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Branch / Tag:
refs/tags/v0.4.0 - Owner: https://github.com/marcandre259
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@0b5fa6d419cd953eb2e198bd3c44a49d03ceb87f -
Trigger Event:
push
-
Statement type: