在自家电脑上敲了几个月 — Tappy 击键库

二百余人 4 国自装 / 人月 CSV / ODC-BY 1.0

来源 https://physionet.org/content/tappy/1.0.0/发布时间: 2026-10-28最后更新: 2026-10-28 阅读 10
在自家电脑上敲了几个月 — Tappy 击键库

信息速览

数据集名称在自家电脑上敲了几个月 — Tappy 击键库
数据类型时序信号,元数据,人工标注
规模二百余名受试者(官方口径),论文另称 217 人与 103 人两说
接入方式https://physionet.org/content/tappy/1.0.0/
AI 就绪度

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:完整参考文献

  1. Adams WR. High-accuracy detection of early Parkinson’s Disease using multiple characteristics of finger movement while typing. PLOS ONE 12(11): e0188226, 2017。方法、人数、特征与外部验证均出自此文。
  2. Tappy Keystroke Data v1.0.0. PhysioNet. DOI 10.13026/C2K08D。字段、体积、许可与发布日期的官方出处。
  3. PhysioNet 平台综述. Nature Health, 2026。平台引用惯例。
  4. 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 — 共享标签:生理信号 / 神经科学 / 可穿戴传感

导航说明:本章节由全站统一标签体系自动计算生成(标签重合度算法),双向可达;点击链接可跳转至对应数据集词条。

返回 AI-Ready 数据集