class_docstr: Interface for simple dictionary-based dataset format using a
  compressed representation that uses the cartesian product between
  key dimensions. As with DictInterface, the dictionary keys correspond
  to the column (i.e. dimension) names and the values are NumPy arrays
  representing the values in that column.
  
  To use this compressed format, the key dimensions must be orthogonal
  to one another with each key dimension specifying an axis of the
  multidimensional space occupied by the value dimension data. For
  instance, given an temperature recordings sampled regularly across
  the earth surface, a list of N unique latitudes and M unique
  longitudes can specify the position of NxM temperature samples.
code: def groupby(
      cls, dataset, dim_names, container_type, group_type, **kwargs
  ):
      # Get dimensions information
      dimensions = [
          dataset.get_dimension(d, strict=True) for d in dim_names
      ]
      if "kdims" in kwargs:
          kdims = kwargs["kdims"]
      else:
          kdims = [
              kdim for kdim in dataset.kdims if kdim not in dimensions
          ]
          kwargs["kdims"] = kdims
  
      invalid = [d for d in dimensions if dataset.data[d.name].ndim > 1]
      if invalid:
          if len(invalid) == 1:
              invalid = f"'{invalid[0]}'"
          raise ValueError(
              "Cannot groupby irregularly sampled dimension(s) %s."
              % invalid
          )
  
      # Update the kwargs appropriately for Element group types
      group_kwargs = {}
      group_type = dict if group_type == "raw" else group_type
      if issubclass(group_type, Element):
          group_kwargs.update(util.get_param_values(dataset))
      else:
          kwargs.pop("kdims")
      group_kwargs.update(kwargs)
  
      drop_dim = any(d not in group_kwargs["kdims"] for d in kdims)
  
      # Find all the keys along supplied dimensions
      keys = [cls.coords(dataset, d.name) for d in dimensions]
      transpose = [
          dataset.ndims - dataset.kdims.index(kd) - 1 for kd in kdims
      ]
      transpose += [
          i for i in range(dataset.ndims) if i not in transpose
      ]
  
      # Iterate over the unique entries applying selection masks
      grouped_data = []
      for unique_key in zip(*util.cartesian_product(keys)):
          select = dict(zip(dim_names, unique_key))
          if drop_dim:
              group_data = dataset.select(**select)
              group_data = (
                  group_data
                  if np.isscalar(group_data)
                  else group_data.columns()
              )
          else:
              group_data = cls.select(dataset, **select)
  
          if np.isscalar(group_data) or (
              isinstance(group_data, get_array_types())
              and group_data.shape == ()
          ):
              group_data = {
                  dataset.vdims[0].name: np.atleast_1d(group_data)
              }
              for dim, v in zip(dim_names, unique_key):
                  group_data[dim] = np.atleast_1d(v)
          elif not drop_dim:
              if isinstance(group_data, get_array_types()):
                  group_data = {dataset.vdims[0].name: group_data}
              for vdim in dataset.vdims:
                  data = group_data[vdim.name]
                  data = data.transpose(transpose[::-1])
                  group_data[vdim.name] = np.squeeze(data)
          group_data = group_type(group_data, **group_kwargs)
          grouped_data.append((tuple(unique_key), group_data))
  
      if issubclass(container_type, NdMapping):
          with item_check(False):
              return container_type(grouped_data, kdims=dimensions)
      else:
          return container_type(grouped_data)
  
imports: from collections import defaultdict
  import numpy
  from ..None import util
  from ..dimension import dimension_name
  from ..element import Element
  from ..ndmapping import NdMapping, item_check, sorted_context
  from .dictionary import DictInterface
  from .interface import DataError, Interface
  from .util import dask_array_module, finite_range, get_array_types, is_dask
label_desc: Use image node_id 20 for calling the GridInterface obj's underlying member method code with example usage: obj.groupby(cls, dataset, dim_names, container_type, group_type) and returns: container_type, container_type