• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

adaptive-machine-learning / CapyMOA / 25090331288

29 Apr 2026 04:05AM UTC coverage: 75.028% (+0.1%) from 74.909%
25090331288

Pull #355

github

web-flow
Merge 263e60102 into 2ef7fe73e
Pull Request #355: feat(ocl): add lwf

57 of 60 new or added lines in 4 files covered. (95.0%)

51 existing lines in 4 files now uncovered.

7247 of 9659 relevant lines covered (75.03%)

0.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

80.2
/src/capymoa/evaluation/evaluation.py
1
import csv
1✔
2
import json
1✔
3
import os
1✔
4
import time
1✔
5
import warnings
1✔
6
from itertools import islice
1✔
7
from typing import Optional, Sized, Union
1✔
8

9
from capymoa.base import Batch
1✔
10
import torch
1✔
11
import numpy as np
1✔
12
import pandas as pd
1✔
13
from com.yahoo.labs.samoa.instances import Attribute, DenseInstance, Instances
1✔
14
from java.util import ArrayList
1✔
15
from moa.core import InstanceExample
1✔
16
from moa.evaluation import (
1✔
17
    BasicAUCImbalancedPerformanceEvaluator,
18
    BasicClassificationPerformanceEvaluator,
19
    BasicPredictionIntervalEvaluator,
20
    BasicRegressionPerformanceEvaluator,
21
    EfficientEvaluationLoops,
22
    WindowAUCImbalancedPerformanceEvaluator,
23
    WindowClassificationPerformanceEvaluator,
24
    WindowPredictionIntervalEvaluator,
25
    WindowRegressionPerformanceEvaluator,
26
)
27
from moa.streams import InstanceStream
1✔
28
from tqdm import tqdm
1✔
29

30
from capymoa._utils import _translate_metric_name, batched
1✔
31
from capymoa.base import (
1✔
32
    AnomalyDetector,
33
    BatchClassifier,
34
    BatchRegressor,
35
    Classifier,
36
    ClassifierSSL,
37
    Clusterer,
38
    MOAPredictionIntervalLearner,
39
    Regressor,
40
)
41
from capymoa.evaluation._progress_bar import resolve_progress_bar
1✔
42
from capymoa.evaluation.results import PrequentialResults
1✔
43
from capymoa.instance import LabeledInstance, RegressionInstance
1✔
44
from capymoa.stream import Schema, Stream
1✔
45

46

47
def _is_fast_mode_compilable(stream: Stream, learner, optimise=True) -> bool:
1✔
48
    # refuse prediction interval learner
49
    if not hasattr(learner, "moa_learner") or isinstance(
1✔
50
        learner.moa_learner, MOAPredictionIntervalLearner
51
    ):
52
        return False
1✔
53

54
    """Check if the stream is compatible with the efficient loops in MOA."""
1✔
55
    is_moa_stream = isinstance(stream.get_moa_stream(), InstanceStream)
1✔
56
    is_moa_learner = hasattr(learner, "moa_learner") and learner.moa_learner is not None
1✔
57

58
    return is_moa_stream and is_moa_learner and optimise
1✔
59

60

61
def _get_expected_length(
1✔
62
    stream: Stream, max_instances: Optional[int] = None
63
) -> Optional[int]:
64
    """Get the expected length of the stream."""
65
    if isinstance(stream, Sized) and max_instances is not None:
1✔
66
        return min(len(stream), max_instances)
1✔
67
    elif isinstance(stream, Sized) and max_instances is None:
1✔
68
        return len(stream)
1✔
69
    elif max_instances is not None:
1✔
70
        return max_instances
1✔
71
    else:
72
        return None
×
73

74

75
def _setup_progress_bar(
1✔
76
    msg: str,
77
    progress_bar: Union[bool, tqdm],
78
    stream: Stream,
79
    learner,
80
    max_instances: Optional[int],
81
):
82
    expected_length = _get_expected_length(stream, max_instances)
1✔
83
    progress_bar = resolve_progress_bar(
1✔
84
        progress_bar,
85
        f"{msg} {type(learner).__name__!r} on {type(stream).__name__!r}",
86
    )
87
    if progress_bar is not None and expected_length is not None:
1✔
88
        progress_bar.set_total(expected_length)
1✔
89
    return progress_bar
1✔
90

91

92
class ClassificationEvaluator:
1✔
93
    """
94
    Wrapper for the Classification Performance Evaluator from MOA. By default, it uses the
95
    BasicClassificationPerformanceEvaluator
96
    """
97

98
    def __init__(
1✔
99
        self,
100
        schema: Schema = None,
101
        window_size=None,
102
        allow_abstaining=True,
103
        moa_evaluator=None,
104
    ):
105
        self.instances_seen = 0
1✔
106
        self.result_windows = []
1✔
107
        self.window_size = window_size
1✔
108

109
        self.allow_abstaining = allow_abstaining
1✔
110

111
        self.moa_basic_evaluator = moa_evaluator
1✔
112
        if self.moa_basic_evaluator is None:
1✔
113
            self.moa_basic_evaluator = BasicClassificationPerformanceEvaluator()
1✔
114

115
        self.moa_basic_evaluator.recallPerClassOption.set()
1✔
116
        self.moa_basic_evaluator.precisionPerClassOption.set()
1✔
117
        self.moa_basic_evaluator.precisionRecallOutputOption.set()
1✔
118
        self.moa_basic_evaluator.f1PerClassOption.set()
1✔
119
        self.moa_basic_evaluator.prepareForUse()
1✔
120

121
        _attributeValues = ArrayList()
1✔
122
        self.pred_template = [0, 0]
1✔
123

124
        self.schema = schema
1✔
125
        self._header = None
1✔
126
        if self.schema is not None:
1✔
127
            if self.schema.get_label_indexes() is not None:
1✔
128
                for value in self.schema.get_label_indexes():
1✔
129
                    _attributeValues.append(value)
1✔
130
                _classAttribute = Attribute("Class", _attributeValues)
1✔
131
                attSub = ArrayList()
1✔
132
                attSub.append(_classAttribute)
1✔
133
                self._header = Instances("", attSub, 1)
1✔
134
                self._header.setClassIndex(0)
1✔
135
            else:
136
                raise ValueError(
×
137
                    "Schema was not initialised properly, please define a proper Schema."
138
                )
139
        else:
140
            raise ValueError("Schema is None, please define a proper Schema.")
×
141

142
        self.pred_template = [0] * len(self.schema.get_label_indexes())
1✔
143

144
        # Create the denseInstance just once and keep reusing it by changing the classValue (more efficient).
145
        self._instance = DenseInstance(1)
1✔
146
        self._instance.setDataset(self._header)
1✔
147

148
    def __repr__(self):
1✔
149
        return str(self)
×
150

151
    def __str__(self):
1✔
152
        return str(self.metrics_dict())
×
153

154
    def get_instances_seen(self):
1✔
155
        return self.instances_seen
1✔
156

157
    def update(self, y_target_index: int, y_pred_index: Optional[int]):
1✔
158
        """Update the evaluator with the ground-truth and the prediction.
159

160
        :param y_target_index: The ground-truth class index. This is NOT
161
            the actual class value, but the index of the class value in the
162
            schema.
163
        :param y_pred_index: The predicted class index. If the classifier
164
            abstains from making a prediction, this value can be None.
165
        :raises ValueError: If the values are not valid indexes in the schema.
166
        """
167
        if not isinstance(y_target_index, (np.integer, int)):
1✔
168
            raise ValueError(
×
169
                f"y_target_index must be an integer, not {type(y_target_index)}"
170
            )
171
        if not (y_pred_index is None or isinstance(y_pred_index, (np.integer, int))):
1✔
172
            raise ValueError(
×
173
                f"y_pred_index must be an integer, not {type(y_pred_index)}"
174
            )
175

176
        # If the prediction is invalid, it could mean the classifier is abstaining from making a prediction;
177
        # thus, it is allowed to continue (unless parameterized differently).
178
        if y_pred_index is not None and not self.schema.is_y_index_in_range(
1✔
179
            y_pred_index
180
        ):
181
            if self.allow_abstaining:
×
182
                y_pred_index = None
×
183
            else:
184
                raise ValueError(f"Invalid prediction y_pred_index = {y_pred_index}")
×
185

186
        # Notice, in MOA the class value is an index, not the actual value
187
        # (e.g. not "one" but 0 assuming labels=["one", "two"])
188
        self._instance.setClassValue(y_target_index)
1✔
189
        example = InstanceExample(self._instance)
1✔
190

191
        # Shallow copy of the pred_template
192
        # MOA evaluator accepts the result of getVotesForInstance which is similar to a predict_proba
193
        #    (may or may not be normalised, but for our purposes it doesn't matter)
194
        prediction_array = self.pred_template[:]
1✔
195

196
        # if y_pred is None, it indicates the learner did not produce a prediction for this instance,
197
        # count as an error
198
        if y_pred_index is None:
1✔
199
            # TODO: Modify this once the option to abstain from predictions is implemented. Currently, by default it
200
            #  sets the prediction to the first class (index zero), which is consistent with MOA.
201
            y_pred_index = 0
1✔
202
            # Set y_pred_index to any valid prediction that is not y (force an incorrect prediction)
203
            # This does not affect recall or any other metrics, because the selected value is always
204
            # incorrect.
205

206
            # Create an intermediary array with indices excluding the y
207
            # indexesWithoutY = [
208
            #     i for i in range(len(self.schema.get_label_indexes())) if i != y_target_index
209
            # ]
210
            # random_y_pred = random.choice(indexesWithoutY)
211
            # y_pred_index = self.schema.get_label_indexes()[random_y_pred]
212

213
        prediction_array[int(y_pred_index)] += 1
