Skip to main content

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:

  1. The underlying latent classes (e.g., "high-risk" vs. "low-risk" respondents), and
  2. 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.py and python scripts/benchmark_scaling.py to populate with fresh numbers (requires R with poLCA and jsonlite installed). Speed-up is relative to R poLCA.

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polca-0.4.0.tar.gz (62.1 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl (3.1 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ x86-64

polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.17+ x86-64

polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl (2.1 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl (3.1 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.17+ x86-64

polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl (2.1 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

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

Hashes for polca-0.4.0.tar.gz
Algorithm Hash digest
SHA256 43d72d3a6f29601f87263c1d8b874be66d0b5b2a38f967abf466c413e17dddce
MD5 05e1d6b88fa706218c6f2ca9394535e8
BLAKE2b-256 7c43f73fbae960d9bddd0e45da91d37ca072e2fdcc3c170edd0d6007a5c810ee

See more details on using hashes here.

Provenance

The following attestation bundles were made for polca-0.4.0.tar.gz:

Publisher: wheels.yml on marcandre259/pypolca

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for polca-0.4.0-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 1b39252aa2319569040c243ece813dbac127c5c62e78cb9b6a9bfa11bb95f289
MD5 e124c25328e9bea014313fc8754a2083
BLAKE2b-256 794c15c4ef7a031ea6a63ee6422f6e67404b4926fcd809c9704cde448376eb5e

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polca-0.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 9d3ac9e12ccf0b4743d895998cb5905d1a2a489f74356861dfae939b4af0965a
MD5 95b374743f3ec71950632e3999c4244b
BLAKE2b-256 50f5d892cec07492df5c288002e3dc8b011c68606a9c23ebeb0ad689b8ab9978

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polca-0.4.0-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 92419f3cfd34383cfc6d9150c656d3f7cbcae2d0aa822f3d3cc20e9ffba0eb67
MD5 3de96a9a2aa4b82b65359728e1fac19f
BLAKE2b-256 7686eed75ae77b090175ab4c10c8012b9d0fdde431a8550d859af87e27798941

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for polca-0.4.0-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 3e9ec2f0496846a4234c8e862abb944ab1d2f61744d0ae54c478162091664206
MD5 205ec4fbcac45f88043aa5d0f3cc03d6
BLAKE2b-256 742ed1a2aeeb8c63c69b1fcebed2f6412fc12d7fe7cbf99738e93f6ba15943c9

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for polca-0.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 99f6963366ec8de7ce518b85d10346663768c67e478e1a19ceeffcec5cc5172b
MD5 99b45c77110cd816aa55805e025e7d60
BLAKE2b-256 cd420fb6f2b5283020c4b8918b08ee00e5166962f32a6745cccb9acfe6b064d2

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for polca-0.4.0-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 10e38f849803d9e1245de77808af652f687e47c72e637dbbb187516a2b9b7d7d
MD5 a30d2d076c227a8cd0d41a9ec65b849c
BLAKE2b-256 2c186d20141490a1441a077f9db50c5c4d63c8cc24d61b196f04a280f6d68606

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page