Repository: alteryx/evalml (python)

Issue:
Public method to evaluate all the preprocessing steps of a pipeline
We currently define `PipelineBase._transform`--let's make that public so its easy for users to get the data which is passed to the estimator. And of course we should also add good test coverage to ensure the column names are set correctly, all the features are computed as expected.

An initial idea for a name: `compute_estimator_features`

Sidenote, once we add this, it'll be another thing we should think about as part of the DAG project #273 

Pull request:
Replace `._transform` with public method to evaluate all the preprocessing steps of a pipeline
Closes #1225


API reference here: https://evalml.alteryx.com/en/1225_public_pipeline_eval_method/generated/methods/evalml.pipelines.PipelineBase.compute_estimator_features.html#evalml.pipelines.PipelineBase.compute_estimator_features

Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 4664d4fa..dc8c1105 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -23,6 +23,7 @@ Release Notes
     * Changes
         * Added `allow_writing_files` as a named argument to CatBoost estimators. :pr:`1202`
         * Added `solver` and `multi_class` as named arguments to LogisticRegressionClassifier :pr:`1202`
+        * Replaced pipeline's `._transform` method to evaluate all the preprocessing steps of a pipeline with `.compute_estimator_features` :pr:`1231`
     * Documentation Changes
         * Included description of how to access the component instances and features for pipeline user guide :pr:`1163`
         * Updated API docs to refer to target as "target" instead of "labels" for non-classification tasks and minor docs cleanup :pr:`1160`
@@ -39,6 +40,7 @@ Release Notes
 
     **Breaking Changes**
         * DefaultDataChecks now accepts a problem_type parameter that must be specified :pr:`1167`
+        * Pipeline's `._transform` method to evaluate all the preprocessing steps of a pipeline has been replaced with `.compute_estimator_features` :pr:`1231`
 
 
 **v0.13.2 Sep. 17, 2020**
diff --git a/evalml/model_understanding/prediction_explanations/_algorithms.py b/evalml/model_understanding/prediction_explanations/_algorithms.py
index fb1863d4..01408a5d 100644
--- a/evalml/model_understanding/prediction_explanations/_algorithms.py
+++ b/evalml/model_understanding/prediction_explanations/_algorithms.py
@@ -82,7 +82,7 @@ def _compute_shap_values(pipeline, features, training_data=None):
 
         # More than 100 datapoints can negatively impact runtime according to SHAP
         # https://github.com/slundberg/shap/blob/master/shap/explainers/kernel.py#L114
-        sampled_training_data_features = pipeline._transform(shap.sample(training_data, 100))
+        sampled_training_data_features = pipeline.compute_estimator_features(shap.sample(training_data, 100))
         sampled_training_data_features = check_array(sampled_training_data_features)
 
         if pipeline.problem_type == ProblemTypes.REGRESSION:
diff --git a/evalml/model_understanding/prediction_explanations/_user_interface.py b/evalml/model_understanding/prediction_explanations/_user_interface.py
index 450055b4..e92c7e80 100644
--- a/evalml/model_understanding/prediction_explanations/_user_interface.py
+++ b/evalml/model_understanding/prediction_explanations/_user_interface.py
@@ -212,7 +212,7 @@ def _make_single_prediction_shap_table(pipeline, input_features, top_k=3, traini
     """
     if not (isinstance(input_features, pd.DataFrame) and input_features.shape[0] == 1):
         raise ValueError("features must be stored in a dataframe of one row.")
-    pipeline_features = pipeline._transform(input_features)
+    pipeline_features = pipeline.compute_estimator_features(input_features)
 
     shap_values = _compute_shap_values(pipeline, pipeline_features, training_data)
     normalized_shap_values = _normalize_shap_values(shap_values)
diff --git a/evalml/pipelines/binary_classification_pipeline.py b/evalml/pipelines/binary_classification_pipeline.py
index b2096ce4..e5aa773a 100644
--- a/evalml/pipelines/binary_classification_pipeline.py
+++ b/evalml/pipelines/binary_classification_pipeline.py
@@ -31,7 +31,7 @@ class BinaryClassificationPipeline(ClassificationPipeline):
         """
         if not isinstance(X, pd.DataFrame):
             X = pd.DataFrame(X)
-        X_t = self._transform(X)
+        X_t = self.compute_estimator_features(X)
 
         if objective is not None:
             objective = get_objective(objective, return_instance=True)
diff --git a/evalml/pipelines/classification_pipeline.py b/evalml/pipelines/classification_pipeline.py
index 9951542f..a88219a4 100644
--- a/evalml/pipelines/classification_pipeline.py
+++ b/evalml/pipelines/classification_pipeline.py
@@ -78,7 +78,7 @@ class ClassificationPipeline(PipelineBase):
         """
         if not isinstance(X, pd.DataFrame):
             X = pd.DataFrame(X)
-        X_t = self._transform(X)
+        X_t = self.compute_estimator_features(X)
         return self.estimator.predict(X_t)
 
     def predict(self, X, objective=None):
@@ -106,7 +106,7 @@ class ClassificationPipeline(PipelineBase):
         if not isinstance(X, pd.DataFrame):
             X = pd.DataFrame(X)
 
-        X = self._transform(X)
+        X = self.compute_estimator_features(X)
         proba = self.estimator.predict_proba(X)
         proba.columns = self._encoder.classes_
         return proba
diff --git a/evalml/pipelines/pipeline_base.py b/evalml/pipelines/pipeline_base.py
index ecfd3103..1163a683 100644
--- a/evalml/pipelines/pipeline_base.py
+++ b/evalml/pipelines/pipeline_base.py
@@ -175,14 +175,14 @@ class PipelineBase(ABC, metaclass=PipelineBaseMeta):
             logger.info(component_string)
             component.describe(print_name=False)
 
-    def _transform(self, X):
+    def compute_estimator_features(self, X):
         """Transforms the data by applying all pre-processing components.
 
         Arguments:
             X (pd.DataFrame): Input data to the pipeline to transform.
 
         Returns:
-            pd.DataFrame - New dataframe.
+            pd.DataFrame - New transformed features.
         """
         X_t = X
         for component in self.component_graph[:-1]:
@@ -226,7 +226,7 @@ class PipelineBase(ABC, metaclass=PipelineBaseMeta):
         if not isinstance(X, pd.DataFrame):
             X = pd.DataFrame(X)
 
-        X_t = self._transform(X)
+        X_t = self.compute_estimator_features(X)
         return self.estimator.predict(X_t)
 
     @abstractmethod


Test patch:
diff --git a/evalml/tests/model_understanding_tests/prediction_explanations_tests/test_explainers.py b/evalml/tests/model_understanding_tests/prediction_explanations_tests/test_explainers.py
index 5541dca6..40c734c1 100644
--- a/evalml/tests/model_understanding_tests/prediction_explanations_tests/test_explainers.py
+++ b/evalml/tests/model_understanding_tests/prediction_explanations_tests/test_explainers.py
@@ -159,7 +159,7 @@ def test_explain_prediction(mock_normalize_shap_values,
     pipeline.classes_ = ["class_0", "class_1", "class_2"]
 
     # By the time we call transform, we are looking at only one row of the input data.
-    pipeline._transform.return_value = pd.DataFrame({"a": [10], "b": [20], "c": [30], "d": [40]})
+    pipeline.compute_estimator_features.return_value = pd.DataFrame({"a": [10], "b": [20], "c": [30], "d": [40]})
     features = pd.DataFrame({"a": [1], "b": [2]})
     table = explain_prediction(pipeline, features, output_format=output_format, top_k=2)
 
diff --git a/evalml/tests/pipeline_tests/classification_pipeline_tests/test_binary_classification.py b/evalml/tests/pipeline_tests/classification_pipeline_tests/test_binary_classification.py
index 50f00f8d..0027375c 100644
--- a/evalml/tests/pipeline_tests/classification_pipeline_tests/test_binary_classification.py
+++ b/evalml/tests/pipeline_tests/classification_pipeline_tests/test_binary_classification.py
@@ -59,7 +59,7 @@ def test_binary_classification_pipeline_predict(mock_predict, mock_predict_proba
     mock_obj_decision.assert_called()
 
 
-@patch('evalml.pipelines.PipelineBase._transform')
+@patch('evalml.pipelines.PipelineBase.compute_estimator_features')
 def test_binary_predict_pipeline_objective_mismatch(mock_transform, X_y_binary, dummy_binary_pipeline_class):
     X, y = X_y_binary
     binary_pipeline = dummy_binary_pipeline_class(parameters={})