Skip to content

Commit a86d2cf

Browse files
authored
Merge pull request #48 from eli5-org/py39
Drop python<3.9, support modern libraries
2 parents 8eed51a + 0756b9f commit a86d2cf

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+580
-841
lines changed

.github/workflows/python-package.yml

+14-16
Original file line numberDiff line numberDiff line change
@@ -17,26 +17,24 @@ jobs:
1717
fail-fast: false
1818
matrix:
1919
include:
20-
- python-version: '3.6'
20+
- python-version: '3.10'
2121
tox-env: 'mypy'
22-
- python-version: '3.9'
22+
- python-version: '3.10'
2323
tox-env: 'docs'
24-
- python-version: '3.6'
25-
tox-env: 'py36'
26-
- python-version: '3.6'
27-
tox-env: 'py36-nodeps'
28-
- python-version: '3.6'
29-
tox-env: 'py36-extra'
30-
- python-version: '3.7'
31-
tox-env: 'py37'
32-
- python-version: '3.8'
33-
tox-env: 'py38'
34-
- python-version: '3.8'
35-
tox-env: 'py38-nodeps'
3624
- python-version: '3.9'
3725
tox-env: 'py39'
38-
- python-version: '3.9'
39-
tox-env: 'py39-nodeps'
26+
- python-version: '3.10'
27+
tox-env: 'py310'
28+
- python-version: '3.10'
29+
tox-env: 'py310-nodeps'
30+
- python-version: '3.10'
31+
tox-env: 'py310-extra'
32+
- python-version: '3.11'
33+
tox-env: 'py311'
34+
- python-version: '3.12'
35+
tox-env: 'py312'
36+
- python-version: '3.13'
37+
tox-env: 'py313'
4038

4139
steps:
4240
- uses: actions/checkout@v2

CHANGES.rst

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
Changelog
22
=========
33

4+
0.14.0 (?)
5+
-------------------
6+
7+
* drop support for python 3.6, 3.7, 3.8
8+
* add support for python 3.11, 3.12, 3.13
9+
410
0.13.0 (2022-05-11)
511
-------------------
612

_ci/runtests_default.sh

-11
This file was deleted.

_ci/runtests_default_with_crfsuite.sh

-10
This file was deleted.

_ci/runtests_extra.sh

-12
This file was deleted.

_ci/runtests_nodeps.sh

-13
This file was deleted.

docs/requirements.txt

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ sphinx_rtd_theme
44
ipython
55
scipy
66
numpy > 1.9.0
7-
scikit-learn >= 0.20
8-
typing
7+
pandas
8+
scikit-learn >= 1.6.0

docs/source/conf.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __getattr__(cls, name):
5454
'keras.models',
5555
'keras.layers',
5656
'keras.preprocessing.image',
57-
'pandas',
57+
# 'pandas',
5858
'PIL',
5959
'matplotlib',
6060
'matplotlib.pyplot',
@@ -69,7 +69,7 @@ def __getattr__(cls, name):
6969

7070
def setup(app):
7171
# see https://github.com/snide/sphinx_rtd_theme/issues/117
72-
app.add_stylesheet("rtfd_overrides.css")
72+
app.add_css_file("rtfd_overrides.css")
7373

7474
suppress_warnings = ['image.nonlocal_uri']
7575

@@ -123,7 +123,7 @@ def setup(app):
123123
#
124124
# This is also used if you do content translation via gettext catalogs.
125125
# Usually you set "language" from the command line for these cases.
126-
language = None
126+
language = 'en'
127127

128128
# There are two options for replacing |today|: either, you set today to some
129129
# non-false value, then it is used:

docs/source/libraries/keras.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Keras_ is "a high-level neural networks API, written in Python and capable of ru
88
Keras can be used for many Machine Learning tasks, and it has support for both popular
99
and experimental neural network architectures.
1010

11-
Note: only TensorFlow 1.x is supported, recommended Keras version is 2.3.1 or earlier.
11+
Note: only TensorFlow 1.x is supported, recommended Keras version is 2.3.1 or earlier, and eli5 version 0.13 or earlier, as you can't install TensorFlow 1.x on Python 3.9+ which is required for eli5 0.14+
1212

1313
.. _Keras: https://keras.io/
1414

docs/source/libraries/xgboost.rst

+3-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ XGBoost
66
XGBoost_ is a popular Gradient Boosting library with Python interface.
77
eli5 supports :func:`eli5.explain_weights` and :func:`eli5.explain_prediction`
88
for XGBClassifer_, XGBRegressor_ and Booster_ estimators. It is tested for
9-
xgboost >= 0.6a2.
9+
xgboost >= 0.6a2 and < 2.0.0.
10+
Versions starting from 2.0.0 likely produce incorrect results in
11+
:func:`eli5.explain_prediction`, and will issue a warning.
1012

1113
.. _XGBoost: https://github.com/dmlc/xgboost
1214
.. _XGBClassifer: https://xgboost.readthedocs.io/en/latest/python/python_api.html#xgboost.XGBClassifier

eli5/_feature_names.py

+22-36
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import re
2-
import six
32
from typing import (
4-
Any, Iterable, Iterator, Tuple, Sized, List, Optional, Dict,
5-
Union, Callable, Pattern
6-
)
3+
Any, Iterable, Iterator, Sized, Optional, Union, Callable, Pattern)
74

