docs: def split(
          self, expression, positions, *, units=None, parent=None, verbose=True
      ) -> wt_collection.Collection:
          """
          Split the data object along a given expression, in units.
  
          Parameters
          ----------
          expression : int or str
              The expression to split along. If given as an integer, the axis at that index
              is used.
          positions : number-type or 1D array-type
              The position(s) to split at, in units.
          units : str (optional)
              The units of the given positions. Default is same, which assumes
              input units are identical to first variable units.
          parent : WrightTools.Collection (optional)
              The parent collection in which to place the 'split' collection.
              Default is a new Collection.
          verbose : bool (optional)
              Toggle talkback. Default is True.
  
          Returns
          -------
          WrightTools.collection.Collection
              A Collection of data objects.
              The order of the objects is such that the axis points retain their original order.
  
          See Also
          --------
          chop
              Divide the dataset into its lower-dimensionality components.
          collapse
              Collapse the dataset along one axis.
          """
          # axis ------------------------------------------------------------------------------------
          old_expr = self.axis_expressions
          old_units = self.units
          out = wt_collection.Collection(name="split", parent=parent)
          if isinstance(expression, int):
              if units is None:
                  units = self._axes[expression].units
              expression = self._axes[expression].expression
          elif isinstance(expression, str):
              pass
          else:
              raise TypeError("expression: expected {int, str}, got %s" % type(expression))
  
          self.transform(expression)
          if units:
              self.convert(units)
  
          try:
              positions = [-np.inf] + sorted(list(positions)) + [np.inf]
          except TypeError:
              positions = [-np.inf, positions, np.inf]
  
          values = self._axes[0].full
          masks = [(values >= lo) & (values < hi) for lo, hi in wt_kit.pairwise(positions)]
          omasks = []
          cuts = []
          for mask in masks:
              try:
                  omasks.append(wt_kit.mask_reduce(mask))
                  cuts.append([i == 1 for i in omasks[-1].shape])
                  # Ensure at least one axis is kept
                  if np.all(cuts[-1]):
                      cuts[-1][0] = False
              except ValueError:
                  omasks.append(None)
                  cuts.append(None)
          for i in range(len(positions) - 1):
              out.create_data("split%03i" % i)
  
          for var in self.variables:
              for i, (imask, omask, cut) in enumerate(zip(masks, omasks, cuts)):
                  if omask is None:
                      # Zero length split
                      continue
                  omask = wt_kit.enforce_mask_shape(omask, var.shape)
                  omask.shape = tuple([s for s, c in zip(omask.shape, cut) if not c])
                  out_arr = np.full(omask.shape, np.nan)
                  imask = wt_kit.enforce_mask_shape(imask, var.shape)
                  out_arr[omask] = var[:][imask]
                  out[i].create_variable(values=out_arr, **var.attrs)
  
          for ch in self.channels:
              for i, (imask, omask, cut) in enumerate(zip(masks, omasks, cuts)):
                  if omask is None:
                      # Zero length split
                      continue
                  omask = wt_kit.enforce_mask_shape(omask, ch.shape)
                  omask.shape = tuple([s for s, c in zip(omask.shape, cut) if not c])
                  out_arr = np.full(omask.shape, np.nan)
                  imask = wt_kit.enforce_mask_shape(imask, ch.shape)
                  out_arr[omask] = ch[:][imask]
                  out[i].create_channel(values=out_arr, **ch.attrs)
  
          if verbose:
              for d in out.values():
                  try:
                      d.transform(expression)
                  except IndexError:
                      continue
  
              print("split data into {0} pieces along <{1}>:".format(len(positions) - 1, expression))
              for i, (lo, hi) in enumerate(wt_kit.pairwise(positions)):
                  new_data = out[i]
                  if new_data.shape == ():
                      print("  {0} : None".format(i))
                  else:
                      new_axis = new_data.axes[0]
                      print(
                          "  {0} : {1:0.2f} to {2:0.2f} {3} {4}".format(
                              i, lo, hi, new_axis.units, new_axis.shape
                          )
                      )
  
          for d in out.values():
              try:
                  d.transform(*old_expr)
                  keep = []
                  keep_units = []
                  for ax in d.axes:
                      if ax.size > 1:
                          keep.append(ax.expression)
                          keep_units.append(ax.units)
                      else:
                          d.create_constant(ax.expression, verbose=False)
                  d.transform(*keep)
                  for ax, u in zip(d.axes, keep_units):
                      ax.convert(u)
              except IndexError:
                  continue
              tempax = Axis(d, expression)
              if all(
                  np.all(
                      np.sum(~np.isnan(tempax.masked), axis=tuple(set(range(tempax.ndim)) - {j}))
                      <= 1
                  )
                  for j in range(tempax.ndim)
              ):
                  d.create_constant(expression, verbose=False)
          self.transform(*old_expr)
          for ax, u in zip(self.axes, old_units):
              ax.convert(u)
  
          return out