dataeval 0.88.1__tar.gz → 0.89.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. {dataeval-0.88.1 → dataeval-0.89.0}/PKG-INFO +1 -1
  2. {dataeval-0.88.1 → dataeval-0.89.0}/pyproject.toml +1 -1
  3. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/_version.py +2 -2
  4. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/_embeddings.py +2 -2
  5. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/_metadata.py +2 -1
  6. dataeval-0.89.0/src/dataeval/detectors/drift/_base.py +351 -0
  7. dataeval-0.89.0/src/dataeval/detectors/drift/_cvm.py +105 -0
  8. dataeval-0.89.0/src/dataeval/detectors/drift/_ks.py +119 -0
  9. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_mmd.py +44 -18
  10. dataeval-0.89.0/src/dataeval/detectors/drift/_uncertainty.py +242 -0
  11. dataeval-0.89.0/src/dataeval/outputs/_drift.py +181 -0
  12. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_workflows.py +19 -5
  13. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/typing.py +23 -4
  14. dataeval-0.88.1/src/dataeval/detectors/drift/_base.py +0 -226
  15. dataeval-0.88.1/src/dataeval/detectors/drift/_cvm.py +0 -86
  16. dataeval-0.88.1/src/dataeval/detectors/drift/_ks.py +0 -91
  17. dataeval-0.88.1/src/dataeval/detectors/drift/_uncertainty.py +0 -168
  18. dataeval-0.88.1/src/dataeval/outputs/_drift.py +0 -143
  19. {dataeval-0.88.1 → dataeval-0.89.0}/.gitignore +0 -0
  20. {dataeval-0.88.1 → dataeval-0.89.0}/LICENSE +0 -0
  21. {dataeval-0.88.1 → dataeval-0.89.0}/README.md +0 -0
  22. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/__init__.py +0 -0
  23. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/_log.py +0 -0
  24. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/config.py +0 -0
  25. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/__init__.py +0 -0
  26. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/_images.py +0 -0
  27. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/_selection.py +0 -0
  28. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/_split.py +0 -0
  29. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/__init__.py +0 -0
  30. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_classbalance.py +0 -0
  31. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_classfilter.py +0 -0
  32. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_indices.py +0 -0
  33. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_limit.py +0 -0
  34. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_prioritize.py +0 -0
  35. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_reverse.py +0 -0
  36. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/data/selections/_shuffle.py +0 -0
  37. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/__init__.py +0 -0
  38. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/__init__.py +0 -0
  39. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_mvdc.py +0 -0
  40. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/__init__.py +0 -0
  41. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_base.py +0 -0
  42. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_chunk.py +0 -0
  43. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_domainclassifier.py +0 -0
  44. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_result.py +0 -0
  45. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/_nml/_thresholds.py +0 -0
  46. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/drift/updates.py +0 -0
  47. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/linters/__init__.py +0 -0
  48. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/linters/duplicates.py +0 -0
  49. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/linters/outliers.py +0 -0
  50. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/ood/__init__.py +0 -0
  51. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/ood/ae.py +0 -0
  52. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/ood/base.py +0 -0
  53. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/ood/knn.py +0 -0
  54. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/detectors/ood/mixin.py +0 -0
  55. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metadata/__init__.py +0 -0
  56. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metadata/_distance.py +0 -0
  57. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metadata/_ood.py +0 -0
  58. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metadata/_utils.py +0 -0
  59. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/__init__.py +0 -0
  60. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/bias/__init__.py +0 -0
  61. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/bias/_balance.py +0 -0
  62. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/bias/_completeness.py +0 -0
  63. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/bias/_coverage.py +0 -0
  64. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/bias/_diversity.py +0 -0
  65. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/bias/_parity.py +0 -0
  66. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/estimators/__init__.py +0 -0
  67. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_ber.py +0 -0
  68. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_clusterer.py +0 -0
  69. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_divergence.py +0 -0
  70. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/estimators/_uap.py +0 -0
  71. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/__init__.py +0 -0
  72. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_base.py +0 -0
  73. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_boxratiostats.py +0 -0
  74. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_dimensionstats.py +0 -0
  75. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_hashstats.py +0 -0
  76. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_imagestats.py +0 -0
  77. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_labelstats.py +0 -0
  78. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_pixelstats.py +0 -0
  79. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/metrics/stats/_visualstats.py +0 -0
  80. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/__init__.py +0 -0
  81. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_base.py +0 -0
  82. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_bias.py +0 -0
  83. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_estimators.py +0 -0
  84. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_linters.py +0 -0
  85. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_metadata.py +0 -0
  86. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_ood.py +0 -0
  87. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_stats.py +0 -0
  88. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/outputs/_utils.py +0 -0
  89. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/py.typed +0 -0
  90. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/__init__.py +0 -0
  91. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_array.py +0 -0
  92. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_bin.py +0 -0
  93. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_clusterer.py +0 -0
  94. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_fast_mst.py +0 -0
  95. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_image.py +0 -0
  96. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_method.py +0 -0
  97. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_mst.py +0 -0
  98. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_multiprocessing.py +0 -0
  99. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/_plot.py +0 -0
  100. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/data/__init__.py +0 -0
  101. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/data/_merge.py +0 -0
  102. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/data/_validate.py +0 -0
  103. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/data/collate.py +0 -0
  104. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/torch/__init__.py +0 -0
  105. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/torch/_blocks.py +0 -0
  106. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/torch/_gmm.py +0 -0
  107. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/torch/_internal.py +0 -0
  108. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/torch/models.py +0 -0
  109. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/utils/torch/trainer.py +0 -0
  110. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/workflows/__init__.py +0 -0
  111. {dataeval-0.88.1 → dataeval-0.89.0}/src/dataeval/workflows/sufficiency.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dataeval
