Skip to content

Commit 1e1a12a

Browse files
Merge pull request #321 from benmsanderson/fix/pandas3-compat
Fix pandas 3.0 / xarray 2025+ compat across groupby, _xarray, run, netcdf
2 parents edf9b62 + 35147db commit 1e1a12a

13 files changed

Lines changed: 83 additions & 27 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
uses: actions/checkout@v3
1616
- uses: ./.github/actions/setup
1717
with:
18-
python-version: "3.9"
18+
python-version: "3.10"
1919
venv-id: "docs"
2020
poetry-dependency-install-flags: "--all-extras --only 'main,dev'"
2121
# There are still a lot of mypy errors to resolve
@@ -31,7 +31,7 @@ jobs:
3131
uses: actions/checkout@v3
3232
- uses: ./.github/actions/setup
3333
with:
34-
python-version: "3.9"
34+
python-version: "3.10"
3535
venv-id: "docs"
3636
poetry-dependency-install-flags: "--all-extras --only 'main,docs,notebooks'"
3737
- name: docs
@@ -43,7 +43,7 @@ jobs:
4343
fail-fast: false
4444
matrix:
4545
os: [ "ubuntu-latest" ]
46-
python-version: [ "3.9", "3.10", "3.11" ]
46+
python-version: [ "3.10", "3.11" ]
4747
runs-on: "${{ matrix.os }}"
4848
steps:
4949
- name: Check out repository
@@ -88,7 +88,7 @@ jobs:
8888
runs-on: ubuntu-latest
8989
strategy:
9090
matrix:
91-
python-version: ["3.9"]
91+
python-version: ["3.10"]
9292

9393
steps:
9494
- name: Checkout repository
@@ -107,7 +107,7 @@ jobs:
107107
fail-fast: false
108108
matrix:
109109
os: [ "ubuntu-latest" ]
110-
python-version: [ "3.9", "3.10", "3.11" ]
110+
python-version: [ "3.10", "3.11" ]
111111
runs-on: "${{ matrix.os }}"
112112
steps:
113113
- name: Check out repository
@@ -127,7 +127,7 @@ jobs:
127127
uses: actions/checkout@v3
128128
- uses: ./.github/actions/setup
129129
with:
130-
python-version: "3.9"
130+
python-version: "3.10"
131131
venv-id: "check-build-${{ runner.os }}"
132132
run-poetry-install: false
133133
poetry-dependency-install-flags: "not used"
@@ -145,7 +145,7 @@ jobs:
145145
uses: actions/checkout@v3
146146
- uses: ./.github/actions/setup
147147
with:
148-
python-version: "3.9"
148+
python-version: "3.10"
149149
venv-id: "licence-check"
150150
poetry-dependency-install-flags: "--all-extras"
151151
- name: Check licences of dependencies

.github/workflows/deploy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
fetch-depth: 0
2020
- uses: ./.github/actions/setup
2121
with:
22-
python-version: "3.9"
22+
python-version: "3.10"
2323
venv-id: "deploy"
2424
poetry-dependency-install-flags: "--all-extras"
2525
- name: Run tests

.github/workflows/install-conda.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
15-
python-version: ['3.8', '3.9', '3.10', '3.11']
15+
python-version: ['3.10', '3.11']
1616

1717
steps:
1818
- name: Setup conda

.github/workflows/install.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
fail-fast: false
1313
matrix:
1414
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
15-
python-version: [ "3.9", "3.10", "3.11" ]
15+
python-version: [ "3.10", "3.11" ]
1616
steps:
1717
- name: Set up Python "${{ matrix.python-version }}"
1818
id: setup-python

.github/workflows/release.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
fetch-depth: 0
2020
- uses: ./.github/actions/setup
2121
with:
22-
python-version: "3.9"
22+
python-version: "3.10"
2323
venv-id: "release"
2424
poetry-dependency-install-flags: "--all-extras"
2525
- name: Add version to environment

.readthedocs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ version: 2
1111
build:
1212
os: ubuntu-22.04
1313
tools:
14-
python: "3.9"
14+
python: "3.10"
1515
jobs:
1616
post_create_environment:
1717
- pip install poetry

changelog/321.fix.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Restored compatibility with pandas 3.0 and xarray 2025+ by patching four places where the older APIs are no longer accepted:
2+
3+
- `RunGroupBy.__init__` previously called `numpy.issubdtype(col.dtype, numpy.number)` to detect numeric meta columns, which raises `TypeError` on `pandas.StringDtype` (the new default for inferred string columns). Routed through `pandas.api.types.is_numeric_dtype`.
4+
- `_xarray._many_to_one` ended with `Series[0]` positional indexing, which pandas 3 has removed. Replaced with `.iloc[0]`.
5+
- `ScmRun.convert_unit`, `_binary_op` and `_unary_op` wrote results via `self._df.values[:] = ...`. pandas 3 makes `DataFrame.values` return a read-only array, so the in-place write raises `ValueError`. Switched to `self._df.iloc[:, :] = ...`; the binary/unary op sites additionally cast their right-hand side to `float` so that comparison ops (`lt`, `eq`, `ne`, ...) which return `bool` arrays continue to land as `float64` in the underlying numeric block, preserving the prior silent-cast behaviour.
6+
- `_read_nc` passed `use_cftime=True` to `xarray.load_dataset`, which xarray 2025+ deprecates in favour of `decode_times=xr.coders.CFDatetimeCoder(use_cftime=True)`. The deprecation otherwise fires a `FutureWarning` on every `ScmRun.from_nc()` call. Routes through the new API where available and falls back to the bare kwarg on xarray older than 2024.09.
7+
8+
The previously-failing `ScmRun.groupby`, `ScmRun.to_xarray`, `ScmRun.convert_unit`, `ScmRun.to_nc` / `ScmRun.from_nc`, and the arithmetic and comparison operators on `ScmRun` now all run on both pandas 2 and pandas 3.

pyproject.toml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,13 @@ classifiers = [
2020
"License :: OSI Approved :: BSD License",
2121
"Intended Audience :: Developers",
2222
"Operating System :: OS Independent",
23-
"Programming Language :: Python :: 3.8",
24-
"Programming Language :: Python :: 3.9",
2523
"Programming Language :: Python :: 3.10",
2624
"Programming Language :: Python :: 3.11",
2725
]
2826

2927
[tool.poetry.dependencies]
3028
# TODO: get rid of this stupid upper level bound
31-
python = ">=3.9,<4"
29+
python = ">=3.10,<4"
3230
cftime = ">=1.5"
3331
numpy = "*"
3432
openscm-units = "*"
@@ -39,10 +37,7 @@ pint-pandas = "*"
3937
python-dateutil = "*"
4038
tqdm = "*"
4139
six = "*"
42-
xarray = [
43-
{ version = "*", python = ">=3.10" },
44-
{ version = "<=2025.03", python = ">=3.9,<3.10" },
45-
]
40+
xarray = "*"
4641
nc-time-axis = { version = ">=1.2.0", optional = true }
4742
typing-extensions = "*"
4843
matplotlib = { version = ">=3.7.1", optional = true }
@@ -83,6 +78,7 @@ pre-commit = "^3.3.1"
8378
towncrier = "^23.6.0"
8479
liccheck = "^0.9.1"
8580
pandas-stubs = "<3"
81+
setuptools = "<81" # liccheck imports pkg_resources, removed in setuptools 81
8682

8783
[tool.poetry.group.notebooks.dependencies]
8884
myst-nb = "^0.17.0"
@@ -250,12 +246,19 @@ authorized_licenses = [
250246
"bsd",
251247
"bsd license",
252248
"BSD 3-Clause",
249+
"BSD-2-Clause",
250+
"BSD-3-Clause",
253251
"CC0",
252+
"CC0-1.0",
254253
"apache",
255254
"apache 2.0",
256255
"apache software",
257256
"apache software license",
258257
"Apache License, Version 2.0",
258+
"Apache-2.0",
259+
# SPDX AND-expressions are not split by liccheck, so list as-is
260+
"MPL-2.0 AND MIT",
261+
"BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0",
259262
"Historical Permission Notice and Disclaimer (HPND)",
260263
"isc license",
261264
"isc license (iscl)",
@@ -266,7 +269,9 @@ authorized_licenses = [
266269
"GNU Lesser General Public License v2 or later (LGPLv2+)",
267270
"mit",
268271
"mit license",
272+
"MIT",
269273
"Mozilla Public License 2.0 (MPL 2.0)",
274+
"MPL-2.0",
270275
"psf-2.0",
271276
"python software foundation",
272277
"python software foundation license",

src/scmdata/_xarray.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,10 @@ def _many_to_one(df, col1, col2):
198198
# thanks https://stackoverflow.com/a/59091549
199199
checker = df[[col1, col2]].drop_duplicates()
200200

201-
max_count = checker.groupby(col2).count().max()[0]
201+
# ``.iloc[0]`` rather than ``[0]``: pandas 3.0 removed positional
202+
# integer indexing on label-indexed Series, so ``[0]`` would raise
203+
# ``KeyError: 0`` on the Series returned by the chained ``.max()``.
204+
max_count = checker.groupby(col2).count().max().iloc[0]
202205
if max_count < 1: # pragma: no cover # emergency valve
203206
raise AssertionError
204207

src/scmdata/groupby.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,12 @@ def __init__(
5757
m = run.meta.reset_index(drop=True)
5858
self.na_fill_value = float(na_fill_value)
5959

60-
# Work around the bad handling of NaN values in groupbys
61-
if any([np.issubdtype(m[c].dtype, np.number) for c in m]):
60+
# Work around the bad handling of NaN values in groupbys.
61+
# pd.api.types.is_numeric_dtype accepts every dtype scmdata
62+
# ever emits; np.issubdtype(..., np.number) raises on
63+
# pandas 3.0's default StringDtype with
64+
# ``TypeError: Cannot interpret '<StringDtype(...)>'``.
65+
if any([pd.api.types.is_numeric_dtype(m[c]) for c in m]):
6266
if (m == na_fill_value).any(axis=None):
6367
raise ValueError(
6468
"na_fill_value conflicts with data value. Choose a na_fill_value "

0 commit comments

Comments
 (0)