Repository: alteryx/woodwork (python)

Issue:
Make box plot low/high indices/values optional to return
In a series with many outliers, returning all of the low and high indices and the low and high values can make the results of `box_plot_dict` difficult to read, as the low bound, the high bound, and the quantiles are such small parts of the output in that case.

We should add a parameter to `series.ww.box_plot_dict` that makes the inclusion of `low_indices`, `high_indices`, `low_values`, and `high_values` in the resulting dictionary optional, defaulting to including them in the returned dictionary.

```
# To avoid getting the indices and values returned set the parameter to False
series.ww.box_plot_dict(include_indices_and_values=False)
# defaults to True - if no parameter is passed in, returns the indices and values
series.ww.box_plot_dict()
```

Pull request:
Make box plot low/high indices/values optional to return
- Adds a parameter to `box_plot_dict` that determines whether the lists of outlier values and their indices are included in the returned dictionary
- Closes #1168 

Patch:
diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst
index cc04524..4d72b0d 100644
--- a/docs/source/release_notes.rst
+++ b/docs/source/release_notes.rst
@@ -9,6 +9,7 @@ Future Release
         * Set a maximum version for pyspark until we understand why :pr:`1169` failed (:pr:`1179`)
         * Require newer dask version (:pr:`1180`)
     * Changes
+        * Make box plot low/high indices/values optional to return in ``box_plot_dict`` (:pr:`1184`)
     * Documentation Changes
         * Update docs dependencies (:pr:`1176`)
     * Testing Changes
@@ -16,7 +17,7 @@ Future Release
         * Updated notebook standardizer to standardize python versions (:pr:`1166`)
 
     Thanks to the following people for contributing to this release:
-    :user:`davesque`, :user:`gsheni`, :user:`bchen1116`, :user:`rwedge`
+    :user:`davesque`, :user:`gsheni`, :user:`bchen1116`, :user:`rwedge`, :user:`tamargrey`
 
 v0.8.2 Oct 12, 2021
 ===================
diff --git a/woodwork/column_accessor.py b/woodwork/column_accessor.py
index 8ca4e22..f341bf0 100644
--- a/woodwork/column_accessor.py
+++ b/woodwork/column_accessor.py
@@ -393,7 +393,7 @@ class WoodworkColumnAccessor:
         self._schema._set_semantic_tags(semantic_tags)
 
     @_check_column_schema
-    def box_plot_dict(self, quantiles=None):
+    def box_plot_dict(self, quantiles=None, include_indices_and_values=True):
         """Gets the information necessary to create a box and whisker plot with outliers for a numeric column
         using the IQR method.
 
@@ -401,6 +401,9 @@ class WoodworkColumnAccessor:
             quantiles (dict[float -> float], optional): A dictionary containing the quantiles for the data
                 where the key indicates the quantile, and the value is the quantile's value for the data. If
                 no qantiles are provided, they will be computed from the data.
+            include_indices_and_values (bool, optional): Whether or not the lists containing individual
+                outlier values and their indices will be included in the returned dictionary.
+                Defaults to True.
 
         Note:
             The minimum quantiles necessary for outlier detection using the IQR method are the
@@ -417,12 +420,20 @@ class WoodworkColumnAccessor:
                 - quantiles (list[float]): the quantiles used to determine the bounds.
                     If quantiles were passed in, will contain all quantiles passed in. Otherwise, contains the five
                     quantiles {0.0, 0.25, 0.5, 0.75, 1.0}.
-                - low_values (list[float, int]): the values of the lower outliers
-                - high_values (list[float, int]): the values of the upper outliers
-                - low_indices (list[int]): the corresponding index values for each of the lower outliers
-                - high_indices (list[int]): the corresponding index values for each of the upper outliers
+                - low_values (list[float, int], optional): the values of the lower outliers.
+                    Will not be included if ``include_indices_and_values`` is False.
+                - high_values (list[float, int], optional): the values of the upper outliers
+                    Will not be included if ``include_indices_and_values`` is False.
+                - low_indices (list[int], optional): the corresponding index values for each of the lower outliers
+                    Will not be included if ``include_indices_and_values`` is False.
+                - high_indices (list[int], optional): the corresponding index values for each of the upper outliers
+                    Will not be included if ``include_indices_and_values`` is False.
         """
