Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

REFACTOR: Add support for NumPy 2.0 #376

Open
wants to merge 1 commit into
base: 2.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/facet/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@

# a function representing a model to be inspected
ModelFunction: TypeAlias = Callable[
[Union[pd.Series, pd.DataFrame, npt.NDArray[np.float_]]],
Union[pd.Series, npt.NDArray[np.float_], float],
[Union[pd.Series, pd.DataFrame, npt.NDArray[np.float64]]],
Union[pd.Series, npt.NDArray[np.float64], float],
]

# a supervised learner in scikit-learn
Expand Down
4 changes: 2 additions & 2 deletions src/facet/data/partition/_partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
T_RangePartitioner = TypeVar("T_RangePartitioner", bound="RangePartitioner[Any, Any]")
T_CategoryPartitioner = TypeVar("T_CategoryPartitioner", bound="CategoryPartitioner")
T_Values = TypeVar("T_Values", bound=np.generic)
T_Values_Numeric = TypeVar("T_Values_Numeric", np.int_, np.float_)
T_Values_Numeric = TypeVar("T_Values_Numeric", np.int_, np.float64)
T_Values_Scalar = TypeVar("T_Values_Scalar", int, float)


Expand Down Expand Up @@ -311,7 +311,7 @@ def _partition_center_offset(self) -> T_Values_Scalar:
pass


class ContinuousRangePartitioner(RangePartitioner[np.float_, float]):
class ContinuousRangePartitioner(RangePartitioner[np.float64, float]):
"""
Partition numerical values in adjacent intervals of the same length.

Expand Down
2 changes: 1 addition & 1 deletion src/facet/explanation/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@


ArraysAny: TypeAlias = Union[npt.NDArray[Any], List[npt.NDArray[Any]]]
ArraysFloat: TypeAlias = Union[npt.NDArray[np.float_], List[npt.NDArray[np.float_]]]
ArraysFloat: TypeAlias = Union[npt.NDArray[np.float64], List[npt.NDArray[np.float64]]]
CatboostPool = catboost.Pool
XType: TypeAlias = Union[npt.NDArray[Any], pd.DataFrame, catboost.Pool]
YType: TypeAlias = Union[npt.NDArray[Any], pd.Series, None]
2 changes: 1 addition & 1 deletion src/facet/inspection/_inspection.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
# Type aliases
#

FloatArray: TypeAlias = npt.NDArray[np.float_]
FloatArray: TypeAlias = npt.NDArray[np.float64]


#
Expand Down
66 changes: 33 additions & 33 deletions src/facet/inspection/_shap_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ class AffinityMatrix:
"""

# shape: (2, 2, n_outputs, n_features, n_features)
_matrices: npt.NDArray[np.float_]
_matrices: npt.NDArray[np.float64]

def __init__(self, matrices: npt.NDArray[np.float_]) -> None:
def __init__(self, matrices: npt.NDArray[np.float64]) -> None:
shape = matrices.shape
assert len(shape) == 5
assert shape[:2] == (2, 2)
Expand All @@ -76,7 +76,7 @@ def __init__(self, matrices: npt.NDArray[np.float_]) -> None:

