Skip to content

Commit 95f874e

Browse files
Add ECDDWT API example
1 parent da0b243 commit 95f874e

File tree

3 files changed

+62
-17
lines changed

3 files changed

+62
-17
lines changed

frouros/detectors/concept_drift/streaming/statistical_process_control/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,8 @@ def __init__(
313313
:type average_run_length: int
314314
:param lambda_: weight given to recent data compared to older data
315315
:type lambda_: float
316+
:param warning_level: warning level value
317+
:type warning_level: float
316318
:param min_num_instances: minimum numbers of instances
317319
to start looking for changes
318320
:type min_num_instances: int

frouros/detectors/concept_drift/streaming/statistical_process_control/ecdd.py

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,41 +15,83 @@
1515
class ECDDWTConfig(BaseECDDConfig):
1616
"""ECDDWT (EWMA Concept Drift Detection Warning) [ross2012exponentially]_ configuration.
1717
18+
:param lambda_: weight given to recent data compared to older data, defaults to 0.2
19+
:type lambda_: float
20+
:param average_run_length: expected time between false positive detections [100, 400 or 1000], defaults to 400
21+
:type average_run_length: int
22+
:param warning_level: warning level value, defaults to 0.5
23+
:type warning_level: float
24+
:param min_num_instances: minimum numbers of instances to start looking for changes, defaults to 30
25+
:type min_num_instances: int
26+
1827
:References:
1928
2029
.. [ross2012exponentially] Ross, Gordon J., et al.
2130
"Exponentially weighted moving average charts for detecting concept drift."
2231
Pattern recognition letters 33.2 (2012): 191-198.
23-
"""
32+
""" # noqa: E501
33+
34+
def __init__( # noqa: D107
35+
self,
36+
lambda_: float = 0.2,
37+
average_run_length: int = 400,
38+
warning_level: float = 0.5,
39+
min_num_instances: int = 30,
40+
) -> None:
41+
super().__init__(
42+
lambda_=lambda_,
43+
average_run_length=average_run_length,
44+
warning_level=warning_level,
45+
min_num_instances=min_num_instances,
46+
)
2447

2548

2649
class ECDDWT(BaseSPC):
2750
"""ECDDWT (EWMA Concept Drift Detection Warning) [ross2012exponentially]_ detector.
2851
52+
:param config: configuration object of the detector, defaults to None. If None, the default configuration of :class:`ECDDWTConfig` is used.
53+
:type config: Optional[ECDDWTConfig]
54+
:param callbacks: callbacks, defaults to None
55+
:type callbacks: Optional[Union[BaseCallbackStreaming, List[BaseCallbackStreaming]]]
56+
57+
:Note:
58+
:func:`update` method expects to receive a value of 0 if the instance is correctly classified (no error) and 1 otherwise (error).
59+
2960
:References:
3061
3162
.. [ross2012exponentially] Ross, Gordon J., et al.
3263
"Exponentially weighted moving average charts for detecting concept drift."
3364
Pattern recognition letters 33.2 (2012): 191-198.
34-
"""
65+
66+
:Example:
67+
68+
>>> from frouros.detectors.concept_drift import ECDDWT
69+
>>> import numpy as np
70+
>>> np.random.seed(seed=31)
71+
>>> dist_a = np.random.binomial(n=1, p=0.6, size=1000)
72+
>>> dist_b = np.random.binomial(n=1, p=0.8, size=1000)
73+
>>> stream = np.concatenate((dist_a, dist_b))
74+
>>> detector = ECDDWT()
75+
>>> warning_flag = False
76+
>>> for i, value in enumerate(stream):
77+
... _ = detector.update(value=value)
78+
... if detector.drift:
79+
... print(f"Change detected at step {i}")
80+
... break
81+
... if not warning_flag and detector.warning:
82+
... print(f"Warning detected at step {i}")
83+
... warning_flag = True
84+
""" # noqa: E501
3585

3686
config_type = ECDDWTConfig # type: ignore
3787

38-
def __init__(
88+
def __init__( # noqa: D107
3989
self,
4090
config: Optional[ECDDWTConfig] = None,
4191
callbacks: Optional[
4292
Union[BaseCallbackStreaming, List[BaseCallbackStreaming]]
4393
] = None,
4494
) -> None:
45-
"""Init method.
46-
47-
:param config: configuration parameters
48-
:type config: Optional[ECDDWTConfig]
49-
:param callbacks: callbacks
50-
:type callbacks: Optional[Union[BaseCallbackStreaming,
51-
List[BaseCallbackStreaming]]]
52-
"""
5395
super().__init__(
5496
config=config, # type: ignore
5597
callbacks=callbacks,
@@ -131,11 +173,12 @@ def _update(self, value: Union[int, float], **kwargs) -> None:
131173
* error_rate_variance
132174
)
133175
control_limit = self.config.control_limit_func( # type: ignore
134-
p=self.p.mean
176+
p=self.p.mean,
135177
)
136178

137179
drift_flag = self._check_threshold(
138-
control_limit=control_limit, z_variance=z_variance
180+
control_limit=control_limit,
181+
z_variance=z_variance,
139182
)
140183

141184
if drift_flag:

frouros/utils/stats.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ def update(self, value: Union[int, float]) -> None:
125125
:type value: int
126126
:raises TypeError: Type error exception
127127
"""
128-
if not isinstance(value, (int, float)):
129-
raise TypeError("value must be of type int or float.")
128+
if not isinstance(value, (int, float, np.number)):
129+
raise TypeError("value must be of type int, float or numpy number.")
130130
element = self.queue.enqueue(value=value)
131131
self.num_values = len(self.queue)
132132
self.mean += self.incremental_op(
@@ -195,8 +195,8 @@ def update(self, value: Union[int, float]) -> None:
195195
:type value: int
196196
:raises TypeError: Type error exception
197197
"""
198-
if not isinstance(value, (int, float)):
199-
raise TypeError("value must be of type int or float.")
198+
if not isinstance(value, (int, float, np.number)):
199+
raise TypeError("value must be of type int, float or numpy number.")
200200
self.mean = self.alpha * value + self.one_minus_alpha * self.mean
201201

202202
def get(self) -> float:

0 commit comments

Comments
 (0)