Skip to content

Commit c45923c

Browse files
authored
Removed deprecated behaviour from shapiq. (#496)
* Removed deprecated behaviour from shapiq: - removed `path_to_values` from `Game` - made json the only viable file format for `InteractionValues` * updated CHANGELOG.md
1 parent 5d8c92c commit c45923c

5 files changed

Lines changed: 46 additions & 194 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## v1.5.0 (unreleased)
4+
5+
### Removed Deprecated Features
6+
- removes `path_to_values` parameter from `shapiq.Game`, which was previously deprecated. Use `shapiq.Game.load()` instead.
7+
- removes pickle support from `shapiq.InteractionValues`. JSON is now the only supported file format. Use `InteractionValues.save()` and `InteractionValues.load()` with JSON files.
8+
39
## v1.4.1 (2025-11-10)
410

511
### Bugfix

src/shapiq/game.py

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
from __future__ import annotations
44

55
import json
6-
import os
76
import warnings
87
from pathlib import Path
98
from typing import TYPE_CHECKING, Any, TypedDict, cast
@@ -13,7 +12,6 @@
1312

1413
from shapiq.utils import (
1514
powerset,
16-
raise_deprecation_warning,
1715
transform_array_to_coalitions,
1816
transform_coalitions_to_array,
1917
)
@@ -99,10 +97,6 @@ class Game:
9997
>>> new_game.load_values("dummy_game.npz")
10098
>>> game.precomputed, game.n_values_stored
10199
True, 2
102-
>>> # you can also load a game of any class with the Game class
103-
>>> new_game = Game(path_to_values="dummy_game.npz")
104-
>>> new_game.precomputed, new_game.n_values_stored
105-
True, 2
106100
>>> # save and load the game
107101
>>> game.save("game.pkl")
108102
>>> new_game = DummyGame.load("game.pkl")
@@ -130,7 +124,6 @@ def __init__(
130124
*,
131125
normalize: bool = True,
132126
normalization_value: float | None = None,
133-
path_to_values: Path | str | None = None,
134127
verbose: bool = False,
135128
player_names: list[str] | None = None,
136129
**kwargs: Any, # noqa: ARG002
@@ -151,9 +144,6 @@ def __init__(
151144
to ``False`` this value is not required. Otherwise, the value is needed to normalize and
152145
center the game. If no value is provided, the game raises a warning.
153146
154-
path_to_values: The path to load the game values from. If the path is provided, the game
155-
values are loaded from the given path. Defaults to ``None``.
156-
157147
verbose: Whether to show a progress bar for the evaluation. Defaults to ``False``. Note
158148
that this only has an effect if the game is not precomputed and may slow down the
159149
evaluation.
@@ -164,14 +154,7 @@ def __init__(
164154
kwargs: Additional keyword arguments (not used).
165155
166156
"""
167-
if path_to_values is not None:
168-
msg = (
169-
"Initializing a Game with `path_to_values` is deprecated and will be removed in a"
170-
" future version. Use `Game.load` or `Game().load_values()` instead."
171-
)
172-
raise_deprecation_warning(message=msg, deprecated_in="1.3.1", removed_in="1.4.0")
173-
174-
if n_players is None and path_to_values is None:
157+
if n_players is None:
175158
msg = "The number of players has to be provided if game is not loaded from values."
176159
raise ValueError(msg)
177160

@@ -180,11 +163,10 @@ def __init__(
180163

181164
# define storage variables
182165
self.game_values = {}
183-
# TODO(mmschlk): Remove None Assignment below and drop the pyright ignore when we remove path_to_values # noqa: TD003
184-
self.n_players = n_players # type: ignore[assignment]
166+
self.n_players = n_players
185167
# setup normalization of the game
186168
self.normalization_value = 0.0
187-
if normalize and path_to_values is None:
169+
if normalize:
188170
if normalization_value is None:
189171
# this is desired behavior, as in some cases normalization is set by the subclasses
190172
# after init of the base Game class. For example, in the imputer classes.
@@ -201,12 +183,6 @@ def __init__(
201183

202184
game_id = str(hash(self))[:8]
203185
self.game_id = f"{self.get_game_name()}_{game_id}"
204-
if path_to_values is not None:
205-
self.load_values(path_to_values, precomputed=True)
206-
self.game_id = str(path_to_values).split(os.path.sep)[-1].split(".")[0]
207-
# if game should not be normalized, reset normalization value to 0
208-
if not normalize and self.normalization_value != 0:
209-
self.normalization_value = 0.0
210186

211187
# define some handy coalition variables
212188
self.empty_coalition = np.zeros(self.n_players, dtype=bool)

src/shapiq/interaction_values.py

Lines changed: 7 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
import contextlib
66
import copy
77
import json
8-
import pickle
98
from pathlib import Path
109
from typing import TYPE_CHECKING
1110
from warnings import warn
@@ -19,8 +18,6 @@
1918
is_index_aggregated,
2019
is_index_valid,
2120
)
22-
from .utils.errors import raise_deprecation_warning
23-
from .utils.saving import safe_str_to_tuple, safe_tuple_to_str
2421
from .utils.sets import generate_interaction_lookup
2522

2623
if TYPE_CHECKING:
@@ -32,11 +29,6 @@
3229

3330
from shapiq.typing import InteractionScores, JSONType
3431

35-
SAVE_JSON_DEPRECATION_MSG = (
36-
"Saving InteractionValues not as a JSON file is deprecated. "
37-
"The parameters `as_pickle` and `as_npz` will be removed in the future. "
38-
)
39-
4032

4133
class InteractionValues:
4234
"""This class contains the interaction values as estimated by an approximator.
@@ -773,61 +765,18 @@ def get_subset(self, players: list[int]) -> InteractionValues:
773765
baseline_value=self.baseline_value,
774766
)
775767

776-
def save(self, path: Path, *, as_pickle: bool = False, as_npz: bool = False) -> None:
777-
"""Save the InteractionValues object to a file.
778-
779-
By default, the InteractionValues object is saved as a JSON file.
768+
def save(self, path: Path) -> None:
769+
"""Save the InteractionValues object to a JSON file.
780770
781771
Args:
782772
path: The path to save the InteractionValues object to.
783-
as_pickle: Whether to save the InteractionValues object as a pickle file (``True``).
784-
as_npz: Whether to save the InteractionValues object as a ``npz`` file (``True``).
785-
786-
Raises:
787-
DeprecationWarning: If `as_pickle` or `as_npz` is set to ``True``, a deprecation
788-
warning is raised
789773
"""
790774
# check if the directory exists
791775
directory = Path(path).parent
792776
if not Path(directory).exists():
793777
with contextlib.suppress(FileNotFoundError):
794778
Path(directory).mkdir(parents=True, exist_ok=True)
795-
if as_pickle:
796-
raise_deprecation_warning(
797-
message=SAVE_JSON_DEPRECATION_MSG,
798-
deprecated_in="1.3.1",
799-
removed_in="1.4.0",
800-
)
801-
with Path(path).open("wb") as file:
802-
pickle.dump(self, file)
803-
elif as_npz:
804-
raise_deprecation_warning(
805-
message=SAVE_JSON_DEPRECATION_MSG,
806-
deprecated_in="1.3.1",
807-
removed_in="1.4.0",
808-
)
809-
# save object as npz file
810-
interaction_keys = np.array(
811-
list(map(safe_tuple_to_str, self.interaction_lookup.keys()))
812-
)
813-
interaction_indices = np.array(list(self.interaction_lookup.values()))
814-
estimation_budget = self.estimation_budget if self.estimation_budget is not None else -1
815-
816-
np.savez(
817-
path,
818-
values=self.values,
819-
index=self.index,
820-
max_order=self.max_order,
821-
n_players=self.n_players,
822-
min_order=self.min_order,
823-
interaction_lookup_keys=interaction_keys,
824-
interaction_lookup_indices=interaction_indices,
825-
estimated=self.estimated,
826-
estimation_budget=estimation_budget,
827-
baseline_value=self.baseline_value,
828-
)
829-
else:
830-
self.to_json_file(path)
779+
self.to_json_file(path)
831780

832781
@classmethod
833782
def load(cls, path: Path | str) -> InteractionValues:
@@ -841,46 +790,10 @@ def load(cls, path: Path | str) -> InteractionValues:
841790
842791
"""
843792
path = Path(path)
844-
# check if path ends with .json
845-
if path.name.endswith(".json"):
846-
return cls.from_json_file(path)
847-
848-
raise_deprecation_warning(
849-
SAVE_JSON_DEPRECATION_MSG, deprecated_in="1.3.1", removed_in="1.4.0"
850-
)
851-
852-
# try loading as npz file
853-
if path.name.endswith(".npz"):
854-
data = np.load(path, allow_pickle=True)
855-
try:
856-
# try to load Pyright save format
857-
interaction_lookup = {
858-
safe_str_to_tuple(key): int(value)
859-
for key, value in zip(
860-
data["interaction_lookup_keys"],
861-
data["interaction_lookup_indices"],
862-
strict=False,
863-
)
864-
}
865-
except KeyError:
866-
# fallback to old format
867-
interaction_lookup = data["interaction_lookup"].item()
868-
estimation_budget = data["estimation_budget"].item()
869-
if estimation_budget == -1:
870-
estimation_budget = None
871-
return InteractionValues(
872-
values=data["values"],
873-
index=str(data["index"]),
874-
max_order=int(data["max_order"]),
875-
n_players=int(data["n_players"]),
876-
min_order=int(data["min_order"]),
877-
interaction_lookup=interaction_lookup,
878-
estimated=bool(data["estimated"]),
879-
estimation_budget=estimation_budget,
880-
baseline_value=float(data["baseline_value"]),
881-
)
882-
msg = f"Path {path} does not end with .json or .npz. Cannot load InteractionValues."
883-
raise ValueError(msg)
793+
if not path.name.endswith(".json"):
794+
msg = f"Path {path} does not end with .json. Cannot load InteractionValues."
795+
raise ValueError(msg)
796+
return cls.from_json_file(path)
884797

885798
@classmethod
886799
def from_dict(cls, data: dict[str, Any]) -> InteractionValues:
Lines changed: 18 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,23 @@
1-
"""Collects all deprecated behaviour tests."""
1+
"""Collects all deprecated behavior tests.
22
3-
from __future__ import annotations
4-
5-
import warnings
6-
from typing import TYPE_CHECKING
7-
8-
if TYPE_CHECKING:
9-
import pytest
10-
11-
from .features import register_deprecated
12-
13-
14-
@register_deprecated(name="Game(path_to_values=...)", deprecated_in="1.3.1", removed_in="1.4.0")
15-
def deprecated_game_init_with_path(request: pytest.FixtureRequest) -> None:
16-
from shapiq.game import Game
17-
18-
tmp_path = request.getfixturevalue("tmp_path")
19-
game = request.getfixturevalue("cooking_game_pre_computed")
20-
path = tmp_path / "dummy_game.json"
21-
game.save(path)
22-
Game(path_to_values=path)
23-
24-
25-
@register_deprecated(
26-
name="InteractionValues.save(..., as_pickle=True)", deprecated_in="1.3.1", removed_in="1.4.0"
27-
)
28-
def save_interaction_values_as_pickle(request: pytest.FixtureRequest) -> None:
29-
"""Tests that old methods work but also warn with deprecation."""
30-
import pathlib
3+
Test functions in this module are registered as deprecated features using the `register_deprecated`
4+
decorator from `features.py`. Each test function should be decorated with `register_deprecated`,
5+
which takes the name of the deprecated feature, the version in which it was deprecated, and the
6+
version in which it will be removed as arguments.
317
32-
tmp_path = request.getfixturevalue("tmp_path")
33-
iv = request.getfixturevalue("iv_7_all")
8+
Example:
9+
>>> from .features import register_deprecated
3410
35-
path = tmp_path / pathlib.Path("test_interaction_values")
36-
iv.save(path, as_pickle=True)
11+
>>> @register_deprecated(name="Game(path_to_values=...)", deprecated_in="1.3.1", removed_in="1.4.0")
12+
>>> def deprecated_game_init_with_path(request: pytest.FixtureRequest) -> None:
13+
>>> from shapiq.game import Game
14+
>>>
15+
>>> tmp_path = request.getfixturevalue("tmp_path")
16+
>>> game = request.getfixturevalue("cooking_game_pre_computed")
17+
>>> path = tmp_path / "dummy_game.json"
18+
>>> game.save(path)
19+
>>> Game(path_to_values=path)
3720
21+
"""
3822

39-
@register_deprecated(
40-
name="InteractionValues.save(..., as_npz=True)", deprecated_in="1.3.1", removed_in="1.4.0"
41-
)
42-
def save_interaction_values_as_npz(request: pytest.FixtureRequest) -> None:
43-
"""Tests that old methods work but also warn with deprecation."""
44-
import pathlib
45-
46-
tmp_path = request.getfixturevalue("tmp_path")
47-
iv = request.getfixturevalue("iv_7_all")
48-
49-
path = tmp_path / pathlib.Path("test_interaction_values")
50-
iv.save(path, as_npz=True)
51-
52-
53-
@register_deprecated(
54-
name="InteractionValues.load() from non-json file", deprecated_in="1.3.1", removed_in="1.4.0"
55-
)
56-
def load_interaction_values_from_non_json(request: pytest.FixtureRequest) -> None:
57-
"""Tests that old methods work but also warn with deprecation."""
58-
import pathlib
59-
60-
from shapiq.interaction_values import InteractionValues
61-
62-
tmp_path = request.getfixturevalue("tmp_path")
63-
iv: InteractionValues = request.getfixturevalue("iv_7_all")
64-
path = tmp_path / pathlib.Path("test_interaction_values.npz")
65-
66-
# supress the warning for saving as .npz
67-
with warnings.catch_warnings():
68-
warnings.simplefilter("ignore", DeprecationWarning)
69-
iv.save(path, as_npz=True)
70-
71-
InteractionValues.load(path)
23+
from __future__ import annotations

tests/shapiq/tests_unit/tests_games/test_base_game.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -246,11 +246,12 @@ class TestSavingGames:
246246

247247
@pytest.mark.parametrize("suffix", [".json", ".npz"])
248248
def test_init_from_saved_game(self, suffix, cooking_game_pre_computed: Game, tmp_path: Path):
249-
"""Test initializing a game from a saved file."""
249+
"""Test loading a game from a saved file using load_values."""
250250
path = tmp_path / "dummy_game"
251251
path = path.with_suffix(suffix)
252252
cooking_game_pre_computed.save_values(path)
253-
loaded_game = Game(path_to_values=path)
253+
loaded_game = Game(n_players=cooking_game_pre_computed.n_players)
254+
loaded_game.load_values(path, precomputed=True)
254255
check_game_equality(cooking_game_pre_computed, loaded_game)
255256

256257
@pytest.mark.parametrize("suffix", [".json", ".npz"])
@@ -262,10 +263,11 @@ def test_save_adds_suffix(self, cooking_game_pre_computed: Game, tmp_path: Path,
262263
assert path_with_suffix.exists()
263264

264265
def test_save_game_npz(self, cooking_game_pre_computed: Game, tmp_path: Path):
265-
"""Test initializing a game from a saved file."""
266+
"""Test loading a game from a saved npz file using load_values."""
266267
path = tmp_path / "dummy_game.npz"
267268
cooking_game_pre_computed.save_values(path, as_npz=True)
268-
loaded_game = Game(path_to_values=path)
269+
loaded_game = Game(n_players=cooking_game_pre_computed.n_players)
270+
loaded_game.load_values(path, precomputed=True)
269271
check_game_equality(cooking_game_pre_computed, loaded_game)
270272

271273
def test_save_and_load_json(self, cooking_game_pre_computed: Game, tmp_path: Path):
@@ -295,15 +297,18 @@ def test_save_and_load_with_normalization(self, suffix, tmp_path: Path):
295297
path = tmp_path / f"dummy_game.{suffix}"
296298
game.save_values(path)
297299

298-
game_loaded = Game(path_to_values=path)
300+
game_loaded = Game(n_players=4)
301+
game_loaded.load_values(path, precomputed=True)
299302
assert game_loaded.normalize # should be normalized
300303
assert game_loaded.normalization_value == normalization_value
301304
empty_value = game_loaded(game_loaded.empty_coalition)
302305
# the output should be the same as the original game with normalization (-0.25)
303306
assert empty_value == dummy_game_empty_output - normalization_value
304307

305-
# load with normalization set to False
306-
game_loaded = Game(path_to_values=path, normalize=False)
308+
# load without normalization by resetting normalization_value after loading
309+
game_loaded = Game(n_players=4)
310+
game_loaded.load_values(path, precomputed=True)
311+
game_loaded.normalization_value = 0.0
307312
assert not game_loaded.normalize # should not be normalized
308313
assert game_loaded.normalization_value == 0.0
309314
empty_value = game_loaded(game_loaded.empty_coalition)

0 commit comments

Comments
 (0)