|
8 | 8 |
|
9 | 9 | import numpy as np |
10 | 10 | import quantities as pq |
11 | | - |
| 11 | +from typing import Union |
12 | 12 | from sciunit import errors, utils |
13 | 13 |
|
14 | 14 | from .base import Score |
@@ -241,8 +241,83 @@ def norm_score(self) -> float: |
241 | 241 | return 1 - 2 * math.fabs(0.5 - cdf) |
242 | 242 |
|
243 | 243 | def __str__(self): |
244 | | - return "Ratio = %.2f" % self.score |
| 244 | + return 'Ratio = %.2f' % self.score |
| 245 | + |
| 246 | + |
| 247 | +class RelativeDifferenceScore(Score): |
| 248 | + """A relative difference between prediction and observation. |
| 249 | + |
| 250 | + The absolute value of the difference between the prediction and the |
| 251 | + observation is divided by a reference value with the same units. This |
| 252 | + reference scale should be chosen for each test such that normalization |
| 253 | + produces directly comparable scores across tests. For example, if 5 volts |
| 254 | + represents a medium size difference for TestA, and 10 seconds represents a |
| 255 | + medium size difference for TestB, then 5 volts and 10 seconds should be |
| 256 | + used for this reference scale in TestA and TestB, respectively. The |
| 257 | + attribute `scale` can be passed to the compute method or set for the whole |
| 258 | + class in advance. Otherwise, a scale of 1 (in the units of the |
| 259 | + observation and prediction) will be used. |
| 260 | + """ |
| 261 | + |
| 262 | + _allowed_types = (float,) |
| 263 | + |
| 264 | + _description = ('The relative difference between the prediction and the observation') |
| 265 | + |
| 266 | + _best = 0.0 # A RelativeDifferenceScore of 0.0 is best |
| 267 | + |
| 268 | + _worst = np.inf |
| 269 | + |
| 270 | + scale = None |
| 271 | + |
| 272 | + def _check_score(self, score): |
| 273 | + if score < 0.0: |
| 274 | + raise errors.InvalidScoreError(("RelativeDifferenceScore was initialized with " |
| 275 | + "a score of %f, but a RelativeDifferenceScore " |
| 276 | + "must be non-negative.") % score) |
245 | 277 |
|
| 278 | + @classmethod |
| 279 | + def compute(cls, observation: Union[dict, float, int, pq.Quantity], |
| 280 | + prediction: Union[dict, float, int, pq.Quantity], |
| 281 | + key=None, |
| 282 | + scale: Union[float, int, pq.Quantity, None] = None) -> 'RelativeDifferenceScore': |
| 283 | + """Compute the relative difference between the observation and a prediction. |
| 284 | +
|
| 285 | + Returns: |
| 286 | + RelativeDifferenceScore: A relative difference between an observation and a prediction. |
| 287 | + """ |
| 288 | + assert isinstance(observation, (dict, float, int, pq.Quantity)) |
| 289 | + assert isinstance(prediction, (dict, float, int, pq.Quantity)) |
| 290 | + |
| 291 | + obs, pred = cls.extract_means_or_values(observation, prediction, |
| 292 | + key=key) |
| 293 | + |
| 294 | + scale = scale or cls.scale or (obs/float(obs)) |
| 295 | + assert type(obs) is type(scale) |
| 296 | + assert type(obs) is type(pred) |
| 297 | + if isinstance(obs, pq.Quantity): |
| 298 | + assert obs.units == pred.units, \ |
| 299 | + "Prediction must have the same units as the observation" |
| 300 | + assert obs.units == scale.units, \ |
| 301 | + "RelativeDifferenceScore.Scale must have the same units as the observation" |
| 302 | + assert scale > 0, \ |
| 303 | + "RelativeDifferenceScore.scale must be positive (not %g)" % scale |
| 304 | + value = np.abs(pred - obs) / scale |
| 305 | + value = utils.assert_dimensionless(value) |
| 306 | + return RelativeDifferenceScore(value) |
| 307 | + |
| 308 | + @property |
| 309 | + def norm_score(self) -> float: |
| 310 | + """Return 1.0 for a ratio of 0.0, falling to 0.0 for extremely large values. |
| 311 | +
|
| 312 | + Returns: |
| 313 | + float: The value of the norm score. |
| 314 | + """ |
| 315 | + x = self.score |
| 316 | + return 1 / (1+x) |
| 317 | + |
| 318 | + def __str__(self): |
| 319 | + return 'Relative Difference = %.2f' % self.score |
| 320 | + |
246 | 321 |
|
247 | 322 | class RelativeDifferenceScore(Score): |
248 | 323 | """A relative difference between prediction and observation. |
|
0 commit comments