class_docstr: Holds the grouping information for a single key
  
  Parameters
  ----------
  index : Index
  grouper :
  obj : DataFrame or Series
  name : Label
  level :
  observed : bool, default False
      If we are a Categorical, use the observed values
  in_axis : if the Grouping is a column in self.obj and hence among
      Groupby.exclusions list
  dropna : bool, default True
      Whether to drop NA groups.
  uniques : Array-like, optional
      When specified, will be used for unique values. Enables including empty groups
      in the result for a BinGrouper. Must not contain duplicates.
  
  Attributes
  -------
  indices : dict
      Mapping of {group -> index_list}
  codes : ndarray
      Group codes
  group_index : Index or None
      unique groups
  groups : dict
      Mapping of {group -> label_list}
code: def __init__(
      self,
      index: Index,
      grouper=None,
      obj: NDFrame | None = None,
      level=None,
      sort: bool = True,
      observed: bool = False,
      in_axis: bool = False,
      dropna: bool = True,
      uniques: ArrayLike | None = None,
  ) -> None:
      self.level = level
      self._orig_grouper = grouper
      grouping_vector = _convert_grouper(index, grouper)
      self._all_grouper = None
      self._orig_cats = None
      self._index = index
      self._sort = sort
      self.obj = obj
      self._observed = observed
      self.in_axis = in_axis
      self._dropna = dropna
      self._uniques = uniques
  
      # we have a single grouper which may be a myriad of things,
      # some of which are dependent on the passing in level
  
      ilevel = self._ilevel
      if ilevel is not None:
          # In extant tests, the new self.grouping_vector matches
          #  `index.get_level_values(ilevel)` whenever
          #  mapper is None and isinstance(index, MultiIndex)
          if isinstance(index, MultiIndex):
              index_level = index.get_level_values(ilevel)
          else:
              index_level = index
  
          if grouping_vector is None:
              grouping_vector = index_level
          else:
              mapper = grouping_vector
              grouping_vector = index_level.map(mapper)
  
      # a passed Grouper like, directly get the grouper in the same way
      # as single grouper groupby, use the group_info to get codes
      elif isinstance(grouping_vector, Grouper):
          # get the new grouper; we already have disambiguated
          # what key/level refer to exactly, don't need to
          # check again as we have by this point converted these
          # to an actual value (rather than a pd.Grouper)
          assert self.obj is not None  # for mypy
          newgrouper, newobj = grouping_vector._get_grouper(
              self.obj, validate=False
          )
          self.obj = newobj
  
          if isinstance(newgrouper, ops.BinGrouper):
              # TODO: can we unwrap this and get a tighter typing
              #  for self.grouping_vector?
              grouping_vector = newgrouper
          else:
              # ops.BaseGrouper
              # TODO: 2023-02-03 no test cases with len(newgrouper.groupings) > 1.
              #  If that were to occur, would we be throwing out information?
              # error: Cannot determine type of "grouping_vector"  [has-type]
              ng = newgrouper.groupings[0].grouping_vector  # type: ignore[has-type]
              # use Index instead of ndarray so we can recover the name
              grouping_vector = Index(
                  ng, name=newgrouper.result_index.name
              )
  
      elif not isinstance(
          grouping_vector, (Series, Index, ExtensionArray, np.ndarray)
      ):
          # no level passed
          if getattr(grouping_vector, "ndim", 1) != 1:
              t = str(type(grouping_vector))
              raise ValueError(f"Grouper for '{t}' not 1-dimensional")
  
          grouping_vector = index.map(grouping_vector)
  
          if not (
              hasattr(grouping_vector, "__len__")
              and len(grouping_vector) == len(index)
          ):
              grper = pprint_thing(grouping_vector)
              errmsg = (
                  "Grouper result violates len(labels) == "
                  f"len(data)\nresult: {grper}"
              )
              raise AssertionError(errmsg)
  
      if isinstance(grouping_vector, np.ndarray):
          if grouping_vector.dtype.kind in "mM":
              # if we have a date/time-like grouper, make sure that we have
              # Timestamps like
              # TODO 2022-10-08 we only have one test that gets here and
              #  values are already in nanoseconds in that case.
              grouping_vector = Series(grouping_vector).to_numpy()
      elif isinstance(
          getattr(grouping_vector, "dtype", None), CategoricalDtype
      ):
          # a passed Categorical
          self._orig_cats = grouping_vector.categories
          grouping_vector, self._all_grouper = recode_for_groupby(
              grouping_vector, sort, observed
          )
  
      self.grouping_vector = grouping_vector
  
imports: from __future__ import annotations
  from typing import TYPE_CHECKING, final
  import warnings
  import numpy
  from pandas._config import using_copy_on_write, warn_copy_on_write
  from pandas._libs import lib
  from pandas._libs.tslibs import OutOfBoundsDatetime
  from pandas.errors import InvalidIndexError
  from pandas.util._decorators import cache_readonly
  from pandas.util._exceptions import find_stack_level
  from pandas.core.dtypes.common import is_list_like, is_scalar
  from pandas.core.dtypes.dtypes import CategoricalDtype
  from pandas.core import algorithms
  from pandas.core.arrays import Categorical, ExtensionArray
  import pandas.core.common
  from pandas.core.frame import DataFrame
  from pandas.core.groupby import ops
  from pandas.core.groupby.categorical import recode_for_groupby
  from pandas.core.indexes.api import CategoricalIndex, Index, MultiIndex
  from pandas.core.series import Series
  from pandas.io.formats.printing import pprint_thing
label_desc: Use image node_id 1 to create a new Grouping object  with example: obj = Grouping(index, grouper, obj, level, sort, observed, in_axis, dropna, uniques)