Repository: deepchecks/deepchecks

Issue:
[FEAT] Create the as_scalar mixin functionality
Some checks should have the functionality of converting the CheckResult value to a single scalar, to enable over-time measurements. We want to have some Mixin/other Python functionality that will enforce that some checks will implement this `as_scalar` function.


Discussion:

Resolving patch:
diff --git a/deepchecks/core/check_result.py b/deepchecks/core/check_result.py
--- a/deepchecks/core/check_result.py
+++ b/deepchecks/core/check_result.py
@@ -24,6 +24,7 @@
 from pandas.io.formats.style import Styler
 from plotly.basedatatypes import BaseFigure
 
+from deepchecks.core.checks import ReduceMixin
 from deepchecks.core.condition import ConditionCategory, ConditionResult
 from deepchecks.core.errors import DeepchecksValueError
 from deepchecks.core.serialization.check_failure.html import CheckFailureSerializer as CheckFailureHtmlSerializer
@@ -194,6 +195,12 @@ def priority(self) -> int:
 
         return 4
 
+    def reduce_output(self) -> Dict[str, float]:
+        """Return the check result as a reduced dict."""
+        if isinstance(self.check, ReduceMixin):
+            return self.check.reduce_output(self)
+        raise DeepchecksValueError('Check be an instance of ReduceMixin to use this function')
+
     def display_check(
         self,
         unique_id: Optional[str] = None,
diff --git a/deepchecks/core/checks.py b/deepchecks/core/checks.py
--- a/deepchecks/core/checks.py
+++ b/deepchecks/core/checks.py
@@ -45,6 +45,25 @@ class CheckMetadata(TypedDict):
     summary: str
 
 
+class ReduceMixin(abc.ABC):
+    """Mixin for reduce_output function."""
+
+    def reduce_output(self, check_result: 'check_types.CheckResult') -> Dict[str, float]:
+        """Return the check result as a reduced dict. Being Used for monitoring.
+
+        Parameters
+        ----------
+        check_result : CheckResult
+            The check result.
+
+        Returns
+        -------
+        Dict[str, float]
+            reduced dictionary in format {str: float} (i.e {'AUC': 0.1}), based on the check's original returned value
+        """
+        raise NotImplementedError('Must implement reduce_output function')
+
+
 class BaseCheck(abc.ABC):
     """Base class for check."""
 
diff --git a/deepchecks/tabular/checks/model_evaluation/performance_report.py b/deepchecks/tabular/checks/model_evaluation/performance_report.py
--- a/deepchecks/tabular/checks/model_evaluation/performance_report.py
+++ b/deepchecks/tabular/checks/model_evaluation/performance_report.py
@@ -15,6 +15,7 @@
 import plotly.express as px
 
 from deepchecks.core import CheckResult, ConditionResult
+from deepchecks.core.checks import DatasetKind, ReduceMixin
 from deepchecks.core.condition import ConditionCategory
 from deepchecks.core.errors import DeepchecksValueError
 from deepchecks.tabular import Context, TrainTestCheck
@@ -27,7 +28,7 @@
 PR = TypeVar('PR', bound='PerformanceReport')
 
 
-class PerformanceReport(TrainTestCheck):
+class PerformanceReport(TrainTestCheck, ReduceMixin):
     """Summarize given scores on a dataset and model.
 
     Parameters
@@ -145,6 +146,12 @@ def run_logic(self, context: Context) -> CheckResult:
             display=fig
         )
 
+    def reduce_output(self, check_result: CheckResult) -> Dict[str, float]:
+        """Return the values of the metrics for the test dataset in {metric: value} format."""
+        df = check_result.value
+        df = df[df['Dataset'] == DatasetKind.TEST.value]
+        return dict(zip(df['Metric'], df['Value']))
+
     def add_condition_test_performance_not_less_than(self: PR, min_score: float) -> PR:
         """Add condition - metric scores are not less than given score.
 


Test patch:
diff --git a/tests/tabular/checks/model_evaluation/performance_report_test.py b/tests/tabular/checks/model_evaluation/performance_report_test.py
--- a/tests/tabular/checks/model_evaluation/performance_report_test.py
+++ b/tests/tabular/checks/model_evaluation/performance_report_test.py
@@ -141,6 +141,18 @@ def test_regression(diabetes_split_dataset_and_model):
             assert_that(metric_col['Value'].iloc[0], instance_of(float))
 
 
+def test_regression_reduced(diabetes_split_dataset_and_model):
+    # Arrange
+    train, test, model = diabetes_split_dataset_and_model
+    check = PerformanceReport()
+    # Act X
+    result = check.run(train, test, model).reduce_output()
+    # Assert
+    assert_that(result['Neg RMSE'], close_to(-57.412, 0.001))
+    assert_that(result['Neg MAE'], close_to(-45.5645, 0.001))
+    assert_that(result['R2'], close_to(0.427, 0.001))
+
+
 def test_condition_min_score_not_passed(iris_split_dataset_and_model):
     # Arrange
     train, test, model = iris_split_dataset_and_model