generate pandas DataFrame

        Parameters
        ----------
        channels : list
            filter a subset of channels; default *None*
        raster : float | np.array | str
            new raster that can be

            * a float step value
            * a channel name who's timestamps will be used as raster
            * an array

            see `resample` for examples of urisng this argument

        time_from_zero : bool
            adjust time channel to start from 0; default *True*
        empty_channels : str
            behaviour for channels without samples; the options are *skip* or
            *zeros*; default is *skip*
        use_display_names : bool
            use display name instead of standard channel name, if available.
        keep_arrays : bool
            keep arrays and structure channels as well as the
            component channels. If *True* this can be very slow. If *False*
            only the component channels are saved, and their names will be
            prefixed with the parent channel.
        time_as_date : bool
            the dataframe index will contain the datetime timestamps
            according to the measurement start time; default *False*. If
            *True* then the argument ``time_from_zero`` will be ignored.
        reduce_memory_usage : bool
            reduce memory usage by converting all float columns to float32 and
            searching for minimum dtype that can reprezent the values found
            in integer columns; default *False*

        Returns
        -------
        dataframe : pandas.DataFrame

def to_dataframe(
        self,
        channels=None,
        raster=None,
        time_from_zero=True,
        empty_channels="skip",
        keep_arrays=False,
        use_display_names=False,
        time_as_date=False,
        reduce_memory_usage=False,
    ):
        """ generate pandas DataFrame

        Parameters
        ----------
        channels : list
            filter a subset of channels; default *None*
        raster : float | np.array | str
            new raster that can be

            * a float step value
            * a channel name who's timestamps will be used as raster
            * an array

            see `resample` for examples of urisng this argument

        time_from_zero : bool
            adjust time channel to start from 0; default *True*
        empty_channels : str
            behaviour for channels without samples; the options are *skip* or
            *zeros*; default is *skip*
        use_display_names : bool
            use display name instead of standard channel name, if available.
        keep_arrays : bool
            keep arrays and structure channels as well as the
            component channels. If *True* this can be very slow. If *False*
            only the component channels are saved, and their names will be
            prefixed with the parent channel.
        time_as_date : bool
            the dataframe index will contain the datetime timestamps
            according to the measurement start time; default *False*. If
            *True* then the argument ``time_from_zero`` will be ignored.
        reduce_memory_usage : bool
            reduce memory usage by converting all float columns to float32 and
            searching for minimum dtype that can reprezent the values found
            in integer columns; default *False*

        Returns
        -------
        dataframe : pandas.DataFrame

        """

        if channels:
            mdf = self.filter(channels)
            return mdf.to_dataframe(
                raster=raster,
                time_from_zero=time_from_zero,
                empty_channels=empty_channels,
                keep_arrays=keep_arrays,
                use_display_names=use_display_names,
                time_as_date=time_as_date,
                reduce_memory_usage=reduce_memory_usage,
            )

        df = pd.DataFrame()

        if raster:
            try:
                raster = float(raster)
                assert raster > 0
            except (TypeError, ValueError):
                if isinstance(raster, str):
                    raster = self.get(raster).timestamps
                else:
                    raster = np.array(raster)
            else:
                raster = master_using_raster(self, raster)
        else:
            masters = [
                self.get_master(i, copy_master=False)
                for i, _ in enumerate(self.groups)
            ]
            self._master_channel_cache.clear()
            master = reduce(np.union1d, masters)

        df["time"] = pd.Series(master, index=np.arange(len(master)))

        df.set_index('time', inplace=True)

        used_names = UniqueDB()
        used_names.get_unique_name("time")

        for group_index, grp in enumerate(self.groups):
            if grp.channel_group.cycles_nr == 0 and empty_channels == "skip":
                continue

            included_channels = self._included_channels(group_index)

            channels = grp.channels

            data = self._load_data(grp)
            _, dtypes = self._prepare_record(grp)

            signals = [[] for _ in included_channels]
            invalidation_bits = [[] for _ in included_channels]
            timestamps = []

            for fragment in data:
                if dtypes.itemsize:
                    grp.record = np.core.records.fromstring(fragment[0], dtype=dtypes)
                else:
                    grp.record = None
                for k, index in enumerate(included_channels):
                    signal = self.get(
                        group=group_index, index=index, data=fragment, samples_only=True, ignore_invalidation_bits=True
                    )
                    signals[k].append(signal[0])
                    invalidation_bits[k].append(signal[1])
                timestamps.append(
                    self.get_master(
                        group_index,
                        data=fragment,
                        copy_master=False,
                    )
                )

                grp.record = None

            if len(timestamps):
                signals = [np.concatenate(parts) for parts in signals]

                timestamps = np.concatenate(timestamps)
                for idx, parts in enumerate(invalidation_bits):
                    if parts[0] is None:
                        invalidation_bits[idx] = None
                    else:
                        invalidation_bits[idx] = np.concatenate(parts)

            signals = [
                Signal(
                    samples,
                    timestamps,
                    name=channels[idx].name,
                    invalidation_bits=invalidation,
                )
                .validate()
                .interp(
                    master,
                    self._integer_interpolation,
                )
                for samples, invalidation, idx in zip(signals, invalidation_bits, included_channels)
            ]

            for i, sig in enumerate(signals):
                if sig.conversion:
                    signals[i] = sig.physical()

            for sig in signals:
                if len(sig) == 0:
                    if empty_channels == "zeros":
                        sig.samples = np.zeros(len(master), dtype=sig.samples.dtype)
                    else:
                        continue

            signals = [sig for sig in signals if len(sig)]

            for k, sig in enumerate(signals):
                # byte arrays
                if len(sig.samples.shape) > 1:
                    arr = [sig.samples]
                    types = [(sig.name, sig.samples.dtype, sig.samples.shape[1:])]
                    sig.samples = np.core.records.fromarrays(arr, dtype=types)

                    if use_display_names:
                        channel_name = sig.display_name or sig.name
                    else:
                        channel_name = sig.name

                    channel_name = used_names.get_unique_name(channel_name)

                    df[channel_name] = pd.Series(sig.samples, index=master, dtype="O")

                # arrays and structures
                elif sig.samples.dtype.names:
                    for name, series in components(sig.samples, sig.name, used_names, master=master):
                        df[name] = series

                # scalars
                else:
                    if use_display_names:
                        channel_name = sig.display_name or sig.name
                    else:
                        channel_name = sig.name

                    channel_name = used_names.get_unique_name(channel_name)

                    if sig.samples.dtype.kind in 'SU':
                        unique = np.unique(sig.samples)
                        if len(sig.samples) / len(unique) >= 2:
                            df[channel_name] = pd.Series(sig.samples, index=master, dtype="category")
                        else:
                            df[channel_name] = pd.Series(sig.samples, index=master)
                    else:
                        if reduce_memory_usage:
                            sig.samples = downcast(sig.samples)
                        df[channel_name] = pd.Series(sig.samples, index=master)

        if time_as_date:
            new_index = np.array(df.index) + self.header.start_time.timestamp()
            new_index = pd.to_datetime(new_index, unit='s')

            df.set_index(new_index, inplace=True)
        elif time_from_zero and len(master):
            df.set_index(df.index - df.index[0], inplace=True)

        return df