assets/2026-05-30-质效精研-系列预告_2026-06-01_09-49-25.jpg

[!ABSTRACT] 核心摘要
项目编号:质效精研 · P36
专业领域:医疗质量管理 / 数据分析 / 质管信息化
核心问题:质管办每天面对海量 Excel、CSV、数据库导出文件,手工核查 1 个月的病案首页要 3 天,一个 Python 脚本只要 10 分钟——质管人要不要学代码?
三条战线:

  • 🟢 基础扫盲:Python 在医院质管的现实价值、3 个 80% 问题、Excel → SQL → Python 学习路径、5 大应用清单
  • 🟡 实战进阶:5 个核心库 + 10 个质控实战场景(全部带 Python 代码)+ 质控 Checklist
  • 🔴 极客升维:Jupyter Notebook 协作、Jinja2 自动报表、病历文本 NLP、Streamlit 简易 Web 应用
    目标篇幅:9,000-11,000 字

前言:周三下午 4 点,质控员小李的 3 天 vs Python 的 10 分钟

周三下午 4 点,深圳市某三甲医院质管办。

质控员小李对着电脑发呆。桌面上摊着 30 个 Excel——HIS 系统导出的「2026 年 5 月全院病案首页数据」,每个科室 1 个文件,共 12,847 条记录。她需要做的事很明确:

  • 核查主要诊断编码是否为空;
  • 核查主要手术操作是否漏填;
  • 核查入院病情与出院诊断是否符合;
  • 核查 24 小时内归档率;
  • 统计每个科室的入组率、CMI、时间消耗指数;
  • 输出 1 份 30 页的《5 月病案首页质控月报》。

按她过去 3 年的经验,这事要 3 天——一个字段一个字段筛选、复制、粘贴、汇总,中间还要反复核对。

她叹了口气,打开一个新的 Jupyter Notebook,在第一个单元格里敲下:

1
2
3
4
import pandas as pd

df = pd.concat([pd.read_excel(f) for f in files], ignore_index=True)
print(df.shape) # (12847, 86)

10 分钟后,她把月报交给主任。主任看了 3 秒,问了一句:“你今天怎么这么快?”

她说:”我学了一点 Python。

这就是 P36 要讲的事——质管人要不要学代码?答案是:要。但不是「成为程序员」,而是「让代码替我干重复活」

这一篇,我们讲清楚四件事:

  1. Python 在医院质管的现实价值,以及「3 个 80%」问题;
  2. 5 个核心库 + 10 个实战场景(每个场景都带可运行的 Python 代码);
  3. 极客层面的协作、自动报表、NLP、Web 应用;
  4. 一位三甲医院质控员的真实转型案例,以及 30 天行动清单。

不绕弯子,我们开始。

Part 1:基础扫盲层——质管人要不要学 Python?

很多质管同行听到「Python」「代码」,第一反应是「那是信息科的事」「我是医生不是程序员」「学了也用不上」。

这三种反应都对——但都不完整。

一、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%

二、质管人学 Python 的「3 个 80%」问题

我把它总结成「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 Excel 读写 每天
pymysql / sqlalchemy 数据库连接 每周
matplotlib / seaborn 图表 每周
numpy 数值计算 每周

也就是说,质管人不需要「精通 Python」,只需要「会用这 5 个库的 20% 功能」

[!INFO] 老炮提醒
质管人学 Python,不是「转行当程序员」,而是「给质管工作装一个外挂」。你不需要懂 Python 的内存管理、多线程、装饰器——你只需要懂「怎么让 Python 替我读 Excel、出报表、画图」。

三、学习路径:Excel → SQL → Python(阶梯式)

很多质管同行一上来就买《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(自动报表)——这是「极客层」的进阶。

[!TIP] 给质管人的学习节奏建议

  • 不要一次性学完所有语法——用啥学啥,边用边学;
  • 每学一个语法,立刻找一个质管场景练手;
  • 3 个月不写代码,就忘了——保持每周写 1-2 次。

