From 98b2b84c60951899e3ed69ace036e55001895246 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Wed, 19 Aug 2026 17:16:07 +0300 Subject: [PATCH 1/3] Add LOWESS migration helper --- .maintenance/replace_statsmodels_lowess.py | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .maintenance/replace_statsmodels_lowess.py diff --git a/.maintenance/replace_statsmodels_lowess.py b/.maintenance/replace_statsmodels_lowess.py new file mode 100644 index 00000000..a3012705 --- /dev/null +++ b/.maintenance/replace_statsmodels_lowess.py @@ -0,0 +1,30 @@ +from pathlib import Path + +calibration_path = Path('src/rtichoke/calibration/calibration.py') +text = calibration_path.read_text() + +old_import = 'from polarstate import predict_aj_estimates, prepare_event_table\nfrom ._secondary_cox import calculate_secondary_cox_smooth\n' +new_import = 'from polarstate import predict_aj_estimates, prepare_event_table\nfrom smoothstate import smooth_state_lowess\nfrom ._secondary_cox import calculate_secondary_cox_smooth\n' +if old_import not in text: + raise SystemExit('Expected import block not found') +text = text.replace(old_import, new_import, 1) + +old_local_import = ' from statsmodels.nonparametric.smoothers_lowess import lowess\n\n' +if old_local_import not in text: + raise SystemExit('Expected statsmodels LOWESS import not found') +text = text.replace(old_local_import, '', 1) + +old_block = ''' else:\n # lowess returns a 2D array where the first column is x and the second is y\n smoothed = lowess(r, p, it=0)\n xout = np.linspace(0, 1, 101)\n yout = np.clip(np.interp(xout, smoothed[:, 0], smoothed[:, 1]), 0.0, 1.0)\n return pl.DataFrame(\n {"x": xout, "y": yout, "reference_group": [group_name] * len(xout)}\n )\n''' +new_block = ''' else:\n smoothed = smooth_state_lowess(p, r)\n return smoothed.with_columns(\n pl.lit(group_name).alias("reference_group")\n )\n''' +if old_block not in text: + raise SystemExit('Expected LOWESS implementation block not found') +text = text.replace(old_block, new_block, 1) +calibration_path.write_text(text) + +pyproject_path = Path('pyproject.toml') +pyproject = pyproject_path.read_text() +pyproject = pyproject.replace(' "smoothstate>=0.1.0",\n', ' "smoothstate>=0.1.1",\n', 1) +if ' "statsmodels>=0.14.0",\n' not in pyproject: + raise SystemExit('Expected statsmodels dependency not found') +pyproject = pyproject.replace(' "statsmodels>=0.14.0",\n', '', 1) +pyproject_path.write_text(pyproject) From 3d2150e8e4185e42ddac06e20630203b6cbb5d23 Mon Sep 17 00:00:00 2001 From: Uriah Finkel Date: Wed, 19 Aug 2026 17:16:27 +0300 Subject: [PATCH 2/3] Apply LOWESS migration --- .github/workflows/apply-lowess-migration.yml | 33 ++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/apply-lowess-migration.yml diff --git a/.github/workflows/apply-lowess-migration.yml b/.github/workflows/apply-lowess-migration.yml new file mode 100644 index 00000000..cda7a613 --- /dev/null +++ b/.github/workflows/apply-lowess-migration.yml @@ -0,0 +1,33 @@ +name: Apply LOWESS migration + +on: + push: + branches: [agent/remove-statsmodels-lowess] + paths: + - '.maintenance/replace_statsmodels_lowess.py' + - '.github/workflows/apply-lowess-migration.yml' + +permissions: + contents: write + +jobs: + migrate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: agent/remove-statsmodels-lowess + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Apply migration + run: python .maintenance/replace_statsmodels_lowess.py + - name: Remove migration helpers + run: rm -rf .maintenance .github/workflows/apply-lowess-migration.yml + - name: Commit migration + run: | + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add -A + git commit -m 'Use smoothstate LOWESS and drop statsmodels' + git push From d7f6911137081e37f11c0b2d86207abcef215bee Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:16:37 +0000 Subject: [PATCH 3/3] Use smoothstate LOWESS and drop statsmodels --- .github/workflows/apply-lowess-migration.yml | 33 -------------------- .maintenance/replace_statsmodels_lowess.py | 30 ------------------ pyproject.toml | 3 +- src/rtichoke/calibration/calibration.py | 12 +++---- 4 files changed, 5 insertions(+), 73 deletions(-) delete mode 100644 .github/workflows/apply-lowess-migration.yml delete mode 100644 .maintenance/replace_statsmodels_lowess.py diff --git a/.github/workflows/apply-lowess-migration.yml b/.github/workflows/apply-lowess-migration.yml deleted file mode 100644 index cda7a613..00000000 --- a/.github/workflows/apply-lowess-migration.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Apply LOWESS migration - -on: - push: - branches: [agent/remove-statsmodels-lowess] - paths: - - '.maintenance/replace_statsmodels_lowess.py' - - '.github/workflows/apply-lowess-migration.yml' - -permissions: - contents: write - -jobs: - migrate: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - ref: agent/remove-statsmodels-lowess - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Apply migration - run: python .maintenance/replace_statsmodels_lowess.py - - name: Remove migration helpers - run: rm -rf .maintenance .github/workflows/apply-lowess-migration.yml - - name: Commit migration - run: | - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add -A - git commit -m 'Use smoothstate LOWESS and drop statsmodels' - git push diff --git a/.maintenance/replace_statsmodels_lowess.py b/.maintenance/replace_statsmodels_lowess.py deleted file mode 100644 index a3012705..00000000 --- a/.maintenance/replace_statsmodels_lowess.py +++ /dev/null @@ -1,30 +0,0 @@ -from pathlib import Path - -calibration_path = Path('src/rtichoke/calibration/calibration.py') -text = calibration_path.read_text() - -old_import = 'from polarstate import predict_aj_estimates, prepare_event_table\nfrom ._secondary_cox import calculate_secondary_cox_smooth\n' -new_import = 'from polarstate import predict_aj_estimates, prepare_event_table\nfrom smoothstate import smooth_state_lowess\nfrom ._secondary_cox import calculate_secondary_cox_smooth\n' -if old_import not in text: - raise SystemExit('Expected import block not found') -text = text.replace(old_import, new_import, 1) - -old_local_import = ' from statsmodels.nonparametric.smoothers_lowess import lowess\n\n' -if old_local_import not in text: - raise SystemExit('Expected statsmodels LOWESS import not found') -text = text.replace(old_local_import, '', 1) - -old_block = ''' else:\n # lowess returns a 2D array where the first column is x and the second is y\n smoothed = lowess(r, p, it=0)\n xout = np.linspace(0, 1, 101)\n yout = np.clip(np.interp(xout, smoothed[:, 0], smoothed[:, 1]), 0.0, 1.0)\n return pl.DataFrame(\n {"x": xout, "y": yout, "reference_group": [group_name] * len(xout)}\n )\n''' -new_block = ''' else:\n smoothed = smooth_state_lowess(p, r)\n return smoothed.with_columns(\n pl.lit(group_name).alias("reference_group")\n )\n''' -if old_block not in text: - raise SystemExit('Expected LOWESS implementation block not found') -text = text.replace(old_block, new_block, 1) -calibration_path.write_text(text) - -pyproject_path = Path('pyproject.toml') -pyproject = pyproject_path.read_text() -pyproject = pyproject.replace(' "smoothstate>=0.1.0",\n', ' "smoothstate>=0.1.1",\n', 1) -if ' "statsmodels>=0.14.0",\n' not in pyproject: - raise SystemExit('Expected statsmodels dependency not found') -pyproject = pyproject.replace(' "statsmodels>=0.14.0",\n', '', 1) -pyproject_path.write_text(pyproject) diff --git a/pyproject.toml b/pyproject.toml index 51175d22..6eb96143 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,7 @@ dependencies = [ "plotly<6.0.0,>=5.13.1", "pandas>=2.2.3", "polarstate==0.1.8", - "smoothstate>=0.1.0", - "statsmodels>=0.14.0", + "smoothstate>=0.1.1", "polars>=1.31.0", "reactable>=0.1.5", "great-tables>=0.18.0", diff --git a/src/rtichoke/calibration/calibration.py b/src/rtichoke/calibration/calibration.py index e031ba3e..05954caf 100644 --- a/src/rtichoke/calibration/calibration.py +++ b/src/rtichoke/calibration/calibration.py @@ -11,6 +11,7 @@ import polars as pl import numpy as np from polarstate import predict_aj_estimates, prepare_event_table +from smoothstate import smooth_state_lowess from ._secondary_cox import calculate_secondary_cox_smooth # from rtichoke.helpers.send_post_request_to_r_rtichoke import send_requests_to_rtichoke_r @@ -757,8 +758,6 @@ def _calculate_smooth_curve( """ Calculate the smoothed calibration curve using lowess. """ - from statsmodels.nonparametric.smoothers_lowess import lowess - smooth_frames = [] # Helper function to process a single probability and real array @@ -772,12 +771,9 @@ def process_single_array(p, r, group_name): } ) else: - # lowess returns a 2D array where the first column is x and the second is y - smoothed = lowess(r, p, it=0) - xout = np.linspace(0, 1, 101) - yout = np.clip(np.interp(xout, smoothed[:, 0], smoothed[:, 1]), 0.0, 1.0) - return pl.DataFrame( - {"x": xout, "y": yout, "reference_group": [group_name] * len(xout)} + smoothed = smooth_state_lowess(p, r) + return smoothed.with_columns( + pl.lit(group_name).alias("reference_group") ) if isinstance(reals, dict):