1✔
214
        self.moa_basic_evaluator.addResult(example, prediction_array)
1✔
215

216
        self.instances_seen += 1
1✔
217

218
        # If the window_size is set, then check if it should record the intermediary results.
219
        if self.window_size is not None and self.instances_seen % self.window_size == 0:
1✔
220
            performance_values = self.metrics()
1✔
221
            self.result_windows.append(performance_values)
1✔
222

223
    def metrics_header(self):
1✔
224
        performance_measurements = self.moa_basic_evaluator.getPerformanceMeasurements()
1✔
225
        performance_names = [
1✔
226
            _translate_metric_name("".join(measurement.getName()), to="capymoa")
227
            for measurement in performance_measurements
228
        ]
229
        return performance_names
1✔
230

231
    def metrics(self):
1✔
232
        return [
1✔
233
            measurement.getValue()
234
            for measurement in self.moa_basic_evaluator.getPerformanceMeasurements()
235
        ]
236

237
    def metrics_dict(self):
1✔
238
        return {
×
239
            header: value
240
            for header, value in zip(self.metrics_header(), self.metrics())
241
        }
242

243
    def metrics_per_window(self):
1✔
244
        return pd.DataFrame(self.result_windows, columns=self.metrics_header())
1✔
245

246
    def __getitem__(self, key):
1✔
247
        if hasattr(self, key):
×
248
            attr = getattr(self, key)
×
249
            return attr()
×
250
        return self.__getattr__(key)()
×
251

252
    # This allows access to metrics that are generated dynamically like recall_0, f1_score_3, ...
253
    def __getattr__(self, metric):
1✔
254
        if metric in self.metrics_header():
1✔
255
            index = self.metrics_header().index(metric)
1✔
256

257
            def metric_value():
1✔
258
                return float(self.metrics()[index])
1✔
259

260
            return metric_value
1✔
261
        return None
×
262

263
    def accuracy(self):
1✔
264
        index = self.metrics_header().index("accuracy")
1✔
265
        return float(self.metrics()[index])
1✔
266

267
    def kappa(self):
1✔
268
        index = self.metrics_header().index("kappa")
1✔
269
        return float(self.metrics()[index])
1✔
270

271
    def kappa_t(self):
1✔
272
        index = self.metrics_header().index("kappa_t")
1✔
273
        return float(self.metrics()[index])
1✔
274

275
    def kappa_m(self):
1✔
276
        index = self.metrics_header().index("kappa_m")
1✔
277
        return float(self.metrics()[index])
1✔
278

279
    def f1_score(self):
1✔
280
        index = self.metrics_header().index("f1_score")
1✔
281
        return float(self.metrics()[index])
1✔
282

283
    def precision(self):
1✔
284
        index = self.metrics_header().index("precision")
1✔
285
        return float(self.metrics()[index])
1✔
286

287
    def recall(self):
1✔
288
        index = self.metrics_header().index("recall")
1✔
289
        return float(self.metrics()[index])
1✔
290

291

292
class RegressionEvaluator:
1✔
293
    """
294
    Wrapper for the Regression Performance Evaluator from MOA.
295
    By default, it uses the MOA BasicRegressionPerformanceEvaluator as moa_evaluator.
296
    """
297

298
    def __init__(self, schema=None, window_size=None, moa_evaluator=None):
1✔
299
        self.instances_seen = 0
1✔
300
        self.result_windows = []
1✔
301
        self.window_size = window_size
1✔
302

303
        self.moa_basic_evaluator = moa_evaluator
1✔
304
        if self.moa_basic_evaluator is None:
1✔
305
            self.moa_basic_evaluator = BasicRegressionPerformanceEvaluator()
1✔
306

307
        _attributeValues = ArrayList()
1✔
308

309
        self.schema = schema
1✔
310
        self._header = None
1✔
311
        if self.schema is not None:
1✔
312
            if self.schema.is_regression():
1✔
313
                attSub = ArrayList()
1✔
314
                for _ in range(self.schema.get_num_attributes()):
1✔
315
                    attSub.append(Attribute("Attribute"))
1✔
316
                _targetAttribute = Attribute("Target")
1✔
317

318
                attSub.append(_targetAttribute)
1✔
319
                self._header = Instances("", attSub, 1)
1✔
320
                self._header.setClassIndex(self.schema.get_num_attributes())
1✔
321
            else:
322
                raise ValueError("Schema was not set for a regression task")
×
323
        else:
324
            raise ValueError("Schema is None, please define a proper Schema.")
×
325

326
        # Regression has only one output
327
        self.pred_template = [0]
1✔
328

329
        # Create the denseInstance just once and keep reusing it by changing the classValue (more efficient).
330
        self._instance = DenseInstance(self.schema.get_num_attributes() + 1)
1✔
331
        self._instance.setDataset(self._header)
1✔
332

333
    def __str__(self):
1✔
334
        return str(self.metrics_dict())
×
335

336
    def get_instances_seen(self):
1✔
337
        return self.instances_seen
1✔
338

339
    def update(self, y, y_pred: Optional[float]):
1✔
340
        if y is None:
1✔
341
            raise ValueError(f"Invalid ground-truth y = {y}")
×
342

343
        self._instance.setClassValue(y)
1✔
344
        example = InstanceExample(self._instance)
1✔
345

346
        # The learner did not produce a prediction for this instance, thus y_pred is None
347
        if y_pred is None:
1✔
348
            # We used to produce a warning here, but since `None` predictions are common
349
            # at the beginning of training, most warnings that were not useful.
350
            y_pred = 0.0
1✔
351

352
        # Different from classification, there is no need to copy the prediction array, just override the value.
353
        self.pred_template[0] = y_pred
1✔
354
        self.moa_basic_evaluator.addResult(example, self.pred_template)
1✔
355

356
        self.instances_seen += 1
1✔
357

358
        # If the window_size is set, then check if it should record the intermediary results.
359
        if self.window_size is not None and self.instances_seen % self.window_size == 0:
1✔
360
            performance_values = [
1✔
361
                measurement.getValue()
362
                for measurement in self.moa_basic_evaluator.getPerformanceMeasurements()
363
            ]
364
            self.result_windows.append(performance_values)
1✔
365

366
    def metrics_header(self):
1✔
367
        performance_measurements = self.moa_basic_evaluator.getPerformanceMeasurements()
1✔
368
        performance_names = [
1✔
369
            _translate_metric_name("".join(measurement.getName()), to="capymoa")
370
            for measurement in performance_measurements
371
        ]
372
        return performance_names
1✔
373

374
    def metrics(self):
1✔
375
        return [
1✔
376
            measurement.getValue()
377
            for measurement in self.moa_basic_evaluator.getPerformanceMeasurements()
378
        ]
379

380
    def metrics_dict(self):
1✔
381
        return {
×
382
            header: value
383
            for header, value in zip(self.metrics_header(), self.metrics())
384
        }
385

386
    def metrics_per_window(self):
1✔
387
        return pd.DataFrame(self.result_windows, columns=self.metrics_header()).copy()
1✔
388

389
    def ground_truth_y(self):
1✔
390
        return self.gt_y
×
391

392
    def mae(self):
1✔
393
        index = self.metrics_header().index("mae")
1✔
394
        return self.metrics()[index]
1✔
395

396
    def rmse(self):
1✔
397
        index = self.metrics_header().index("rmse")
1✔
398
        return self.metrics()[index]
1✔
399

400
    def rmae(self):
1✔
401
        index = self.metrics_header().index("rmae")
×
402
        return self.metrics()[index]
×
403

404
    def r2(self):
1✔
405
        index = self.metrics_header().index("r2")
1✔
406
        return self.metrics()[index]
1✔
407

408
    def adjusted_r2(self):
1✔
409
        index = self.metrics_header().index("adjusted_r2")
×
410
        return self.metrics()[index]
×
411

412

413
class AnomalyDetectionEvaluator:
1✔
414
    """
415
    Wrapper for the Anomaly (AUC) Performance Evaluator from MOA. By default, it uses the
416
    BasicAUCImbalancedPerformanceEvaluator
417
    """
418

419
    def __init__(
1✔
420
        self,
421
        schema: Schema = None,
422
        window_size=None,
423
    ):
424
        self.instances_seen = 0
1✔
425
        self.result_windows = []
1✔
426
        self.window_size = window_size
1✔
427

428
        self.moa_basic_evaluator = BasicAUCImbalancedPerformanceEvaluator()
1✔
429

430
        self.moa_basic_evaluator.calculateAUC.set()
1✔
431

432
        _attributeValues = ArrayList()
1✔
433
        self.pred_template = [0, 0]
1✔
434

435
        self.schema = schema
1✔
436
        self._header = None
1✔
437
        if self.schema is not None:
1✔
438
            if self.schema.get_label_indexes() is not None:
1✔
439
                for value in self.schema.get_label_indexes():
1✔
440
                    _attributeValues.append(value)
1✔
441
                _classAttribute = Attribute("Class", _attributeValues)
1✔
442
                attSub = ArrayList()
1✔
443
                attSub.append(_classAttribute)
1✔
444
                self._header = Instances("", attSub, 1)
1✔
445
                self._header.setClassIndex(0)
1✔
446
            else:
447
                raise ValueError(
×
448
                    "Schema was not initialised properly, please define a proper Schema."
449
                )
450
        else:
451
            raise ValueError("Schema is None, please define a proper Schema.")
×
452

453
        # Create the denseInstance just once and keep reusing it by changing the classValue (more efficient).
454
        self._instance = DenseInstance(1)
1✔
455
        self._instance.setDataset(self._header)
1✔
456

457
    def __str__(self):
1✔
458
        return str(self.metrics_dict())
×
459

460
    def get_instances_seen(self):
