信息速览
2016 年 7 月到 2017 年 3 月,澳大利亚查尔斯特大学的研究者在项目网站上挂出一个 Windows 后台程序,请帕金森患者和健康人自己下载安装,装完就不再干预。受试者照常写邮件、改文档、搜网页,程序在后台把每一次按键的按下与抬起写进本地 CSV,一天往服务器上传一次。九个月后,散落在美国、加拿大、英国、澳大利亚四国家用电脑上的这批记录,被整理成 PhysioNet 上的 Tappy Keystroke Data,2017 年 10 月 20 日以 1.0.0 版发布,许可为 ODC-BY 1.0。
它与诊室里做出来的击键数据最大的不同,是全程没有人盯着。没有指定文本,没有固定时长,没有研究者在旁边计时;受试者可能凌晨三点敲一段代码,也可能下午回一封邮件。库里的文件因此按人按月切分,一个人的一个月构成一份 CSV,而不是一个人的一次会话一份。这也决定了它最适合回答的问题:当采集完全脱离实验室,手指的节奏里还剩下多少信息。
它不能拿来训练一个能直接上门诊的诊断模型,也不能还原受试者打了什么字——程序只记左右手和键在键盘上的列位,不记字符。它更像一份采集方式的可行性证据,而不是一份产品级的训练集。一屏速览见下表。
| 维度 | 内容 |
|---|---|
| 全名 | Tappy Keystroke Data(Tappy 击键数据) |
| 版本与发布日期 | 1.0.0,2017 年 10 月 20 日 |
| 发布平台 | PhysioNet(MIT 计算生理学实验室维护) |
| 发起机构 | 澳大利亚查尔斯特大学,伦理批号 H17013 |
| 受试者规模 | 官方称二百余名,论文另称 217 人与 103 人 |
| 受试者来源 | 美国、加拿大、英国、澳大利亚四国网络自招募 |
| 年龄范围 | 论文写明入组条件为 50 至 80 岁 |
| 采集设备 | 受试者自己的 Windows 电脑,程序 Tappy 常驻后台 |
| 采集场景 | 全系统键盘钩子,跨应用,无监督,数周至数月 |
| 记录单元 | 一次按键一行;一个人的一个月一份 CSV |
| 事件字段 | 8 列:用户、日期、时刻、手别、按住、方向、延迟、飞行 |
| 参与者字段 | 13 项:代号、出生年、性别、患病、震颤、诊断年、侧别、UPDRS、影响、三类用药 |
| 时间精度 | 官方称数毫秒以内,论文写 3 毫秒 |
| 体积 | 页面标 85.1 MB;目录里事件包 85.0 MB、详情包 71.3 KB |
| 许可 | ODC-BY 1.0(论文数据声明另写作公共领域奉献) |
| 引用论文 | Adams WR,PLOS ONE 12(11): e0188226,2017 |
§1 它要解决的具体麻烦
1.1 早期帕金森最缺的不是药,是一个能反复测量的场合
帕金森病的运动症状评估长期靠量表。医生在诊室里让受试者走几步、抬抬手、对敲几下手指,打一个分。这套流程标准化程度高,跨中心可比,缺点同样明确:一次门诊只能得到一个时点,而症状本身在一天之内会随药效起落;一次评估要占用医生十几分钟,一年真正能做的次数屈指可数。
早期诊断对这个缺点的敏感度最高。早期最需要发现的是「还看不出来」的细微变化,而肉眼在诊室里看几眼,恰恰是捕捉这类变化最弱的手段。数字生物标志物的整个思路,就是把这个测量从诊室挪到日常生活里,让设备在没人监督的时候自己记。麻烦在于,多数可穿戴方案都要额外添硬件:腕带、鞋垫、贴片,受试者要记得充电、要戴得住、要接受身上多一个东西。
键盘是这条路上少数不需要新增任何设备的入口。一个人在电脑前坐一天,敲击上千次,每次都是一次伸出、按下、保持、抬起。这套动作由皮质—基底节—皮质环路调度,而这条环路正是帕金森病最早受损的位置。Tappy 想验证的就是:在完全不受控的家庭环境里,这层对应关系还能不能被看见。
1.2 三条常见的错误期待
第一条,期待它是一份带任务的打字测验。 名字里有 keystroke,又常和手指敲击测试并列出现,容易让人以为受试者被要求照着一段固定文本敲几分钟。实际情况正相反:没有任务,没有文本,没有计时员,程序挂在系统上,受试者干什么就记什么。任何以「固定刺激—固定反应」为前提的实验设计,在这个库上都要重新考虑。
第二条,期待它是一份文本日志。 事件表里没有字符,只有左右手与列位。你无法统计错字率、词汇丰富度、句法复杂度,也无法判断这个人当时在写信还是在写代码。这一层信息在设计阶段就被主动丢弃了,理由写在论文里:不记字符才能让数据成为不可识别信息。
第三条,期待它是一个统一队列。 受试者是陆续从网上报名的,各自的电脑不同、系统版本不同、键盘不同、每天敲多少也不同,记录跨度从几周到几个月不等。它更像一批平行进行的小型自我观察,而不是一次统一执行的采集。
1.3 它能给到的与给不到的
| 你的需求 | 它给的 | 够不够 |
|---|---|---|
| 家庭场景下自然打字的原始事件流 | 按人按月的八列 CSV,逐次按键落行 | 够,是它的主业 |
| 帕金森与对照的分组标签 | 参与者表中一列布尔值,自报 | 够用,但只有自报 |
| 疾病严重度的连续量 | 只有 1 至 5 档自评分与轻中重三档 | 不够,粒度太粗 |
| 临床量表原始分 | 没有任何 UPDRS-III 分项或总分 | 不够,结构上就没有 |
| 疾病进展的纵向观察 | 记录本身跨月,但无复评 | 部分,只能看打字行为随时间的变化 |
| 文本或语言层面的分析 | 不记字符,无输入法状态 | 不够,设计上排除 |
| 跨设备、跨系统的比较 | 每人的硬件都是自己的,但未标注型号 | 勉强,只能当随机噪声处理 |
| 用药与症状波动的对照 | 有用药类别,无服药时刻 | 部分,只能做组间不能做时点 |
| 与其它队列的合并 | 字段与标签口径都与门诊队列不同 | 不能按行合并,只能任务级配对 |
1.4 你的处境与它能帮到的事
| 你的处境 | 它能帮你什么 | 下一步该做什么 |
|---|---|---|
| 你在做被动感知,需要证明「装个软件就能收到几个月的键盘数据」 | 一份真实完成过的家庭采集,含事件结构与参与者表 | 先按 §9.2 的流程跑通人月索引,再谈模型 |
| 你手上有门诊队列的击键数据,想找一个外部验证集 | 字段与场景都与门诊数据不同,正好构成域外检验 | 读 §7.4,注意两库标签口径不可直接对齐 |
| 你在设计远程随访,担心受试者装不上、留不住 | 官方未公布留存率,但按月切片的文件结构本身就是留存证据 | 先统计每人月数,再判断可行性 |
| 你需要说明「不记字符也能做分析」在伦理上站得住 | 列位化的设计是一份现成的方法学先例 | 引用 §4.3 的设计说明作为论证材料 |
| 你在教被动数据的清洗,需要一个脏得真实的样本 | 跨月断档、跨设备、无监督,三种现实问题都在 | 用 §5.6 的六个坑做课堂练习 |
| 你要做击键与其它模态的融合 | 没有配对的影像、语音、可穿戴样本 | 另找同一批人的其它数据,这里只有键盘一支 |
1.5 一句话判断
它不是一份能训练出诊断模型的数据,而是一次把采集搬到受试者家里的完整尝试:证明在无人监督、不限文本、不记字符的条件下,键盘上仍然留得下可用的运动信息,并把这次尝试的全部原始事件摊开。
1.6 不适合谁:四条边界
不适合拿来做诊断模型的训练底座。 疾病状态靠自报,严重度靠自评,没有临床复核,也没有统一设备。在这样的标签上训练出来的分类器,衡量的是「这批自报数据里能不能分开」,不是「门诊里能不能筛出来」。
不适合拿来做流行病学的人群推断。 受试者是自己看到项目网站后主动报名的,年龄限定在 50 至 80 岁,四个英语国家,能用 Windows 电脑。这是一群高度自我选择的人,任何发生率、患病率一类的推断在这里都不成立。
不适合拿来做文本或认知分析。 没有字符,没有输入法,没有前后文。打字错误、句法复杂度、词汇多样性,一个都算不出来。
不适合拿它替代任何临床评估。 库里的 UPDRS 是一列 1 至 5 的自评分,与临床上按分项累加得到的 UPDRS-III 总分不是一回事。把两者放进同一个回归,等于把两把不同刻度的尺子接在一起。
§2 数据构成与规格
2.1 两个压缩包,一张事件表加一张人名表
整库的结构简单到几乎没有层级。根目录下只有三个文件:一个装事件、一个装人、一个装校验和。
| 路径 | 内容 | 体积 |
|---|---|---|
Archived-Data.zip |
全部受试者的月度击键事件 CSV | 85.0 MB |
Archived-users.zip |
每位受试者一行的参与者详情表 | 71.3 KB |
SHA256SUMS.txt |
两个压缩包的校验和 | 167 字节 |
两个包解开后并不形成复杂的目录树。事件包里是大量平铺的 CSV,文件名由十位代号与年月拼成;详情包里是每位受试者一份的详情文件,文件名就是那串十位代号。两者靠代号互相对上,除此之外没有别的连接键。
| 对比项 | 事件包 | 详情包 |
|---|---|---|
| 压缩体积 | 85.0 MB | 71.3 KB |
| 解压后的组织 | 平铺的月度 CSV | 每人一份的详情文件 |
| 文件名构成 | 十位代号加 YYMM | 十位代号 |
| 一行代表 | 一次按键 | 不适用,一人一文件 |
| 时间粒度 | 毫秒级时刻 | 年 |
| 是否含疾病信息 | 不含 | 含,全部为自报 |
2.2 字段的完整字典
字段分布在两处,先看人这一侧。详情文件的十三项,官方逐项给了取值说明。
| 字段 | 类型 | 取值形态 | 含义与使用注意 |
|---|---|---|---|
| 代号 | 字符串 | 十位随机字符 | 主键,同时是事件文件名前缀 |
| 出生年 | 整数 | 四位年份 | 只能算年龄,没有出生月日 |
| 性别 | 字符串 | Male / Female | 二值,无其它取值 |
| 患病 | 布尔 | True / False | 自报,未经核验 |
| 震颤 | 布尔 | True / False | 自报,与患病列相互独立 |
| 诊断年 | 整数或空 | 四位年份 | 仅患者有值,可据此粗算病程 |
| 侧别 | 字符串 | Left / Right / None | 自报的运动症状偏侧,None 表示无 |
| UPDRS | 整数或空 | 1 至 5 | 自评五档,不是量表总分 |
| 影响 | 字符串 | Mild / Medium / Severe | 自报的日常影响程度 |
| 左旋多巴 | 字符串 | Yes / No | 是否服用息宁一类药物 |
| 多巴胺激动剂 | 字符串 | Yes / No | 是否服用 DA 类 |
| MAO-B 抑制剂 | 字符串 | Yes / No | 是否服用 MAO-B 类 |
| 其它用药 | 字符串 | Yes / No | 是否服用其它帕金森药物 |
这张表里最容易被读错的是 UPDRS 一行。它的取值域是 1 到 5,官方说明写的是「如已知」的自评分。临床上 UPDRS 第三部分的分项累加可以到一百多分,两者量纲完全不同。看到这一列叫 UPDRS 就当成量表总分去做回归,是这个库上最常见的硬错误。
第二处容易读错的是侧别。Left、Right、None 三值描述的是自报的运动症状偏侧性,与事件表里的 Hand 列不是一回事:前者是人的属性,后者是这一次按键落在哪只手。两个 Left 放在同一句话里,含义完全不同。
再看事件这一侧,八列,官方逐列给了单位。
| 列名 | 类型 | 单位 | 含义与使用注意 |
|---|---|---|---|
| UserKey | 字符串 | 无 | 十位代号,与详情文件同名 |
| Date | 字符串 | 无 | YYMMDD,六位 |
| Timestamp | 字符串 | 时:分:秒.毫秒 | HH:MM:SS.SSS,当日时刻 |
| Hand | 字符 | 无 | L 或 R,仅左右手二值 |
| Hold time | 浮点 | 毫秒 | 当前键按下到抬起 |
| Direction | 字符串 | 无 | LL、LR、RL、RR,空格单记为 S |
| Latency time | 浮点 | 毫秒 | 上一键按下到当前键按下 |
| Flight time | 浮点 | 毫秒 | 上一键抬起到当前键按下 |
八列里有三个时间量,它们不是彼此独立的。已知按住时长与前后的按下时刻,飞行时间就可以由延迟减去上一键的按住时长推出来。这一点在 §4.2 展开,它决定了你在做特征工程时不能把三个量当成三个独立信号。
还有一处细节需要提前交代:Direction 一列把空格单独记成 S,而不是按左右手归类。这意味着空格被当作一个特殊事件,任何按左右手统计转移矩阵的代码,都要先把 S 单独处理,否则会在空格上得到一批无法归类的行。
2.3 一份月度文件的完整解剖
事件文件的命名规则是十位代号加年月,例如某个代号的人 2016 年 11 月的数据,文件名后半段就是 1611。文件内部是纯 CSV,没有表头,八列按固定顺序排列。官方没有公开任何一份原始文件的内容,下面这几行是按官方字段定义与论文时序图复原的结构样例,只用于说明列序与量级,不对应真实记录:
K7M2QX9P4T,161101,09:14:22.418,L,96.0,LL,412.0,188.0
K7M2QX9P4T,161101,09:14:22.902,R,88.0,LR,484.0,316.0
K7M2QX9P4T,161101,09:14:23.271,L,104.0,RL,369.0,281.0
K7M2QX9P4T,161101,09:14:23.688,L,91.0,LL,417.0,313.0
K7M2QX9P4T,161101,09:14:24.055,L,272.0,S,367.0,276.0
第一行说明:这个人在 2016 年 11 月 1 日上午九点十四分二十二秒按下了一个左手键,按住九十六毫秒;它与上一键同属左手,两次按下之间隔了四百一十二毫秒,上一次抬起之后隔了一百八十八毫秒才按下这一次。第二行换成右手,方向变成从左到右。第四行又是左手同侧。第五行的方向是 S,说明这是一次空格。
连起来看,同侧连续击键的飞行时间明显短于跨手击键,空格前后的延迟偏长。这是所有击键数据共有的规律,也正因为共有,它可以当作判断解析是否正确的自查。
文件末尾通常是这样:
K7M2QX9P4T,161130,22:41:07.663,R,79.0,RR,398.0,319.0
K7M2QX9P4T,161130,22:41:08.041,L,112.0,S,378.0,299.0
最后一行落在月末最后一天的深夜。这与「受试者在自己的电脑上随时打字」这一采集方式一致:记录的时间分布跟着人的生活节奏走,而不是跟着实验安排走。
2.4 一条记录的字段对应关系
把一行拆开,八个位置依次是代号、日期、时刻、手别、按住、方向、延迟、飞行。前四个是标识与定位,后四个才是测量值。写解析代码时,真正需要小心的是第三位:时刻的毫秒固定三位,不是可长可短的小数。用朴素的时间字符串解析,遇到跨零点、跨月或跨年时容易错位;稳妥做法是把日期与时刻拼成一个完整时间戳,再交给标准库处理。
2.5 与同类资源的字段差异
把 Tappy 与库内另一份击键数据、以及常见的公开击键库放在一张表里,差异会立刻显出来。
| 项 | Tappy | 门诊采集的击键库 | 通用击键生物识别库 |
|---|---|---|---|
| 采集地点 | 受试者家中 | 诊室或实验室 | 实验室 |
| 是否有监督 | 无 | 有 | 有 |
| 记录跨度 | 数周至数月 | 单场十几分钟 | 单场数分钟 |
| 是否指定文本 | 不指定 | 常指定 | 指定固定串 |
| 是否记字符 | 不记 | 常记 | 记 |
| 疾病标签 | 自报布尔值加自评五档 | 临床评分 | 通常没有 |
| 用药信息 | 三类布尔 | 常缺 | 没有 |
| 文件粒度 | 人月 | 人场 | 人场 |
| 典型用途 | 被动监测可行性 | 临床对照 | 身份识别 |
最后一行是三个库真正的分野。身份识别关心的是「这个人是不是他」,所以要求文本固定、环境可控;临床对照关心的是「这组人和那组人能不能分开」,所以要求有量表;而 Tappy 关心的是「把采集搬到家里还剩下什么」,所以它牺牲了文本与环境控制,换来的是时间跨度。
§3 它是谁的数据:一次自招募的跨国采集
3.1 作者、机构与出处
这份数据背后是一篇单作者论文。Adams WR 于 2017 年在 PLOS ONE 上发表《High-accuracy detection of early Parkinson’s Disease using multiple characteristics of finger movement while typing》,数据与论文同期公开。伦理审查由澳大利亚查尔斯特大学的人类研究伦理委员会出具,批号为 H17013,论文写明执行的是澳大利亚国家健康与医学研究委员会的要求,包含知情同意与匿名化。数据的托管与发布在 PhysioNet,平台由 MIT 计算生理学实验室维护,许可由页面 Access 一节声明。
单作者这一点在这个体量的数据上并不常见。二百多人、跨四国、历时九个月的家庭采集,通常需要一个团队分工做招募、开发、运维与统计;这里由一人完成,代价是文档相对简略:官方页面没有给出逐人月数、按键总量、患者与对照的具体比例,也没有给出采集程序的留存率。
3.2 时间线
| 时间 | 事件 | 依据 |
|---|---|---|
| 2016 年 7 月 | 项目网站开放报名,开始招募 | 论文方法与受试者一节 |
| 2016 年 7 月至 2017 年 3 月 | 报名窗口,期间陆续有人安装程序 | 同上 |
| 2017 年 10 月 20 日 | 数据以 1.0.0 版上线 PhysioNet | 版本页 |
| 2017 年 11 月 | 论文发表于 PLOS ONE | 卷期与文章号 |
| 2017 年 11 月 22 日 | 两个压缩包的归档时间 | 目录清单 |
| 2019 年 2 月 20 日 | 补入校验和文件 | 目录清单 |
数据的上线早于论文正式出版约两周。此后版本号没有变过,目录里只多了一个校验和文件,两个压缩包的修改时间都停在 2017 年 11 月。
3.3 采集场景:一台不属于研究者的电脑
采集的技术路径写在论文的方法一节里,细节比官方页面的两句话多得多。
| 环节 | 做法 | 对数据的影响 |
|---|---|---|
| 分发 | 受试者从项目网站自行下载安装 | 无现场指导,安装失败者不在数据里 |
| 常驻方式 | 后台进程,随系统启动 | 记录覆盖全天,不限于某几个时段 |
| 截获方式 | 调用 Windows 系统级键盘钩子 | 全系统生效,跨邮件、文档、浏览器 |
| 记录范围 | 只记左右手五列对应的键,排除数值键区 | 无数字串,无密码区输入 |
| 字符处理 | 不记字符,只记左右手与列位 | 数据不可识别,但文本分析不可能 |
| 时间精度 | 论文写 3 毫秒,页面写数毫秒以内 | 足够区分按住与飞行,不足以做力估计 |
| 落盘 | 本地持续写 CSV | 断电或崩溃会丢当段记录 |
| 上传 | 每日一次增量上传 | 断网期间数据留在本地,可能积压 |
第三行与第四行是这个库最本质的设计选择。系统级钩子让它能覆盖受试者的全部输入行为,而不是某一次被指定的任务;排除数值键区则切掉了密码、卡号、金额这类最敏感的输入。两者合起来,构成了一套「看得见节奏、看不见内容」的折中。
折中的代价是:同一左手列位可能对应好几个键,这些键的差别在数据里完全消失,方向列因而只剩 LL、LR、RL、RR 与空格五种取值。做序列分析时,可用的字母表就只有手别与列位。
3.4 受试者是谁
受试者的画像是几项条件叠加的结果,每一项都在收窄人群。
| 条件 | 取值 | 收窄效果 |
|---|---|---|
| 年龄 | 50 至 80 岁 | 排除青年与高龄两端 |
| 设备 | 装有 Windows 的个人电脑 | 排除 Mac、Linux、平板与手机用户 |
| 语言环境 | 英文项目网站 | 事实上以四个英语国家为主 |
| 报名方式 | 主动访问网站并同意 | 强自我选择,偏向关注该话题的人 |
| 电脑使用习惯 | 每天都在电脑上打字 | 排除极少用电脑者 |
| 疾病状态 | 自报患病或健康 | 无临床核验 |
这六条叠起来,得到的是一个相当特定的群体:五十岁以上、日常用 Windows 电脑、讲英语、对帕金森研究有兴趣、愿意在自己机器上装一个后台程序。这个群体既不代表帕金森患者总体,也不代表一般电脑用户。
论文给这群体贴了一个标签叫 Group A,并说明完整数据集由 217 人构成;官方页面则写作二百余人。两个口径指向同一批人,差别只在「完整」二字的边界。这一点在 §7.2 单独处理。
§4 它最独特的东西:人月切片,且不记字符
4.1 为什么「一个人的一个月」这个粒度值得单独成库
绝大多数行为数据集的时间单位是「场」:一次实验、一段任务、一个疗程。这个单位的隐含假设是,采集是有边界的,起点与终点由研究者划定。Tappy 把单位换成了「月」,边界由日历划定,采集的起止则交给受试者自己的生活。
这个替换带来三个直接后果。第一,样本量不再由人数决定,而由人月数决定,同一个人可以贡献十几份文件,也可能只贡献一份。第二,同一个人在不同月份之间的差异,变成了可分析的对象:季节、病程、用药调整、换电脑,都会落在相邻两个月的对比里。第三,缺失变成了常态——受试者某个月不开机、卸载了程序、或者那个月敲得太少,都会让这个人的序列出现断档。
| 维度 | 以场为单位 | 以月为单位(本库) |
|---|---|---|
| 边界由谁定 | 研究者 | 日历 |
| 每单位时长 | 十几分钟到一小时 | 自然月,实际覆盖不规则 |
| 样本计数 | 人数约等于场数 | 人月数远大于人数 |
| 纵向对比 | 需要专门设计随访 | 天然存在,只要连续两个月都有数据 |
| 缺失形态 | 整场缺失 | 断月,序列出现空洞 |
| 分析单位 | 受试者 | 受试者,或受试者月 |
第二行与第五行是一对。以场为单位的数据,缺失通常表现为「这个人没来」;以月为单位的数据,缺失表现为「这个人这个月没敲」,而人还在,前后月份都在。这让清洗逻辑必须换一套:不能简单地按人删掉,而要决定断月之后是否续接。
4.2 三个时间量之间的恒等关系
八列里最需要说清楚的是 Hold、Latency、Flight 三者的关系。它们描述同一段时间轴上的三段,而不是三个独立测量。
| 量 | 起点 | 终点 | 与其它量的关系 |
|---|---|---|---|
| 按住时长 | 当前键按下 | 当前键抬起 | 与前后的键无直接恒等 |
| 延迟时间 | 上一键按下 | 当前键按下 | 等于上一键按住加飞行时间 |
| 飞行时间 | 上一键抬起 | 当前键按下 | 等于延迟减去上一键按住 |
第二行与第三行的关系是可以直接验算的:延迟减去上一键的按住时长,应当等于飞行时间。在实际文件里,这个等式会因为四舍五入与毫秒截断出现零点几毫秒的偏差,偶尔也会因为键盘固件的上报顺序出现更明显的异常。写解析器时,把这条恒等式当作质量检查的第一道闸,比事后看分布更有效。
两者在语义上容易混:延迟量的是两次按下的间隔,飞行量的是上一次抬起之后到下一次按下之间的空隙,前者包含后一次的按住段。论文把两者分作两个特征族,正因为它们在临床上对应不同环节——按住偏执行力,延迟偏启动与转换。
4.3 只记左右手与列位:隐私换来的代价
这个库在隐私上的做法比多数公开数据集更彻底。它不记字符,不记数值键,不记设备型号,不记姓名与联系方式,只用一串随机生成的十位代号。论文的说法是,这样的处理让数据成为不可识别信息。
| 设计选择 | 换来什么 | 失去什么 |
|---|---|---|
| 不记字符 | 无法还原输入内容 | 文本、语义、错字分析全部不可能 |
| 只记到列位 | 同一列的多个键不可区分 | 无法做按键级 n-gram |
| 排除数值键区 | 密码、卡号、金额不会入库 | 数字输入相关的行为完全空白 |
| 随机十位代号 | 无法反查到人 | 也无法与其它数据集做个人级链接 |
| 不记设备型号 | 免去硬件指纹 | 跨设备差异只能当噪声处理 |
第四行是容易被忽略的连带损失:代号随机生成且官方不公布映射,这批人无法与任何其它队列按个体对齐,想找同一批人的影像或语音是没有路的。
第五行的后果出现在建模阶段。同一个人的记录可能横跨两台电脑:换机器、公司配新机、家里添了台式机。程序不记型号,所以这种切换在数据里表现为一段无法解释的分布漂移。做纵向分析时,至少要把「同月内出现两套节奏特征」当作可疑信号排查一遍。
4.4 案例:代号 K7M2QX9P4T 的 2016 年 11 月
为了让结构具体起来,取一个代号为 K7M2QX9P4T 的受试者、2016 年 11 月这一份月度文件作为贯穿案例。需要提前说明:官方没有公开任何一份原始文件的逐行内容,下面的数值是按官方字段定义与论文的时序图复原的结构样例,只用于说明字段关系与量级,不对应任何真实受试者。
| 这一份的可核对项 | 取值 | 依据 |
|---|---|---|
| 文件名 | K7M2QX9P4T1611.csv |
代号加 YYMM 的命名规则 |
| 覆盖时段 | 2016 年 11 月 1 日至 30 日 | 文件内 Date 列 |
| 首条记录时刻 | 09:14:22 | 文件首行 |
| 末条记录时刻 | 22:41:08 | 文件末行 |
| 涉及手别 | L 与 R 均有 | Hand 列 |
| 方向取值 | LL、LR、RL、RR、S 五类 | Direction 列 |
| 按住时长量级 | 数十至二百余毫秒 | Hold 列 |
| 延迟量级 | 三百至五百毫秒 | Latency 列 |
| 飞行时间量级 | 一百至三百余毫秒 | Flight 列 |
选它作案例,是因为月份完整、首尾都有记录,适合演示断月判定与月度聚合。§5.4 会用它演示解析,§7.3 会用它说明自报标签的解释困难,§9.2 会用它跑一遍最短流程。
还有一件事要强调:单个文件只回答「这个月敲了多少、节奏如何」,回答不了「这个月症状如何」。严重度在参与者表里,是一个全年不变的自评分,与月度文件没有时间对齐关系——这是它在纵向分析上最大的结构性限制。
4.5 它支撑的三类实验
| 实验类型 | 需要的条件 | 本库能否支撑 |
|---|---|---|
| 组间分类(患者对对照) | 有分组标签,样本量足够 | 能,但标签是自报 |
| 个体内纵向追踪 | 同一人多月连续记录 | 能,前提是这个人月数够 |
| 域外泛化检验 | 与训练集场景不同的数据 | 能,场景与门诊数据差异很大 |
| 用药前后对比 | 有服药时刻 | 不能,只有用药类别 |
| 症状与打字量的因果推断 | 有第三方评估 | 不能,无复评 |
| 设备与输入法的影响分解 | 有设备标识 | 不能,型号未记录 |
第一行与第三行是它最常用的两个位置。第二行是它相比门诊数据真正的长项,但受制于断月,实际可用的连续序列比想象中少。第四行最容易被误以为可行:库里有三类用药的布尔值,看上去像能分析用药效应,实际上没有服药时刻,也没有开期关期标注,只能做「用药组与未用药组」的横断比较。
4.6 边界条件:什么时候它不再适用
当研究问题需要文本时。 没有字符,任何与内容相关的假设都无法检验。
当研究问题需要精确的严重度时。 自评五档与轻中重三档,最多支持三分类,做回归没有意义。
当研究问题需要人群代表性时。 自我选择的样本无法外推,这一点无法靠统计方法弥补。
当研究问题需要设备无关性时。 每人一套硬件,且型号未记,跨设备的方差被混进了个体差异里。
当研究问题需要与临床时点对齐时。 月度文件与自评标签之间没有共同的时间轴。
这五条边界不是缺陷清单,而是这个库的设计边界。它选择用「广而浅」换取了真实场景,代价是深度上的每一处让步。
§5 怎么拿到:从两个压缩包到人月索引
5.1 目录与体积
官方提供三种取法:浏览器直接下载 ZIP、用 wget 递归、用 AWS 命令行同步。三种取到的内容一致,都是根目录那三个文件,选用哪种只取决于网络条件与是否要脚本化。
| 文件 | 压缩体积 | 解压后形态 |
|---|---|---|
Archived-Data.zip |
85.0 MB | 平铺的月度 CSV |
Archived-users.zip |
71.3 KB | 每人一份的详情文件 |
SHA256SUMS.txt |
167 字节 | 两行校验和 |
85 MB 这个体量对现代机器不算大,一次读完并全部放进内存是可行的,前提是只保留需要的列。八个字段全部读入并转成对象,内存占用会明显上升,因为时间戳与代号都是字符串。
5.2 批量获取与校验
#-- 建目录,按官方给的三种取法里最稳的一种拉两个包
mkdir -p tappy && cd tappy
curl -sL https://physionet.org/files/tappy/1.0.0/Archived-Data.zip -o Archived-Data.zip
curl -sL https://physionet.org/files/tappy/1.0.0/Archived-users.zip -o Archived-users.zip
curl -sL https://physionet.org/files/tappy/1.0.0/SHA256SUMS.txt -o SHA256SUMS.txt
#-- 校验和先过一遍,再解压
sha256sum -c SHA256SUMS.txt
#-- 解开,看两边的文件数是否对得上
unzip -q Archived-Data.zip -d data
unzip -q Archived-users.zip -d users
find data -name '*.csv' | wc -l
ls users | wc -l
最后两行是第一个体检项。事件侧的文件数应远大于参与者侧:一个人可以有多个月,而人只有一个。两边接近说明多数人只贡献了一个月,纵向空间很小;差距极大则说明少数人贡献了绝大多数月份,聚合时要防止个别人主导。
5.3 建立人月索引
第二件事不是读事件,而是先把文件清单变成一张表:谁、哪个月、多少条、属于哪一组。
import re
from pathlib import Path
import pandas as pd
root_data = Path("tappy/data")
root_users = Path("tappy/users")
#-- 参与者详情:一人一文件,按官方十三项读入
def read_user(p):
d = {}
for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
if ":" in line:
k, v = line.split(":", 1)
d[k.strip()] = v.strip()
d["code"] = p.stem
return d
users = pd.DataFrame([read_user(p) for p in root_users.iterdir() if p.is_file()])
users["pd"] = users["Parkinsons"].str.lower().eq("true")
#-- 事件文件:文件名去掉扩展名,后四位是 YYMM
rows = []
for p in sorted(root_data.glob("*.csv")):
stem = p.stem
rows.append({"code": stem[:-4], "ym": stem[-4:], "path": str(p)})
months = pd.DataFrame(rows)
idx = months.merge(users[["code", "pd", "Birth Year", "Gender", "Impact"]],
on="code", how="left")
print(idx.groupby("pd")["code"].nunique())
print(idx.groupby("code").size().describe())
这段代码的关键在 stem[:-4] 与 stem[-4:]。代号固定十位,年月固定四位,所以从右侧切四位是安全的,即使代号里恰好含数字也不会切错。用固定偏移而不是正则,是为了避免不同批次文件名格式微小差异导致的漏切。
最后一行输出的分布,回答的是「一个人平均贡献几个月」。这个数官方没给,只能自己数;它决定了你能不能做个体内的纵向分析。
5.4 解析一份月度文件
拿 §4.4 的案例文件做示范。八列无表头,必须显式给列名,否则第一行数据会被当表头吞掉。
import pandas as pd
COLS = ["user", "date", "ts", "hand", "hold", "direction", "latency", "flight"]
f = "tappy/data/K7M2QX9P4T1611.csv"
df = pd.read_csv(f, header=None, names=COLS, dtype={"user": str, "date": str})
#-- 拼出完整时间戳,跨月排序才不会错
df["t"] = pd.to_datetime(df["date"] + " " + df["ts"], format="%y%m%d %H:%M:%S.%f")
df = df.sort_values("t").reset_index(drop=True)
#-- 恒等式自检:延迟减上一键按住,应约等于飞行
chk = (df["latency"] - df["hold"].shift(1) - df["flight"]).abs()
print("条数", len(df), "恒等式偏差中位", round(chk.median(), 3))
#-- 断档:相邻两次按键间隔超过一小时的次数
gap = df["t"].diff().dt.total_seconds()
print("超一小时的空档", int((gap > 3600).sum()))
第三行是解析流程里最值钱的一步:偏差中位数在零点几毫秒说明列序读对,若是几百毫秒甚至符号相反,说明列序读反,后面所有特征都会错。
第五行量的是「这个月是连着敲还是零零散散敲」。只有几次长会话的人与每天都在敲的人,节奏特征不是一回事,而这个量没有现成列,只能从时间差里算。
5.5 把事件流折成特征
事件流本身不能直接喂给分类器,要按人(或人月)折成一行。论文的做法是导出 27 个特征,按住类九个、延迟类十八个,再用线性判别分析降维。下面的片段只做最小的一组,用于演示聚合方式。
import numpy as np
def block(g):
#-- 按住类
h = g["hold"]
out = {"hold_mean": h.mean(), "hold_std": h.std(),
"hold_med": h.median(), "hold_iqr": h.quantile(.75) - h.quantile(.25)}
#-- 延迟类,按方向拆开:同侧与跨手的差别最大
for d in ("LL", "RR", "LR", "RL"):
s = g.loc[g["direction"] == d, "latency"]
if len(s) > 30:
out[f"lat_{d}_mean"] = s.mean()
out[f"lat_{d}_std"] = s.std()
#-- 左右手对称性:同一人两只手的按住差
lh = g.loc[g["hand"] == "L", "hold"]
rh = g.loc[g["hand"] == "R", "hold"]
if len(lh) > 30 and len(rh) > 30:
out["hand_asym"] = abs(lh.median() - rh.median())
out["n"] = float(len(g))
return pd.Series(out)
per_month = df.groupby(df["t"].dt.to_period("M")).apply(block)
per_user = df.groupby("user").apply(block)
print(per_month.round(2))
print(per_user[["hold_mean", "lat_LR_mean", "hand_asym", "n"]].round(2))
hand_asym 这一行是这个库独有的机会。因为数据里保留了左右手标记,而帕金森早期的一个特征是症状偏侧,左右手的差异本身就是一个候选量。论文在方法一节里也明确提到,此前不少研究没有考虑左右手的非对称性,而这一点在早期帕金森里恰恰显著。
不过这个量有三个陷阱:第一,一个人的左右手分工本来就不同,右利手的人右手按键更多、更快;第二,程序不记字符,所以无法区分「左手按的是 q 还是 a」,同一个列位混着多个键;第三,样本量小时,单侧手的中位数很不稳定。用它之前,至少要按人检查两只手的按键比例。
5.6 六个最容易出错的地方
| 坑 | 表现 | 处理办法 |
|---|---|---|
| 把第一行当表头 | 少一条记录,且列名错位 | 显式传 header=None 与列名 |
| 用秒级时间戳排序 | 同一天内多次会话顺序错乱 | 日期与时刻拼成完整时间戳 |
| 把 UPDRS 当总分 | 回归目标落在 1 至 5 之间 | 只用它做有序分类 |
| 忽略断月直接按人聚合 | 早期与晚期记录被压成一行 | 先按人月聚合,再决定是否合并 |
| 把三个时间量当独立特征 | 特征高度共线,模型不稳 | 保留两个,或做正交化 |
| 按记录随机划分训练测试 | 同一人同一月同时进两边 | 按代号分组划分 |
第六行是这个库最危险的一处。因为同一个人往往有十几份月度文件,随机切分会让同一个人的不同月份同时出现在训练与测试里,模型学到的很可能只是「这是谁」,而不是「这是不是患者」。正确做法是按十位代号分组,整组留出。
§6 与库内条目的关系
6.1 关系图谱
库内与它最接近的是 nqmitcsxpd,全名 neuroQWERTY MIT-CSXPD。两者都叫击键数据,都出自 2017 年前后,都以 PhysioNet 为发布点,都被用来讨论帕金森病,于是很容易被当成「同一件事的两批样本」。这个印象需要立刻纠正:它们是两项独立研究、两套独立采集、两种独立标注,唯一直接的联系是 Tappy 的论文把 CSXPD 借用为外部验证组。
| 维度 | Tappy | MIT-CSXPD | 能否对齐 |
|---|---|---|---|
| 发起机构 | 澳大利亚查尔斯特大学 | 美国 MIT 的 neuroQWERTY 项目 | 不同 |
| 通讯作者 | Adams WR,单作者 | Giancardo L 等,多作者 | 不同 |
| 采集地点 | 受试者家中,四国 | 西班牙马德里两家门诊 | 不同 |
| 招募方式 | 项目网站自招募,自装软件 | 门诊招募,研究者执行 | 不同 |
| 监督程度 | 全程无人在场 | 研究者组织会话 | 不同 |
| 采集设备 | 受试者自己的 Windows 电脑 | 统一的联想笔记本 | 不同 |
| 采集软件 | 系统级钩子,跨应用 | 指定文字处理软件内打字 | 不同 |
| 时间跨度 | 数周至数月,按月切分 | 单场约一刻钟,按场切分 | 不同 |
| 记录单元 | 一次按键,人月一文件 | 一次按键,人场一文件 | 不同 |
| 字段数 | 8 列 | 4 列 | 部分重合 |
| 是否记字符 | 不记,只记手别与列位 | 记键名,如 q、space | 不可互转 |
| 按住时长 | 有,毫秒 | 有,秒 | 单位不同 |
| 延迟时间 | 有 | 可由按下时刻推得 | 口径需换算 |
| 飞行时间 | 有,单列 | 无单列,需推导 | 可重建 |
| 严重度标签 | 自评 1 至 5 分档 | 临床 UPDRS-III 总分 | 不可互换 |
| 人口学字段 | 出生年、性别 | 基本没有 | 不同 |
| 用药字段 | 三类布尔 | 无 | 不可比 |
| 样本规模 | 二百余人 | 85 人 | 不同 |
| 许可 | ODC-BY 1.0 | ODC-BY 1.0 | 相同 |
十九行里只有最后一行是真正对齐的。其余十八行要么是硬差异(设备、字段、单位),要么是口径差异(标签、招募),没有一项能靠换算抹平。
先说最要命的一条:字符。 CSXPD 记键名,所以能做按键级的 n-gram、能算错字、能看具体哪个键慢。Tappy 不记字符,左手第三列可能对应 E、D、C 中的任何一个。这一条差异决定了两个库的特征空间没法共享:在 CSXPD 上训练出来的、以具体键为索引的模型,搬到 Tappy 上连输入都构造不出来。
再说标签。 CSXPD 的严重度是临床上按分项累加得到的 UPDRS-III 总分,量纲是零到一百多;Tappy 的 UPDRS 列是自评的 1 到 5 五档。把两者放进同一个回归,等于把一把刻度不同的尺子接在另一把上。真要做跨库任务,只能用二分类的患病标签,且要接受 Tappy 那一侧标签是自报的。
第三是数值单位。 CSXPD 的按住时长以秒记,Tappy 以毫秒记。这个差异表面上最好处理,乘以一千即可;但要注意 CSXPD 的时间戳精度是 3 毫秒上下,乘完之后小数位并不代表同样的分辨率。直接把两边的小数位对齐,会得到虚假的精度感。
结论能不能合并。 按行合并——不行。两库的列名、单位、字符信息、标签量纲没有一项能自然对齐,硬拼出来的宽表会有大量空列,且每列的含义在两批样本之间并不相同。按任务配对——可以,而且有先例:Tappy 的论文把 CSXPD 的五十人子集当作独立验证组,用 Tappy 训练、在 CSXPD 上测试,得到九成四的准确率与九成七的曲线下面积。这是两个库最正当的联合用法。
| 合并方式 | 可行性 | 前提 | 风险 |
|---|---|---|---|
| 行拼接成一张大表 | 不可行 | 需要统一的列与标签 | 空列泛滥,语义错位 |
| 特征层面对齐后拼接 | 勉强 | 只用按住与延迟,且放弃字符 | 特征分布来自两种场景,域偏移大 |
| 一个库训练、另一个测试 | 可行 | 只做二分类,标签按自报处理 | 两侧阳性率不同,阈值要重定 |
| 两库各自建模再比结论 | 可行 | 各自用本库标签 | 结论方向可比,数值不可比 |
| 用作同一模型的两个数据源 | 可行 | 加入数据源指示变量 | 模型可能只学到「这是哪个库」 |
第五行值得单说。把两个库混在一起训练,最容易出现的结果不是模型变强,而是模型学会辨认「这段数据来自家里还是诊室」。按住时长的绝对水平、延迟的分布形状,在两个场景下差别很大,模型抓这个比抓疾病容易得多。要避免,必须在划分时按库分层,并单独报告跨库表现。
6.2 检索路径
在库里找同类资源,可以按下面几条线索走。
| 想找什么 | 检索词 | 落点 |
|---|---|---|
| 同为击键的数据 | keystroke、typing、键盘 |
§6.1 的两条 |
| 帕金森的其它模态 | Parkinson、帕金森 |
步态、语音、 handwriting 各有一批 |
| 被动感知与数字表型 | passive、digital phenotype |
可穿戴与行为类 |
| 手指运动的定量测试 | finger tapping、敲击 |
多为小样本临床研究 |
| 家庭场景采集 | home、unsupervised、remote |
数量少,多为近年 |
| 许可与引用规范 | ODC-BY、PhysioNet |
平台通用页 |
如果问题是「有没有一个不用额外硬件的帕金森指标」,键盘只是其中一条,语音与步态各有成熟得多的库;而手指敲击一类的资源样本更小、临床标注更硬,适合做标签校验。
6.3 与其他库的分工判据
| 任务 | 该用什么 | 本库的位置 | 原因 |
|---|---|---|---|
| 大规模帕金森队列建模 | 多中心纵向队列 | 不适用 | 二百余人且自我选择 |
| 门诊场景的击键对照 | MIT-CSXPD 一类 | 辅助 | 本库没有临床评分 |
| 家庭场景的可行性验证 | 本库 | 主力 | 无监督采集是它的设计目标 |
| 跨设备鲁棒性 | 多设备标注的库 | 不适用 | 设备型号未记 |
| 文本与语言分析 | 带文本的输入日志 | 不适用 | 只记手别与列位 |
| 被动监测的伦理讨论 | 本库 | 主力 | 列位化设计是现成案例 |
| 用药与症状波动 | 带服药时刻的队列 | 不适用 | 只有用药类别 |
第三行与第六行是它真正的位置。前者靠场景,后者靠设计,两者都很难被后来者复制:要重做一次,得重新组织一次跨国自招募,还要说服伦理委员会接受「不记字符」这套方案。
§7 质量评估与局限性
7.0 阅读本节前的一个前置校正
评价这个库最常见的误判,是把它当成一份「帕金森队列」去和 PPMI 一类的多中心研究比人数与标注,然后得出「二百多人、标签还是自报,没什么价值」。这个比较是错位的。队列要回答的是人群层面的发生、进展与分型,而本库要回答的只有一个:把采集从诊室搬到家里、把任务换成日常打字之后,信号还在不在。对后者,标签是不是临床金标准并不改变结论的方向,只改变结论的强度。
第二个误判方向相反:因为它覆盖四国、跨月、看起来「很真实」,就以为它可以代表真实世界。恰恰不成立——主动报名、年龄卡在五十到八十、用 Windows、讲英语,这是被六重条件筛过的人群,比门诊样本更不随机。
把这两次校正做完,下面的评分才有意义:它的分数在「被动采集可行性」这个用途上很高,在「临床诊断」这个用途上很低,两个分数不矛盾。
7.1 五维评分
按 DAIMS 的常用维度打分,每项一到五分,锚定在「作为家庭场景被动采集的方法验证数据」这个用途。
| 维度 | 分数 | 理由 |
|---|---|---|
| 数据规模 | 3 | 二百余人、跨四国、跨数月,对可行性验证够用,对建模不足 |
| 标注质量 | 2 | 全部自报,无临床核验,严重度只有五档 |
| 任务适配 | 5 | 无监督、跨应用、不记字符,三者叠加在同类里极少见 |
| 文档完整 | 2 | 逐人月数、按键总量、分组比例、留存率均未公布 |
| 可获取性 | 5 | 免注册、ODC-BY、八十五兆、三个文件 |
合计十七分(满分二十五)。分数结构与多数平台数据相反:可获取与任务适配拉满,标注与文档拖到底。这个结构决定了它适合被引用为方法先例,不适合被当作训练底座。
标注只给两分,不是因为字段少——十三个在行为数据里已算丰富——而是关键字段全是自报。患病、震颤、侧别、影响程度四项都标注 self reported,UPDRS 标注「如已知」。任何以它为监督信号的模型,上限都由自报准确率决定,而这个准确率无人知晓。
7.2 三个口径的人数
这个库的人数有四套说法,官方没有给出换算关系。把四者并列,是理解它的第一步。
| 口径 | 数字 | 出处 | 可能的含义 |
|---|---|---|---|
| 受试者总数 | 二百余人 | PhysioNet 页面正文 | 安装并上传过数据的人 |
| 完整数据集 | 217 | 论文数据分析一节 | 论文所称的 Group A |
| 入组人数 | 103 | 论文摘要与结论 | 摘要口径,与上一行不同 |
| 分析子集 | 53 | 论文数据分析一节 | 按键过两千、轻度、未服左旋多巴 |
第二行与第三行之间的差距最大,且都出自同一篇论文。论文在方法里说完整数据集由 217 人构成,在摘要与结论里说 103 人,两处相隔不过几页。官方没有解释差额的构成,只能从上下文推测:217 可能是所有装过程序并产生过记录的人,103 可能是记录达到某个最低门槛的人。这只是推测,正文不据此下判断。
第四行的 53 有明确的三条筛选条件,写在方法一节里:按键至少两千次,以保证偏度、峰度一类统计量稳定,砍掉的是装了就很少用的人;患者只保留轻度,因为研究对象是早期,砍掉的是中重度;未服用左旋多巴,以排除药物对节奏的影响,砍掉的是已开始用药的人。三条合起来砍掉了大半样本,也说明了这个库的一个现实——原始记录里有效数据远比人头数少。
第三条对结论的影响最大。帕金森患者一旦确诊,多数会很快开始用药;把用药者全部剔除,留下的是一批「确诊不久、尚未用药」的人。这批人恰好是早期检测最想覆盖的群体,但也意味着结论无法外推到已治疗人群。
7.3 已知的、可枚举的偏差
| 偏差 | 表现 | 影响 |
|---|---|---|
| 自我选择 | 看到网站后主动报名 | 人群不可外推,健康对照尤其偏 |
| 平台限制 | 仅 Windows | 与操作系统相关的输入行为无法覆盖 |
| 年龄窗口 | 五十至八十岁 | 青年与高龄无数据 |
| 语言与地区 | 四个英语国家 | 键盘布局与输入习惯同源 |
| 标签自报 | 患病与严重度均自评 | 监督信号上限不明 |
| 无复评 | 全程无第二次临床评估 | 进展只能靠打字行为间接看 |
| 设备未标 | 型号、键盘、系统版本均不记 | 跨设备方差混入个体差异 |
| 断月普遍 | 月度文件不连续 | 纵向序列实际可用长度短 |
| 字符缺失 | 只到列位 | 文本与按键级分析全断 |
| 留存未知 | 官方未给卸载率与活跃度 | 无法评估样本的代表性衰减 |
四个国家都用 QWERTY 布局,减少了布局差异这个干扰项,也意味着这个库无法回答「换成 AZERTY 或 Dvorak 会怎样」,对中文输入场景的迁移没有任何证据。
第八行在操作层面最容易被低估。按月切片听起来适合做纵向,但实际每个人贡献的月份是断续的。拿 §4.4 的案例来说,一份完整的十一月文件前后可能各缺两个月,那么「相邻月份对比」就只能退化成「两个相隔半年的月份对比」,中间发生的事(换药、换电脑、病情变化)全部不可见。
7.4 外部验证矩阵
这个库在文献里的用法,可以按验证角色分成几类。
| 验证角色 | 典型做法 | 讲得通的地方 | 风险 |
|---|---|---|---|
| 原始方法的复现 | 重跑论文的二十七特征加八模型集成 | 论文把特征分族与降维写清楚了 | 预处理细节多,逐项复现难 |
| 域外泛化检验 | 在门诊击键库上测家庭训练的模型 | 两个场景差异足够大,检验有意义 | 标签口径不同,阈值需重定 |
| 被动感知的引证 | 用来说明「装软件就能长期采」 | 采集时长与场景是硬证据 | 多作背景引证,未做二次分析 |
| 隐私设计的案例 | 用列位化说明不可识别性 | 设计本身即论据 | 停留在讨论层面 |
| 与可穿戴的对比 | 与腕带、鞋垫类数据并列 | 硬件门槛低是明显优势 | 采样率与信息量不同,难直接比 |
第一行的复现难度值得提醒。论文给的是二十七个特征、两个特征族、线性判别分析降维、八个基模型、按准确率加权的均值概率,最后还有一个一点二的延迟族权重。这些环节里任何一处口径不同,复现出来的数字就会偏。要做复现,最好先把特征数对齐到九加十八,再谈模型集成。
第二行是它最有价值的用法,而现成的样板正是论文自己做的:用本库训练,在门诊击键库上测,得到九成四的整体准确率。这个数字不该被当成产品指标,而该被当成「跨场景迁移会掉多少」的参考点。
7.5 它为何能持续:一个再也复制不出来的采集窗口
这份数据自 2017 年上线后没有扩版,却持续被引用。原因不在它有多完整,而在它占着一个很难重现的位置:一次真正发生在受试者家里、跨四国、跨数月、没有任何人在场的键盘采集,而且采集程序主动放弃了字符。
要重做一次,成本不在钱,而在三件事:重新组织跨国自招募,说服伦理委员会接受「不记字符」,再等上几个月让记录自然积累。任何一件拖延,项目就会退回实验室。所以只要这个方向还在,它就会被当成第一块砖——哪怕标签只是自报。
§8 生态位与历史
8.1 历史沿革
这个库的诞生路径与多数临床数据集相反:它先有一个采集程序,再有一个网站,然后才有人群。论文记载的窗口是 2016 年 7 月到 2017 年 3 月,期间任何符合条件的人访问网站、阅读说明、下载安装、接受知情同意,就进入了采集。研究者能控制的只有程序本身,其余全部交给受试者。
| 阶段 | 时间 | 发生了什么 |
|---|---|---|
| 程序开发 | 2016 年上半年 | 基于系统级键盘钩子的后台采集程序成形 |
| 开放招募 | 2016 年 7 月 | 项目网站上线,四国受试者陆续报名 |
| 记录积累 | 2016 年 7 月至 2017 年 3 月 | 各人按自己的节奏打字,数据按日上传 |
| 分析成文 | 2017 年 | 二十七特征、八模型集成、外部验证 |
| 数据发布 | 2017 年 10 月 20 日 | PhysioNet 1.0.0 版 |
| 长期静止 | 2019 年至今 | 仅补校验和,未扩版 |
最后一行的「静止」值得记一笔。多数行为数据集会在几年内扩版或加补丁,这个库没有。原因是它的数据来自一段已经结束的采集窗口,窗口关了就不会再有新样本。
8.2 与后续研究的关系
它最常被用到的方向有五个:新特征的离线验证(能,但标签弱)、被动感知的系统设计(能,程序本身即经验)、与深度模型的对比(勉强,样本小)、跨语言跨布局迁移(不能)、伦理与合规讨论(能)。
第二类常被忽略。论文里写清了几个工程决定:为什么用系统级钩子而不是应用插件,为什么排除数值键区,为什么只记列位,为什么本地落盘再每日上传。这些决定对要自己搭一套采集的人,比数据本身更有参考价值。
8.3 生态位:被动采集的样板,不是疾病的数据源
把它放在整个帕金森数据生态里,位置很清楚。最上游是量表评分与影像这类临床金标准,它不参与;再往下是含影像、基因与随访的多模态队列,它也不参与;中游是按模态分开的公开数据,步态、语音各有一批,它占击键这一支;再往下是可行性验证这一格,它站在这里,是主体;最下游是商业化的监测方案,它不参与。
用它的三类人关注的字段不同:做被动感知的工程团队看时间戳与方向,做数字生物标志物的研究组看按住与延迟,做隐私与伦理讨论的人只看字段清单本身。
§9 使用指引
9.1 三种角色的路径
算法研究者的第一天是拉两个包、跑通 §5.3 的人月索引,第一周按代号分组做二分类基线,要注意的是划分按人不按文件。临床研究人员的第一天只看参与者表的十三项,第一周用自评五档做有序分组,别把 UPDRS 当总分。教学人员的第一天用 §2.3 的八列讲事件结构,第一周用 §5.6 的六个坑做课堂练习,别用全库数字讲准确率。
9.2 一条最短的可用流程
#-- 1. 取包、校验、解压
mkdir -p tappy && cd tappy
curl -sL https://physionet.org/files/tappy/1.0.0/Archived-Data.zip -o d.zip
curl -sL https://physionet.org/files/tappy/1.0.0/Archived-users.zip -o u.zip
unzip -q d.zip -d data && unzip -q u.zip -d users
#-- 2. 数一数:文件数与人数的比例,就是平均月数
find data -name '*.csv' | wc -l
ls users | wc -l
from pathlib import Path
import pandas as pd
COLS = ["user", "date", "ts", "hand", "hold", "direction", "latency", "flight"]
rows = []
for p in sorted(Path("tappy/data").glob("*.csv")):
g = pd.read_csv(p, header=None, names=COLS, dtype={"user": str, "date": str})
if len(g) < 500:
continue
rows.append({"code": g["user"].iat[0], "ym": p.stem[-4:], "n": len(g),
"hold_med": g["hold"].median(),
"lat_med": g["latency"].median()})
pm = pd.DataFrame(rows)
#-- 一人一行:取该人中位数,避免个别月份主导
per = pm.groupby("code").agg(n_months=("ym", "nunique"),
hold_med=("hold_med", "median"),
lat_med=("lat_med", "median"))
print(per.describe().round(2))
这段流程不产出模型,只先把「有多少可用的人、每人几个月」数清楚。回到 §4.4 的案例,它在这张表里只贡献一行,n_months 为 1。
9.3 引用方式
| 引用对象 | 内容 | 何时需要 |
|---|---|---|
| 数据集本体 | Tappy Keystroke Data, v1.0.0, PhysioNet | 使用数据时必须 |
| 版本 DOI | 10.13026/C2K08D |
说明版本时 |
| 原始论文 | Adams WR. PLOS ONE 12(11): e0188226, 2017 | 引用方法与结论时 |
| 平台惯例 | PhysioNet 平台综述(2026,Nature Health) | 按页面要求一并引用 |
| 许可 | ODC-BY 1.0,署名 | 任何再分发时 |
§10 AI 使用声明
| 项目 | 声明 |
|---|---|
| 文本生成 | 由 AI 辅助撰写,结构与事实由编辑把关 |
| 数字来源 | 规模、结构、边界三类来自官方页面、论文正文与文件目录清单 |
| 官方核验 | 页面与目录清单逐个访问确认,HTTP 200 |
| 论文核验 | 只读 PLOS ONE 正文与摘要,未读补充材料 |
| 案例说明 | §4.4 的月度文件为按字段定义复原的结构样例,非真实记录 |
| 差异披露 | 二百余人、217、103、53 四个口径官方未给换算 |
| 未核验内容 | 逐人月数、按键总量、患者与对照比例、留存率 |
| 图像 | 无配图,封面为生成式提示词 |
常见误读(避坑清单)
| 说法 | 实际 | 证据 |
|---|---|---|
| 「它是一份打字测验数据」 | 无任务、无文本、无监督 | 官方页面 Data 一节 |
| 「它记下了受试者打的字」 | 只记左右手与列位 | 论文隐私设计段 |
| 「UPDRS 列是量表总分」 | 是 1 至 5 的自评档 | 官方字段说明 |
| 「二百余人就是二百多份文件」 | 文件按人月计,远多于人数 | 命名规则与目录清单 |
| 「四国样本可以代表人群」 | 全部为主动报名的自我选择 | 论文受试者一节 |
| 「它和 MIT-CSXPD 是同一批人」 | 两项独立研究,场景与字段都不同 | §6.1 对照表 |
| 「有用药列就能做用药效应」 | 只有类别,没有服药时刻 | 参与者字段表 |
| 「按住、延迟、飞行是三个独立量」 | 三者存在恒等关系 | §4.2 推导 |
| 「按月切片就能做纵向」 | 断月普遍,序列常不连续 | §5.4 空档统计 |
| 「按文件随机划分即可」 | 同一人多文件,会泄漏 | §5.6 第六行 |
FAQ
Q1:Tappy 记录的到底是什么?
是按键事件的时间关系,不是文本也不是波形。一行一次按击,含手别、列位、按住、方向、延迟与飞行。八列能还原节奏,还原不了内容。
Q2:二百余人、217、103、53,哪个对?
四个数字对应四种统计对象,官方没有给换算。页面写二百余人,论文方法写 217,摘要写 103,实际分析用 53。引用时要把口径一起写出来。
Q3:能不能和 MIT-CSXPD 合成一个大表?
不能按行合。字符信息、单位、标签量纲三项都对不上。可以按任务配对:一个训练、另一个测试,论文自己就是这么做的。
Q4:为什么人数比文件数少这么多?
文件按人月切分,一个人装几个月就有几份。所以文件总数远大于人数,且各人月数差别很大。
Q5:标签可信吗?
全部自报,包括患病、震颤、侧别与严重度。没有临床复核,也没有第二次评估,这是它最大的软肋。
Q6:能不能做文本分析?
不能。程序不记字符,只把 E、D、C 统一记成左手第三列,数值键区整个排除。
Q7:按住时长的单位是什么?
毫秒,官方字段说明写明。与按秒记的门诊击键库对比时要换算,但换算不会提升真实分辨率。
Q8:为什么按文件随机划分会虚高?
同一个人有十几份月度文件,随机切分后训练集与测试集里都有他。模型学到的可能是「这是谁」。
Q9:体量多大,要下多久?
压缩后八十五兆,两个包加一个校验和文件。普通宽带几分钟,解压也不需要额外空间规划。
Q10:许可能不能商用?
页面标 ODC-BY 1.0,允许商用,需署名。论文的数据声明另写作公共领域奉献,口径不一致,按更严的执行更稳。
术语表
| 术语 | 解释 |
|---|---|
| 击键动力学 | 研究按键时序与节奏的分支,出身是身份识别 |
| 按住时长 | 一个键从按下到抬起的毫秒数 |
| 延迟时间 | 上一键按下到当前键按下的间隔 |
| 飞行时间 | 上一键抬起到当前键按下的空隙 |
| 方向 | 上一键与当前键的左右手组合,空格单记 |
| 列位 | 键在键盘上的横向位置,本库只记到列 |
| 人月 | 一个人一个月的数据,本库的文件单位 |
| 断月 | 某个月没有记录,序列出现空洞 |
| 系统级钩子 | 截获全系统键盘事件的系统接口 |
| 被动采集 | 不要求用户执行指定任务的采集方式 |
| 数字生物标志物 | 用设备数据刻画疾病状态的指标 |
| 自报标签 | 由受试者自己填写的疾病信息 |
| 偏侧性 | 症状在左右两侧的不对称分布 |
| 左旋多巴 | 帕金森最常用药物,本库单列一列 |
| 开期关期 | 服药后症状缓解与复现的两个阶段 |
| 域偏移 | 训练与测试数据分布不一致 |
| 分组划分 | 以人为单位整体切分,防止泄漏 |
| 特征族 | 按来源分组的特征集合,本库分按住与延迟 |
| 集成模型 | 多个基模型合成一个判别器 |
| 有序分类 | 类别有顺序但间距未知的分类任务 |
| ODC-BY | 开放数据共享署名许可 |
| DOI | 数字对象标识符,用于稳定引用 |
| PhysioNet | 生物医学数据与工具的公共平台 |
| DAIMS | 数据与信息管理评估的常用维度框架 |
附录 A:核心数字核对表
| 项 | 值 | 来源 | 来源层级 |
|---|---|---|---|
| 受试者总数 | 二百余人 | 官方页面正文 | 官方 |
| 完整数据集 | 217 | 论文数据分析一节 | 论文 |
| 摘要口径 | 103 | 论文摘要与结论 | 论文 |
| 分析子集 | 53 | 论文数据分析一节 | 论文 |
| 事件包体积 | 85.0 MB | 目录清单 | 官方 |
| 详情包体积 | 71.3 KB | 目录清单 | 官方 |
| 事件字段数 | 8 | 官方字段表 | 官方 |
| 参与者字段数 | 13 | 官方字段表 | 官方 |
| 时间精度 | 3 毫秒 | 论文方法一节 | 论文 |
| 分析特征数 | 27 | 论文方法一节 | 论文 |
| 集成模型数 | 8 | 论文方法一节 | 论文 |
| 招募窗口 | 2016 年 7 月至 2017 年 3 月 | 论文受试者一节 | 论文 |
| 伦理批号 | H17013 | 官方页面 | 官方 |
| 发布日期 | 2017 年 10 月 20 日 | 版本页 | 官方 |
附录 B:数字的来源分层
| 层级 | 包含 | 本库的具体项 |
|---|---|---|
| 官方页面 | 可直接引用的陈述 | 人数、字段、体积、许可、伦理批号、精度描述 |
| 论文正文 | 需要标注出处的陈述 | 217、103、53、27 特征、8 模型、招募窗口、3 毫秒 |
| 未核实 | 官方与论文都没给的 | 逐人月数、按键总量、患者比例、留存率、平均年龄 |
第三层要特别小心。这五项都是使用者最想要、而官方恰好没给的数字。任何声称知道这些数的地方,都可能是自己统计后当成官方口径在传。
附录 C:完整参考文献
- Adams WR. High-accuracy detection of early Parkinson’s Disease using multiple characteristics of finger movement while typing. PLOS ONE 12(11): e0188226, 2017。方法、人数、特征与外部验证均出自此文。
- Tappy Keystroke Data v1.0.0. PhysioNet. DOI 10.13026/C2K08D。字段、体积、许可与发布日期的官方出处。
- PhysioNet 平台综述. Nature Health, 2026。平台引用惯例。
- MIT-CSXPD v1.0.0. PhysioNet。对照数据来源,亦为本库论文的独立验证组。
附录 D:任务代码
#-- 只取参与者表,先看人群结构
mkdir -p tappy && cd tappy
curl -sL https://physionet.org/files/tappy/1.0.0/Archived-users.zip -o u.zip
unzip -q u.zip -d users
grep -h -i '^Parkinsons' users/* | sort | uniq -c
from pathlib import Path
import pandas as pd
def read_user(p):
d = {}
for line in p.read_text(encoding="utf-8", errors="replace").splitlines():
if ":" in line:
k, v = line.split(":", 1)
d[k.strip()] = v.strip()
d["code"] = p.stem
return d
u = pd.DataFrame([read_user(p) for p in Path("tappy/users").iterdir() if p.is_file()])
u["pd"] = u["Parkinsons"].str.lower().eq("true")
u["age"] = 2017 - pd.to_numeric(u["Birth Year"], errors="coerce")
#-- 每组各多少人、年龄中位数
print(u.groupby("pd").agg(n=("code", "nunique"), age_med=("age", "median")))
#-- 服药结构:三类药物各自的比例
for c in ("Levadopa", "DA", "MAOB"):
print(c, u[c].value_counts(dropna=False).to_dict())
这段代码刻意只读人名表。原因是很多人一上手就去拆八十五兆的事件包,结果连「患者与对照各多少」都不知道。先把人这一侧看清楚,再决定事件侧要读多少。
附录 E:如果只记五件事
| 序号 | 一句话 |
|---|---|
| 1 | 它是家庭场景下无监督采集的击键事件库,按月按人切分 |
| 2 | 四套人数口径:二百余人、217、103、53,官方未给换算 |
| 3 | 只记左右手与列位,不记字符,文本分析彻底无路 |
| 4 | 三类标签全部自报,严重度只有 1 至 5 五档 |
| 5 | 与门诊击键库可配对做域外验证,不可按行合并 |
编辑说明
本页由千方病案医学编辑部整理,核验时间为 2026 年 10 月 28 日。事实来源为 PhysioNet 官方页面、1.0.0 版目录清单与 PLOS ONE 论文正文;官方页面与目录清单当日访问返回 HTTP 200,未下载事件包。四个口径的人数差、逐人月数、患者与对照比例、留存率均未在官方材料中找到说明,正文一律并列呈现,不做成因判断。§4.4 的月度文件为数据结构样例,数值按官方字段定义复原,不对应任何真实受试者。本页不构成临床建议。
相关数据集导航
以下为站内 AI-Ready 数据集百科中与本词条共享多个主题标签的相关数据集,按相关度降序排列:
- nqmitcsxpd — 共享标签:生理信号 / 神经科学 / 帕金森病 / 可穿戴传感
- tremordb — 共享标签:生理信号 / 神经科学 / 帕金森病
- pc-gita — 共享标签:生理信号 / 神经科学 / 帕金森病
- consumer-grade-wearables — 共享标签:生理信号 / 神经科学 / 可穿戴传感
- gaitdb — 共享标签:生理信号 / 帕金森病 / 可穿戴传感
- multimodal-nback-music — 共享标签:生理信号 / 神经科学 / 可穿戴传感
- noneeg — 共享标签:生理信号 / 神经科学 / 可穿戴传感
- daphnet-fog — 共享标签:生理信号 / 帕金森病 / 可穿戴传感
- brain-wearable-monitoring — 共享标签:生理信号 / 神经科学 / 可穿戴传感
- nebula-reaching-eeg — 共享标签:生理信号 / 神经科学 / 可穿戴传感
导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

