Skip to content
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
40 changes: 36 additions & 4 deletions rulekit/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,40 @@
from rulekit.rules import BaseRule


def _missing_to_none(value: Any) -> Any:
"""Map pandas/numpy missing values to None for the Java DataTable.

Args:
value: A single cell value.

Returns:
None if the value is missing, otherwise the original value.
"""
if value is None:
return None
try:
if pd.isna(value):
return None
except (TypeError, ValueError):
pass
return value


def _to_java_object_array(values: np.ndarray) -> np.ndarray:
"""Copy an array to dtype=object with missing cells as None.

Args:
values: Array produced from a pandas frame or series.

Returns:
Object array safe to pass through JPype into DataTable.
"""
result = np.empty(values.shape, dtype=object)
for index, value in np.ndenumerate(values):
result[index] = _missing_to_none(value)
return result


def get_rule_generator(expert: bool = False) -> Any:
"""Factory for Java RuleGenerator class object

Expand Down Expand Up @@ -259,7 +293,7 @@ def _sanitize_y(self, y: Union[pd.Series, np.ndarray, list]):
elif isinstance(y, pd.Series):
self._label_name = y.name
self._attributes_names.append(self._label_name)
self._y = y.to_numpy()
self._y = _to_java_object_array(y.to_numpy())
elif isinstance(y, list):
self._label_name = self.DEFAULT_LABEL_ATTRIBUTE_NAME
self._attributes_names.append(self._label_name)
Expand All @@ -281,9 +315,7 @@ def _sanitize_X(
) -> tuple[np.ndarray, np.ndarray]:
if isinstance(X, pd.DataFrame):
self._attributes_names = X.columns.tolist()
# replace nan values with None
X = X.where(pd.notnull(X), None)
self._X = X.to_numpy()
self._X = _to_java_object_array(X.astype(object).to_numpy())
elif isinstance(X, np.ndarray):
self._attributes_names = [
f"{self.AUTOMATIC_ATTRIBUTES_NAMES_PREFIX}{index + 1}"
Expand Down
22 changes: 16 additions & 6 deletions rulekit/_operator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""Contains base classes for rule induction operators
"""
"""Contains base classes for rule induction operators"""

from __future__ import annotations

from abc import ABC
Expand Down Expand Up @@ -79,9 +79,7 @@ def fit( # pylint: disable=missing-function-docstring
self.model = RuleSet[BaseRule](java_model)
return self.model

def predict(
self, values: Data
) -> np.ndarray: # pylint: disable=missing-function-docstring
def predict(self, values: Data) -> np.ndarray: # pylint: disable=missing-function-docstring
if self.model is None:
raise ValueError('"fit" method must be called before calling this method')
example_set = ExampleSetFactory(self._get_problem_type()).make(values)
Expand All @@ -90,7 +88,8 @@ def predict(
)

def get_params(
self, deep: bool = True # pylint: disable=unused-argument
self,
deep: bool = True, # pylint: disable=unused-argument
) -> dict[str, Any]:
"""
Parameters
Expand Down Expand Up @@ -146,6 +145,17 @@ def get_coverage_matrix(self, values: Data) -> np.ndarray:
if self.model is None:
raise ValueError('"fit" method must be called before calling this method')
example_set = ExampleSetFactory(self._get_problem_type()).make(values)

# update mapping for nominal attributes
header = self.model._java_object.getTrainingHeader()
if header is not None:
try:
example_set = example_set.updateMapping(header)
except Exception as e:
raise ValueError(
"Error updating mapping. Check if the given set agrees with the training set."
) from e

covering_info = self.model.covering(example_set)
if isinstance(values, (pd.Series, pd.DataFrame)):
values = values.to_numpy()
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion rulekit/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from rulekit._logging import _RuleKitJavaLoggerConfig

__RULEKIT_RELEASE_VERSION__ = "2.1.24"
__RULEKIT_RELEASE_VERSION__ = "2.1.26"
__VERSION__ = f"{__RULEKIT_RELEASE_VERSION__}.1"


Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

setuptools.setup(
name="rulekit",
version='2.1.24.2',
version='2.1.26.1',
author="Cezary Maszczyk",
author_email="cezary.maszczyk@gmail.com",
description="Comprehensive suite for rule-based learning",
Expand All @@ -34,7 +34,7 @@
python_requires='>=3.9',
install_requires=[
'numpy>=1.24',
'pandas>=1.5',
'pandas>=1.5,<=3.0.5',
'scipy>=1.11',
'scikit-learn>=1.1',
'JPype1==1.5.2',
Expand Down
8 changes: 2 additions & 6 deletions tests/test_survival.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,7 @@ def test_passing_survival_time_column_to_fit_method(self):
)
X, y = test_case.example_set.values, test_case.example_set.labels
survival_time_col: pd.Series = X[test_case.survival_time]
X_without_time_col: pd.DataFrame = X.drop(
columns=[test_case.survival_time], axis=1
)
X_without_time_col: pd.DataFrame = X.drop(columns=[test_case.survival_time])
surv1.fit(X_without_time_col, y, survival_time=survival_time_col)
surv2.fit(X, y)

Expand All @@ -154,9 +152,7 @@ def test_ibs_calculation(self):
)
X, y = test_case.example_set.values, test_case.example_set.labels
survival_time_col: pd.Series = X[test_case.survival_time]
X_without_time_col: pd.DataFrame = X.drop(
columns=[test_case.survival_time], axis=1
)
X_without_time_col: pd.DataFrame = X.drop(columns=[test_case.survival_time])
surv.fit(X, y)

ibs: float = surv.score(X, y)
Expand Down
Loading