1✔
461
        return self.instances_seen
×
462

463
    def update(self, y_target_index: int, score: float):
1✔
464
        """Update the evaluator with the ground-truth and the prediction.
465

466
        :param y_target_index: The ground-truth class index. This is NOT
467
            the actual class value, but the index of the class value in the
468
            schema.
469
        :param score: The predicted scores. Should be in the range [0, 1].
470
        """
471
        if not isinstance(y_target_index, (np.integer, int)):
1✔
472
            raise ValueError(
×
473
                f"y_target_index must be an integer, not {type(y_target_index)}"
474
            )
475

476
        # Notice, in MOA the class value is an index, not the actual value
477
        # (e.g. not "one" but 0 assuming labels=["one", "two"])
478
        self._instance.setClassValue(y_target_index)
1✔
479
        example = InstanceExample(self._instance)
1✔
480

481
        self.moa_basic_evaluator.addResult(example, [1 - score, score])
1✔
482

483
        self.instances_seen += 1
1✔
484

485
        # If the window_size is set, then check if it should record the intermediary results.
486
        if self.window_size is not None and self.instances_seen % self.window_size == 0:
1✔
487
            performance_values = self.metrics()
1✔
488
            self.result_windows.append(performance_values)
1✔
489

490
    def metrics_header(self):
1✔
491
        performance_measurements = self.moa_basic_evaluator.getPerformanceMeasurements()
1✔
492
        performance_names = [
1✔
493
            _translate_metric_name("".join(measurement.getName()), to="capymoa")
494
            for measurement in performance_measurements
495
        ]
496
        return performance_names
1✔
497

498
    def metrics(self):
1✔
499
        return [
1✔
500
            measurement.getValue()
501
            for measurement in self.moa_basic_evaluator.getPerformanceMeasurements()
502
        ]
503

504
    def metrics_dict(self):
1✔
505
        return {
×
506
            header: value
507
            for header, value in zip(self.metrics_header(), self.metrics())
508
        }
509

510
    def metrics_per_window(self):
1✔
511
        return pd.DataFrame(self.result_windows, columns=self.metrics_header())
×
512

513
    def auc(self):
1✔
514
        index = self.metrics_header().index("auc")
1✔
515
        return self.metrics()[index]
1✔
516

517
    def s_auc(self):
1✔
518
        index = self.metrics_header().index("s_auc")
×
519
        return self.metrics()[index]
×
520

521

522
class AnomalyDetectionWindowedEvaluator:
1✔
523
    """
524
    Wrapper for the AUC Performance Evaluator from MOA. By default, it uses the
525
    WindowAUCImbalancedPerformanceEvaluator
526
    """
527

528
    def __init__(
1✔
529
        self,
530
        schema: Schema = None,
531
        window_size=None,
532
    ):
533
        self.instances_seen = 0
1✔
534
        self.result_windows = []
1✔
535
        self.window_size = window_size
1✔
536

537
        self.moa_evaluator = WindowAUCImbalancedPerformanceEvaluator()
1✔
538
        self.moa_evaluator.widthOption.setValue(window_size)
1✔
539

540
        _attributeValues = ArrayList()
1✔
541
        self.pred_template = [0, 0]
1✔
542

543
        self.schema = schema
1✔
544
        self._header = None
1✔
545
        if self.schema is not None:
1✔
546
            if self.schema.get_label_indexes() is not None:
1✔
547
                for value in self.schema.get_label_indexes():
1✔
548
                    _attributeValues.append(value)
1✔
549
                _classAttribute = Attribute("Class", _attributeValues)
1✔
550
                attSub = ArrayList()
1✔
551
                attSub.append(_classAttribute)
1✔
552
                self._header = Instances("", attSub, 1)
1✔
553
                self._header.setClassIndex(0)
1✔
554
            else:
555
                raise ValueError(
×
556
                    "Schema was not initialised properly, please define a proper Schema."
557
                )
558
        else:
559
            raise ValueError("Schema is None, please define a proper Schema.")
×
560

561
        # Create the denseInstance just once and keep reusing it by changing the classValue (more efficient).
562
        self._instance = DenseInstance(1)
1✔
563
        self._instance.setDataset(self._header)
1✔
564

565
    def __str__(self):
1✔
566
        return str(self.metrics_dict())
×
567

568
    def get_instances_seen(self):
1✔
569
        return self.instances_seen
1✔
570

571
    def update(self, y_target_index: int, score: float):
1✔
572
        """Update the evaluator with the ground-truth and the prediction.
573

574
        :param y_target_index: The ground-truth class index. This is NOT
575
            the actual class value, but the index of the class value in the
576
            schema.
577
        :param score: The predicted scores. Should be in the range [0, 1].
578
        """
579
        if not isinstance(y_target_index, (np.integer, int)):
1✔
580
            raise ValueError(
×
581
                f"y_target_index must be an integer, not {type(y_target_index)}"
582
            )
583

584
        # Notice, in MOA the class value is an index, not the actual value
585
        # (e.g. not "one" but 0 assuming labels=["one", "two"])
586
        self._instance.setClassValue(y_target_index)
1✔
587
        example = InstanceExample(self._instance)
1✔
588

589
        self.moa_evaluator.addResult(example, [1 - score, score])
1✔
590

591
        self.instances_seen += 1
1✔
592

593
        # If the window_size is set, then check if it should record the intermediary results.
594
        if self.window_size is not None and self.instances_seen % self.window_size == 0:
1✔
595
            performance_values = self.metrics()
1✔
596
            self.result_windows.append(performance_values)
1✔
597

598
    def metrics_header(self):
1✔
599
        performance_measurements = self.moa_evaluator.getPerformanceMeasurements()
1✔
600
        performance_names = [
1✔
601
            _translate_metric_name("".join(measurement.getName()), to="capymoa")
602
            for measurement in performance_measurements
603
        ]
604
        return performance_names
1✔
605

606
    def metrics(self):
1✔
607
        return [
1✔
608
            measurement.getValue()
609
            for measurement in self.moa_evaluator.getPerformanceMeasurements()
610
        ]
611

612
    def metrics_dict(self):
1✔
613
        return {
×
614
            header: value
615
            for header, value in zip(self.metrics_header(), self.metrics())
616
        }
617

618
    def metrics_per_window(self):
1✔
619
        return pd.DataFrame(self.result_windows, columns=self.metrics_header())
×
620

621
    def auc(self):
1✔
622
        index = self.metrics_header().index("auc")
1✔
623
        return self.metrics()[index]
1✔
624

625
    def s_auc(self):
1✔
626
        index = self.metrics_header().index("s_auc")
×
627
        return self.metrics()[index]
×
628

629

630
class ClusteringEvaluator:
1✔
631
    """
632
    Abstract clustering evaluator for CapyMOA.
633
    It is slightly different from the other evaluators because it does not have a moa_evaluator object.
634
    Clustering evaluation at this point is very simple and only uses the unsupervised metrics.
635
    """
636

637
    def __init__(self, update_interval=1000):
1✔
638
        """
639
        Only the update_interval is set here.
640
        :param update_interval: The interval at which the evaluator should update the measurements
641
        """
642
        self.instances_seen = 0
×
643
        self.update_interval = update_interval
×
644
        self.measurements = {name: [] for name in self.metrics_header()}
×
645
        self.clusterer_name = None
×
646

647
    def __str__(self):
1✔
648
        return str(self.metrics_dict())
×
649

650
    def get_instances_seen(self):
1✔
651
        return self.instances_seen
×
652

653
    def get_update_interval(self):
1✔
654
        return self.update_interval
×
655

656
    def get_clusterer_name(self):
1✔
657
        return self.clusterer_name
×
658

659
    def update(self, clusterer: Clusterer):
1✔
660
        if self.clusterer_name is None:
×
661
            self.clusterer_name = str(clusterer)
×
662
        self.instances_seen += 1
×
663
        if self.instances_seen % self.update_interval == 0:
×
664
            self._update_measurements(clusterer)
×
665

666
    def _update_measurements(self, clusterer: Clusterer):
1✔
667
        # update centers, weights, sizes, and radii
668
        if clusterer.implements_macro_clusters():
×
669
            macro = clusterer.get_clustering_result()
×
670
            if len(macro.get_centers()) > 0:
×
671
                self.measurements["macro"].append(macro)
×
672

673
        if clusterer.implements_micro_clusters():
×
674
            micro = clusterer.get_micro_clustering_result()
×
675
            if len(micro.get_centers()) > 0:
×
676
                self.measurements["micro"].append(micro)
×
677

678
        # calculate silhouette score
679
        # TODO: delegate silhouette to moa
680
        # Check how it is done among different clusterers
681

682
    def metrics_header(self):
1✔
683
        performance_names = ["macro", "micro"]
×
684
        return performance_names
×
685

686
    def metrics(self):
1✔
687
        # using the static list to keep the order of the metrics
688
        return [self.measurements[key] for key in self.metrics_header()]
×
689

690
    def get_measurements(self):
1✔
691
        return self.measurements
×
692

693

694
class ClassificationWindowedEvaluator(ClassificationEvaluator):
1✔
695
    """
696
    Uses the ClassificationEvaluator to perform a windowed evaluation.
697

698
    IMPORTANT: The results for the last window are not always available through ```metrics()```, if the window_size does
699
    not perfectly divide the stream, the metrics corresponding to the last remaining instances in the last window can
700
    be obtained by invoking ```metrics()```
701
    """
702

703
    def __init__(self, schema=None, window_size=1000):
1✔
704
        self.moa_evaluator = WindowClassificationPerformanceEvaluator()
1✔
705
        self.moa_evaluator.widthOption.setValue(window_size)
1✔
706