四、质控场景的 Python 应用清单

为了让你对「Python 在医院质管能干啥」有直观印象,我列一份应用清单(后续章节会逐个展开):

应用方向 典型场景 替代的人工工作量
数据清洗 去重、缺失值处理、异常值识别 1 周 → 10 分钟
统计计算 CMI、时间消耗指数、DDDs、入组率 1 天 → 5 分钟
报表自动化 月报、季报、年报、专项报告 1 周 → 1 小时
可视化 趋势图、分布图、对比图、驾驶舱 1 天 → 30 分钟
数据质量核查 病案首页核查、字段完整性、逻辑校验 3 天 → 10 分钟
文本分析 病程记录关键词提取、不良事件分类 1 周 → 30 分钟
数据库直连 直接从 HIS 取数,不用等信息科导出 每次节省 1-2 天
Web 应用 质控员在线核查工具、科室自助查询 自建轻量工具

到这里,你应该明白了:Python 不是「程序员的专利」,而是「质管人的外挂」。下一步,我们进入「怎么用」——5 个核心库 + 10 个实战场景。

Part 2:实战进阶层——5 个核心库 + 10 个质控场景

这一节,我们把质管人最常用的 5 个 Python 库 + 10 个实战场景,逐个拆给你看。每个场景都配可运行的代码——你复制粘贴就能用。

一、5 个核心库速览

在写代码之前,先把 5 个核心库「装到你的工具箱」里:

1. pandas:表格数据处理(最重要)

pandas 的核心数据结构是 DataFrame(可以理解为「加强版 Excel 表」)。一行代码读 Excel:

1
2
3
4
5
6
7
8
import pandas as pd

df = 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 np

arr = np.array([1, 2, 3, 4, 5])
print(arr.mean()) # 3.0
print(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'] = False

plt.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_workbook
from openpyxl.styles import Font, PatternFill

wb = 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 pymysql

conn = 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()

[!WARNING] 数据库访问安全提示

  • 不要用 root / admin 账号直连生产库——申请只读账号;
  • 不要在脚本里写明文密码——用环境变量或密钥管理;
  • 不要在脚本里改数据——分析脚本只读,不改。

二、10 个实战场景(全部带代码)

下面 10 个场景覆盖质管办 80% 的日常工作。每个场景包含「场景描述 + 完整代码 + 运行说明」。

场景 1:批量读取 HIS 导出的 Excel

场景: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 pd
import glob
import 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. 合并所有 DataFrame
df_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 小时。

场景 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 pd

df = 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) * 100
print('缺失率 > 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%。

场景 3:数据合并(多表 JOIN)

场景:有两张表——「患者信息表」(patient_id, 姓名, 性别, 年龄)、「诊断记录表」(patient_id, 诊断编码, 诊断名称)。需要合并成 1 张表用于分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import pandas as pd

df_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;

场景 4:病案首页关键字段核查

场景:病案首页 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 pd

df = 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

场景 5:不良事件分类统计

场景:护理部上报的不良事件数据,需要按「级别 + 类别 + 科室」三维统计。

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 pd

df = 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='科室统计')

场景 6:抗菌药物 DDDs 自动计算

场景:药剂科要求每月统计抗菌药物 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 pd

df_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['住院人天'] * 100
print('科室抗菌药物使用强度:')
print(df_dept.sort_values('DDDs_100人天', ascending=False))

[!INFO] 关于 DDDs
DDDs(DDDs,Defined Daily Doses,限定日剂量数)反映的是「每种药物被使用的平均日剂量数」,数值越大,说明该药使用越广泛。WHO ATC/DDD 系统是国际通用的药物计量标准。

场景 7:临床路径入径率分析

场景:统计每个科室每个月的「临床路径入径率」「完成率」「变异率」。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pandas as pd

df = 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['符合路径'] * 100
grouped['完成率'] = grouped['完成路径'] / grouped['实际入径'] * 100
grouped['变异率'] = grouped['发生变异'] / grouped['实际入径'] * 100

