Repository: deepchecks/deepchecks

Issue:
String Length Out Of Bounds should show outlier examples
Currently only tells you there are outlier samples and what length they are, but doesn't show you an example of these. 


Discussion:

Resolving patch:
diff --git a/deepchecks/checks/integrity/string_length_out_of_bounds.py b/deepchecks/checks/integrity/string_length_out_of_bounds.py
--- a/deepchecks/checks/integrity/string_length_out_of_bounds.py
+++ b/deepchecks/checks/integrity/string_length_out_of_bounds.py
@@ -10,7 +10,6 @@
 #
 """String length outlier check."""
 from collections import defaultdict
-from functools import reduce
 from typing import Union, Dict, Tuple, List
 
 import numpy as np
@@ -58,6 +57,10 @@ class StringLengthOutOfBounds(SingleDatasetBaseCheck):
             Minimum unique values in column to calculate string length outlier
         n_top_columns (int): (optional - used only if model was specified)
           amount of columns to show ordered by feature importance (date, index, label are first)
+        outlier_length_to_show (int):
+            Maximum length of outlier to show in results. If an outlier is longer it is trimmed and added '...'
+        samples_per_range_to_show (int):
+            Number of outlier samples to show in results per outlier range found.
     """
 
     def __init__(
@@ -71,7 +74,9 @@ def __init__(
         min_length_ratio_difference: int = 0.5,
         min_unique_value_ratio: float = 0.01,
         min_unique_values: int = 100,
-        n_top_columns: int = 10
+        n_top_columns: int = 10,
+        outlier_length_to_show: int = 50,
+        samples_per_range_to_show: int = 3
     ):
         super().__init__()
         self.columns = columns
@@ -84,6 +89,8 @@ def __init__(
         self.min_length_ratio_difference = min_length_ratio_difference
         self.min_unique_value_ratio = min_unique_value_ratio
         self.min_unique_values = min_unique_values
+        self.outlier_length_to_show = outlier_length_to_show
+        self.samples_per_range_to_show = samples_per_range_to_show
 
     def run(self, dataset, model=None) -> CheckResult:
         """Run check.
@@ -115,10 +122,9 @@ def _string_length_out_of_bounds(self, dataset: Union[pd.DataFrame, Dataset],
 
             # If not a lot of unique values, calculate the percentiles for existing values.
             if string_length_column.nunique() < self.num_percentiles:
-                string_length_column = string_length_column.to_numpy()
-                string_length_column.sort()
+                string_length_column = string_length_column.sort_values()
                 quantile_list = 100 * stats.rankdata(string_length_column, 'ordinal') / len(string_length_column)
-                percentile_histogram = {quantile_list[i]: string_length_column[i] for i in
+                percentile_histogram = {quantile_list[i]: string_length_column.iloc[i] for i in
                                         range(len(string_length_column))}
             else:
                 quantile_list = list(np.linspace(0.0, 100.0, self.num_percentiles + 1))
@@ -144,16 +150,20 @@ def _string_length_out_of_bounds(self, dataset: Union[pd.DataFrame, Dataset],
                     if lower_range > upper_range:
                         continue
 
-                    n_outlier_samples = reduce(lambda value, x, lr=lower_range, ur=upper_range:
-                                               value + _in_range(x, lr, ur),
-                                               string_length_column, 0)
-                    if n_outlier_samples:
+                    outlier_samples = string_length_column[
+                        string_length_column.between(lower_range, upper_range, inclusive='both')]
+
+                    if not outlier_samples.empty:
+                        outlier_examples = column[outlier_samples[:self.samples_per_range_to_show].index]
+                        outlier_examples = [trim(x, self.outlier_length_to_show) for x in outlier_examples]
+
                         display_format.append([column_name,
                                                f'{format_number(non_outlier_lower_limit)} -'
                                                f' {format_number(non_outlier_upper_limit)}',
                                                f'{format_number(lower_range)} -'
                                                f' {format_number(upper_range)}',
-                                               f'{n_outlier_samples}'
+                                               f'{outlier_samples.size}',
+                                               outlier_examples
                                                ])
                         results[column_name]['normal_range'] = {
                                 'min': non_outlier_lower_limit,
@@ -164,7 +174,7 @@ def _string_length_out_of_bounds(self, dataset: Union[pd.DataFrame, Dataset],
                             'range': {'min': lower_range,
                                       'max': upper_range
                                       },
-                            'n_samples': n_outlier_samples
+                            'n_samples': outlier_samples.size
                         })
 
         # Create dataframe to display graph
@@ -172,7 +182,8 @@ def _string_length_out_of_bounds(self, dataset: Union[pd.DataFrame, Dataset],
                              columns=['Column Name',
                                       'Range of Detected Normal String Lengths',
                                       'Range of Detected Outlier String Lengths',
-                                      'Number of Outlier Samples'])
+                                      'Number of Outlier Samples',
+                                      'Example Samples'])
         df_graph = df_graph.set_index(['Column Name',
                                        'Range of Detected Normal String Lengths',
                                        'Range of Detected Outlier String Lengths'])
@@ -304,3 +315,9 @@ def outlier_on_percentile_histogram(percentile_histogram: Dict[float, float], iq
 
 def _in_range(x, a, b):
     return a <= x <= b
+
+
+def trim(x, max_length):
+    if len(x) <= max_length:
+        return x
+    return x[:max_length] + '...'


Test patch: