From 485dcb80a9e795af8423e0c9455b7473fc811a3e Mon Sep 17 00:00:00 2001 From: Andres Rodriguez Date: Thu, 3 Sep 2026 13:38:16 -0700 Subject: [PATCH 1/2] Fix BoxPlot KeyError when columns is not provided _validate_inputs wrapped the default columns=None into [None] before indexing the dataframe, so the None branch was unreachable and run_test("validmind.plots.BoxPlot", inputs={"dataset": ds}) raised KeyError: "None of [Index([None], dtype='object')] are in the [columns]". This broke any loop over list_tests(tags=["data_quality"]), such as the validate_application_scorecard notebook. Default to dataset.feature_columns_numeric like HistogramPlot, ViolinPlot and CorrelationHeatmap already do, and filter explicit columns against it instead of indexing the frame directly. Co-Authored-By: Claude Fable 5.1 --- tests/unit_tests/plots/__init__.py | 0 tests/unit_tests/plots/test_BoxPlot.py | 45 ++++++++++++++++++++++++++ validmind/tests/plots/BoxPlot.py | 18 +++-------- 3 files changed, 49 insertions(+), 14 deletions(-) create mode 100644 tests/unit_tests/plots/__init__.py create mode 100644 tests/unit_tests/plots/test_BoxPlot.py diff --git a/tests/unit_tests/plots/__init__.py b/tests/unit_tests/plots/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit_tests/plots/test_BoxPlot.py b/tests/unit_tests/plots/test_BoxPlot.py new file mode 100644 index 000000000..cd75cc722 --- /dev/null +++ b/tests/unit_tests/plots/test_BoxPlot.py @@ -0,0 +1,45 @@ +import unittest + +import pandas as pd +import validmind as vm +from plotly.graph_objects import Figure + +from validmind.errors import SkipTestError +from validmind.tests.plots.BoxPlot import BoxPlot + + +class TestBoxPlot(unittest.TestCase): + def setUp(self): + df = pd.DataFrame( + { + "num1": [1.0, 2.0, 3.0, 4.0], + "num2": [10, 20, 30, 40], + "cat": ["a", "b", "a", "b"], + "target": [0, 1, 0, 1], + } + ) + self.vm_dataset = vm.init_dataset( + input_id="dataset", dataset=df, target_column="target", __log=False + ) + + def test_default_columns_uses_numeric_features(self): + # Regression: columns=None used to raise KeyError("None of [Index([None]...") + fig = BoxPlot(self.vm_dataset) + self.assertIsInstance(fig, Figure) + self.assertEqual(sorted(t.name for t in fig.data), ["num1", "num2"]) + + def test_explicit_columns_filtered_to_numeric(self): + fig = BoxPlot(self.vm_dataset, columns=["num1", "cat", "missing"]) + self.assertEqual([t.name for t in fig.data], ["num1"]) + + def test_group_by(self): + fig = BoxPlot(self.vm_dataset, columns=["num1"], group_by="cat") + self.assertEqual(len(fig.data), 2) + + def test_no_numeric_columns_skips(self): + with self.assertRaises(SkipTestError): + BoxPlot(self.vm_dataset, columns=["cat"]) + + +if __name__ == "__main__": + unittest.main() diff --git a/validmind/tests/plots/BoxPlot.py b/validmind/tests/plots/BoxPlot.py index 402cdaff8..16fb479c1 100644 --- a/validmind/tests/plots/BoxPlot.py +++ b/validmind/tests/plots/BoxPlot.py @@ -4,7 +4,6 @@ from typing import List, Optional -import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots @@ -18,21 +17,12 @@ def _validate_inputs( ): """Validate inputs and return validated columns.""" - # Get dtypes without loading data into memory - if not isinstance(columns, list): - columns = [columns] - - columns_dtypes = dataset._df[columns].dtypes - - columns_numeric = [] - columns_numeric = columns_dtypes[ - columns_dtypes.apply(lambda x: pd.api.types.is_numeric_dtype(x)) - ].index.tolist() - if columns is None: - columns = columns_numeric + columns = list(dataset.feature_columns_numeric) else: - available_columns = set(columns_numeric) + if not isinstance(columns, list): + columns = [columns] + available_columns = set(dataset.feature_columns_numeric) columns = [col for col in columns if col in available_columns] if not columns: From 688490b7eebc006c9a21a2ce6e2edc134172fb43 Mon Sep 17 00:00:00 2001 From: Andres Rodriguez Date: Thu, 3 Sep 2026 13:41:51 -0700 Subject: [PATCH 2/2] 2.13.14 --- pyproject.toml | 2 +- r/validmind/DESCRIPTION | 2 +- uv.lock | 2 +- validmind/__version__.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ce818e0dd..17db15752 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "validmind" -version = "2.13.13" +version = "2.13.14" description = "ValidMind Library" readme = "README.pypi.md" requires-python = ">=3.9,<3.15" diff --git a/r/validmind/DESCRIPTION b/r/validmind/DESCRIPTION index 618acbcef..b51f38ce5 100644 --- a/r/validmind/DESCRIPTION +++ b/r/validmind/DESCRIPTION @@ -1,7 +1,7 @@ Package: validmind Type: Package Title: Interface to the 'ValidMind' Platform -Version: 2.13.13 +Version: 2.13.14 Authors@R: c(person("Andres", "Rodriguez", role = c("aut", "cre","cph"), email = "andres@validmind.ai")) Maintainer: Andres Rodriguez diff --git a/uv.lock b/uv.lock index 28242e3b4..2ebf8c8e0 100644 --- a/uv.lock +++ b/uv.lock @@ -11268,7 +11268,7 @@ wheels = [ [[package]] name = "validmind" -version = "2.13.13" +version = "2.13.14" source = { editable = "." } dependencies = [ { name = "aiohttp", extra = ["speedups"] }, diff --git a/validmind/__version__.py b/validmind/__version__.py index dd7a8b6cd..33c1e1e85 100644 --- a/validmind/__version__.py +++ b/validmind/__version__.py @@ -1 +1 @@ -__version__ = "2.13.13" +__version__ = "2.13.14"