Skip to content

Commit f621591

Browse files
Add pandas 3.0 support (#294)
* Fix pandas 3.0 incompatibilities in GEO, BEYONDCOAL and MASTR loaders - clean_name: astype(str) preserves NA under the pandas 3 string dtype, so unidecode received float NaN for unnamed plants (GEO, reduced retrieval). fillna("") before the conversion. - BEYONDCOAL: the future.no_silent_downcasting option was removed in pandas 3 (its behaviour is now the default); requesting it raises. Guard the option_context behind a version check. - MASTR: the chunked low-memory parser crashes with usecols on pandas 3 (IndexError in the DtypeWarning column lookup); read with low_memory=False. Postleitzahl astype(str) preserves NA, breaking the final astype(int); fillna("0") in between, identical outcome on pandas 2 where NaN already became the string "nan" -> "000". All previously failing non-Duke tests pass under pandas 3.0.3 (test_data, test_plots, test_aggregate[GEO/BEYONDCOAL/MASTR], test_reduced_retrieval); behaviour unchanged under pandas 2.x. * Complete pandas 3.0 support: lift pin to <4, fix Duke/aggregate/matching paths Relax the dependency pin from pandas<3 to pandas<4 and fix the four remaining breakages that surface once pandas 3 is installed (these sit behind the Java-gated Duke aggregate/matching tests, so they are not visible without a JDK on PATH): - duke.add_geoposition_for_duke: astype(str) preserves NA on pandas 3, so ','.join hit a float NaN. Stringify per item (map(str, s)) so NaN becomes 'nan', keeping the 'nan,nan' -> NaN cleanup valid. - cleaning.aggregate_units: second future.no_silent_downcasting option_context (the twin of the BEYONDCOAL one) version-gated; drop the deprecated copy= kwarg from infer_objects (no-op under CoW). - matching.best_matches: groupby(...).apply with as_index=False re-inserts the grouping column on pandas 3, absent from the per-group Series -> KeyError. Rewrote with a vectorised idxmax (behaviour- identical, faster, no apply). Full test suite (incl. Duke aggregate + matching) passes under both pandas 2.x and 3.0.3. * Remove inline review comments from pandas 3 fixes Per maintainer review on #294: drop the explanatory comments added for review. Code changes are unchanged. * Extract pandas-3 check into PANDAS_V3 constant Addresses the review on #294: replace the inline parse_version(pd.__version__).major < 3 check with a named PANDAS_V3 constant defined once in core.py, used by both cleaning.aggregate_units and data.BEYONDCOAL. Behaviour unchanged; full test suite passes (48) under pandas 3.0.3.
1 parent 459f19f commit f621591

6 files changed

Lines changed: 35 additions & 15 deletions

File tree

powerplantmatching/cleaning.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Functions for vertically cleaning a dataset.
77
"""
88

9+
import contextlib
910
import logging
1011

1112
import networkx as nx
@@ -14,7 +15,7 @@
1415
import unidecode
1516
from deprecation import deprecated
1617

17-
from .core import get_config, get_obj_if_Acc
18+
from .core import PANDAS_V3, get_config, get_obj_if_Acc
1819
from .duke import duke
1920
from .utils import get_name, set_column_name
2021

@@ -74,7 +75,7 @@ def clean_name(df, config=None):
7475
if config is None:
7576
config = get_config()
7677

77-
name = df.Name.astype(str).copy().apply(unidecode.unidecode)
78+
name = df.Name.fillna("").astype(str).copy().apply(unidecode.unidecode)
7879

7980
roman_to_arabic = {
8081
"I": "1",
@@ -513,9 +514,13 @@ def aggregate_units(
513514
df = cliques(df, duplicates)
514515
df = df.groupby("grouped").agg(props_for_groups)
515516

516-
# Downcasting in replace is deprecated
517-
with pd.option_context("future.no_silent_downcasting", True):
518-
df[str_cols] = df[str_cols].replace("", pd.NA).infer_objects(copy=False)
517+
no_downcast_ctx = (
518+
contextlib.nullcontext()
519+
if PANDAS_V3
520+
else pd.option_context("future.no_silent_downcasting", True)
521+
)
522+
with no_downcast_ctx:
523+
df[str_cols] = df[str_cols].replace("", pd.NA).infer_objects()
519524

520525
df = (
521526
df.assign(

powerplantmatching/core.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@
66
from os import environ, makedirs
77
from os.path import abspath, dirname, exists, expanduser, isdir, join
88

9+
import pandas as pd
10+
from packaging.version import parse as parse_version
11+
12+
# pandas >= 3.0 changed several defaults (NA-preserving string dtype,
13+
# no-silent-downcasting) and removed knobs the loaders previously gated on.
14+
PANDAS_V3 = parse_version(pd.__version__).major >= 3
15+
916
# for the writable data directory (i.e. the one where new data goes), follow
1017
# the XDG guidelines found at
1118
# https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html

powerplantmatching/data.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
Collection of power plant data bases and statistical data
77
"""
88

9+
import contextlib
910
import json
1011
import logging
1112
import os
@@ -24,7 +25,7 @@
2425
gather_set_info,
2526
gather_specifications,
2627
)
27-
from .core import _package_data, get_config
28+
from .core import PANDAS_V3, _package_data, get_config
2829
from .heuristics import PLZ_to_LatLon_map, scale_to_net_capacities
2930
from .utils import (
3031
config_filter,
@@ -93,7 +94,12 @@ def BEYONDCOAL(raw=False, update=False, config=None):
9394
"heat": "CHP",
9495
}
9596

