Coverage for credoai/prism/comparators/metric_comparator.py: 95%
44 statements
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-13 21:56 +0000
« prev ^ index » next coverage.py v7.1.0, created at 2023-02-13 21:56 +0000
1"""Comparators for metric type containers"""
2from typing import List
4from connect.evidence import MetricContainer
5from pandas import DataFrame, concat
7from credoai.evaluators.utils.validation import check_instance
8from credoai.prism.comparators.comparator import Comparator
11class MetricComparator(Comparator):
12 """
13 Class for comparing metric evidence objects.
15 Each metric is compared to the respective reference value. The reference value is the metric value
16 associated to a specific model or dataset. The reference model/dataset are identified by the user, see
17 ref type and ref in Parameters.
19 Supported comparisons for Metrics include differences, ratio, percentage ratio, and percentage
20 difference.
22 Parameters
23 ----------
24 EvidenceContainers : List[MetricContainer]
25 A list of metric containers.
26 ref_type: str
27 Accepted values: model, assessment_data, training_data. Indicates which of the
28 artifacts should be used as a refence, by default model.
29 ref : str
30 The model/dataset name by which to compare all others. Model/dataset names are
31 defined when instantiating Lens objects, by the usage of credo.artifacts.
32 operation : str
33 Accepted operations: "diff", "ratio", "perc", "perc_diff", by default "diff"
34 abs : bool, optional
35 If true the absolute value of the operation is returned, by default False
36 """
38 OPERATIONS = {
39 "diff": lambda x, ref: x - ref,
40 "ratio": lambda x, ref: x / ref,
41 "perc": lambda x, ref: x * 100 / ref,
42 "perc_diff": lambda x, ref: ((x - ref) / ref) * 100,
43 }
44 ID_COLS = [
45 "evaluator",
46 "model",
47 "assessment_data",
48 "training_data",
49 "sensitive_feature",
50 ]
52 def __init__(
53 self,
54 EvidenceContainers: List[MetricContainer],
55 ref_type: str,
56 ref: str,
57 operation: str = "diff",
58 abs: bool = False,
59 ):
60 # attributes all comparators will need
61 self.ref_type = ref_type
62 self.overall_ref = ref
63 self.operation = self.OPERATIONS[operation]
64 self.abs = abs
65 super().__init__(EvidenceContainers)
67 def _setup(self):
68 """Extracts all the results from the result containers"""
69 self._extract_results_from_containers()
71 def _validate(self):
72 """
73 Check that provided containers are all MetricContainer type
74 """
75 for container in self.EvidenceContainers:
76 check_instance(container, MetricContainer)
78 def compare(self):
79 """
80 Runs all comparisons
81 """
82 # Calculate scalar differences across all the metrics
83 self.comparisons["scalar_comparison"] = self._scalar_operation()
84 return self
86 def _extract_results_from_containers(self):
87 """
88 Extract results from containers.
89 """
90 # Create id columns from the result id.
91 self.all_results = [
92 res.data.assign(id=res.id) for res in self.EvidenceContainers
93 ]
94 self.all_results = concat(self.all_results, ignore_index=True)
95 self.all_results[self.ID_COLS] = self.all_results.id.str.split("~", expand=True)
97 # Drop columns with no variance
98 self.all_results.drop("id", axis=1, inplace=True)
100 def _scalar_operation(self) -> DataFrame:
101 """
102 Compares all each metric to a specific reference value
104 Returns
105 -------
106 DataFrame
107 Containing a comparison column with the results. The other columns
108 include initial values and all other necessary identifiers.
109 """
110 # Group by metrics and calculate comparison
111 output = []
113 # Remove reference type from ids, and add metric type to create unique groups
114 to_grp_by = [x for x in self.ID_COLS if x != self.ref_type] + ["type"]
116 for _, results in self.all_results.groupby(to_grp_by):
117 # Define reference value
118 ref_value = results.value.loc[
119 results[self.ref_type] == self.overall_ref
120 ].iloc[0]
122 if ref_value:
123 results["comparison"] = self.operation(results.value, ref_value)
124 else:
125 results["comparison"] = None
126 output.append(results)
128 final = concat(output, ignore_index=True)
130 if self.abs:
131 final["comparison"] = final["comparison"].abs()
133 # Clean data frame
134 nunique = final.nunique()
135 cols_to_drop = nunique[nunique == 1].index
136 final = final.drop(cols_to_drop, axis=1)
137 final[final == "NA"] = None
139 return final