非官方中文翻译 本页译自 MNE-Python 官方文档: 查看英文原文 ↗。 文档版权归 MNE Developers 所有(BSD-3-Clause)。

将 Epochs 导出为 Pandas DataFrame#

本教程展示如何将 Epochs 对象中的数据导出为 Pandas DataFrame,并应用典型的 Pandas split-apply-combine(拆分-应用-合并)工作流,考察各个 epoch 与各种条件下反应最大值的潜伏期。

我们将使用 Sample 数据集,但加载的是原始文件的一个版本,该版本已经过滤波和降采样,并且其 EEG 通道已施加平均参考。和往常一样,我们先导入所需的模块并加载数据:

# Authors: The MNE-Python contributors.
# License: BSD-3-Clause
# Copyright the MNE-Python contributors.
import matplotlib.pyplot as plt
import seaborn as sns

import mne

sample_data_folder = mne.datasets.sample.data_path()
sample_data_raw_file = (
    sample_data_folder / "MEG" / "sample" / "sample_audvis_filt-0-40_raw.fif"
)
raw = mne.io.read_raw_fif(sample_data_raw_file, verbose=False)

接下来,我们从文件中加载事件列表,用事件字典将其映射为条件名称,设置一些信号拒绝阈值(参见 Rejecting Epochs based on peak-to-peak channel amplitude),然后将连续数据分割为 epoch:

sample_data_events_file = (
    sample_data_folder / "MEG" / "sample" / "sample_audvis_filt-0-40_raw-eve.fif"
)
events = mne.read_events(sample_data_events_file)

event_dict = {
    "auditory/left": 1,
    "auditory/right": 2,
    "visual/left": 3,
    "visual/right": 4,
}

reject_criteria = dict(
    mag=3000e-15,  # 3000 fT
    grad=3000e-13,  # 3000 fT/cm
    eeg=100e-6,  # 100 µV
    eog=200e-6,
)  # 200 µV

tmin, tmax = (-0.2, 0.5)  # epoch from 200 ms before event to 500 ms after it
baseline = (None, 0)  # baseline period from start of epoch to time=0

epochs = mne.Epochs(
    raw,
    events,
    event_dict,
    tmin,
    tmax,
    proj=True,
    baseline=baseline,
    reject=reject_criteria,
    preload=True,
)
del raw
Not setting metadata
288 matching events found
Setting baseline interval to [-0.19979521315838786, 0.0] s
Applying baseline correction (mode: mean)
Created an SSP operator (subspace dimension = 4)
4 projection items activated
Loading data for 288 events and 106 original time points ...
    Rejecting  epoch based on EEG : ['EEG 003']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 004', 'EEG 006', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 001']
    Rejecting  epoch based on EEG : ['EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 007']
    Rejecting  epoch based on EEG : ['EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 007']
    Rejecting  epoch based on MAG : ['MEG 1411', 'MEG 1421', 'MEG 1441']
    Rejecting  epoch based on MAG : ['MEG 1711']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 007']
    Rejecting  epoch based on MAG : ['MEG 1411', 'MEG 1421']
    Rejecting  epoch based on MAG : ['MEG 1711']
    Rejecting  epoch based on EOG : ['EOG 061']
    Rejecting  epoch based on EEG : ['EEG 008']
    Rejecting  epoch based on EEG : ['EEG 008']
    Rejecting  epoch based on EOG : ['EOG 061']
    Rejecting  epoch based on EEG : ['EEG 007', 'EEG 008']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on MAG : ['MEG 1411', 'MEG 1421']
    Rejecting  epoch based on MAG : ['MEG 1411']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 007']
    Rejecting  epoch based on EEG : ['EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 007']
    Rejecting  epoch based on MAG : ['MEG 1331', 'MEG 1421']
    Rejecting  epoch based on EEG : ['EEG 007']
    Rejecting  epoch based on EOG : ['EOG 061']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
    Rejecting  epoch based on EEG : ['EEG 001', 'EEG 002', 'EEG 003', 'EEG 007']
35 bad epochs dropped

将 Epochs 对象转换为 DataFrame#

有了 Epochs 对象之后,将其转换为 DataFrame 就很简单:只需调用 epochs.to_data_frame()。每个通道的数据都会成为新 DataFrame 的一列,此外还有三列分别记录事件名称、epoch 编号和采样时间。这里我们只展示前几行和前几列:

time condition epoch MEG 0113 MEG 0112 MEG 0111 MEG 0122 MEG 0123 MEG 0121 MEG 0132
0 -0.199795 visual/left 1 21.320252 11.567619 -73.248584 -10.609895 -18.700540 -68.352746 -17.908667
1 -0.193135 visual/left 1 7.049675 -9.702938 -34.168267 17.480975 -40.182237 -121.225684 2.072452
2 -0.186476 visual/left 1 5.283711 -13.006895 -26.912317 52.130290 -65.157198 -117.842466 6.986073
3 -0.179816 visual/left 1 23.040471 1.643716 -37.835772 48.517952 -65.214631 -82.043144 9.672744
4 -0.173156 visual/left 1 25.688943 7.974112 -85.561683 6.450115 -43.778315 -111.795987 17.277138


缩放时间与通道数值#

默认情况下,时间值会从秒转换为毫秒,然后四舍五入到最接近的整数;如果你不希望这样,可以传入 time_format=None,以秒为单位将时间保留为 float 浮点值,也可以通过 time_format='timedelta' 将其转换为 Timedelta 值。

