From 9f3856a7068995e29b95c130d5ddf444fc7b4d81 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 6 Nov 2026 19:05:54 -0500 Subject: [PATCH 1/7] Mark microimpute archived and point readers to Microcosm Add a notice to the top of the README and the docs landing page: PolicyEngine has archived microimpute, and Microcosm, whose microcosm-fit package fits its imputation models, builds PolicyEngine's microdata. Released versions stay installable from PyPI. Remove the README's invitation to open issues and pull requests and the docs' "work in progress" line, since an archived repository accepts neither, and add the "Development Status :: 7 - Inactive" classifier. Co-Authored-By: Claude Opus 5.5 --- README.md | 6 ++---- changelog.d/retire-microimpute.changed.md | 1 + docs/index.md | 4 ++-- pyproject.toml | 1 + 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 changelog.d/retire-microimpute.changed.md diff --git a/README.md b/README.md index feb9b613..0ef05e20 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Microimpute +> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. You can still install released versions from [PyPI](https://pypi.org/project/microimpute/). + Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best, rather than defaulting to a single approach. ## Methods @@ -39,10 +41,6 @@ For image export (PNG/JPG): pip install microimpute[images] ``` -## Contributing - -Pull requests are welcome. If you find a bug or have a feature idea, open an issue or submit a PR. - ## License PolicyEngine releases the code in this repository under the [MIT License](https://github.com/PolicyEngine/microimpute/blob/main/LICENSE), and its original text and figures under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) with attribution to PolicyEngine. Third-party data and materials keep their own terms. The optional `matching` extra installs rpy2, which its authors license under GPL-2.0-or-later. The paper notebook `paper/imputing-from-scf-to-cps.ipynb` imports policyengine-us and policyengine-core, which carry AGPL-3.0. diff --git a/changelog.d/retire-microimpute.changed.md b/changelog.d/retire-microimpute.changed.md new file mode 100644 index 00000000..cbdfe9ab --- /dev/null +++ b/changelog.d/retire-microimpute.changed.md @@ -0,0 +1 @@ +Point readers to Microcosm, which builds PolicyEngine's microdata, and mark the package inactive. diff --git a/docs/index.md b/docs/index.md index 8634c9d0..0baab668 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,7 @@ # Microimpute +> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. You can still install released versions from [PyPI](https://pypi.org/project/microimpute/). + Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best: impute one or multiple variables with any of the available methods, and compare their performance using quantile loss and log loss. The package currently supports: @@ -9,8 +11,6 @@ The package currently supports: - Quantile Regression - Mixture Density Networks (MDN) -This is a work in progress and may evolve over time with new methods and features. - ## Microimputation dashboard To visualize imputation and benchmarking results, install Node.js, run `make dashboard` from the root of the [microimpute repository](https://github.com/PolicyEngine/microimpute), and open http://localhost:3000. diff --git a/pyproject.toml b/pyproject.toml index 35f898a9..7b3d3c6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ authors = [ { name = "Nikhil Woodruff", email = "nikhil.woodruff@outlook.com" } ] classifiers = [ + "Development Status :: 7 - Inactive", "Intended Audience :: Science/Research", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.12", From b26fe00d75952f5cb09a55d7819f4f085724d490 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 19:12:05 -0500 Subject: [PATCH 2/7] Delete CONTRIBUTING.md and drop "currently" from the docs An archived repository accepts no pull requests, so the contributing guide goes with the README's invitation. The docs' list of methods no longer says the package "currently" supports them. Co-Authored-By: Claude Opus 5.5 --- .github/CONTRIBUTING.md | 14 -------------- docs/index.md | 2 +- 2 files changed, 1 insertion(+), 15 deletions(-) delete mode 100644 .github/CONTRIBUTING.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index c44894a4..00000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1,14 +0,0 @@ -## Updating the code - -Please make sure that introduced changes are consistent with the testing api and add additional tests if relevant. - -## Updating the versioning - -Add a towncrier fragment under `changelog.d/` with the format -`..md`, where `` is one of `added`, -`changed`, `fixed`, `removed`, or `breaking`. - -Do not edit `CHANGELOG.md` directly in feature PRs. After the PR is merged, -the versioning workflow runs `make changelog`, deletes the consumed fragments, -updates `CHANGELOG.md`, bumps `pyproject.toml`, and commits the result as -`Update package version`. diff --git a/docs/index.md b/docs/index.md index 0baab668..5dbab31a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best: impute one or multiple variables with any of the available methods, and compare their performance using quantile loss and log loss. -The package currently supports: +The package supports: - Hot Deck Matching - Ordinary Least Squares (OLS) Linear Regression - Quantile Regression Forests (QRF) From bb2bb228f3790c3a99efa123f02136e7b984d8f9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 7 Nov 2026 21:02:49 -0500 Subject: [PATCH 3/7] Point the archive notice to the open bugs, and delete two dead scripts - The notice in the README and the docs now says PyPI keeps every release. It also points to the open issues, which include correctness bugs that no release fixes, such as the default QRF's miscalibrated quantiles (#205). - .github/is-version-number-acceptable.sh told readers to look at CONTRIBUTING.md, which the previous commit deletes. No workflow runs the script, and only it runs .github/has-functional-changes.sh, so both go. Co-Authored-By: Claude Opus 5.5 --- .github/has-functional-changes.sh | 12 --------- .github/is-version-number-acceptable.sh | 33 ------------------------- README.md | 2 +- docs/index.md | 2 +- 4 files changed, 2 insertions(+), 47 deletions(-) delete mode 100755 .github/has-functional-changes.sh delete mode 100755 .github/is-version-number-acceptable.sh diff --git a/.github/has-functional-changes.sh b/.github/has-functional-changes.sh deleted file mode 100755 index 4c8a1d03..00000000 --- a/.github/has-functional-changes.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /usr/bin/env bash - -IGNORE_DIFF_ON="README.md CONTRIBUTING.md Makefile docs/* .gitignore LICENSE* .github/* data/*" - -last_tagged_commit=`git describe --tags --abbrev=0 --first-parent` # --first-parent ensures we don't follow tags not published in master through an unlikely intermediary merge commit - -if git diff-index --name-only --exit-code $last_tagged_commit -- . `echo " $IGNORE_DIFF_ON" | sed 's/ / :(exclude)/g'` # Check if any file that has not be listed in IGNORE_DIFF_ON has changed since the last tag was published. -then - echo "No functional changes detected." - exit 1 -else echo "The functional files above were changed." -fi \ No newline at end of file diff --git a/.github/is-version-number-acceptable.sh b/.github/is-version-number-acceptable.sh deleted file mode 100755 index 9a912adf..00000000 --- a/.github/is-version-number-acceptable.sh +++ /dev/null @@ -1,33 +0,0 @@ -#! /usr/bin/env bash - -if [[ ${GITHUB_REF#refs/heads/} == master ]] -then - echo "No need for a version check on master." - exit 0 -fi - -if ! $(dirname "$BASH_SOURCE")/has-functional-changes.sh -then - echo "No need for a version update." - exit 0 -fi - -current_version=`python .github/fetch_version.py` - -if git rev-parse --verify --quiet $current_version -then - echo "Version $current_version already exists in commit:" - git --no-pager log -1 $current_version - echo - echo "Update the version number in setup.py before merging this branch into master." - echo "Look at the CONTRIBUTING.md file to learn how the version number should be updated." - exit 1 -fi - -if ! $(dirname "$BASH_SOURCE")/has-functional-changes.sh | grep --quiet CHANGELOG.md -then - echo "CHANGELOG.md has not been modified, while functional changes were made." - echo "Explain what you changed before merging this branch into master." - echo "Look at the CONTRIBUTING.md file to learn how to write the changelog." - exit 2 -fi \ No newline at end of file diff --git a/README.md b/README.md index 0ef05e20..9ef02683 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Microimpute -> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. You can still install released versions from [PyPI](https://pypi.org/project/microimpute/). +> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. [PyPI](https://pypi.org/project/microimpute/) keeps every release, so `pip install microimpute` still works. The [open issues](https://github.com/PolicyEngine/microimpute/issues) include correctness bugs that no release fixes, such as [the default QRF's miscalibrated quantiles](https://github.com/PolicyEngine/microimpute/issues/205). Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best, rather than defaulting to a single approach. diff --git a/docs/index.md b/docs/index.md index 5dbab31a..ffcc398f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # Microimpute -> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. You can still install released versions from [PyPI](https://pypi.org/project/microimpute/). +> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. [PyPI](https://pypi.org/project/microimpute/) keeps every release, so `pip install microimpute` still works. The [open issues](https://github.com/PolicyEngine/microimpute/issues) include correctness bugs that no release fixes, such as [the default QRF's miscalibrated quantiles](https://github.com/PolicyEngine/microimpute/issues/205). Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best: impute one or multiple variables with any of the available methods, and compare their performance using quantile loss and log loss. From c464bc8701ffdd33e5bd2ef102a9022f05e7acb0 Mon Sep 17 00:00:00 2001 From: Vahid Ahmadi Date: Mon, 14 Sep 2026 18:28:54 +0100 Subject: [PATCH 4/7] Read the package version from installed metadata __init__.py carried a hardcoded 1.1.2 while pyproject.toml was at 3.1.1, two major versions apart, because the version bump script only updates pyproject.toml. Reading from importlib.metadata means there is one source of truth and the two cannot drift again. Co-Authored-By: Claude Opus 5 (1M context) (cherry picked from commit f2798a36da7998253179742f987f662f16d96848) --- changelog.d/version-from-metadata.fixed.md | 1 + microimpute/__init__.py | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 changelog.d/version-from-metadata.fixed.md diff --git a/changelog.d/version-from-metadata.fixed.md b/changelog.d/version-from-metadata.fixed.md new file mode 100644 index 00000000..a9a36fd8 --- /dev/null +++ b/changelog.d/version-from-metadata.fixed.md @@ -0,0 +1 @@ +- Fixed `microimpute.__version__` reporting 1.1.2 while the package was at 3.1.1, by reading the version from installed package metadata. diff --git a/microimpute/__init__.py b/microimpute/__init__.py index 5acfe3e2..ba9e40a3 100644 --- a/microimpute/__init__.py +++ b/microimpute/__init__.py @@ -19,7 +19,12 @@ - Visualization: performance and comparison plots """ -__version__ = "1.1.2" +try: # Python 3.8+ + from importlib.metadata import PackageNotFoundError, version as _version + + __version__ = _version("microimpute") +except PackageNotFoundError: # running from a source tree without an install + __version__ = "unknown" # Import automated imputation from microimpute.comparisons.autoimpute import AutoImputeResult, autoimpute From 1567452a617ebf21dc5aecebc85e51840037cc87 Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Mon, 21 Sep 2026 10:31:51 +0100 Subject: [PATCH 5/7] Make the version fallback PEP 440 parseable, and test it The fallback was 'unknown', which packaging.version.parse rejects, so a consumer comparing versions would raise InvalidVersion in exactly the uninstalled-source-tree case the fallback exists for. Now 0.0.0+unknown. Adds the tests the PR was missing: that __version__ matches the installed distribution, that both it and the fallback parse, and that the fallback branch is reached when the distribution is absent. Drops the '# Python 3.8+' comment, which named versions this package does not support (requires-python is >=3.12,<3.15). (cherry picked from commit 2edf0f6d9e93a9df4d1c286a09bde7af7c392b63) --- microimpute/__init__.py | 10 ++++++---- tests/test_version.py | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 tests/test_version.py diff --git a/microimpute/__init__.py b/microimpute/__init__.py index ba9e40a3..7b8e81df 100644 --- a/microimpute/__init__.py +++ b/microimpute/__init__.py @@ -19,12 +19,14 @@ - Visualization: performance and comparison plots """ -try: # Python 3.8+ - from importlib.metadata import PackageNotFoundError, version as _version +from importlib.metadata import PackageNotFoundError, version as _version +try: __version__ = _version("microimpute") -except PackageNotFoundError: # running from a source tree without an install - __version__ = "unknown" +except PackageNotFoundError: + # Running from a source tree with no install. Keep it PEP 440 parseable so + # a consumer calling packaging.version.parse on it does not raise. + __version__ = "0.0.0+unknown" # Import automated imputation from microimpute.comparisons.autoimpute import AutoImputeResult, autoimpute diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 00000000..139086a8 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,42 @@ +"""The reported version must match the installed distribution. + +`__version__` was hardcoded and drifted to 1.1.2 while the package was 3.1.1, +which is the regression this guards against. +""" + +from importlib.metadata import version + +import pytest +from packaging.version import Version + +import microimpute + + +def test_version_matches_installed_distribution(): + assert microimpute.__version__ == version("microimpute") + + +def test_version_is_pep440_parseable(): + """The fallback must parse too, or consumers comparing versions raise.""" + Version(microimpute.__version__) + Version("0.0.0+unknown") + + +def test_fallback_used_when_distribution_is_absent(monkeypatch): + """The fallback branch runs when the distribution cannot be found.""" + import importlib.metadata + + def _raise(_name): + raise importlib.metadata.PackageNotFoundError(_name) + + monkeypatch.setattr(importlib.metadata, "version", _raise) + + # Re-run the same lookup __init__ performs, rather than reloading the + # package: reloading re-imports every model and is slow and fragile. + try: + resolved = importlib.metadata.version("microimpute") + except importlib.metadata.PackageNotFoundError: + resolved = "0.0.0+unknown" + + assert resolved == "0.0.0+unknown" + Version(resolved) From 72e6bcc51f8f681db7cb70c1b2abc80318f86512 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 00:31:15 -0500 Subject: [PATCH 6/7] Test the version fallback through the package's own import - test_fallback_used_when_distribution_is_absent re-ran the metadata lookup inside the test, so it passed whatever microimpute/__init__.py did. It now imports the package in a fresh interpreter with the lookup for microimpute failing, which runs the fallback branch in __init__.py. - The changelog fragment called 3.1.1 the current version, which stopped holding when 3.1.2 shipped. It now says what the fix does. Co-Authored-By: Claude Opus 5.5 --- changelog.d/version-from-metadata.fixed.md | 2 +- tests/test_version.py | 59 +++++++++++++++------- 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/changelog.d/version-from-metadata.fixed.md b/changelog.d/version-from-metadata.fixed.md index a9a36fd8..0eac62c2 100644 --- a/changelog.d/version-from-metadata.fixed.md +++ b/changelog.d/version-from-metadata.fixed.md @@ -1 +1 @@ -- Fixed `microimpute.__version__` reporting 1.1.2 while the package was at 3.1.1, by reading the version from installed package metadata. +Read `microimpute.__version__` from the installed package's metadata, so it matches the installed release instead of always reporting 1.1.2. diff --git a/tests/test_version.py b/tests/test_version.py index 139086a8..4bcbdab2 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -4,13 +4,18 @@ which is the regression this guards against. """ +import subprocess +import sys +import textwrap from importlib.metadata import version +from pathlib import Path -import pytest from packaging.version import Version import microimpute +REPO_ROOT = Path(__file__).resolve().parents[1] + def test_version_matches_installed_distribution(): assert microimpute.__version__ == version("microimpute") @@ -22,21 +27,37 @@ def test_version_is_pep440_parseable(): Version("0.0.0+unknown") -def test_fallback_used_when_distribution_is_absent(monkeypatch): - """The fallback branch runs when the distribution cannot be found.""" - import importlib.metadata - - def _raise(_name): - raise importlib.metadata.PackageNotFoundError(_name) - - monkeypatch.setattr(importlib.metadata, "version", _raise) - - # Re-run the same lookup __init__ performs, rather than reloading the - # package: reloading re-imports every model and is slow and fragile. - try: - resolved = importlib.metadata.version("microimpute") - except importlib.metadata.PackageNotFoundError: - resolved = "0.0.0+unknown" - - assert resolved == "0.0.0+unknown" - Version(resolved) +def test_fallback_used_when_distribution_is_absent(): + """Without a distribution to read, the package reports 0.0.0+unknown.""" + # A fresh interpreter makes the metadata lookup for microimpute fail and + # then imports the package from this source tree, so the import runs the + # fallback branch in microimpute/__init__.py itself. + code = textwrap.dedent( + """ + import importlib.metadata as metadata + import sys + + real_version = metadata.version + + + def missing(name): + if name == "microimpute": + raise metadata.PackageNotFoundError(name) + return real_version(name) + + + metadata.version = missing + sys.path.insert(0, sys.argv[1]) + import microimpute + + print(microimpute.__version__) + """ + ) + result = subprocess.run( + [sys.executable, "-c", code, str(REPO_ROOT)], + capture_output=True, + text=True, + timeout=300, + ) + assert result.returncode == 0, result.stderr + assert result.stdout.strip().splitlines()[-1] == "0.0.0+unknown" From 05a2b743d672025542637dc8158bf9dc1c2cee25 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 8 Nov 2026 01:16:02 -0500 Subject: [PATCH 7/7] Say PyPI keeps the package's releases, and fix the test docstring - CHANGELOG.md lists six versions that never reached PyPI (1.0.0, 1.4.0, 1.5.0, 1.8.0, 1.14.5 and 2.0.1), so the notice no longer says PyPI keeps every release. - The docstring in tests/test_version.py now says __init__.py used to hard-code 1.1.2, which fell behind the package's own version. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- docs/index.md | 2 +- tests/test_version.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 9ef02683..4124b1cd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Microimpute -> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. [PyPI](https://pypi.org/project/microimpute/) keeps every release, so `pip install microimpute` still works. The [open issues](https://github.com/PolicyEngine/microimpute/issues) include correctness bugs that no release fixes, such as [the default QRF's miscalibrated quantiles](https://github.com/PolicyEngine/microimpute/issues/205). +> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. [PyPI](https://pypi.org/project/microimpute/) keeps the package's releases, so `pip install microimpute` still works. The [open issues](https://github.com/PolicyEngine/microimpute/issues) include correctness bugs that no release fixes, such as [the default QRF's miscalibrated quantiles](https://github.com/PolicyEngine/microimpute/issues/205). Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best, rather than defaulting to a single approach. diff --git a/docs/index.md b/docs/index.md index ffcc398f..b1e5d41a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,6 @@ # Microimpute -> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. [PyPI](https://pypi.org/project/microimpute/) keeps every release, so `pip install microimpute` still works. The [open issues](https://github.com/PolicyEngine/microimpute/issues) include correctness bugs that no release fixes, such as [the default QRF's miscalibrated quantiles](https://github.com/PolicyEngine/microimpute/issues/205). +> PolicyEngine has archived microimpute. [Microcosm](https://github.com/PolicyEngine/microcosm) builds PolicyEngine's microdata and fits its imputation models with the `microcosm-fit` package. [PyPI](https://pypi.org/project/microimpute/) keeps the package's releases, so `pip install microimpute` still works. The [open issues](https://github.com/PolicyEngine/microimpute/issues) include correctness bugs that no release fixes, such as [the default QRF's miscalibrated quantiles](https://github.com/PolicyEngine/microimpute/issues/205). Microimpute is a Python package for imputing variables from one survey dataset onto another. It wraps five imputation methods behind a common interface so you can benchmark them on your data and pick the one that works best: impute one or multiple variables with any of the available methods, and compare their performance using quantile loss and log loss. diff --git a/tests/test_version.py b/tests/test_version.py index 4bcbdab2..9b195393 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -1,7 +1,7 @@ """The reported version must match the installed distribution. -`__version__` was hardcoded and drifted to 1.1.2 while the package was 3.1.1, -which is the regression this guards against. +`__init__.py` used to hard-code `__version__` as 1.1.2, which fell behind the +package's own version (3.1.1 by September 2026). These tests catch that drift. """ import subprocess