diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index a585fa41..5423dcf0 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -11,36 +11,36 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@master + uses: actions/checkout@v4 - name: Create Release id: create_release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + uses: softprops/action-gh-release@v2 with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} body: | Changes in this Release draft: false + name: Release ${{ github.ref }} prerelease: false + tag_name: ${{ github.ref }} + token: ${{ secrets.GITHUB_TOKEN }} deploy: needs: release runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install dependencies run: | python -m pip install --upgrade pip - pip install build + python -m pip install build - name: Build and publish - run: python -m build + run: | + python -m build - name: Publish package - uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29 + uses: pypa/gh-action-pypi-publish@v1.12.4 with: user: __token__ password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.github/workflows/upstream-dev-ci.yml b/.github/workflows/upstream-dev-ci.yml index 9ca8e3ea..5c9b980b 100644 --- a/.github/workflows/upstream-dev-ci.yml +++ b/.github/workflows/upstream-dev-ci.yml @@ -24,8 +24,9 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false fetch-depth: 2 - - uses: xarray-contrib/ci-trigger@v1.1 + - uses: xarray-contrib/ci-trigger@v1.1.1 id: detect-trigger with: keyword: "[test-upstream]" @@ -57,9 +58,10 @@ jobs: - name: setup conda (micromamba) uses: mamba-org/setup-micromamba@v1 with: + cache-downloads: true + cache-environment: true environment-file: ci/dev.yml environment-name: xskillscore-dev - cache-environment: true create-args: >- python=${{ matrix.python-version }} pytest-reportlog @@ -110,8 +112,10 @@ jobs: run: shell: bash steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-python@v5 with: python-version: "3.x" - uses: actions/download-artifact@v2 @@ -127,7 +131,7 @@ jobs: wget https://raw.githubusercontent.com/pydata/xarray/master/.github/workflows/parse_logs.py python parse_logs.py logs/**/*-log - name: Report failures - uses: actions/github-script@v3 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/xskillscore_installs.yml b/.github/workflows/xskillscore_installs.yml index 1abfd609..3d97cbd6 100644 --- a/.github/workflows/xskillscore_installs.yml +++ b/.github/workflows/xskillscore_installs.yml @@ -34,13 +34,13 @@ jobs: matrix: os: ["ubuntu-latest", "macos-latest", "windows-latest"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Setup python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: 3.9 - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . + python -m pip install -e . python -c "import xskillscore" diff --git a/.github/workflows/xskillscore_testing.yml b/.github/workflows/xskillscore_testing.yml index 7ca0ff11..1d475d8e 100644 --- a/.github/workflows/xskillscore_testing.yml +++ b/.github/workflows/xskillscore_testing.yml @@ -15,16 +15,17 @@ jobs: outputs: triggered: ${{ steps.detect-trigger.outputs.trigger-found }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 with: + persist-credentials: false fetch-depth: 2 - - uses: xarray-contrib/ci-trigger@v1.1 + - uses: xarray-contrib/ci-trigger@v1.1.1 id: detect-trigger with: keyword: "[skip-ci]" test: # Runs testing suite on various python versions. - name: Test xskillscore, python ${{ matrix.python-version }} + name: Test xskillscore (Python ${{ matrix.python-version }}) runs-on: ubuntu-latest needs: detect-ci-trigger if: needs.detect-ci-trigger.outputs.triggered == 'false' @@ -34,24 +35,19 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v2 - - name: Set up conda - uses: conda-incubator/setup-miniconda@v2 + - uses: actions/checkout@v4 with: - auto-update-conda: true - channels: conda-forge - mamba-version: '*' - activate-environment: xskillscore-minimum-tests - python-version: ${{ matrix.python-version }} - - name: Set up conda environment - run: | - mamba env update -f ci/minimum-tests.yml - - name: Conda info - run: conda info - - name: Conda list - run: conda list + persist-credentials: false + - name: Set up conda (micromamba) + uses: mamba-org/setup-micromamba@v2.0.4 + with: + cache-downloads: true + cache-environment: true + environment-file: ci/minimum-tests.yml + create-args: > + python=${{ matrix.python-version }} - name: Run tests run: | pytest -n 4 --cov=xskillscore --cov-report=xml --verbose @@ -70,25 +66,25 @@ jobs: defaults: run: shell: bash -l {0} + strategy: + fail-fast: false + matrix: + python-version: [ "3.9", "3.13" ] steps: - - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 + - uses: actions/checkout@v4 with: - auto-update-conda: true - channels: conda-forge - mamba-version: "*" - activate-environment: xskillscore-minimum-tests - python-version: 3.9 - - name: Set up conda environment - run: | - mamba env update -f ci/minimum-tests.yml + persist-credentials: false + - name: Set up conda (micromamba) + uses: mamba-org/setup-micromamba@v2.0.4 + with: + cache-downloads: true + cache-environment: true + environment-file: ci/minimum-tests.yml + create-args: > + python=${{ matrix.python-version }} - name: Install xskillscore run: | python -m pip install --no-deps -e . - - name: Conda info - run: conda info - - name: Conda list - run: conda list - name: Run doctests run: | python -m pytest --doctest-modules xskillscore --ignore xskillscore/tests @@ -100,23 +96,24 @@ jobs: defaults: run: shell: bash -l {0} + strategy: + matrix: + python-version: [ "3.9" ] steps: - - uses: actions/checkout@v2 - - name: Set up conda - uses: conda-incubator/setup-miniconda@v2 + - uses: actions/checkout@v4 with: - auto-update-conda: true - channels: conda-forge - mamba-version: "*" - activate-environment: xskillscore-docs-notebooks - python-version: 3.9 - - name: Set up conda environment + persist-credentials: false + - name: Set up conda (micromamba) + uses: mamba-org/setup-micromamba@v2.0.4 + with: + cache-downloads: true + cache-environment: true + environment-file: ci/docs_notebooks.yml + create-args: > + python=${{ matrix.python-version }} + - name: Install xskillscore run: | - mamba env update -f ci/docs_notebooks.yml - - name: Conda info - run: conda info - - name: Conda list - run: conda list + python -m pip install --no-deps -e . - name: Test notebooks in docs run: | pushd docs diff --git a/.gitignore b/.gitignore index cca0565d..f3f3ebd3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ build .ipynb_checkpoints/ .eggs/ +# Visual Studio Code .vscode # asv environments @@ -15,3 +16,9 @@ build # Mac stuff .DS_Store + +# JetBrains +.idea + +# Documentation +docs/build/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ee0599d9..b1fcdec6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,7 +15,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v5.0.0 hooks: - id: check-added-large-files - id: check-docstring-first @@ -34,7 +34,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.3.5" + rev: "v0.11.8" hooks: - id: ruff args: ["--select", "E,F,I001"] @@ -49,7 +49,7 @@ repos: additional_dependencies: ["black==23.10.1"] - repo: https://github.com/PyCQA/doc8 - rev: v1.1.1 + rev: v1.1.2 hooks: - id: doc8 args: @@ -62,14 +62,14 @@ repos: ] - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.9.0" + rev: "v1.15.0" hooks: - id: mypy exclude: "asv_bench" additional_dependencies: [ # Type stubs types-python-dateutil, - types-pkg_resources, + types-setuptools, types-PyYAML, types-pytz, typing-extensions, @@ -78,7 +78,7 @@ repos: ] - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.1 + rev: 0.33.0 hooks: - id: check-github-workflows - id: check-readthedocs diff --git a/readthedocs.yml b/.readthedocs.yml similarity index 71% rename from readthedocs.yml rename to .readthedocs.yml index 6891ac15..6b8f407d 100644 --- a/readthedocs.yml +++ b/.readthedocs.yml @@ -1,9 +1,9 @@ version: 2 build: - os: "ubuntu-22.04" + os: "ubuntu-24.04" tools: - python: "mambaforge-22.9" + python: "mambaforge-23.11" jobs: post_checkout: - (git --no-pager log --pretty="tformat:%s" -1 | grep -vqF "[skip-rtd]") || exit 183 @@ -15,6 +15,12 @@ conda: environment: ci/doc.yml sphinx: + configuration: docs/source/conf.py fail_on_warning: false formats: [] + +python: + install: + - method: pip + path: . diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d7182379..08dc3687 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,24 @@ Changelog History ================= + +xskillscore v0.0.27 (unreleased) +-------------------------------- + +Bug Fixes +~~~~~~~~~ +- Updated and corrected the build configurations in the GitHub workflows so that the correct + Python is used when running automated build tests. (:pr:`426`) `Trevor James Smith`_ + +Internal Changes +~~~~~~~~~~~~~~~~ +- Adapted code base for modern `numpy` and `xarray`. (:pr:`426`) `Trevor James Smith`_ +- Removed :py:func:`xskillscore.core.utils.suppress_warnings` in lieu of + :py:func:`warnings.filterwarnings`. (:pr:`426`) `Trevor James Smith`_ +- The minimum supported versions for several dependencies have been + updated. (:pr:`426`) `Trevor James Smith`_ + + xskillscore v0.0.26 (2024-03-10) -------------------------------- @@ -9,7 +27,6 @@ Internal Changes ~~~~~~~~~~~~~~~~ - Fix build `Ray Bell`_. - xskillscore v0.0.25 (2024-03-10) -------------------------------- @@ -22,7 +39,6 @@ Bug Fixes this is allowed in :py:func:`~xskillscore.resample_iterations` also. (:issue:`375`, :pr:`376`) `Aaron Spring`_. - Internal Changes ~~~~~~~~~~~~~~~~ - Reduce dependencies (:issue:`359`, :pr:`363`) `Aaron Spring`_. @@ -48,7 +64,6 @@ Features - :py:func:`~xskillscore.multipletests` controlling the false discovery rate for multiple hypothesis tests. (:issue:`365`, :pr:`370`) `Aaron Spring`_. - Bug Fixes ~~~~~~~~~ - :py:func:`~xskillscore.crps_ensemble` broadcasts @@ -77,7 +92,6 @@ xskillscore v0.0.21 (2021-06-13) - Allow ``float`` or ``integer`` forecasts in :py:func:`~xskillscore.brier_score` (:issue:`285`, :pr:`342`) `Aaron Spring`_ - Internal Changes ~~~~~~~~~~~~~~~~ - Added mypy to linting (:pr:`320`) `Zachary Blackwood`_. @@ -324,14 +338,12 @@ Deprecations ~~~~~~~~~~~~ - ``mad`` no longer works and is replaced by ``median_absolute_error``. `Riley X. Brady`_ - Bug Fixes ~~~~~~~~~ - ``skipna`` for ``pearson_r`` and ``spearman_r`` and their p-values now reports accurate results when there are pairwise nans (i.e., nans that occur in different indices in ``a`` and ``b``) `Riley X. Brady`_ - Testing ~~~~~~~ - Test that results from grid cells in a gridded product match the same value if their time @@ -348,4 +360,5 @@ Testing .. _`Riley X. Brady`: https://github.com/bradyrx .. _`Ray Bell`: https://github.com/raybellwaves .. _`Taher Chegini`: https://github.com/cheginit +.. _`Trevor James Smith`: https://github.com/Zeitsperre .. _`Zachary Blackwood`: https://github.com/blackary diff --git a/ci/dev.yml b/ci/dev.yml index 0899e463..3e911d7c 100644 --- a/ci/dev.yml +++ b/ci/dev.yml @@ -2,29 +2,29 @@ name: xskillscore-dev channels: - conda-forge dependencies: - - python >= 3.9,<3.12 + - python >= 3.9,<3.14 + - pip >= 23.0 # Documentation - matplotlib-base - nbsphinx - nbstripout - - sphinx - - sphinx_rtd_theme + - sphinx >=6.0.0 + - sphinx_rtd_theme >=1.0 - sphinx-autosummary-accessors - - sphinxcontrib-napoleon # IDE - jupyterlab # Numerics - bottleneck - cftime - - dask-core - - numba>=0.52 - - numpy + - dask-core >=2023.4.0 + - numba >=0.57 + - numpy >=1.24 - properscoring - scikit-learn - - scipy + - scipy >=1.10.0 - statsmodels - - xarray>=0.16.1 - - xhistogram>=0.3.0 + - xarray>=2023.4.0 + - xhistogram>=0.3.2 # Package Management - asv - build @@ -33,7 +33,8 @@ dependencies: - pre-commit - pytest - pytest-cov - - pytest-xdist - pytest-sugar + - pytest-timeout + - pytest-xdist - mypy - ruff diff --git a/ci/doc.yml b/ci/doc.yml index d56ccdb9..6e6cdc54 100644 --- a/ci/doc.yml +++ b/ci/doc.yml @@ -2,26 +2,24 @@ name: xskillscore-docs channels: - conda-forge dependencies: - - python >=3.9 + - python >=3.9,<3.14 + - pip >=23.0 - bottleneck - cftime - - dask-core + - dask-core >=2023.4.0 - doc8 - ipykernel - ipython - matplotlib-base - nbsphinx - - numpy - - pip + - numpy >=1.24 - properscoring - scikit-learn - - scipy - - sphinx!=4.4.0 - - sphinx_rtd_theme + - scipy >=1.10 + - sphinx >=6.0.0 + - sphinx_rtd_theme >=1.0 - sphinx-autosummary-accessors - sphinx-copybutton - statsmodels - - xarray>=0.16.1 - - xhistogram>=0.3.0 - - pip: - - -e .. + - xarray >=2023.4.0 + - xhistogram>=0.3.2 diff --git a/ci/docs_notebooks.yml b/ci/docs_notebooks.yml index 6f4403f1..e21733d1 100644 --- a/ci/docs_notebooks.yml +++ b/ci/docs_notebooks.yml @@ -3,12 +3,14 @@ channels: - conda-forge - nodefaults dependencies: + - python >=3.9,<3.14 + - pip >=23.0 - bottleneck - cftime - - dask-core + - dask-core >=2023.4.0 - doc8 - - numba>=0.52 - - numpy + - numba >=0.57 + - numpy >=1.24 - properscoring - ipykernel - jupyterlab @@ -16,17 +18,12 @@ dependencies: - matplotlib-base - nbsphinx - nbstripout - - pip - pre-commit - scikit-learn - - scipy - - sphinx + - scipy >=1.10 + - sphinx >=6.0.0 - sphinx-autosummary-accessors - - sphinx_rtd_theme - - sphinxcontrib-napoleon + - sphinx_rtd_theme >=1.0 - statsmodels - - xarray>=0.16.1 - - xhistogram>=0.3.0 - - pip: - # Install latest version of xskillscore. - - -e .. + - xarray >=2023.4.0 + - xhistogram >=0.3.2 diff --git a/ci/install-upstream-wheels.sh b/ci/install-upstream-wheels.sh index b5c0d54a..9402466b 100644 --- a/ci/install-upstream-wheels.sh +++ b/ci/install-upstream-wheels.sh @@ -21,7 +21,6 @@ python -m pip install \ scipy \ matplotlib \ pandas -python -m pip install pytest-timeout python -m pip install \ --no-deps \ --upgrade \ diff --git a/ci/minimum-tests.yml b/ci/minimum-tests.yml index 7871922e..d7ad63b8 100644 --- a/ci/minimum-tests.yml +++ b/ci/minimum-tests.yml @@ -2,21 +2,21 @@ name: xskillscore-minimum-tests channels: - conda-forge dependencies: + - python >=3.9,<3.14 + - pip >=23.0 - bottleneck - cftime - coveralls - - dask-core + - dask-core >=2023.4.0 - matplotlib-base - - numpy - - pip + - numpy >=1.24 - properscoring - pytest - pytest-cov + - pytest-timeout - pytest-xdist - scikit-learn - - scipy + - scipy >=1.10 - statsmodels - - xarray>=0.16.1 - - xhistogram>=0.3.0 - - pip: - - -e .. + - xarray>=2023.4.0 + - xhistogram>=0.3.2 diff --git a/conftest.py b/conftest.py index bce8b883..8cd21b24 100644 --- a/conftest.py +++ b/conftest.py @@ -21,7 +21,7 @@ def add_standard_imports(doctest_namespace): @pytest.fixture def times(): - return xr.cftime_range(start="2000", periods=PERIODS, freq="D") + return xr.date_range(start="2000", periods=PERIODS, freq="D", use_cftime=True) @pytest.fixture @@ -165,7 +165,7 @@ def b_1d(b): @pytest.fixture def a_1d_fixed_nan(): - time = xr.cftime_range("2000-01-01", "2000-01-03", freq="D") + time = xr.date_range("2000-01-01", "2000-01-03", freq="D", use_cftime=True) return xr.DataArray([3, np.nan, 5], dims=["time"], coords=[time]) @@ -211,7 +211,7 @@ def weights_lonlat(a): @pytest.fixture def weights_time(): - time = xr.cftime_range("2000-01-01", "2000-01-03", freq="D") + time = xr.date_range("2000-01-01", "2000-01-03", freq="D", use_cftime=True) return xr.DataArray([1, 2, 3], dims=["time"], coords=[time]) @@ -232,7 +232,7 @@ def category_edges(): @pytest.fixture def forecast_3d_int(): """Random integer 3D forecast used for testing Contingency.""" - times = xr.cftime_range(start="2000", freq="D", periods=10) + times = xr.date_range(start="2000", freq="D", periods=10, use_cftime=True) lats = np.arange(4) lons = np.arange(5) data = np.random.randint(0, 10, size=(len(times), len(lats), len(lons))) diff --git a/docs/source/api/xskillscore.Contingency.accuracy.rst b/docs/source/api/xskillscore.Contingency.accuracy.rst index ae7d4189..f5edcdff 100644 --- a/docs/source/api/xskillscore.Contingency.accuracy.rst +++ b/docs/source/api/xskillscore.Contingency.accuracy.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.accuracy +xskillscore.Contingency.accuracy ================================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.bias_score.rst b/docs/source/api/xskillscore.Contingency.bias_score.rst index baa691d8..16bbd9ed 100644 --- a/docs/source/api/xskillscore.Contingency.bias_score.rst +++ b/docs/source/api/xskillscore.Contingency.bias_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.bias\_score +xskillscore.Contingency.bias\_score =================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.correct_negatives.rst b/docs/source/api/xskillscore.Contingency.correct_negatives.rst index ef72d531..fc05eb2c 100644 --- a/docs/source/api/xskillscore.Contingency.correct_negatives.rst +++ b/docs/source/api/xskillscore.Contingency.correct_negatives.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.correct\_negatives +xskillscore.Contingency.correct\_negatives ========================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.equit_threat_score.rst b/docs/source/api/xskillscore.Contingency.equit_threat_score.rst index 99a2998b..ed7aad9c 100644 --- a/docs/source/api/xskillscore.Contingency.equit_threat_score.rst +++ b/docs/source/api/xskillscore.Contingency.equit_threat_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.equit\_threat\_score +xskillscore.Contingency.equit\_threat\_score ============================================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.false_alarm_rate.rst b/docs/source/api/xskillscore.Contingency.false_alarm_rate.rst index 8d68188f..7f1e886b 100644 --- a/docs/source/api/xskillscore.Contingency.false_alarm_rate.rst +++ b/docs/source/api/xskillscore.Contingency.false_alarm_rate.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.false\_alarm\_rate +xskillscore.Contingency.false\_alarm\_rate ========================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.false_alarm_ratio.rst b/docs/source/api/xskillscore.Contingency.false_alarm_ratio.rst index 034d4f0e..6fa4a7e5 100644 --- a/docs/source/api/xskillscore.Contingency.false_alarm_ratio.rst +++ b/docs/source/api/xskillscore.Contingency.false_alarm_ratio.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.false\_alarm\_ratio +xskillscore.Contingency.false\_alarm\_ratio =========================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.false_alarms.rst b/docs/source/api/xskillscore.Contingency.false_alarms.rst index 121208a4..5f50ff63 100644 --- a/docs/source/api/xskillscore.Contingency.false_alarms.rst +++ b/docs/source/api/xskillscore.Contingency.false_alarms.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.false\_alarms +xskillscore.Contingency.false\_alarms ===================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.gerrity_score.rst b/docs/source/api/xskillscore.Contingency.gerrity_score.rst index d4f71860..269d59ef 100644 --- a/docs/source/api/xskillscore.Contingency.gerrity_score.rst +++ b/docs/source/api/xskillscore.Contingency.gerrity_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.gerrity\_score +xskillscore.Contingency.gerrity\_score ====================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.heidke_score.rst b/docs/source/api/xskillscore.Contingency.heidke_score.rst index 90a346f9..5f9dbfa0 100644 --- a/docs/source/api/xskillscore.Contingency.heidke_score.rst +++ b/docs/source/api/xskillscore.Contingency.heidke_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.heidke\_score +xskillscore.Contingency.heidke\_score ===================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.hit_rate.rst b/docs/source/api/xskillscore.Contingency.hit_rate.rst index 27d315e2..37df3fbd 100644 --- a/docs/source/api/xskillscore.Contingency.hit_rate.rst +++ b/docs/source/api/xskillscore.Contingency.hit_rate.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.hit\_rate +xskillscore.Contingency.hit\_rate ================================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.hits.rst b/docs/source/api/xskillscore.Contingency.hits.rst index a4b86d0d..700e22b4 100644 --- a/docs/source/api/xskillscore.Contingency.hits.rst +++ b/docs/source/api/xskillscore.Contingency.hits.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.hits +xskillscore.Contingency.hits ============================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.misses.rst b/docs/source/api/xskillscore.Contingency.misses.rst index 2fbcd092..4b53e7d0 100644 --- a/docs/source/api/xskillscore.Contingency.misses.rst +++ b/docs/source/api/xskillscore.Contingency.misses.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.misses +xskillscore.Contingency.misses ============================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.odds_ratio.rst b/docs/source/api/xskillscore.Contingency.odds_ratio.rst index 708e6947..c9fe1bd9 100644 --- a/docs/source/api/xskillscore.Contingency.odds_ratio.rst +++ b/docs/source/api/xskillscore.Contingency.odds_ratio.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.odds\_ratio +xskillscore.Contingency.odds\_ratio =================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.odds_ratio_skill_score.rst b/docs/source/api/xskillscore.Contingency.odds_ratio_skill_score.rst index 8bda715d..db253f53 100644 --- a/docs/source/api/xskillscore.Contingency.odds_ratio_skill_score.rst +++ b/docs/source/api/xskillscore.Contingency.odds_ratio_skill_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.odds\_ratio\_skill\_score +xskillscore.Contingency.odds\_ratio\_skill\_score ================================================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.peirce_score.rst b/docs/source/api/xskillscore.Contingency.peirce_score.rst index e8ac4a6b..08676287 100644 --- a/docs/source/api/xskillscore.Contingency.peirce_score.rst +++ b/docs/source/api/xskillscore.Contingency.peirce_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.peirce\_score +xskillscore.Contingency.peirce\_score ===================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.rst b/docs/source/api/xskillscore.Contingency.rst index 2df8fdcc..cfc1062f 100644 --- a/docs/source/api/xskillscore.Contingency.rst +++ b/docs/source/api/xskillscore.Contingency.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency +xskillscore.Contingency ======================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.success_ratio.rst b/docs/source/api/xskillscore.Contingency.success_ratio.rst index fae146f7..e9a7f1ed 100644 --- a/docs/source/api/xskillscore.Contingency.success_ratio.rst +++ b/docs/source/api/xskillscore.Contingency.success_ratio.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.success\_ratio +xskillscore.Contingency.success\_ratio ====================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.table.rst b/docs/source/api/xskillscore.Contingency.table.rst index 30d140fc..d68bb368 100644 --- a/docs/source/api/xskillscore.Contingency.table.rst +++ b/docs/source/api/xskillscore.Contingency.table.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.table +xskillscore.Contingency.table ============================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.Contingency.threat_score.rst b/docs/source/api/xskillscore.Contingency.threat_score.rst index ff831898..5e1ec45e 100644 --- a/docs/source/api/xskillscore.Contingency.threat_score.rst +++ b/docs/source/api/xskillscore.Contingency.threat_score.rst @@ -1,4 +1,4 @@ -xskillscore.Contingency.threat\_score +xskillscore.Contingency.threat\_score ===================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.brier_score.rst b/docs/source/api/xskillscore.brier_score.rst index f7367449..7e2daaac 100644 --- a/docs/source/api/xskillscore.brier_score.rst +++ b/docs/source/api/xskillscore.brier_score.rst @@ -1,4 +1,4 @@ -xskillscore.brier\_score +xskillscore.brier\_score ======================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.core.resampling.resample_iterations.rst b/docs/source/api/xskillscore.core.resampling.resample_iterations.rst index 1bd0e02c..39c27332 100644 --- a/docs/source/api/xskillscore.core.resampling.resample_iterations.rst +++ b/docs/source/api/xskillscore.core.resampling.resample_iterations.rst @@ -1,4 +1,4 @@ -xskillscore.core.resampling.resample\_iterations +xskillscore.core.resampling.resample\_iterations ================================================ .. currentmodule:: xskillscore.core.resampling diff --git a/docs/source/api/xskillscore.core.resampling.resample_iterations_idx.rst b/docs/source/api/xskillscore.core.resampling.resample_iterations_idx.rst index 1f09a5b9..96a858dc 100644 --- a/docs/source/api/xskillscore.core.resampling.resample_iterations_idx.rst +++ b/docs/source/api/xskillscore.core.resampling.resample_iterations_idx.rst @@ -1,4 +1,4 @@ -xskillscore.core.resampling.resample\_iterations\_idx +xskillscore.core.resampling.resample\_iterations\_idx ===================================================== .. currentmodule:: xskillscore.core.resampling diff --git a/docs/source/api/xskillscore.crps_ensemble.rst b/docs/source/api/xskillscore.crps_ensemble.rst index 17961474..d0bc86da 100644 --- a/docs/source/api/xskillscore.crps_ensemble.rst +++ b/docs/source/api/xskillscore.crps_ensemble.rst @@ -1,4 +1,4 @@ -xskillscore.crps\_ensemble +xskillscore.crps\_ensemble ========================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.crps_gaussian.rst b/docs/source/api/xskillscore.crps_gaussian.rst index 6a893fcc..628db93b 100644 --- a/docs/source/api/xskillscore.crps_gaussian.rst +++ b/docs/source/api/xskillscore.crps_gaussian.rst @@ -1,4 +1,4 @@ -xskillscore.crps\_gaussian +xskillscore.crps\_gaussian ========================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.crps_quadrature.rst b/docs/source/api/xskillscore.crps_quadrature.rst index a19ec43b..158e6ed5 100644 --- a/docs/source/api/xskillscore.crps_quadrature.rst +++ b/docs/source/api/xskillscore.crps_quadrature.rst @@ -1,4 +1,4 @@ -xskillscore.crps\_quadrature +xskillscore.crps\_quadrature ============================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.discrimination.rst b/docs/source/api/xskillscore.discrimination.rst index 0dea567e..3f94859b 100644 --- a/docs/source/api/xskillscore.discrimination.rst +++ b/docs/source/api/xskillscore.discrimination.rst @@ -1,4 +1,4 @@ -xskillscore.discrimination +xskillscore.discrimination ========================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.effective_sample_size.rst b/docs/source/api/xskillscore.effective_sample_size.rst index df06b4ce..5954e912 100644 --- a/docs/source/api/xskillscore.effective_sample_size.rst +++ b/docs/source/api/xskillscore.effective_sample_size.rst @@ -1,4 +1,4 @@ -xskillscore.effective\_sample\_size +xskillscore.effective\_sample\_size =================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.halfwidth_ci_test.rst b/docs/source/api/xskillscore.halfwidth_ci_test.rst index e4b6d121..b140ce9c 100644 --- a/docs/source/api/xskillscore.halfwidth_ci_test.rst +++ b/docs/source/api/xskillscore.halfwidth_ci_test.rst @@ -1,4 +1,4 @@ -xskillscore.halfwidth\_ci\_test +xskillscore.halfwidth\_ci\_test =============================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.linslope.rst b/docs/source/api/xskillscore.linslope.rst index f714a499..36b3c947 100644 --- a/docs/source/api/xskillscore.linslope.rst +++ b/docs/source/api/xskillscore.linslope.rst @@ -1,4 +1,4 @@ -xskillscore.linslope +xskillscore.linslope ==================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.mae.rst b/docs/source/api/xskillscore.mae.rst index f61dc203..7630437e 100644 --- a/docs/source/api/xskillscore.mae.rst +++ b/docs/source/api/xskillscore.mae.rst @@ -1,4 +1,4 @@ -xskillscore.mae +xskillscore.mae =============== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.mape.rst b/docs/source/api/xskillscore.mape.rst index f55b8b28..c0f808f0 100644 --- a/docs/source/api/xskillscore.mape.rst +++ b/docs/source/api/xskillscore.mape.rst @@ -1,4 +1,4 @@ -xskillscore.mape +xskillscore.mape ================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.me.rst b/docs/source/api/xskillscore.me.rst index bdbae363..0febd634 100644 --- a/docs/source/api/xskillscore.me.rst +++ b/docs/source/api/xskillscore.me.rst @@ -1,4 +1,4 @@ -xskillscore.me +xskillscore.me ============== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.median_absolute_error.rst b/docs/source/api/xskillscore.median_absolute_error.rst index d5d548f4..785bb0c2 100644 --- a/docs/source/api/xskillscore.median_absolute_error.rst +++ b/docs/source/api/xskillscore.median_absolute_error.rst @@ -1,4 +1,4 @@ -xskillscore.median\_absolute\_error +xskillscore.median\_absolute\_error =================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.mse.rst b/docs/source/api/xskillscore.mse.rst index e795ec2d..c3a2f9e9 100644 --- a/docs/source/api/xskillscore.mse.rst +++ b/docs/source/api/xskillscore.mse.rst @@ -1,4 +1,4 @@ -xskillscore.mse +xskillscore.mse =============== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.pearson_r.rst b/docs/source/api/xskillscore.pearson_r.rst index c95f3fce..a12844fa 100644 --- a/docs/source/api/xskillscore.pearson_r.rst +++ b/docs/source/api/xskillscore.pearson_r.rst @@ -1,4 +1,4 @@ -xskillscore.pearson\_r +xskillscore.pearson\_r ====================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.pearson_r_eff_p_value.rst b/docs/source/api/xskillscore.pearson_r_eff_p_value.rst index cd5f7224..e921a745 100644 --- a/docs/source/api/xskillscore.pearson_r_eff_p_value.rst +++ b/docs/source/api/xskillscore.pearson_r_eff_p_value.rst @@ -1,4 +1,4 @@ -xskillscore.pearson\_r\_eff\_p\_value +xskillscore.pearson\_r\_eff\_p\_value ===================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.pearson_r_p_value.rst b/docs/source/api/xskillscore.pearson_r_p_value.rst index 55716c85..aeb25484 100644 --- a/docs/source/api/xskillscore.pearson_r_p_value.rst +++ b/docs/source/api/xskillscore.pearson_r_p_value.rst @@ -1,4 +1,4 @@ -xskillscore.pearson\_r\_p\_value +xskillscore.pearson\_r\_p\_value ================================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.r2.rst b/docs/source/api/xskillscore.r2.rst index 0d1e65da..b2db1b7e 100644 --- a/docs/source/api/xskillscore.r2.rst +++ b/docs/source/api/xskillscore.r2.rst @@ -1,4 +1,4 @@ -xskillscore.r2 +xskillscore.r2 ============== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.rank_histogram.rst b/docs/source/api/xskillscore.rank_histogram.rst index 08e310b3..b43479b4 100644 --- a/docs/source/api/xskillscore.rank_histogram.rst +++ b/docs/source/api/xskillscore.rank_histogram.rst @@ -1,4 +1,4 @@ -xskillscore.rank\_histogram +xskillscore.rank\_histogram =========================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.reliability.rst b/docs/source/api/xskillscore.reliability.rst index 69a2709d..26de7145 100644 --- a/docs/source/api/xskillscore.reliability.rst +++ b/docs/source/api/xskillscore.reliability.rst @@ -1,4 +1,4 @@ -xskillscore.reliability +xskillscore.reliability ======================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.rmse.rst b/docs/source/api/xskillscore.rmse.rst index 30712511..1cf739db 100644 --- a/docs/source/api/xskillscore.rmse.rst +++ b/docs/source/api/xskillscore.rmse.rst @@ -1,4 +1,4 @@ -xskillscore.rmse +xskillscore.rmse ================ .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.roc.rst b/docs/source/api/xskillscore.roc.rst index 36566797..fe435933 100644 --- a/docs/source/api/xskillscore.roc.rst +++ b/docs/source/api/xskillscore.roc.rst @@ -1,4 +1,4 @@ -xskillscore.roc +xskillscore.roc =============== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.rps.rst b/docs/source/api/xskillscore.rps.rst index ce484b3e..cc6f6888 100644 --- a/docs/source/api/xskillscore.rps.rst +++ b/docs/source/api/xskillscore.rps.rst @@ -1,4 +1,4 @@ -xskillscore.rps +xskillscore.rps =============== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.sign_test.rst b/docs/source/api/xskillscore.sign_test.rst index 351189d4..851bcff3 100644 --- a/docs/source/api/xskillscore.sign_test.rst +++ b/docs/source/api/xskillscore.sign_test.rst @@ -1,4 +1,4 @@ -xskillscore.sign\_test +xskillscore.sign\_test ====================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.smape.rst b/docs/source/api/xskillscore.smape.rst index d013d9a5..c6e5bf5b 100644 --- a/docs/source/api/xskillscore.smape.rst +++ b/docs/source/api/xskillscore.smape.rst @@ -1,4 +1,4 @@ -xskillscore.smape +xskillscore.smape ================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.spearman_r.rst b/docs/source/api/xskillscore.spearman_r.rst index af101feb..a20703b4 100644 --- a/docs/source/api/xskillscore.spearman_r.rst +++ b/docs/source/api/xskillscore.spearman_r.rst @@ -1,4 +1,4 @@ -xskillscore.spearman\_r +xskillscore.spearman\_r ======================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.spearman_r_eff_p_value.rst b/docs/source/api/xskillscore.spearman_r_eff_p_value.rst index b8bc2654..4dd5082d 100644 --- a/docs/source/api/xskillscore.spearman_r_eff_p_value.rst +++ b/docs/source/api/xskillscore.spearman_r_eff_p_value.rst @@ -1,4 +1,4 @@ -xskillscore.spearman\_r\_eff\_p\_value +xskillscore.spearman\_r\_eff\_p\_value ====================================== .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.spearman_r_p_value.rst b/docs/source/api/xskillscore.spearman_r_p_value.rst index 8ed38761..30a2e9ad 100644 --- a/docs/source/api/xskillscore.spearman_r_p_value.rst +++ b/docs/source/api/xskillscore.spearman_r_p_value.rst @@ -1,4 +1,4 @@ -xskillscore.spearman\_r\_p\_value +xskillscore.spearman\_r\_p\_value ================================= .. currentmodule:: xskillscore diff --git a/docs/source/api/xskillscore.threshold_brier_score.rst b/docs/source/api/xskillscore.threshold_brier_score.rst index bc536f65..229f7ce7 100644 --- a/docs/source/api/xskillscore.threshold_brier_score.rst +++ b/docs/source/api/xskillscore.threshold_brier_score.rst @@ -1,4 +1,4 @@ -xskillscore.threshold\_brier\_score +xskillscore.threshold\_brier\_score =================================== .. currentmodule:: xskillscore diff --git a/docs/source/conf.py b/docs/source/conf.py index cc1568eb..f08aa3f1 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -33,8 +33,8 @@ extensions = [ "sphinx.ext.autodoc", "sphinx.ext.autosummary", - "sphinx.ext.intersphinx", "sphinx.ext.extlinks", + "sphinx.ext.intersphinx", "sphinx.ext.mathjax", "sphinx.ext.napoleon", "IPython.sphinxext.ipython_directive", @@ -65,7 +65,7 @@ templates_path = ["_templates", sphinx_autosummary_accessors.templates_path] # The suffix of source filenames. -source_suffix = ".rst" +source_suffix = {".rst": "restructuredtext"} # The master toctree document. master_doc = "index" diff --git a/pyproject.toml b/pyproject.toml index 2ae2e11e..6061a3f5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,13 +6,13 @@ build-backend = "setuptools.build_meta" name = "xskillscore" version = "0.0.26" dependencies = [ - "dask[array]", - "numpy", + "dask[array] >=2023.4.0", + "numpy >=1.24", "properscoring", - "scipy", + "scipy >=1.10", "statsmodels", - "xarray>=0.16.1", - "xhistogram>=0.3.0", + "xarray>=2023.4.0", + "xhistogram>=0.3.2", ] authors = [{name = "Ray Bell", email = "rayjohnbell0@gmail.com"}] description = "Metrics for verifying forecasts" @@ -26,6 +26,8 @@ classifiers = [ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Scientific/Engineering :: Atmospheric Science", "Topic :: Scientific/Engineering :: Mathematics", ] @@ -39,32 +41,27 @@ test = [ "bottleneck", "cftime", "matplotlib", - "numba>=0.52", + "numba >=0.57", "pre-commit", "pytest", "pytest-cov", + "pytest-timeout", "pytest-xdist", "scikit-learn", ] complete = [ - "bottleneck", - "cftime", + "xskillscore[test]", "doc8", - "matplotlib", + "ipykernel", + "ipython", "nbsphinx", "nbstripout", - "numba>=0.52", - "pre-commit", - "pytest", - "pytest-cov", - "pytest-xdist", + "properscoring", "pytest-sugar", - "scikit-learn", - "sphinx", + "sphinx >=6.0.0", "sphinx-autosummary-accessors", "sphinx-copybutton", "sphinx-rtd-theme>=1.0", - "sphinxcontrib-napoleon", ] [project.urls] @@ -86,6 +83,8 @@ testpaths = ["xskillscore/tests"] addopts = "--color=yes --verbose" markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "flaky: marks tests as flaky (deselect with '-m \"not flaky\"')", + "network: marks tests that require network access (deselect with '-m \"not network\"')", ] [tool.doc8] diff --git a/xskillscore/core/deterministic.py b/xskillscore/core/deterministic.py index c8a96095..81e38791 100644 --- a/xskillscore/core/deterministic.py +++ b/xskillscore/core/deterministic.py @@ -53,13 +53,13 @@ def _determine_input_core_dims( dim: str | List[str], weights: XArray | None -) -> List[object]: +) -> List[List[str]]: """ Determine input_core_dims based on type of dim and weights. Parameters ---------- - dim : str, list + dim : str or list of str The dimension(s) to apply the metric along. weights : xarray.Dataset or xarray.DataArray or None Weights matching dimensions of ``dim`` to apply during the function. @@ -1210,12 +1210,12 @@ def mape( skipna: bool = False, keep_attrs: bool = False, ) -> XArray: - """Mean Absolute Percentage Error. + r"""Mean Absolute Percentage Error. .. math:: - \\mathrm{MAPE} = \\frac{1}{n} \\sum_{i=1}^{n} - \\frac{\\vert a_{i} - b_{i} \\vert} - {max(\epsilon, \\vert a_{i} \\vert)} + \mathrm{MAPE} = \frac{1}{n} \sum_{i=1}^{n} + \frac{\vert a_{i} - b_{i} \vert} + {max(\epsilon, \vert a_{i} \vert)} .. note:: The percent error is calculated in reference to ``a``. Percent diff --git a/xskillscore/core/np_deterministic.py b/xskillscore/core/np_deterministic.py index 82c8a7de..3c8d7676 100644 --- a/xskillscore/core/np_deterministic.py +++ b/xskillscore/core/np_deterministic.py @@ -1,9 +1,9 @@ +import warnings + import numpy as np from scipy import special from scipy.stats import distributions -from .utils import suppress_warnings - try: from bottleneck import nanrankdata as rankdata except ImportError: @@ -94,12 +94,12 @@ def __compute_anomalies(a, b, weights, axis, skipna): # Only do weighted sums if there are weights. Cannot have a # single generic function with weights of all ones, because # the denominator gets inflated when there are masked regions. - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: ma = sumfunc(a * weights, axis=axis) / sumfunc(weights, axis=axis) mb = sumfunc(b * weights, axis=axis) / sumfunc(weights, axis=axis) - else: - with suppress_warnings("Mean of empty slice"): + else: ma = meanfunc(a, axis=axis) mb = meanfunc(b, axis=axis) am, bm = a - ma, b - mb @@ -144,7 +144,7 @@ def _effective_sample_size(a, b, axis, skipna): b = np.rollaxis(b, axis) # count total number of samples that are non-nan. - n = np.count_nonzero(~np.isnan(a), axis=0) + n = np.count_nonzero(~np.isnan(np.atleast_1d(a)), axis=0) # compute lag-1 autocorrelation. am, bm = __compute_anomalies(a, b, weights=None, axis=0, skipna=skipna) @@ -152,7 +152,8 @@ def _effective_sample_size(a, b, axis, skipna): b_auto = _pearson_r(bm[0:-1], bm[1::], weights=None, axis=0, skipna=skipna) # compute effective sample size per Bretherton et al. 1999 - with suppress_warnings("divide by zero encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) n_eff = n * (1 - a_auto * b_auto) / (1 + a_auto * b_auto) n_eff = np.floor(n_eff) n_eff = np.clip(n_eff, 0, n) @@ -202,9 +203,9 @@ def _linslope(a, b, weights, axis, skipna): s_num = sumfunc(am * bm, axis=0) s_den = sumfunc(am * am, axis=0) - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("invalid value encountered in double_scalars"): - res = s_num / s_den + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + res = s_num / s_den return res @@ -256,11 +257,9 @@ def _r2(a, b, weights, axis, skipna): am_squared = am**2 num = sumfunc(squared_error, axis=0) den = sumfunc(am_squared, axis=0) - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("divide by zero encountered in true_divide"): - with suppress_warnings("divide by zero encountered in double_scalars"): - with suppress_warnings("invalid value encountered in double_scalars"): - r2 = 1 - (num / den) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + r2 = 1 - (num / den) return r2 @@ -309,9 +308,9 @@ def _pearson_r(a, b, weights, axis, skipna): r_num = sumfunc(am * bm, axis=0) r_den = np.sqrt(sumfunc(am * am, axis=0) * sumfunc(bm * bm, axis=0)) - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("invalid value encountered in double_scalars"): - r = r_num / r_den + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + r = r_num / r_den res = np.clip(r, -1.0, 1.0) return res @@ -351,8 +350,9 @@ def _pearson_r_p_value(a, b, weights, axis, skipna): a = np.rollaxis(a, axis) b = np.rollaxis(b, axis) # count non-nans - dof = np.count_nonzero(~np.isnan(a), axis=0) - 2 - with suppress_warnings("invalid value encountered in true_divide"): + dof = np.count_nonzero(~np.isnan(np.atleast_1d(a)), axis=0) - 2 + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) t_squared = r**2 * (dof / ((1.0 - r) * (1.0 + r))) _x = dof / (dof + t_squared) _x = np.asarray(_x) @@ -365,7 +365,7 @@ def _pearson_r_p_value(a, b, weights, axis, skipna): # on 0d arrays is deprecated, as it behaves surprisingly. Use # `atleast_1d(cond).nonzero()` if the old behavior was intended. If the context # of this warning is of the form `arr[nonzero(cond)]`, just use `arr[cond]`. - nan_locs = np.where(np.isnan(r)) + nan_locs = np.where(np.isnan(np.atleast_1d(r))) if len(nan_locs[0]) > 0: res[nan_locs] = np.nan return res @@ -411,7 +411,8 @@ def _pearson_r_eff_p_value(a, b, axis, skipna): else: dof = _effective_sample_size(a, b, axis, skipna) - 2 t_squared = r**2 * (dof / ((1.0 - r) * (1.0 + r))) - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) _x = dof / (dof + t_squared) _x = np.asarray(_x) _x = np.where(_x < 1.0, _x, 1.0) @@ -492,10 +493,10 @@ def _spearman_r_p_value(a, b, weights, axis, skipna): a = np.rollaxis(a, axis) b = np.rollaxis(b, axis) # count non-nans - dof = np.count_nonzero(~np.isnan(a), axis=0) - 2 - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("divide by zero encountered in true_divide"): - t = rs * np.sqrt((dof / ((rs + 1.0) * (1.0 - rs))).clip(0)) + dof = np.count_nonzero(~np.isnan(np.atleast_1d(a)), axis=0) - 2 + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + t = rs * np.sqrt((dof / ((rs + 1.0) * (1.0 - rs))).clip(0)) p = 2 * distributions.t.sf(np.abs(t), dof) return p @@ -540,9 +541,9 @@ def _spearman_r_eff_p_value(a, b, axis, skipna): a, b, _ = _match_nans(a, b, None) rs = _spearman_r(a, b, None, axis, skipna) dof = _effective_sample_size(a, b, axis, skipna) - 2 - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("divide by zero encountered in true_divide"): - t = rs * np.sqrt((dof / ((rs + 1.0) * (1.0 - rs))).clip(0)) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + t = rs * np.sqrt((dof / ((rs + 1.0) * (1.0 - rs))).clip(0)) p = 2 * distributions.t.sf(np.abs(t), dof) return p @@ -574,13 +575,13 @@ def _me(a, b, weights, axis, skipna): weights = _check_weights(weights) error = a - b - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: mean_error = sumfunc(error * weights, axis=axis) / sumfunc( weights, axis=axis ) - else: - with suppress_warnings("Mean of empty slice"): + else: mean_error = meanfunc(error, axis=axis) return mean_error @@ -616,13 +617,13 @@ def _rmse(a, b, weights, axis, skipna): weights = _check_weights(weights) squared_error = (a - b) ** 2 - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: mean_squared_error = sumfunc(squared_error * weights, axis=axis) / sumfunc( weights, axis=axis ) - else: - with suppress_warnings("Mean of empty slice"): + else: mean_squared_error = meanfunc(squared_error, axis=axis) res = np.sqrt(mean_squared_error) return res @@ -659,13 +660,13 @@ def _mse(a, b, weights, axis, skipna): weights = _check_weights(weights) squared_error = (a - b) ** 2 - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: return sumfunc(squared_error * weights, axis=axis) / sumfunc( weights, axis=axis ) - else: - with suppress_warnings("Mean of empty slice"): + else: return meanfunc(squared_error, axis=axis) @@ -700,13 +701,13 @@ def _mae(a, b, weights, axis, skipna): weights = _check_weights(weights) absolute_error = np.absolute(a - b) - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: return sumfunc(absolute_error * weights, axis=axis) / sumfunc( weights, axis=axis ) - else: - with suppress_warnings("Mean of empty slice"): + else: return meanfunc(absolute_error, axis=axis) @@ -737,17 +738,18 @@ def _median_absolute_error(a, b, axis, skipna): if skipna: a, b, _ = _match_nans(a, b, None) absolute_error = np.absolute(a - b) - with suppress_warnings("All-NaN slice encountered"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) return medianfunc(absolute_error, axis=axis) def _mape(a, b, weights, axis, skipna): - """Mean Absolute Percentage Error. + r"""Mean Absolute Percentage Error. .. math:: - \\mathrm{MAPE} = \\frac{1}{n} \\sum_{i=1}^{n} - \\frac{\\vert a_{i} - b_{i} \\vert} - {max(\epsilon, \\vert a_{i} \\vert)} + \mathrm{MAPE} = \frac{1}{n} \sum_{i=1}^{n} + \frac{\vert a_{i} - b_{i} \vert} + {max(\epsilon, \vert a_{i} \vert)} Parameters ---------- @@ -771,8 +773,7 @@ def _mape(a, b, weights, axis, skipna): ----- The percent error is calculated in reference to ``a``. - Percent error is reported as decimal percent. I.e., a value of - 1 is 100%. + Percent error is reported as decimal percent. i.e., a value of 1 is 100%. \epsilon is an arbitrary small yet strictly positive number to avoid undefined results when ``a`` is zero. @@ -791,11 +792,11 @@ def _mape(a, b, weights, axis, skipna): weights = _check_weights(weights) epsilon = np.finfo(np.float64).eps mape = np.absolute(a - b) / np.maximum(np.absolute(a), epsilon) - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: return sumfunc(mape * weights, axis=axis) / sumfunc(weights, axis=axis) - else: - with suppress_warnings("Mean of empty slice"): + else: return meanfunc(mape, axis=axis) @@ -839,9 +840,9 @@ def _smape(a, b, weights, axis, skipna): a, b, weights = _match_nans(a, b, weights) weights = _check_weights(weights) smape = np.absolute(a - b) / (np.absolute(a) + np.absolute(b)) - if weights is not None: - with suppress_warnings("invalid value encountered in true_divide"): + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + if weights is not None: return sumfunc(smape * weights, axis=axis) / sumfunc(weights, axis=axis) - else: - with suppress_warnings("Mean of empty slice"): + else: return meanfunc(smape, axis=axis) diff --git a/xskillscore/core/np_probabilistic.py b/xskillscore/core/np_probabilistic.py index 021f4f51..d7d6cda2 100644 --- a/xskillscore/core/np_probabilistic.py +++ b/xskillscore/core/np_probabilistic.py @@ -1,6 +1,6 @@ -import numpy as np +import warnings -from .utils import suppress_warnings +import numpy as np __all__ = ["_reliability"] @@ -35,10 +35,10 @@ def _reliability(o, f, bin_edges): r.append(N_o_f_in_bin / N_f_in_bin) N.append(N_f_in_bin) else: - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("invalid value encountered in long_scalars"): - r[..., i] = N_o_f_in_bin / N_f_in_bin - N[..., i] = N_f_in_bin + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + r[..., i] = N_o_f_in_bin / N_f_in_bin + N[..., i] = N_f_in_bin if is_dask_array: import dask.array as da diff --git a/xskillscore/core/probabilistic.py b/xskillscore/core/probabilistic.py index a300d14c..9942f58f 100644 --- a/xskillscore/core/probabilistic.py +++ b/xskillscore/core/probabilistic.py @@ -1,5 +1,6 @@ from __future__ import annotations +import warnings from typing import Callable, List, Literal, Optional, Tuple import numpy as np @@ -20,7 +21,6 @@ _preprocess_dims, _stack_input_if_needed, histogram, - suppress_warnings, ) try: @@ -46,7 +46,7 @@ def probabilistic_broadcast( observations: XArray, forecasts: XArray, member_dim: str = "member" -) -> XArray: +) -> Tuple[XArray, ...]: """Broadcast dimension except for member_dim in forecasts.""" observations = observations.broadcast_like( forecasts.isel({member_dim: 0}, drop=True) @@ -113,9 +113,9 @@ def crps_gaussian( """ xmu = xr.DataArray(mu) if isinstance(mu, (int, float)) else mu xsig = xr.DataArray(sig) if isinstance(sig, (int, float)) else sig - if xmu.dims != observations.dims: + if xmu.sizes != observations.sizes: observations, xmu = xr.broadcast(observations, xmu) - if xsig.dims != observations.dims: + if xsig.sizes != observations.sizes: observations, xsig = xr.broadcast(observations, xsig) res = xr.apply_ufunc( properscoring.crps_gaussian, @@ -524,9 +524,9 @@ def _assign_rps_category_bounds( } ) res[f"{name}_category_edge"] = ( - f"[-np.inf, {edges[bin_dim].isel({bin_dim:0}).values}), " + f"[-np.inf, {edges[bin_dim].isel({bin_dim: 0}).values}), " f"{str(res[f'{name}_category_edge'].values)[:-1]}), " - f"[{edges[bin_dim].isel({bin_dim:-1}).values}, np.inf]" + f"[{edges[bin_dim].isel({bin_dim: -1}).values}, np.inf]" ) return res @@ -1178,11 +1178,11 @@ def _drop_intermediate(fpr, tpr): optimal_idxs["probability_bin"] = np.arange(optimal_idxs.probability_bin.size) if isinstance(optimal_idxs, xr.Dataset): optimal_idxs = optimal_idxs.to_array() - with suppress_warnings("invalid value encountered in true_divide"): - with suppress_warnings("invalid value encountered in long_scalars"): - optimal_idxs = optimal_idxs.where( - optimal_idxs, drop=True - ).probability_bin.values + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + optimal_idxs = optimal_idxs.where( + optimal_idxs, drop=True + ).probability_bin.values tpr = tpr.isel(probability_bin=optimal_idxs) fpr = fpr.isel(probability_bin=optimal_idxs) return fpr, tpr @@ -1191,12 +1191,15 @@ def _drop_intermediate(fpr, tpr): def _auc(fpr, tpr, dim="probability_bin"): """Get area under the curve with trapez method.""" # reverse tpr, fpr to fpr, tpr, see numpy.trapz(y, x=None) - with suppress_warnings("The `numpy.trapz` function is not implemented"): - with suppress_warnings("invalid value encountered in long_scalars"): - with suppress_warnings("invalid value encountered in true_divide"): - area = xr.apply_ufunc( - np.trapz, tpr, fpr, input_core_dims=[[dim], [dim]], dask="allowed" - ) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) + area = xr.apply_ufunc( + np.trapezoid, + tpr, + fpr, + input_core_dims=[[dim], [dim]], + dask="allowed", + ) area = abs(area) if (area > 1).any(): area = area.clip(0, 1) # allow only values between 0 and 1 @@ -1274,7 +1277,7 @@ def roc( References ---------- - http://www.cawcr.gov.au/projects/verification/ + https://www.cawcr.gov.au/projects/verification/ """ if dim is None: diff --git a/xskillscore/core/stattests.py b/xskillscore/core/stattests.py index b51c1894..445bd8ef 100644 --- a/xskillscore/core/stattests.py +++ b/xskillscore/core/stattests.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Literal, Mapping, Optional, Tuple +from typing import Literal, Mapping, Optional, Tuple, Union import xarray as xr from statsmodels.stats.multitest import multipletests as statsmodels_multipletests @@ -31,9 +31,9 @@ def multipletests( "pvals_corrected", "all_as_result_dim", "all_as_tuple" ] = "all_as_result_dim", **multipletests_kwargs: Mapping, -) -> Tuple[XArray, XArray]: +) -> Union[XArray, Tuple[XArray, ...]]: """Apply statsmodels.stats.multitest.multipletests for controlling the false - discovery rate for multiple hypothesis tests for multi-dimensional + discovery rate for multiple hypothesis tests for multidimensional xr.DataArray and xr.Datasets. Parameters @@ -74,7 +74,6 @@ def multipletests( **multipletests_kwargs : dict, optional is_sorted, returnsorted, see statsmodels.stats.multitest.multitest - Returns ------- reject : xarray.Dataset or xarray.DataArray @@ -143,7 +142,7 @@ def multipletests( "fdr_tsbh", "fdr_tsbky", ] - msg = "alpha must be float between 0.0 and 1.0" + msg = "Alpha must be float between 0.0 and 1.0." if not isinstance(alpha, float): raise ValueError(msg) elif alpha <= 0.0 or alpha >= 1.0: @@ -162,7 +161,7 @@ def multipletests( allowed_return_results = ["all_as_tuple", "pvals_corrected", "all_as_result_dim"] if return_results not in allowed_return_results: raise ValueError( - f"expect `return_results` from {allowed_return_results}, " + f"Expected `return_results` from {allowed_return_results}, " f"found {return_results}" ) @@ -180,10 +179,10 @@ def multipletests( ret = tuple(r.unstack("s").transpose(*p.dims, ...) for r in ret) - def _add_kwargs_as_coords(ret): - ret.coords["multipletests_method"] = method - ret.coords["multipletests_alpha"] = alpha - return ret + def _add_kwargs_as_coords(r: XArray): + r.coords["multipletests_method"] = method + r.coords["multipletests_alpha"] = alpha + return r ret = tuple(_add_kwargs_as_coords(r) for r in ret) @@ -195,5 +194,5 @@ def _add_kwargs_as_coords(ret): for i, r in enumerate(ret): r.coords["result"] = returns[i] return ret - elif return_results == "pvals_corrected": + else: return ret[1].assign_coords(result="pvals_corrected") diff --git a/xskillscore/core/utils.py b/xskillscore/core/utils.py index 1cb2476d..97de4fef 100644 --- a/xskillscore/core/utils.py +++ b/xskillscore/core/utils.py @@ -1,7 +1,5 @@ from __future__ import annotations -import contextlib -import warnings from typing import List, Tuple import numpy as np @@ -13,16 +11,6 @@ __all__ = ["histogram"] -@contextlib.contextmanager -def suppress_warnings(msg=None): - """Catch warnings with message msg. From - https://github.com/TheClimateCorporation/properscoring/blob/master/properscoring/ - _utils.py#L23.""" - with warnings.catch_warnings(): - warnings.filterwarnings("ignore", msg) - yield - - def _preprocess_dims(dim: Dim | None, a: XArray) -> Tuple[List[str], Tuple[int, ...]]: """Preprocesses dimensions to prep for stacking. Parameters @@ -142,9 +130,9 @@ def histogram(*args, bins=None, bin_names=None, **kwargs): if isinstance(kwargs["dim"], str): kwargs["dim"] = [kwargs["dim"]] for bin in bins: - assert isinstance( - bin, np.ndarray - ), f"all bins must be numpy arrays, found {type(bin)}" + assert isinstance(bin, np.ndarray), ( + f"all bins must be numpy arrays, found {type(bin)}" + ) if isinstance(args[0], xr.Dataset): # Get list of variables that are shared across all Datasets diff --git a/xskillscore/tests/test_deterministic.py b/xskillscore/tests/test_deterministic.py index 8b446459..fd4e40a9 100644 --- a/xskillscore/tests/test_deterministic.py +++ b/xskillscore/tests/test_deterministic.py @@ -333,7 +333,7 @@ def test_dim_None(a, b, metrics): else: metric, _metric = metrics res = metric(a, b, dim=None) - assert len(res.dims) == 0, print(res.dims) + assert len(res.sizes) == 0, print(res.sizes) @pytest.mark.parametrize( @@ -352,7 +352,7 @@ def test_dim_empty_list(a, b, metrics): elif metrics in distance_metrics: metric, _metric = metrics res = metric(a, b, dim=[]) - assert len(res.dims) == len(a.dims), print(res.dims) + assert len(res.sizes) == len(a.sizes), print(res.sizes) @pytest.mark.parametrize( diff --git a/xskillscore/tests/test_metric_results_accurate.py b/xskillscore/tests/test_metric_results_accurate.py index 8b402099..c4549818 100644 --- a/xskillscore/tests/test_metric_results_accurate.py +++ b/xskillscore/tests/test_metric_results_accurate.py @@ -62,7 +62,7 @@ def test_xs_same_as_skl(a_1d, b_1d, xs_skl_metrics): def test_xs_same_as_skl_rmse(a_1d, b_1d): actual = rmse(a_1d, b_1d, "time") - expected = mean_squared_error(a_1d, b_1d, squared=False) + expected = np.sqrt(mean_squared_error(a_1d, b_1d)) assert np.allclose(actual, expected) diff --git a/xskillscore/tests/test_probabilistic.py b/xskillscore/tests/test_probabilistic.py index 283122af..26b08e53 100644 --- a/xskillscore/tests/test_probabilistic.py +++ b/xskillscore/tests/test_probabilistic.py @@ -1,3 +1,5 @@ +import warnings + import numpy as np import numpy.testing as npt import properscoring @@ -21,7 +23,6 @@ rps, threshold_brier_score, ) -from xskillscore.core.utils import suppress_warnings DIMS = ["lon", "lat", ["lon", "lat"], None, []] @@ -242,7 +243,7 @@ def test_threshold_brier_score_api_and_inputs( # test for numerical identity of xs threshold and properscoring threshold if keep_attrs: expected = expected.assign_attrs(**actual.attrs) - assert_identical(actual, expected) + npt.assert_allclose(actual, expected, rtol=1e7) # test that returns chunks assert_chunk(actual, chunk_bool) # test that attributes are kept @@ -386,8 +387,9 @@ def test_discrimination_sum(o, f_prob, dim, chunk_bool, input_type): assign_type_input_output(disc, o) if "Dataset" in input_type: disc = disc[list(o.data_vars)[0]] - # dont understand the error message here, but it appeared - with suppress_warnings("invalid value encountered in true_divide"): + # don't understand the error message here, but it appeared + with warnings.catch_warnings(): + warnings.simplefilter("ignore", RuntimeWarning) hist_event_sum = ( disc.sel(event=True).sum("forecast_probability", skipna=False).values ) @@ -420,7 +422,7 @@ def test_discrimination_perfect_values(o): def test_reliability_api_and_inputs(o, f_prob, dim, chunk_bool, input_type): """Test that reliability keeps chunking and input types.""" o, f_prob = modify_inputs(o, f_prob, input_type, chunk_bool) - if dim == []: + if isinstance(dim, list) and len(dim) == 0: with pytest.raises(ValueError): reliability(o > 0.5, (f_prob > 0.5).mean("member"), dim) else: @@ -680,7 +682,7 @@ def test_2_category_rps_equals_brier_score(o, f_prob, fair_bool): category_edges=category_edges, dim=None, fair=fair_bool, - ).drop(["forecasts_category_edge", "observations_category_edge"]), + ).drop_vars(["forecasts_category_edge", "observations_category_edge"]), brier_score(o > 0.5, (f_prob > 0.5), dim=None, fair=fair_bool), ) @@ -855,7 +857,8 @@ def test_rps_new_identical_old_xhistogram(o, f_prob, fair_bool): expected = rps_xhist(o, f_prob, dim=dim, category_edges=category_edges_np) drop = ["observations_category_edge", "forecasts_category_edge"] assert_allclose( - actual.rename("histogram_category_edge").drop(drop), expected.drop(drop) + actual.rename("histogram_category_edge").drop_vars(drop), + expected.drop_vars(drop), ) @@ -906,7 +909,7 @@ def test_roc_returns( np.random.normal(size=(100)), coords=[("time", np.arange(100))] ) else: - times = xr.cftime_range(start="2000", freq="D", periods=10) + times = xr.date_range(start="2000", freq="D", periods=10, use_cftime=True) lats = np.arange(4) lons = np.arange(5) data_obs = np.random.randint(0, 10, size=(len(times), len(lats), len(lons))) diff --git a/xskillscore/tests/test_weighted_metric_results_accurate.py b/xskillscore/tests/test_weighted_metric_results_accurate.py index 2f6e2e74..01d1cfb3 100644 --- a/xskillscore/tests/test_weighted_metric_results_accurate.py +++ b/xskillscore/tests/test_weighted_metric_results_accurate.py @@ -70,8 +70,10 @@ def test_xs_same_as_skl_weighted(a_1d, b_1d, weights_linear_time_1d, xs_skl_metr def test_xs_same_as_skl_rmse_weighted(a_1d, b_1d, weights_linear_time_1d): actual = rmse(a_1d, b_1d, "time", weights_linear_time_1d) - expected = mean_squared_error( - a_1d, b_1d, squared=False, sample_weight=weights_linear_time_1d + expected = np.sqrt( + mean_squared_error( + a_1d.values, b_1d.values, sample_weight=weights_linear_time_1d + ) ) assert np.allclose(actual, expected) diff --git a/xskillscore/versioning/print_versions.py b/xskillscore/versioning/print_versions.py index 0dee3a44..e5749313 100644 --- a/xskillscore/versioning/print_versions.py +++ b/xskillscore/versioning/print_versions.py @@ -130,7 +130,7 @@ def main(): "--json", metavar="FILE", nargs=1, - help="Save output as JSON into file, pass in " "'-' to output to stdout", + help="Save output as JSON into file, pass in '-' to output to stdout", ) (options, args) = parser.parse_args()