85
import numpy as np
96
import scipy.sparse as sp
@@ -14,15 +11,14 @@ class FeatureNames(Sized, Iterable):
1411
A list-like object with feature names. It allows
1512
feature names for unknown features to be generated using
1613
a provided template, and to avoid making copies of large objects
17-
in get_feature_names.
14+
in get_feature_names_out.
1815
"""
1916
def __init__(self,
2017
feature_names=None,
21-
bias_name=None, # type: str
22-
unkn_template=None, # type: str
23-
n_features=None, # type: int
18+
bias_name: Optional[str] = None,
19+
unkn_template: Optional[str] = None,
20+
n_features: Optional[int] = None,
2421
):
25-
# type: (...) -> None
2622
if not (feature_names is not None or
2723
(unkn_template is not None and n_features)):
2824
raise ValueError(
@@ -39,20 +35,17 @@ def __init__(self,
3935
'unkn_template should be set for sparse features')
4036
self.feature_names = feature_names
4137
self.unkn_template = unkn_template
42-
self.n_features = n_features or len(feature_names) # type: int
38+
self.n_features: int = n_features or len(feature_names)
4339
self.bias_name = bias_name
4440

45-
def __repr__(self):
46-
# type: () -> str
41+
def __repr__(self) -> str:
4742
return '<FeatureNames: {} features {} bias>'.format(
4843
self.n_features, 'with' if self.has_bias else 'without')
4944

50-
def __len__(self):
51-
# type: () -> int
45+
def __len__(self) -> int:
5246
return self.n_features + int(self.has_bias)
5347

54-
def __iter__(self):
55-
# type: () -> Iterator[str]
48+
def __iter__(self) -> Iterator[str]:
5649
return (self[i] for i in range(len(self)))
5750

5851
def __getitem__(self, idx):
@@ -69,10 +62,10 @@ def __getitem__(self, idx):
6962
return self.unkn_template % idx
7063
raise IndexError('Feature index out of range')
7164

72-
def _slice(self, aslice):
73-
# type: (slice) -> Any
65+
def _slice(self, aslice: slice):
7466
if isinstance(self.feature_names, (list, np.ndarray)):
7567
# Fast path without going through __getitem__
68+
lst: Union[list, np.ndarray]
7669
if self.has_bias:
7770
lst = list(self.feature_names)
7871
lst.append(self.bias_name)
@@ -84,29 +77,26 @@ def _slice(self, aslice):
8477
return [self[idx] for idx in indices]
8578

8679
@property
87-
def has_bias(self):
88-
# type: () -> bool
80+
def has_bias(self) -> bool:
8981
return self.bias_name is not None
9082

9183
@property
92-
def bias_idx(self):
93-
# type: () -> Optional[int]
84+
def bias_idx(self) -> Optional[int]:
9485
if self.has_bias:
9586
return self.n_features
9687
return None
9788

98-
def filtered(self, feature_filter, x=None):
99-
# type: (Callable, Any) -> Tuple[FeatureNames, List[int]]
89+
def filtered(self, feature_filter: Callable, x=None) -> tuple['FeatureNames', list[int]]:
10090
""" Return feature names filtered by a regular expression
10191
``feature_re``, and indices of filtered elements.
10292
"""
10393
indices = []
10494
filtered_feature_names = []
105-
indexed_names = None # type: Optional[Iterable[Tuple[int, Any]]]
95+
indexed_names: Optional[Iterable[tuple[int, Any]]] = None
10696
if isinstance(self.feature_names, (np.ndarray, list)):
10797
indexed_names = enumerate(self.feature_names)
10898
elif isinstance(self.feature_names, dict):
109-
indexed_names = six.iteritems(self.feature_names)
99+
indexed_names = self.feature_names.items()
110100
elif self.feature_names is None:
111101
indexed_names = []
112102
assert indexed_names is not None
@@ -116,8 +106,7 @@ def filtered(self, feature_filter, x=None):
116106
assert x.shape[0] == 1
117107
flt = lambda nm, i: feature_filter(nm, x[0, i])
118108
else:
119-
# FIXME: mypy warns about x[i] because it thinks x can be None
120-
flt = lambda nm, i: feature_filter(nm, x[i]) # type: ignore
109+
flt = lambda nm, i: feature_filter(nm, x[i])
121110
else:
122111
flt = lambda nm, i: feature_filter(nm)
123112

@@ -141,10 +130,9 @@ def filtered(self, feature_filter, x=None):
141130

142131
def handle_filter(self,
143132
feature_filter,
144-
feature_re, # type: Pattern[str]
145-
x=None, # type: Any
146-
):
147-
# type: (...) -> Tuple[FeatureNames, Union[List[int], None]]
133+
feature_re: Pattern[str],
134+
x=None,
135+
) -> tuple['FeatureNames', Union[list[int], None]]:
148136
if feature_re is not None and feature_filter:
149137
raise ValueError('pass either feature_filter or feature_re')
150138
if feature_re is not None:
@@ -158,8 +146,7 @@ def handle_filter(self,
158146
else:
159147
return self, None
160148

161-
def add_feature(self, feature):
162-
# type: (Any) -> int
149+
def add_feature(self, feature) -> int:
163150
""" Add a new feature name, return it's index.
164151
"""
165152
# A copy of self.feature_names is always made, because it might be
@@ -179,8 +166,7 @@ def add_feature(self, feature):
179166
return idx
180167

181168

182-
def _all_feature_names(name):
183-
# type: (Union[str, bytes, List[Dict]]) -> List[str]
169+
def _all_feature_names(name: Union[str, bytes, list[dict]]) -> list[str]:
184170
""" All feature names for a feature: usually just the feature itself,
185171
but can be several features for unhashed features with collisions.
186172
"""

0 commit comments

Comments
 (0)