grouped = grouped.round(2)
high_variation = grouped[grouped['变异率'] > 30].sort_values('变异率', ascending=False)
print('高变异率路径:')
print(high_variation)

grouped.to_excel('output/临床路径入径率分析.xlsx')

场景 8:DRG 入组结果核查

场景: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 pd

df = pd.read_excel('data/5月DRG入组结果.xlsx')

# 总体入组率
total_rate = (df['DRG编码'].notnull().sum() / len(df)) * 100
print(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_count
print(f'全院 CMI:{cmi:.4f}')

dept_rate.to_excel('output/DRG入组率核查.xlsx')

场景 9:指标月度趋势图自动生成

场景:每月输出 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 pd
import matplotlib.pyplot as plt
import os

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

df = 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('趋势图已生成')

场景 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
34
35
36
37
38
39
40
41
42
43
import pandas as pd
from datetime import datetime

df = 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() / 3600
on_time_rate = (df['归档时效'] <= 24).sum() / len(df) * 100
report.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 条

三、质控 Checklist:10 项核查点

Python 脚本写完后,怎么知道脚本「靠不靠谱」?用这张 Checklist:

序号 核查项 检查方法 通过标准
1 脚本运行成功率 连续 3 个月运行同一脚本 ≥ 99%
2 数据准确率 与人工核查 100 条样本对比 ≥ 99%
3 效率提升比 对比脚本运行时长 vs 人工时长 ≥ 10 倍
4 字段口径一致性 与指标库定义文档比对 100% 一致
5 空值 / 异常值处理 故意构造 100 条异常数据测试 全部按预期处理
6 版本控制 脚本用 git 管理,变更可追溯 所有变更在 git log
7 文档完整性 每个脚本有 README,说明输入 / 输出 / 依赖 100% 覆盖
8 权限最小化 数据库账号只有读权限 验证账号权限
9 可重复运行 同一脚本运行两次结果一致 完全一致
10 维护成本 每季度人工维护时长 ≤ 4 小时/季度

[!TIP] 落地建议
把这张 Checklist 嵌入 CI/CD(持续集成/持续部署)流水线,每次脚本变更前自动跑一遍——脚本质量有保障,才不会「跑 1 次准,跑 100 次偏」。

到这里,10 个实战场景 + 5 个核心库都拆完了。但这只是「会用 Python」的阶段——下一步,我们看「把 Python 用到极致」的极客层面。

Part 3:极客升维层——从「脚本」到「系统」

上一节的脚本,适合个人使用;这一节的工具,适合团队协作 + 流程自动化。

一、Jupyter Notebook:质管团队的协作神器

Jupyter Notebook 是一种「交互式编程环境」——你可以一段一段地写代码,每一段的结果(表格、图表、文字)直接显示在代码下面。

为什么质管团队特别适合用 Jupyter?

传统脚本 Jupyter Notebook
一次跑完,中间看不到结果 一段段执行,每段有输出
文字说明要写在另一个文档 文字 + 代码 + 图表混排
团队协作靠 git diff 直接看「输出」就知道改了什么
出问题要从头跑 可以只跑出问题的部分

质管团队用 Jupyter 的 3 个真实场景:

  1. 月报协作:质管办 3 个人分工写 3 个 Cell,合并成 1 个月报 Notebook;
  2. 培训教学:信息科给临床讲解数据时,用 Jupyter 边讲边演示;
  3. 复盘分析:事故复盘时,用 Jupyter 一步步追溯数据,留作「事故档案」。

二、Python 自动化报表(Jinja2 + PDF)

场景:每月要出 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 Template
import pandas as pd
from datetime import datetime

df = pd.read_excel('output/5月病案首页_全院合并.xlsx')
total = len(df)
grouped_rate = df['DRG编码'].notnull().sum() / total * 100
cmi = 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 月报自动生成。

三、病历文本 NLP(关键词提取)

场景:病程记录、出院小结是「非结构化文本」,如何自动提取关键信息?

方案:用 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 jieba
import re

sample_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 详述。

四、Streamlit:5 分钟做一个 Web 应用

场景:质管办做了一个「病案首页核查工具」脚本,但临床科室不会用 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.py
import streamlit as st
import pandas as pd

st.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

五、整体架构(Mermaid 图)

为了让你看清「Python 自动化体系」的全貌,画一张架构图:

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
graph TB
subgraph Data["数据源层"]
D1[HIS 系统]
D2[LIS 检验系统]
D3[PACS 影像系统]
D4[EMR 电子病历]
D5[不良事件系统]
D6[DRG 分组器]
end

subgraph ETL["ETL 层"]
E1[Python 脚本读取]
E2[数据清洗]
E3[数据合并]
E4[数据校验]
end

subgraph Analysis["分析层"]
A1[统计分析]
A2[指标计算]
A3[趋势预测]
A4[异常检测]
end

subgraph Output["输出层"]
O1[Excel 月报]
O2[PDF 自动报表]
O3[图表可视化]
O4[Streamlit Web 工具]
end

subgraph User["用户层"]
U1[质管办]
U2[临床科室]
U3[职能部门]
U4[院长]
end

Data --> ETL --> Analysis --> Output --> User

六、工作流流程图(Mermaid)

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

1
2
3
4
5
6
7
8
9
10
11
12
flowchart LR
A[HIS 导出 Excel] --> B[Python 脚本读取]
B --> C{数据完整?}
C -->|否| D[数据清洗]
C -->|是| E[分析计算]
D --> E
E --> F[生成图表]
E --> G[生成月报]
F --> H[月报整合]
G --> H
H --> I[质管办主任审核]
I --> J[提交院领导]

到这里,极客层面的工具都摆齐了。但这一切要落地,中间还隔着「人」这道坎——下一节,我们走进一家三甲医院的真实场景,看一个质控员是怎么从「手工」走到「自动化」的。

Part 4:真实案例——某三甲医院质控员的 Python 转型之路

2025 年,深圳某三甲医院(化名「海珠医院」,开放床位 1800 张,日均门诊 9000 人次)质管办来了一位新人——小陈,护理学硕士,刚毕业 1 年。

一、起点:3 天 vs 10 分钟

小陈入职第 1 周:

  • 主任交代:「把 2025 年 1 月的病案首页核查报告做出来」;
  • 数据:1 个 Excel,8,234 条;
  • 工作内容:核查 15 个必填字段、统计 30 个科室的入组率、生成 1 份月报;
  • 用时:3 天(筛选、复制、粘贴、汇总、出图表、写报告)。

小陈入职第 3 个月:

  • 同样的工作,她用 Python 脚本完成;
  • 用时:10 分钟(脚本 1 次运行,自动出报表、自动出图、自动生成月报)。

效率提升:432 倍

二、转型路径:6 个月从「零基础」到「自动化」

月份 学习内容 实际产出
第 1 月 Excel 高阶功能 + SQL 基础 把 HIS 导出的 Excel 用 SQL 直连数据库,节省每天 2 小时「等导出」
第 2 月 Python 基础语法 + pandas 入门 写出第 1 个脚本:批量核查必填字段
第 3 月 pandas 进阶 + matplotlib 绘图 月报图表自动化,从「手工画图」变「脚本出图」
第 4 月 openpyxl + Jinja2 报表模板 月报自动生成,从「手工排版」变「模板填充」
第 5 月 pymysql + 数据库直连 直连 HIS 数据库,不用等信息科导出
第 6 月 Streamlit + 团队协作 给临床做了一个「病案首页在线核查」Web 工具

三、改革效果(6 个月后)

维度 改革前 改革后 变化
月报出稿时间 3 天 1 小时 -92%
数据准确率 95%(手工有错) 99.5%(代码不累) +4.5 pp
临床「等数据」时长 每周 4 小时 每周 0.5 小时 -87.5%
质管办可分析维度 5-8 个 20+ 个 +150%

四、三条经验

[!EXAMPLE] 小陈的转型经验

  1. 从「最痛的一个点」切入:小陈第 1 个脚本不是「全院自动化」,而是「必填字段核查」——就这一件事,让她每天节省 1 小时,从此有了「继续学」的动力。
  2. 每学一个语法,立刻找一个质管场景练手:她学 merge 的那天,就找了「患者表 + 诊断表合并」这个真实场景;学 groupby 的那天,就找了「按科室统计入组率」。学以致用,3 个月就出师
  3. 把自己变成「团队的桥梁」:小陈学会 Python 后,主动帮信息科写脚本、帮临床做核查工具——她从「新人」变成了「质管办最被需要的人」。技能,是医院里最硬的「关系」

到这里,4 个层级都拆完了。最后,我们给出 30 天行动清单 + P37 预告。

结语:Python 不是「程序员的专利」,是「质管人的外挂」

回到周三下午 4 点的小李。

她面前的那 30 个 Excel 不会消失——病案首页每个月都要核查,月报每个月都要出,指标每个月都要统计。但从今天起,她做三件事:

第一,装上 Python 环境——Anaconda 一键安装,30 分钟搞定。

第二,写第 1 个脚本——哪怕只是「读 1 个 Excel,看前 5 行」——只要迈出第一步,后面就顺了。

第三,加入 1 个 Python 质管社群——遇到问题有人问、有人答,比自己摸索快 10 倍。

她不需要成为程序员,她只需要让 Python 替她干那 80% 的重复活

全文三句话

[!SUCCESS] 一句话总结

  1. 质管人学 Python 不是「转行」,是「给质管工作装外挂」——3 个 80% 问题决定了学 Python 是「性价比最高的技能投资」
  2. 5 个核心库 + 10 个实战场景覆盖质管办 80% 的工作——pandas 读表、numpy 算数值、matplotlib 出图、openpyxl 控 Excel、pymysql 连数据库,这 5 个就够用
  3. 从「脚本」到「系统」要走 3 步:Jupyter Notebook 协作、Jinja2 + PDF 自动报表、Streamlit 做 Web 工具——每一步都让 Python 从「个人技能」升级为「团队能力」

30 天行动清单:从「零基础」到「会写脚本」

[!TIP] 给质管人的「30 天 Python 入门清单」

天数 动作 输出物
Day 1 安装 Anaconda,打开 Jupyter Notebook 环境就绪
Day 2 第 1 个脚本:读 1 个 Excel,看前 5 行 df.head() 运行成功
Day 3 pandasdescribe() / 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 matplotlibplot() + figure() 1 张折线图
Day 10 实战 4:指标月度趋势图自动生成 5 张趋势图 PNG
Day 11 openpyxlFont + PatternFill 1 份带样式的 Excel
Day 12 pivot_table() 1 张透视表
Day 13 实战 5:不良事件分类统计 透视表 + 科室统计
Day 14 merge() 合并 2 张表
Day 15 numpymean() / 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 该有的节奏。


[!INFO] 系列预告

  • P37 AI 辅助质控:大语言模型 + 病历质控——AI 能不能帮质控员自动核查病案首页、自动识别疑似医疗不良事件?
  • P38 数据治理实战:从「脏数据」到「干净数据」——医院数据治理的 6 步法
  • P39 质管信息化选型:选 HIS、选 BI、选 AI——质管办的信息化该怎么「搭」?

关注「质领未来」,每一篇,都让质管人少走一年弯路。
留言区留下你 学 Python 踩过最深的坑(比如装环境装到崩溃、写脚本写到半夜、出错找不到原因、坚持不下去想放弃……),狼叔会在 P37-P39 里挑 3 个高频痛点做深度拆解。


《质效精研》P36 · Python 赋能:质管人也要学一点代码,批量处理病历数据
深圳市盐田区人民医院质管办 · 2026-06-24