code: def ninteraction(df, drop=False):
      
      if len(df) == 0:
          return []
  
      # Special case for single variable
      if len(df.columns) == 1:
          return _id_var(df[df.columns[0]], drop)
  
      # Calculate individual ids
      ids = df.apply(_id_var, axis=0)
      ids = ids.reindex(columns=reversed(ids.columns))
  
      # Calculate dimensions
      def len_unique(x):
          return len(np.unique(x))
      ndistinct = ids.apply(len_unique, axis=0).values
  
      combs = np.array(
          np.hstack([1, np.cumprod(ndistinct[:-1])]))
      mat = np.array(ids)
      res = (mat - 1) @ combs.T + 1
      res = np.array(res).flatten().tolist()
  
      if drop:
          return _id_var(res, drop)
      else:
          return res
docs: Compute a unique numeric id for each unique row in
      a data frame. The ids start at 1 -- in the spirit
      of `plyr::id`
  
      Parameters
      ----------
      df : dataframe
          Rows
      drop : bool
          If true, drop unused categorical levels leaving no
          gaps in the assignments.
  
      Returns
      -------
      out : list
          Row asssignments.
  
      Notes
      -----
      So far there has been no need not to drop unused levels
      of categorical variables.