-        return _get_box_plot_info_for_column(self._series, quantiles=quantiles)
+        return _get_box_plot_info_for_column(
+            self._series,
+            quantiles=quantiles,
+            include_indices_and_values=include_indices_and_values,
+        )
 
 
 def _validate_schema(schema, series):
diff --git a/woodwork/statistics_utils.py b/woodwork/statistics_utils.py
index 3190972..0a74f67 100644
--- a/woodwork/statistics_utils.py
+++ b/woodwork/statistics_utils.py
@@ -428,7 +428,9 @@ def _get_value_counts(dataframe, ascending=False, top_n=10, dropna=False):
     return val_counts
 
 
-def _get_box_plot_info_for_column(series, quantiles=None):
+def _get_box_plot_info_for_column(
+    series, quantiles=None, include_indices_and_values=True
+):
     """Gets the information necessary to create a box and whisker plot with outliers for a numeric column
         using the IQR method.
 
@@ -437,6 +439,9 @@ def _get_box_plot_info_for_column(series, quantiles=None):
             Will be used to calculate quantiles if none are provided.
         quantiles (dict[float -> float], optional): A dictionary containing the quantiles for the data
             where the key indicates the quantile, and the value is the quantile's value for the data.
+        include_indices_and_values (bool, optional): Whether or not the lists containing individual
+            outlier values and their indices will be included in the returned dictionary.
+            Defaults to True.
 
     Note:
         The minimum quantiles necessary for outlier detection using the IQR method are the
@@ -453,10 +458,14 @@ def _get_box_plot_info_for_column(series, quantiles=None):
             - quantiles (list[float]): the quantiles used to determine the bounds.
                 If quantiles were passed in, will contain all quantiles passed in. Otherwise, contains the five
                 quantiles {0.0, 0.25, 0.5, 0.75, 1.0}.
-            - low_values (list[float, int]): the values of the lower outliers
-            - high_values (list[float, int]): the values of the upper outliers
-            - low_indices (list[int]): the corresponding index values for each of the lower outliers
-            - high_indices (list[int]): the corresponding index values for each of the upper outliers
+            - low_values (list[float, int], optional): the values of the lower outliers.
+                Will not be included if ``include_indices_and_values`` is False.
+            - high_values (list[float, int], optional): the values of the upper outliers
+                Will not be included if ``include_indices_and_values`` is False.
+            - low_indices (list[int], optional): the corresponding index values for each of the lower outliers
+                Will not be included if ``include_indices_and_values`` is False.
+            - high_indices (list[int], optional): the corresponding index values for each of the upper outliers
+                Will not be included if ``include_indices_and_values`` is False.
     """
     if not series.ww._schema.is_numeric:
         raise TypeError("Cannot calculate box plot statistics for non-numeric column")
@@ -472,8 +481,16 @@ def _get_box_plot_info_for_column(series, quantiles=None):
     # remove null values from the data
     series = series.dropna()
 
+    outliers_dict = {}
     # An empty or fully null Series has no outliers, bounds, or quantiles
     if series.shape[0] == 0:
+        if include_indices_and_values:
+            outliers_dict = {
+                "low_values": [],
+                "high_values": [],
+                "low_indices": [],
+                "high_indices": [],
+            }
         return {
             "low_bound": np.nan,
             "high_bound": np.nan,
@@ -484,10 +501,7 @@ def _get_box_plot_info_for_column(series, quantiles=None):
                 0.75: np.nan,
                 1.0: np.nan,
             },
