静息态EEG微状态组水平聚类实战:Cartool全流程与避坑指南
2026/9/20 12:12:27 网站建设 项目流程

静息态EEG微状态分析这件事,我前前后后折腾了快两年。最开始用脚本自己拼流程,后来被组里做临床的同事拉着复现一批数据,才认真把Cartool这套工具链跑通。微状态分析听起来玄乎,其实核心就两件事:把连续EEG信号在每个时间点上归到有限几个"头皮电位地形图"里(聚类),再拿这套地形图去套新数据(模板匹配)。单被试分析相对好办,真正让人头大的是组水平——十几个被试、每人几十分钟数据,怎么聚出一套大家都能用的模板,这里面坑特别多。

这篇东西写给已经跑过单被试微状态、准备往组水平推进的人,也写给被Cartool那套有点反直觉的界面劝退过的同行。我会把组水平聚类的完整链路拆开讲:数据怎么准备、聚类参数怎么定、模板怎么匹配、结果怎么读,以及我在实际操作里踩过的那些坑。全程以Cartool为主线,涉及到的原理和参数选择逻辑,换成其他工具也一样适用。

1. 先搞清楚组水平微状态到底在聚什么

很多人一上来就急着点按钮,结果聚类出来的模板乱七八糟,回头根本没法解释。问题往往出在没想明白组水平聚类的对象是什么。

1.1 单被试聚类和组水平聚类的本质区别

单被试微状态分析,聚类的对象是这一个被试所有时间点的头皮电位地形图。假设采样率1000Hz,记录10分钟,那就是60万个地形图(每个地形图是一个N通道的电压向量)。聚成4到7个微状态,每个微状态就是这个被试数据里反复出现的一种"稳定地形"。

组水平聚类不一样。它的对象不是所有被试的所有时间点直接堆在一起——那样会被数据量大的被试主导。主流做法是两级策略:先对每个被试单独聚类,得到每个被试的若干模板(比如每人5个),再把这些模板汇总起来做第二次聚类,得到组水平模板。Cartool里对应的就是"individual clustering"加"group clustering"两步走。

这个设计背后的逻辑很实在:每个被试的头皮地形受头型、电极位置、阻抗影响,直接混在一起聚,个体差异会被当成噪声。先各自提取代表性地形,再汇总,相当于让每个被试"投票",权重更均衡。

1.2 为什么模板数量不能拍脑袋定

微状态个数K的选择是这门分析里最容易被质疑的点。文献里常见4个(经典的四微状态A/B/C/D),但那是针对静息态闭眼数据的经验值。你要是做运动想象、做高密度运动EEG,地形模式可能更多。

判断K的合理方法有几个,我实际用下来比较靠谱的是交叉验证加解释方差:对K从3到8分别聚类,看全局解释方差(GEV, Global Explained Variance)随K增长的曲线。K增大GEV必然上升,但会出现拐点,拐点之后收益递减。另一个是Silhouette或Davies-Bouldin指标,Cartool本身不直接给,但可以把聚类结果导出后用Python算。

我的经验是:静息态闭眼数据,K=4或5通常够用;如果加了睁眼、任务态,K=6到7更稳。别迷信固定值,一定要看你自己数据的GEV曲线。

1.3 组水平模板的"代表性"怎么保证

组水平模板要能代表整个群体,前提是每个被试贡献的模板质量过关。这里有个容易被忽略的点:被试间模板的对应关系不是自动的。你聚出5个组模板,怎么知道模板1在被试A和被试B里指的是同一个微状态?Cartool的组聚类会给出模板间的空间相关性矩阵,你需要检查这些相关性是否够高(一般要求组模板之间相关性低于0.7到0.8,否则说明模板冗余,K取大了)。

提示:组水平聚类前,务必确认每个被试的电极排布一致、参考一致、滤波一致。任何一步不一致,聚类结果都会失真,而且这种失真很难事后补救。

2. 数据准备阶段那些不起眼但要命的细节

组水平分析翻车,十有八九是数据准备阶段埋的雷。这一节我把Cartool导入数据前必须处理干净的东西列清楚。

2.1 电极坐标与通道对齐

Cartool做地形图聚类,依赖电极的三维坐标来插值和绘图。如果你的电极坐标文件(.xyz或.elc)和实际数据通道顺序对不上,聚出来的地形图会左右颠倒或者位置错乱,但软件不会报错——这是最阴险的坑。

我的做法是:导入数据后,先在Cartool里看一眼2D地形图投影,确认Fz在前额正中、Cz在头顶中央、Oz在后枕。如果发现某个通道位置明显不对,回去检查坐标文件的通道命名和顺序。不同系统导出的命名习惯不一样(比如T3/T7、T4/T8、T5/P7、T6/P8),Cartool对命名敏感,最好统一成标准10-20或10-10命名。

