code: def filter_string(
      df: pd.DataFrame,
      column_name: Hashable,
      search_string: str,
      complement: bool = False,
      case: bool = True,
      flags: int = 0,
      na: Any = None,
      regex: bool = True,
  ) -> pd.DataFrame:
      """Filter a string-based column according to whether it contains a substring.
  
      This is super sugary syntax that builds on top of `pandas.Series.str.contains`.
      It is meant to be the method-chaining equivalent of the following:
  
      ```python
      df = df[df[column_name].str.contains(search_string)]]
      ```
  
      This method does not mutate the original DataFrame.
  
      Examples:
          Retain rows whose column values contain a particular substring.
  
          >>> import pandas as pd
          >>> import janitor
          >>> df = pd.DataFrame({"a": range(3, 6), "b": ["bear", "peeL", "sail"]})
          >>> df
             a     b
          0  3  bear
          1  4  peeL
          2  5  sail
          >>> df.filter_string(column_name="b", search_string="ee")
             a     b
          1  4  peeL
          >>> df.filter_string(column_name="b", search_string="L", case=False)
             a     b
          1  4  peeL
          2  5  sail
  
          Filter names does not contain `'.'` (disable regex mode).
  
          >>> import pandas as pd
          >>> import janitor
          >>> df = pd.Series(["JoseChen", "Brian.Salvi"], name="Name").to_frame()
          >>> df
                    Name
          0     JoseChen
          1  Brian.Salvi
          >>> df.filter_string(column_name="Name", search_string=".", regex=False, complement=True)
                 Name
          0  JoseChen
  
      Args:
          df: A pandas DataFrame.
          column_name: The column to filter. The column should contain strings.
          search_string: A regex pattern or a (sub-)string to search.
          complement: Whether to return the complement of the filter or not. If
              set to True, then the rows for which the string search fails are retained
              instead.
          case: If True, case sensitive.
          flags: Flags to pass through to the re module, e.g. re.IGNORECASE.
          na: Fill value for missing values. The default depends on dtype of
              the array. For object-dtype, `numpy.nan` is used. For `StringDtype`,
              `pandas.NA` is used.
          regex: If True, assumes `search_string` is a regular expression. If False,
              treats the `search_string` as a literal string.
  
      Returns:
          A filtered pandas DataFrame.
      """  # noqa: E501
  
      criteria = df[column_name].str.contains(
          pat=search_string,
          case=case,
          flags=flags,
          na=na,
          regex=regex,
      )
  
      if complement:
          return df[~criteria]
  
      return df[criteria]
  
file_path: pyjanitor/janitor/functions/filter.py
imports: import datetime
  import warnings
  from functools import reduce
  from typing import Any, Dict, Hashable, Iterable, List, Optional
  import numpy
  import pandas
  import pandas_flavor
  from janitor.utils import deprecated_alias, find_stack_level, refactored_function
label_desc: Use image node_id 1 for calling a global function with example usage: filter_string(df, column_name, search_string, complement, case, flags, na, regex) and returns: df, df