EORTC QLQ-C30/LC13中文版量表计分与条目映射实战

发布时间:2026/9/18 6:04:10
EORTC QLQ-C30/LC13中文版量表计分与条目映射实战 简介EORTC-QLQ-C30LC13 中文版文档面向肿瘤临床研究、护理评估与医学论文写作用户提供一份可直接查阅的生活质量调查量表。核心包含 EORTC QLQ-C30第三版三十个条目与 QLQ-LC13 肺癌特异模块覆盖躯体、角色、认知、情绪、社会功能以及疲劳、疼痛、恶心呕吐、气短、咳嗽、咯血等症状维度并附整体健康状况与生活质量评分条目。量表适用于肺癌、乳腺癌、结肠癌、胃癌等多种成人癌症患者可用于评估治疗效果及其对生活质量的影响。文档还给出条目计分、领域粗分、极差化标化分的计算公式以及功能领域与症状领域得分方向转换说明便于研究者完成数据录入、维度汇总和跨组比较。资源为单一 doc 文件压缩包约 68KB已有 404 人学习或下载适合需要中文版量表原文、评分规则与临床研究参考的医生、护理人员、研究生及课题成员快速取用。1. 中文版问卷落地最容易错的地方不是录入而是条目映射肺癌随访研究里患者报告结局问卷常被当成最简单的一环CRF 上照着EORTC-QLQ-C30LC13中文版.doc抄下 43 个条目EDC 建 43 个字段就上线。两年后出报告才发现条目 20 和 25 被当成两个独立症状报了出去LC13 的三条呼吸困难被拆成三个互不相干的变量7 点量表按 4 点量表除了 3 而不是 6。根源不在于这份文档不权威而在于它是一份给人看的问卷条目号、题干、选项、维度归属、计分方向全混在排版里。正确顺序是先把.doc拆成条目字典再把字典翻译成可执行的计分规则最后才谈数据入库和分析。跳过第一步后面每个数字都可能是错的。这条链路要覆盖的是结构化抽取、C30 十五个维度与 LC13 十三个条目的编号衔接、原始分与 0-100 线性变换、缺失条目的保留阈值以及与 SAS、SPSS 结果对拍的方法。2. 从 EORTC-QLQ-C30LC13 中文版文档到条目字典2.1 把 .doc 转成可解析格式LibreOffice 无头模式与两条备选路径.doc是二进制复合文档格式python-docx只能读.docx所以第一步必然是一次格式转换。常见的三条路径Word 手动另存为.docx样本量小、一次性任务最省事、antiword或textract抽纯文本快但丢掉表格结构、LibreOffice 无头模式批量转换可脚本化保留表格。我一般选第三条因为量表的选项行往往排在表格里纯文本抽取后没有行界限反而更难对齐。命令如下注意文件名里的必须加引号否则 shell 会把它当成后台执行符号命令行为完全跑偏# 无头模式批量转换保留表格与段落结构 soffice --headless --convert-to docx:MS Word 2007 XML \ --outdir ./converted ./EORTC-QLQ-C30LC13中文版.doc参数说明--headless不启动图形界面可以在服务器或容器里跑--convert-to docx:MS Word 2007 XML显式指定输出过滤器避免某些发行版默认转到 odt--outdir指定输出目录输出的文件名为原名加.docx。提示中文版条目应来自 EORTC 官方翻译流程的版本机构使用前按其要求完成登记。自行翻译或改写措辞会让数据与文献中的常模、MCID 判读标准不可比。2.2 用 python-docx 抽出 43 个条目题干、编号与选项关键点是同时遍历doc.paragraphs和doc.tables并对全角数字做 NFKC 归一化。很多中文 CRF 导出的题干用全角编号「」不归一化就一条都匹配不上最后表现为只抽到 12 个条目这种莫名其妙的结果。import re import unicodedata from docx import Document NUMBER re.compile(r^\s*(\d{1,2})\s*[\.、,]\s*) # 1. / 1、 / 1 def iter_lines(doc): 段落 表格单元格里的文本统一迭代 for p in doc.paragraphs: yield p.text for tb in doc.tables: for row in tb.rows: for cell in row.cells: for p in cell.paragraphs: yield p.text doc Document(./converted/EORTC-QLQ-C30LC13中文版.docx) items {} for raw in iter_lines(doc): t unicodedata.normalize(NFKC, raw).strip().replace(\u3000, ) m NUMBER.match(t) if not m: continue no int(m.group(1)) body NUMBER.sub(, t).strip() if 1 no 43 and len(body) 4: items[no] body missing [i for i in range(1, 44) if i not in items] print(f抽到 {len(items)} 条缺失条目号{missing})逻辑说明iter_lines把段落和表格压成一维文本流避免漏掉表格排版的条目NUMBER只认行首编号防止把选项里的数字当成新条目len(body) 4用来过滤页脚、页码、页眉里那些第 2 页之类的行。参数说明unicodedata.normalize(NFKC, ...)把全角数字、全角标点统一为半角如果抽出来的missing非空先回去翻原文的编号格式是不是用了「①」或纯中文序号而不是直接改NUMBER正则加宽匹配范围——放宽后误匹配的概率远大于收益。2.3 C30 十五个维度与 LC13 十三个条目的编号衔接C30 与 LC13 合并发放时LC13 的条目顺延为 31 至 43。很多团队在 EDC 里用 LC13 内部编号 1 至 13 建字段计分脚本却按 31 至 43 取列结果是整组 LC13 维度全为缺失事后查起来极其费劲。入库前先把映射定死写进文档量表维度条目号条目数方向QLQ-C30PF 躯体功能1-55功能QLQ-C30RF 角色功能6, 72功能QLQ-C30EF 情绪功能21-244功能QLQ-C30CF 认知功能20, 252功能QLQ-C30SF 社会功能26, 272功能QLQ-C30GHS 总体健康29, 302总体QLQ-C30FA 疲乏10, 12, 183症状QLQ-C30NV 恶心呕吐14, 152症状QLQ-C30PA 疼痛9, 192症状QLQ-C30DY/SL/AP/CO/DI/FI8 / 11 / 13 / 16 / 17 / 28各 1症状QLQ-LC13LC-CO 咳嗽、LC-HA 咯血31, 32各 1症状QLQ-LC13LC-DY 呼吸困难33, 34, 353症状QLQ-LC13LC-SM/DS/PN/AL36, 37, 38, 39各 1症状QLQ-LC13LC-PC/PA/OP40, 41, 42各 1症状QLQ-LC13止痛药使用431不计分用一段断言锁住重叠检查比人工核表可靠C30_ITEMS set(range(1, 31)) LC13_ITEMS set(range(31, 44)) assert not (C30_ITEMS LC13_ITEMS), C30 与 LC13 条目号重叠 assert C30_ITEMS | LC13_ITEMS set(range(1, 44))3. QLQ-C30 与 LC13 计分算法原始分与 0-100 线性变换3.1 原始分怎么算条目均值与「至少一半」缺失规则多维度的原始分是所辖条目的算术均值但缺失处理不是简单的mean(skipnaTrue)。通用规则是该维度内至少有一半条目作答才用已作答条目的均值代替低于这个比例整个维度记为缺失。对只有 1 个条目的维度这条规则自然退化为必须作答。import numpy as np import pandas as pd def raw_score(df: pd.DataFrame, items: list, min_ratio: float 0.5) - pd.Series: 多条目维度原始分items 为列名列表未答在 df 中为 NaN sub df[items] n_answered sub.notna().sum(axis1) need int(np.ceil(len(items) * min_ratio)) # 条目数 * 比例向上取整 rs sub.mean(axis1) rs[n_answered need] np.nan # 未达阈值整条维度作缺失 return rs参数说明min_ratio0.5对应 2 条目维度只要答 1 条就保留这是多数实现的做法如果研究方案更保守设成 1.0 即要求全部作答代价是缺失率上升。无论选哪个都必须写进统计分析计划否则不同分析者跑出的缺失率会不一致。注意SAS 与 SPSS 的MEAN()函数默认忽略缺失值且不做比例判断直接用它们算维度分缺失记录也会得到一个分数。对拍时这类差异会集中冒出来。3.2 功能量表、症状量表与总体健康状态的三套变换公式线性变换的分母是极差即选项最大值减最小值4 点条目为 37 点条目为 6。功能量表Score (1 - (RS - 1) / range) × 100分数越高功能越好。 症状量表与单项Score ((RS - 1) / range) × 100分数越高症状越重。 总体健康状态条目 29、30用症状那一侧的公式range 6。def linear_transform(rs: pd.Series, score_range: int, direction: str) - pd.Series: if direction function: return (1 - (rs - 1) / score_range) * 100 return (rs - 1) / score_range * 100方向别记混的记法功能量表第 1 档完全没有困难必须给 100 分所以减号不能丢症状量表第 1 档没有给 0 分。很多人把方向反向挪到数据清洗阶段把 1 到 4 改成 4 到 1 再套公式结果功能与症状的趋势完全一致这种错误在单臂研究里几乎看不出来。3.3 用 pandas 一次性算出 C30 的 15 个维度与 LC13 的 10 个维度把映射写成配置计分函数只做遍历条目调整时不用碰逻辑代码SCALE_MAP { PF: {items: [1, 2, 3, 4, 5], range: 3, dir: function}, RF: {items: [6, 7], range: 3, dir: function}, EF: {items: [21, 22, 23, 24], range: 3, dir: function}, CF: {items: [20, 25], range: 3, dir: function}, SF: {items: [26, 27], range: 3, dir: function}, GHS: {items: [29, 30], range: 6, dir: symptom}, # 7 点极差 6 FA: {items: [10, 12, 18], range: 3, dir: symptom}, NV: {items: [14, 15], range: 3, dir: symptom}, PA: {items: [9, 19], range: 3, dir: symptom}, DY: {items: [8], range: 3, dir: symptom}, SL: {items: [11], range: 3, dir: symptom}, AP: {items: [13], range: 3, dir: symptom}, CO: {items: [16], range: 3, dir: symptom}, DI: {items: [17], range: 3, dir: symptom}, FI: {items: [28], range: 3, dir: symptom}, LC_CO: {items: [31], range: 3, dir: symptom}, LC_HA: {items: [32], range: 3, dir: symptom}, LC_DY: {items: [33, 34, 35], range: 3, dir: symptom}, LC_SM: {items: [36], range: 3, dir: symptom}, LC_DS: {items: [37], range: 3, dir: symptom}, LC_PN: {items: [38], range: 3, dir: symptom}, LC_AL: {items: [39], range: 3, dir: symptom}, LC_PC: {items: [40], range: 3, dir: symptom}, LC_PA: {items: [41], range: 3, dir: symptom}, LC_OP: {items: [42], range: 3, dir: symptom}, } def score_all(df: pd.DataFrame, scale_map: dict) - pd.DataFrame: out {} for name, cfg in scale_map.items(): cols [fq{i} for i in cfg[items]] rs raw_score(df, cols) out[name] linear_transform(rs, cfg[range], cfg[dir]).round(2) return pd.DataFrame(out, indexdf.index)3.4 参数表与边界校验跑完计分先做三项体检所有维度是否落在 0 到 100单条目维度是否只出现 0、33.33、66.67、100 四个值GHS 是否出现 16.67、50、83.33 这类只能由极差 6 产生的取值。维度条目数选项范围RS 区间极差0 分含义100 分含义PF51-41-43完全受限毫无困难GHS21-71-76极差极好FA31-41-43无疲乏极度疲乏LC_DY31-41-43无呼吸困难严重呼吸困难如果 GHS 的取值只落在 0、33.33、66.67、100 四个点上几乎可以断定分母写成了 3。这类错误不会报错只会让总体健康状态的分布看起来特别整齐。4. 实战从 EDC 导出到分析数据集4.1 变量命名与列对齐q1 到 q43 的一次性约定入库时保持问卷原始编码4 点量表存 1 到 47 点量表存 1 到 7止痛药存 1 到 2不要提前转成 0 起始。转换越晚回溯核对越容易。列对齐之后先跑一遍编码范围检查CODEBOOK {**{fq{i}: (1, 4) for i in range(1, 29)}, q29: (1, 7), q30: (1, 7), **{fq{i}: (1, 4) for i in range(31, 43)}, q43: (1, 2)} def check_codes(df: pd.DataFrame, codebook: dict) - dict: bad {} for col, (lo, hi) in codebook.items(): if col not in df.columns: bad[col] 列缺失 continue s pd.to_numeric(df[col], errorscoerce) off df.loc[s.notna() ~s.between(lo, hi), col].unique() if len(off): bad[col] list(off) return bad逻辑说明errorscoerce把全角数字、空字符串、拒答统一变成 NaN走同一套缺失逻辑比在计分函数里散落 try/except 干净得多。返回的字典为空才继续计分非空则先回到 EDC 查导出规则。4.2 反向计分、LC13 单项与第 43 题的处理方向只在线性变换的公式里体现不要在清洗阶段改条目取值。LC13 第 43 题是是否使用止痛药的二分类变量不进入任何维度分析时作为描述性指标或协变量报告。df[lc_painkiller] df[q43].map({1: 是, 2: 否}) # q43 不参与任何维度均值计算仅做描述统计与亚组分析把第 43 题和其余 42 个 4 点条目混在一起求均值的做法并不罕见后果是 LC_DY 等维度被硬生生拉偏而且因为 1 和 2 都在合法取值范围内编码检查也拦不住。4.3 与 SAS、SPSS 对拍三个交叉校验第一挑 3 条记录手工复算全维度值用 Excel 逐格验证覆盖有缺失和无缺失两种情形。第二用 SPSS 或 SAS 独立实现一份跑同一份数据。COMPUTE PF (1 - (MEAN(Q1 TO Q5) - 1)/3) * 100. EXECUTE.第三构造极端样本全部填 1、全部填 4、全部填 7检查功能量表是否恰好为 100 和 0症状量表是否恰好为 0 和 100。三个校验里最容易暴露问题的是第三个方向错误和极差错误都会在这里现形。注意上面这段 SPSS 语法的MEAN会忽略缺失值即便只答了 1 条也照样算分和脚本实现的一半规则不一致。对拍时必须同步输出每个维度实际作答条目数两边差异基本都出在这一列上。4.4 常见坑与对应处理现象根因处理GHS 只出现 0/33.33/66.67/100极差用了 3 而非 6核对条目 29、30 的rangeLC13 维度整列为缺失内部编号 1-13 未映射为 31-43入库前统一顺延编号功能与症状趋势一致清洗阶段做了反向编码方向只保留在变换公式里缺失记录仍被计分直接用了MEAN()默认行为显式实现一半规则整列变 NaN全角数字或文字型拒答NFKC 归一化 to_numeric(coerce)5. 把量表计分做成可复用工具的两个技巧5.1 映射配置化与最小单元测试SCALE_MAP从代码里挪到 YAML条目号改动就不需要动逻辑也方便统计师自己核对PF: items: [1, 2, 3, 4, 5] range: 3 dir: function GHS: items: [29, 30] range: 6 dir: symptom配套的测试只要两条断言就能覆盖大部分致命错误import pandas as pd def test_pf_extremes(): cols [fq{i} for i in range(1, 6)] df_lo pd.DataFrame({c: [1] for c in cols}) df_hi pd.DataFrame({c: [4] for c in cols}) assert score_all(df_lo)[PF].iloc[0] 100.0 # 全选完全没有困难 assert score_all(df_hi)[PF].iloc[0] 0.0方向写反、极差填错、条目清单漏一个这三类问题都会被这两条断言拦住。再补一条 GHS 的断言检查是否出现 16.67 这类只有极差 6 才可能产生的取值基本可以封住计分环节。5.2 纵向随访的差值核查、地板天花板效应与 MCID随访研究的核心输出是差值delta 随访值 - 基线值。功能量表 delta 为正表示改善症状量表 delta 为正表示恶化方向解释要写进统计分析计划不能留到出图时再定。地板和天花板效应值得在建库阶段就查如果某维度基线超过 15% 的患者取 0 或 100这个维度对干预的敏感度会明显下降报告时需说明。def floor_ceiling(scores: pd.DataFrame, thresh: float 0.15) - dict: 返回每个维度是否存在地板/天花板效应 return {c: {floor: (scores[c] 0).mean() thresh, ceiling: (scores[c] 100).mean() thresh} for c in scores.columns}参数thresh一般取 0.15也可按研究方案调整。判读差值时个体层面常把 5 到 10 分作为最小临床重要差异的参考区间组间比较再结合效应量与置信区间一起看不要只报 p 值。把这三段核查代码挂在每次数据清理之后自动跑一遍量表数据踩坑的概率会明显低于事后靠统计师逐张表地肉眼翻。本文还有配套的精品资源点击获取