2.2 参考方式对聚类的影响

参考电极的选择会直接改变头皮地形图的形状,进而影响聚类。静息态微状态研究里,平均参考(average reference)是最常用的,因为它让所有通道电压之和为零,地形图更"中性"。但平均参考要求通道数足够多(一般建议64导以上),通道太少时平均参考会引入虚假的远场效应。

如果你用的是32导或更少,可以考虑CZ参考或乳突平均参考,但要在方法里写清楚,并且组内所有被试必须统一。我见过有人一半被试平均参考、一半乳突参考,聚出来的组模板根本没法看。

2.3 滤波、去伪迹与分段策略

微状态分析对低频成分敏感,因为微状态反映的是大规模网络的准稳定状态。标准做法是0.5到2Hz的高通30到40Hz的低通(或50Hz陷波去工频)。高通别设太高,否则会削掉微状态本身的慢变特征;低通别设太低,否则肌电残留会污染地形图。

去伪迹方面,眼电、肌电、心电都要处理。ICA是常用手段,但要注意:剔除ICA成分后要检查重建信号的地形图是否还合理。有时候过度剔除会把真实的脑电地形也削掉。分段策略上,静息态一般取2到4秒的连续段,段与段之间不重叠,避免时间自相关影响聚类。

处理步骤推荐参数常见错误
高通滤波0.5-2 Hz设到0.1Hz导致漂移残留
低通滤波30-40 Hz设到20Hz削掉有效信号
工频陷波50Hz或60Hz忘记按地区电网频率设置
参考平均参考(≥64导)通道少时强行平均参考
分段2-4秒连续段段间重叠导致自相关

2.4 坏导插值与数据质量门槛

坏导必须插值,但插值前要记录哪些通道被插了。如果一个被试插值通道超过总通道数的10%到15%,这个被试的数据质量就存疑了,建议在组水平分析里标记出来,必要时剔除。

Cartool的插值用的是球面样条,插值后地形图会变平滑,所以坏导多的被试地形图会偏"糊",聚类时容易和其他被试对不上。我一般会在组聚类前,把所有被试的坏导数量列个表,超过阈值的单独评估。

3. Cartool组水平聚类的完整操作链路

这一节是核心,我把从单被试聚类到组模板生成的每一步都拆开,包括参数在哪设、为什么这么设。

3.1 单被试聚类:先把每个人的模板提出来

打开Cartool,加载预处理好的数据(通常是.eeg或.eph格式)。第一步是Individual Clustering

操作路径大致是:菜单里找到Segmentation或Microstate相关选项,选择对当前被试做聚类。关键参数有三个:

  • K值(聚类个数):先按经验设4到6,后面再优化。
  • 聚类算法:Cartool默认用的是改进的K-means变体(有些版本叫modified K-means),它对初始化和地形图的空间平滑有处理。别去改成普通K-means,普通K-means对EEG地形图容易陷入局部最优。
  • 忽略极性:这个选项一定要勾上。微状态分析里,地形图的正负翻转(比如A变成-A)被认为是同一个微状态,因为脑电的极性取决于参考,不代表不同的网络状态。不勾这个,聚类结果会翻倍且混乱。

跑完单被试聚类,你会得到该被试的K个模板地形图,以及每个时间点归属哪个微状态的时间序列。先别急着往下走,检查一下这几个模板:它们之间的空间相关性是否都低于0.7?如果有两个模板相关性很高,说明K取大了,回去减K重跑。

3.2 汇总所有被试模板:组聚类的输入准备

单被试都跑完后,需要把每个被试的模板汇总。Cartool的做法是把所有被试的模板地形图作为新的数据集,再做一次聚类。这一步在界面上通常叫Group Clustering或Grand Average Clustering。

这里有个操作细节:汇总时,每个被试贡献的模板数量应该一致(比如都取5个)。如果有的被试取了4个、有的取了6个,汇总时权重就不均了。所以单被试阶段就要统一K值,或者至少统一到相近的数量。

汇总后的数据集大小是:被试数×每人模板数。比如15个被试、每人5个模板,就是75个地形图。对这75个地形图再聚类,聚成组水平的K个模板(通常还是4到7个)。

3.3 组聚类参数:K值、迭代次数与收敛判据

组聚类的参数设置和单被试类似,但K值的选择更关键,因为它直接决定最终模板。

我的实操流程是这样的:

  1. 先对组数据跑K=3到8,每个K跑多次(因为K-means有随机初始化,跑10到20次取最优)。
  2. 记录每个K下的GEV和模板间相关性。
  3. 选GEV拐点且模板间相关性都低于0.7的K。

