docs: def make_step_table(self, custom_step_definition=False,
                          step_specifications=None,
                          short=False,
                          dataset_number=None):
  
          """ Create a table (v.4) that contains summary information for each step.
  
          This function creates a table containing information about the
          different steps for each cycle and, based on that, decides what type of
          step it is (e.g. charge) for each cycle.
  
          The format of the step_table is:
  
              index: cycleno - stepno - sub-step-no
              Logging info (average, stdev, max, min, start, end, delta) -
              Current info (average, stdev, max, min, start, end, delta) -
              Voltage info (average,  stdev, max, min, start, end, delta) -
              Type (from pre-defined list) - SubType -
              Info
          """
          time_00 = time.time()
          dataset_number = self._validate_dataset_number(dataset_number)
          if dataset_number is None:
              self._report_empty_dataset()
              return
  
          nhdr = self.headers_normal
          shdr = self.headers_step_table
  
          df = self.datasets[dataset_number].dfdata
          # df[shdr.internal_resistance_change] = \
          #     df[nhdr.internal_resistance_txt].pct_change()
  
          def first(x):
              return x.iloc[0]
  
          def last(x):
              return x.iloc[-1]
  
          def delta(x):
              if x.iloc[0] == 0.0:
                  # starts from a zero value
                  difference = 100.0 * x.iloc[-1]
              else:
                  difference = (x.iloc[-1] - x.iloc[0]) * 100 / x.iloc[0]
  
              return difference
  
          keep = [
              nhdr.data_point_txt,
              nhdr.step_time_txt,
              nhdr.step_index_txt,
              nhdr.cycle_index_txt,
              nhdr.current_txt,
              nhdr.voltage_txt,
              nhdr.ref_voltage_txt,
              nhdr.charge_capacity_txt,
              nhdr.discharge_capacity_txt,
              nhdr.internal_resistance_txt,
              # "ir_pct_change"
          ]
  
          # only use col-names that exist:
          keep = [col for col in keep if col in df.columns]
  
          df = df[keep]
          df[nhdr.sub_step_index_txt] = 1
          rename_dict = {
              nhdr.cycle_index_txt: shdr.cycle,
              nhdr.step_index_txt: shdr.step,
              nhdr.sub_step_index_txt: shdr.sub_step,
              nhdr.data_point_txt: shdr.point,
              nhdr.step_time_txt: shdr.step_time,
              nhdr.current_txt: shdr.current,
              nhdr.voltage_txt: shdr.voltage,
              nhdr.charge_capacity_txt: shdr.charge,
              nhdr.discharge_capacity_txt: shdr.discharge,
              nhdr.internal_resistance_txt: shdr.internal_resistance,
          }
  
          df = df.rename(columns=rename_dict)
  
          by = [shdr.cycle, shdr.step, shdr.sub_step]
  
          self.logger.debug(f"groupby: {by}")
  
          gf = df.groupby(by=by)
          df_steps = (gf.agg(
              [np.mean, np.std, np.amin, np.amax, first, last, delta]
          ).rename(columns={'amin': 'min', 'amax': 'max', 'mean': 'avr'}))
  
          df_steps = df_steps.reset_index()
  
          df_steps[shdr.type] = np.nan
          df_steps[shdr.sub_type] = np.nan
          df_steps[shdr.info] = np.nan
  
          current_limit_value_hard = self.raw_limits["current_hard"]
          current_limit_value_soft = self.raw_limits["current_soft"]
          stable_current_limit_hard = self.raw_limits["stable_current_hard"]
          stable_current_limit_soft = self.raw_limits["stable_current_soft"]
          stable_voltage_limit_hard = self.raw_limits["stable_voltage_hard"]
          stable_voltage_limit_soft = self.raw_limits["stable_voltage_soft"]
          stable_charge_limit_hard = self.raw_limits["stable_charge_hard"]
          stable_charge_limit_soft = self.raw_limits["stable_charge_soft"]
          ir_change_limit = self.raw_limits["ir_change"]
  
          mask_no_current_hard = (
              df_steps.loc[:, (shdr.current, "max")].abs()
              + df_steps.loc[:, (shdr.current, "min")].abs()
          ) < current_limit_value_hard
  
          mask_voltage_down = df_steps.loc[:, (shdr.voltage, "delta")] < \
              - stable_voltage_limit_hard
  
          mask_voltage_up = df_steps.loc[:, (shdr.voltage, "delta")] > \
              stable_voltage_limit_hard
  
          mask_voltage_stable = df_steps.loc[:, (shdr.voltage, "delta")].abs() < \
              stable_voltage_limit_hard
  
          mask_current_down = df_steps.loc[:, (shdr.current, "delta")] < \
              - stable_current_limit_soft
  
          mask_current_up = df_steps.loc[:, (shdr.current, "delta")] > \
              stable_current_limit_soft
  
          mask_current_negative = df_steps.loc[:, (shdr.current, "avr")] < \
              - current_limit_value_hard
  
          mask_current_positive = df_steps.loc[:, (shdr.current, "avr")] > \
              current_limit_value_hard
  
          mask_galvanostatic = df_steps.loc[:, (shdr.current, "delta")].abs() < \
              stable_current_limit_soft
  
          mask_charge_changed = df_steps.loc[:, (shdr.charge, "delta")].abs() > \
              stable_charge_limit_hard
  
          mask_discharge_changed = df_steps.loc[:, (shdr.discharge, "delta")].abs() > \
              stable_charge_limit_hard
  
          mask_no_change = (df_steps.loc[:, (shdr.voltage, "delta")] == 0) & \
              (df_steps.loc[:, (shdr.current, "delta")] == 0) & \
              (df_steps.loc[:, (shdr.charge, "delta")] == 0) & \
              (df_steps.loc[:, (shdr.charge, "delta")] == 0)
  
          if custom_step_definition:
              self.logger.debug("parsing custom step definition")
              if not short:
                  self.logger.debug("using long format (cycle,step)")
                  for row in step_specifications.itertuples():
                      # self.logger.debug(f"cycle: {row.cycle} step: {row.step}"
                      #                   f" type: {row.type}")
                      df_steps.loc[(df_steps[shdr.step] == row.step) &
                                   (df_steps[shdr.cycle] == row.cycle),
                                   "type"] = row.type
                      df_steps.loc[(df_steps[shdr.step] == row.step) &
                                   (df_steps[shdr.cycle] == row.cycle),
                                   "info"] = row.info
              else:
                  self.logger.debug("using short format (step)")
                  for row in step_specifications.itertuples():
                      # self.logger.debug(f"step: {row.step} "
                      #                   f"type: {row.type}"
                      #                   f"info: {row.info}")
  
                      df_steps.loc[df_steps[shdr.step] == row.step,
                                   "type"] = row.type
                      df_steps.loc[df_steps[shdr.step] == row.step,
                                   "info"] = row.info
  
          else:
              self.logger.debug("masking and labelling steps")
              df_steps.loc[mask_no_current_hard & mask_voltage_stable,
                           shdr.type] = 'rest'
  
              df_steps.loc[mask_no_current_hard & mask_voltage_up,
                           shdr.type] = 'ocvrlx_up'
  
              df_steps.loc[mask_no_current_hard & mask_voltage_down,
                           shdr.type] = 'ocvrlx_down'
  
              df_steps.loc[mask_discharge_changed & mask_current_negative,
                           shdr.type] = 'discharge'
  
              df_steps.loc[mask_charge_changed & mask_current_positive,
                           shdr.type] = 'charge'
  
              df_steps.loc[
                  mask_voltage_stable & mask_current_negative & mask_current_down,
                  shdr.type
              ] = 'cv_discharge'
  
              df_steps.loc[mask_voltage_stable & mask_current_positive &
                           mask_current_down, shdr.type] = 'cv_charge'
  
              # --- internal resistance ----
              df_steps.loc[mask_no_change, shdr.type] = 'ir'
              # assumes that IR is stored in just one row
  
              # --- sub-step-txt -----------
              df_steps[shdr.sub_type] = None
  
              # --- CV steps ----
  
              # "voltametry_charge"
              # mask_charge_changed
              # mask_voltage_up
              # (could also include abs-delta-cumsum current)
  
              # "voltametry_discharge"
              # mask_discharge_changed
              # mask_voltage_down
  
          # check if all the steps got categorizes
          self.logger.debug("looking for un-categorized steps")
          empty_rows = df_steps.loc[df_steps[shdr.type].isnull()]
          if not empty_rows.empty:
              logging.warning(
                  f"found {len(empty_rows)}"
                  f":{len(df_steps)} non-categorized steps "
                  f"(please, check your raw-limits)")
  
          # flatten (possible remove in the future),
          # (maybe we will implement mulitindexed tables)
  
          self.logger.debug(f"flatten columns")
          flat_cols = []
          for col in df_steps.columns:
              if isinstance(col, tuple):
                  if col[-1]:
                      col = "_".join(col)
                  else:
                      col = col[0]
              flat_cols.append(col)
  
          df_steps.columns = flat_cols
  
          self.datasets[dataset_number].step_table = df_steps
          self.logger.debug(f"(dt: {(time.time() - time_00):4.2f}s)")
          return self