validmind 2.8.29__py3-none-any.whl → 2.10.0rc1__py3-none-any.whl

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 (71) hide show
  1. validmind/__init__.py +16 -5
  2. validmind/__version__.py +1 -1
  3. validmind/ai/utils.py +4 -24
  4. validmind/api_client.py +6 -17
  5. validmind/datasets/credit_risk/lending_club.py +13 -1
  6. validmind/datasets/nlp/cnn_dailymail.py +15 -1
  7. validmind/logging.py +48 -0
  8. validmind/tests/__init__.py +2 -0
  9. validmind/tests/__types__.py +18 -0
  10. validmind/tests/data_validation/ChiSquaredFeaturesTable.py +14 -2
  11. validmind/tests/data_validation/DickeyFullerGLS.py +13 -2
  12. validmind/tests/data_validation/PhillipsPerronArch.py +13 -2
  13. validmind/tests/data_validation/SeasonalDecompose.py +14 -2
  14. validmind/tests/data_validation/ShapiroWilk.py +14 -1
  15. validmind/tests/data_validation/TimeSeriesDescriptiveStatistics.py +14 -1
  16. validmind/tests/data_validation/WOEBinPlots.py +14 -1
  17. validmind/tests/data_validation/WOEBinTable.py +13 -2
  18. validmind/tests/data_validation/ZivotAndrewsArch.py +13 -2
  19. validmind/tests/data_validation/nlp/CommonWords.py +14 -2
  20. validmind/tests/data_validation/nlp/LanguageDetection.py +14 -1
  21. validmind/tests/data_validation/nlp/PolarityAndSubjectivity.py +13 -1
  22. validmind/tests/data_validation/nlp/Sentiment.py +13 -1
  23. validmind/tests/data_validation/nlp/StopWords.py +14 -2
  24. validmind/tests/data_validation/nlp/TextDescription.py +14 -2
  25. validmind/tests/data_validation/nlp/Toxicity.py +13 -1
  26. validmind/tests/model_validation/BertScore.py +13 -2
  27. validmind/tests/model_validation/BleuScore.py +13 -2
  28. validmind/tests/model_validation/ContextualRecall.py +13 -1
  29. validmind/tests/model_validation/MeteorScore.py +13 -2
  30. validmind/tests/model_validation/ModelPredictionResiduals.py +14 -1
  31. validmind/tests/model_validation/RegardScore.py +13 -2
  32. validmind/tests/model_validation/RougeScore.py +14 -1
  33. validmind/tests/model_validation/TimeSeriesPredictionWithCI.py +14 -1
  34. validmind/tests/model_validation/ToxicityScore.py +13 -1
  35. validmind/tests/model_validation/sklearn/KMeansClustersOptimization.py +14 -2
  36. validmind/tests/model_validation/sklearn/SHAPGlobalImportance.py +13 -2
  37. validmind/tests/model_validation/statsmodels/RegressionCoeffs.py +14 -2
  38. validmind/tests/ongoing_monitoring/ClassDiscriminationDrift.py +14 -1
  39. validmind/tests/ongoing_monitoring/PredictionProbabilitiesHistogramDrift.py +14 -1
  40. validmind/tests/ongoing_monitoring/ScorecardHistogramDrift.py +14 -1
  41. validmind/tests/ongoing_monitoring/TargetPredictionDistributionPlot.py +14 -1
  42. validmind/tests/output.py +9 -2
  43. validmind/tests/plots/BoxPlot.py +260 -0
  44. validmind/tests/plots/CorrelationHeatmap.py +235 -0
  45. validmind/tests/plots/HistogramPlot.py +233 -0
  46. validmind/tests/plots/ViolinPlot.py +125 -0
  47. validmind/tests/plots/__init__.py +0 -0
  48. validmind/tests/stats/CorrelationAnalysis.py +251 -0
  49. validmind/tests/stats/DescriptiveStats.py +197 -0
  50. validmind/tests/stats/NormalityTests.py +147 -0
  51. validmind/tests/stats/OutlierDetection.py +173 -0
  52. validmind/tests/stats/__init__.py +0 -0
  53. validmind/unit_metrics/classification/individual/AbsoluteError.py +42 -0
  54. validmind/unit_metrics/classification/individual/BrierScore.py +56 -0
  55. validmind/unit_metrics/classification/individual/CalibrationError.py +77 -0
  56. validmind/unit_metrics/classification/individual/ClassBalance.py +65 -0
  57. validmind/unit_metrics/classification/individual/Confidence.py +52 -0
  58. validmind/unit_metrics/classification/individual/Correctness.py +41 -0
  59. validmind/unit_metrics/classification/individual/LogLoss.py +61 -0
  60. validmind/unit_metrics/classification/individual/OutlierScore.py +86 -0
  61. validmind/unit_metrics/classification/individual/ProbabilityError.py +54 -0
  62. validmind/unit_metrics/classification/individual/Uncertainty.py +60 -0
  63. validmind/unit_metrics/classification/individual/__init__.py +0 -0
  64. validmind/vm_models/dataset/dataset.py +147 -1
  65. validmind/vm_models/result/result.py +30 -6
  66. validmind-2.10.0rc1.dist-info/METADATA +845 -0
  67. {validmind-2.8.29.dist-info → validmind-2.10.0rc1.dist-info}/RECORD +70 -49
  68. validmind-2.8.29.dist-info/METADATA +0 -137
  69. {validmind-2.8.29.dist-info → validmind-2.10.0rc1.dist-info}/LICENSE +0 -0
  70. {validmind-2.8.29.dist-info → validmind-2.10.0rc1.dist-info}/WHEEL +0 -0
  71. {validmind-2.8.29.dist-info → validmind-2.10.0rc1.dist-info}/entry_points.txt +0 -0