-            "low_values": [],
-            "high_values": [],
-            "low_indices": [],
-            "high_indices": [],
+            **outliers_dict,
         }
 
     # calculate the outlier bounds using IQR
@@ -507,26 +521,32 @@ def _get_box_plot_info_for_column(series, quantiles=None):
     low_bound = q1 - (iqr * 1.5)
     high_bound = q3 + (iqr * 1.5)
 
-    # identify outliers in the series
-    min = quantiles.get(0.0)
-    max = quantiles.get(1.0)
-    if min is not None and max is not None and low_bound <= min and high_bound >= max:
-        outliers_dict = {
-            "low_values": [],
-            "high_values": [],
-            "low_indices": [],
-            "high_indices": [],
-        }
-    else:
-        low_series = series[series < low_bound]
-        high_series = series[series > high_bound]
-
-        outliers_dict = {
-            "low_values": low_series.tolist(),
-            "high_values": high_series.tolist(),
-            "low_indices": low_series.index.tolist(),
-            "high_indices": high_series.index.tolist(),
-        }
+    if include_indices_and_values:
+        # identify outliers in the series
+        min = quantiles.get(0.0)
+        max = quantiles.get(1.0)
+        if (
+            min is not None
+            and max is not None
+            and low_bound <= min
+            and high_bound >= max
+        ):
+            outliers_dict = {
+                "low_values": [],
+                "high_values": [],
+                "low_indices": [],
+                "high_indices": [],
+            }
+        else:
+            low_series = series[series < low_bound]
+            high_series = series[series > high_bound]
+
+            outliers_dict = {
+                "low_values": low_series.tolist(),
+                "high_values": high_series.tolist(),
+                "low_indices": low_series.index.tolist(),
+                "high_indices": high_series.index.tolist(),
+            }
 
     return {
         "low_bound": low_bound,


Test patch:
diff --git a/woodwork/tests/accessor/test_statistics.py b/woodwork/tests/accessor/test_statistics.py
index 352c583..68094d1 100644
--- a/woodwork/tests/accessor/test_statistics.py
+++ b/woodwork/tests/accessor/test_statistics.py
@@ -1250,3 +1250,51 @@ def test_box_plot_quantiles_errors(outliers_df):
     error = "quantiles must be a dictionary."
     with pytest.raises(TypeError, match=error):
         series.ww.box_plot_dict(quantiles=1)
+
+
+def test_box_plot_optional_return_values(outliers_df):
+    has_outliers_series = outliers_df["has_outliers"]
+    has_outliers_series.ww.init()
+
+    has_outliers_box_plot_info_without_optional = has_outliers_series.ww.box_plot_dict(
+        include_indices_and_values=False
+    )
+    has_outliers_box_plot_info_with_optional = has_outliers_series.ww.box_plot_dict(
+        include_indices_and_values=True
+    )
+
+    assert {"low_bound", "high_bound", "quantiles"} == set(
+        has_outliers_box_plot_info_without_optional.keys()
+    )
+    assert {
+        "low_bound",
+        "high_bound",
+        "quantiles",
+        "low_values",
+        "high_values",
+        "low_indices",
+        "high_indices",
+    } == set(has_outliers_box_plot_info_with_optional.keys())
+
+    no_outliers_series = outliers_df["no_outliers"]
+    no_outliers_series.ww.init()
+
+    no_outliers_box_plot_info_without_optional = no_outliers_series.ww.box_plot_dict(
+        include_indices_and_values=False
+    )
+    no_outliers_box_plot_info_with_optional = no_outliers_series.ww.box_plot_dict(
+        include_indices_and_values=True
+    )
+
+    assert {"low_bound", "high_bound", "quantiles"} == set(
+        no_outliers_box_plot_info_without_optional.keys()
+    )
+    assert {
+        "low_bound",
+        "high_bound",
+        "quantiles",
+        "low_values",
+        "high_values",
+        "low_indices",
+        "high_indices",
+    } == set(no_outliers_box_plot_info_with_optional.keys())