另请注意,默认情况下通道测量值会被缩放,使 EEG 数据转换为 µV、磁力计数据转换为 fT、梯度计数据转换为 fT/cm。这些缩放可以通过 scalings 参数自定义,或者通过传入 scalings=dict(eeg=1, mag=1, grad=1) 来取消。

df = epochs.to_data_frame(time_format=None, scalings=dict(eeg=1, mag=1, grad=1))
df.iloc[:5, :10]
time condition epoch MEG 0113 MEG 0112 MEG 0111 MEG 0122 MEG 0123 MEG 0121 MEG 0132
0 -0.199795 visual/left 1 2.132025e-12 1.156762e-12 -7.324858e-14 -1.060990e-12 -1.870054e-12 -6.835275e-14 -1.790867e-12
1 -0.193135 visual/left 1 7.049675e-13 -9.702938e-13 -3.416827e-14 1.748098e-12 -4.018224e-12 -1.212257e-13 2.072452e-13
2 -0.186476 visual/left 1 5.283711e-13 -1.300690e-12 -2.691232e-14 5.213029e-12 -6.515720e-12 -1.178425e-13 6.986073e-13
3 -0.179816 visual/left 1 2.304047e-12 1.643716e-13 -3.783577e-14 4.851795e-12 -6.521463e-12 -8.204314e-14 9.672744e-13
4 -0.173156 visual/left 1 2.568894e-12 7.974112e-13 -8.556168e-14 6.450115e-13 -4.377831e-12 -1.117960e-13 1.727714e-12


注意,时间值不再是整数,而且与前一个 DataFrame 相比,通道数值变化了好几个数量级。

设置索引#

还可以通过向 index 参数传入一个字符串或字符串列表,将一个或多个指示列(事件名称、epoch 编号和采样时间)移入索引。这里我们还会演示 time_format='timedelta' 的效果,在“time”列中生成 Timedelta 值。

df = epochs.to_data_frame(index=["condition", "epoch"], time_format="timedelta")
df.iloc[:5, :10]
time MEG 0113 MEG 0112 MEG 0111 MEG 0122 MEG 0123 MEG 0121 MEG 0132 MEG 0133 MEG 0131
condition epoch
visual/left 1 -1 days +23:59:59.800204787 21.320252 11.567619 -73.248584 -10.609895 -18.700540 -68.352746 -17.908667 45.379736 -53.123525
1 -1 days +23:59:59.806864627 7.049675 -9.702938 -34.168267 17.480975 -40.182237 -121.225684 2.072452 27.734826 -95.339465
1 -1 days +23:59:59.813524468 5.283711 -13.006895 -26.912317 52.130290 -65.157198 -117.842466 6.986073 5.996158 -177.627037
1 -1 days +23:59:59.820184308 23.040471 1.643716 -37.835772 48.517952 -65.214631 -82.043144 9.672744 1.748030 -211.024973
1 -1 days +23:59:59.826844149 25.688943 7.974112 -85.561683 6.450115 -43.778315 -111.795987 17.277138 2.123811 -173.917677


宽格式与长格式 DataFrame#

另一个参数 long_format 决定每个通道的数据是放在 DataFrame 的单独一列中(long_format=False),还是将测量值透视为单个 'value' 列,并额外增加一列用于指示通道名称(long_format=True)。传入 long_format=True 还会额外创建一列 ch_type,用于指示通道类型。

long_df = epochs.to_data_frame(time_format=None, index="condition", long_format=True)
long_df.head()
Converting "epoch" to "category"...
Converting "channel" to "category"...
Converting "ch_type" to "category"...
epoch time channel ch_type value
condition
visual/left 1 -0.199795 MEG 0113 grad 21.320252
visual/left 1 -0.199795 MEG 0112 grad 11.567619
visual/left 1 -0.199795 MEG 0111 mag -73.248584
visual/left 1 -0.199795 MEG 0122 grad -10.609895
visual/left 1 -0.199795 MEG 0123 grad -18.700540


在使用其他 Python 模块进行后续分析或绘图时,以长格式生成 DataFrame 会很有帮助。例如,这里我们取出“auditory/left”条件的数据,挑选几个 MEG 通道,并使用 seaborn.lineplot() 自动绘制每个通道的均值和置信带,其中的置信度是在所选条件的各个 epoch 上计算得到的:

plt.figure()
channels = ["MEG 1332", "MEG 1342"]
data = long_df.loc["auditory/left"].query("channel in @channels")
# convert channel column (CategoryDtype → string; for a nicer-looking legend)
data["channel"] = data["channel"].astype(str)
data.reset_index(drop=True, inplace=True)  # speeds things up
sns.lineplot(x="time", y="value", hue="channel", data=data)
50 epochs to data frame

现在,我们还可以充分利用 Pandas 的强大功能来对数据分组和变换。这里,我们找出 2 个梯度计(一个靠近听觉皮层,一个靠近视觉皮层)峰值激活的潜伏期,并用 violinplot() 绘制每个通道峰值出现时间的分布:

plt.figure()
df = epochs.to_data_frame(time_format=None)
peak_latency = (
    df.filter(regex=r"condition|epoch|MEG 1332|MEG 2123")
    .groupby(["condition", "epoch"])
    .aggregate(lambda x: df["time"].iloc[x.idxmax()])
    .reset_index()
    .melt(
        id_vars=["condition", "epoch"], var_name="channel", value_name="latency of peak"
    )
)
ax = sns.violinplot(
    x="channel",
    y="latency of peak",
    hue="condition",
    data=peak_latency,
    palette="deep",
    saturation=1,
)
50 epochs to data frame

脚本总运行时间:(0 分钟 4.167 秒)

图库由 Sphinx-Gallery 生成