Repository: alteryx/evalml (python)
Issue: explain_predictions_best_worst does not support custom index in the target
Found during the blitz.

Repro:

```python
from evalml.demos import load_breast_cancer
from evalml.pipelines import BinaryClassificationPipeline
from evalml.model_understanding import explain_predictions_best_worst, explain_predictions

class RfPipeline(BinaryClassificationPipeline):
    component_graph = ["Random Forest Classifier"]

X, y = load_breast_cancer()
y.index = range(100, 100 + len(y))

pl = RfPipeline({})

explain_predictions_best_worst(pl, X, y)
```
Pull request description: explain_predictions_best_worst custom index bug fix
### Pull Request Description
Fixes #1317 

-----
*After creating the pull request: in order to pass the **release_notes_updated** check you will need to update the "Future Release" section of* `docs/source/release_notes.rst` *to include this pull request by adding :pr:`123`.*


Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index 1b04c7e4..16f69c66 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -13,6 +13,7 @@ Release Notes
         * Added `PCA Transformer` component for dimensionality reduction :pr:`1270`
     * Fixes
         * Fixed ML performance issue with ordered datasets: always shuffle data in automl's default CV splits :pr:`1265`
+        * Fixed bug in ``explain_predictions_best_worst`` where a custom index in the target variable would cause a ``ValueError`` :pr:`1318`
     * Changes
         * Allow ``add_to_rankings`` to be called before AutoMLSearch is called :pr:`1250`
     * Documentation Changes
diff --git a/evalml/model_understanding/prediction_explanations/_user_interface.py b/evalml/model_understanding/prediction_explanations/_user_interface.py
index e92c7e80..07b454d7 100644
--- a/evalml/model_understanding/prediction_explanations/_user_interface.py
+++ b/evalml/model_understanding/prediction_explanations/_user_interface.py
@@ -287,12 +287,12 @@ class _ClassificationPredictedValues(_SectionMaker):
         pred_value = [f"{col_name}: {pred}" for col_name, pred in
                       zip(y_pred.columns, round(y_pred.iloc[index], 3).tolist())]
         pred_value = "[" + ", ".join(pred_value) + "]"
-        true_value = y_true[index]
+        true_value = y_true.iloc[index]
 
         return [f"\t\tPredicted Probabilities: {pred_value}\n",
                 f"\t\tPredicted Value: {self.predicted_values[index]}\n",
                 f"\t\tTarget Value: {true_value}\n",
-                f"\t\t{self.error_name}: {round(scores[index], 3)}\n\n"]
+                f"\t\t{self.error_name}: {round(scores.iloc[index], 3)}\n\n"]
 
     def make_dict(self, index, y_pred, y_true, scores):
         """Makes the predicted values section for classification problem best/worst reports formatted as dictionary."""
@@ -300,9 +300,9 @@ class _ClassificationPredictedValues(_SectionMaker):
 
         return {"probabilities": pred_values,
                 "predicted_value": _make_json_serializable(self.predicted_values[index]),
-                "target_value": _make_json_serializable(y_true[index]),
+                "target_value": _make_json_serializable(y_true.iloc[index]),
                 "error_name": self.error_name,
-                "error_value": _make_json_serializable(scores[index])}
+                "error_value": _make_json_serializable(scores.iloc[index])}
 
 
 class _RegressionPredictedValues(_SectionMaker):
@@ -315,14 +315,14 @@ class _RegressionPredictedValues(_SectionMaker):
     def make_text(self, index, y_pred, y_true, scores):
         """Makes the predicted values section for regression problem best/worst reports formatted as text."""
         return [f"\t\tPredicted Value: {round(y_pred.iloc[index], 3)}\n",
-                f"\t\tTarget Value: {round(y_true[index], 3)}\n",
-                f"\t\t{self.error_name}: {round(scores[index], 3)}\n\n"]
+                f"\t\tTarget Value: {round(y_true.iloc[index], 3)}\n",
+                f"\t\t{self.error_name}: {round(scores.iloc[index], 3)}\n\n"]
 
     def make_dict(self, index, y_pred, y_true, scores):
         """Makes the predicted values section for regression problem best/worst reports formatted as a dictionary."""
         return {"probabilities": None, "predicted_value": round(y_pred.iloc[index], 3),
-                "target_value": round(y_true[index], 3), "error_name": self.error_name,
-                "error_value": round(scores[index], 3)}
+                "target_value": round(y_true.iloc[index], 3), "error_name": self.error_name,
+                "error_value": round(scores.iloc[index], 3)}
 
 
 class _SHAPTable(_SectionMaker):


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 40c734c1..d08362ba 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
@@ -393,23 +393,36 @@ multiclass_no_best_worst_answer = """Test Pipeline Name
 """.format(multiclass_table=multiclass_table)
 
 
