class_docstr: Abstract class with common methods to be inherited by WeakSegmentsPerformance checks.
class_docstr_tok: ["Abstract","class","with","common","methods","to","be","inherited","by","WeakSegmentsPerformance","checks","."]
docstr: Search for weak segments based on scorer.
docstr_tok: ["Search","for","weak","segments","based","on","scorer","."]
code: def _weak_segments_search(
      self,
      data: pd.DataFrame,
      score_per_sample: pd.Series,
      label_col: Optional[pd.Series] = None,
      feature_rank_for_search: Optional[np.ndarray] = None,
      dummy_model: Optional[_DummyModel] = None,
      scorer: Optional[DeepcheckScorer] = None,
      scorer_name: Optional[str] = None,
      multiple_segments_per_feature: bool = False,
  ) -> pd.DataFrame:
      """Search for weak segments based on scorer."""
      # Remove samples with NaN score per sample
      score_per_sample = score_per_sample.dropna()
      data = data.loc[score_per_sample.index]
      if label_col is not None:
          label_col = label_col.loc[score_per_sample.index]
  
      if scorer_name is None and scorer is None:
          score_title = "Average Score Per Sample"
      else:
          score_title = (
              scorer_name
              if scorer_name is not None
              else scorer.name + " Score"
          )
      if feature_rank_for_search is None:
          feature_rank_for_search = np.asarray(data.columns)
  
      weak_segments = pd.DataFrame(
          columns=[
              score_title,
              "Feature1",
              "Feature1 Range",
              "Feature2",
              "Feature2 Range",
              "% of Data",
              "Samples in Segment",
          ]
      )
      n_features = (
          min(len(feature_rank_for_search), self.n_top_features)
          if self.n_top_features is not None
          else len(feature_rank_for_search)
      )
      for i in range(n_features):
          for j in range(i + 1, n_features):
              feature1, feature2 = feature_rank_for_search[[i, j]]
              # Categorical feature come first
              if (
                  feature1 not in self.encoder_mapping
                  and feature2 in self.encoder_mapping
              ):
                  feature2, feature1 = feature_rank_for_search[[i, j]]
              (
                  weak_segment_score,
                  weak_segment_filter,
              ) = self._find_weak_segment(
                  data,
                  [feature1, feature2],
                  score_per_sample,
                  label_col,
                  dummy_model,
                  scorer,
              )
              if (
                  weak_segment_score is None
                  or len(weak_segment_filter.filters) == 0
              ):
                  continue
              data_of_segment = weak_segment_filter.filter(data)
              data_size = round(
                  100 * data_of_segment.shape[0] / data.shape[0], 2
              )
              filters = weak_segment_filter.filters
              if len(filters.keys()) == 1:
                  weak_segments.loc[len(weak_segments)] = [
                      weak_segment_score,
                      list(filters.keys())[0],
                      tuple(list(filters.values())[0]),
                      "",
                      None,
                      data_size,
                      list(data_of_segment.index),
                  ]
              else:
                  weak_segments.loc[len(weak_segments)] = [
                      weak_segment_score,
                      feature1,
                      tuple(filters[feature1]),
                      feature2,
                      tuple(filters[feature2]),
                      data_size,
                      list(data_of_segment.index),
                  ]
  
      # Filter and adapt the weak segments results
      result = pd.DataFrame(columns=weak_segments.columns)
      used_features = set()
      for _, row in weak_segments.sort_values(score_title).iterrows():
          new_row = row.copy()
          if not multiple_segments_per_feature and (
              row["Feature1"] in used_features
              or row["Feature2"] in used_features
          ):
              continue
  
          # Make sure segments based on categorical features are based only on a single category
          if (
              self.max_categories_weak_segment is not None
              and row["Feature1"] in self.encoder_mapping
          ):
              unique_values_in_range = [
                  x
                  for x in self.encoder_mapping[row["Feature1"]][
                      "encoded_value"
                  ].values
                  if row["Feature1 Range"][1]
                  > x
                  > row["Feature1 Range"][0]
              ]
              if (
                  len(unique_values_in_range)
                  > self.max_categories_weak_segment
              ):
                  subset = data.loc[new_row["Samples in Segment"]]
                  value_segment_size = [
                      len(subset[subset[row["Feature1"]] == x])
                      for x in unique_values_in_range
                  ]
                  # If all sub segments are too small, remove feature 2 filter
                  if (
                      max(value_segment_size)
                      < len(data) * self.segment_minimum_size_ratio
                      and row["Feature2"] != ""
                  ):
                      subset = data
                      value_segment_size = [
                          len(data[data[row["Feature1"]] == x])
                          for x in unique_values_in_range
                      ]
                      new_row["Feature2"] = ""
                      new_row["Feature2 Range"] = None
                  if (
                      max(value_segment_size)
                      < len(data) * self.segment_minimum_size_ratio
                  ):
                      continue
  
                  value_to_use = unique_values_in_range[
                      np.argmax(value_segment_size)
                  ]
                  subset = subset[
                      subset[row["Feature1"]] == value_to_use
                  ]
                  new_row["Samples in Segment"] = list(subset.index)
                  new_row["% of Data"] = round(
                      100
                      * len(new_row["Samples in Segment"])
                      / len(data),
                      2,
                  )
                  new_row["Feature1 Range"] = [
                      value_to_use - 0.5,
                      value_to_use + 0.5,
                  ]
                  if (
                      dummy_model is not None
                      and label_col is not None
                      and scorer is not None
                  ):
                      new_row[
                          score_title
                      ] = scorer.run_on_data_and_label(
                          dummy_model,
                          subset,
                          label_col[list(subset.index)],
                      )
                  else:
                      new_row[score_title] = score_per_sample[
                          list(subset.index)
                      ].mean()
  
          if (
              self.max_categories_weak_segment is not None
              and new_row["Feature2"] != ""
              and row["Feature2"] in self.encoder_mapping
          ):
              unique_values_in_range = [
                  x
                  for x in self.encoder_mapping[row["Feature2"]][
                      "encoded_value"
                  ].values
                  if row["Feature2 Range"][1]
                  > x
                  > row["Feature2 Range"][0]
              ]
              if (
                  len(unique_values_in_range)
                  > self.max_categories_weak_segment
              ):
                  subset = data.loc[new_row["Samples in Segment"]]
                  value_segment_size = [
                      len(subset[subset[row["Feature2"]] == x])
                      for x in unique_values_in_range
                  ]
                  # Feature 1 cannot be empty so if feature 2 do not have a large enough segment, ignore the row
                  if (
                      max(value_segment_size)
                      < len(data) * self.segment_minimum_size_ratio
                  ):
                      continue
                  value_to_use = unique_values_in_range[
                      np.argmax(value_segment_size)
                  ]
                  subset = subset[
                      subset[row["Feature2"]] == value_to_use
                  ]
                  new_row["Samples in Segment"] = list(subset.index)
                  new_row["% of Data"] = round(
                      100
                      * len(new_row["Samples in Segment"])
                      / len(data),
                      2,
                  )
                  new_row["Feature2 Range"] = [
                      value_to_use - 0.5,
                      value_to_use + 0.5,
                  ]
                  if (
                      dummy_model is not None
                      and label_col is not None
                      and scorer is not None
                  ):
                      new_row[
                          score_title
                      ] = scorer.run_on_data_and_label(
                          dummy_model,
                          subset,
                          label_col[list(subset.index)],
                      )
                  else:
                      new_row[score_title] = score_per_sample[
                          list(subset.index)
                      ].mean()
  
          result.loc[len(result)] = new_row
          used_features.add(new_row["Feature1"])
          if new_row["Feature2"] != "":
              used_features.add(new_row["Feature2"])
  
      return result.sort_values(score_title).drop_duplicates(
          subset=["Feature1", "Feature2"]
      )
  
imports: import abc
  from collections import defaultdict
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
  import numpy
  import pandas
  import plotly.express
  import sklearn
  from category_encoders import TargetEncoder
  from packaging import version
  from sklearn.model_selection import GridSearchCV
  from sklearn.tree import DecisionTreeRegressor
  from deepchecks import ConditionCategory, ConditionResult
  from deepchecks.tabular import Dataset
  from deepchecks.tabular.context import _DummyModel
  from deepchecks.tabular.metric_utils.scorers import DeepcheckScorer
  from deepchecks.utils.dataframes import default_fill_na_per_column_type
  from deepchecks.utils.performance.partition import DeepchecksFilter, convert_tree_leaves_into_filters, partition_numeric_feature_around_segment
  from deepchecks.utils.strings import format_number, format_percent
label_desc: Use image node_id 3 for calling the WeakSegmentAbstract obj's underlying member method code with example usage: obj._weak_segments_search(data, score_per_sample, label_col, feature_rank_for_search, dummy_model, scorer, scorer_name, multiple_segments_per_feature) and returns: result