Skip to content

Commit 0b22e8c

Browse files
dweindlclaude
andcommitted
Fix pandas 3.1 deprecations, require pandas>=2.3
* Don't pass `float_precision` to `read_csv` for pandas>=3.1, where round-trip float parsing is the default * Replace deprecated `inplace=True` by reassignment. As a consequence, `get_{condition,observable,parameter,mapping}_df` no longer modify DataFrames passed by the caller * Avoid `fillna` with incompatible fill values * Use a full-length key when adding the sum row to a MultiIndex DataFrame in `get_data_per_observable` * Turn all warnings, including `DeprecationWarning`s, into errors in tests. The pandas deprecation warnings derive from `DeprecationWarning`, which was ignored so far * Require pandas>=2.3 in accordance with NEP 29 Closes #570 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 68be6d1 commit 0b22e8c

24 files changed

Lines changed: 108 additions & 88 deletions

‎petab/_utils.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,20 @@
22

33
from pathlib import Path
44

5+
import pandas as pd
56
from pydantic import AnyUrl, TypeAdapter
67

78
PathOrUrlAdapter = TypeAdapter(AnyUrl | Path)
89

10+
#: Extra keyword arguments for :func:`pandas.read_csv` to ensure lossless
11+
#: float parsing. ``float_precision`` is deprecated in pandas>=3.1,
12+
#: where round-trip parsing is the default.
13+
READ_CSV_FLOAT_KWARGS = (
14+
{"float_precision": "round_trip"}
15+
if tuple(map(int, pd.__version__.split(".")[:2])) < (3, 1)
16+
else {}
17+
)
18+
919

1020
def _generate_path(
1121
file_path: str | Path | AnyUrl,

‎petab/v1/conditions.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import numpy as np
77
import pandas as pd
88

9+
from .._utils import READ_CSV_FLOAT_KWARGS
910
from . import core, lint
1011
from .C import *
1112

@@ -32,21 +33,20 @@ def get_condition_df(
3233

3334
if isinstance(condition_file, str | Path):
3435
condition_file = pd.read_csv(
35-
condition_file, sep="\t", float_precision="round_trip"
36+
condition_file, sep="\t", **READ_CSV_FLOAT_KWARGS
3637
)
3738

3839
lint.assert_no_leading_trailing_whitespace(
3940
condition_file.columns.values, "condition"
4041
)
4142

4243
if not isinstance(condition_file.index, pd.RangeIndex):
43-
condition_file.reset_index(
44+
condition_file = condition_file.reset_index(
4445
drop=condition_file.index.name != CONDITION_ID,
45-
inplace=True,
4646
)
4747

4848
try:
49-
condition_file.set_index([CONDITION_ID], inplace=True)
49+
condition_file = condition_file.set_index([CONDITION_ID])
5050
except KeyError:
5151
raise KeyError(
5252
f"Condition table missing mandatory field {CONDITION_ID}."
@@ -90,7 +90,7 @@ def create_condition_df(
9090
raise ValueError("Invalid parameter ID: " + p)
9191
df[p] = np.nan
9292

93-
df.set_index(CONDITION_ID, inplace=True)
93+
df = df.set_index(CONDITION_ID)
9494

9595
return df
9696

‎petab/v1/core.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import pandas as pd
1515
from pandas.api.types import is_string_dtype
1616

17+
from .._utils import READ_CSV_FLOAT_KWARGS
1718
from . import yaml
1819
from .C import *
1920

@@ -52,7 +53,7 @@ def get_simulation_df(simulation_file: str | Path) -> pd.DataFrame:
5253
Simulation DataFrame
5354
"""
5455
return pd.read_csv(
55-
simulation_file, sep="\t", index_col=None, float_precision="round_trip"
56+
simulation_file, sep="\t", index_col=None, **READ_CSV_FLOAT_KWARGS
5657
)
5758

5859

@@ -94,7 +95,7 @@ def get_visualization_df(
9495
sep="\t",
9596
index_col=None,
9697
converters=types,
97-
float_precision="round_trip",
98+
**READ_CSV_FLOAT_KWARGS,
9899
)
99100
except pd.errors.EmptyDataError:
100101
warn(

‎petab/v1/mapping.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import pandas as pd
77

8+
from .._utils import READ_CSV_FLOAT_KWARGS
89
from . import lint
910
from .C import *
1011
from .models import Model
@@ -34,13 +35,12 @@ def get_mapping_df(
3435

3536
if isinstance(mapping_file, str | Path):
3637
mapping_file = pd.read_csv(
37-
mapping_file, sep="\t", float_precision="round_trip"
38+
mapping_file, sep="\t", **READ_CSV_FLOAT_KWARGS
3839
)
3940

4041
if not isinstance(mapping_file.index, pd.RangeIndex):
41-
mapping_file.reset_index(
42+
mapping_file = mapping_file.reset_index(
4243
drop=mapping_file.index.name != PETAB_ENTITY_ID,
43-
inplace=True,
4444
)
4545

4646
for col in MAPPING_DF_REQUIRED_COLS:
@@ -51,7 +51,7 @@ def get_mapping_df(
5151
mapping_file.reset_index()[col].values, col
5252
)
5353

54-
mapping_file.set_index([PETAB_ENTITY_ID], inplace=True)
54+
mapping_file = mapping_file.set_index([PETAB_ENTITY_ID])
5555

5656
return mapping_file
5757

‎petab/v1/measurements.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import numpy as np
99
import pandas as pd
1010

11+
from .._utils import READ_CSV_FLOAT_KWARGS
1112
from . import core, lint, observables
1213
from .C import *
1314

@@ -42,7 +43,7 @@ def get_measurement_df(
4243

4344
if isinstance(measurement_file, str | Path):
4445
measurement_file = pd.read_csv(
45-
measurement_file, sep="\t", float_precision="round_trip"
46+
measurement_file, sep="\t", **READ_CSV_FLOAT_KWARGS
4647
)
4748

4849
lint.assert_no_leading_trailing_whitespace(
@@ -94,7 +95,8 @@ def get_simulation_conditions(measurement_df: pd.DataFrame) -> pd.DataFrame:
9495
# We require NaN-containing rows, but they are ignored by `groupby`,
9596
# therefore replace them before
9697
simulation_conditions = (
97-
measurement_df.fillna("")
98+
measurement_df[grouping_cols]
99+
.fillna("")
98100
.groupby(grouping_cols)
99101
.size()
100102
.reset_index()[grouping_cols]
@@ -129,7 +131,9 @@ def get_rows_for_condition(
129131
# check for equality in all grouping cols
130132
if PREEQUILIBRATION_CONDITION_ID in condition:
131133
row_filter = (
132-
measurement_df[PREEQUILIBRATION_CONDITION_ID].fillna("")
134+
measurement_df[PREEQUILIBRATION_CONDITION_ID]
135+
.astype("object")
136+
.fillna("")
133137
== condition[PREEQUILIBRATION_CONDITION_ID]
134138
) & row_filter
135139
if SIMULATION_CONDITION_ID in condition:
@@ -251,7 +255,7 @@ def measurements_have_replicates(measurement_df: pd.DataFrame) -> bool:
251255
],
252256
)
253257
return np.any(
254-
measurement_df.fillna("").groupby(grouping_cols).size().values - 1
258+
measurement_df.groupby(grouping_cols, dropna=False).size().values - 1
255259
)
256260

257261

‎petab/v1/observables.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import pandas as pd
99

10+
from .._utils import READ_CSV_FLOAT_KWARGS
1011
from . import core, lint
1112
from .C import *
1213
from .math import sympify_petab
@@ -39,21 +40,20 @@ def get_observable_df(
3940

4041
if isinstance(observable_file, str | Path):
4142
observable_file = pd.read_csv(
42-
observable_file, sep="\t", float_precision="round_trip"
43+
observable_file, sep="\t", **READ_CSV_FLOAT_KWARGS
4344
)
4445

4546
lint.assert_no_leading_trailing_whitespace(
4647
observable_file.columns.values, "observable"
4748
)
4849

4950
if not isinstance(observable_file.index, pd.RangeIndex):
50-
observable_file.reset_index(
51+
observable_file = observable_file.reset_index(
5152
drop=observable_file.index.name != OBSERVABLE_ID,
52-
inplace=True,
5353
)
5454

5555
try:
56-
observable_file.set_index([OBSERVABLE_ID], inplace=True)
56+
observable_file = observable_file.set_index([OBSERVABLE_ID])
5757
except KeyError:
5858
raise KeyError(
5959
f"Observable table missing mandatory field {OBSERVABLE_ID}."

‎petab/v1/parameters.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import numpy as np
1515
import pandas as pd
1616

17+
from .._utils import READ_CSV_FLOAT_KWARGS
1718
from . import conditions, core, lint, measurements, observables
1819
from .C import *
1920
from .models import Model
@@ -59,7 +60,7 @@ def get_parameter_df(
5960
parameter_df = parameter_file
6061
elif isinstance(parameter_file, str | Path):
6162
parameter_df = pd.read_csv(
62-
parameter_file, sep="\t", float_precision="round_trip"
63+
parameter_file, sep="\t", **READ_CSV_FLOAT_KWARGS
6364
)
6465
elif isinstance(parameter_file, Iterable):
6566
dfs = [get_parameter_df(x) for x in parameter_file if x]
@@ -78,13 +79,12 @@ def get_parameter_df(
7879
)
7980

8081
if not isinstance(parameter_df.index, pd.RangeIndex):
81-
parameter_df.reset_index(
82+
parameter_df = parameter_df.reset_index(
8283
drop=parameter_file.index.name != PARAMETER_ID,
83-
inplace=True,
8484
)
8585

8686
try:
87-
parameter_df.set_index([PARAMETER_ID], inplace=True)
87+
parameter_df = parameter_df.set_index([PARAMETER_ID])
8888
except KeyError as e:
8989
raise KeyError(
9090
f"Parameter table missing mandatory field {PARAMETER_ID}."
@@ -240,7 +240,7 @@ def create_parameter_df(
240240
OBJECTIVE_PRIOR_PARAMETERS: "",
241241
}
242242
)
243-
df.set_index([PARAMETER_ID], inplace=True)
243+
df = df.set_index([PARAMETER_ID])
244244

245245
# For model parameters, set nominal values as defined in the model
246246
for parameter_id in df.index:

‎petab/v1/simplify.py‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,7 @@ def remove_nan_measurements(problem: Problem):
2323
"""Drop any measurements that are NaN"""
2424
problem.measurement_df = problem.measurement_df[
2525
~problem.measurement_df[MEASUREMENT].isna()
26-
]
27-
problem.measurement_df.reset_index(inplace=True, drop=True)
26+
].reset_index(drop=True)
2827

2928

3029
def remove_unused_observables(problem: Problem):
@@ -103,8 +102,7 @@ def condition_parameters_to_parameter_table(problem: Problem):
103102
}
104103
for parameter_id, value in replacements.items()
105104
]
106-
rows = pd.DataFrame(rows)
107-
rows.set_index(PARAMETER_ID, inplace=True)
105+
rows = pd.DataFrame(rows).set_index(PARAMETER_ID)
108106

109107
if problem.parameter_df is None:
110108
problem.parameter_df = rows

‎petab/v1/visualize/data_overview.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,10 @@ def get_data_per_observable(measurement_df: pd.DataFrame) -> pd.DataFrame:
8383
)
8484

8585
# Add row and column sums
86-
data_per_observable.loc["SUM", :] = data_per_observable.sum(axis=0).values
86+
sum_row = "SUM" if len(index) == 1 else ("SUM", "")
87+
data_per_observable.loc[sum_row, :] = data_per_observable.sum(
88+
axis=0
89+
).values
8790
data_per_observable["SUM"] = data_per_observable.sum(axis=1).values
8891

8992
data_per_observable = data_per_observable.astype(int)

‎petab/v1/visualize/plotting.py‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@ def __init__(
6161
conditions_: np.ndarray | pd.Series | None,
6262
data_to_plot: pd.DataFrame | None = None,
6363
):
64-
self.data_to_plot = data_to_plot
65-
self.data_to_plot.sort_index(inplace=True)
64+
self.data_to_plot = data_to_plot.sort_index()
6665

6766
self.conditions = conditions_
6867
if self.conditions is not None:
@@ -76,7 +75,7 @@ def __init__(
7675
if isinstance(self.conditions, np.ndarray):
7776
self.conditions.sort()
7877
elif isinstance(self.conditions, pd.Series):
79-
self.conditions.sort_index(inplace=True)
78+
self.conditions = self.conditions.sort_index()
8079

8180
def add_x_offset(self, offset) -> None:
8281
"""

0 commit comments

Comments
 (0)