-@pytest.mark.parametrize("problem_type,output_format,answer,explain_predictions_answer",
-                         [(ProblemTypes.REGRESSION, "text", regression_best_worst_answer, no_best_worst_answer),
-                          (ProblemTypes.REGRESSION, "dict", regression_best_worst_answer_dict, no_best_worst_answer_dict),
-                          (ProblemTypes.BINARY, "text", binary_best_worst_answer, no_best_worst_answer),
-                          (ProblemTypes.BINARY, "dict", binary_best_worst_answer_dict, no_best_worst_answer_dict),
-                          (ProblemTypes.MULTICLASS, "text", multiclass_best_worst_answer, multiclass_no_best_worst_answer),
-                          (ProblemTypes.MULTICLASS, "dict", multiclass_best_worst_answer_dict, no_best_worst_answer_dict)])
+@pytest.mark.parametrize("problem_type,output_format,answer,explain_predictions_answer,custom_index",
+                         [(ProblemTypes.REGRESSION, "text", regression_best_worst_answer, no_best_worst_answer, [0, 1]),
+                          (ProblemTypes.REGRESSION, "dict", regression_best_worst_answer_dict, no_best_worst_answer_dict, [0, 1]),
+                          (ProblemTypes.REGRESSION, "text", regression_best_worst_answer, no_best_worst_answer, [4, 23]),
+                          (ProblemTypes.REGRESSION, "dict", regression_best_worst_answer_dict, no_best_worst_answer_dict, [4, 10]),
+                          (ProblemTypes.REGRESSION, "text", regression_best_worst_answer, no_best_worst_answer, ["foo", "bar"]),
+                          (ProblemTypes.REGRESSION, "dict", regression_best_worst_answer_dict, no_best_worst_answer_dict, ["foo", "bar"]),
+                          (ProblemTypes.BINARY, "text", binary_best_worst_answer, no_best_worst_answer, [0, 1]),
+                          (ProblemTypes.BINARY, "dict", binary_best_worst_answer_dict, no_best_worst_answer_dict, [0, 1]),
+                          (ProblemTypes.BINARY, "text", binary_best_worst_answer, no_best_worst_answer, [7, 11]),
+                          (ProblemTypes.BINARY, "dict", binary_best_worst_answer_dict, no_best_worst_answer_dict, [7, 11]),
+                          (ProblemTypes.BINARY, "text", binary_best_worst_answer, no_best_worst_answer, ["first", "second"]),
+                          (ProblemTypes.BINARY, "dict", binary_best_worst_answer_dict, no_best_worst_answer_dict, ["first", "second"]),
+                          (ProblemTypes.MULTICLASS, "text", multiclass_best_worst_answer, multiclass_no_best_worst_answer, [0, 1]),
+                          (ProblemTypes.MULTICLASS, "dict", multiclass_best_worst_answer_dict, no_best_worst_answer_dict, [0, 1]),
+                          (ProblemTypes.MULTICLASS, "text", multiclass_best_worst_answer, multiclass_no_best_worst_answer, [19, 103]),
+                          (ProblemTypes.MULTICLASS, "dict", multiclass_best_worst_answer_dict, no_best_worst_answer_dict, [17, 235]),
+                          (ProblemTypes.MULTICLASS, "text", multiclass_best_worst_answer, multiclass_no_best_worst_answer, ["2020-10", "2020-11"]),
+                          (ProblemTypes.MULTICLASS, "dict", multiclass_best_worst_answer_dict, no_best_worst_answer_dict, ["2020-15", "2020-15"])
+                          ])
 @patch("evalml.model_understanding.prediction_explanations.explainers.DEFAULT_METRICS")
 @patch("evalml.model_understanding.prediction_explanations._user_interface._make_single_prediction_shap_table")
 def test_explain_predictions_best_worst_and_explain_predictions(mock_make_table, mock_default_metrics,
                                                                 problem_type, output_format, answer,
-                                                                explain_predictions_answer):
+                                                                explain_predictions_answer, custom_index):
 
     mock_make_table.return_value = "table goes here" if output_format == "text" else {"explanations": ["explanation_dictionary_goes_here"]}
     pipeline = MagicMock()
     pipeline.parameters = "Parameters go here"
-    input_features = pd.DataFrame({"a": [3, 4]})
+    input_features = pd.DataFrame({"a": [3, 4]}, index=custom_index)
     pipeline.problem_type = problem_type
     pipeline.name = "Test Pipeline Name"
 
@@ -418,7 +431,7 @@ def test_explain_predictions_best_worst_and_explain_predictions(mock_make_table,
         abs_error_mock.return_value = pd.Series([4, 1], dtype="int")
         mock_default_metrics.__getitem__.return_value = abs_error_mock
         pipeline.predict.return_value = pd.Series([2, 1])
-        y_true = pd.Series([3, 2])
+        y_true = pd.Series([3, 2], index=custom_index)
     elif problem_type == ProblemTypes.BINARY:
         pipeline.classes_.return_value = ["benign", "malignant"]
         cross_entropy_mock = MagicMock(__name__="cross_entropy")
@@ -426,7 +439,7 @@ def test_explain_predictions_best_worst_and_explain_predictions(mock_make_table,
         cross_entropy_mock.return_value = pd.Series([0.2, 0.78])
         pipeline.predict_proba.return_value = pd.DataFrame({"benign": [0.05, 0.1], "malignant": [0.95, 0.9]})
         pipeline.predict.return_value = pd.Series(["malignant"] * 2)
-        y_true = pd.Series(["malignant", "benign"])
+        y_true = pd.Series(["malignant", "benign"], index=custom_index)
     else:
         # Multiclass text output is formatted slightly different so need to account for that
         if output_format == "text":
@@ -438,7 +451,7 @@ def test_explain_predictions_best_worst_and_explain_predictions(mock_make_table,
         pipeline.predict_proba.return_value = pd.DataFrame({"setosa": [0.8, 0.2], "versicolor": [0.1, 0.75],
                                                             "virginica": [0.1, 0.05]})
         pipeline.predict.return_value = ["setosa", "versicolor"]
-        y_true = pd.Series(["setosa", "versicolor"])
+        y_true = pd.Series(["setosa", "versicolor"], index=custom_index)
 
     best_worst_report = explain_predictions_best_worst(pipeline, input_features, y_true=y_true,
                                                        num_to_explain=1, output_format=output_format)