@@ -5,15 +5,27 @@
5
5
  import string
6
6
  from typing import Tuple
7
7
 
8
- import nltk
9
8
  import pandas as pd
10
9
  import plotly.express as px
11
10
  import plotly.graph_objects as go
12
- from nltk.corpus import stopwords
13
11
 
14
12
  from validmind import RawData, tags, tasks
13
+ from validmind.errors import MissingDependencyError
15
14
  from validmind.vm_models import VMDataset
16
15
 
16
+ try:
17
+ import nltk
18
+ from nltk.corpus import stopwords
19
+ except ImportError as e:
20
+ if "nltk" in str(e).lower():
21
+ raise MissingDependencyError(
22
+ "Missing required package `nltk` for TextDescription. "
23
+ "Please run `pip install validmind[nlp]` to use NLP tests",
24
+ required_dependencies=["nltk"],
25
+ extra="nlp",
26
+ ) from e
27
+ raise e
28
+
17
29
 
18
30
  def create_metrics_df(df, text_column, unwanted_tokens, lang):
19
31
  stop_words = set(word.lower() for word in stopwords.words(lang))
@@ -4,11 +4,23 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import evaluate
8
7
  import matplotlib.pyplot as plt
9
8
  import seaborn as sns
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
+
13
+ try:
14
+ import evaluate
15
+ except ImportError as e:
16
+ if "evaluate" in str(e):
17
+ raise MissingDependencyError(
18
+ "Missing required package `evaluate` for Toxicity. "
19
+ "Please run `pip install validmind[nlp]` to use NLP tests",
20
+ required_dependencies=["evaluate"],
21
+ extra="nlp",
22
+ ) from e
23
+ raise e
12
24
 
13
25
 
14
26
  @tags("nlp", "text_data", "data_validation")
@@ -4,14 +4,26 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import evaluate
8
7
  import pandas as pd
9
8
  import plotly.graph_objects as go
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.tests.utils import validate_prediction
13
13
  from validmind.vm_models import VMDataset, VMModel
14
14
 
15
+ try:
16
+ import evaluate
17
+ except ImportError as e:
18
+ if "evaluate" in str(e):
19
+ raise MissingDependencyError(
20
+ "Missing required package `evaluate` for BertScore. "
21
+ "Please run `pip install validmind[nlp]` to use NLP tests",
22
+ required_dependencies=["evaluate"],
23
+ extra="nlp",
24
+ ) from e
25
+ raise e
26
+
15
27
 
16
28
  @tags("nlp", "text_data", "visualization")
17
29
  @tasks("text_classification", "text_summarization")
@@ -75,7 +87,6 @@ def BertScore(
75
87
  # Ensure equal lengths and get truncated data if necessary
76
88
  y_true, y_pred = validate_prediction(y_true, y_pred)
77
89
 
78
- # Load the BERT evaluation metric
79
90
  bert = evaluate.load("bertscore")
80
91
 
81
92
  # Compute the BERT score
@@ -4,14 +4,26 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import evaluate
8
7
  import pandas as pd
9
8
  import plotly.graph_objects as go
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.tests.utils import validate_prediction
13
13
  from validmind.vm_models import VMDataset, VMModel
14
14
 
15
+ try:
16
+ import evaluate
17
+ except ImportError as e:
18
+ if "evaluate" in str(e):
19
+ raise MissingDependencyError(
20
+ "Missing required package `evaluate` for BleuScore. "
21
+ "Please run `pip install validmind[nlp]` to use NLP tests",
22
+ required_dependencies=["evaluate"],
23
+ extra="nlp",
24
+ ) from e
25
+ raise e
26
+
15
27
 
16
28
  @tags("nlp", "text_data", "visualization")
17
29
  @tasks("text_classification", "text_summarization")
@@ -70,7 +82,6 @@ def BleuScore(
70
82
  # Ensure equal lengths and get truncated data if necessary
71
83
  y_true, y_pred = validate_prediction(y_true, y_pred)
72
84
 
73
- # Load the BLEU evaluation metric
74
85
  bleu = evaluate.load("bleu")
75
86
 
76
87
  # Calculate BLEU scores
@@ -4,11 +4,23 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import nltk
8
7
  import pandas as pd
9
8
  import plotly.graph_objects as go
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
+
13
+ try:
14
+ import nltk
15
+ except ImportError as e:
16
+ if "nltk" in str(e).lower():
17
+ raise MissingDependencyError(
18
+ "Missing required package `nltk` for ContextualRecall. "
19
+ "Please run `pip install validmind[nlp]` to use NLP tests",
20
+ required_dependencies=["nltk"],
21
+ extra="nlp",
22
+ ) from e
23
+ raise e
12
24
  from validmind.tests.utils import validate_prediction
13
25
  from validmind.vm_models import VMDataset, VMModel
14
26
 
@@ -4,14 +4,26 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import evaluate
8
7
  import pandas as pd
9
8
  import plotly.graph_objects as go
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.tests.utils import validate_prediction
13
13
  from validmind.vm_models import VMDataset, VMModel
14
14
 
15
+ try:
16
+ import evaluate
17
+ except ImportError as e:
18
+ if "evaluate" in str(e):
19
+ raise MissingDependencyError(
20
+ "Missing required package `evaluate` for MeteorScore. "
21
+ "Please run `pip install validmind[nlp]` to use NLP tests",
22
+ required_dependencies=["evaluate"],
23
+ extra="nlp",
24
+ ) from e
25
+ raise e
26
+
15
27
 
16
28
  @tags("nlp", "text_data", "visualization")
17
29
  @tasks("text_classification", "text_summarization")
@@ -73,7 +85,6 @@ def MeteorScore(
73
85
 
74
86
  validate_prediction(y_true, y_pred)
75
87
 
76
- # Load the METEOR evaluation metric
77
88
  meteor = evaluate.load("meteor")
78
89
 
79
90
  # Calculate METEOR scores
@@ -6,11 +6,24 @@ from typing import Optional, Tuple
6
6
 
7
7
  import pandas as pd
8
8
  import plotly.graph_objects as go
9
- from scipy.stats import kstest
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.vm_models import VMDataset, VMModel
13
13
 
14
+ try:
15
+ from scipy.stats import kstest
16
+ except ImportError as e:
17
+ if "scipy" in str(e):
18
+ raise MissingDependencyError(
19
+ "Missing required package `scipy` for ModelPredictionResiduals. "
20
+ "Please run `pip install validmind[stats]` to use statistical tests",
21
+ required_dependencies=["scipy"],
22
+ extra="stats",
23
+ ) from e
24
+
25
+ raise e
26
+
14
27
 
15
28
  @tags("regression")
16
29
  @tasks("residual_analysis", "visualization")
@@ -4,14 +4,26 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import evaluate
8
7
  import pandas as pd
9
8
  import plotly.graph_objects as go
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.tests.utils import validate_prediction
13
13
  from validmind.vm_models import VMDataset, VMModel
14
14
 
15
+ try:
16
+ import evaluate
17
+ except ImportError as e:
18
+ if "evaluate" in str(e):
19
+ raise MissingDependencyError(
20
+ "Missing required package `evaluate` for RegardScore. "
21
+ "Please run `pip install validmind[nlp]` to use NLP tests",
22
+ required_dependencies=["evaluate"],
23
+ extra="nlp",
24
+ ) from e
25
+ raise e
26
+
15
27
 
16
28
  @tags("nlp", "text_data", "visualization")
17
29
  @tasks("text_classification", "text_summarization")
@@ -66,7 +78,6 @@ def RegardScore(
66
78
  # Ensure equal lengths and get truncated data if necessary
67
79
  y_true, y_pred = validate_prediction(y_true, y_pred)
68
80
 
69
- # Load the regard evaluation metric
70
81
  regard_tool = evaluate.load("regard", module_type="measurement")
71
82
 
72
83
  # Function to calculate regard scores
@@ -6,11 +6,24 @@ from typing import Tuple
6
6
 
7
7
  import pandas as pd
8
8
  import plotly.graph_objects as go
9
- from rouge import Rouge
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.vm_models import VMDataset, VMModel
13
13
 
14
+ try:
15
+ from rouge import Rouge
16
+ except ImportError as e:
17
+ if "rouge" in str(e):
18
+ raise MissingDependencyError(
19
+ "Missing required package `rouge` for RougeScore. "
20
+ "Please run `pip install validmind[nlp]` to use NLP tests",
21
+ required_dependencies=["rouge"],
22
+ extra="nlp",
23
+ ) from e
24
+
25
+ raise e
26
+
14
27
 
15
28
  @tags("nlp", "text_data", "visualization")
16
29
  @tasks("text_classification", "text_summarization")
@@ -7,11 +7,24 @@ from typing import Tuple
7
7
  import numpy as np
8
8
  import pandas as pd
9
9
  import plotly.graph_objects as go
10
- from scipy.stats import norm
11
10
 
12
11
  from validmind import RawData, tags, tasks
12
+ from validmind.errors import MissingDependencyError
13
13
  from validmind.vm_models import VMDataset, VMModel
14
14
 
15
+ try:
16
+ from scipy.stats import norm
17
+ except ImportError as e:
18
+ if "scipy" in str(e):
19
+ raise MissingDependencyError(
20
+ "Missing required package `scipy` for TimeSeriesPredictionWithCI. "
21
+ "Please run `pip install validmind[stats]` to use statistical tests",
22
+ required_dependencies=["scipy"],
23
+ extra="stats",
24
+ ) from e
25
+
26
+ raise e
27
+
15
28
 
16
29
  @tags("model_predictions", "visualization")
17
30
  @tasks("regression", "time_series_forecasting")
@@ -4,13 +4,25 @@
4
4
 
5
5
  from typing import Tuple
6
6
 
7
- import evaluate
8
7
  import pandas as pd
9
8
  import plotly.graph_objects as go
10
9
 
11
10
  from validmind import RawData, tags, tasks
11
+ from validmind.errors import MissingDependencyError
12
12
  from validmind.vm_models import VMDataset, VMModel
13
13
 
14
+ try:
15
+ import evaluate
16
+ except ImportError as e:
17
+ if "evaluate" in str(e):
18
+ raise MissingDependencyError(
19
+ "Missing required package `evaluate` for ToxicityScore. "
20
+ "Please run `pip install validmind[nlp]` to use NLP tests",
21
+ required_dependencies=["evaluate"],
22
+ extra="nlp",
23
+ ) from e
24
+ raise e
25
+
14
26
 
15
27
  @tags("nlp", "text_data", "visualization")
16
28
  @tasks("text_classification", "text_summarization")
@@ -7,12 +7,24 @@ from typing import List, Optional, Tuple
7
7
  import numpy as np
8
8
  import plotly.graph_objects as go
9
9
  from plotly.subplots import make_subplots
10
- from scipy.spatial.distance import cdist
11
10
  from sklearn import clone
12
11
  from sklearn.metrics import silhouette_score
13
12
 
14
13
  from validmind import RawData, tags, tasks
15
- from validmind.errors import SkipTestError
14
+ from validmind.errors import MissingDependencyError, SkipTestError
15
+
16
+ try:
17
+ from scipy.spatial.distance import cdist
18
+ except ImportError as e:
19
+ if "scipy" in str(e):
20
+ raise MissingDependencyError(
21
+ "Missing required package `scipy` for KMeansClustersOptimization. "
22
+ "Please run `pip install validmind[stats]` to use statistical tests",
23
+ required_dependencies=["scipy"],
24
+ extra="stats",
25
+ ) from e
26
+
27
+ raise e
16
28
  from validmind.vm_models import VMDataset, VMModel
17
29
 
18
30
 
@@ -9,14 +9,25 @@ from warnings import filters as _warnings_filters
9
9
  import matplotlib.pyplot as plt
10
10
  import numpy as np
11
11
  import pandas as pd
12
- import shap
13
12
 
14
13
  from validmind import RawData, tags, tasks
15
- from validmind.errors import UnsupportedModelForSHAPError
14
+ from validmind.errors import MissingDependencyError, UnsupportedModelForSHAPError
16
15
  from validmind.logging import get_logger
17
16
  from validmind.models import CatBoostModel, SKlearnModel, StatsModelsModel
18
17
  from validmind.vm_models import VMDataset, VMModel
19
18
 
19
+ try:
20
+ import shap
21
+ except ImportError as e:
22
+ if "shap" in str(e):
23
+ raise MissingDependencyError(
24
+ "Missing required package `shap` for SHAPGlobalImportance. "
25
+ "Please run `pip install validmind[explainability]` to use SHAP tests",
26
+ required_dependencies=["shap"],
27
+ extra="explainability",
28
+ ) from e
29
+ raise e
30
+
20
31
  logger = get_logger(__name__)
21
32
 
22
33
 
@@ -7,10 +7,22 @@ from typing import Tuple
7
7
 
8
8
  import pandas as pd
9
9
  import plotly.graph_objects as go
10
- from scipy import stats
11
10
 
12
11
  from validmind import RawData, tags, tasks
13
- from validmind.errors import SkipTestError
12
+ from validmind.errors import MissingDependencyError, SkipTestError
13
+
14
+ try:
15
+ from scipy import stats
16
+ except ImportError as e:
17
+ if "scipy" in str(e):
18
+ raise MissingDependencyError(
19
+ "Missing required package `scipy` for RegressionCoeffs. "
20
+ "Please run `pip install validmind[stats]` to use statistical tests",
21
+ required_dependencies=["scipy"],
22
+ extra="stats",
23
+ ) from e
24
+
25
+ raise e
14
26
  from validmind.vm_models import VMModel
15
27
 
16
28
 
@@ -6,13 +6,26 @@ from typing import Dict, List, Tuple
6
6
 
7
7
  import numpy as np
8
8
  import pandas as pd
9
- from scipy import stats
10
9
  from sklearn.metrics import roc_auc_score
11
10
  from sklearn.preprocessing import LabelBinarizer
12
11
 
13
12
  from validmind import tags, tasks
13
+ from validmind.errors import MissingDependencyError
14
14
  from validmind.vm_models import VMDataset, VMModel
15
15
 
16
+ try:
17
+ from scipy import stats
18
+ except ImportError as e:
19
+ if "scipy" in str(e):
20
+ raise MissingDependencyError(
21
+ "Missing required package `scipy` for ClassDiscriminationDrift. "
22
+ "Please run `pip install validmind[stats]` to use statistical tests",
23
+ required_dependencies=["scipy"],
24
+ extra="stats",
25
+ ) from e
26
+
27
+ raise e
28
+
16
29
 
17
30
  def multiclass_roc_auc_score(y_test, y_pred, average="macro"):
18
31
  lb = LabelBinarizer()
@@ -8,11 +8,24 @@ import numpy as np
8
8
  import pandas as pd
9
9
  import plotly.graph_objects as go
10
10
  from plotly.subplots import make_subplots
11
- from scipy import stats
12
11
 
13
12
  from validmind import RawData, tags, tasks
13
+ from validmind.errors import MissingDependencyError
14
14
  from validmind.vm_models import VMDataset, VMModel
15
15
 
16
+ try:
17
+ from scipy import stats
18
+ except ImportError as e:
19
+ if "scipy" in str(e):
20
+ raise MissingDependencyError(
21
+ "Missing required package `scipy` for PredictionProbabilitiesHistogramDrift. "
22
+ "Please run `pip install validmind[stats]` to use statistical tests",
23
+ required_dependencies=["scipy"],
24
+ extra="stats",
25
+ ) from e
26
+
27
+ raise e
28
+
16
29
 
17
30
  @tags("visualization", "credit_risk")
18
31
  @tasks("classification")
@@ -8,11 +8,24 @@ import numpy as np
8
8
  import pandas as pd
9
9
  import plotly.graph_objects as go
10
10
  from plotly.subplots import make_subplots
11
- from scipy import stats
12
11
 
13
12
  from validmind import tags, tasks
13
+ from validmind.errors import MissingDependencyError
14
14
  from validmind.vm_models import VMDataset
15
15
 
16
+ try:
17
+ from scipy import stats
18
+ except ImportError as e:
19
+ if "scipy" in str(e):
20
+ raise MissingDependencyError(
21
+ "Missing required package `scipy` for ScorecardHistogramDrift. "
22
+ "Please run `pip install validmind[stats]` to use statistical tests",
23
+ required_dependencies=["scipy"],
24
+ extra="stats",
25
+ ) from e
26
+
27
+ raise e
28
+
16
29
 
17
30
  @tags("visualization", "credit_risk", "logistic_regression")
18
31
  @tasks("classification")
@@ -7,11 +7,24 @@ from typing import Dict, List, Tuple
7
7
  import pandas as pd
8
8
  import plotly.figure_factory as ff
9
9
  import plotly.graph_objects as go
10
- from scipy.stats import kurtosis, skew
11
10
 
12
11
  from validmind import RawData, tags, tasks
12
+ from validmind.errors import MissingDependencyError
13
13
  from validmind.vm_models import VMDataset, VMModel
14
14
 
15
+ try:
16
+ from scipy.stats import kurtosis, skew
17
+ except ImportError as e:
18
+ if "scipy" in str(e):
19
+ raise MissingDependencyError(
20
+ "Missing required package `scipy` for TargetPredictionDistributionPlot. "
21
+ "Please run `pip install validmind[stats]` to use statistical tests",
22
+ required_dependencies=["scipy"],
23
+ extra="stats",
24
+ ) from e
25
+
26
+ raise e
27
+
15
28
 
16
29
  @tags("visualization")
17
30
  @tasks("monitoring")
validmind/tests/output.py CHANGED
@@ -45,7 +45,13 @@ class BooleanOutputHandler(OutputHandler):
45
45
 
46
46
  class MetricOutputHandler(OutputHandler):
47
47
  def can_handle(self, item: Any) -> bool:
48
- return isinstance(item, (int, float))
48
+ # Accept individual numbers
49
+ if isinstance(item, (int, float)):
50
+ return True
51
+ # Accept lists/arrays of numbers for per-row metrics
52
+ if isinstance(item, (list, tuple, np.ndarray)):
53
+ return all(isinstance(x, (int, float, np.number)) for x in item)
54
+ return False
49
55
 
50
56
  def process(self, item: Any, result: TestResult) -> None:
51
57
  if result.metric is not None:
@@ -169,11 +175,12 @@ def process_output(item: Any, result: TestResult) -> None:
169
175
  """Process a single test output item and update the TestResult."""
170
176
  handlers = [
171
177
  BooleanOutputHandler(),
172
- MetricOutputHandler(),
173
178
  FigureOutputHandler(),
174
179
  TableOutputHandler(),
175
180
  RawDataOutputHandler(),
176
181
  StringOutputHandler(),
182
+ # Unit metrics should be processed last
183
+ MetricOutputHandler(),
177
184
  ]
178
185
 
179
186
  for handler in handlers: