Repository: onnx/sklearn-onnx

Issue:
CastTransformer does not work with dataframes



Discussion:

Resolving patch:
diff --git a/skl2onnx/operator_converters/calibrated_classifier_cv.py b/skl2onnx/operator_converters/calibrated_classifier_cv.py
--- a/skl2onnx/operator_converters/calibrated_classifier_cv.py
+++ b/skl2onnx/operator_converters/calibrated_classifier_cv.py
@@ -3,7 +3,7 @@
 # Licensed under the MIT License. See License.txt in the project root for
 # license information.
 # --------------------------------------------------------------------------
-
+import pprint
 import numpy as np
 from ..proto import onnx_proto
 from ..common._apply_operation import (
@@ -116,12 +116,14 @@ def _transform_isotonic(scope, container, model, T, k):
         atX, atY = '_X_', '_y_'
     elif hasattr(model.calibrators_[k], '_necessary_X_'):
         atX, atY = '_necessary_X_', '_necessary_y_'
+    elif hasattr(model.calibrators_[k], 'X_thresholds_'):
+        atX, atY = 'X_thresholds_', 'y_thresholds_'
     else:
         raise AttributeError(
             "Unable to find attribute '_X_' or '_necessary_X_' "
             "for type {}\n{}."
             "".format(type(model.calibrators_[k]),
-                      dir(model.calibrators_[k])))
+                      pprint.pformat(dir(model.calibrators_[k]))))
 
     container.add_initializer(
         calibrator_x_name, onnx_proto.TensorProto.FLOAT,
diff --git a/skl2onnx/sklapi/cast_transformer.py b/skl2onnx/sklapi/cast_transformer.py
--- a/skl2onnx/sklapi/cast_transformer.py
+++ b/skl2onnx/sklapi/cast_transformer.py
@@ -43,7 +43,13 @@ def __init__(self, *, dtype=np.float32):
 
     def _cast(self, a, name):
         if not isinstance(a, np.ndarray):
-            raise TypeError("{} must be a numpy array.".format(name))
+            if hasattr(a, 'values') and hasattr(a, 'iloc'):
+                # dataframe
+                a = a.values
+            else:
+                raise TypeError(
+                    "{} must be a numpy array or a dataframe.".format(
+                        name))
         try:
             a2 = a.astype(self.dtype)
         except ValueError:


Test patch:
diff --git a/tests/test_sklearn_cast_transformer.py b/tests/test_sklearn_cast_transformer.py
--- a/tests/test_sklearn_cast_transformer.py
+++ b/tests/test_sklearn_cast_transformer.py
@@ -5,12 +5,17 @@
 import math
 from distutils.version import StrictVersion
 import numpy
+from pandas import DataFrame
 from onnxruntime import InferenceSession, __version__ as ort_version
 from sklearn.datasets import make_regression
 from sklearn.model_selection import train_test_split
 from sklearn.pipeline import Pipeline
 from sklearn.preprocessing import StandardScaler
 from sklearn.tree import DecisionTreeRegressor
+try:
+    from sklearn.compose import ColumnTransformer
+except ImportError:
+    ColumnTransformer = None
 from skl2onnx.sklapi import CastTransformer
 from skl2onnx import convert_sklearn, to_onnx
 from skl2onnx.common.data_types import (
@@ -25,7 +30,8 @@ def common_test_cast_transformer(self, dtype, input_type):
             ('cast', CastTransformer(dtype=dtype)),
             ('invcast', CastTransformer(dtype=numpy.float32)),
         ])
-        data = numpy.array([[0, 0, 3], [1, 1, 0], [0, 2, 1], [1, 0, 2]],
+        data = numpy.array([[0.1, 0.2, 3.1], [1, 1, 0],
+                            [0, 2, 1], [1, 0, 2]],
                            dtype=numpy.float32)
         model.fit(data)
         pred = model.steps[0][1].transform(data)
@@ -110,6 +116,29 @@ def maxdiff(a1, a2):
         assert md2 <= md1
         assert md2 <= 0.01
 
+    @unittest.skipIf(ColumnTransformer is None,
+                     reason="scikit-learn too old")
+    def test_cast_transformer_dataframe(self):
+        model = Pipeline([
+            ('prep', ColumnTransformer([
+                ('prep1', CastTransformer(), [0, 1]),
+                ('prep2', CastTransformer(), [2]),
+            ])),
+            ('invcast', CastTransformer(dtype=numpy.float32)),
+        ])
+        data = numpy.array([[0.1, 0.2, 3.4], [1, 1, 0],
+                            [0, 2, 1], [1, 0, 2]],
+                           dtype=numpy.float32)
+        data = DataFrame(data)
+        model.fit(data)
+        model_onnx = convert_sklearn(
+            model, "cast", [("input", FloatTensorType([None, 3]))],
+            target_opset=TARGET_OPSET)
+        self.assertTrue(model_onnx is not None)
+        dump_data_and_model(
+            data.values, model, model_onnx,
+            basename="SklearnCastTransformerCT")
+
 
 if __name__ == "__main__":
     unittest.main()