707
        super().__init__(
1✔
708
            schema=schema,
709
            window_size=window_size,
710
            moa_evaluator=self.moa_evaluator,
711
        )
712

713
    def __repr__(self):
1✔
714
        return str(self)
×
715

716
    def __str__(self):
1✔
717
        pass
718

719
    # This allows access to metrics that are generated dynamically like recall_0, f1_score_3, ...
720
    def __getattr__(self, metric):
1✔
721
        if metric in self.metrics_header():
×
722

723
            def metric_value():
×
724
                return self.metrics_per_window()[metric].tolist()
×
725

726
            return metric_value
×
727
        return None
×
728

729
    def accuracy(self):
1✔
730
        return self.metrics_per_window()["accuracy"].tolist()
1✔
731

732
    def kappa(self):
1✔
733
        return self.metrics_per_window()["kappa"].tolist()
×
734

735
    def kappa_t(self):
1✔
736
        return self.metrics_per_window()["kappa_t"].tolist()
×
737

738
    def kappa_m(self):
1✔
739
        return self.metrics_per_window()["kappa_m"].tolist()
×
740

741
    def f1_score(self):
1✔
742
        return self.metrics_per_window()["f1_score"].tolist()
×
743

744
    def precision(self):
1✔
745
        return self.metrics_per_window()["precision"].tolist()
×
746

747
    def recall(self):
1✔
748
        return self.metrics_per_window()["recall"].tolist()
×
749

750

751
class RegressionWindowedEvaluator(RegressionEvaluator):
1✔
752
    """
753
    Uses the RegressionEvaluator to perform a windowed evaluation.
754

755
    IMPORTANT: The results for the last window are always through ```metrics()```, if the window_size does not
756
    perfectly divide the stream, the metrics corresponding to the last remaining instances in the last window can
757
    be obtained by invoking ```metrics()```
758
    """
759

760
    def __init__(self, schema=None, window_size=1000):
1✔
761
        self.moa_evaluator = WindowRegressionPerformanceEvaluator()
1✔
762
        self.moa_evaluator.widthOption.setValue(window_size)
1✔
763

764
        super().__init__(
1✔
765
            schema=schema, window_size=window_size, moa_evaluator=self.moa_evaluator
766
        )
767

768
    def mae(self):
1✔
769
        return self.metrics_per_window()["mae"].tolist()
×
770

771
    def rmse(self):
1✔
772
        return self.metrics_per_window()["rmse"].tolist()
1✔
773

774
    def rmae(self):
1✔
775
        return self.metrics_per_window()["rmae"].tolist()
×
776

777
    def r2(self):
1✔
778
        return self.metrics_per_window()["r2"].tolist()
×
779

780
    def adjusted_r2(self):
1✔
781
        return self.metrics_per_window()["adjusted_r2"].tolist()
×
782

783

784
class PredictionIntervalEvaluator(RegressionEvaluator):
1✔
785
    def __init__(self, schema=None, window_size=None, moa_evaluator=None):
1✔
786
        self.instances_seen = 0
1✔
787
        self.result_windows = []
1✔
788
        self.window_size = window_size
1✔
789

790
        self.moa_basic_evaluator = moa_evaluator
1✔
791
        if self.moa_basic_evaluator is None:
1✔
792
            self.moa_basic_evaluator = BasicPredictionIntervalEvaluator()
1✔
793

794
        # self.moa_basic_evaluator.prepareForUse()
795

796
        _attributeValues = ArrayList()
1✔
797

798
        self.schema = schema
1✔
799
        self._header = None
1✔
800
        if self.schema is not None:
1✔
801
            if self.schema.is_regression():
1✔
802
                attSub = ArrayList()
1✔
803
                for _ in range(self.schema.get_num_attributes()):
1✔
804
                    attSub.append(Attribute("Attribute"))
1✔
805
                _targetAttribute = Attribute("Target")
1✔
806

807
                attSub.append(_targetAttribute)
1✔
808
                self._header = Instances("", attSub, 1)
1✔
809
                self._header.setClassIndex(self.schema.get_num_attributes())
1✔
810
                # print(self._header)
811
            else:
812
                raise ValueError("Schema was not set for a regression task")
×
813
        else:
814
            raise ValueError("Schema is None, please define a proper Schema.")
×
815

816
        # Prediction Interval has three outputs: lower bound, prediction, upper bound
817
        self.pred_template = [0, 0, 0]
1✔
818

819
        # Create the denseInstance just once and keep reusing it by changing the classValue (more efficient).
820
        self._instance = DenseInstance(self.schema.get_num_attributes() + 1)
1✔
821
        self._instance.setDataset(self._header)
1✔
822

823
    def update(self, y, y_pred):
1✔
824
        if y is None:
1✔
825
            raise ValueError(f"Invalid ground-truth y = {y}")
×
826

827
        self._instance.setClassValue(y)
1✔
828
        example = InstanceExample(self._instance)
1✔
829

830
        # if y_pred is None, it indicates the learner did not produce a prediction for this instace
831
        if y_pred is None:
1✔
832
            # if the y_pred is None, give a warning and then assign y_pred with an all zero prediction array
833
            warnings.warn(
×
834
                "The learner did not produce a prediction interval for this instance"
835
            )
836
            y_pred = [0, 0, 0]
×
837

838
        if len(y_pred) != len(self.pred_template):
1✔
839
            warnings.warn(
×
840
                "The learner did not produce a valid prediction interval for this instance"
841
            )
842

843
        for i in range(len(y_pred)):
1✔
844
            self.pred_template[i] = y_pred[i]
1✔
845

846
        self.moa_basic_evaluator.addResult(example, self.pred_template)
1✔
847
        self.instances_seen += 1
1✔
848

849
        # If the window_size is set, then check if it should record the intermediary results.
850
        if self.window_size is not None and self.instances_seen % self.window_size == 0:
1✔
851
            performance_values = [
1✔
852
                measurement.getValue()
853
                for measurement in self.moa_basic_evaluator.getPerformanceMeasurements()
854
            ]
855
            self.result_windows.append(performance_values)
1✔
856

857
    def metrics_header(self):
1✔
858
        performance_measurements = self.moa_basic_evaluator.getPerformanceMeasurements()
1✔
859
        performance_names = [
1✔
860
            _translate_metric_name("".join(measurement.getName()), to="capymoa")
861
            for measurement in performance_measurements
862
        ]
863
        return performance_names
1✔
864

865
    def metrics(self):
1✔
866
        return [
1✔
867
            measurement.getValue()
868
            for measurement in self.moa_basic_evaluator.getPerformanceMeasurements()
869
        ]
870

871
    def metrics_per_window(self):
1✔
872
        return pd.DataFrame(self.result_windows, columns=self.metrics_header())
1✔
873

874
    def coverage(self):
1✔
875
        index = self.metrics_header().index("coverage")
1✔
876
        return self.metrics()[index]
1✔
877

878
    def average_length(self):
1✔
879
        index = self.metrics_header().index("average_length")
×
880
        return self.metrics()[index]
×
881

882
    def nmpiw(self):
1✔
883
        index = self.metrics_header().index("nmpiw")
×
884
        return self.metrics()[index]
×
885

886

887
class PredictionIntervalWindowedEvaluator(PredictionIntervalEvaluator):
1✔
888
    def __init__(self, schema=None, window_size=1000):
1✔
889
        self.moa_evaluator = WindowPredictionIntervalEvaluator()
1✔
890
        self.moa_evaluator.widthOption.setValue(window_size)
1✔
891

892
        super().__init__(
1✔
893
            schema=schema, window_size=window_size, moa_evaluator=self.moa_evaluator
894
        )
895

896
    def coverage(self):
1✔
897
        return self.metrics_per_window()["coverage"].tolist()
1✔
898

899
    def average_length(self):
1✔
900
        return self.metrics_per_window()["average length"].tolist()
×
901

902
    def nmpiw(self):
1✔
903
        return self.metrics_per_window()["nmpiw"].tolist()
×
904

905

906
def start_time_measuring():
1✔
907
    start_wallclock_time = time.time()
1✔
908
    start_cpu_time = time.process_time()
1✔
909

910
    return start_wallclock_time, start_cpu_time
1✔
911

912

913
def stop_time_measuring(start_wallclock_time, start_cpu_time):
1✔
914
    # Stop measuring time
915
    end_wallclock_time = time.time()
1✔
916
    end_cpu_time = time.process_time()
1✔
917

918
    # Calculate and print the elapsed time and CPU times
919
    elapsed_wallclock_time = end_wallclock_time - start_wallclock_time
1✔
920
    elapsed_cpu_time = end_cpu_time - start_cpu_time
1✔
921

922
    return elapsed_wallclock_time, elapsed_cpu_time
1✔
923

924

925
def _get_target(
1✔
926
    instance: Union[LabeledInstance, RegressionInstance],
927
) -> Union[int, np.double]:
928
    """Get the target value from an instance."""
929
    if isinstance(instance, LabeledInstance):
1✔
930
        return instance.y_index
1✔
931
    elif isinstance(instance, RegressionInstance):
1✔
932
        return instance.y_value
1✔
933
    else:
934
        raise ValueError("Unknown instance type")
×
935

936

937
def prequential_evaluation(
1✔
938
    stream: Stream,
939
    learner: Union[Classifier, Regressor],
940
    max_instances: Optional[int] = None,
941
    window_size: int = 1000,
942
    store_predictions: bool = False,
943
    store_y: bool = False,
944
    optimise: bool = True,
945
    restart_stream: bool = True,
946
    progress_bar: Union[bool, tqdm] = False,
947
    batch_size: int = 1,
948
) -> PrequentialResults:
949
    """Run and evaluate a learner on a stream using prequential evaluation.
950

951
    Calculates the metrics cumulatively (i.e. test-then-train) and in a
952
    window-fashion (i.e. windowed prequential evaluation). Returns both
953
    evaluators so that the user has access to metrics from both evaluators.
954

955
    :param stream: A data stream to evaluate the learner on. Will be restarted if
956
        ``restart_stream`` is True.
957
    :param learner: The learner to evaluate.
958
    :param max_instances: The number of instances to evaluate before exiting. If
959
        None, the evaluation will continue until the stream is empty.
960
    :param window_size: The size of the window used for windowed evaluation,
961
        defaults to 1000
962
    :param store_predictions: Store the learner's prediction in a list, defaults
963
        to False
964
    :param store_y: Store the ground truth targets in a list, defaults to False
965
    :param optimise: If True and the learner is compatible, the evaluator will
966
        use a Java native evaluation loop, defaults to True.
967
    :param restart_stream: If False, evaluation will continue from the current
968
        position in the stream, defaults to True. Not restarting the stream is
969
        useful for switching between learners or evaluators, without starting
970
        from the beginning of the stream.
971
    :param progress_bar: Enable, disable, or override the progress bar. Currently
972
        incompatible with ``optimize=True``.
973
    :param mini_batch: The size of the mini-batch to use for the learner.
974
    :return: An object containing the results of the evaluation windowed metrics,
975
        cumulative metrics, ground truth targets, and predictions.
976
    """
977
    if restart_stream:
1✔
978
        stream.restart()
1✔
979
    if batch_size != 1 and not isinstance(learner, (BatchClassifier, BatchRegressor)):
1✔
980
        raise ValueError(
×
981
            "The learner is not a batch learner, but mini_batch is set to a value greater than 1."
982
        )
983
    if _is_fast_mode_compilable(stream, learner, optimise):
1✔
984
        return _prequential_evaluation_fast(
1✔
985
            stream,
986
            learner,
987
            max_instances,
988
            window_size,
989
            store_y=store_y,
990
            store_predictions=store_predictions,
991
        )
992

993
    predictions = None
1✔
994
    if store_predictions:
1✔
995
        predictions = []
1✔
996

997
    ground_truth_y = None
1✔
998
    if store_y:
1✔
999
        ground_truth_y = []
1✔
1000

1001
    # Start measuring time
1002
    start_wallclock_time, start_cpu_time = start_time_measuring()
1✔
1003

1004
    evaluator_cumulative = None
1✔
1005
    evaluator_windowed = None
1✔
1006
    if stream.get_schema().is_classification():
1✔
1007
        evaluator_cumulative = ClassificationEvaluator(
1✔
1008
            schema=stream.get_schema(), window_size=window_size
1009
        )
1010
        if window_size is not None:
1✔
1011
            evaluator_windowed = ClassificationWindowedEvaluator(
1✔
1012
                schema=stream.get_schema(), window_size=window_size
1013
            )
1014
    else:
1015
        if not isinstance(learner, MOAPredictionIntervalLearner):
1✔
1016
            evaluator_cumulative = RegressionEvaluator(
1✔
1017
                schema=stream.get_schema(), window_size=window_size
1018
            )
1019
            if window_size is not None:
1✔
1020
                evaluator_windowed = RegressionWindowedEvaluator(
1✔
1021
                    schema=stream.get_schema(), window_size=window_size
1022
                )
1023
        else:
1024
            evaluator_cumulative = PredictionIntervalEvaluator(
×
1025
                schema=stream.get_schema(), window_size=window_size
1026
            )
1027
            if window_size is not None:
×
1028
                evaluator_windowed = PredictionIntervalWindowedEvaluator(
×
1029
                    schema=stream.get_schema(), window_size=window_size
1030
                )
1031

1032
    progress_bar = _setup_progress_bar(
1✔
1033
        "Eval", progress_bar, stream, learner, max_instances
1034
    )
1035

1036
    for i, batch in enumerate(batched(islice(stream, max_instances), batch_size)):
1✔
1037
        yb_true = [_get_target(instance) for instance in batch]  # batch of targets
1✔
1038
        yb_pred = []
1✔
1039

1040
        if isinstance(learner, Batch):
1✔
1041
            # Collect a batch of instances and predict them all at once
1042
            np_x = np.stack([instance.x for instance in batch])
1✔
1043
            torch_x = torch.from_numpy(np_x).to(
1✔
1044
                device=learner.device, dtype=learner.x_dtype
1045
            )
1046
            torch_y = torch.tensor(
1✔
1047
                yb_true, dtype=learner.y_dtype, device=learner.device
1048
            )
1049
            yb_pred = learner.batch_predict(torch_x).tolist()
1✔
1050
            learner.batch_train(torch_x, torch_y)
1✔
1051
        else:
1052
            for instance in batch:
1✔
1053
                yb_pred.append(learner.predict(instance))
1✔
1054
                learner.train(instance)
1✔
1055

1056
        for y_true, y_pred in zip(yb_true, yb_pred, strict=True):
1✔
1057
            evaluator_cumulative.update(y_true, y_pred)
1✔
1058
            if window_size is not None:
1✔
1059
                evaluator_windowed.update(y_true, y_pred)
1✔
1060

1061
            # Storing predictions if store_predictions was set to True during initialisation
1062
            if predictions is not None:
1✔
1063
                predictions.append(y_pred)
1✔
1064

1065
            # Storing ground-truth if store_y was set to True during initialisation
1066
            if ground_truth_y is not None:
1✔
1067
                ground_truth_y.append(y_true)
1✔
1068

1069
        if progress_bar is not None:
1✔
1070
            progress_bar.update(len(batch))
1✔
1071

1072
    if progress_bar is not None:
1✔
1073
        progress_bar.close()
1✔
1074

1075
    # Stop measuring time
1076
    elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1077
        start_wallclock_time, start_cpu_time
1078
    )
1079

1080
    # Add the results corresponding to the remainder of the stream in case the number of processed
1081
    # instances is not perfectly divisible by the window_size (if it was, then it is already be in
1082
    # the result_windows variable). The evaluator_windowed will be None if the window_size is None.
1083
    if (
1✔
1084
        evaluator_windowed is not None
1085
        and evaluator_windowed.get_instances_seen() % window_size != 0
1086
    ):
1087
        evaluator_windowed.result_windows.append(evaluator_windowed.metrics())
1✔
1088

1089
    results = PrequentialResults(
1✔
1090
        learner=str(learner),
1091
        stream=stream,
1092
        wallclock=elapsed_wallclock_time,
1093
        cpu_time=elapsed_cpu_time,
1094
        max_instances=max_instances,
1095
        cumulative_evaluator=evaluator_cumulative,
1096
        windowed_evaluator=evaluator_windowed,
1097
        ground_truth_y=np.array(ground_truth_y) if ground_truth_y else None,
1098
        predictions=np.array(predictions) if predictions else None,
1099
    )
1100

1101
    return results
1✔
1102

1103

1104
def prequential_ssl_evaluation(
1✔
1105
    stream: Stream,
1106
    learner: Union[ClassifierSSL, Classifier],
1107
    max_instances: Optional[int] = None,
1108
    window_size: int = 1000,
1109
    initial_window_size: int = 0,
1110
    delay_length: int = 0,
1111
    label_probability: float = 0.01,
1112
    random_seed: int = 1,
1113
    store_predictions: bool = False,
1114
    store_y: bool = False,
1115
    optimise: bool = True,
1116
    restart_stream: bool = True,
1117
    progress_bar: Union[bool, tqdm] = False,
1118
    batch_size: int = 1,
1119
):
1120
    """Run and evaluate a learner on a semi-supervised stream using prequential evaluation.
1121

1122
    :param stream: A data stream to evaluate the learner on. Will be restarted if
1123
        ``restart_stream`` is True.
1124
    :param learner: The learner to evaluate. If the learner is an SSL learner,
1125
        it will be trained on both labeled and unlabeled instances. If the
1126
        learner is not an SSL learner, then it will be trained only on the
1127
        labeled instances.
1128
    :param max_instances: The number of instances to evaluate before exiting.
1129
        If None, the evaluation will continue until the stream is empty.
1130
    :param window_size: The size of the window used for windowed evaluation,
1131
        defaults to 1000
1132
    :param initial_window_size: Not implemented yet
1133
    :param delay_length: If greater than zero the labeled (``label_probability``%)
1134
        instances will appear as unlabeled before reappearing as labeled after
1135
        ``delay_length`` instances, defaults to 0
1136
    :param label_probability: The proportion of instances that will be labeled,
1137
        must be in the range [0, 1], defaults to 0.01
1138
    :param random_seed: A random seed to define the random state that decides
1139
        which instances are labeled and which are not, defaults to 1.
1140
    :param store_predictions: Store the learner's prediction in a list, defaults
1141
        to False
1142
    :param store_y: Store the ground truth targets in a list, defaults to False
1143
    :param optimise: If True and the learner is compatible, the evaluator will
1144
        use a Java native evaluation loop, defaults to True.
1145
    :param restart_stream: If False, evaluation will continue from the current
1146
        position in the stream, defaults to True. Not restarting the stream is
1147
        useful for switching between learners or evaluators, without starting
1148
        from the beginning of the stream.
1149
    :param progress_bar: Enable, disable, or override the progress bar. Currently
1150
        incompatible with ``optimize=True``.
1151
    :return: An object containing the results of the evaluation windowed metrics,
1152
        cumulative metrics, ground truth targets, and predictions.
1153
    """
1154

1155
    if restart_stream:
1✔
1156
        stream.restart()
1✔
1157

1158
    if _is_fast_mode_compilable(stream, learner, optimise):
1✔
1159
        return _prequential_ssl_evaluation_fast(
1✔
1160
            stream,
1161
            learner,
1162
            max_instances,
1163
            window_size,
1164
            initial_window_size,
1165
            delay_length,
1166
            label_probability,
1167
            random_seed,
1168
            store_y,
1169
            store_predictions,
1170
        )
1171

1172
    # IMPORTANT: delay_length and initial_window_size have not been implemented in python yet
1173
    # In MOA it is implemented so _prequential_ssl_evaluation_fast works just fine.
1174
    if initial_window_size != 0:
1✔
UNCOV
1175
        raise ValueError(
×
1176
            "Initial window size must be 0 for this function as the feature is not implemented yet."
1177
        )
1178

1179
    if delay_length != 0:
1✔
UNCOV
1180
        raise ValueError(
×
1181
            "Delay length must be 0 for this function as the feature is not implemented yet."
1182
        )
1183

1184
    # Reset the random state
1185
    mt19937 = np.random.MT19937()
1✔
1186
    mt19937._legacy_seeding(random_seed)
1✔
1187
    rand = np.random.Generator(mt19937)
1✔
1188

1189
    predictions = None
1✔
1190
    if store_predictions:
1✔
1191
        predictions = []
1✔
1192

1193
    ground_truth_y = None
1✔
1194
    if store_y:
1✔
1195
        ground_truth_y = []
1✔
1196

1197
    # Start measuring time
1198
    start_wallclock_time, start_cpu_time = start_time_measuring()
1✔
1199

1200
    evaluator_cumulative = None
1✔
1201
    evaluator_windowed = None
1✔
1202
    if stream.get_schema().is_classification():
1✔
1203
        evaluator_cumulative = ClassificationEvaluator(
1✔
1204
            schema=stream.get_schema(), window_size=window_size
1205
        )
1206
        # If the window_size is None, then should not initialise or produce prequential (window) results.
1207
        if window_size is not None:
1✔
1208
            evaluator_windowed = ClassificationWindowedEvaluator(
1✔
1209
                schema=stream.get_schema(), window_size=window_size
1210
            )
1211
    else:
1212
        raise ValueError("The learning task is not classification")
1✔
1213

1214
    unlabeled_counter = 0
1✔
1215

1216
    progress_bar = _setup_progress_bar(
1✔
1217
        "SSL Eval", progress_bar, stream, learner, max_instances
1218
    )
1219
    for i, instance in enumerate(stream):
1✔
1220
        prediction = learner.predict(instance)
1✔
1221

1222
        if stream.get_schema().is_classification():
1✔
1223
            y = instance.y_index
1✔
1224
        else:
UNCOV
1225
            y = instance.y_value
×
1226

1227
        evaluator_cumulative.update(instance.y_index, prediction)
1✔
1228
        if evaluator_windowed is not None:
1✔
1229
            evaluator_windowed.update(instance.y_index, prediction)
1✔
1230

1231
        if rand.random(dtype=np.float64) >= label_probability:
1✔
1232
            # if 0.00 >= label_probability:
1233
            # Do not label the instance
1234
            if isinstance(learner, ClassifierSSL):
1✔
1235
                learner.train_on_unlabeled(instance)
1✔
1236
                # Otherwise, just ignore the unlabeled instance
1237
            unlabeled_counter += 1
1✔
1238
        else:
1239
            # Labeled instance
1240
            learner.train(instance)
1✔
1241

1242
        # Storing predictions if store_predictions was set to True during initialisation
1243
        if predictions is not None:
1✔
1244
            predictions.append(prediction)
1✔
1245

1246
        # Storing ground-truth if store_y was set to True during initialisation
1247
        if ground_truth_y is not None:
1✔
1248
            ground_truth_y.append(y)
1✔
1249

1250
        if progress_bar is not None:
1✔
1251
            progress_bar.update(1)
1✔
1252

1253
        if max_instances is not None and i >= (max_instances - 1):
1✔
1254
            break
1✔
1255

1256
    if progress_bar is not None:
1✔
1257
        progress_bar.close()
1✔
1258

1259
    # # Stop measuring time
1260
    elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1261
        start_wallclock_time, start_cpu_time
1262
    )
1263

1264
    # Add the results corresponding to the remainder of the stream in case the number of processed instances is not
1265
    # perfectly divisible by the window_size (if it was, then it is already in the result_windows variable).
1266
    if (
1✔
1267
        evaluator_windowed is not None
1268
        and evaluator_windowed.get_instances_seen() % window_size != 0
1269
    ):
1270
        evaluator_windowed.result_windows.append(evaluator_windowed.metrics())
1✔
1271

1272
    return PrequentialResults(
1✔
1273
        learner=str(learner),
1274
        stream=stream,
1275
        wallclock=elapsed_wallclock_time,
1276
        cpu_time=elapsed_cpu_time,
1277
        max_instances=max_instances,
1278
        cumulative_evaluator=evaluator_cumulative,
1279
        windowed_evaluator=evaluator_windowed,
1280
        ground_truth_y=np.array(ground_truth_y) if ground_truth_y else None,
1281
        predictions=np.array(predictions) if predictions else None,
1282
        other_metrics={
1283
            "unlabeled": unlabeled_counter,
1284
            "unlabeled_ratio": unlabeled_counter / i,
1285
        },
1286
    )
1287

1288

1289
def prequential_evaluation_anomaly(
1✔
1290
    stream,
1291
    learner,
1292
    max_instances=None,
1293
    window_size=1000,
1294
    optimise=True,
1295
    store_predictions=False,
1296
    store_y=False,
1297
    progress_bar: Union[bool, tqdm] = False,
1298
):
1299
    """
1300
    Calculates the metrics cumulatively (i.e. test-then-train) and in a window-fashion (i.e. windowed prequential
1301
    evaluation). Returns both evaluators so that the user has access to metrics from both evaluators.
1302

1303
    :param progress_bar: Enable, disable, or override the progress bar. Currently
1304
        incompatible with ``optimize=True``.
1305
    """
1306
    stream.restart()
1✔
1307
    if _is_fast_mode_compilable(stream, learner, optimise):
1✔
1308
        return _prequential_evaluation_anomaly_fast(
1✔
1309
            stream, learner, max_instances, window_size, store_y, store_predictions
1310
        )
1311

1312
    predictions = None
1✔
1313
    if store_predictions:
1✔
UNCOV
1314
        predictions = []
×
1315

1316
    ground_truth_y = None
1✔
1317
    if store_y:
1✔
UNCOV
1318
        ground_truth_y = []
×
1319

1320
    # Start measuring time
1321
    start_wallclock_time, start_cpu_time = start_time_measuring()
1✔
1322
    instances_processed = 1
1✔
1323

1324
    evaluator_cumulative = AnomalyDetectionEvaluator(
1✔
1325
        schema=stream.get_schema(), window_size=window_size
1326
    )
1327
    evaluator_windowed = None
1✔
1328
    if window_size is not None:
1✔
1329
        evaluator_windowed = AnomalyDetectionWindowedEvaluator(
1✔
1330
            schema=stream.get_schema(), window_size=window_size
1331
        )
1332

1333
    progress_bar = _setup_progress_bar(
1✔
1334
        "AD Eval", progress_bar, stream, learner, max_instances
1335
    )
1336
    while stream.has_more_instances() and (
1✔
1337
        max_instances is None or instances_processed <= max_instances
1338
    ):
1339
        instance = stream.next_instance()
1✔
1340
        prediction = learner.score_instance(instance)
1✔
1341
        y = instance.y_index
1✔
1342
        evaluator_cumulative.update(y, prediction)
1✔
1343
        if window_size is not None:
1✔
1344
            evaluator_windowed.update(y, prediction)
1✔
1345
        learner.train(instance)
1✔
1346

1347
        # Storing predictions if store_predictions was set to True during initialisation
1348
        if predictions is not None:
1✔
UNCOV
1349
            predictions.append(prediction)
×
1350

1351
        # Storing ground-truth if store_y was set to True during initialisation
1352
        if ground_truth_y is not None:
1✔
UNCOV
1353
            ground_truth_y.append(y)
×
1354

1355
        instances_processed += 1
1✔
1356
        if progress_bar is not None:
1✔
1357
            progress_bar.update(1)
1✔
1358

1359
    if progress_bar is not None:
1✔
1360
        progress_bar.close()
1✔
1361

1362
    # Stop measuring time
1363
    elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1364
        start_wallclock_time, start_cpu_time
1365
    )
1366

1367
    # Add the results corresponding to the remainder of the stream in case the number of processed
1368
    # instances is not perfectly divisible by the window_size (if it was, then it is already be in
1369
    # the result_windows variable). The evaluator_windowed will be None if the window_size is None.
1370
    if (
1✔
1371
        evaluator_windowed is not None
1372
        and evaluator_windowed.get_instances_seen() % window_size != 0
1373
    ):
1374
        evaluator_windowed.result_windows.append(evaluator_windowed.metrics())
1✔
1375

1376
    results = PrequentialResults(
1✔
1377
        learner=str(learner),
1378
        stream=stream,
1379
        wallclock=elapsed_wallclock_time,
1380
        cpu_time=elapsed_cpu_time,
1381
        max_instances=max_instances,
1382
        cumulative_evaluator=evaluator_cumulative,
1383
        windowed_evaluator=evaluator_windowed,
1384
        ground_truth_y=ground_truth_y,
1385
        predictions=predictions,
1386
    )
1387

1388
    return results
1✔
1389

1390

1391
##############################################################
1392
###### OPTIMISED VERSIONS (use experimental MOA method) ######
1393
##############################################################
1394

1395

1396
def _prequential_evaluation_fast(
1✔
1397
    stream,
1398
    learner,
1399
    max_instances=None,
1400
    window_size=1000,
1401
    store_y=False,
1402
    store_predictions=False,
1403
):
1404
    """
1405
    Prequential evaluation fast. This function should not be used directly, users should use prequential_evaluation.
1406
    """