3
- Version: 0.88.1
3
+ Version: 0.89.0
4
4
  Summary: DataEval provides a simple interface to characterize image data and its impact on model performance across classification and object-detection tasks
5
5
  Project-URL: Homepage, https://dataeval.ai/
6
6
  Project-URL: Repository, https://github.com/aria-ml/dataeval/
@@ -252,7 +252,7 @@ docstring-code-line-length = "dynamic"
252
252
 
253
253
  [tool.codespell]
254
254
  skip = './*env*,./output,./docs/build,./docs/source/.jupyter_cache,CHANGELOG.md,uv.lock,requirements.txt,*.html,./docs/source/*/data'
255
- ignore-words-list = ["Hart"]
255
+ ignore-words-list = ["Hart","FPR"]
256
256
 
257
257
  [build-system]
258
258
  requires = ["hatchling", "hatch-vcs"]
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.88.1'
21
- __version_tuple__ = version_tuple = (0, 88, 1)
20
+ __version__ = version = '0.89.0'
21
+ __version_tuple__ = version_tuple = (0, 89, 0)
@@ -5,7 +5,7 @@ __all__ = []
5
5
  import logging
6
6
  import math
7
7
  import os
8
- from collections.abc import Iterator, Sequence
8
+ from collections.abc import Iterable, Iterator, Sequence
9
9
  from pathlib import Path
10
10
  from typing import Any, cast
11
11
 
@@ -80,7 +80,7 @@ class Embeddings:
80
80
  # Technically more permissive than ImageClassificationDataset or ObjectDetectionDataset
81
81
  dataset: Dataset[tuple[ArrayLike, Any, Any]] | Dataset[ArrayLike],
82
82
  batch_size: int,
83
- transforms: Transform[torch.Tensor] | Sequence[Transform[torch.Tensor]] | None = None,
83
+ transforms: Transform[torch.Tensor] | Iterable[Transform[torch.Tensor]] | None = None,
84
84
  model: torch.nn.Module | None = None,
85
85
  device: DeviceLike | None = None,
86
86
  cache: Path | str | bool = False,
@@ -15,6 +15,7 @@ from tqdm.auto import tqdm
15
15
  from dataeval.typing import (
16
16
  AnnotatedDataset,
17
17
  Array,
18
+ DatumMetadata,
18
19
  ObjectDetectionTarget,
19
20
  )
