"""inst.to_data_frame() helper functions."""
# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
from inspect import signature
import numpy as np
from ..defaults import _handle_default
from ._logging import logger, verbose
from .check import check_version
@verbose
def _set_pandas_dtype(df, columns, dtype, verbose=None):
"""Try to set the right columns to dtype."""
for column in columns:
df[column] = df[column].astype(dtype)
logger.info(f'Converting "{column}" to "{dtype}"...')
def _scale_dataframe_data(inst, data, picks, scalings):
ch_types = inst.get_channel_types()
ch_types_used = list()
scalings = _handle_default("scalings", scalings)
for tt in scalings.keys():
if tt in ch_types:
ch_types_used.append(tt)
for tt in ch_types_used:
scaling = scalings[tt]
idx = [ii for ii in range(len(picks)) if ch_types[ii] == tt]
if len(idx):
data[:, idx] *= scaling
return data
def _convert_times(times, time_format, meas_date=None, first_time=0):
"""Convert vector of time in seconds to ms, datetime, or timedelta."""
# private function; pandas already checked in calling function
from pandas import to_timedelta
if time_format == "ms":
times = np.round(times * 1e3).astype(np.int64)
elif time_format == "timedelta":
times = to_timedelta(times, unit="s")
elif time_format == "datetime":
times = to_timedelta(times + first_time, unit="s") + meas_date
return times
def _inplace(df, method, **kwargs):
# Handle transition: inplace=True (pandas <1.5) → copy=False (>=1.5)
# and 3.0 warning:
# E DeprecationWarning: The copy keyword is deprecated and will be removed in a
# future version. Copy-on-Write is active in pandas since 3.0 which utilizes a
# lazy copy mechanism that defers copies until necessary. Use .copy() to make
# an eager copy if necessary.
_meth = getattr(df, method) # used for set_index() and rename()
if check_version("pandas", "3.0"):
return _meth(**kwargs)
elif "copy" in signature(_meth).parameters:
return _meth(**kwargs, copy=False)
else:
_meth(**kwargs, inplace=True)
return df
@verbose
def _build_data_frame(
inst,
data,
picks,
long_format,
mindex,
index,
default_index,
col_names=None,
col_kind="channel",
verbose=None,
):
"""Build DataFrame from MNE-object-derived data array."""
# private function; pandas already checked in calling function
from pandas import DataFrame
from ..source_estimate import _BaseSourceEstimate
# build DataFrame
if col_names is None:
col_names = [inst.ch_names[p] for p in picks]
df = DataFrame(data, columns=col_names)
for i, (k, v) in enumerate(mindex):
df.insert(i, k, v)
# build Index
if long_format:
df = _inplace(df, "set_index", keys=default_index)
df.columns.name = col_kind
elif index is not None:
df = _inplace(df, "set_index", keys=index)
if set(index) == set(default_index):
df.columns.name = col_kind
# long format
if long_format:
df = df.stack().reset_index()
df = _inplace(df, "rename", columns={0: "value"})
# add column for channel types (as appropriate)
ch_map = (
None
if isinstance(inst, _BaseSourceEstimate)
else dict(
zip(
np.array(inst.ch_names)[picks],
np.array(inst.get_channel_types())[picks],
)
)
)
if ch_map is not None:
col_index = len(df.columns) - 1
ch_type = df["channel"].map(ch_map)
df.insert(col_index, "ch_type", ch_type)
# restore index
if index is not None:
df = _inplace(df, "set_index", keys=index)
# convert channel/vertex/ch_type columns to factors
to_factor = [
c for c in df.columns.tolist() if c not in ("freq", "time", "value")
]
_set_pandas_dtype(df, to_factor, "category")
return df