Cartool里可以设置迭代次数和收敛阈值。迭代次数别设太低(默认可能就几十次),我一般设到200到500次,收敛阈值用默认的就行。跑多次取最优这个功能,如果Cartool版本支持就一定要用,能显著降低随机性带来的不稳定。

3.4 模板匹配:把组模板套回每个被试

组模板生成后,最后一步是Template Matching(模板匹配)。这一步是把组水平的K个模板,逐一去匹配每个被试的每个时间点,得到每个被试在组模板体系下的微状态时间序列。

匹配的判据是空间相关性最大:对某个时间点的地形图,计算它和K个组模板各自的空间相关系数(考虑极性),归到相关性最高的那个模板。Cartool会自动完成这一步,输出每个被试的微状态序列和对应的统计量(比如每个微状态的持续时间、出现频率、覆盖率、转换概率)。

注意:模板匹配和重新聚类是两回事。匹配不改变模板,只是给每个时间点贴标签。如果你发现匹配后某个被试的GEV特别低(比如低于60%),说明组模板不太适合这个被试,可能是个体差异太大,需要在结果里说明。

3.5 结果导出与后续统计

Cartool能导出每个被试的微状态指标:持续时间(duration)、出现频率(occurrence)、覆盖率(coverage)、全局解释方差(GEV)、转换概率矩阵。这些指标导成表格后,就可以拿去SPSS、R或Python做组间统计了。

导出时注意时间单位。Cartool默认可能用毫秒或秒,统计前统一。转换概率矩阵是个K×K的矩阵,对角线是自转换(一般不看),非对角线是微状态之间的转移,做组间比较时通常关注特定转移路径。

4. 聚类结果不稳定?先排查这几个地方

组水平聚类最让人崩溃的就是"跑一次一个样"。这一节我把导致不稳定的常见原因和排查方法讲透。

4.1 随机初始化导致的模板漂移

K-means类算法对初始聚类中心敏感。Cartool虽然做了改进,但如果你只跑一次,结果可能不是全局最优。解决办法就是多次运行取GEV最高的那次。我一般跑20次,如果20次里GEV最高的和最低的差超过5%,说明数据本身聚类结构不清晰,要么K不对,要么数据质量有问题。

4.2 被试间数据量差异过大

如果被试A有20分钟数据、被试B只有5分钟,直接汇总模板时A的模板会被过度代表。虽然两级聚类已经缓解了这个问题,但如果差异太悬殊,还是会有偏。我的做法是在单被试阶段就控制数据量,比如每个被试都截取相同长度的干净数据(比如都取5分钟),保证贡献均衡。

4.3 电极排布不一致的隐蔽影响

前面提过电极坐标的问题,这里再强调一次:组水平分析要求所有被试的通道完全一致。如果某个被试少了几个通道,要么插值补齐,要么整个被试剔除。Cartool对通道不一致的处理不够智能,经常是默默算错。

排查方法:把所有被试的通道列表导出来对比,用脚本diff一下,确保完全一致。

4.4 GEV偏低时的处理思路

如果组模板的GEV普遍偏低(比如平均低于65%),说明模板对数据的解释力不够。可能的原因:K太小、数据噪声太大、被试间差异太大。处理顺序是:先加K试试,再看数据质量,最后考虑是不是这批被试本身就不适合放在一起做组分析。

问题现象可能原因排查动作
模板每次跑都不一样随机初始化多次运行取最优
某被试GEV特别低个体差异大单独检查该被试数据
模板间相关性高K取大了减小K重跑
地形图位置错乱电极坐标不对检查坐标文件
组模板解释力差数据量不均或噪声大控制数据量、加强去伪迹

5. 从Cartool到Python:把结果接进自己的分析流

Cartool的界面操作适合探索,但批量处理和自定义统计还是得靠脚本。这一节讲怎么把Cartool的结果导出来,用Python接着做。

5.1 导出格式与数据结构

Cartool通常能导出文本格式的微状态时间序列和模板地形图。时间序列一般是一列标签(每个时间点属于哪个微状态),模板是K×通道数的矩阵。导出后建议先做一次完整性检查:时间点总数是否和原始数据一致、标签值是否都在1到K之间。

5.2 用Python复算微状态指标

拿到时间序列后,持续时间、频率、覆盖率这些指标都可以自己算,比依赖软件导出更灵活。核心逻辑是:遍历标签序列,统计每段连续相同标签的长度(持续时间),统计每个标签出现的总次数(频率),覆盖率是该标签占用的时间点比例。