1407

1408
    if not _is_fast_mode_compilable(stream, learner):
1✔
UNCOV
1409
        raise ValueError(
×
1410
            "`prequential_evaluation_fast` requires the stream object to have a`Stream.moa_stream`"
1411
        )
1412

1413
    if max_instances is None:
1✔
1414
        max_instances = -1
1✔
1415

1416
    # Start measuring time
1417
    start_wallclock_time, start_cpu_time = start_time_measuring()
1✔
1418

1419
    basic_evaluator = None
1✔
1420
    windowed_evaluator = None
1✔
1421
    if stream.get_schema().is_classification():
1✔
1422
        basic_evaluator = ClassificationEvaluator(schema=stream.get_schema())
1✔
1423
        windowed_evaluator = ClassificationWindowedEvaluator(
1✔
1424
            schema=stream.get_schema(), window_size=window_size
1425
        )
1426
    else:
1427
        # If it is not classification, could be regression or prediction interval
1428
        if not isinstance(learner, MOAPredictionIntervalLearner):
1✔
1429
            basic_evaluator = RegressionEvaluator(schema=stream.get_schema())
1✔
1430
            windowed_evaluator = RegressionWindowedEvaluator(
1✔
1431
                schema=stream.get_schema(), window_size=window_size
1432
            )
1433
        else:
UNCOV
1434
            basic_evaluator = PredictionIntervalEvaluator(schema=stream.get_schema())
×
UNCOV
1435
            windowed_evaluator = PredictionIntervalWindowedEvaluator(
×
1436
                schema=stream.get_schema(), window_size=window_size
1437
            )
1438
    moa_results = EfficientEvaluationLoops.PrequentialEvaluation(
1✔
1439
        stream.moa_stream,
1440
        learner.moa_learner,
1441
        basic_evaluator.moa_basic_evaluator,
1442
        windowed_evaluator.moa_evaluator,
1443
        max_instances,
1444
        window_size,
1445
        store_y,
1446
        store_predictions,
1447
    )
1448

1449
    # Reset the windowed_evaluator result_windows
1450
    if moa_results is not None:
1✔
1451
        windowed_evaluator.result_windows = []
1✔
1452
        if moa_results.windowedResults is not None:
1✔
1453
            for entry_idx in range(len(moa_results.windowedResults)):
1✔
1454
                windowed_evaluator.result_windows.append(
1✔
1455
                    moa_results.windowedResults[entry_idx]
1456
                )
1457

1458
    # Stop measuring time
1459
    elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1460
        start_wallclock_time, start_cpu_time
1461
    )
1462

1463
    results = PrequentialResults(
1✔
1464
        learner=str(learner),
1465
        stream=stream,
1466
        wallclock=elapsed_wallclock_time,
1467
        cpu_time=elapsed_cpu_time,
1468
        max_instances=max_instances,
1469
        cumulative_evaluator=basic_evaluator,
1470
        windowed_evaluator=windowed_evaluator,
1471
        ground_truth_y=np.array(moa_results.targets) if store_y else None,
1472
        predictions=np.array(moa_results.predictions) if store_predictions else None,
1473
    )
1474

1475
    return results
1✔
1476

1477

1478
def _prequential_ssl_evaluation_fast(
1✔
1479
    stream,
1480
    learner,
1481
    max_instances,
1482
    window_size,
1483
    initial_window_size,
1484
    delay_length,
1485
    label_probability,
1486
    random_seed,
1487
    store_y,
1488
    store_predictions,
1489
):
1490
    """
1491
    Prequential SSL evaluation fast.
1492
    """
1493
    if not _is_fast_mode_compilable(stream, learner):
1✔
UNCOV
1494
        raise ValueError(
×
1495
            "`prequential_evaluation_fast` requires the stream object to have a`Stream.moa_stream`"
1496
        )
1497

1498
    if max_instances is None:
1✔
UNCOV
1499
        max_instances = -1
×
1500

1501
    # Start measuring time
1502
    start_wallclock_time, start_cpu_time = start_time_measuring()
1✔
1503

1504
    basic_evaluator = ClassificationEvaluator(schema=stream.get_schema())
1✔
1505
    # Always create the windowed_evaluator, even if window_size is None.
1506
    # TODO: may want to avoid creating it if window_size is None.
1507
    windowed_evaluator = ClassificationWindowedEvaluator(
1✔
1508
        schema=stream.get_schema(), window_size=window_size
1509
    )
1510

1511
    # TODO: requires update to MOA to include store_y and store_predictions
1512
    moa_results = EfficientEvaluationLoops.PrequentialSSLEvaluation(
1✔
1513
        stream.moa_stream,
1514
        learner.moa_learner,
1515
        basic_evaluator.moa_basic_evaluator,
1516
        windowed_evaluator.moa_evaluator,
1517
        max_instances,
1518
        window_size,
1519
        initial_window_size,
1520
        delay_length,
1521
        label_probability,
1522
        random_seed,
1523
        True,
1524
        store_y,
1525
        store_predictions,
1526
    )
1527

1528
    # Reset the windowed_evaluator result_windows
1529
    if moa_results is not None:
1✔
1530
        windowed_evaluator.result_windows = []
1✔
1531
        if moa_results.windowedResults is not None:
1✔
1532
            for entry_idx in range(len(moa_results.windowedResults)):
1✔
1533
                windowed_evaluator.result_windows.append(
1✔
1534
                    moa_results.windowedResults[entry_idx]
1535
                )
1536

1537
    # Stop measuring time
1538
    elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1539
        start_wallclock_time, start_cpu_time
1540
    )
1541

1542
    return PrequentialResults(
1✔
1543
        learner=str(learner),
1544
        stream=stream,
1545
        wallclock=elapsed_wallclock_time,
1546
        cpu_time=elapsed_cpu_time,
1547
        max_instances=max_instances,
1548
        cumulative_evaluator=basic_evaluator,
1549
        windowed_evaluator=windowed_evaluator,
1550
        other_metrics=dict(moa_results.otherMeasurements),
1551
        ground_truth_y=np.array(moa_results.targets) if store_y else None,
1552
        predictions=np.array(moa_results.predictions) if store_predictions else None,
1553
    )
1554

1555

1556
def _prequential_evaluation_anomaly_fast(
1✔
1557
    stream,
1558
    learner,
1559
    max_instances=None,
1560
    window_size=1000,
1561
    store_y=False,
1562
    store_predictions=False,
1563
):
1564
    """
1565
    Fast prequential evaluation for Anomaly Detectors.
1566
    """
1567

1568
    predictions = None
1✔
1569
    if store_predictions:
1✔
UNCOV
1570
        predictions = []
×
1571

1572
    ground_truth_y = None
1✔
1573
    if store_y:
1✔
UNCOV
1574
        ground_truth_y = []
×
1575

1576
    if not _is_fast_mode_compilable(stream, learner):
1✔
UNCOV
1577
        raise ValueError(
×
1578
            "`prequential_evaluation_fast` requires the stream object to have a`Stream.moa_stream`"
1579
        )
1580

1581
    if max_instances is None:
1✔
1582
        max_instances = -1
1✔
1583

1584
    # Start measuring time
1585
    start_wallclock_time, start_cpu_time = start_time_measuring()
1✔
1586

1587
    basic_evaluator = None
1✔
1588
    windowed_evaluator = None
1✔
1589
    if not isinstance(learner, AnomalyDetector):
1✔
UNCOV
1590
        raise ValueError("The learner is not an AnomalyDetector")
×
1591
    basic_evaluator = AnomalyDetectionEvaluator(schema=stream.get_schema())
1✔
1592
    windowed_evaluator = AnomalyDetectionWindowedEvaluator(
1✔
1593
        schema=stream.get_schema(), window_size=window_size
1594
    )
1595

1596
    moa_results = EfficientEvaluationLoops.PrequentialEvaluation(
1✔
1597
        stream.moa_stream,
1598
        learner.moa_learner,
1599
        basic_evaluator.moa_basic_evaluator,
1600
        windowed_evaluator.moa_evaluator,
1601
        max_instances,
1602
        window_size,
1603
        store_y,
1604
        store_predictions,
1605
    )
1606

1607
    # Reset the windowed_evaluator result_windows
1608
    if moa_results is not None:
1✔
1609
        windowed_evaluator.result_windows = []
1✔
1610
        if moa_results.windowedResults is not None:
1✔
1611
            for entry_idx in range(len(moa_results.windowedResults)):
1✔
1612
                windowed_evaluator.result_windows.append(
1✔
1613
                    moa_results.windowedResults[entry_idx]
1614
                )
1615

1616
    # Stop measuring time
1617
    elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1618
        start_wallclock_time, start_cpu_time
1619
    )
1620

1621
    if store_y or store_predictions:
1✔
UNCOV
1622
        for i in range(
×
1623
            len(
1624
                moa_results.targets
1625
                if len(moa_results.targets) != 0
1626
                else moa_results.predictions
1627
            )
1628
        ):
UNCOV
1629
            if store_y:
×
UNCOV
1630
                ground_truth_y.append(moa_results.targets[i])
×
1631
            if store_predictions:
×
1632
                predictions.append(moa_results.predictions[i])
×
1633

1634
    results = PrequentialResults(
1✔
1635
        learner=str(learner),
1636
        stream=stream,
1637
        wallclock=elapsed_wallclock_time,
1638
        cpu_time=elapsed_cpu_time,
1639
        max_instances=max_instances,
1640
        cumulative_evaluator=basic_evaluator,
1641
        windowed_evaluator=windowed_evaluator,
1642
        ground_truth_y=ground_truth_y,
1643
        predictions=predictions,
1644
    )
