
项目编号:质效精研 · P36专业领域:医疗质量管理 / 数据分析 / 质管信息化核心问题:质管办每天面对海量 Excel、CSV、数据库导出文件,手工核查 1 个月的病案首页要 3 天,一个 Python 脚本只要 10 分钟——质管人要不要学代码?三条战线:
周三下午 4 点,深圳市某三甲医院质管办。
质控员小李对着电脑发呆。桌面上摊着 30 个 Excel——HIS 系统导出的「2026 年 5 月全院病案首页数据」,每个科室 1 个文件,共 12,847 条记录。她需要做的事很明确:
按她过去 3 年的经验,这事要 3 天——一个字段一个字段筛选、复制、粘贴、汇总,中间还要反复核对。
她叹了口气,打开一个新的 Jupyter Notebook,在第一个单元格里敲下:
1 2 3 4 import pandas as pddf = pd.concat([pd.read_excel(f) for f in files], ignore_index=True)print(df.shape) # (12847, 86) 10 分钟后,她把月报交给主任。主任看了 3 秒,问了一句:“你今天怎么这么快?”
她说:"我学了一点 Python。"
这就是 P36 要讲的事——质管人要不要学代码?答案是:要。但不是「成为程序员」,而是「让代码替我干重复活」。
这一篇,我们讲清楚四件事:
不绕弯子,我们开始。
很多质管同行听到「Python」「代码」,第一反应是「那是信息科的事」「我是医生不是程序员」「学了也用不上」。
这三种反应都对——但都不完整。
先看 4 个真实场景,看完你再决定要不要学:
场景 1:批量核查病案首页
人工核查 1 个月病案首页 12,847 条 → 3 天; Python 脚本 10 分钟,准确率 100%(代码不会累)。
场景 2:月度指标趋势图自动生成
人工做 1 张折线图 → 30 分钟(HIS 导出 → Excel 整理 → 选数据 → 调格式); Python 一行代码 → 2 秒:df.plot.line()。
场景 3:DRG 入组结果核查
人工对照 1 个 DRG 分组器的输出 → 半天; Python 脚本批量对照 → 3 分钟。
场景 4:抗菌药物 DDDs(Defined Daily Doses,限定日剂量)计算
人工查 WHO ATC 编码 → 对照 DDD 值 → 求和 → 半天; Python 用 pandas + merge → 5 分钟。
这就是 Python 在医院质管的现实价值——它不是「替代人」,而是「把人从重复劳动里解放出来」。
一个公式总结:
质管人的时间分配 = 80% 重复劳动 + 20% 真正的「分析 + 决策」Python 的作用 = 把那 80% 重复劳动压缩到 5%
我把它总结成「3 个 80%」——这是质管人学 Python 的「必要性证明」:
80% 的工作是重复的
质管办的日常工作里,至少有 80% 是「周期性重复」的:每月 1 次的病案首页核查、每月 1 次的指标月报、每季度 1 次的三级公立医院绩效考核数据填报、每次医保飞检前的数据准备、每次三级评审条款的自评表。
这些工作「流程固定、数据格式固定、输出格式固定」——正是 Python 最擅长干的活。
80% 的数据格式是规整的
医院的数据看似杂乱,其实 80% 都是规整的结构化数据:HIS 导出的 Excel(每个字段都有固定列名)、病案首页(国家卫健委统一规范的 86 项字段)、DRG 分组器输出(标准 CSV)、不良事件上报系统(标准字段)、临床路径系统(标准字典)。
规整的数据 = Python 的菜。Python 最怕的是「非结构化」,比如一份手写病程记录扫描件——但这种数据只占质管工作的 20%。
80% 的分析只用到 20% 的语法
Python 有 30+ 关键字、上百个内置函数、上万个第三方库——但质管人 80% 的日常分析,只用 5 个库 + 20% 的语法:
pandas | ||
openpyxl | ||
pymysqlsqlalchemy | ||
matplotlibseaborn | ||
numpy |
也就是说,质管人不需要「精通 Python」,只需要「会用这 5 个库的 20% 功能」。
质管人学 Python,不是「转行当程序员」,而是「给质管工作装一个外挂」。你不需要懂 Python 的内存管理、多线程、装饰器——你只需要懂「怎么让 Python 替我读 Excel、出报表、画图」。
很多质管同行一上来就买《Python 编程:从入门到实践》,啃了 200 页,放弃了——因为书里讲的是「开发一个网站」「写一个小游戏」,跟医院质管完全脱节。
我推荐一条「阶梯式」学习路径,每一步都跟质管工作直接挂钩:
第 1 阶:Excel 高手(0-3 个月)
如果你 Excel 还在用「筛选 + 复制粘贴」的阶段,先别碰 Python。先把 Excel 学到「数据透视表 + VLOOKUP + 简单函数」的阶段——这一步解决 50% 的日常工作。
第 2 阶:SQL 入门(3-6 个月)
SQL 是「结构化查询语言」,专门用来「问数据库要数据」。医院 HIS、LIS、EMR(电子病历,Electronic Medical Record)系统背后基本都是 MySQL 或 Oracle 数据库——学会 SQL,你就能「直接问数据库要数据」,不用等信息科导出 Excel。
第 3 阶:Python 入门(6-12 个月)
SQL 学到「能自己取数」之后,再学 Python。Python 的入门只需 3 个核心概念:变量与数据类型(数字、字符串、列表、字典)、流程控制(if / for / while)、函数与库(def / import)。把这 3 个概念学会,就能开始写「自动化脚本」了。
第 4 阶:pandas 实战(12-18 个月)
学会 Python 基础语法后,主攻 pandas——这是质管人 80% 工作会用的库。学完 pandas,你的 Excel 工作量会减少 70%。
第 5 阶:可视化 + 数据库 + 报表自动化(18-24 个月)
最后是 matplotlib / seaborn(画图)、pymysql(读数据库)、Jinja2(自动报表)——这是「极客层」的进阶。
为了让你对「Python 在医院质管能干啥」有直观印象,我列一份应用清单(后续章节会逐个展开):
| 数据清洗 | ||
| 统计计算 | ||
| 报表自动化 | ||
| 可视化 | ||
| 数据质量核查 | ||
| 文本分析 | ||
| 数据库直连 | ||
| Web 应用 |
到这里,你应该明白了:Python 不是「程序员的专利」,而是「质管人的外挂」。下一步,我们进入「怎么用」——5 个核心库 + 10 个实战场景。
这一节,我们把质管人最常用的 5 个 Python 库 + 10 个实战场景,逐个拆给你看。每个场景都配可运行的代码——你复制粘贴就能用。
在写代码之前,先把 5 个核心库「装到你的工具箱」里:
1. pandas:表格数据处理(最重要)
pandas 的核心数据结构是 DataFrame(可以理解为「加强版 Excel 表」)。一行代码读 Excel:
1 2 3 4 5 6 7 8 import pandas as pddf = pd.read_excel('5月病案首页.xlsx')print(df.head()) # 看前 5 行print(df.shape) # (12847, 86) 12847 行,86 列print(df.columns) # 所有列名print(df.dtypes) # 每列的数据类型print(df.describe()) # 数值列的统计描述 2. numpy:数值计算
numpy 是 pandas 的「底层引擎」,处理矩阵和数值运算:
1 2 3 4 5 import numpy as nparr = np.array([1, 2, 3, 4, 5])print(arr.mean()) # 3.0print(arr.std()) # 1.4142135623730951 3. matplotlib / seaborn:可视化
matplotlib 是 Python 最基础的绘图库,seaborn 是「美化版」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import matplotlib.pyplot as plt# 折线图:某指标 12 个月趋势months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']values = [85, 87, 86, 88, 90, 89, 91, 92, 93, 92, 94, 95]plt.rcParams['font.sans-serif'] = ['SimHei']plt.rcParams['axes.unicode_minus'] = Falseplt.figure(figsize=(10, 5))plt.plot(months, values, marker='o')plt.title('CMI 月度趋势')plt.xlabel('月份')plt.ylabel('CMI')plt.grid(True)plt.savefig('cmi_trend.png', dpi=200)plt.show() 4. openpyxl:Excel 读写
openpyxl 主要用来「精细控制 Excel 样式」(合并单元格、设置字体、加颜色):
1 2 3 4 5 6 7 8 9 10 11 12 13 from openpyxl import load_workbookfrom openpyxl.styles import Font, PatternFillwb = load_workbook('月报.xlsx')ws = wb.active# 设置标题字体ws['A1'].font = Font(bold=True, size=14)# 设置单元格颜色ws['A1'].fill = PatternFill(start_color='FFFF00', end_color='FFFF00', fill_type='solid')wb.save('月报_样式.xlsx') 5. pymysql / sqlalchemy:数据库连接
pymysql 直连 MySQL,sqlalchemy 是「ORM 版」更通用:
1 2 3 4 5 6 7 8 9 10 11 12 import pymysqlconn = pymysql.connect( host='10.1.2.3', # HIS 数据库地址 user='quality_reader', # 只读账号 password='******', database='his_db', charset='utf8mb4')df = pd.read_sql('SELECT * FROM diagnosis LIMIT 1000', conn)conn.close() 下面 10 个场景覆盖质管办 80% 的日常工作。每个场景包含「场景描述 + 完整代码 + 运行说明」。
场景:HIS 系统每月导出 30 个科室的病案首页 Excel,每个文件结构相同。需要合并成 1 个总表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import pandas as pdimport globimport os# 1. 找到所有 Excel 文件files = glob.glob('data/病案首页_2026年5月_*.xlsx')print(f'共找到 {len(files)} 个文件')# 2. 循环读取并合并dfs = []for f in files: dept_name = os.path.basename(f).replace('病案首页_2026年5月_', '').replace('.xlsx', '') df = pd.read_excel(f) df['科室'] = dept_name # 加 1 列「科室」,方便后续分组 dfs.append(df)# 3. 合并所有 DataFramedf_all = pd.concat(dfs, ignore_index=True)print(f'合并后总行数:{len(df_all)}')# 4. 保存df_all.to_excel('output/5月病案首页_全院合并.xlsx', index=False) 效率:30 个文件,合并时间 5-10 秒;手工复制粘贴至少 2 小时。
场景:合并后的全院数据有重复行、有缺失值、有异常值,需要清洗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import pandas as pddf = pd.read_excel('output/5月病案首页_全院合并.xlsx')# 步骤 1:去重print(f'去重前行数:{len(df)}')df = df.drop_duplicates(subset=['住院号'], keep='first')print(f'去重后行数:{len(df)}')# 步骤 2:缺失值处理missing_rate = df.isnull().sum() / len(df) * 100print('缺失率 > 5% 的列:')print(missing_rate[missing_rate > 5].sort_values(ascending=False))df = df.dropna(subset=['主要诊断编码'])median_age = df['年龄'].median()df['年龄'] = df['年龄'].fillna(median_age)df['婚姻状况'] = df['婚姻状况'].fillna('未知')# 步骤 3:异常值处理abnormal_age = df[(df['年龄'] < 0) | (df['年龄'] > 150)]print(f'年龄异常记录:{len(abnormal_age)} 条')df = df[(df['年龄'] >= 0) & (df['年龄'] <= 150)]df = df[(df['住院天数'] >= 0) & (df['住院天数'] <= 365)]df = df[df['性别'].isin(['男', '女', '未知'])]print(f'清洗后行数:{len(df)}') 效果:清洗后数据「干净度」从 80% 提升到 99%。
场景:有两张表——「患者信息表」(patient_id, 姓名, 性别, 年龄)、「诊断记录表」(patient_id, 诊断编码, 诊断名称)。需要合并成 1 张表用于分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import pandas as pddf_patient = pd.read_excel('data/患者信息.xlsx')df_diagnosis = pd.read_excel('data/诊断记录.xlsx')# 内连接(只保留有诊断的患者)df_merged = pd.merge(df_patient, df_diagnosis, on='patient_id', how='inner')# 左连接(保留所有患者,没诊断的填 NaN)df_left = pd.merge(df_patient, df_diagnosis, on='patient_id', how='left')print(f'患者表行数:{len(df_patient)}')print(f'诊断表行数:{len(df_diagnosis)}')print(f'内连接后:{len(df_merged)}') SQL 对照:SELECT * FROM patient p INNER JOIN diagnosis d ON p.patient_id = d.patient_id;
场景:病案首页 86 项字段中,有 15 项是「必填且非空」的。需要自动核查,生成问题清单。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import pandas as pddf = pd.read_excel('output/5月病案首页_全院合并.xlsx')required_fields = [ '住院号', '姓名', '性别', '年龄', '入院日期', '出院日期', '主要诊断编码', '主要诊断名称', '主要手术操作编码', '主要手术操作名称', '入院病情', '出院病情', '科别', '床位医师']issues = []for field in required_fields: if field not in df.columns: print(f'警告:字段「{field}」不存在') continue missing_count = df[field].isnull().sum() empty_count = (df[field].astype(str).str.strip() == '').sum() total_problems = missing_count + empty_count issues.append({ '字段': field, '缺失数': missing_count, '空白数': empty_count, '总问题数': total_problems, '问题率(%)': round(total_problems / len(df) * 100, 2) })df_issues = pd.DataFrame(issues).sort_values('总问题数', ascending=False)print(df_issues)df_issues.to_excel('output/病案首页必填字段核查报告.xlsx', index=False) 输出:
1 2 3 4 字段 缺失数 空白数 总问题数 问题率(%) 主要手术操作名称 1245 89 1334 10.38 主要手术操作编码 1203 89 1292 10.06 入院病情 56 0 56 0.44 场景:护理部上报的不良事件数据,需要按「级别 + 类别 + 科室」三维统计。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import pandas as pddf = pd.read_excel('data/2026年不良事件.xlsx')# 透视表:按级别 × 类别pivot1 = df.pivot_table( index='事件级别', columns='事件类别', values='事件ID', aggfunc='count', fill_value=0, margins=True, margins_name='合计')print('事件级别 × 事件类别:')print(pivot1)# 按科室统计,降序dept_stats = df.groupby('科室').agg( 事件总数=('事件ID', 'count'), 严重事件数=('事件级别', lambda x: (x == '严重').sum()), 已结案数=('状态', lambda x: (x == '已结案').sum())).sort_values('事件总数', ascending=False)print('科室统计:')print(dept_stats)with pd.ExcelWriter('output/不良事件统计月报.xlsx', engine='openpyxl') as writer: pivot1.to_excel(writer, sheet_name='级别×类别') dept_stats.to_excel(writer, sheet_name='科室统计') 场景:药剂科要求每月统计抗菌药物 DDDs(DDDs = 总用药量 / DDD 值),衡量抗菌药物使用强度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import pandas as pddf_use = pd.read_excel('data/5月抗菌药物使用记录.xlsx')df_ddd = pd.read_excel('data/抗菌药物DDD字典.xlsx')df_merged = pd.merge(df_use, df_ddd, on='药物编码', how='left')df_merged['DDDs'] = df_merged['用药总量(克)'] / df_merged['DDD值(克)']drug_ddds = df_merged.groupby(['药物名称', 'ATC分类']).agg( 用药总量=('用药总量(克)', 'sum'), DDDs=('DDDs', 'sum')).sort_values('DDDs', ascending=False)print(drug_ddds.head(20))df_dept = df_merged.groupby('科室').agg( DDDs合计=('DDDs', 'sum'), 住院人天=('住院人天', 'sum'))df_dept['DDDs_100人天'] = df_dept['DDDs合计'] / df_dept['住院人天'] * 100print('科室抗菌药物使用强度:')print(df_dept.sort_values('DDDs_100人天', ascending=False)) DDDs(DDDs,Defined Daily Doses,限定日剂量数)反映的是「每种药物被使用的平均日剂量数」,数值越大,说明该药使用越广泛。WHO ATC/DDD 系统是国际通用的药物计量标准。
场景:统计每个科室每个月的「临床路径入径率」「完成率」「变异率」。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import pandas as pddf = pd.read_excel('data/临床路径数据_2026.xlsx')grouped = df.groupby(['科室', '月份', '路径名称']).agg( 符合路径=('是否符合', lambda x: (x == '是').sum()), 实际入径=('是否入径', lambda x: (x == '是').sum()), 完成路径=('是否完成', lambda x: (x == '是').sum()), 发生变异=('是否变异', lambda x: (x == '是').sum()))grouped['入径率'] = grouped['实际入径'] / grouped['符合路径'] * 100grouped['完成率'] = grouped['完成路径'] / grouped['实际入径'] * 100grouped['变异率'] = grouped['发生变异'] / grouped['实际入径'] * 100grouped = grouped.round(2)high_variation = grouped[grouped['变异率'] > 30].sort_values('变异率', ascending=False)print('高变异率路径:')print(high_variation)grouped.to_excel('output/临床路径入径率分析.xlsx') 场景:DRG 分组器输出的入组结果,需要核查「入组率」「低风险死亡率」「错误入组」。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import pandas as pddf = pd.read_excel('data/5月DRG入组结果.xlsx')# 总体入组率total_rate = (df['DRG编码'].notnull().sum() / len(df)) * 100print(f'全院 DRG 入组率:{total_rate:.2f}%')# 各科室入组率dept_rate = df.groupby('科室').apply( lambda x: pd.Series({ '病例数': len(x), '入组数': x['DRG编码'].notnull().sum(), '入组率(%)': round(x['DRG编码'].notnull().sum() / len(x) * 100, 2) })).sort_values('入组率(%)', ascending=False)warning = dept_rate[dept_rate['入组率(%)'] < 95]print('入组率 < 95% 的科室:')print(warning)# CMI 计算total_weight = df['DRG权重'].sum()cni_count = len(df[df['DRG编码'].notnull()])cmi = total_weight / cni_countprint(f'全院 CMI:{cmi:.4f}')dept_rate.to_excel('output/DRG入组率核查.xlsx') 场景:每月输出 5-10 个关键指标的「趋势图」,用于月报。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 import pandas as pdimport matplotlib.pyplot as pltimport osplt.rcParams['font.sans-serif'] = ['SimHei']plt.rcParams['axes.unicode_minus'] = Falsedf = pd.read_excel('data/2025年指标数据.xlsx')os.makedirs('output/趋势图', exist_ok=True)indicators = ['CMI', '入组率', '平均住院日', '抗菌药物使用强度', '手术并发症发生率']fig, axes = plt.subplots(2, 3, figsize=(15, 10))axes = axes.flatten()for i, ind in enumerate(indicators): if ind not in df.columns: continue ax = axes[i] ax.plot(df['月份'], df[ind], marker='o', linewidth=2) ax.set_title(ind, fontsize=14) ax.set_xlabel('月份') ax.set_ylabel(ind) ax.grid(True, alpha=0.3) ax.tick_params(axis='x', rotation=45)for j in range(len(indicators), len(axes)): axes[j].set_visible(False)plt.tight_layout()plt.savefig('output/趋势图/月度指标趋势图.png', dpi=200, bbox_inches='tight')plt.show()print('趋势图已生成') 场景:每月自动出一份《数据质量报告》,包括「完整性、一致性、及时性、唯一性」四个维度。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import pandas as pdfrom datetime import datetimedf = pd.read_excel('output/5月病案首页_全院合并.xlsx')report = []report.append(f'数据质量报告 - {datetime.now().strftime("%Y-%m-%d")}')report.append(f'总记录数:{len(df)}')report.append('=' * 50)# 完整性report.append('\n【维度 1:完整性】')required_fields = ['主要诊断编码', '主要手术操作编码', '入院病情', '出院病情']for field in required_fields: if field in df.columns: missing_rate = df[field].isnull().sum() / len(df) * 100 status = '通过' if missing_rate < 1 else '不通过' report.append(f' {field}:缺失率 {missing_rate:.2f}% [{status}]')# 一致性report.append('\n【维度 2:一致性】')invalid_date = (df['入院日期'] >= df['出院日期']).sum()report.append(f' 入院日期 >= 出院日期:{invalid_date} 条')icd_pattern = df['主要诊断编码'].astype(str).str.match(r'^[A-Z]\d{2}(\.\d{1,2})?$')invalid_icd = (~icd_pattern & df['主要诊断编码'].notnull()).sum()report.append(f' 主要诊断编码格式不规范:{invalid_icd} 条')# 及时性report.append('\n【维度 3:及时性】')df['归档时效'] = (df['归档日期'] - df['出院日期']).dt.total_seconds() / 3600on_time_rate = (df['归档时效'] <= 24).sum() / len(df) * 100report.append(f' 24 小时内归档率:{on_time_rate:.2f}%')# 唯一性report.append('\n【维度 4:唯一性】')duplicate_id = df.duplicated(subset=['住院号'], keep=False).sum()report.append(f' 住院号重复:{duplicate_id} 条')with open('output/数据质量报告_5月.txt', 'w', encoding='utf-8') as f: f.write('\n'.join(report))print('\n'.join(report)) 输出示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 数据质量报告 - 2026-06-24总记录数:12847==================================================【维度 1:完整性】 主要诊断编码:缺失率 0.12% [通过] 主要手术操作编码:缺失率 10.06% [不通过]【维度 2:一致性】 入院日期 >= 出院日期:3 条 主要诊断编码格式不规范:127 条【维度 3:及时性】 24 小时内归档率:89.32%【维度 4:唯一性】 住院号重复:0 条 Python 脚本写完后,怎么知道脚本「靠不靠谱」?用这张 Checklist:
| 脚本运行成功率 | |||
| 数据准确率 | |||
| 效率提升比 | |||
| 字段口径一致性 | |||
| 空值 / 异常值处理 | |||
| 版本控制 | |||
| 文档完整性 | |||
| 权限最小化 | |||
| 可重复运行 | |||
| 维护成本 |
把这张 Checklist 嵌入 CI/CD(持续集成/持续部署)流水线,每次脚本变更前自动跑一遍——脚本质量有保障,才不会「跑 1 次准,跑 100 次偏」。
到这里,10 个实战场景 + 5 个核心库都拆完了。但这只是「会用 Python」的阶段——下一步,我们看「把 Python 用到极致」的极客层面。
上一节的脚本,适合个人使用;这一节的工具,适合团队协作 + 流程自动化。
Jupyter Notebook 是一种「交互式编程环境」——你可以一段一段地写代码,每一段的结果(表格、图表、文字)直接显示在代码下面。
为什么质管团队特别适合用 Jupyter?
质管团队用 Jupyter 的 3 个真实场景:
场景:每月要出 1 份《全院质控月报》(PDF),内容固定,只是数据变。
方案:用 Jinja2(模板引擎)+ WeasyPrint(PDF 生成器)——把「文字模板 + 数据」自动合并成 PDF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 from jinja2 import Templateimport pandas as pdfrom datetime import datetimedf = pd.read_excel('output/5月病案首页_全院合并.xlsx')total = len(df)grouped_rate = df['DRG编码'].notnull().sum() / total * 100cmi = df['DRG权重'].sum() / df['DRG编码'].notnull().sum()data = { 'report_month': '2026年5月', 'generate_date': datetime.now().strftime('%Y-%m-%d'), 'total_cases': total, 'drg_rate': f'{grouped_rate:.2f}', 'cmi': f'{cmi:.4f}', 'avg_los': f'{df["住院天数"].mean():.2f}'}template_str = """<!DOCTYPE html><html><head><meta charset="utf-8"><title>质控月报</title></head><body><h1>{{ report_month }} 全院质控月报</h1><p>生成日期:{{ generate_date }}</p><h2>一、总体情况</h2><ul> <li>出院病例总数:<b>{{ total_cases }}</b> 例</li> <li>DRG 入组率:<b>{{ drg_rate }}%</b></li> <li>CMI:<b>{{ cmi }}</b></li> <li>平均住院日:<b>{{ avg_los }}</b> 天</li></ul></body></html>"""template = Template(template_str)html_content = template.render(**data)with open('output/月报.html', 'w', encoding='utf-8') as f: f.write(html_content)# 转 PDF(需要安装 weasyprint)# from weasyprint import HTML# HTML(string=html_content).write_pdf('output/月报.pdf') 效果:改模板,数据自动填入;每月只需跑 1 次脚本,PDF 月报自动生成。
场景:病程记录、出院小结是「非结构化文本」,如何自动提取关键信息?
方案:用 jieba(中文分词)+ re(正则表达式)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 import jiebaimport resample_text = """患者于 2026-05-10 在全身麻醉下行腹腔镜下胆囊切除术,术中出血约 50ml,术后安返病房。术后第 2 天出现发热,体温最高 38.5℃,考虑切口感染,给予头孢呋辛钠抗感染治疗后好转。"""# 中文分词words = jieba.lcut(sample_text)print('分词结果:')print('/'.join(words))# 提取关键信息surgery_match = re.search(r'行(.*?术)[,。]', sample_text)if surgery_match: print(f'手术名称:{surgery_match.group(1)}')dates = re.findall(r'\d{4}-\d{2}-\d{2}', sample_text)print(f'涉及日期:{dates}')temps = re.findall(r'(\d+\.?\d*)℃', sample_text)print(f'体温记录:{temps}')complication_keywords = ['感染', '出血', '发热', '疼痛', '肿胀', '渗出']found = [k for k in complication_keywords if k in sample_text]print(f'并发症关键词:{found}') 输出:
1 2 3 4 手术名称:腹腔镜下胆囊切除术涉及日期:['2026-05-10']体温记录:['38.5']并发症关键词:['感染', '发热'] 进阶:用 sklearn 的 TF-IDF 或 BERT 模型做「病历自动分类」(病种、病情严重程度)——这一层需要更多 NLP 基础,可以放到 P37 详述。
场景:质管办做了一个「病案首页核查工具」脚本,但临床科室不会用 Python——怎么让临床也能用?
方案:用 Streamlit 把 Python 脚本包装成 Web 应用,临床打开浏览器就能用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 # streamlit_app.pyimport streamlit as stimport pandas as pdst.title('病案首页核查工具')uploaded = st.file_uploader('上传病案首页 Excel', type=['xlsx'])if uploaded: df = pd.read_excel(uploaded) st.success(f'共读取 {len(df)} 条记录') st.subheader('必填字段核查') required = ['主要诊断编码', '主要手术操作编码', '入院病情'] for field in required: if field in df.columns: missing = df[field].isnull().sum() rate = missing / len(df) * 100 if rate > 5: st.error(f'{field}:缺失 {missing} 条 ({rate:.2f}%) - 不通过') else: st.success(f'{field}:缺失 {missing} 条 ({rate:.2f}%) - 通过') if st.checkbox('显示原始数据'): st.dataframe(df.head(100)) 运行:streamlit run streamlit_app.py,打开浏览器访问 http://localhost:8501,临床就能直接上传 Excel 看核查结果——完全不用懂 Python。
为了让你看清「Python 自动化体系」的全貌,画一张架构图:

再画一张「质控员日常 Python 工作流」:

到这里,极客层面的工具都摆齐了。但这一切要落地,中间还隔着「人」这道坎——下一节,我们走进一家三甲医院的真实场景,看一个质控员是怎么从「手工」走到「自动化」的。
2025 年,深圳某三甲医院(化名「海珠医院」,开放床位 1800 张,日均门诊 9000 人次)质管办来了一位新人——小陈,护理学硕士,刚毕业 1 年。
小陈入职第 1 周:
小陈入职第 3 个月:
效率提升:432 倍。
| 第 1 月 | ||
| 第 2 月 | ||
| 第 3 月 | ||
| 第 4 月 | ||
| 第 5 月 | ||
| 第 6 月 |
| -92% | |||
| +4.5 pp | |||
| -87.5% | |||
| +150% |
merge 的那天,就找了「患者表 + 诊断表合并」这个真实场景;学 groupby 的那天,就找了「按科室统计入组率」。学以致用,3 个月就出师。到这里,4 个层级都拆完了。最后,我们给出 30 天行动清单 + P37 预告。
回到周三下午 4 点的小李。
她面前的那 30 个 Excel 不会消失——病案首页每个月都要核查,月报每个月都要出,指标每个月都要统计。但从今天起,她做三件事:
第一,装上 Python 环境——Anaconda 一键安装,30 分钟搞定。
第二,写第 1 个脚本——哪怕只是「读 1 个 Excel,看前 5 行」——只要迈出第一步,后面就顺了。
第三,加入 1 个 Python 质管社群——遇到问题有人问、有人答,比自己摸索快 10 倍。
她不需要成为程序员,她只需要让 Python 替她干那 80% 的重复活。
| 天数 | 动作 | 输出物 | |—|—|—| | Day 1 | 安装 Anaconda,打开 Jupyter Notebook | 环境就绪 | | Day 2 | 第 1 个脚本:读 1 个 Excel,看前 5 行 | df.head() 运行成功 | | Day 3 | 学 pandas 的 describe() / info() / shape | 3 行代码搞定 | | Day 4 | 学 dropna() / drop_duplicates() | 清洗 1 个真实 Excel | | Day 5 | 学 groupby() + agg() | 按科室统计 1 个指标 | | Day 6 | 实战 1:批量合并 30 个科室的病案首页 | 合并后的总表 | | Day 7 | 实战 2:数据清洗(去重 + 缺失值 + 异常值) | 清洗后的总表 | | Day 8 | 实战 3:病案首页必填字段核查 | 核查报告 Excel | | Day 9 | 学 matplotlib 的 plot() + figure() | 1 张折线图 | | Day 10 | 实战 4:指标月度趋势图自动生成 | 5 张趋势图 PNG | | Day 11 | 学 openpyxl 的 Font + PatternFill | 1 份带样式的 Excel | | Day 12 | 学 pivot_table() | 1 张透视表 | | Day 13 | 实战 5:不良事件分类统计 | 透视表 + 科室统计 | | Day 14 | 学 merge() | 合并 2 张表 | | Day 15 | 学 numpy 的 mean() / std() / median() | 3 个统计函数 | | Day 16 | 实战 6:抗菌药物 DDDs 自动计算 | 科室 DDDs 报表 | | Day 17 | 实战 7:临床路径入径率分析 | 路径入径率报表 | | Day 18 | 实战 8:DRG 入组结果核查 | 入组率 + CMI + 时间消耗指数 | | Day 19 | 实战 9:数据质量自动报告 | 4 维度数据质量报告 | | Day 20 | 学 pymysql 直连 MySQL | 1 个 SQL 查询结果 | | Day 21 | 实战 10:从 HIS 数据库直接取数 | 不再等信息科导出 | | Day 22 | 学 Jinja2 模板语法 | 1 个 HTML 模板 | | Day 23 | 实战:用 Jinja2 生成月报 HTML | 1 份自动月报 | | Day 24 | 学 Streamlit 基础 | 1 个简单 Web 应用 | | Day 25 | 实战:把核查脚本包装成 Streamlit | 临床可用的 Web 工具 | | Day 26 | 学 jieba 中文分词 + re 正则 | 病程记录分词结果 | | Day 27 | 实战:病历文本关键词提取 | 手术名 + 并发症关键词 | | Day 28 | 把所有脚本用 git 管理 | GitHub 仓库 | | Day 29 | 写一份《我的 Python 学习笔记》 | 学习笔记 Markdown | | Day 30 | 30 天复盘:出《P36 30 天落地报告》 | 复盘报告 + 下一阶段计划 |
30 天不是空话,是从「不会 Python」到「会写脚本」的硬约束。 Day 1 必须今天完成,Day 30 必须 30 天后能跑出 1 份自动化月报——这就是质管人学 Python 该有的节奏。
关注「质领未来」,每一篇,都让质管人少走一年弯路。 留言区留下你 学 Python 踩过最深的坑(比如装环境装到崩溃、写脚本写到半夜、出错找不到原因、坚持不下去想放弃……),狼叔会在 P37-P39 里挑 3 个高频痛点做深度拆解。
《质效精研》P36 · Python 赋能:质管人也要学一点代码,批量处理病历数据
📖 扩展阅读
医数智联·第 14 期:Python 入门 —— 医院人零基础友好,30 分钟跑通第一个脚本
质效精研 P35 | 极客工具:Excel 不只是表格,用函数实现质控自动化