You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by jl...@apache.org on 2026/06/09 17:28:40 UTC

(superset) branch master updated: fix: use pd.to_numeric in df_metrics_to_num to handle string-encoded numerics from ClickHouse (#40190)

This is an automated email from the ASF dual-hosted git repository.

sadpandajoe pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git


The following commit(s) were added to refs/heads/master by this push:
     new 6311e2c315e fix: use pd.to_numeric in df_metrics_to_num to handle string-encoded numerics from ClickHouse (#40190)
6311e2c315e is described below

commit 6311e2c315e3c45e8b1f527598531b16be83b430
Author: EMMANUELA OPURUM <86...@users.noreply.github.com>
AuthorDate: Tue Jun 9 18:28:34 2026 +0100

    fix: use pd.to_numeric in df_metrics_to_num to handle string-encoded numerics from ClickHouse (#40190)
    
    Co-authored-by: Emmanuela Opurum <yo...@example.com>
    Co-authored-by: Đỗ Trọng Hải <41...@users.noreply.github.com>
---
 superset/common/utils/dataframe_utils.py        | 10 +++++++---
 tests/unit_tests/common/test_dataframe_utils.py | 25 +++++++++++++++++++++++++
 2 files changed, 32 insertions(+), 3 deletions(-)

diff --git a/superset/common/utils/dataframe_utils.py b/superset/common/utils/dataframe_utils.py
index 89563dec06f..82f01502f83 100644
--- a/superset/common/utils/dataframe_utils.py
+++ b/superset/common/utils/dataframe_utils.py
@@ -55,9 +55,13 @@ def df_metrics_to_num(df: pd.DataFrame, query_object: QueryObject) -> None:
     """Converting metrics to numeric when pandas.read_sql cannot"""
     for col, dtype in df.dtypes.items():
         if dtype.type == np.object_ and col in query_object.metric_names:
-            # soft-convert a metric column to numeric
-            # will stay as strings if conversion fails
-            df[col] = df[col].infer_objects()
+            # soft-convert a metric column to numeric only if all
+            # non-null values look numeric (e.g. ClickHouse returns
+            # SUM() results as strings). Leaves truly non-numeric
+            # columns unchanged.
+            converted = pd.to_numeric(df[col], errors="coerce")
+            if converted.notna().eq(df[col].notna()).all():
+                df[col] = converted
 
 
 def is_datetime_series(series: Any) -> bool:
diff --git a/tests/unit_tests/common/test_dataframe_utils.py b/tests/unit_tests/common/test_dataframe_utils.py
index 01fa4224c3c..51d17f401ed 100644
--- a/tests/unit_tests/common/test_dataframe_utils.py
+++ b/tests/unit_tests/common/test_dataframe_utils.py
@@ -15,6 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 import datetime
+from unittest.mock import MagicMock
 
 import pandas as pd
 
@@ -48,3 +49,27 @@ def test_is_datetime_series():
             datetime.datetime(2018, 1, 1), datetime.datetime(2018, 2, 1)
         ).to_series()
     )
+
+
+def test_df_metrics_to_num_converts_string_numerics():
+    """Test that string-encoded numeric columns (e.g. from ClickHouse) are converted."""
+    query_object = MagicMock()
+    query_object.metric_names = ["sum_col", "mixed_col", "text_col"]
+    df = pd.DataFrame(
+        {
+            "sum_col": pd.Series(["100", "200", "300"], dtype=object),
+            "mixed_col": pd.Series(["1", "not_a_number", "3"], dtype=object),
+            "text_col": pd.Series(["foo", "bar", "baz"], dtype=object),
+            "dim_col": pd.Series(["a", "b", "c"], dtype=object),
+        }
+    )
+    dataframe_utils.df_metrics_to_num(df, query_object)
+    # sum_col: all numeric strings -> should be converted to numeric
+    assert pd.api.types.is_numeric_dtype(df["sum_col"]), "sum_col should be numeric"
+    assert df["sum_col"].tolist() == [100.0, 200.0, 300.0]
+    # mixed_col: has non-numeric value -> should NOT be converted
+    assert df["mixed_col"].dtype == object, "mixed_col should remain object"
+    # text_col: all non-numeric -> should NOT be converted
+    assert df["text_col"].dtype == object, "text_col should remain object"
+    # dim_col: not in metric_names -> should NOT be touched
+    assert df["dim_col"].dtype == object, "dim_col should not be touched"