1645

1646
    return results
1✔
1647

1648

1649
########################################################################################
1650
###### EXPERIMENTAL (optimisation to go over the data once for several learners)  ######
1651
########################################################################################
1652

1653

1654
def prequential_evaluation_multiple_learners(
1✔
1655
    stream,
1656
    learners,
1657
    max_instances=None,
1658
    window_size=1000,
1659
    store_predictions=False,
1660
    store_y=False,
1661
    progress_bar: Union[bool, tqdm] = False,
1662
):
1663
    """
1664
    Calculates the metrics cumulatively (i.e., test-then-train) and in a windowed-fashion for multiple streams and
1665
    learners. It behaves as if we invoked prequential_evaluation() multiple times, but we only iterate through the
1666
    stream once.
1667
    This function is useful in situations where iterating through the stream is costly, but we still want to assess
1668
    several learners on it.
1669
    Returns the results in a dictionary format. Infers whether it is a Classification or Regression problem based on the
1670
    stream schema.
1671

1672
    :param progress_bar: Enable, disable, or override the progress bar.
1673
    """
1674
    results = {}
1✔
1675

1676
    stream.restart()
1✔
1677

1678
    for learner_name, learner in learners.items():
1✔
1679
        predictions = [] if store_predictions else None
1✔
1680
        ground_truth_y = [] if store_y else None
1✔
1681

1682
        if stream.get_schema().is_classification():
1✔
1683
            cumulative_evaluator = ClassificationEvaluator(
1✔
1684
                schema=stream.get_schema(), window_size=window_size
1685
            )
1686
            windowed_evaluator = (
1✔
1687
                ClassificationWindowedEvaluator(
1688
                    schema=stream.get_schema(), window_size=window_size
1689
                )
1690
                if window_size is not None
1691
                else None
1692
            )
1693
        else:
UNCOV
1694
            if not isinstance(learner, MOAPredictionIntervalLearner):
×
UNCOV
1695
                cumulative_evaluator = RegressionEvaluator(
×
1696
                    schema=stream.get_schema(), window_size=window_size
1697
                )
UNCOV
1698
                windowed_evaluator = (
×
1699
                    RegressionWindowedEvaluator(
1700
                        schema=stream.get_schema(), window_size=window_size
1701
                    )
1702
                    if window_size is not None
1703
                    else None
1704
                )
1705
            else:
UNCOV
1706
                cumulative_evaluator = PredictionIntervalEvaluator(
×
1707
                    schema=stream.get_schema(), window_size=window_size
1708
                )
UNCOV
1709
                windowed_evaluator = (
×
1710
                    PredictionIntervalWindowedEvaluator(
1711
                        schema=stream.get_schema(), window_size=window_size
1712
                    )
1713
                    if window_size is not None
1714
                    else None
1715
                )
1716

1717
        results[learner_name] = {
1✔
1718
            "learner": learner,
1719
            "cumulative_evaluator": cumulative_evaluator,
1720
            "windowed_evaluator": windowed_evaluator,
1721
            "predictions": predictions,
1722
            "ground_truth_y": ground_truth_y,
1723
            "start_wallclock_time": start_time_measuring()[0],
1724
            "start_cpu_time": start_time_measuring()[1],
1725
        }
1726

1727
    instancesProcessed = 1
1✔
1728

1729
    progress_bar = resolve_progress_bar(
1✔
1730
        progress_bar, f"Eval {len(learners)} learners on {type(stream).__name__}"
1731
    )
1732
    expected_length = _get_expected_length(stream, max_instances)
1✔
1733
    if progress_bar is not None and expected_length is not None:
1✔
1734
        progress_bar.set_total(expected_length)
1✔
1735

1736
    while stream.has_more_instances() and (
1✔
1737
        max_instances is None or instancesProcessed <= max_instances
1738
    ):
1739
        instance = stream.next_instance()
1✔
1740

1741
        for learner_name, learner in learners.items():
1✔
1742
            # Predict for the current learner
1743
            prediction = learner.predict(instance)
1✔
1744

1745
            if stream.get_schema().is_classification():
1✔
1746
                y = instance.y_index
1✔
1747
            else:
UNCOV
1748
                y = instance.y_value
×
1749

1750
            results[learner_name]["cumulative_evaluator"].update(y, prediction)
1✔
1751
            if window_size is not None:
1✔
1752
                results[learner_name]["windowed_evaluator"].update(y, prediction)
1✔
1753

1754
            learner.train(instance)
1✔
1755

1756
            # Storing predictions if store_predictions was set to True during initialization
1757
            if results[learner_name]["predictions"] is not None:
1✔
UNCOV
1758
                results[learner_name]["predictions"].append(prediction)
×
1759

1760
            # Storing ground-truth if store_y was set to True during initialization
1761
            if results[learner_name]["ground_truth_y"] is not None:
1✔
UNCOV
1762
                results[learner_name]["ground_truth_y"].append(y)
×
1763

1764
        instancesProcessed += 1
1✔
1765
        if progress_bar is not None:
1✔
1766
            progress_bar.update(1)
1✔
1767

1768
    if progress_bar is not None:
1✔
1769
        progress_bar.close()
1✔
1770

1771
    # Iterate through the results of each learner and add (if needed) the last window of results to it.
1772
    if window_size is not None:
1✔
1773
        for learner_name, result in results.items():
1✔
1774
            if (
1✔
1775
                result["windowed_evaluator"] is not None
1776
                and result["windowed_evaluator"].get_instances_seen() % window_size != 0
1777
            ):
1778
                result["windowed_evaluator"].result_windows.append(
1✔
1779
                    result["windowed_evaluator"].metrics()
1780
                )
1781

1782
    # Creating PrequentialResults instances for each learner
1783
    final_results = {}
1✔
1784
    for learner_name, result in results.items():
1✔
1785
        elapsed_wallclock_time, elapsed_cpu_time = stop_time_measuring(
1✔
1786
            result["start_wallclock_time"], result["start_cpu_time"]
1787
        )
1788
        final_results[learner_name] = PrequentialResults(
1✔
1789
            learner=str(result["learner"]),
1790
            stream=stream,
1791
            wallclock=elapsed_wallclock_time,
1792
            cpu_time=elapsed_cpu_time,
1793
            max_instances=max_instances,
1794
            cumulative_evaluator=result["cumulative_evaluator"],
1795
            windowed_evaluator=result["windowed_evaluator"],
1796
            ground_truth_y=result["ground_truth_y"],
1797
            predictions=result["predictions"],
1798
        )
1799

1800
    return final_results
1✔
1801

1802

1803
def write_results_to_files(
1✔
1804
    path: str = None, results=None, file_name: str = None, directory_name: str = None
1805
):
UNCOV
1806
    if results is None:
×
UNCOV
1807
        raise ValueError("The results object is None")
×
1808

1809
    path = path if path.endswith("/") else (path + "/")
×
1810

1811
    if isinstance(results, ClassificationWindowedEvaluator) or isinstance(
×
1812
        results, RegressionWindowedEvaluator
1813
    ):
UNCOV
1814
        data = results.metrics_per_window()
×
UNCOV
1815
        data.to_csv(
×
1816
            ("./" if path is None else path)
1817
            + ("/windowed_results.csv" if file_name is None else file_name),
1818
            index=False,
1819
        )
UNCOV
1820
    elif isinstance(results, ClassificationEvaluator) or isinstance(
×
1821
        results, RegressionEvaluator
1822
    ):
UNCOV
1823
        json_str = json.dumps(results.metrics_dict())
×
UNCOV
1824
        data = json.loads(json_str)
×
1825
        with open(
×
1826
            ("./" if path is None else path)
1827
            + ("/cumulative_results.csv" if file_name is None else file_name),
1828
            "w",
1829
            newline="",
1830
        ) as csv_file:
UNCOV
1831
            writer = csv.writer(csv_file)
×
UNCOV
1832
            writer.writerow(data.keys())
×
1833
            writer.writerow(data.values())
×
1834
    elif isinstance(results, PrequentialResults):
×
1835
        directory_name = (
×
1836
            "prequential_results" if directory_name is None else directory_name
1837
        )
UNCOV
1838
        if os.path.exists(path + "/" + directory_name):
×
UNCOV
1839
            raise ValueError(
×
1840
                f"Directory {directory_name} already exists, please use another name"
1841
            )
1842
        else:
UNCOV
1843
            os.makedirs(path + "/" + directory_name)
×
1844

1845
        write_results_to_files(
×
1846
            path=path + "/" + directory_name,
1847
            file_name=file_name,
1848
            results=results.cumulative,
1849
        )
UNCOV
1850
        write_results_to_files(
×
1851
            path=path + "/" + directory_name,
1852
            file_name=file_name,
1853
            results=results.windowed,
1854
        )
1855

1856
        # If the ground truth and predictions are available, they will be writen to a file
UNCOV
1857
        if (
×
1858
            results.get_ground_truth_y() is not None
1859
            and results.get_predictions() is not None
1860
        ):
UNCOV
1861
            y_vs_predictions = {
×
1862
                "ground_truth_y": results.get_ground_truth_y(),
1863
                "predictions": results.get_predictions(),
1864
            }
UNCOV
1865
            if len(y_vs_predictions) > 0:
×
UNCOV
1866
                t_p = pd.DataFrame(y_vs_predictions)
×
1867
                t_p.to_csv(
×
1868
                    ("./" if path is None else path)
1869
                    + "/"
1870
                    + directory_name
1871
                    + "/ground_truth_y_and_predictions.csv",
1872
                    index=False,
1873
                )
1874
    else:
UNCOV
1875
        raise ValueError(
×
1876
            "Writing results to file is not supported for type "
1877
            + str(type(results))
1878
            + " yet"
1879
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc