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