import numpy as np def microstate_metrics(labels, sfreq): # labels: 每个时间点的微状态标签(1..K) # sfreq: 采样率 K = int(labels.max()) metrics = {} for k in range(1, K+1): mask = (labels == k) coverage = mask.mean() # 计算连续段 diff = np.diff(np.concatenate([[0], mask.astype(int), [0]])) starts = np.where(diff == 1)[0] ends = np.where(diff == -1)[0] durations = (ends - starts) / sfreq metrics[k] = { 'coverage': coverage, 'occurrence': len(starts) / (len(labels)/sfreq), 'mean_duration': durations.mean() if len(durations) > 0 else 0 } return metrics

这段代码我用了很久,注意occurrence的单位是"每秒出现次数",别和总次数搞混。

5.3 转换概率矩阵的计算

转换概率矩阵描述微状态之间的转移倾向。计算方法是统计从微状态i转移到微状态j的次数,再按行归一化。对角线(自转换)通常置零或单独处理。

def transition_matrix(labels, K): T = np.zeros((K, K)) for i in range(len(labels)-1): a, b = int(labels[i])-1, int(labels[i+1])-1 if a != b: T[a, b] += 1 # 按行归一化 row_sums = T.sum(axis=1, keepdims=True) row_sums[row_sums == 0] = 1 return T / row_sums

5.4 组间统计的注意事项

把每个被试的指标汇总成表格后,做组间比较时要注意:微状态指标往往不满足正态分布,尤其是持续时间和频率。我一般先用Shapiro-Wilk检验正态性,不满足就用非参数检验(Mann-Whitney U或Kruskal-Wallis)。另外,多重比较校正别忘了,K个微状态×多个指标,不校正很容易假阳性。

6. 几个我踩过的坑和对应的解法

这一节全是血泪教训,都是文档里不会写、但实际做的时候一定会遇到的东西。

6.1 极性忽略没勾导致模板翻倍

第一次做组聚类时,我忘了勾"忽略极性",结果聚出来的模板里有一半是另一半的镜像。当时还纳闷为什么K=4聚出来像8个。后来才明白,脑电地形图的正负取决于参考,同一个网络状态可能因为参考不同而极性相反。这个选项在单被试和组聚类里都要勾,漏一个都不行。

6.2 数据长度不一致导致模板偏移

有次组分析,15个被试里有3个数据特别长(20分钟以上),其他都是5分钟。结果组模板明显偏向那3个长数据的被试。后来我把所有被试都截到5分钟,模板立刻稳定了。组水平分析里,数据量的均衡比数据量的大小更重要

6.3 滤波参数不统一导致地形图对不上

还有一次,部分被试用了0.5Hz高通,部分用了1Hz高通。单看每个被试都没问题,但组聚类时模板相关性怎么都上不去。统一滤波参数后问题解决。组内所有预处理参数必须完全一致,这是铁律。

6.4 模板匹配后GEV骤降的排查

模板匹配后,如果某个被试的GEV比单被试聚类时低很多,先别急着下结论说这个被试特殊。检查顺序是:通道顺序对不对、参考一致不一致、数据段是不是同一批。我遇到过一次是导出时通道顺序被打乱了,重新导出就好了。

6.5 Cartool版本差异带来的操作困惑

Cartool不同版本的菜单名称和参数位置有差异,网上教程经常对不上。我的建议是:以你手头版本的官方文档为准,别硬套别人的截图。核心参数(K值、忽略极性、迭代次数)的逻辑是不变的,界面变了不影响理解。

7. 关于高密度运动EEG场景的一点延伸

现在做高密度运动EEG的人越来越多,微状态分析在这个场景下有些特殊考虑,顺便说一下。

运动EEG的伪迹比静息态严重得多,肌电、运动相关电位、电极位移都会污染地形图。做微状态分析前,去伪迹要更激进,但又要小心别把真实的运动相关网络削掉。我的经验是:先用ICA去掉明显的肌电和眼电成分,再用独立成分的偶极子拟合辅助判断哪些成分该留

另外,运动态下微状态个数可能比静息态多,因为运动准备、执行、恢复可能对应不同的网络状态。K值选择要更谨慎,GEV曲线要仔细看。高密度(64导以上)在这个场景下优势明显,因为地形图分辨率高,聚类更稳。

如果你用Python做层次聚类或K-means来交叉验证Cartool的结果,注意EEG地形图的聚类和普通数据的聚类不一样:要考虑空间平滑和极性。直接用scikit-learn的K-means跑原始地形图,结果往往和Cartool对不上,因为Cartool做了针对EEG的优化。交叉验证时,建议先把地形图做空间平滑,再聚类,结果会更接近。

最后分享一个我常用的检查习惯:每次组聚类跑完,把K个组模板的地形图并排画出来,肉眼过一遍。如果某个模板看起来"不像"典型的头皮地形(比如能量集中在边缘、或者形状很怪),大概率是聚类出了问题或者数据里有残留伪迹。这一步花不了几分钟,但能拦住很多后续的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询