@staticmethod
def from_relative_affinity(
affinity_rel_ij: npt.NDArray[np.float_], std_p_i: npt.NDArray[np.float_]
affinity_rel_ij: npt.NDArray[np.float64], std_p_i: npt.NDArray[np.float64]
) -> AffinityMatrix:
"""
:param affinity_rel_ij: the affinity matrix from which to create all variations,
Expand Down Expand Up @@ -125,15 +125,15 @@ def from_relative_affinity(
).reshape((2, 2, *affinity_rel_ij.shape))
)

def get_values(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float_]:
def get_values(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float64]:
"""
Get the matrix matching the given criteria.
:param symmetrical: if ``True``, get the symmetrical version of the matrix
:param absolute: if ``True``, get the absolute version of the matrix
:return: the affinity matrix
"""
return cast(
npt.NDArray[np.float_], self._matrices[int(symmetrical), int(absolute)]
npt.NDArray[np.float64], self._matrices[int(symmetrical), int(absolute)]
)


Expand All @@ -142,7 +142,7 @@ def get_values(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float_
#


def ensure_last_axis_is_fast(array: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
def ensure_last_axis_is_fast(array: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
"""
For future implementations, ensure that the last axis of the given array is `fast`
to allow for `partial summation`.
Expand All @@ -159,7 +159,7 @@ def ensure_last_axis_is_fast(array: npt.NDArray[np.float_]) -> npt.NDArray[np.fl
return array


def sqrt(array: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
def sqrt(array: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
"""
Get the square root of each element in the given array.

Expand All @@ -174,7 +174,7 @@ def sqrt(array: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
return np.sqrt(np.clip(array, 0, None))


def make_symmetric(m: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
def make_symmetric(m: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
"""
Enforce matrix symmetry by transposing the `feature x feature` matrix for each
output and averaging it with the original matrix.
Expand All @@ -186,7 +186,7 @@ def make_symmetric(m: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
return (m + transpose(m)) / 2


def transpose(m: npt.NDArray[np.float_], ndim: int = 3) -> npt.NDArray[np.float_]:
def transpose(m: npt.NDArray[np.float64], ndim: int = 3) -> npt.NDArray[np.float64]:
"""
Transpose the `feature x feature` matrix for each output.

Expand All @@ -206,7 +206,7 @@ def transpose(m: npt.NDArray[np.float_], ndim: int = 3) -> npt.NDArray[np.float_
return m.swapaxes(1, 2)


def diagonal(m: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:
def diagonal(m: npt.NDArray[np.float64]) -> npt.NDArray[np.float64]:
"""
Get the diagonal of the `feature x feature` matrix for each output.

Expand All @@ -219,7 +219,7 @@ def diagonal(m: npt.NDArray[np.float_]) -> npt.NDArray[np.float_]:


def fill_diagonal(
m: npt.NDArray[np.float_], value: Union[float, npt.NDArray[np.float_]]
m: npt.NDArray[np.float64], value: Union[float, npt.NDArray[np.float64]]
) -> None:
"""
In each `feature x feature` matrix for each output, fill the diagonal with the given
Expand All @@ -240,8 +240,8 @@ def fill_diagonal(


def cov(
vectors: npt.NDArray[np.float_], weight: Optional[npt.NDArray[np.float_]]
) -> npt.NDArray[np.float_]:
vectors: npt.NDArray[np.float64], weight: Optional[npt.NDArray[np.float64]]
) -> npt.NDArray[np.float64]:
"""
Calculate the covariance matrix of pairs of vectors along the observations axis and
for each output, assuming all vectors are centered (µ=0).
Expand All @@ -267,16 +267,16 @@ def cov(
weight_total = weight.sum()

return cast(
npt.NDArray[np.float_],
npt.NDArray[np.float64],
np.matmul(vectors_weighted, vectors.swapaxes(1, 2)) / weight_total,
)


def cov_broadcast(
vector_sequence: npt.NDArray[np.float_],
vector_grid: npt.NDArray[np.float_],
weight: Optional[npt.NDArray[np.float_]],
) -> npt.NDArray[np.float_]:
vector_sequence: npt.NDArray[np.float64],
vector_grid: npt.NDArray[np.float64],
weight: Optional[npt.NDArray[np.float64]],
) -> npt.NDArray[np.float64]:
"""
Calculate the covariance matrix between a sequence of vectors and a grid of vectors
along the observations axis and for each output, assuming all vectors are centered
Expand Down Expand Up @@ -311,7 +311,7 @@ def cov_broadcast(
weight_total = weight.sum()

return cast(
npt.NDArray[np.float_],
npt.NDArray[np.float64],
np.einsum("...io,...ijo->...ij", vectors_weighted, vector_grid) / weight_total,
)

Expand All @@ -323,29 +323,29 @@ class ShapContext:

#: SHAP vectors
#: with shape `(n_outputs, n_features, n_observations)`
p_i: npt.NDArray[np.float_]
p_i: npt.NDArray[np.float64]

#: observation weights (optional),
#: with shape `(n_observations)`
weight: Optional[npt.NDArray[np.float_]]
weight: Optional[npt.NDArray[np.float64]]

#: Covariance matrix for p[i],
#: with shape `(n_outputs, n_features, n_features)`
cov_p_i_p_j: npt.NDArray[np.float_]
cov_p_i_p_j: npt.NDArray[np.float64]

#: Variances for p[i],
#: with shape `(n_outputs, n_features, 1)`
var_p_i: npt.NDArray[np.float_]
var_p_i: npt.NDArray[np.float64]

#: SHAP interaction vectors
#: with shape `(n_outputs, n_features, n_features, n_observations)`
p_ij: Optional[npt.NDArray[np.float_]]
p_ij: Optional[npt.NDArray[np.float64]]

def __init__(
self,
p_i: npt.NDArray[np.float_],
p_ij: Optional[npt.NDArray[np.float_]],
weight: Optional[npt.NDArray[np.float_]],
p_i: npt.NDArray[np.float64],
p_ij: Optional[npt.NDArray[np.float64]],
weight: Optional[npt.NDArray[np.float64]],
) -> None:
assert p_i.ndim == 3
if weight is not None:
Expand Down Expand Up @@ -377,7 +377,7 @@ def __init__(
) -> None:
shap_values: pd.DataFrame = shap_calculator.shap_values

def _p_i() -> npt.NDArray[np.float_]:
def _p_i() -> npt.NDArray[np.float64]:
assert (
shap_calculator.feature_index_ is not None
), ASSERTION__CALCULATOR_IS_FITTED
Expand All @@ -395,15 +395,15 @@ def _p_i() -> npt.NDArray[np.float_]:
)
)

def _weight() -> Optional[npt.NDArray[np.float_]]:
def _weight() -> Optional[npt.NDArray[np.float64]]:
# weights
# shape: (n_observations)
# return a 1d array of weights that aligns with the observations axis of the
# SHAP values tensor (axis 1)

if sample_weight is not None:
return cast(
npt.NDArray[np.float_],
npt.NDArray[np.float64],
sample_weight.loc[shap_values.index.get_level_values(-1)].values,
)
else:
Expand Down Expand Up @@ -440,7 +440,7 @@ def __init__(
# shape: (n_observations)
# return a 1d array of weights that aligns with the observations axis of the
# SHAP values tensor (axis 1)
weight: Optional[npt.NDArray[np.float_]]
weight: Optional[npt.NDArray[np.float64]]

if sample_weight is not None:
_observation_indices = shap_values.index.get_level_values(
Expand Down Expand Up @@ -479,8 +479,8 @@ def __init__(

@staticmethod
def __get_orthogonalized_interaction_vectors(
p_ij: npt.NDArray[np.float_], weight: Optional[npt.NDArray[np.float_]]
) -> npt.NDArray[np.float_]:
p_ij: npt.NDArray[np.float64], weight: Optional[npt.NDArray[np.float64]]
) -> npt.NDArray[np.float64]:
# p_ij: shape: (n_outputs, n_features, n_features, n_observations)

assert p_ij.ndim == 4
Expand Down
8 changes: 4 additions & 4 deletions src/facet/inspection/_shap_projection.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ def fit( # type: ignore[override]
return self

@fitted_only
def association(self, absolute: bool, symmetrical: bool) -> npt.NDArray[np.float_]:
def association(self, absolute: bool, symmetrical: bool) -> npt.NDArray[np.float64]:
"""
The association matrix for all feature pairs.

Expand All @@ -130,7 +130,7 @@ def association(self, absolute: bool, symmetrical: bool) -> npt.NDArray[np.float
assert self.association_ is not None
return self.association_.get_values(symmetrical=symmetrical, absolute=absolute)

def to_frames(self, matrix: npt.NDArray[np.float_]) -> List[pd.DataFrame]:
def to_frames(self, matrix: npt.NDArray[np.float64]) -> List[pd.DataFrame]:
"""
Transforms one or more affinity matrices into a list of data frames.

Expand Down Expand Up @@ -241,7 +241,7 @@ def __init__(self) -> None:
self.redundancy_: Optional[AffinityMatrix] = None

@fitted_only
def synergy(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float_]:
def synergy(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float64]:
"""
The synergy matrix for all feature pairs.

Expand All @@ -261,7 +261,7 @@ def synergy(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float_]:
return self.synergy_.get_values(symmetrical=symmetrical, absolute=absolute)

@fitted_only
def redundancy(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float_]:
def redundancy(self, symmetrical: bool, absolute: bool) -> npt.NDArray[np.float64]:
"""
The redundancy matrix for all feature pairs.

Expand Down
4 changes: 2 additions & 2 deletions src/facet/inspection/base/_model_inspector.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
# Type aliases
#

FloatArray: TypeAlias = npt.NDArray[np.float_]
FloatMatrix: TypeAlias = Matrix[np.float_]
FloatArray: TypeAlias = npt.NDArray[np.float64]
FloatMatrix: TypeAlias = Matrix[np.float64]


#
Expand Down
2 changes: 1 addition & 1 deletion src/facet/inspection/shap/_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def output_names(self) -> List[str]:

def _convert_shap_to_df(
self,
raw_shap_tensors: List[npt.NDArray[np.float_]],
raw_shap_tensors: List[npt.NDArray[np.float64]],
observation_idx: pd.Index,
feature_idx: pd.Index,
) -> List[pd.DataFrame]:
Expand Down
12 changes: 6 additions & 6 deletions src/facet/inspection/shap/_shap.py
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ def _calculate_shap(
feature_names = self.feature_index_

# calculate the shap values, and ensure the result is a list of arrays
shap_values: List[npt.NDArray[np.float_]] = self._convert_shap_tensors_to_list(
shap_values: List[npt.NDArray[np.float64]] = self._convert_shap_tensors_to_list(
shap_tensors=(
explainer.shap_interaction_values(X=features)
if self.interaction_values
Expand Down Expand Up @@ -379,10 +379,10 @@ def _calculate_shap(
def _convert_shap_tensors_to_list(
self,
*,
shap_tensors: Union[npt.NDArray[np.float_], List[npt.NDArray[np.float_]]],
shap_tensors: Union[npt.NDArray[np.float64], List[npt.NDArray[np.float64]]],
n_outputs: int,
) -> List[npt.NDArray[np.float_]]:
def _validate_shap_tensor(_t: npt.NDArray[np.float_]) -> None:
) -> List[npt.NDArray[np.float64]]:
def _validate_shap_tensor(_t: npt.NDArray[np.float64]) -> None:
if np.isnan(np.sum(_t)):
raise AssertionError(
"Output of SHAP explainer includes NaN values. "
Expand All @@ -409,7 +409,7 @@ def _validate_shap_tensor(_t: npt.NDArray[np.float_]) -> None:
@abstractmethod
def _convert_shap_to_df(
self,
raw_shap_tensors: List[npt.NDArray[np.float_]],
raw_shap_tensors: List[npt.NDArray[np.float64]],
observation_idx: pd.Index,
feature_idx: pd.Index,
) -> List[pd.DataFrame]:
Expand All @@ -425,7 +425,7 @@ def _convert_shap_to_df(

def _convert_raw_shap_to_df(
self,
raw_shap_tensors: List[npt.NDArray[np.float_]],
raw_shap_tensors: List[npt.NDArray[np.float64]],
observation_idx: pd.Index,
feature_idx: pd.Index,
) -> List[pd.DataFrame]:
Expand Down
8 changes: 4 additions & 4 deletions src/facet/inspection/shap/sklearn/_sklearn.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def output_names(self) -> List[str]:

def _convert_shap_to_df(
self,
raw_shap_tensors: List[npt.NDArray[np.float_]],
raw_shap_tensors: List[npt.NDArray[np.float64]],
observation_idx: pd.Index,
feature_idx: pd.Index,
) -> List[pd.DataFrame]:
Expand Down Expand Up @@ -238,9 +238,9 @@ def validate_learner(learner: T_Classifier) -> None:
def _convert_shap_tensors_to_list(
self,
*,
shap_tensors: Union[npt.NDArray[np.float_], List[npt.NDArray[np.float_]]],
shap_tensors: Union[npt.NDArray[np.float64], List[npt.NDArray[np.float64]]],
n_outputs: int,
) -> List[npt.NDArray[np.float_]]:
) -> List[npt.NDArray[np.float64]]:
if n_outputs == 1 and isinstance(shap_tensors, list) and len(shap_tensors) == 2:
# in the binary classification case, we will proceed with SHAP values
# for class 0 only, since values for class 1 will just be the same
Expand Down Expand Up @@ -268,7 +268,7 @@ def _convert_shap_tensors_to_list(

def _convert_shap_to_df(
self,
raw_shap_tensors: List[npt.NDArray[np.float_]],
raw_shap_tensors: List[npt.NDArray[np.float64]],
observation_idx: pd.Index,
feature_idx: pd.Index,
) -> List[pd.DataFrame]:
Expand Down
Loading