diff --git a/docs/changes.rst b/docs/changes.rst index 803bd053..44d36d89 100644 --- a/docs/changes.rst +++ b/docs/changes.rst @@ -17,6 +17,18 @@ v0.17 with an error while it is read, before anything in it is audited or constructed, instead of failing with an unrelated error, or being accepted, during construction. :pr:`547` by `Adrin Jalali`_. +- Add support for pandas objects: :class:`~pandas.DataFrame`, + :class:`~pandas.Series`, every kind of :class:`~pandas.Index`, extension + arrays and extension dtypes can now be saved and loaded. They are stored as + the numpy arrays and scalars they are made of and rebuilt through the public + pandas constructors, so no pandas internals end up in the file, and they are + trusted by default. Estimators from other libraries that keep pandas objects + in their fitted attributes, such as ``category_encoders``, can now be + persisted. A file written with one pandas version loads with any other from + 2.0 on, keeping the dtypes of the version that wrote it. Not preserved are + the ``freq`` of datetime-like indexes and arrays, the ``attrs`` and ``flags`` + of a Series or DataFrame, and the storage, python or pyarrow, of a string + dtype. :pr:`552` by `Adrin Jalali`_. - Fix a regression since v0.12.0 where saving an object whose ``__reduce__`` raises failed at dump time. ``__reduce__`` is called on every object to detect a plain constructor call, but Cython extension types with a diff --git a/docs/persistence.rst b/docs/persistence.rst index 5c75d657..2a5aecd1 100644 --- a/docs/persistence.rst +++ b/docs/persistence.rst @@ -250,7 +250,15 @@ Supported libraries Skops intends to support all of **scikit-learn**, that is, not only its estimators, but also other classes like cross validation splitters. Furthermore, most types from **numpy** and **scipy** should be supported, such as (sparse) -arrays, dtypes, random generators, and ufuncs. +arrays, dtypes, random generators, and ufuncs. **pandas** objects, that is +``DataFrame``, ``Series``, every kind of ``Index``, extension arrays and +extension dtypes, are supported as well with pandas 2.0 or later: they are +stored as the arrays they are made of and rebuilt through the public pandas +constructors, so that no pandas internals end up in the file, and a file +written with one pandas version loads with any other. Not preserved are the +``freq`` of datetime-like indexes and arrays, the ``attrs`` and ``flags`` of a +``Series`` or ``DataFrame``, and the storage, python or pyarrow, of a string +dtype, which is an environment choice over the same values. Apart from this core, we plan to support machine learning libraries commonly used be the community. So far, we have tested the following libraries: diff --git a/docs/requirements.txt b/docs/requirements.txt index 3c5b8c06..6cac6248 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ # to be synced with the versions in pyproject.toml matplotlib>=3.3 -pandas>=1 +pandas>=2 fairlearn>=0.7.0 sphinx>=3.2.0 sphinx-gallery>=0.7.0 diff --git a/pixi.lock b/pixi.lock index 15aa943e..19e2d39b 100644 --- a/pixi.lock +++ b/pixi.lock @@ -117,7 +117,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-heb1ab33_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.4-h781a0a9_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.5-py312h8ecdadd_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pandoc-3.11-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-h8b3dc9c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py312h38079b3_4.conda @@ -210,6 +209,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/c7/bd05c5c430feb347aa040fcc8870135d70b256718deee9bc7d2ca74a77ff/xgboost-3.4.1-py3-none-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/c4/0e/57f6bb3024a597b2e8ec4aee710ffe62ddc95af2e2bb1ee7a7abdc22c68c/wcwidth-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl osx-64: @@ -307,7 +307,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.5.3-py314he0ba898_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-he4eae51_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.4-h332eb6d_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.5-py314h99bb933_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pandoc-3.11-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h31793e3_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.3.0-py314h909093e_4.conda @@ -335,6 +334,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/c4/0e/57f6bb3024a597b2e8ec4aee710ffe62ddc95af2e2bb1ee7a7abdc22c68c/wcwidth-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/05/7213965863cba1ed0150ad045bceed6276a1afaaaedbaeff4699ec4f0ccb/lightgbm-4.7.0-py3-none-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-macosx_10_15_x86_64.whl osx-arm64: @@ -432,7 +432,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.5.3-py314he06036b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-h4d1e80c_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.4-h55eecbc_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.5-py314he609de1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandoc-3.11-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-he63d830_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py314h709c99d_4.conda @@ -460,6 +459,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/c4/0e/57f6bb3024a597b2e8ec4aee710ffe62ddc95af2e2bb1ee7a7abdc22c68c/wcwidth-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/94/e5c37a8972ad780edc1d8459d1931356344ca133f7f99ba9cfda516b5bba/xgboost-3.4.1-py3-none-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-macosx_12_0_arm64.whl win-64: @@ -494,7 +494,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.0-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-9_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda @@ -560,7 +559,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.5.3-py314h02f10f6_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h90fa87c_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.4-hf411b9b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pandas-3.0.5-py314hf700ef7_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pandoc-3.11-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-h8466c1e_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py314h6acc0e1_4.conda @@ -591,7 +589,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/0e/57f6bb3024a597b2e8ec4aee710ffe62ddc95af2e2bb1ee7a7abdc22c68c/wcwidth-0.8.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/0b/c5c17d862b12ce292f24cd85d40f2f8f8981668fbdbd43fdc2625eccbc79/lightgbm-4.7.0-py3-none-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-win_amd64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-win_amd64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-win_amd64.whl ci-sklearn12: @@ -5333,11 +5333,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-h853b02a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scikit-learn-1.9.0-np2py314hf09ca88_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py314hf07bd8e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/statsmodels-0.15.0-np2py314h8874201_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h366c992_103.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.7-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py314h9891dd4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py314h5bd0f2a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.25.0-hd6090a7_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-2.4.1-py314hfe1a184_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda @@ -5367,6 +5369,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/category_encoders-2.11.1-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cuda-cudart_linux-64-12.9.79-h3f2d84a_0.conda @@ -5384,9 +5387,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/formulaic-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/interface_meta-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -5394,6 +5399,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -5412,6 +5418,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.5.1-pyhcf101f3_0.conda @@ -5425,6 +5432,7 @@ environments: osx-64: - conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/category_encoders-2.11.1-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda @@ -5440,9 +5448,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/formulaic-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/interface_meta-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -5450,6 +5460,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -5468,6 +5479,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.5.1-pyhcf101f3_0.conda @@ -5551,10 +5563,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/readline-8.3-h68b038d_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scikit-learn-1.9.0-np2py314h67cc4f9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/scipy-1.18.0-py314h5727af0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/statsmodels-0.15.0-np2py314hc91a65a_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/tornado-6.5.7-py314h217eccc_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/ukkonen-1.1.0-py314h473ef84_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/unicodedata2-17.0.1-py314h4f144dc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/wrapt-2.4.1-py314h17af80e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxdmcp-1.1.5-h8616949_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/yaml-0.2.5-h4132b18_3.conda @@ -5568,6 +5582,7 @@ environments: osx-arm64: - conda: https://conda.anaconda.org/conda-forge/noarch/adwaita-icon-theme-49.0-unix_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/category_encoders-2.11.1-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda @@ -5583,9 +5598,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/formulaic-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/interface_meta-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -5593,6 +5610,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -5611,6 +5629,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.5.1-pyhcf101f3_0.conda @@ -5694,10 +5713,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h46df422_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scikit-learn-1.9.0-np2py314h15f0f0f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py314h18e1515_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/statsmodels-0.15.0-np2py314h40f2a13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.7-py314h6c2aa35_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py314h6cfcd04_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py314h6c2aa35_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/wrapt-2.4.1-py314hd98292b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-hc919400_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h925e9cb_3.conda @@ -5710,6 +5731,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl win-64: - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.6.17-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/category_encoders-2.11.1-pyh5ded981_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda @@ -5725,9 +5747,11 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/formulaic-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.19-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/interface_meta-1.3.0-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/joblib-1.5.3-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda @@ -5735,6 +5759,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/narwhals-2.22.1-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.2-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.3-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.10.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/plotly-6.8.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda @@ -5754,6 +5779,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/threadpoolctl-3.6.0-pyhecae5ae_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.15.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.5.1-pyhcf101f3_0.conda @@ -5838,6 +5864,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/quantile-forest-1.4.2-py314h13fbf68_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/scikit-learn-1.9.0-np2py314h1b5b07a_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.0-py314h221f224_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/statsmodels-0.15.0-np2py314hea88fa1_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h6ed50ae_3.conda - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.7-py314h5a2d7ad_0.conda @@ -5847,6 +5874,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-h1b7c187_39.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36231-h1b9f54f_39.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36231-h1b9f54f_39.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/wrapt-2.4.1-py314hc5dbbe4_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libice-1.1.2-h0e40799_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libsm-1.2.6-h0e40799_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libx11-1.8.13-hfa52320_0.conda @@ -5959,7 +5987,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py314hb4ffadd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pandoc-3.11-ha770c72_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py314h8ec4b1a_0.conda @@ -6101,6 +6128,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl osx-64: @@ -6248,7 +6276,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.5.0-py314h7b24d9b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.3-hc881268_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py314h99bb933_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pandoc-3.11-h694c41f_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.2.0-py314hc904d5e_0.conda @@ -6276,6 +6303,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/75/cffc9962cca296bc5536896b7e65b4a7cdeb8db208e71b9c0133c08f8f7e/lightgbm-4.6.0-py3-none-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fc/72/3b68983c0215ef65d48e9eeb1f168c3c6e3d62a61ece605de3209c79cae1/xgboost-3.3.0-py3-none-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-macosx_10_15_x86_64.whl osx-arm64: @@ -6423,7 +6451,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.5.0-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py314he609de1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandoc-3.11-hce30654_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.2.0-py314hab283cf_0.conda @@ -6451,6 +6478,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c9/62/b49e756822b29909d0c95ed334662dc6c7c81a99ec6bc10dc18e69f3d6e7/xgboost-3.3.0-py3-none-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-macosx_12_0_arm64.whl win-64: @@ -6511,7 +6539,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-cov-7.1.0-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.4.2-pyhcf101f3_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -6605,7 +6632,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pandas-3.0.3-py314hf700ef7_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pandoc-3.11-h57928b3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.2.0-py314h61b30b5_0.conda @@ -6639,7 +6665,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/5e/23/f8b28ca248bb629b9e08f877dd2965d1994e1674a03d67cd10c5246da248/lightgbm-4.6.0-py3-none-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl + - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-win_amd64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-win_amd64.whl - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp314-cp314-win_amd64.whl docs: @@ -6734,7 +6762,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.3-py314hb4ffadd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.2.0-py314h8ec4b1a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_1.conda @@ -6835,6 +6862,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/ba/ffdcb19be4ff6bfe7d969e7cef2c567c633df5a3a1cc1053394ad053bca8/pandas-3.0.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f0/af/4d72d9e475ac83719160c662619e4bf7b95c19507cd582e7d0167a3c3dae/scikit_learn-1.9.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl osx-64: @@ -6946,7 +6974,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/numpy-2.5.0-py314h7b24d9b_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openjpeg-2.5.4-h52bb76a_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/openssl-3.6.3-hc881268_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.3-py314h99bb933_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pcre2-10.47-h13923f0_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pillow-12.2.0-py314hc904d5e_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pixman-0.46.4-ha059160_1.conda @@ -6964,6 +6991,7 @@ environments: - pypi: ./ - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/48/ca/36339329c4604adbcc99c899b7eb1ce1a555c499b6a6860757dc9bfed36d/narwhals-2.22.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/75/55/1a8875395b05ccd572cbca0b9255dcd2db6e6508e632a558c1a6884b39ad/pandas-3.0.6-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl @@ -7079,7 +7107,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.5.0-py314hb79c6fa_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_0.conda - - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.3-py314he609de1_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-h30297fc_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.2.0-py314hab283cf_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h81086ad_1.conda @@ -7096,6 +7123,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hbf9d68e_6.conda - pypi: ./ - pypi: https://files.pythonhosted.org/packages/32/d5/f9a850d79b0851d1d4ef6456097579a9005b31fea68726a4ae5f2d82ddd9/threadpoolctl-3.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/35/61/47ae13476995cc8a40cd609e93e7cf11f273d8692925c2903cb6d38aa0d1/pandas-3.0.6-cp314-cp314-macosx_11_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/3c/a7/552a7821597c632b907f7bfe8f36f9f572777af8ef8a48353041cf8e091a/scikit_learn-1.9.0-cp314-cp314-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/48/ca/36339329c4604adbcc99c899b7eb1ce1a555c499b6a6860757dc9bfed36d/narwhals-2.22.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl @@ -7135,7 +7163,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/rich-15.0.0-pyhcf101f3_0.conda @@ -7221,7 +7248,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/onemkl-license-2026.0.0-h57928b3_908.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/pandas-3.0.3-py314hf700ef7_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.2.0-py314h61b30b5_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_1.conda @@ -7250,7 +7276,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/99/bd/776182bf82b0a3223773a43b0e7b9510afe6c3d32ed8dd5a27b30a89530b/fairlearn-0.14.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/d5/6a58eea2cb9abbb9b3f2bb8b2cfb3243d1152d69f442d256c7af71304769/scikit_learn-1.9.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/b7/e9/f43410fada510b43fec09993c08f552086c3d247d3ee801a678f3cb10ea5/pandas-3.0.6-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fe/be/2e6798ace5cc036f5d05d36b7b2fd85346f1a708c87060890b070d0ec607/prettytable-3.18.0-py3-none-any.whl lint: channels: @@ -12411,64 +12439,6 @@ packages: run_exports: {} size: 15303815 timestamp: 1778602611222 -- conda: https://conda.anaconda.org/conda-forge/linux-64/pandas-3.0.5-py312h8ecdadd_1.conda - sha256: 393e529c0574c020a9b790275af10ff35e21cbb4e12740888bd3f214f2f07034 - md5: 85eb29ade84d1bd97be5ec55607efb00 - depends: - - python - - numpy >=1.26.0 - - python-dateutil >=2.8.2 - - __glibc >=2.17,<3.0.a0 - - libgcc >=14 - - libstdcxx >=14 - - numpy >=1.23,<3 - - python_abi 3.12.* *_cp312 - constrains: - - adbc-driver-postgresql >=1.2.0 - - adbc-driver-sqlite >=1.2.0 - - beautifulsoup4 >=4.12.3 - - blosc >=1.21.3 - - bottleneck >=1.4.2 - - fastparquet >=2024.11.0 - - fsspec >=2024.10.0 - - gcsfs >=2024.10.0 - - html5lib >=1.1 - - hypothesis >=6.116.0 - - jinja2 >=3.1.5 - - lxml >=5.3.0 - - matplotlib >=3.9.3 - - numba >=0.60.0 - - numexpr >=2.10.2 - - odfpy >=1.4.1 - - openpyxl >=3.1.5 - - psycopg2 >=2.9.10 - - pyarrow >=13.0.0 - - pyiceberg >=0.8.1 - - pymysql >=1.1.1 - - pyqt5 >=5.15.9 - - pyreadstat >=1.2.8 - - pytables >=3.10.1 - - pytest >=8.3.4 - - pytest-xdist >=3.6.1 - - python-calamine >=0.3.0 - - pytz >=2024.2 - - pyxlsb >=1.0.10 - - qtpy >=2.4.2 - - scipy >=1.14.1 - - s3fs >=2024.10.0 - - sqlalchemy >=2.0.36 - - tabulate >=0.9.0 - - xarray >=2024.10.0 - - xlrd >=2.0.1 - - xlsxwriter >=3.2.0 - - zstandard >=0.23.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandas?source=hash-mapping - run_exports: {} - size: 14936796 - timestamp: 1785276227779 - conda: https://conda.anaconda.org/conda-forge/linux-64/pandoc-3.11-ha770c72_0.conda sha256: cb14839e33fbbb2163f259476091dd99f9e83fbb1690b789cd1e6e6a05d17db7 md5: 2c71f77db75fa5e9dd64abb706c66788 @@ -14001,6 +13971,28 @@ packages: run_exports: {} size: 17260022 timestamp: 1781912924009 +- conda: https://conda.anaconda.org/conda-forge/linux-64/statsmodels-0.15.0-np2py314h8874201_2.conda + sha256: b31c70966a738e052eeda0bb5e6bb711c6a04bc6c75875e13b89bcaadd8c2ed5 + md5: f7ce33f5fb4b72ccfdadcd68571b7a81 + depends: + - python + - numpy >=1.23.5,<3 + - scipy >=1.8,!=1.9.2 + - pandas >=1.4,!=2.1.0 + - patsy >=0.5.6 + - packaging >=21.3 + - formulaic >=1.1.0 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - numpy >=1.25,<3 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/statsmodels?source=compressed-mapping + run_exports: {} + size: 14186732 + timestamp: 1790033647794 - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda build_number: 104 sha256: a1a241d172c1ccab067ba245206dd048bc3c2d1b84504b53c9468e99adfc16a1 @@ -14383,6 +14375,21 @@ packages: - wayland >=1.26.0,<2.0a0 size: 340058 timestamp: 1787793849093 +- conda: https://conda.anaconda.org/conda-forge/linux-64/wrapt-2.4.1-py314hfe1a184_0.conda + sha256: 1d706a003b2bdee04784a8fac90a14e6f00c3dca91bf4137a5f6db50c358bd42 + md5: 4f79d07d0f466843e04c7a54bd63d6d7 + depends: + - python + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/wrapt?source=compressed-mapping + run_exports: {} + size: 149268 + timestamp: 1789986014273 - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda sha256: ad8cab7e07e2af268449c2ce855cbb51f43f4664936eff679b1f3862e6e4b01d md5: fdc27cb255a7a2cc73b7919a968b48f0 @@ -15138,6 +15145,24 @@ packages: run_exports: {} size: 131780 timestamp: 1784754889428 +- conda: https://conda.anaconda.org/conda-forge/noarch/category_encoders-2.11.1-pyh5ded981_0.conda + sha256: 93dd5e34d287e264fbe548a375a7bb512dcc39d75fe93db4c947127094a347a9 + md5: e0b2a8381b5cb1f6c53e5756180ea258 + depends: + - numpy >=1.14.0 + - pandas >=1.0.5 + - python >=3.11 + - scikit-learn >=1.6.0 + - scipy >=1.0.0 + - statsmodels >=0.9.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/category-encoders?source=hash-mapping + run_exports: {} + size: 123562 + timestamp: 1788904404770 - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2025.8.3-pyhd8ed1ab_0.conda sha256: a1ad5b0a2a242f439608f22a538d2175cac4444b7b3f4e2b8c090ac337aaea40 md5: 11f59985f49df4620890f3e746ed7102 @@ -15544,6 +15569,29 @@ packages: run_exports: {} size: 846038 timestamp: 1778770337113 +- conda: https://conda.anaconda.org/conda-forge/noarch/formulaic-1.2.2-pyhd8ed1ab_0.conda + sha256: a6e065128ef1f2b6b2636268409155c1013460babb619e6e8415686305e776ea + md5: f18f43fb3e20ab309d46faebfb3348b0 + depends: + - interface_meta >=1.2 + - narwhals >=1.17 + - numpy >=1.20 + - pandas >=1.3 + - python >=3.10 + - scipy >=1.6 + - typing-extensions >=4.2 + - wrapt >=1.17 + constrains: + - polars >=1 + - pyarrow >=1 + - sympy >=1.3,!=1.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/formulaic?source=hash-mapping + run_exports: {} + size: 90142 + timestamp: 1780453843138 - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.2.0-pyhd8ed1ab_0.conda sha256: 0aa1cdc67a9fe75ea95b5644b734a756200d6ec9d0dff66530aec3d1c1e9df75 md5: b4754fb1bdcb70c8fd54f918301582c6 @@ -15726,6 +15774,17 @@ packages: run_exports: {} size: 13387 timestamp: 1760831448842 +- conda: https://conda.anaconda.org/conda-forge/noarch/interface_meta-1.3.0-pyhd8ed1ab_1.conda + sha256: cdd4377e9565f383e3e69f26b43fc1abc892fda591fa724af508e498c2dc0859 + md5: 97f07ae607e01877f0454fce4c833e40 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/interface-meta?source=hash-mapping + size: 18422 + timestamp: 1734284523576 - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.14.1-pyh53cf698_0.conda sha256: a3f76e06c31bcf1bda0f633d5c9f1c834286b4f6decc6626067a6cffee283318 md5: fbd58549b374103c1a80577f09a328ef @@ -16009,6 +16068,20 @@ packages: run_exports: {} size: 82472 timestamp: 1777722955579 +- conda: https://conda.anaconda.org/conda-forge/noarch/patsy-1.0.3-pyhcf101f3_0.conda + sha256: d204290be1e2d895ebd945ffd310ff00ea6a7670fe20a92a04ade7d197cdadf5 + md5: 13e1355debaf21a0b8c2726697ebb01b + depends: + - numpy >=1.4.0 + - packaging + - python >=3.10 + - python + license: BSD-2-Clause AND PSF-2.0 + purls: + - pkg:pypi/patsy?source=hash-mapping + run_exports: {} + size: 193838 + timestamp: 1788106018957 - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a md5: d0d408b1f18883a944376da5cf8101ea @@ -16491,18 +16564,6 @@ packages: run_exports: {} size: 146639 timestamp: 1777068997932 -- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda - sha256: 3f05db78cf8be33cf6dbc469664b8e3a01f3980d61d6d6bef48669b171896d8a - md5: eefc8d916bd2e708d76d40398ef9a1ee - depends: - - python >=3.10 - license: Apache-2.0 - license_family: APACHE - purls: - - pkg:pypi/tzdata?source=hash-mapping - run_exports: {} - size: 146862 - timestamp: 1783704822814 - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.10-8_cp310.conda build_number: 8 sha256: 7ad76fa396e4bde336872350124c0819032a9e8a0a40590744ff9527b54351c1 @@ -17037,6 +17098,17 @@ packages: run_exports: {} size: 115158 timestamp: 1780507822178 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.15.0-h396c80c_0.conda + sha256: 7c2df5721c742c2a47b2c8f960e718c930031663ac1174da67c1ed5999f7938c + md5: edd329d7d3a4ab45dcf905899a7a6115 + depends: + - typing_extensions ==4.15.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 91383 + timestamp: 1756220668932 - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.14.1-pyhe01879c_0.conda sha256: 4f52390e331ea8b9019b87effaebc4f80c6466d09f68453f52d5cdc2a3e1194f md5: e523f4f1e980ed7a4240d7e27e9ec81f @@ -20495,63 +20567,6 @@ packages: run_exports: {} size: 14597208 timestamp: 1778602856255 -- conda: https://conda.anaconda.org/conda-forge/osx-64/pandas-3.0.5-py314h99bb933_1.conda - sha256: 7910c507692b67567627edbed6ff0ce80bb800b704bd0b238729e4c64d9fbc4b - md5: 7c7abc0faf169efe0e103c62f2824857 - depends: - - python - - numpy >=1.26.0 - - python-dateutil >=2.8.2 - - libcxx >=19 - - __osx >=11.0 - - numpy >=1.23,<3 - - python_abi 3.14.* *_cp314 - constrains: - - adbc-driver-postgresql >=1.2.0 - - adbc-driver-sqlite >=1.2.0 - - beautifulsoup4 >=4.12.3 - - blosc >=1.21.3 - - bottleneck >=1.4.2 - - fastparquet >=2024.11.0 - - fsspec >=2024.10.0 - - gcsfs >=2024.10.0 - - html5lib >=1.1 - - hypothesis >=6.116.0 - - jinja2 >=3.1.5 - - lxml >=5.3.0 - - matplotlib >=3.9.3 - - numba >=0.60.0 - - numexpr >=2.10.2 - - odfpy >=1.4.1 - - openpyxl >=3.1.5 - - psycopg2 >=2.9.10 - - pyarrow >=13.0.0 - - pyiceberg >=0.8.1 - - pymysql >=1.1.1 - - pyqt5 >=5.15.9 - - pyreadstat >=1.2.8 - - pytables >=3.10.1 - - pytest >=8.3.4 - - pytest-xdist >=3.6.1 - - python-calamine >=0.3.0 - - pytz >=2024.2 - - pyxlsb >=1.0.10 - - qtpy >=2.4.2 - - scipy >=1.14.1 - - s3fs >=2024.10.0 - - sqlalchemy >=2.0.36 - - tabulate >=0.9.0 - - xarray >=2024.10.0 - - xlrd >=2.0.1 - - xlsxwriter >=3.2.0 - - zstandard >=0.23.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandas?source=hash-mapping - run_exports: {} - size: 14639133 - timestamp: 1785276464478 - conda: https://conda.anaconda.org/conda-forge/osx-64/pandoc-3.11-h694c41f_0.conda sha256: 0ef2d60b6d9ae28d4e70fae3fbb716e751db77743b6f5d18dc298fe13e280827 md5: 932c47502f555da85dcf6f562b3e36a5 @@ -21576,6 +21591,27 @@ packages: run_exports: {} size: 15667374 timestamp: 1781913667133 +- conda: https://conda.anaconda.org/conda-forge/osx-64/statsmodels-0.15.0-np2py314hc91a65a_2.conda + sha256: 8c115627f8aa864727e23f85a7858d9cef57ee88df4391967441318c09648d5d + md5: f2bce35ac57139912e622ceef1f20d20 + depends: + - python + - numpy >=1.23.5,<3 + - scipy >=1.8,!=1.9.2 + - pandas >=1.4,!=2.1.0 + - patsy >=0.5.6 + - packaging >=21.3 + - formulaic >=1.1.0 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + - numpy >=1.25,<3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/statsmodels?source=compressed-mapping + run_exports: {} + size: 13777604 + timestamp: 1790033664701 - conda: https://conda.anaconda.org/conda-forge/osx-64/tk-8.6.13-h7142dee_3.conda sha256: 7f0d9c320288532873e2d8486c331ec6d87919c9028208d3f6ac91dc8f99a67b md5: 6e6efb7463f8cef69dbcb4c2205bf60e @@ -21867,6 +21903,20 @@ packages: run_exports: {} size: 406478 timestamp: 1770909238815 +- conda: https://conda.anaconda.org/conda-forge/osx-64/wrapt-2.4.1-py314h17af80e_0.conda + sha256: dda78e5462fb86a2682d00dee62927d3c791ba8e4fbc0851c3460c571fd9db78 + md5: fef84b3d0694187c6c4642a3ebf78882 + depends: + - python + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/wrapt?source=compressed-mapping + run_exports: {} + size: 144915 + timestamp: 1789986067548 - conda: https://conda.anaconda.org/conda-forge/osx-64/xorg-libxau-1.0.12-h8616949_1.conda sha256: 928f28bd278c7da674b57d71b2e7f4ac4e7c7ce56b0bf0f60d6a074366a2e76d md5: 47f1b8b4a76ebd0cd22bd7153e54a4dc @@ -25343,64 +25393,6 @@ packages: run_exports: {} size: 14368928 timestamp: 1778602917992 -- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandas-3.0.5-py314he609de1_0.conda - sha256: 9db72f83b107fab7393e02c4005003c97c7a7aaffc33417e78aadb5a82056cd5 - md5: b1dde16791eca59c8316d0ad1487bff0 - depends: - - python - - numpy >=1.26.0 - - python-dateutil >=2.8.2 - - python 3.14.* *_cp314 - - libcxx >=19 - - __osx >=11.0 - - numpy >=1.23,<3 - - python_abi 3.14.* *_cp314 - constrains: - - adbc-driver-postgresql >=1.2.0 - - adbc-driver-sqlite >=1.2.0 - - beautifulsoup4 >=4.12.3 - - blosc >=1.21.3 - - bottleneck >=1.4.2 - - fastparquet >=2024.11.0 - - fsspec >=2024.10.0 - - gcsfs >=2024.10.0 - - html5lib >=1.1 - - hypothesis >=6.116.0 - - jinja2 >=3.1.5 - - lxml >=5.3.0 - - matplotlib >=3.9.3 - - numba >=0.60.0 - - numexpr >=2.10.2 - - odfpy >=1.4.1 - - openpyxl >=3.1.5 - - psycopg2 >=2.9.10 - - pyarrow >=13.0.0 - - pyiceberg >=0.8.1 - - pymysql >=1.1.1 - - pyqt5 >=5.15.9 - - pyreadstat >=1.2.8 - - pytables >=3.10.1 - - pytest >=8.3.4 - - pytest-xdist >=3.6.1 - - python-calamine >=0.3.0 - - pytz >=2024.2 - - pyxlsb >=1.0.10 - - qtpy >=2.4.2 - - scipy >=1.14.1 - - s3fs >=2024.10.0 - - sqlalchemy >=2.0.36 - - tabulate >=0.9.0 - - xarray >=2024.10.0 - - xlrd >=2.0.1 - - xlsxwriter >=3.2.0 - - zstandard >=0.23.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandas?source=hash-mapping - run_exports: {} - size: 14402418 - timestamp: 1784821943837 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandoc-3.11-hce30654_0.conda sha256: 1520d26e93867d62aa105d6f50e279c446d4bcd79f8db8d5b6a8442b148e8389 md5: a2f9df29541bc9c9ec08950fcedfea84 @@ -26455,6 +26447,27 @@ packages: run_exports: {} size: 14122215 timestamp: 1781912992503 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/statsmodels-0.15.0-np2py314h40f2a13_2.conda + sha256: 6762596d9b868d6dfb84e62feb1cd7a29d7b6bcbc38b54afdeac2d6875879413 + md5: ad5201d6bc5426933389149c98c7f08a + depends: + - python + - numpy >=1.23.5,<3 + - scipy >=1.8,!=1.9.2 + - pandas >=1.4,!=2.1.0 + - patsy >=0.5.6 + - packaging >=21.3 + - formulaic >=1.1.0 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + - numpy >=1.25,<3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/statsmodels?source=compressed-mapping + run_exports: {} + size: 13740298 + timestamp: 1790033597056 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-h010d191_3.conda sha256: 799cab4b6cde62f91f750149995d149bc9db525ec12595e8a1d91b9317f038b3 md5: a9d86bc62f39b94c4661716624eb21b0 @@ -26764,6 +26777,20 @@ packages: run_exports: {} size: 416130 timestamp: 1770909728445 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/wrapt-2.4.1-py314hd98292b_0.conda + sha256: 149b00f33024a5e25acc59195ecda340672ad3a17701b911a986a225ed6ade06 + md5: d36dc009656ba3dd1dcfed7890d7ac3b + depends: + - python + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/wrapt?source=compressed-mapping + run_exports: {} + size: 144486 + timestamp: 1789986025741 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-hb001647_2.conda sha256: cde7cecabb662b435e6537591e5845e870a0648887542d3e538c9f6c20dd6be5 md5: 610f557e75d6a0d3f676914da5822227 @@ -30708,65 +30735,6 @@ packages: run_exports: {} size: 14062915 timestamp: 1778602665890 -- conda: https://conda.anaconda.org/conda-forge/win-64/pandas-3.0.5-py314hf700ef7_1.conda - sha256: 399c1d7f7918986d9c1a646f4e789ac24749149598d16b529febc3361e9aad23 - md5: a519b50147401cf6bad2fd188028ad38 - depends: - - python - - numpy >=1.26.0 - - python-dateutil >=2.8.2 - - python-tzdata - - vc >=14.3,<15 - - vc14_runtime >=14.44.35208 - - ucrt >=10.0.20348.0 - - python_abi 3.14.* *_cp314 - - numpy >=1.23,<3 - constrains: - - adbc-driver-postgresql >=1.2.0 - - adbc-driver-sqlite >=1.2.0 - - beautifulsoup4 >=4.12.3 - - blosc >=1.21.3 - - bottleneck >=1.4.2 - - fastparquet >=2024.11.0 - - fsspec >=2024.10.0 - - gcsfs >=2024.10.0 - - html5lib >=1.1 - - hypothesis >=6.116.0 - - jinja2 >=3.1.5 - - lxml >=5.3.0 - - matplotlib >=3.9.3 - - numba >=0.60.0 - - numexpr >=2.10.2 - - odfpy >=1.4.1 - - openpyxl >=3.1.5 - - psycopg2 >=2.9.10 - - pyarrow >=13.0.0 - - pyiceberg >=0.8.1 - - pymysql >=1.1.1 - - pyqt5 >=5.15.9 - - pyreadstat >=1.2.8 - - pytables >=3.10.1 - - pytest >=8.3.4 - - pytest-xdist >=3.6.1 - - python-calamine >=0.3.0 - - pytz >=2024.2 - - pyxlsb >=1.0.10 - - qtpy >=2.4.2 - - scipy >=1.14.1 - - s3fs >=2024.10.0 - - sqlalchemy >=2.0.36 - - tabulate >=0.9.0 - - xarray >=2024.10.0 - - xlrd >=2.0.1 - - xlsxwriter >=3.2.0 - - zstandard >=0.23.0 - license: BSD-3-Clause - license_family: BSD - purls: - - pkg:pypi/pandas?source=hash-mapping - run_exports: {} - size: 14103773 - timestamp: 1785276287020 - conda: https://conda.anaconda.org/conda-forge/win-64/pandoc-3.11-h57928b3_0.conda sha256: 03b03e1c841f25e738bafe60e20cae65a98951683f14e72aae6a8ac359e0501c md5: 4587e6544ff96ef0ee50b042f9d3719d @@ -32126,6 +32094,29 @@ packages: run_exports: {} size: 15353018 timestamp: 1781914001107 +- conda: https://conda.anaconda.org/conda-forge/win-64/statsmodels-0.15.0-np2py314hea88fa1_2.conda + sha256: 13b1e307d29164a9f5d18de89cb8e3281026b0e514463fe0b1f05a2987ec8134 + md5: 9fc91bab302b183bcf3d6fee97e0a368 + depends: + - python + - numpy >=1.23.5,<3 + - scipy >=1.8,!=1.9.2 + - pandas >=1.4,!=2.1.0 + - patsy >=0.5.6 + - packaging >=21.3 + - formulaic >=1.1.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + - numpy >=1.25,<3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/statsmodels?source=compressed-mapping + run_exports: {} + size: 13612207 + timestamp: 1790033589104 - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2021.13.0-h62715c5_1.conda sha256: 03cc5442046485b03dd1120d0f49d35a7e522930a2ab82f275e938e17b07b302 md5: 9190dd0a23d925f7602f9628b3aed511 @@ -32610,6 +32601,22 @@ packages: run_exports: {} size: 20355 timestamp: 1781320968804 +- conda: https://conda.anaconda.org/conda-forge/win-64/wrapt-2.4.1-py314hc5dbbe4_0.conda + sha256: 87917bc5e8fa8cc18432c07ac8b1d7348df4cad957d741f8c618dd8f667f6ab8 + md5: 2c0ae1b4889d3ff02546a45ac3df3b8e + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/wrapt?source=compressed-mapping + run_exports: {} + size: 142909 + timestamp: 1789986015962 - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-kbproto-1.0.7-hcd874cb_1002.tar.bz2 sha256: 5b16e1ca1ecc0d2907f236bc4d8e6ecfd8417db013c862a01afb7f9d78e48c09 md5: 8d11c1dac4756ca57e78c1bfe173bba4 @@ -33186,6 +33193,96 @@ packages: version: 3.6.0 sha256: 43a0b8fd5a2928500110039e43a5eed8480b918967083ea48dc3ab9f13c4a7fb requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/35/61/47ae13476995cc8a40cd609e93e7cf11f273d8692925c2903cb6d38aa0d1/pandas-3.0.6-cp314-cp314-macosx_11_0_arm64.whl + name: pandas + version: 3.0.6 + sha256: ff51a4459ed036e93d1eb1bb5e6e7b28685d3cb6b7c12b91c05b31024e234729 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4,<9.1 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/3c/a7/552a7821597c632b907f7bfe8f36f9f572777af8ef8a48353041cf8e091a/scikit_learn-1.9.0-cp314-cp314-macosx_12_0_arm64.whl name: scikit-learn version: 1.9.0 @@ -33389,6 +33486,96 @@ packages: - pandas>=0.24.0 ; extra == 'pandas' - scikit-learn>=0.24.2 ; extra == 'scikit-learn' requires_python: '>=3.7' +- pypi: https://files.pythonhosted.org/packages/75/55/1a8875395b05ccd572cbca0b9255dcd2db6e6508e632a558c1a6884b39ad/pandas-3.0.6-cp314-cp314-macosx_10_15_x86_64.whl + name: pandas + version: 3.0.6 + sha256: ee913a91669056c1de1a6b733fbfeab711de9e54e3bee2dfa5fe79d9457247d1 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4,<9.1 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl name: scipy version: 1.18.0 @@ -33720,6 +33907,96 @@ packages: - scikit-learn ; extra == 'pyspark' - scikit-learn ; extra == 'scikit-learn' requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/b7/e9/f43410fada510b43fec09993c08f552086c3d247d3ee801a678f3cb10ea5/pandas-3.0.6-cp314-cp314-win_amd64.whl + name: pandas + version: 3.0.6 + sha256: 77ccbe5057aece6fc172b9b77f19c04335af6882bc2e10c8f3ee4e6bfb3da553 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4,<9.1 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/bd/6e/95b0e537de1f4d4301f76f944642c6da50d1511cc7b3d64dc418a66c7509/wcwidth-0.8.1-py3-none-any.whl name: wcwidth version: 0.8.1 @@ -33749,6 +34026,96 @@ packages: - scikit-learn ; extra == 'pyspark' - scikit-learn ; extra == 'scikit-learn' requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/ca/ba/ffdcb19be4ff6bfe7d969e7cef2c567c633df5a3a1cc1053394ad053bca8/pandas-3.0.6-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.0.6 + sha256: 62f51d7f651c8054c5e82a69265c98082e795d1442df7ca6edc3a545d61214b1 + requires_dist: + - numpy>=1.26.0 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.8.2 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - hypothesis>=6.116.0 ; extra == 'test' + - pytest>=8.3.4,<9.1 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=13.0.0 ; extra == 'pyarrow' + - bottleneck>=1.4.2 ; extra == 'performance' + - numba>=0.60.0 ; extra == 'performance' + - numexpr>=2.10.2 ; extra == 'performance' + - scipy>=1.14.1 ; extra == 'computation' + - xarray>=2024.10.0 ; extra == 'computation' + - fsspec>=2024.10.0 ; extra == 'fss' + - s3fs>=2024.10.0 ; extra == 'aws' + - gcsfs>=2024.10.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.3.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.1 ; extra == 'excel' + - xlsxwriter>=3.2.0 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.8.1 ; extra == 'iceberg' + - tables>=3.10.1 ; extra == 'hdf5' + - pyreadstat>=1.2.8 ; extra == 'spss' + - sqlalchemy>=2.0.36 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.2.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.36 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.36 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.2.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.2.0 ; extra == 'sql-other' + - beautifulsoup4>=4.12.3 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'html' + - lxml>=5.3.0 ; extra == 'xml' + - matplotlib>=3.9.3 ; extra == 'plot' + - jinja2>=3.1.5 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.9 ; extra == 'clipboard' + - qtpy>=2.4.2 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.2.0 ; extra == 'all' + - adbc-driver-sqlite>=1.2.0 ; extra == 'all' + - beautifulsoup4>=4.12.3 ; extra == 'all' + - bottleneck>=1.4.2 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2024.10.0 ; extra == 'all' + - gcsfs>=2024.10.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - hypothesis>=6.116.0 ; extra == 'all' + - jinja2>=3.1.5 ; extra == 'all' + - lxml>=5.3.0 ; extra == 'all' + - matplotlib>=3.9.3 ; extra == 'all' + - numba>=0.60.0 ; extra == 'all' + - numexpr>=2.10.2 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=13.0.0 ; extra == 'all' + - pyiceberg>=0.8.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.9 ; extra == 'all' + - pyreadstat>=1.2.8 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.3.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.2 ; extra == 'all' + - scipy>=1.14.1 ; extra == 'all' + - s3fs>=2024.10.0 ; extra == 'all' + - sqlalchemy>=2.0.36 ; extra == 'all' + - tables>=3.10.1 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2024.10.0 ; extra == 'all' + - xlrd>=2.0.1 ; extra == 'all' + - xlsxwriter>=3.2.0 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' - pypi: https://files.pythonhosted.org/packages/cd/05/7213965863cba1ed0150ad045bceed6276a1afaaaedbaeff4699ec4f0ccb/lightgbm-4.7.0-py3-none-macosx_10_15_x86_64.whl name: lightgbm version: 4.7.0 @@ -34048,6 +34415,11 @@ packages: - scikit-learn ; extra == 'pyspark' - cloudpickle ; extra == 'pyspark' requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/f9/bc/8737e8d54cf51106118039b83f485a4783112fab49ea9d044b234978a46e/tzdata-2026.4-py2.py3-none-any.whl + name: tzdata + version: '2026.4' + sha256: c2169a8b0a7a5e9674da5a135ccdfb2b3e671b333ed9fed17b41f73c34476e81 + requires_python: '>=2' - pypi: https://files.pythonhosted.org/packages/fc/72/3b68983c0215ef65d48e9eeb1f168c3c6e3d62a61ece605de3209c79cae1/xgboost-3.3.0-py3-none-macosx_10_15_x86_64.whl name: xgboost version: 3.3.0 @@ -34077,61 +34449,501 @@ packages: - pytest-lazy-fixtures ; extra == 'tests' - pytest>=9 ; extra == 'tests' requires_python: '>=3.10' +- pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.1.0.dev0+2043.g7aac401536 + index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + requires_dist: + - numpy>=2.0.2 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.9.0 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=16.0.0 ; extra == 'pyarrow' + - bottleneck>=1.5.0 ; extra == 'performance' + - numba>=0.61.2 ; extra == 'performance' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'performance' + - scipy>=1.16.1 ; extra == 'computation' + - xarray>=2025.7.1 ; extra == 'computation' + - fsspec>=2025.7.0 ; extra == 'fss' + - s3fs>=2025.7.0 ; extra == 'aws' + - gcsfs>=2025.7.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.4.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.2 ; extra == 'excel' + - xlsxwriter>=3.2.5 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.9.1 ; extra == 'iceberg' + - tables>=3.10.2 ; extra == 'hdf5' + - pyreadstat>=1.3.0 ; extra == 'spss' + - sqlalchemy>=2.0.42 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.7.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.42 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.42 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.7.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.7.0 ; extra == 'sql-other' + - beautifulsoup4>=4.13.4 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'xml' + - matplotlib>=3.10.5 ; extra == 'plot' + - jinja2>=3.1.6 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.11 ; extra == 'clipboard' + - qtpy>=2.4.3 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.7.0 ; extra == 'all' + - adbc-driver-sqlite>=1.7.0 ; extra == 'all' + - beautifulsoup4>=4.13.4 ; extra == 'all' + - bottleneck>=1.5.0 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2025.7.0 ; extra == 'all' + - gcsfs>=2025.7.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - jinja2>=3.1.6 ; extra == 'all' + - lxml>=6.0.0 ; extra == 'all' + - matplotlib>=3.10.5 ; extra == 'all' + - numba>=0.61.2 ; extra == 'all' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=16.0.0 ; extra == 'all' + - pyiceberg>=0.9.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.11 ; extra == 'all' + - pyreadstat>=1.3.0 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.4.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.3 ; extra == 'all' + - scipy>=1.16.1 ; extra == 'all' + - s3fs>=2025.7.0 ; extra == 'all' + - sqlalchemy>=2.0.42 ; extra == 'all' + - tables>=3.10.2 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2025.7.1 ; extra == 'all' + - xlrd>=2.0.2 ; extra == 'all' + - xlsxwriter>=3.2.5 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-macosx_10_15_x86_64.whl + name: pandas + version: 3.1.0.dev0+2043.g7aac401536 + index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + requires_dist: + - numpy>=2.0.2 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.9.0 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=16.0.0 ; extra == 'pyarrow' + - bottleneck>=1.5.0 ; extra == 'performance' + - numba>=0.61.2 ; extra == 'performance' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'performance' + - scipy>=1.16.1 ; extra == 'computation' + - xarray>=2025.7.1 ; extra == 'computation' + - fsspec>=2025.7.0 ; extra == 'fss' + - s3fs>=2025.7.0 ; extra == 'aws' + - gcsfs>=2025.7.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.4.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.2 ; extra == 'excel' + - xlsxwriter>=3.2.5 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.9.1 ; extra == 'iceberg' + - tables>=3.10.2 ; extra == 'hdf5' + - pyreadstat>=1.3.0 ; extra == 'spss' + - sqlalchemy>=2.0.42 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.7.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.42 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.42 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.7.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.7.0 ; extra == 'sql-other' + - beautifulsoup4>=4.13.4 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'xml' + - matplotlib>=3.10.5 ; extra == 'plot' + - jinja2>=3.1.6 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.11 ; extra == 'clipboard' + - qtpy>=2.4.3 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.7.0 ; extra == 'all' + - adbc-driver-sqlite>=1.7.0 ; extra == 'all' + - beautifulsoup4>=4.13.4 ; extra == 'all' + - bottleneck>=1.5.0 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2025.7.0 ; extra == 'all' + - gcsfs>=2025.7.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - jinja2>=3.1.6 ; extra == 'all' + - lxml>=6.0.0 ; extra == 'all' + - matplotlib>=3.10.5 ; extra == 'all' + - numba>=0.61.2 ; extra == 'all' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=16.0.0 ; extra == 'all' + - pyiceberg>=0.9.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.11 ; extra == 'all' + - pyreadstat>=1.3.0 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.4.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.3 ; extra == 'all' + - scipy>=1.16.1 ; extra == 'all' + - s3fs>=2025.7.0 ; extra == 'all' + - sqlalchemy>=2.0.42 ; extra == 'all' + - tables>=3.10.2 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2025.7.1 ; extra == 'all' + - xlrd>=2.0.2 ; extra == 'all' + - xlsxwriter>=3.2.5 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-macosx_11_0_arm64.whl + name: pandas + version: 3.1.0.dev0+2043.g7aac401536 + index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + requires_dist: + - numpy>=2.0.2 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.9.0 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=16.0.0 ; extra == 'pyarrow' + - bottleneck>=1.5.0 ; extra == 'performance' + - numba>=0.61.2 ; extra == 'performance' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'performance' + - scipy>=1.16.1 ; extra == 'computation' + - xarray>=2025.7.1 ; extra == 'computation' + - fsspec>=2025.7.0 ; extra == 'fss' + - s3fs>=2025.7.0 ; extra == 'aws' + - gcsfs>=2025.7.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.4.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.2 ; extra == 'excel' + - xlsxwriter>=3.2.5 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.9.1 ; extra == 'iceberg' + - tables>=3.10.2 ; extra == 'hdf5' + - pyreadstat>=1.3.0 ; extra == 'spss' + - sqlalchemy>=2.0.42 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.7.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.42 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.42 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.7.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.7.0 ; extra == 'sql-other' + - beautifulsoup4>=4.13.4 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'xml' + - matplotlib>=3.10.5 ; extra == 'plot' + - jinja2>=3.1.6 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.11 ; extra == 'clipboard' + - qtpy>=2.4.3 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.7.0 ; extra == 'all' + - adbc-driver-sqlite>=1.7.0 ; extra == 'all' + - beautifulsoup4>=4.13.4 ; extra == 'all' + - bottleneck>=1.5.0 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2025.7.0 ; extra == 'all' + - gcsfs>=2025.7.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - jinja2>=3.1.6 ; extra == 'all' + - lxml>=6.0.0 ; extra == 'all' + - matplotlib>=3.10.5 ; extra == 'all' + - numba>=0.61.2 ; extra == 'all' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=16.0.0 ; extra == 'all' + - pyiceberg>=0.9.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.11 ; extra == 'all' + - pyreadstat>=1.3.0 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.4.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.3 ; extra == 'all' + - scipy>=1.16.1 ; extra == 'all' + - s3fs>=2025.7.0 ; extra == 'all' + - sqlalchemy>=2.0.42 ; extra == 'all' + - tables>=3.10.2 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2025.7.1 ; extra == 'all' + - xlrd>=2.0.2 ; extra == 'all' + - xlsxwriter>=3.2.5 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl + name: pandas + version: 3.1.0.dev0+2043.g7aac401536 + index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + requires_dist: + - numpy>=2.0.2 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.9.0 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=16.0.0 ; extra == 'pyarrow' + - bottleneck>=1.5.0 ; extra == 'performance' + - numba>=0.61.2 ; extra == 'performance' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'performance' + - scipy>=1.16.1 ; extra == 'computation' + - xarray>=2025.7.1 ; extra == 'computation' + - fsspec>=2025.7.0 ; extra == 'fss' + - s3fs>=2025.7.0 ; extra == 'aws' + - gcsfs>=2025.7.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.4.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.2 ; extra == 'excel' + - xlsxwriter>=3.2.5 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.9.1 ; extra == 'iceberg' + - tables>=3.10.2 ; extra == 'hdf5' + - pyreadstat>=1.3.0 ; extra == 'spss' + - sqlalchemy>=2.0.42 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.7.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.42 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.42 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.7.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.7.0 ; extra == 'sql-other' + - beautifulsoup4>=4.13.4 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'xml' + - matplotlib>=3.10.5 ; extra == 'plot' + - jinja2>=3.1.6 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.11 ; extra == 'clipboard' + - qtpy>=2.4.3 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.7.0 ; extra == 'all' + - adbc-driver-sqlite>=1.7.0 ; extra == 'all' + - beautifulsoup4>=4.13.4 ; extra == 'all' + - bottleneck>=1.5.0 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2025.7.0 ; extra == 'all' + - gcsfs>=2025.7.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - jinja2>=3.1.6 ; extra == 'all' + - lxml>=6.0.0 ; extra == 'all' + - matplotlib>=3.10.5 ; extra == 'all' + - numba>=0.61.2 ; extra == 'all' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=16.0.0 ; extra == 'all' + - pyiceberg>=0.9.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.11 ; extra == 'all' + - pyreadstat>=1.3.0 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.4.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.3 ; extra == 'all' + - scipy>=1.16.1 ; extra == 'all' + - s3fs>=2025.7.0 ; extra == 'all' + - sqlalchemy>=2.0.42 ; extra == 'all' + - tables>=3.10.2 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2025.7.1 ; extra == 'all' + - xlrd>=2.0.2 ; extra == 'all' + - xlsxwriter>=3.2.5 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/pandas/3.1.0.dev0+2043.g7aac401536/pandas-3.1.0.dev0+2043.g7aac401536-cp314-cp314-win_amd64.whl + name: pandas + version: 3.1.0.dev0+2043.g7aac401536 + index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + requires_dist: + - numpy>=2.0.2 ; python_full_version < '3.14' + - numpy>=2.3.3 ; python_full_version >= '3.14' + - python-dateutil>=2.9.0 + - tzdata ; sys_platform == 'win32' + - tzdata ; sys_platform == 'emscripten' + - pytest>=8.3.4 ; extra == 'test' + - pytest-xdist>=3.6.1 ; extra == 'test' + - pyarrow>=16.0.0 ; extra == 'pyarrow' + - bottleneck>=1.5.0 ; extra == 'performance' + - numba>=0.61.2 ; extra == 'performance' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'performance' + - scipy>=1.16.1 ; extra == 'computation' + - xarray>=2025.7.1 ; extra == 'computation' + - fsspec>=2025.7.0 ; extra == 'fss' + - s3fs>=2025.7.0 ; extra == 'aws' + - gcsfs>=2025.7.0 ; extra == 'gcp' + - odfpy>=1.4.1 ; extra == 'excel' + - openpyxl>=3.1.5 ; extra == 'excel' + - python-calamine>=0.4.0 ; extra == 'excel' + - pyxlsb>=1.0.10 ; extra == 'excel' + - xlrd>=2.0.2 ; extra == 'excel' + - xlsxwriter>=3.2.5 ; extra == 'excel' + - pyarrow>=13.0.0 ; extra == 'parquet' + - pyarrow>=13.0.0 ; extra == 'feather' + - pyiceberg>=0.9.1 ; extra == 'iceberg' + - tables>=3.10.2 ; extra == 'hdf5' + - pyreadstat>=1.3.0 ; extra == 'spss' + - sqlalchemy>=2.0.42 ; extra == 'postgresql' + - psycopg2>=2.9.10 ; extra == 'postgresql' + - adbc-driver-postgresql>=1.7.0 ; extra == 'postgresql' + - sqlalchemy>=2.0.42 ; extra == 'mysql' + - pymysql>=1.1.1 ; extra == 'mysql' + - sqlalchemy>=2.0.42 ; extra == 'sql-other' + - adbc-driver-postgresql>=1.7.0 ; extra == 'sql-other' + - adbc-driver-sqlite>=1.7.0 ; extra == 'sql-other' + - beautifulsoup4>=4.13.4 ; extra == 'html' + - html5lib>=1.1 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'html' + - lxml>=6.0.0 ; extra == 'xml' + - matplotlib>=3.10.5 ; extra == 'plot' + - jinja2>=3.1.6 ; extra == 'output-formatting' + - tabulate>=0.9.0 ; extra == 'output-formatting' + - pyqt5>=5.15.11 ; extra == 'clipboard' + - qtpy>=2.4.3 ; extra == 'clipboard' + - zstandard>=0.23.0 ; extra == 'compression' + - pytz>=2020.1 ; extra == 'timezone' + - adbc-driver-postgresql>=1.7.0 ; extra == 'all' + - adbc-driver-sqlite>=1.7.0 ; extra == 'all' + - beautifulsoup4>=4.13.4 ; extra == 'all' + - bottleneck>=1.5.0 ; extra == 'all' + - fastparquet>=2024.11.0 ; extra == 'all' + - fsspec>=2025.7.0 ; extra == 'all' + - gcsfs>=2025.7.0 ; extra == 'all' + - html5lib>=1.1 ; extra == 'all' + - jinja2>=3.1.6 ; extra == 'all' + - lxml>=6.0.0 ; extra == 'all' + - matplotlib>=3.10.5 ; extra == 'all' + - numba>=0.61.2 ; extra == 'all' + - numexpr>=2.11.0,!=2.14.1 ; extra == 'all' + - odfpy>=1.4.1 ; extra == 'all' + - openpyxl>=3.1.5 ; extra == 'all' + - psycopg2>=2.9.10 ; extra == 'all' + - pyarrow>=16.0.0 ; extra == 'all' + - pyiceberg>=0.9.1 ; extra == 'all' + - pymysql>=1.1.1 ; extra == 'all' + - pyqt5>=5.15.11 ; extra == 'all' + - pyreadstat>=1.3.0 ; extra == 'all' + - pytest>=8.3.4 ; extra == 'all' + - pytest-xdist>=3.6.1 ; extra == 'all' + - python-calamine>=0.4.0 ; extra == 'all' + - pytz>=2020.1 ; extra == 'all' + - pyxlsb>=1.0.10 ; extra == 'all' + - qtpy>=2.4.3 ; extra == 'all' + - scipy>=1.16.1 ; extra == 'all' + - s3fs>=2025.7.0 ; extra == 'all' + - sqlalchemy>=2.0.42 ; extra == 'all' + - tables>=3.10.2 ; extra == 'all' + - tabulate>=0.9.0 ; extra == 'all' + - xarray>=2025.7.1 ; extra == 'all' + - xlrd>=2.0.2 ; extra == 'all' + - xlsxwriter>=3.2.5 ; extra == 'all' + - zstandard>=0.23.0 ; extra == 'all' + requires_python: '>=3.11' - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scikit-learn version: 1.10.dev0 index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple requires_dist: - - numpy>=1.24.1 - - scipy>=1.10.0 + - numpy>=1.26.0 + - scipy>=1.11.4 - joblib>=1.4.0 - narwhals>=2.0.1 - threadpoolctl>=3.5.0 - requires_python: '>=3.11' + requires_python: '>=3.12' - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-macosx_10_15_x86_64.whl name: scikit-learn version: 1.10.dev0 index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple requires_dist: - - numpy>=1.24.1 - - scipy>=1.10.0 + - numpy>=1.26.0 + - scipy>=1.11.4 - joblib>=1.4.0 - narwhals>=2.0.1 - threadpoolctl>=3.5.0 - requires_python: '>=3.11' + requires_python: '>=3.12' - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-macosx_12_0_arm64.whl name: scikit-learn version: 1.10.dev0 index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple requires_dist: - - numpy>=1.24.1 - - scipy>=1.10.0 + - numpy>=1.26.0 + - scipy>=1.11.4 - joblib>=1.4.0 - narwhals>=2.0.1 - threadpoolctl>=3.5.0 - requires_python: '>=3.11' + requires_python: '>=3.12' - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scikit-learn version: 1.10.dev0 index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple requires_dist: - - numpy>=1.24.1 - - scipy>=1.10.0 + - numpy>=1.26.0 + - scipy>=1.11.4 - joblib>=1.4.0 - narwhals>=2.0.1 - threadpoolctl>=3.5.0 - requires_python: '>=3.11' + requires_python: '>=3.12' - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scikit-learn/1.10.dev0/scikit_learn-1.10.dev0-cp314-cp314-win_amd64.whl name: scikit-learn version: 1.10.dev0 index: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple requires_dist: - - numpy>=1.24.1 - - scipy>=1.10.0 + - numpy>=1.26.0 + - scipy>=1.11.4 - joblib>=1.4.0 - narwhals>=2.0.1 - threadpoolctl>=3.5.0 - requires_python: '>=3.11' + requires_python: '>=3.12' - pypi: https://pypi.anaconda.org/scientific-python-nightly-wheels/simple/scipy/2.0.0.dev0/scipy-2.0.0.dev0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl name: scipy version: 2.0.0.dev0 diff --git a/pyproject.toml b/pyproject.toml index a0ca2667..645b57c9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,7 +131,6 @@ skops = { path = ".", editable = true } [tool.pixi.feature.docs.dependencies] # To be synced with the versions in docs/requirements.txt matplotlib = ">=3.3" -pandas = ">=1" sphinx = ">=3.2.0" sphinx-gallery = ">=0.7.0" sphinx-rtd-theme = ">=1" @@ -141,8 +140,10 @@ sphinx-issues = ">=1.2.0" [tool.pixi.feature.docs.pypi-dependencies] # everything that depends on scikit-learn needs to be a pypi dependency so that this -# spec is compatible with the nightly build environment. +# spec is compatible with the nightly build environment. The same holds for pandas, +# whose dev version the nightly build environment installs from pypi. fairlearn = ">=0.7.0" +pandas = ">=2" [tool.pixi.feature.tests.dependencies] pytest = ">=7" @@ -151,7 +152,6 @@ flaky = ">=3.7.0" pandoc = ">=3.6.4" rich = ">=12" matplotlib = ">=3.3" -pandas = ">=1" [tool.pixi.feature.tests.pypi-dependencies] # these are packages that require scikit-learn. They need to be as a pypi dependency @@ -159,6 +159,11 @@ pandas = ">=1" # when installing pre-release nightly release. lightgbm = ">=3" xgboost = ">=1.6" +# skops.io supports pandas 2.0 and later; each CI environment pins one minor +# version so that the whole range is tested, see the sklearn* features below. +# A pypi dependency for the same reason as above: the nightly environment +# installs the dev version of pandas from pypi. +pandas = ">=2" [tool.pixi.feature.lint.dependencies] pre-commit = "*" @@ -242,6 +247,8 @@ numpy = "~=2.5.0" scipy = "~=1.18.0" catboost = ">=1.0" quantile-forest = "~=1.4.0" +# keeps pandas objects in fitted attributes, see the test for issue #450 +category_encoders = ">=2.6" python = "~=3.14.0" # [tool.pixi.feature.sklearn17] @@ -260,7 +267,9 @@ extra-index-urls = ["https://pypi.anaconda.org/scientific-python-nightly-wheels/ # The version value here needs to be exact, hence == instead of ~= scikit-learn = "==1.10.dev0" fairlearn = "*" -pandas = "*" +# The dev version of pandas from the nightly index; "*" would pick the latest +# release, since pre-releases are only considered when named explicitly. +pandas = "==3.1.0.dev0" numpy = "*" scipy = "*" diff --git a/skops/io/_pandas.py b/skops/io/_pandas.py new file mode 100644 index 00000000..4dead483 --- /dev/null +++ b/skops/io/_pandas.py @@ -0,0 +1,551 @@ +"""Persistence of pandas objects. + +pandas objects are not persisted through ``__reduce__`` or ``__getstate__``: +those expose internals such as block managers, index engines and reference +trackers, which change between pandas versions and cannot be rebuilt from data +alone. Instead, every object is taken apart into the public pieces its +constructor accepts, and rebuilt by calling that constructor with an explicit +dtype, so that no type inference happens on load: + +- an :class:`~pandas.Index` is stored as its values and name, +- a :class:`~pandas.Series` as its values, index and name, +- a :class:`~pandas.DataFrame` as its columns, index and one array per column, +- extension arrays as the numpy arrays or scalars they are made of, plus their + dtype, and extension dtypes as their string representation. + +Values with a numpy dtype are stored as numpy arrays, everything else as lists +of scalars. + +pandas is optional and slow to import, so ``skops.io`` does not import it. The +``get_state`` handlers are registered on the first dump after the user has +imported pandas, see :func:`register_if_imported`, and the nodes only import +pandas when they construct an object. + +Not preserved: the ``freq`` of datetime-like indexes and arrays, the ``attrs`` +and ``flags`` of a Series or DataFrame, and the storage, python or pyarrow, of +a string dtype, which is an environment choice over the same values. +""" + +from __future__ import annotations + +import sys +import warnings +from typing import Any + +import numpy as np + +from ._audit import Node, get_tree +from ._general import JsonNode, ListNode +from ._numpy import NdArrayNode +from ._protocol import PROTOCOL +from ._trusted_types import PANDAS_TYPE_NAMES +from ._utils import ( + LoadContext, + SaveContext, + TrustedTypes, + _get_state, + get_module, + get_state, + gettype, +) +from .exceptions import UnsupportedTypeException + + +def _public_module(cls: type) -> str: + # The public module of a pandas class: "pandas.arrays" for the array + # classes and "pandas" for everything else. pandas 3 reports these as + # ``__module__`` already, but older versions report the defining module, + # e.g. ``pandas.core.series``, and a file must not depend on that: the + # public name is what the default trusted list knows, and what stays + # importable across versions. Deprecated aliases such as + # ``pandas.arrays.PandasArray`` warn when accessed, hence the suppression. + import pandas as pd + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + for module in (pd.arrays, pd): + if getattr(module, cls.__name__, None) is cls: + return module.__name__ + return get_module(cls) + + +# Classes that later pandas versions renamed, mapped to their current name, so +# that a file never names a class the loading version may not have. +_RENAMED_CLASSES = { + # renamed in pandas 2.1 + "PandasArray": "NumpyExtensionArray", +} + + +def _pandas_state( + obj: Any, loader: str, content: dict[str, Any], save_context: SaveContext +) -> dict[str, Any]: + cls = type(obj) + # The nodes below rebuild objects with the pandas constructors, so an + # instance of a subclass defined by another library would silently be + # loaded as its pandas base class. Refuse those instead. + if cls.__module__.partition(".")[0] != "pandas": + raise UnsupportedTypeException( + f"{get_module(cls)}.{cls.__name__} is a subclass of a pandas type" + " defined outside pandas, which is not supported: it would be loaded" + " as its pandas base class." + ) + + return { + "__class__": _RENAMED_CLASSES.get(cls.__name__, cls.__name__), + "__module__": _public_module(cls), + "__loader__": loader, + "content": { + key: get_state(value, save_context) for key, value in content.items() + }, + } + + +def _values(obj: Any) -> Any: + # The array behind an Index or Series: a numpy array for numpy dtypes, the + # extension array otherwise. + if isinstance(obj.dtype, np.dtype): + return obj.to_numpy() + return obj.array + + +def index_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = {"values": _values(obj), "name": obj.name} + return _pandas_state(obj, "PandasIndexNode", content, save_context) + + +def range_index_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = {"start": obj.start, "stop": obj.stop, "step": obj.step, "name": obj.name} + return _pandas_state(obj, "PandasRangeIndexNode", content, save_context) + + +def multi_index_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = { + "levels": list(obj.levels), + "codes": list(obj.codes), + "sortorder": obj.sortorder, + "names": list(obj.names), + } + return _pandas_state(obj, "PandasMultiIndexNode", content, save_context) + + +def series_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = {"values": _values(obj), "index": obj.index, "name": obj.name} + return _pandas_state(obj, "PandasSeriesNode", content, save_context) + + +def dataframe_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = { + "columns": obj.columns, + "index": obj.index, + "data": [_values(obj.iloc[:, i]) for i in range(obj.shape[1])], + } + return _pandas_state(obj, "PandasDataFrameNode", content, save_context) + + +def numpy_backed_array_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + # NumpyExtensionArray, DatetimeArray and TimedeltaArray wrap a numpy array. + # Time zone aware datetimes are stored as naive UTC values plus the zone, + # since ``to_numpy`` would otherwise give an array of Timestamp objects. + tz = getattr(obj, "tz", None) + values = obj if tz is None else obj.tz_convert("UTC").tz_localize(None) + content = {"values": values.to_numpy(), "tz": None if tz is None else str(tz)} + return _pandas_state(obj, "PandasNumpyBackedArrayNode", content, save_context) + + +def masked_array_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + # IntegerArray, FloatingArray and BooleanArray: a numpy array of values and + # a boolean mask of the missing entries, which is what their constructor + # takes. Masked positions hold arbitrary values, so they are zeroed. + numpy_dtype = obj.dtype.numpy_dtype + content = { + "values": obj.to_numpy(dtype=numpy_dtype, na_value=numpy_dtype.type(0)), + "mask": obj.isna(), + } + return _pandas_state(obj, "PandasMaskedArrayNode", content, save_context) + + +def categorical_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = {"codes": obj.codes, "dtype": obj.dtype} + return _pandas_state(obj, "PandasCategoricalNode", content, save_context) + + +def period_array_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = {"ordinals": obj.asi8, "dtype": obj.dtype} + return _pandas_state(obj, "PandasPeriodArrayNode", content, save_context) + + +def interval_array_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + content = {"left": obj.left, "right": obj.right, "closed": obj.closed} + return _pandas_state(obj, "PandasIntervalArrayNode", content, save_context) + + +def extension_array_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + # Any other extension array, e.g. string, sparse or pyarrow backed arrays: + # stored as its scalars, with ``None`` for missing values, plus its dtype. + content = {"values": obj.to_numpy(dtype=object, na_value=None), "dtype": obj.dtype} + return _pandas_state(obj, "PandasExtensionArrayNode", content, save_context) + + +def extension_dtype_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + # Extension dtypes are rebuilt from their string form, e.g. "Int64", + # "datetime64[ns, UTC]" or "period[M]". + content = {"name": str(obj)} + return _pandas_state(obj, "PandasExtensionDtypeNode", content, save_context) + + +def categorical_dtype_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + # The string form of a categorical dtype does not include its categories. + content = {"categories": obj.categories, "ordered": obj.ordered} + return _pandas_state(obj, "PandasCategoricalDtypeNode", content, save_context) + + +def sparse_dtype_get_state(obj: Any, save_context: SaveContext) -> dict[str, Any]: + # The string form of a sparse dtype can only be parsed back when the fill + # value is the default one for its subtype. + content = {"subtype": str(obj.subtype), "fill_value": obj.fill_value} + return _pandas_state(obj, "PandasSparseDtypeNode", content, save_context) + + +class _PandasNode(Node): + """Base class of the pandas nodes. + + The children are the entries of ``state["content"]``. ``_allowed_types`` + of each subclass names every entry and the node types it may hold, which + is checked while the file is read, and ``_construct`` builds the object + from the constructed children. + """ + + def __init__( + self, + state: dict[str, Any], + load_context: LoadContext, + trusted: TrustedTypes | None = None, + ) -> None: + super().__init__(state, load_context, trusted) + self.trusted = self._get_trusted(trusted, PANDAS_TYPE_NAMES) + allowed_types = self._allowed_types() + if set(state["content"]) != set(allowed_types): + raise ValueError( + f"Expected the entries {sorted(allowed_types)}, got" + f" {sorted(state['content'])}. This is probably due to a corrupted" + " or a malicious file." + ) + self.content = { + key: get_tree( + value, load_context, trusted=trusted, allowed_types=allowed_types[key] + ) + for key, value in state["content"].items() + } + self.children = dict(self.content) + + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + # The node types each entry may hold, ``None`` for any: names for + # instance can be any hashable. + raise NotImplementedError + + def _construct_content(self) -> dict[str, Any]: + return {key: node.construct() for key, node in self.content.items()} + + +class PandasIndexNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"values": _ARRAY_NODES, "name": None} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + values = content["values"] + # The dtype is passed to prevent inference, and ``tupleize_cols`` keeps + # an object Index of tuples from becoming a MultiIndex. + return pd.Index( + values, dtype=values.dtype, name=content["name"], tupleize_cols=False + ) + + +class PandasRangeIndexNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return { + "start": (JsonNode,), + "stop": (JsonNode,), + "step": (JsonNode,), + "name": None, + } + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.RangeIndex( + content["start"], content["stop"], content["step"], name=content["name"] + ) + + +class PandasMultiIndexNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return { + "levels": (ListNode,), + "codes": (ListNode,), + "sortorder": (JsonNode,), + "names": (ListNode,), + } + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.MultiIndex( + levels=content["levels"], + codes=content["codes"], + sortorder=content["sortorder"], + names=content["names"], + ) + + +class PandasSeriesNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"values": _ARRAY_NODES, "index": _INDEX_NODES, "name": None} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + values = content["values"] + return pd.Series( + values, index=content["index"], dtype=values.dtype, name=content["name"] + ) + + +class PandasDataFrameNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"columns": _INDEX_NODES, "index": _INDEX_NODES, "data": (ListNode,)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + columns = [pd.Series(values, dtype=values.dtype) for values in content["data"]] + if columns: + frame = pd.concat(columns, axis=1, ignore_index=True) + frame.index = content["index"] + else: + frame = pd.DataFrame(index=content["index"]) + frame.columns = content["columns"] + return frame + + +class PandasNumpyBackedArrayNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"values": (NdArrayNode,), "tz": (JsonNode,)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + values = content["values"] + # A numpy dtype makes ``pd.array`` return a NumpyExtensionArray, which + # is what is wanted for all but datetime64 and timedelta64 arrays: for + # those pandas 2.0 also returns one when the dtype is given with a unit + # other than nanoseconds, while without a dtype every version infers + # a DatetimeArray or TimedeltaArray. + dtype = None if values.dtype.kind in "Mm" else values.dtype + array = pd.array(values, dtype=dtype) + if content["tz"] is not None: + array = array.tz_localize("UTC").tz_convert(content["tz"]) + return array + + +class PandasMaskedArrayNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"values": (NdArrayNode,), "mask": (NdArrayNode,)} + + def _construct(self): + content = self._construct_content() + cls = gettype(self.module_name, self.class_name) + return cls(content["values"], content["mask"]) + + +class PandasCategoricalNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"codes": (NdArrayNode,), "dtype": (PandasCategoricalDtypeNode,)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.Categorical.from_codes(content["codes"], dtype=content["dtype"]) + + +class PandasPeriodArrayNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"ordinals": (NdArrayNode,), "dtype": (PandasExtensionDtypeNode,)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.arrays.PeriodArray(content["ordinals"], dtype=content["dtype"]) + + +class PandasIntervalArrayNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"left": _INDEX_NODES, "right": _INDEX_NODES, "closed": (JsonNode,)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.arrays.IntervalArray.from_arrays( + content["left"], content["right"], closed=content["closed"] + ) + + +class PandasExtensionArrayNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"values": (NdArrayNode,), "dtype": _DTYPE_NODES} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.array(content["values"], dtype=content["dtype"]) + + +class PandasExtensionDtypeNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"name": (JsonNode,)} + + def _construct(self): + import pandas as pd + + name = self._construct_content()["name"] + # The declared, trusted dtype class parses the name itself. + # ``pandas.api.types.pandas_dtype`` would look the name up in pandas' + # registry of extension dtypes instead, where any imported library can + # register one, and run that library's code for a name from the file. + cls = gettype(self.module_name, self.class_name) + if not issubclass(cls, pd.api.extensions.ExtensionDtype): + raise ValueError( + f"{self.module_name}.{self.class_name} is not a pandas extension" + " dtype. This is probably due to a corrupted or a malicious file." + ) + try: + return cls.construct_from_string(name) + except TypeError: + if name == "str" and cls is pd.StringDtype: + # "str" is the default string dtype of pandas 3. Older versions + # do not know it, and keep strings in object arrays instead, + # which is what the values are stored as. + return np.dtype(object) + raise + + +class PandasCategoricalDtypeNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"categories": _INDEX_NODES + (JsonNode,), "ordered": (JsonNode,)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + return pd.CategoricalDtype(content["categories"], ordered=content["ordered"]) + + +class PandasSparseDtypeNode(_PandasNode): + def _allowed_types(self) -> dict[str, tuple[type[Node], ...] | None]: + return {"subtype": (JsonNode,), "fill_value": (JsonNode, NdArrayNode)} + + def _construct(self): + import pandas as pd + + content = self._construct_content() + # numpy parses the subtype, so that the name from the file is not + # looked up in pandas' registry of extension dtypes + return pd.SparseDtype(np.dtype(content["subtype"]), content["fill_value"]) + + +# The node types the values, the index and the dtype of a pandas object may be +# stored as. +_ARRAY_NODES = ( + NdArrayNode, + PandasNumpyBackedArrayNode, + PandasMaskedArrayNode, + PandasCategoricalNode, + PandasPeriodArrayNode, + PandasIntervalArrayNode, + PandasExtensionArrayNode, +) +_INDEX_NODES = (PandasIndexNode, PandasRangeIndexNode, PandasMultiIndexNode) +_DTYPE_NODES = ( + PandasExtensionDtypeNode, + PandasCategoricalDtypeNode, + PandasSparseDtypeNode, +) + + +_registered = False + + +def register_if_imported() -> None: + """Register the ``get_state`` handlers of pandas types, if pandas is imported. + + This is called before every dump. An object can only contain pandas + objects if pandas has been imported, so checking ``sys.modules`` is + enough, and skops itself never imports pandas. + """ + global _registered + if _registered or "pandas" not in sys.modules: + return + + import pandas as pd + + dispatch_functions = [ + (pd.Index, index_get_state), + (pd.RangeIndex, range_index_get_state), + (pd.MultiIndex, multi_index_get_state), + (pd.Series, series_get_state), + (pd.DataFrame, dataframe_get_state), + (pd.api.extensions.ExtensionArray, extension_array_get_state), + (pd.arrays.DatetimeArray, numpy_backed_array_get_state), + (pd.arrays.TimedeltaArray, numpy_backed_array_get_state), + (pd.arrays.IntegerArray, masked_array_get_state), + (pd.arrays.FloatingArray, masked_array_get_state), + (pd.arrays.BooleanArray, masked_array_get_state), + (pd.Categorical, categorical_get_state), + (pd.arrays.PeriodArray, period_array_get_state), + (pd.arrays.IntervalArray, interval_array_get_state), + (pd.api.extensions.ExtensionDtype, extension_dtype_get_state), + (pd.CategoricalDtype, categorical_dtype_get_state), + (pd.SparseDtype, sparse_dtype_get_state), + ] + # pandas.arrays.PandasArray was renamed to NumpyExtensionArray in pandas 2.1 + numpy_backed = getattr(pd.arrays, "NumpyExtensionArray", None) + if numpy_backed is None: + numpy_backed = pd.arrays.PandasArray + dispatch_functions.append((numpy_backed, numpy_backed_array_get_state)) + # StringArray subclasses NumpyExtensionArray, but its numpy form is an + # object array which loses the string dtype, so it takes the generic path. + dispatch_functions.append((pd.arrays.StringArray, extension_array_get_state)) + + for cls, func in dispatch_functions: + _get_state.register(cls)(func) + _registered = True + + +NODE_TYPE_MAPPING = { + ("PandasIndexNode", PROTOCOL): PandasIndexNode, + ("PandasRangeIndexNode", PROTOCOL): PandasRangeIndexNode, + ("PandasMultiIndexNode", PROTOCOL): PandasMultiIndexNode, + ("PandasSeriesNode", PROTOCOL): PandasSeriesNode, + ("PandasDataFrameNode", PROTOCOL): PandasDataFrameNode, + ("PandasNumpyBackedArrayNode", PROTOCOL): PandasNumpyBackedArrayNode, + ("PandasMaskedArrayNode", PROTOCOL): PandasMaskedArrayNode, + ("PandasCategoricalNode", PROTOCOL): PandasCategoricalNode, + ("PandasPeriodArrayNode", PROTOCOL): PandasPeriodArrayNode, + ("PandasIntervalArrayNode", PROTOCOL): PandasIntervalArrayNode, + ("PandasExtensionArrayNode", PROTOCOL): PandasExtensionArrayNode, + ("PandasExtensionDtypeNode", PROTOCOL): PandasExtensionDtypeNode, + ("PandasCategoricalDtypeNode", PROTOCOL): PandasCategoricalDtypeNode, + ("PandasSparseDtypeNode", PROTOCOL): PandasSparseDtypeNode, +} diff --git a/skops/io/_persist.py b/skops/io/_persist.py index ba1fc8cb..027bb220 100644 --- a/skops/io/_persist.py +++ b/skops/io/_persist.py @@ -9,13 +9,21 @@ import skops +from . import _pandas from ._audit import NODE_TYPE_MAPPING, audit_tree, get_tree from ._utils import SaveContext, TrustedTypes, _get_state, get_state, read_schema # We load the dispatch functions from the corresponding modules and register # them. Old protocols are found in the 'old/' directory, with the protocol # version appended to the corresponding module name. -modules = ["._general", "._numpy", "._scipy", "._sklearn", "._quantile_forest"] +modules = [ + "._general", + "._numpy", + "._scipy", + "._sklearn", + "._quantile_forest", + "._pandas", +] modules.extend( [".old._general_v0", ".old._general_v2", ".old._numpy_v0", ".old._numpy_v1"] ) @@ -29,6 +37,10 @@ def _save(obj: Any, compression: int, compresslevel: int | None) -> io.BytesIO: + # pandas is optional and only imported by the user, so its get_state + # functions are registered here rather than when skops.io is imported. + _pandas.register_if_imported() + buffer = io.BytesIO() with ZipFile( diff --git a/skops/io/_trusted_types.py b/skops/io/_trusted_types.py index 5fcb40d0..a223e25a 100644 --- a/skops/io/_trusted_types.py +++ b/skops/io/_trusted_types.py @@ -135,3 +135,51 @@ if (type_name := get_type_name(dtype)).startswith("numpy") } ) + +# pandas types which ``skops.io._pandas`` rebuilds from their data through the +# public pandas constructors, by the public names it writes to the file. They +# are listed as strings so that pandas, which is optional, is not imported +# here. +PANDAS_TYPE_NAMES = [ + "pandas.DataFrame", + "pandas.Series", + "pandas.Index", + "pandas.RangeIndex", + "pandas.MultiIndex", + "pandas.CategoricalIndex", + "pandas.DatetimeIndex", + "pandas.TimedeltaIndex", + "pandas.PeriodIndex", + "pandas.IntervalIndex", + "pandas.arrays.ArrowExtensionArray", + "pandas.arrays.ArrowStringArray", + "pandas.arrays.BooleanArray", + "pandas.arrays.Categorical", + "pandas.arrays.DatetimeArray", + "pandas.arrays.FloatingArray", + "pandas.arrays.IntegerArray", + "pandas.arrays.IntervalArray", + "pandas.arrays.NumpyExtensionArray", + "pandas.arrays.PeriodArray", + "pandas.arrays.SparseArray", + "pandas.arrays.StringArray", + "pandas.arrays.TimedeltaArray", + "pandas.ArrowDtype", + "pandas.BooleanDtype", + "pandas.CategoricalDtype", + "pandas.DatetimeTZDtype", + "pandas.Float32Dtype", + "pandas.Float64Dtype", + "pandas.Int8Dtype", + "pandas.Int16Dtype", + "pandas.Int32Dtype", + "pandas.Int64Dtype", + "pandas.IntervalDtype", + "pandas.PeriodDtype", + "pandas.SparseDtype", + "pandas.StringDtype", + "pandas.UInt8Dtype", + "pandas.UInt16Dtype", + "pandas.UInt32Dtype", + "pandas.UInt64Dtype", +] diff --git a/skops/io/tests/data/pandas-2.0.3.skops b/skops/io/tests/data/pandas-2.0.3.skops new file mode 100644 index 00000000..21408da0 Binary files /dev/null and b/skops/io/tests/data/pandas-2.0.3.skops differ diff --git a/skops/io/tests/data/pandas-3.0.3.skops b/skops/io/tests/data/pandas-3.0.3.skops new file mode 100644 index 00000000..92fb2356 Binary files /dev/null and b/skops/io/tests/data/pandas-3.0.3.skops differ diff --git a/skops/io/tests/test_pandas.py b/skops/io/tests/test_pandas.py new file mode 100644 index 00000000..4aed0920 --- /dev/null +++ b/skops/io/tests/test_pandas.py @@ -0,0 +1,422 @@ +"""Tests for persisting pandas objects.""" + +from __future__ import annotations + +import datetime as dt +import io +import json +from pathlib import Path +from zipfile import ZipFile + +import numpy as np +import pytest +from sklearn.base import BaseEstimator + +from skops.io import dump, dumps, get_untrusted_types, load, loads, visualize +from skops.io._pandas import _public_module +from skops.io._trusted_types import PANDAS_TYPE_NAMES +from skops.io._utils import get_type_name, gettype +from skops.io.exceptions import UnsupportedTypeException + +pd = pytest.importorskip("pandas") +tm = pytest.importorskip("pandas.testing") + + +def assert_equal(expected, actual): + assert type(actual) is type(expected) + if isinstance(expected, pd.MultiIndex): + assert actual.sortorder == expected.sortorder + if isinstance(expected, pd.DataFrame): + tm.assert_frame_equal(expected, actual, check_freq=False) + elif isinstance(expected, pd.Series): + tm.assert_series_equal(expected, actual, check_freq=False) + elif isinstance(expected, (pd.DatetimeIndex, pd.TimedeltaIndex)): + # the freq is not preserved, and assert_index_equal starts checking it + # by default in pandas 3.1 + expected = type(expected)(expected, freq=None) + tm.assert_index_equal(expected, actual, exact=True) + elif isinstance(expected, pd.Index): + tm.assert_index_equal(expected, actual, exact=True) + elif isinstance(expected, pd.api.extensions.ExtensionArray): + tm.assert_extension_array_equal(expected, actual) + else: + assert expected == actual + + +INDEXES = [ + pd.Index([1, 2, 3], name="ints"), + pd.Index([1.5, np.nan, 3.0]), + pd.Index([True, False]), + pd.Index(["a", None, "c"], name="strings"), + pd.Index([1, "a", None], dtype=object), + pd.Index([(1, 2), (3, 4)], dtype=object, tupleize_cols=False), + pd.Index([], dtype=object), + pd.Index([1, None, 3], dtype="Int64"), + pd.RangeIndex(5), + pd.RangeIndex(2, 20, 3, name="range"), + pd.date_range("2024-01-01", periods=3, name="dates"), + pd.date_range("2024-01-01", periods=3, tz="Europe/Berlin"), + pd.date_range("2024-01-01", periods=2, tz="UTC"), + # a fixed offset parsed from the strings + pd.to_datetime(["2024-01-01T00:00:00+01:00", "2024-01-02T00:00:00+01:00"]), + pd.DatetimeIndex(["2024-01-01", None]), + pd.timedelta_range("1D", periods=2), + pd.period_range("2024-01", periods=2, freq="M"), + pd.interval_range(0, 3), + pd.CategoricalIndex(["a", "b", "a"], categories=["b", "a"], ordered=True), + pd.MultiIndex.from_tuples([("a", 1), ("b", 2)], names=["letters", None]), + pd.MultiIndex.from_tuples([("a", 1), ("b", 2)], sortorder=0), +] + +SERIES = [ + pd.Series([1, 2, 3]), + pd.Series([1.5, np.nan], index=["a", "b"], name="floats"), + pd.Series(["x", "y", None]), + pd.Series(["x", 1, None], dtype=object), + pd.Series(np.array(["x", "y"], dtype=object), dtype=object), + pd.Series([1, None, 3], dtype="Int64"), + pd.Series([True, None], dtype="boolean"), + pd.Series([1.5, None], dtype="Float64"), + pd.Series(["a", "b", "a"], dtype="category"), + pd.Series(pd.Categorical(["a", "b"], categories=["b", "a", "c"], ordered=True)), + pd.Series(pd.to_datetime(["2024-01-01", None])), + pd.Series(pd.date_range("2024-01-01", periods=2, tz="UTC")), + pd.Series(pd.to_timedelta([1, 2], unit="D")), + pd.Series(pd.period_range("2024-01", periods=2, freq="M")), + pd.Series(pd.interval_range(0, 2)), + pd.Series(pd.arrays.SparseArray([0, 0, 1.5])), + pd.Series([1, 2], index=pd.MultiIndex.from_tuples([("a", 1), ("b", 2)])), + pd.Series([1, 2, 3], index=[1, 1, 2]), + pd.Series([1], name=("a", "b")), + pd.Series([], dtype=float), + # the shape of category_encoders' TargetEncoder.mapping values + pd.Series([0.49, 0.66, 0.6], index=pd.Index([1, 2, -1]), name="category"), +] + +FRAMES = [ + pd.DataFrame( + {"i": [1, 2], "f": [1.5, np.nan], "s": ["a", None], "b": [True, False]} + ), + pd.DataFrame( + { + "o": pd.Series(["a", "b"], dtype=object), + "n": pd.array([1, None], dtype="Int64"), + "c": pd.Categorical(["x", "y"]), + "t": pd.date_range("2024-01-01", periods=2, tz="Europe/Berlin"), + } + ), + pd.DataFrame([[1, 2], [3, 4]], columns=["a", "a"]), + pd.DataFrame({"a": [1, 2]}, index=pd.Index(["x", "x"], name="dups")), + pd.DataFrame(index=pd.RangeIndex(3)), + pd.DataFrame(columns=["a", "b"]), + pd.DataFrame(), + pd.DataFrame( + np.arange(6).reshape(2, 3), + columns=pd.MultiIndex.from_tuples([("x", 1), ("x", 2), ("y", 1)]), + ), + pd.DataFrame( + {"a": [1]}, index=pd.MultiIndex.from_tuples([("k", 0)], names=["l", "n"]) + ), +] + +ARRAYS = [ + pd.array([1, None], dtype="Int64"), + pd.array([1.5, None], dtype="Float64"), + pd.array([True, None], dtype="boolean"), + pd.array(["a", None], dtype="string"), + pd.Categorical(["a", "b"], categories=["b", "a"], ordered=True), + pd.array(pd.to_datetime(["2024-01-01", None])), + pd.array( + pd.to_datetime(["2024-01-01"]).tz_localize(dt.timezone(dt.timedelta(hours=1))) + ), + pd.array(pd.to_timedelta([1], unit="s")), + pd.array(pd.period_range("2024-01", periods=1, freq="M")), + pd.array(pd.interval_range(0, 2)), + pd.arrays.SparseArray([0, 1]), + pd.Series([1, 2]).array, +] + +DTYPES = [ + pd.Int64Dtype(), + pd.BooleanDtype(), + pd.StringDtype(), + pd.CategoricalDtype(["b", "a"], ordered=True), + pd.CategoricalDtype(), + pd.DatetimeTZDtype("ns", "UTC"), + pd.PeriodDtype("M"), + pd.IntervalDtype("int64", closed="left"), + pd.SparseDtype(float, 0.0), +] + + +def _id(obj): + return f"{type(obj).__name__}-{getattr(obj, 'dtype', '')}" + + +@pytest.mark.parametrize("obj", INDEXES + SERIES + FRAMES + ARRAYS + DTYPES, ids=_id) +def test_roundtrip(obj): + # pandas types are trusted by default, so no trusted list is needed + loaded = loads(dumps(obj)) + assert_equal(obj, loaded) + + +def test_pandas_types_are_trusted_by_default(): + assert get_untrusted_types(data=dumps(FRAMES[1])) == [] + + +class Encoder(BaseEstimator): + """Mirrors the fitted attributes of category_encoders' TargetEncoder.""" + + def fit(self, X, y=None): + self.mapping_ = {"col": pd.Series([0.49, 0.66], index=pd.Index([1, 2]))} + self.categories_ = pd.Index(["A", "B"], name="col") + self.dtypes_ = [pd.StringDtype(), pd.Int64Dtype()] + return self + + +def test_estimator_with_pandas_attributes(): + estimator = Encoder().fit(None) + dumped = dumps(estimator) + # only the estimator itself needs to be trusted + assert get_untrusted_types(data=dumped) == [get_type_name(Encoder)] + + loaded = loads(dumped, trusted=[Encoder]) + tm.assert_series_equal(loaded.mapping_["col"], estimator.mapping_["col"]) + tm.assert_index_equal(loaded.categories_, estimator.categories_, exact=True) + assert loaded.dtypes_ == estimator.dtypes_ + + +def _with_edited_schema(dumped, edit): + # ``dumped`` with ``edit`` applied to its schema, to mimic a crafted file + with ZipFile(io.BytesIO(dumped)) as zip_file: + schema = json.loads(zip_file.read("schema.json")) + files = { + name: zip_file.read(name) + for name in zip_file.namelist() + if name != "schema.json" + } + edit(schema) + buffer = io.BytesIO() + with ZipFile(buffer, "w") as zip_file: + zip_file.writestr("schema.json", json.dumps(schema)) + for name, data in files.items(): + zip_file.writestr(name, data) + return buffer.getvalue() + + +def test_dtype_node_only_builds_the_declared_class(): + # the name in the file is parsed by the declared, trusted dtype class and + # not looked up in pandas' registry of extension dtypes, where it could + # name the dtype of another library, whose code would then run + dumped = dumps(pd.Int64Dtype()) + + def edit(schema): + schema["content"]["name"]["content"] = json.dumps("period[M]") + + with pytest.raises(TypeError, match="Cannot construct"): + loads(_with_edited_schema(dumped, edit)) + + +def test_child_of_wrong_kind_is_refused(): + # the values of an Index are an array; a file holding something else there + # is refused while it is read, before anything is constructed + dumped = dumps(pd.Index([1, 2])) + + def edit(schema): + schema["content"]["values"] = { + "__class__": "str", + "__module__": "builtins", + "__loader__": "JsonNode", + "content": json.dumps("x"), + "is_json": True, + "__id__": 1, + } + + with pytest.raises(ValueError, match="Expected a node of type"): + loads(_with_edited_schema(dumped, edit)) + + +def test_missing_entry_is_refused(): + dumped = dumps(pd.Index([1, 2])) + + def edit(schema): + del schema["content"]["name"] + + with pytest.raises(ValueError, match="Expected the entries"): + loads(_with_edited_schema(dumped, edit)) + + +def test_subclass_from_other_library_is_unsupported(): + class MySeries(pd.Series): + pass + + with pytest.raises(UnsupportedTypeException, match="subclass of a pandas type"): + dumps(MySeries([1, 2])) + + +def test_visualize(capsys): + visualize(dumps(FRAMES[0])) + assert "pandas.DataFrame" in capsys.readouterr().out + + +def test_file_uses_public_type_names(): + # older pandas versions report the defining module of a class, e.g. + # pandas.core.series.Series, and pandas 3 reports pandas.Series; the file + # always holds the public name, which is the one trusted by default + dumped = dumps(pd.Series([1], index=pd.Index([1]))) + with ZipFile(io.BytesIO(dumped)) as zip_file: + schema = json.loads(zip_file.read("schema.json")) + assert (schema["__module__"], schema["__class__"]) == ("pandas", "Series") + index = schema["content"]["index"] + assert (index["__module__"], index["__class__"]) == ("pandas", "Index") + + +def test_trusted_type_names_are_valid(): + # every name is the public path of a pandas class, as written by dumps, so + # that a rename in pandas does not silently leave a type untrusted + for name in PANDAS_TYPE_NAMES: + module, _, class_name = name.rpartition(".") + try: + cls = gettype(module, class_name) + except AttributeError: + # types that only exist in some pandas versions, e.g. PandasArray + continue + assert f"{_public_module(cls)}.{cls.__name__}" == name + + +FIXTURE_DIR = Path(__file__).parent / "data" + + +def cross_version_objects(): + """Objects whose files, written by one pandas version, load on every other. + + :func:`write_pandas_fixture_file` dumps them with the running pandas version + into ``data/``, and :func:`test_load_file_of_other_pandas_version` loads + every file there. Add a file for a new pandas minor version when it changes + how any of these is represented, and never regenerate an existing one. + """ + return { + "str_index": pd.Index(["a", None, "c"], name="strings"), + "str_series": pd.Series(["x", "y", None], index=["a", "b", "c"]), + "mixed_frame": pd.DataFrame( + { + "i": [1, 2], + "f": [1.5, np.nan], + "s": ["a", None], + "o": pd.Series(["a", 1], dtype=object), + "c": pd.Categorical(["x", "y"], categories=["y", "x"], ordered=True), + "t": pd.date_range("2024-01-01", periods=2, tz="Europe/Berlin"), + } + ), + "datetime_index": pd.date_range("2024-01-01", periods=3, name="dates"), + "datetime_index_tz": pd.date_range("2024-01-01", periods=3, tz="UTC"), + "datetime_fixed_offset": pd.to_datetime(["2024-01-01T00:00:00+01:00"]), + "timedelta_index": pd.timedelta_range("1D", periods=2), + "period_series": pd.Series(pd.period_range("2024-01", periods=2, freq="M")), + "interval_index": pd.interval_range(0, 3), + "categorical_index": pd.CategoricalIndex( + ["a", "b", "a"], categories=["b", "a"], ordered=True + ), + "multi_index": pd.MultiIndex.from_tuples( + [("a", 1), ("b", 2)], names=["letters", None] + ), + "range_index": pd.RangeIndex(2, 20, 3, name="range"), + "nullable_frame": pd.DataFrame( + { + "i": pd.array([1, None], dtype="Int64"), + "b": pd.array([True, None], dtype="boolean"), + "f": pd.array([1.5, None], dtype="Float64"), + } + ), + "sparse_series": pd.Series(pd.arrays.SparseArray([0, 0, 1.5])), + "duplicate_columns": pd.DataFrame([[1, 2]], columns=["a", "a"]), + # the shape of category_encoders' TargetEncoder.mapping + "encoder_mapping": {"col": pd.Series([0.49, 0.66], index=pd.Index([1, 2]))}, + "dtypes": [ + pd.Int64Dtype(), + pd.CategoricalDtype(["b", "a"], ordered=True), + pd.StringDtype(), + ], + } + + +def write_pandas_fixture_file(): + """Dump the cross-version objects with the running pandas version.""" + path = FIXTURE_DIR / f"pandas-{pd.__version__}.skops" + dump(cross_version_objects(), path) + return path + + +def _as_objects(values): + # the values as Python objects, with ``None`` for every missing value + return values.to_numpy(dtype=object, na_value=None) + + +def assert_same_data(expected, actual): + """Equality up to the dtype differences between pandas versions. + + Strings are ``str`` in pandas 3 and ``object`` before, datetimes have a + microsecond resolution in pandas 3 and nanoseconds before, and a file keeps + the dtypes of the version that wrote it. The values are therefore compared + as Python objects, with a single marker for missing values. + """ + assert type(actual) is type(expected) + if isinstance(expected, pd.DataFrame): + assert expected.shape == actual.shape + assert_same_data(expected.columns, actual.columns) + assert_same_data(expected.index, actual.index) + for i in range(expected.shape[1]): + np.testing.assert_array_equal( + _as_objects(expected.iloc[:, i]), _as_objects(actual.iloc[:, i]) + ) + elif isinstance(expected, pd.Series): + assert expected.name == actual.name + assert_same_data(expected.index, actual.index) + np.testing.assert_array_equal(_as_objects(expected), _as_objects(actual)) + elif isinstance(expected, pd.MultiIndex): + assert list(expected.names) == list(actual.names) + assert_same_data(expected.to_frame(index=False), actual.to_frame(index=False)) + elif isinstance(expected, pd.Index): + assert expected.name == actual.name + np.testing.assert_array_equal(_as_objects(expected), _as_objects(actual)) + elif isinstance(expected, dict): + assert expected.keys() == actual.keys() + for key in expected: + assert_same_data(expected[key], actual[key]) + elif isinstance(expected, list): + assert len(expected) == len(actual) + for expected_item, actual_item in zip(expected, actual): + assert_same_data(expected_item, actual_item) + else: + assert expected == actual + + +@pytest.mark.parametrize( + "path", sorted(FIXTURE_DIR.glob("pandas-*.skops")), ids=lambda path: path.stem +) +def test_load_file_of_other_pandas_version(path): + # pandas types are trusted by default, so no trusted list is needed + loaded = load(path) + assert_same_data(cross_version_objects(), loaded) + + +# category_encoders uses deprecated pandas options, which the test setup turns +# into errors +@pytest.mark.filterwarnings("ignore") +def test_category_encoders_target_encoder(): + # the report in https://github.com/skops-dev/skops/issues/450 + ce = pytest.importorskip("category_encoders") + + X = pd.DataFrame({"category": list("ABACBACCBA")}) + y = np.array([0, 1, 0, 1, 1, 0, 1, 1, 1, 0]) + encoder = ce.TargetEncoder().fit(X, y) + + dumped = dumps(encoder) + assert get_untrusted_types(data=dumped) == [ + get_type_name(ce.OrdinalEncoder), + get_type_name(ce.TargetEncoder), + ] + loaded = loads(dumped, trusted=[ce.OrdinalEncoder, ce.TargetEncoder]) + + X_new = pd.DataFrame({"category": ["A", "C", "unseen", None]}) + tm.assert_frame_equal(loaded.transform(X_new), encoder.transform(X_new))