十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从Word表格到结构化指标库:免疫组化知识库与鉴别查询

从Word表格到结构化指标库:免疫组化知识库与鉴别查询 简介围绕临床病理诊断中免疫组化指标解读整理的速查文档面向病理科医师、肿瘤科临床医生以及医学检验、规培与进修人员用于免疫组化报告书写与结果判读时快速查证。内容系统梳理P-Gp、GSTπ、TOPOⅡ等耐药预后标记以及ER、PR、C-erbB-2、Ki-67、P53、Nm23、E-Cadherin等常用指标的表达部位与临床意义并补充CK系列、Villin、Hep par 1、CD10、MRP1等标记物在腺癌、肝癌、胃肠道肿瘤及神经内分泌肿瘤鉴别中的适用情况。全文按“标记物—作用—阳性部位—临床意义”对应展开另附乳腺癌全套7项、恶性肿瘤耐药预后全套4项等常用组合便于对照查阅并延伸至内分泌治疗、化疗敏感性判断。资源包内含1个doc文档约30KB轻量易存可打印或离线检索。目前已有118人学习。1. 一份叫「临床常用免疫组化指标的意义.doc」的文件真正的麻烦在检索科室里传过来一个 .doc打开是十来页表格列着 CK7、TTF-1、CDX2、GATA3 这些名字后面跟着「阳性定位」「常见阳性肿瘤」「鉴别要点」。用它的人有三类病理住院医背抗体组合肿瘤内科医生写报告时查一句信息科的人则要把这张表塞进系统给别人查。前两类人只需要读第三类人要把它变成能被程序调用、能被索引、能被持续维护的数据。麻烦恰恰在这里Word 表格是给人眼看的不是给 SQL 看的同一行里既有枚举值又有整句结论克隆号、稀释比、前处理条件常常挤在一格。把这份免疫组化指标文档变成结构化知识库是后面所有查询、推荐和质控动作的地基。2. 免疫组化指标「意义」的语义拆解与建库边界2.1 一个指标的临床意义由四层信息叠起来报告上写「CK7 阳性」信息量其实严重不足。要判断它指向什么至少还要问四件事染在哪里胞膜、胞质、胞核、强度几级、阳性细胞占多少比例、出现在什么形态学背景下。TTF-1 在肺腺癌是胞核阳性在甲状腺乳头状癌同样胞核阳性在部分神经内分泌肿瘤里也能阳性如果字段只存一个is_positive那这条记录在鉴别诊断场景里就是个噪声源查出来的候选肿瘤会多得没法用。所以建模的第一刀不是分表而是把「定位 强度 比例 谱系」拆成独立字段。定位是枚举强度是枚举比例是数值谱系是可以多对多的标签集合。四者混在一个 TEXT 里后面前处理再怎么清洗也救不回来因为自由文本里的「部分阳性」「散在阳性」这类措辞没有统一口径程序无法判定到底是 5% 还是 40%。第二刀是区分「描述性结论」和「可计算结论」。原文档里大量句式属于前者比如「提示胃肠道来源可能」这类句子适合原样保留做展示不适合参与匹配计算而「结直肠癌中 CK20 阳性、CK7 阴性」属于后者可以转成结构化的期望表达状态。两类信息必须分列存放否则查询条件会写成LIKE %阳性%既慢又不可靠。2.2 判读阈值决定字段类型不能全用布尔值不同指标的判读语义差别很大。激素受体看阳性细胞百分比并卡阈值HER2 看分级且中间级别需要补充检测Ki-67 看热点区的计数比例P53 看的是表达模式而不是简单阴阳。把这些塞进同一个TINYINT字段等于提前放弃了一半的查询能力。指标判读语义建议字段类型关键约束ER / PR阳性细胞百分比DECIMAL(5,2)需同时记录阈值口径HER2分级 0/1/2/3SMALLINT 或枚举中间级别需补充检测Ki-67热点区百分比DECIMAL(5,2)需记录计数区域与细胞数P53表达模式弥漫强阳/缺失/野生型枚举不能只存阴阳CK7 / CK20定位 阴阳枚举定位必须单独成列PD-L1评分体系相关数值DECIMAL(5,2)必须绑定克隆号与评分体系表里的约束列很重要判读阈值不是医学上的恒定量它依赖评分体系、抗体克隆和实验室 SOP。落库时把阈值口径写成字段的一部分比写死在代码常量里要安全得多后续更换试剂只需要更新数据行不需要改查询逻辑。提示评分体系相关字段建议与克隆号绑定同一个指标名在不同克隆下阈值可能完全不同只按指标名索引会串数据。2.3 三张主表撑起一个可维护的指标库最小可用的结构是三张表指标主数据、克隆号明细、肿瘤谱期望表达。指标主数据存规范名、别名、谱系和展示用的整句结论克隆号明细存不同试剂平台下的差异期望表达表存「某肿瘤里某指标通常阳性还是阴性」以及权重。第三张表是后面所有鉴别查询的核心。CREATE TABLE marker ( marker_id INT PRIMARY KEY AUTO_INCREMENT, marker_name VARCHAR(32) NOT NULL, -- 规范名如 TTF-1 aliases VARCHAR(255) DEFAULT NULL, -- 别名逗号分隔 lineage VARCHAR(64) DEFAULT NULL, -- 谱系标签如 肺/甲状腺 location VARCHAR(32) DEFAULT NULL, -- nucleus/cytoplasm/membrane score_type VARCHAR(24) DEFAULT NULL, -- binary/percent/grade significance TEXT, -- 原文档整句结论仅供展示 UNIQUE KEY uk_marker_name (marker_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE marker_clone ( clone_id INT PRIMARY KEY AUTO_INCREMENT, marker_id INT NOT NULL, clone_name VARCHAR(64) NOT NULL, -- 克隆号 platform VARCHAR(64) DEFAULT NULL, -- 检测平台 note VARCHAR(512) DEFAULT NULL, -- 该克隆的差异说明 KEY idx_marker (marker_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;aliases用逗号分隔而不是建别名表是在规模可控几百个指标时的一个务实取舍查询侧用FIND_IN_SET或应用层展开即可省掉一次 JOIN。如果别名需要参与高频索引检索再拆成独立表并把FULLTEXT索引加在别名列上。3. 解析「临床常用免疫组化指标的意义.doc」的完整流程3.1 .doc 与 .docx 是两种格式先转再解析.doc是二进制复合文档格式主流 Python 库对它支持都不好python-docx只认 OOXML 包结构的.docx。所以流程的第一步是格式转换把表格结构尽量完整地保留下来。纯文本抽取工具会把表格压成无分隔的段落流行列关系全丢后面切分成本极高不推荐。# 用 LibreOffice 无头模式批量转换保留表格结构 soffice --headless --convert-to docx --outdir ./converted \ 临床常用免疫组化指标的意义.doc # 转换后确认文件存在并查看体积体积异常小通常意味着转换失败 ls -lh ./converted/命令里的--headless表示不启动图形界面适合放在服务器或容器里跑--outdir指定输出目录避免污染源文件目录。转换失败时优先看两点文件是否被其他进程占用以及源文件是否其实是.docx被错误改了扩展名——后者用file命令一看便知直接改回扩展名比走转换更省事。3.2 用 python-docx 抽表格并做行级切分转换完成后表格是按行按格存在的逐格读取再按列语义装配即可。真实文档常见的坑是合并单元格一行里某个格子横跨两列python-docx会把同一个单元格对象返回多次导致重复值。处理办法是同一行的相邻单元格文本相同且列语义明确时去重或者直接用row.cells的下标映射固定列。from docx import Document import json, re doc Document(./converted/临床常用免疫组化指标的意义.docx) records [] for table in doc.tables: # 第一行通常是表头用它定位列下标避免列顺序变化导致错位 header [c.text.strip() for c in table.rows[0].cells] idx_map {name: i for i, name in enumerate(header)} for row in table.rows[1:]: cells [c.text.strip() for c in row.cells] name cells[idx_map.get(指标, 0)] if not name or len(name) 32: # 过滤空行与误读的长句 continue records.append({ marker_name: re.sub(r\s, , name), location: cells[idx_map.get(阳性定位, -1)] if 阳性定位 in idx_map else , positive: cells[idx_map.get(常见阳性肿瘤, -1)] if 常见阳性肿瘤 in idx_map else , note: cells[idx_map.get(鉴别要点, -1)] if 鉴别要点 in idx_map else , }) with open(markers_raw.json, w, encodingutf-8) as f: json.dump(records, f, ensure_asciiFalse, indent2) print(len(records))这段代码的关键设计有三处。其一用表头文本反查列下标而不是写死cells[2]因为不同来源的文档列顺序经常不一致。其二用len(name) 32这种朴素规则过滤掉合并单元格产生的重复长文本指标名不会有这么长。其三re.sub(r\s, , name)去掉全角空格和换行中文指标名里混入的空格是后续 JOIN 匹配失败的头号原因。3.3 定位枚举归一化与克隆号抽取抽出来的定位字段几乎不可能一次干净常见写法有「胞膜/胞质」「细胞核」「核膜」等十几种变体。归一化的做法是建一张映射表把自由文本映射到有限枚举映射不上的记录单独落盘人工确认而不是用模糊匹配硬猜。原始写法归一化结果说明细胞核、核、胞核nucleus归为一类胞质、细胞浆、浆cytoplasm归为一类胞膜、细胞膜、膜membrane归为一类核膜nuclear_membrane独立枚举勿并入 nucleus胞质/胞膜cytoplasm,membrane多值用逗号连接克隆号通常出现在括号里写作SPT24、8G7G3/1、OV-TL 12/30这类形式包含大写字母、数字、连字符和斜杠。CLONE_RE re.compile(r[(]\s*([A-Z0-9][A-Z0-9\-/ ]{1,20})\s*[)]) def extract_clones(text: str): 从描述文本中抽取疑似克隆号返回去重后的列表 hits [m.strip() for m in CLONE_RE.findall(text or )] # 含中文或纯数字的括号内容多半是备注不是克隆号 return [h for h in dict.fromkeys(hits) if not re.search(r[\u4e00-\u9fff], h) and not h.isdigit()]正则把中英文括号都纳入匹配是因为原始文档两种括号混用。「含中文的括号不是克隆号」这条过滤规则挡住了「阳性对照」这类干扰项「纯数字不是克隆号」挡住了「1:100」这类稀释比。剩下的候选再和已维护的克隆号字典比对一次命中率会明显提高。4. 建成指标库后鉴别诊断查询怎么写4.1 期望表达表与权重设计所有查询最终都落在「这个肿瘤里这个指标期望阳性还是阴性」这张表上。权重用来表达区分度某个指标在两种肿瘤里都阳性它对区分二者没有贡献权重应该低某指标只在一种肿瘤里稳定阳性权重应该高。CREATE TABLE marker_expression ( expression_id BIGINT PRIMARY KEY AUTO_INCREMENT, marker_id INT NOT NULL, tumor_id INT NOT NULL, expect_state TINYINT NOT NULL, -- 1 期望阳性, 0 期望阴性, 2 可变 weight DECIMAL(4,2) NOT NULL DEFAULT 1.00, UNIQUE KEY uk_marker_tumor (marker_id, tumor_id), KEY idx_tumor (tumor_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;UNIQUE KEY保证同一个「指标—肿瘤」组合只有一条记录这在批量导入时能挡住重复行idx_tumor让按肿瘤聚合的查询走索引而不是全表扫。expect_state 2表示「可变」参与匹配时直接排除因为可变状态既不能加分也不能减分留在打分里只会稀释信号。4.2 CK7/CK20 这类组合查询固定组合查询是最常见的用法逻辑简单但要求精确。它要回答的是在给定了若干个阳性、若干个阴性指标后哪些肿瘤谱最吻合。-- 输入CK7 阳性、CK20 阴性、TTF-1 阳性、CDX2 阴性 WITH observed AS ( SELECT marker_id, 1 AS state FROM marker WHERE marker_name IN (CK7,TTF-1) UNION ALL SELECT marker_id, 0 AS state FROM marker WHERE marker_name IN (CK20,CDX2) ) SELECT t.tumor_code, t.tumor_name, SUM(CASE WHEN e.expect_state o.state THEN e.weight ELSE -e.weight END) AS match_score, COUNT(*) AS marker_cnt FROM observed o JOIN marker_expression e ON e.marker_id o.marker_id AND e.expect_state IN (0,1) JOIN tumor_type t ON t.tumor_id e.tumor_id GROUP BY t.tumor_id, t.tumor_code, t.tumor_name HAVING marker_cnt 3 -- 至少 3 个指标参与否则结果不可信 ORDER BY match_score DESC, marker_cnt DESC LIMIT 10;CASE WHEN那一行是打分核心命中期望状态加权重冲突减权重于是分数为正且高的肿瘤是吻合的分数为负的可以直接排除。HAVING marker_cnt 3是一道业务闸门只用两个指标推出的结论通常覆盖太多种肿瘤没有临床参考价值。LIMIT 10是给前端留的展示上限实际使用中通常只看前五条。注意负分不等于排除只代表「该指标与该肿瘤的常见表达谱不符」形态学和其他证据仍然要参与判断查询结果不能直接当结论用。4.3 从期望表达反查「下一步该染什么」另一种常见需求是反过来已经染了几个指标接下来染哪个最能分开候选肿瘤。做法是先算出当前候选集再对候选集里表达状态分歧最大的指标排序。-- 统计各指标在候选肿瘤集合中的阳性率接近 50% 的指标区分度最高 SELECT m.marker_name, SUM(e.expect_state 1) / COUNT(*) AS pos_ratio, COUNT(*) AS tumor_cnt FROM marker_expression e JOIN marker m ON m.marker_id e.marker_id WHERE e.tumor_id IN (101, 102, 103, 104) -- 上一步得到的候选肿瘤 ID AND e.expect_state IN (0,1) GROUP BY m.marker_id, m.marker_name HAVING tumor_cnt 3 ORDER BY ABS(pos_ratio - 0.5) ASC LIMIT 5;ABS(pos_ratio - 0.5) ASC把阳性率最接近一半的指标排在最前面这类指标在候选肿瘤之间分化最大继续染它的信息增益最高。tumor_cnt 3保证该指标在候选集里有足够覆盖避免因为只在个别肿瘤中有记录而排到前面。5. 指标库上线后最容易翻车的三个地方5.1 克隆号不同意义就不同把指标名当唯一键是这套系统最典型的设计缺陷。同一个名字在不同克隆号下敏感性和特异性都可能变化某些指标在特定克隆下还会出现额外的阳性谱系。指标常见克隆号需要注意的差异TTF-1SPT24、8G7G3/1敏感性不同部分非肺来源肿瘤也可阳性CK7OV-TL 12/30 等定位一致性较好主要用于谱系排除P40BC28 类鳞癌特异性优于 P63首选用于肺鳞癌判定Ki-67MIB-1 类计数结果高度依赖实验室计数规范PD-L1多种克隆不同克隆对应不同评分体系阈值不可互用应对方式很直接查询入口不是指标名而是「指标 克隆号」前端选择指标后要求选克隆或给一个默认克隆期望表达表按克隆维度建索引。数据量不大时把克隆条件下推到子查询即可代价可以接受。5.2 前处理与对照设置带来的解读偏差抗原修复方式、修复时间、抗体孵育温度、显色时间任何一环偏移都会让弱阳性变阴性或者背景变深。这些信息大多不在指标文档正文里而是散落在实验室 SOP。库表里应当给每个「指标—克隆」组合留出前处理条件字段并且在报告侧记录当次实际条件。ALTER TABLE marker_clone ADD COLUMN retrieval_method VARCHAR(32) DEFAULT NULL, -- 如 HIER/酶消化 ADD COLUMN retrieval_time SMALLINT DEFAULT NULL, -- 分钟 ADD COLUMN control_note VARCHAR(255) DEFAULT NULL; -- 阳/阴性对照要求字段本身不复杂价值在于它让「同一指标结果不一致」这件事有了追溯依据。排查时会发现相当一部分分歧来自前处理或对照缺失而不是指标本身的生物学差异。5.3 别把文档里的整句结论当判定规则用原文档里的结论句高度概括写的是「多数」「常见」「部分」这类程度词。这些词转成查询条件时极易被误当成确定性规则导致结果过度收敛。-- 导入后做一次质量检查找出权重高但没有明确期望状态的记录 SELECT m.marker_name, t.tumor_name, e.expect_state, e.weight FROM marker_expression e JOIN marker m ON m.marker_id e.marker_id JOIN tumor_type t ON t.tumor_id e.tumor_id WHERE e.expect_state 2 AND e.weight 0.5;期望状态为「可变」却给了高权重的记录会同时出现在加分和减分路径上输出结果难以解释。这类记录要么把权重降到 0.3 以下要么补上更明确的表达状态二者选一不要留在中间态。6. 进阶用共现矩阵给指标库加一层组合推荐数据攒够之后可以不加任何医学先验纯从期望表达表里挖出高频共现的指标对用来校验人工录入的组合是否合理也用来发现文档里没写出来的常用搭配。import pandas as pd import numpy as np # 只取期望阳性的记录构建 肿瘤 × 指标 的 0/1 矩阵 df pd.read_sql( SELECT tumor_id, marker_id FROM marker_expression WHERE expect_state 1, conn ) mat df.assign(v1).pivot_table( indextumor_id, columnsmarker_id, valuesv, fill_value0 ) cooc mat.T.dot(mat) # 指标两两共现次数 np.fill_diagonal(cooc.values, 0) # 对角线是自身共现置零 # 用共现次数除以各自的出现次数做归一化抵消高频指标的天然优势 freq mat.sum(axis0) lift cooc.div(np.outer(freq, freq), axis0) * len(mat) pairs lift.stack().sort_values(ascendingFalse).head(20) print(pairs)fill_value0把缺失记录按「不表达」处理这是这一步最重要的假设只有当录入是穷尽式的时候它才成立否则会把「没录」误判成「阴性」进而把共现次数整体拉低。np.fill_diagonal置零排除自身。归一化那一步用共现次数除以两个指标各自的出现频次再乘以肿瘤总数本质是计算提升度避免 CD45 这类到处都阳性的广谱指标霸榜。head(20)取前二十对做人工审阅通常能看到 CK7/CK20、P63/P40 这类预期中的组合也能发现一些文档里分散在不同章节、实际经常一起使用的搭配。审阅完的组合可以回填成一张推荐表与前面的打分查询串联先按观察到的表达谱给出候选肿瘤再按共现矩阵给出下一步建议染色的指标两条路径在同一个页面上并列展示。到这一步那份躺在共享盘里的免疫组化指标文档才算真正变成了一个能被查询、能被校验、能随试剂变化持续维护的指标库。本文还有配套的精品资源点击获取
返回列表