20
21
  from dataeval.utils._array import as_numpy
@@ -76,7 +77,7 @@ class Metadata:
76
77
 
77
78
  def __init__(
78
79
  self,
79
- dataset: AnnotatedDataset[tuple[Any, Any, Mapping[str, Any]]],
80
+ dataset: AnnotatedDataset[tuple[Any, Any, DatumMetadata]],
80
81
  *,
81
82
  continuous_factor_bins: Mapping[str, int | Sequence[float]] | None = None,
82
83
  auto_bin_method: Literal["uniform_width", "uniform_count", "clusters"] = "uniform_width",
@@ -0,0 +1,351 @@
1
+ """
2
+ Source code derived from Alibi-Detect 0.11.4
3
+ https://github.com/SeldonIO/alibi-detect/tree/v0.11.4
4
+
5
+ Original code Copyright (c) 2023 Seldon Technologies Ltd
6
+ Licensed under Apache Software License (Apache 2.0)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __all__ = []
12
+
13
+ import math
14
+ from abc import abstractmethod
15
+ from collections.abc import Callable
16
+ from functools import wraps
17
+ from typing import Any, Literal, Protocol, TypeVar, runtime_checkable
18
+
19
+ import numpy as np
20
+ from numpy.typing import NDArray
21
+
22
+ from dataeval.data import Embeddings
23
+ from dataeval.outputs import DriftOutput
24
+ from dataeval.outputs._base import set_metadata
25
+ from dataeval.typing import Array
26
+ from dataeval.utils._array import as_numpy, flatten
27
+
28
+ R = TypeVar("R")
29
+
30
+
31
+ @runtime_checkable
32
+ class UpdateStrategy(Protocol):
33
+ """
34
+ Protocol for reference dataset update strategy for drift detectors
35
+ """
36
+
37
+ def __call__(self, x_ref: NDArray[np.float32], x_new: NDArray[np.float32], count: int) -> NDArray[np.float32]: ...
38
+
39
+
40
+ def update_strategy(fn: Callable[..., R]) -> Callable[..., R]:
41
+ """Decorator to update x_ref with x using selected update methodology"""
42
+
43
+ @wraps(fn)
44
+ def _(self: BaseDrift, data: Embeddings | Array, *args: tuple[Any, ...], **kwargs: dict[str, Any]) -> R:
45
+ output = fn(self, data, *args, **kwargs)
46
+
47
+ # update reference dataset
48
+ if self.update_strategy is not None:
49
+ self._x_ref = self.update_strategy(self.x_ref, self._encode(data), self.n)
50
+ self.n += len(data)
51
+
52
+ return output
53
+
54
+ return _
55
+
56
+
57
+ class BaseDrift:
58
+ """Base class for drift detection algorithms.
59
+
60
+ Provides common functionality for drift detectors including reference data
61
+ management, encoding of input data, and statistical correction methods.
62
+ Subclasses implement specific drift detection algorithms.
63
+
64
+ Parameters
65
+ ----------
66
+ data : Embeddings or Array
67
+ Reference dataset used as baseline for drift detection.
68
+ Can be image embeddings or raw arrays.
69
+ p_val : float, default 0.05
70
+ Significance threshold for drift detection, between 0 and 1.
71
+ Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
72
+ update_strategy : UpdateStrategy or None, default None
73
+ Strategy for updating reference data when new data arrives.
74
+ When None, reference data remains fixed throughout detection.
75
+ Default None maintains stable baseline for consistent comparison.
76
+ correction : {"bonferroni", "fdr"}, default "bonferroni"
77
+ Multiple testing correction method for multivariate drift detection.
78
+ "bonferroni" provides conservative family-wise error control.
79
+ "fdr" (False Discovery Rate) offers less conservative control.
80
+ Default "bonferroni" minimizes false positive drift detections.
81
+
82
+ Attributes
83
+ ----------
84
+ p_val : float
85
+ Significance threshold for statistical tests.
86
+ update_strategy : UpdateStrategy or None
87
+ Reference data update strategy.
88
+ correction : {"bonferroni", "fdr"}
89
+ Multiple testing correction method.
90
+ n : int
91
+ Number of samples in the reference dataset.
92
+ """
93
+
94
+ p_val: float
95
+ update_strategy: UpdateStrategy | None
96
+ correction: Literal["bonferroni", "fdr"]
97
+ n: int
98
+
99
+ def __init__(
100
+ self,
101
+ data: Embeddings | Array,
102
+ p_val: float = 0.05,
103
+ update_strategy: UpdateStrategy | None = None,
104
+ correction: Literal["bonferroni", "fdr"] = "bonferroni",
105
+ ) -> None:
106
+ # Type checking
107
+ if update_strategy is not None and not isinstance(update_strategy, UpdateStrategy):
108
+ raise ValueError("`update_strategy` is not a valid UpdateStrategy class.")
109
+ if correction not in ["bonferroni", "fdr"]:
110
+ raise ValueError("`correction` must be `bonferroni` or `fdr`.")
111
+
112
+ self._data = data
113
+ self.p_val = p_val
114
+ self.update_strategy = update_strategy
115
+ self.correction = correction
116
+ self.n = len(data)
117
+
118
+ self._x_ref: NDArray[np.float32] | None = None
119
+
120
+ @property
121
+ def x_ref(self) -> NDArray[np.float32]:
122
+ """Reference data for drift detection.
123
+
124
+ Lazily encodes the reference dataset on first access.
125
+ Data is flattened and converted to 32-bit floating point for
126
+ consistent numerical processing across different input types.
127
+
128
+ Returns
129
+ -------
130
+ NDArray[np.float32]
131
+ Reference data as flattened 32-bit floating point array.
132
+ Shape is (n_samples, n_features_flattened).
133
+
134
+ Notes
135
+ -----
136
+ Data is cached after first access to avoid repeated encoding overhead.
137
+ """
138
+ if self._x_ref is None:
139
+ self._x_ref = self._encode(self._data)
140
+ return self._x_ref
141
+
142
+ def _encode(self, data: Embeddings | Array) -> NDArray[np.float32]:
143
+ """
144
+ Encode input data to consistent numpy format.
145
+
146
+ Handles different input types (Embeddings, Arrays) and converts
147
+ them to flattened 32-bit floating point arrays for drift detection.
148
+
149
+ Parameters
150
+ ----------
151
+ data : Embeddings or Array
152
+ Input data to encode.
153
+
154
+ Returns
155
+ -------
156
+ NDArray[np.float32]
157
+ Encoded data as flattened 32-bit floating point array.
158
+ """
159
+ array = (
160
+ data.to_numpy().astype(np.float32)
161
+ if isinstance(data, Embeddings)
162
+ else self._data.new(data).to_numpy().astype(np.float32)
163
+ if isinstance(self._data, Embeddings)
164
+ else as_numpy(data).astype(np.float32)
165
+ )
166
+ return flatten(array)
167
+
168
+
169
+ class BaseDriftUnivariate(BaseDrift):
170
+ """
171
+ Base class for univariate drift detection algorithms.
172
+
173
+ Extends BaseDrift with feature-wise drift detection capabilities.
174
+ Applies statistical tests independently to each feature (pixel) and
175
+ uses multiple testing correction to control false discovery rates.
176
+
177
+ Parameters
178
+ ----------
179
+ data : Embeddings or Array
180
+ Reference dataset used as baseline for drift detection.
181
+ p_val : float, default 0.05
182
+ Significance threshold for drift detection, between 0 and 1.
183
+ Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
184
+ update_strategy : UpdateStrategy or None, default None
185
+ Strategy for updating reference data when new data arrives.
186
+ When None, reference data remains fixed throughout detection.
187
+ Default None maintains stable baseline for consistent comparison.
188
+ correction : {"bonferroni", "fdr"}, default "bonferroni"
189
+ Multiple testing correction method for controlling false positives
190
+ across multiple features. "bonferroni" divides significance level
191
+ by number of features. "fdr" uses Benjamini-Hochberg procedure.
192
+ Default "bonferroni" provides conservative family-wise error control.
193
+ n_features : int or None, default None
194
+ Number of features to analyze. When None, automatically inferred
195
+ from the first sample's flattened shape. Default None enables
196
+ automatic feature detection for flexible input handling.
197
+
198
+ Attributes
199
+ ----------
200
+ p_val : float
201
+ Significance threshold for statistical tests.
202
+ update_strategy : UpdateStrategy or None
203
+ Reference data update strategy.
204
+ correction : {"bonferroni", "fdr"}
205
+ Multiple testing correction method.
206
+ n : int
207
+ Number of samples in the reference dataset.
208
+ """
209
+
210
+ def __init__(
211
+ self,
212
+ data: Embeddings | Array,
213
+ p_val: float = 0.05,
214
+ update_strategy: UpdateStrategy | None = None,
215
+ correction: Literal["bonferroni", "fdr"] = "bonferroni",
216
+ n_features: int | None = None,
217
+ ) -> None:
218
+ super().__init__(data, p_val, update_strategy, correction)
219
+
220
+ self._n_features = n_features
221
+
222
+ @property
223
+ def n_features(self) -> int:
224
+ """Number of features in the reference data.
225
+
226
+ Lazily computes the number of features from the first data sample
227
+ if not provided during initialization. Features correspond to the
228
+ flattened dimensionality of the input data (e.g., pixels for images).
229
+
230
+ Returns
231
+ -------
232
+ int
233
+ Number of features (flattened dimensions) in the reference data.
234
+ Always > 0 for valid datasets.
235
+
236
+ Notes
237
+ -----
238
+ For image data, this equals C x H x W.
239
+ Computed once and cached for efficiency.
240
+ """
241
+ # lazy process n_features as needed
242
+ if self._n_features is None:
243
+ self._n_features = int(math.prod(self._data[0].shape))
244
+
245
+ return self._n_features
246
+
247
+ def score(self, data: Embeddings | Array) -> tuple[NDArray[np.float32], NDArray[np.float32]]:
248
+ """Calculate feature-wise p-values and test statistics.
249
+
250
+ Applies the detector's statistical test independently to each feature,
251
+ comparing the distribution of each feature between reference and test data.
252
+
253
+ Parameters
254
+ ----------
255
+ data : Embeddings or Array
256
+ Test dataset to compare against reference data.
257
+
258
+ Returns
259
+ -------
260
+ tuple[NDArray[np.float32], NDArray[np.float32]]
261
+ First array contains p-values for each feature (all between 0 and 1).
262
+ Second array contains test statistics for each feature (all >= 0).
263
+ Both arrays have shape (n_features,).
264
+
265
+ Notes
266
+ -----
267
+ Lower p-values indicate stronger evidence of drift for that feature.
268
+ Higher test statistics indicate greater distributional differences.
269
+ """
270
+ x_np = self._encode(data)
271
+ p_val = np.zeros(self.n_features, dtype=np.float32)
272
+ dist = np.zeros_like(p_val)
273
+ for f in range(self.n_features):
274
+ dist[f], p_val[f] = self._score_fn(self.x_ref[:, f], x_np[:, f])
275
+ return p_val, dist
276
+
277
+ @abstractmethod
278
+ def _score_fn(self, x: NDArray[np.float32], y: NDArray[np.float32]) -> tuple[np.float32, np.float32]: ...
279
+
280
+ def _apply_correction(self, p_vals: NDArray[np.float32]) -> tuple[bool, float]:
281
+ """
282
+ Apply multiple testing correction to feature-wise p-values.
283
+
284
+ Corrects for multiple comparisons across features to control
285
+ false positive rates. Bonferroni correction divides the significance
286
+ threshold by the number of features. FDR correction uses the
287
+ Benjamini-Hochberg procedure for less conservative control.
288
+
289
+ Parameters
290
+ ----------
291
+ p_vals : NDArray[np.float32]
292
+ Array of p-values from univariate tests for each feature.
293
+ All values should be between 0 and 1.
294
+
295
+ Returns
296
+ -------
297
+ tuple[bool, float]
298
+ Boolean indicating whether drift was detected after correction.
299
+ Float is the effective threshold used for detection.
300
+
301
+ Notes
302
+ -----
303
+ Bonferroni correction: threshold = p_val / n_features
304
+ FDR correction: Uses Benjamini-Hochberg step-up procedure
305
+ """
306
+ if self.correction == "bonferroni":
307
+ threshold = self.p_val / self.n_features
308
+ drift_pred = bool((p_vals < threshold).any())
309
+ return drift_pred, threshold
310
+ if self.correction == "fdr":
311
+ n = p_vals.shape[0]
312
+ i = np.arange(n) + np.int_(1)
313
+ p_sorted = np.sort(p_vals)
314
+ q_threshold = self.p_val * i / n
315
+ below_threshold = p_sorted < q_threshold
316
+ try:
317
+ idx_threshold = int(np.where(below_threshold)[0].max())
318
+ except ValueError: # sorted p-values not below thresholds
319
+ return bool(below_threshold.any()), q_threshold.min()
320
+ return bool(below_threshold.any()), q_threshold[idx_threshold]
321
+ raise ValueError("`correction` needs to be either `bonferroni` or `fdr`.")
322
+
323
+ @set_metadata
324
+ @update_strategy
325
+ def predict(self, data: Embeddings | Array) -> DriftOutput:
326
+ """Predict drift and update reference data using specified strategy.
327
+
328
+ Performs feature-wise drift detection, applies multiple testing
329
+ correction, and optionally updates the reference dataset based
330
+ on the configured update strategy.
331
+
332
+ Parameters
333
+ ----------
334
+ data : Embeddings or Array
335
+ Test dataset to analyze for drift against reference data.
336
+
337
+ Returns
338
+ -------
339
+ DriftOutput
340
+ Complete drift detection results including overall :term:`drift<Drift>` prediction,
341
+ corrected thresholds, feature-level analysis, and summary :term:`statistics<Statistics>`.
342
+ """
343
+
344
+ # compute drift scores
345
+ p_vals, dist = self.score(data)
346
+
347
+ feature_drift = (p_vals < self.p_val).astype(np.bool_)
348
+ drift_pred, threshold = self._apply_correction(p_vals)
349
+ return DriftOutput(
350
+ drift_pred, threshold, float(np.mean(p_vals)), float(np.mean(dist)), feature_drift, self.p_val, p_vals, dist
351
+ )
@@ -0,0 +1,105 @@
1
+ """
2
+ Source code derived from Alibi-Detect 0.11.4
3
+ https://github.com/SeldonIO/alibi-detect/tree/v0.11.4
4
+
5
+ Original code Copyright (c) 2023 Seldon Technologies Ltd
6
+ Licensed under Apache Software License (Apache 2.0)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __all__ = []
12
+
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+ from numpy.typing import NDArray
17
+ from scipy.stats import cramervonmises_2samp
18
+
19
+ from dataeval.data._embeddings import Embeddings
20
+ from dataeval.detectors.drift._base import BaseDriftUnivariate, UpdateStrategy
21
+ from dataeval.typing import Array
22
+
23
+
24
+ class DriftCVM(BaseDriftUnivariate):
25
+ """:term:`Drift` detector using the :term:`Cramér-von Mises (CVM) Test`.
26
+
27
+ Detects distributional changes in continuous data by comparing empirical
28
+ cumulative distribution functions between reference and test datasets.
29
+ For multivariate data, applies CVM test independently to each feature
30
+ and aggregates results using either the Bonferroni or
31
+ :term:`False Discovery Rate (FDR)` correction.
32
+
33
+ The CVM test is particularly effective at detecting subtle
34
+ distributional shifts throughout the entire domain, providing higher
35
+ power than Kolmogorov-Smirnov for many types of drift.
36
+
37
+ Parameters
38
+ ----------
39
+ data : Embeddings or Array
40
+ Reference dataset used as baseline distribution for drift detection.
41
+ Should represent the expected data distribution.
42
+ p_val : float, default 0.05
43
+ Significance threshold for drift detection, between 0 and 1.
44
+ Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
45
+ update_strategy : UpdateStrategy or None, default None
46
+ Strategy for updating reference data when new data arrives.
47
+ When None, reference data remains fixed throughout detection.
48
+ correction : "bonferroni" or "fdr", default "bonferroni"
49
+ Multiple testing correction method for multivariate drift detection.
50
+ "bonferroni" provides conservative family-wise error control by
51
+ dividing significance threshold by number of features.
52
+ "fdr" uses Benjamini-Hochberg procedure for less conservative control.
53
+ Default "bonferroni" minimizes false positive drift detections.
54
+ n_features : int or None, default None
55
+ Number of features to analyze in univariate tests.
56
+ When None, automatically inferred from the flattened shape of first data sample.
57
+
58
+ Example
59
+ -------
60
+ Basic drift detection with image embeddings
61
+
62
+ >>> from dataeval.data import Embeddings
63
+ >>> train_emb = Embeddings(train_images, model=encoder, batch_size=64)
64
+ >>> drift_detector = DriftCVM(train_emb)
65
+
66
+ Test incoming images for distributional drift
67
+
68
+ >>> result = drift_detector.predict(test_images)
69
+ >>> print(f"Drift detected: {result.drifted}")
70
+ Drift detected: True
71
+
72
+ >>> print(f"Mean CVM statistic: {result.distance:.4f}")
73
+ Mean CVM statistic: 24.1325
74
+
75
+ Using different correction methods
76
+
77
+ >>> drift_fdr = DriftCVM(train_emb, correction="fdr", p_val=0.1)
78
+ >>> result = drift_fdr.predict(test_images)
79
+
80
+ Access feature level results
81
+
82
+ >>> n_features = result.feature_drift
83
+ >>> print(f"Features showing drift: {n_features.sum()} / {len(n_features)}")
84
+ Features showing drift: 576 / 576
85
+ """
86
+
87
+ def __init__(
88
+ self,
89
+ data: Embeddings | Array,
90
+ p_val: float = 0.05,
91
+ update_strategy: UpdateStrategy | None = None,
92
+ correction: Literal["bonferroni", "fdr"] = "bonferroni",
93
+ n_features: int | None = None,
94
+ ) -> None:
95
+ super().__init__(
96
+ data=data,
97
+ p_val=p_val,
98
+ update_strategy=update_strategy,
99
+ correction=correction,
100
+ n_features=n_features,
101
+ )
102
+
103
+ def _score_fn(self, x: NDArray[np.float32], y: NDArray[np.float32]) -> tuple[np.float32, np.float32]:
104
+ result = cramervonmises_2samp(x, y, method="auto")
105
+ return np.float32(result.statistic), np.float32(result.pvalue)
@@ -0,0 +1,119 @@
1
+ """
2
+ Source code derived from Alibi-Detect 0.11.4
3
+ https://github.com/SeldonIO/alibi-detect/tree/v0.11.4
4
+
5
+ Original code Copyright (c) 2023 Seldon Technologies Ltd
6
+ Licensed under Apache Software License (Apache 2.0)
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ __all__ = []
12
+
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+ from numpy.typing import NDArray
17
+ from scipy.stats import ks_2samp
18
+
19
+ from dataeval.data._embeddings import Embeddings
20
+ from dataeval.detectors.drift._base import BaseDriftUnivariate, UpdateStrategy
21
+ from dataeval.typing import Array
22
+
23
+
24
+ class DriftKS(BaseDriftUnivariate):
25
+ """:term:`Drift` detector employing the :term:`Kolmogorov-Smirnov (KS) \
26
+ distribution<Kolmogorov-Smirnov (K-S) test>` test.
27
+
28
+ Detects distributional changes by measuring the maximum distance between
29
+ empirical cumulative distribution functions of reference and test datasets.
30
+ For multivariate data, applies KS test independently to each feature
31
+ and aggregates results using multiple testing correction.
32
+
33
+ The Kolmogorov-Smirnov test is particularly sensitive to differences in
34
+ the middle portions of distributions but has reduced power in the tails
35
+ where cumulative distribution functions are constrained near 0 and 1.
36
+
37
+ Parameters
38
+ ----------
39
+ data : Embeddings or Array
40
+ Reference dataset used as baseline distribution for drift detection.
41
+ Should represent the expected data distribution.
42
+ p_val : float, default 0.05
43
+ Significance threshold for drift detection, between 0 and 1.
44
+ Default 0.05 limits false drift alerts to 5% when no drift exists (Type I error rate).
45
+ update_strategy : UpdateStrategy or None, default None
46
+ Strategy for updating reference data when new data arrives.
47
+ When None, reference data remains fixed throughout detection.
48
+ correction : "bonferroni" or "fdr", default "bonferroni"
49
+ Multiple testing correction method for multivariate drift detection.
50
+ "bonferroni" provides conservative family-wise error control by
51
+ dividing significance threshold by number of features.
52
+ "fdr" uses Benjamini-Hochberg procedure for less conservative control.
53
+ Default "bonferroni" minimizes false positive drift detections.
54
+ alternative : "two-sided", "less" or "greater", default "two-sided"
55
+ Alternative hypothesis for the statistical test. "two-sided" detects
56
+ any distributional difference. "less" tests if test distribution is
57
+ stochastically smaller. "greater" tests if test distribution is
58
+ stochastically larger. Default "two-sided" provides most general
59
+ drift detection without directional assumptions.
60
+ n_features : int | None, default None
61
+ Number of features to analyze in univariate tests.
62
+ When None, automatically inferred from the flattened shape of first data sample.
63
+
64
+ Example
65
+ -------
66
+ Basic drift detection with image embeddings:
67
+
68
+ >>> from dataeval.data import Embeddings
69
+ >>> train_emb = Embeddings(train_images, model=encoder, batch_size=64)
70
+ >>> drift_detector = DriftKS(train_emb)
71
+
72
+ Test incoming images for distributional drift
73
+
74
+ >>> result = drift_detector.predict(test_images)
75
+ >>> print(f"Drift detected: {result.drifted}")
76
+ Drift detected: True
77
+
78
+ >>> print(f"Mean KS statistic: {result.distance:.4f}")
79
+ Mean KS statistic: 0.8750
80
+
81
+ Detect if test data has systematically higher values
82
+
83
+ >>> drift_greater = DriftKS(train_emb, alternative="greater")
84
+ >>> result = drift_greater.predict(test_images)
85
+
86
+ Using different correction methods
87
+
88
+ >>> drift_fdr = DriftKS(train_emb, correction="fdr", p_val=0.1)
89
+ >>> result = drift_fdr.predict(test_images)
90
+
91
+ Access feature-level results
92
+
93
+ >>> n_features = result.feature_drift
94
+ >>> print(f"Features showing drift: {n_features.sum()} / {len(n_features)}")
95
+ Features showing drift: 576 / 576
96
+ """
97
+
98
+ def __init__(
99
+ self,
100
+ data: Embeddings | Array,
101
+ p_val: float = 0.05,
102
+ update_strategy: UpdateStrategy | None = None,
103
+ correction: Literal["bonferroni", "fdr"] = "bonferroni",
104
+ alternative: Literal["two-sided", "less", "greater"] = "two-sided",
105
+ n_features: int | None = None,
106
+ ) -> None:
107
+ super().__init__(
108
+ data=data,
109
+ p_val=p_val,
110
+ update_strategy=update_strategy,
111
+ correction=correction,
112
+ n_features=n_features,
113
+ )
114
+
115
+ # Other attributes
116
+ self.alternative = alternative
117
+
118
+ def _score_fn(self, x: NDArray[np.float32], y: NDArray[np.float32]) -> tuple[np.float32, np.float32]:
119
+ return ks_2samp(x, y, alternative=self.alternative, method="exact")