96-
with pd.option_context("future.no_silent_downcasting", True):
97+
no_downcast_ctx = (
98+
contextlib.nullcontext()
99+
if PANDAS_V3
100+
else pd.option_context("future.no_silent_downcasting", True)
101+
)
102+
with no_downcast_ctx:
97103
phaseout_col = "Covered by country phase-out? [if yes: country phase-out year]"
98104
date_out = (
99105
df["(Announced) Retirement year"]
@@ -2398,7 +2404,7 @@ def MASTR(
23982404
)
23992405
usecols = available_columns.intersection(target_columns)
24002406
df = (
2401-
pd.read_csv(file.open(name), usecols=usecols)
2407+
pd.read_csv(file.open(name), usecols=usecols, low_memory=False)
24022408
.assign(Filesuffix=fueltype)
24032409
.query("Nettonennleistung >= @THRESHOLD_KW")
24042410
)
@@ -2434,7 +2440,10 @@ def MASTR(
24342440

24352441
PLZ_map = PLZ_to_LatLon_map()
24362442
df.Postleitzahl = (
2437-
df.Postleitzahl.astype(str).str.replace(r"[^0-9]", "0", regex=True).astype(int)
2443+
df.Postleitzahl.astype(str)
2444+
.fillna("0")
2445+
.str.replace(r"[^0-9]", "0", regex=True)
2446+
.astype(int)
24382447
)
24392448
df["PLZ_lat"] = df.Postleitzahl.map(PLZ_map.lat)
24402449
df["PLZ_lon"] = df.Postleitzahl.map(PLZ_map.lon)

powerplantmatching/duke.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ def add_geoposition_for_duke(df):
2525
if not df.loc[:, ["lat", "lon"]].isnull().all().all():
2626
return df.assign(
2727
Geoposition=df[["lat", "lon"]]
28-
.astype(str)
29-
.apply(lambda s: ",".join(s), axis=1)
28+
.apply(lambda s: ",".join(map(str, s)), axis=1)
3029
.replace("nan,nan", np.nan)
3130
)
3231
else:

powerplantmatching/matching.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ def best_matches(links):
3434
if links.empty:
3535
return pd.DataFrame(columns=labels)
3636
else:
37-
return links.groupby(links.iloc[:, 1], as_index=False, sort=False).apply(
38-
lambda x: x.loc[x.scores.astype(float).idxmax(), labels]
39-
)
37+
scores = links["scores"].astype(float)
38+
best_idx = scores.groupby(links.iloc[:, 1], sort=False).idxmax()
39+
return links.loc[best_idx, labels].reset_index(drop=True)
4040

4141

4242
def compare_two_datasets(dfs, labels, country_wise=True, config=None, **dukeargs):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ requires-python = ">=3.10"
3333
dependencies = [
3434
"numpy",
3535
"scipy",
36-
"pandas>=0.24.0,<3",
36+
"pandas>=0.24.0,<4",
3737
"networkx>=1.10",
3838
"pycountry",
3939
"country_converter",

0 commit comments

Comments
 (0)