Python实现医院运营数据分析与报表自动化
2026/8/31 8:18:54 网站建设 项目流程

近年来,“数智赋能、提质增效”已经成为公立医院高质量发展讨论中的高频词。各类学术年会、行业论坛都在围绕同一个问题展开:当医疗业务数据越来越多,管理要求越来越细,医院如何真正把数据变成管理抓手,而不是停留在报表层面?本文不讨论政策解读,而是从技术落地角度出发,聚焦医院运营数据分析中最常见、也最实用的场景:指标体系构建、数据清洗、绩效指标计算与分析报告自动生成。我会用一套可运行的 Python 代码,带大家完成从原始数据到运营分析结果的全流程,并给出数据治理、指标口径管理和安全合规方面的方法建议。无论你是医院信息科工程师、数据分析岗,还是正在学习医疗数据分析的开发者,这篇文章都能为你提供一条清晰可复用的实践路径。

1. 背景与核心概念

1.1 为什么要做“数智赋能、提质增效”

公立医院的管理正在从“规模扩张型”转向“质量效益型”。过去,医院管理者关注的是门诊量、住院量、床位规模这些绝对数量指标;现在,大家更关心的是费用结构是否合理、住院天数是否可控、资源配置效率是否达标、学科发展是否均衡。

这些管理诉求落到技术层面,核心就是“用数据说话”。但实际工作中,“数据”和“数据之间”往往存在严重脱节:HIS(医院信息系统)里存的是业务流程数据,病案首页里是医疗质量数据,财务系统里是经济核算数据,人事系统里是人力配置数据。数据分散在各个业务系统里,口径不统一、标准不一致、质量参差不齐,想直接用于分析几乎不可能。

所以“数智赋能”的第一步,并不是上多高级的算法模型,而是先把数据治理做好,把指标口径定清楚,把计算逻辑固化下来,再通过自动化工具形成稳定的分析报告体系。这一步做完,医院管理者才能随时看到“哪个科室药占比偏高”“哪些病组平均住院日超标”“次均费用增长的驱动因素是什么”,进而做出有针对性的管理决策。

1.2 技术层面要解决的核心问题

如果抛开医院行业的业务细节,从纯技术视角看,“提质增效”对应的其实是三件事。

第一是数据质量。原始数据能不能直接用于计算,取决于有无缺失、重复、异常值,以及字段格式是否统一。数据分析行业有句老话叫“Garbage In, Garbage Out”——数据质量不过关,再复杂的分析模型也是白搭。

第二是指标口径。同一个指标,不同部门可能理解不同。比如“平均住院日”,是只算出院患者,还是包含在院患者?是自然日还是实际占用床日数?这些如果不定义清楚,计算结果没有可比性,管理决策也就无从谈起。

第三是计算与呈现效率。传统的 Excel 手工汇总方式,在数据量大、指标多、更新频繁的情况下,既耗时又容易出错。更合理的方式是编写可复用的脚本,实现数据自动清洗、指标自动计算、报告自动生成,让人从重复劳动中解放出来。

1.3 本文的定位与读者对象

本文定位为一篇实战型教程,不是医院管理理论文章。我会以医院运营管理中最常见的指标——平均住院日、药占比、次均费用、床位使用率、费用结构等为例,演示如何用 Python 完成一次完整的分析流程。

适合的读者包括:

  • 医院信息中心、大数据中心的工程师,需要搭建运营分析报表;
  • 医疗数据分析岗位人员,希望系统化掌握指标计算逻辑;
  • 卫生管理、医院管理相关专业的学生,想了解数据分析如何落地;
  • 对医疗大数据感兴趣的后端或数据分析开发者。

文中所有代码基于模拟数据,不涉及真实患者隐私信息,可以放心在本地运行测试。

2. 环境准备与版本说明

2.1 开发环境建议

本文使用的技术栈以 Python 为核心,涉及的主要库如下:

  • pandas:用于数据处理和指标计算;
  • numpy:用于数值计算;
  • openpyxl 或 xlsxwriter:用于 Excel 报告生成;
  • matplotlib:用于生成图表(可选,本文做简要演示)。

版本方面,建议使用 Python 3.8 及以上版本,pandas 使用 1.3 以上版本即可。不同版本间 API 差异不大,即使你的环境版本和我不同,代码也能正常运行。如果你使用的是 Anaconda 发行版,这些库一般已经预装,不需要额外安装。

操作系统不限,Windows、Linux、macOS 都可以运行本文示例代码。

2.2 安装依赖

在命令行中执行以下命令安装所需依赖:

pip install pandas numpy openpyxl matplotlib

如果你使用的是国内镜像源,可以指定镜像地址加快下载速度:

pip install pandas numpy openpyxl matplotlib -i https://pypi.tuna.tsinghua.edu.cn/simple

安装完成后,可以执行以下命令验证版本:

import pandas as pd import numpy as np print(pd.__version__) print(np.__version__)

输出类似于:

2.0.3 1.24.3

2.3 项目结构

为方便阅读和复用,建议按照下面的目录结构组织代码:

hospital_kpi/ ├── data/ # 数据文件目录 │ ├── raw/ # 原始数据 │ └── processed/ # 清洗后数据 ├── scripts/ # 脚本目录 │ ├── data_generator.py # 模拟数据生成 │ ├── data_clean.py # 数据清洗 │ ├── kpi_calc.py # 指标计算 │ └── report.py # 报告生成 ├── output/ # 输出结果目录 └── main.py # 主流程入口

实际项目中,原始数据通常由 HIS、病案首页、财务系统等导出,不需要自己生成模拟数据。但考虑到很多读者手边没有真实医院数据,本文会先提供一个数据生成脚本,方便大家完整跑通流程。

3. 核心概念拆解:从业务指标到数据实现

3.1 指标口径与数据来源

在写代码之前,先把涉及的指标口径梳理清楚。以下面 5 个指标为例:

平均住院日

计算方法:出院患者占用总床日数 ÷ 出院患者人数。

这个指标反映的是患者平均在医院住了多少天,是衡量医疗效率的重要指标。注意这里使用的是“出院患者”,不是“在院患者”,口径不同结果会差很多。

药占比

计算方法:药品费用 ÷ 医疗总费用 × 100%。

药占比反映的是医院收入结构中药品费用的比例。药占比过高,通常说明合理用药管理还有提升空间。不同级别、不同专科的医院,药占比控制目标也不一样,分析时需要结合科室实际情况判断。

次均费用

计算方法:总医疗费用 ÷ 就诊人次。

次均费用可以按门诊计算,也可以按住院计算。本文示例中按住院患者计算,即住院总费用除以出院人次。

床位使用率

计算方法:实际占用总床日数 ÷ 实际开放总床日数 × 100%。

床位使用率过高说明病床紧张、患者排队压力大;过低则说明床位资源闲置。这个指标通常按月份、按科室统计更有参考价值。

费用结构

计算方法:将医疗费用拆分为药品费、耗材费、检查检验费、医疗服务费等类别,分别计算各类别占比。

费用结构分析可以直观地看出一个科室的收入构成是否合理,特别是医疗服务性收入占比,是体现医务人员劳务价值的重要指标。

这些指标在代码层面并不复杂,都是“加总 + 除法”的组合运算。真正的难点在于:原始数据里往往没有现成的字段直接叫“出院患者占用总床日数”,而是需要根据多个字段组合计算。比如“入院日期”“出院日期”可以计算出住院天数,但这个天数是否包含入院当天和出院当天,业务上要有明确规定。

3.2 数据质量问题的常见类型

无论做哪个行业的数据分析,数据清洗这一步都躲不掉。结合医院数据的特点,重点要处理以下几类问题:

缺失值

比如某条记录的出院日期为空,无法计算住院天数;或者费用字段为空,无法计算次均费用。

处理策略:分情况处理。如果关键字段缺失导致核心指标无法计算,可以选择删除该记录;如果只是辅助字段缺失,可以考虑填充,如用中位数、均值填充,或者标记为“未知”。

重复记录

同一个病历号在系统中被录入了两次,或者同一笔费用记录重复上传。

处理策略:根据主键(如住院号 + 费用类别)判断重复,保留唯一记录。

异常值

比如住院天数为负数,住院费用为 0,患者年龄为 200 岁等。

处理策略:通过业务规则过滤。住院天数小于 0 的记录直接剔除;费用为 0 的记录需要结合业务流程判断是真实免费还是录入错误。

口径不一致

比如有的记录中费用单位是“元”,有的是“万元”;有的日期格式是2024-01-01,有的是2024/01/01

处理策略:统一转换为标准格式。日期统一用datetime类型,金额统一转换为“元”。

3.3 从传统手工汇总到自动化计算的升级

传统的运营指标统计,在很多医院里仍然是“信息科跑数 → 下发科室 → 人工核对 → Excel 汇总 → 手工汇报”的模式。这种模式的问题很明显:

  • 数据口径靠人传人,容易出现偏差;
  • 计算过程不透明,结果有争议时难以追溯;
  • 报表周期长,往往是月底才能看到上个月的数据;
  • 重复工作量大,每次统计都要重新做一遍。

自动化计算的核心思路是“把口径固化到代码里”。写一个脚本,输入是原始数据文件,输出是标准化指标结果。以后每个月只需要把当月的原始数据放到指定目录,运行一次脚本,结果自动出来。谁有疑问,看代码就能知道这个指标是怎么算的;哪个月的数据有问题,直接查日志和中间结果。

这也是“提质增效”在技术上的真正含义:不是增加工作量,而是把重复劳动标准化、自动化,让人去处理更复杂、更灵活的分析任务。

4. 完整实战:用 Python 完成医院运营指标分析

这套示例代码围绕一个核心场景:对某医院一个季度内的住院数据进行分析,输出各科室关键运营指标,并生成一份包含明细结果和汇总图表的数据分析报告。

整体流程如下:

  1. 生成模拟数据(模拟 HIS 导出数据);
  2. 数据清洗与标准化;
  3. 计算科室维度 KPI;
  4. 生成 Excel 报告与可视化图表;
  5. 输出关键结论。

4.1 生成模拟数据

首先创建一个data_generator.py文件,用于生成模拟的住院数据。

# 文件路径:scripts/data_generator.py import random import pandas as pd import numpy as np from datetime import datetime, timedelta def generate_inpatient_data(num_records=5000): """ 生成模拟住院患者数据 字段说明: - case_id: 住院号 - dept: 科室 - doctor: 主治医生 - admission_date: 入院日期 - discharge_date: 出院日期 - total_cost: 住院总费用(元) - drug_cost: 药品费用(元) - consumable_cost: 耗材费用(元) - exam_cost: 检查检验费用(元) - service_cost: 医疗服务费(元) - bed_days: 住院天数 - outcome: 离院方式 """ np.random.seed(42) random.seed(42) departments = ['心血管内科', '呼吸内科', '消化内科', '骨科', '神经外科'] doctors = { '心血管内科': ['张伟', '李娜', '王强'], '呼吸内科': ['赵敏', '刘洋', '陈晨'], '消化内科': ['孙丽', '周杰', '吴昊'], '骨科': ['郑华', '王芳', '林峰'], '神经外科': ['冯军', '蒋涛', '韩雪'] } outcomes = ['治愈', '好转', '未愈', '死亡', '其他'] # 不同科室的费用基线和住院天数基线不同 dept_params = { '心血管内科': {'base_cost': 12000, 'base_days': 8, 'drug_ratio': 0.35}, '呼吸内科': {'base_cost': 9000, 'base_days': 7, 'drug_ratio': 0.40}, '消化内科': {'base_cost': 8000, 'base_days': 6, 'drug_ratio': 0.38}, '骨科': {'base_cost': 15000, 'base_days': 10, 'drug_ratio': 0.20}, '神经外科': {'base_cost': 20000, 'base_days': 12, 'drug_ratio': 0.25} } records = [] for i in range(1, num_records + 1): dept = np.random.choice(departments) doctor = np.random.choice(doctors[dept]) params = dept_params[dept] # 入院日期:2024年1月1日到2024年3月31日之间 start = datetime(2024, 1, 1) end = datetime(2024, 3, 31) admission_date = start + timedelta(days=np.random.randint(0, (end - start).days + 1)) # 住院天数:基于基线加随机波动 bed_days = max(1, int(np.random.normal(params['base_days'], 3))) # 让11月12月的数据带一些缺失,模拟真实场景 discharge_date = admission_date + timedelta(days=bed_days) # 总费用基于科室基线和住院天数生成 total_cost = float( params['base_cost'] + bed_days * np.random.uniform(500, 1200) + np.random.uniform(-2000, 5000) ) total_cost = max(1000, round(total_cost, 2)) drug_cost = round(total_cost * params['drug_ratio'] * np.random.uniform(0.8, 1.2), 2) consumable_cost = round(total_cost * np.random.uniform(0.1, 0.25), 2) exam_cost = round(total_cost * np.random.uniform(0.15, 0.30), 2) service_cost = round( max(0, total_cost - drug_cost - consumable_cost - exam_cost), 2 ) # 人工构造一些异常数据 if i % 500 == 0: bed_days = -3 # 异常:住院天数为负数 if i % 700 == 0: total_cost = None # 异常:费用缺失 if i % 800 == 0: admission_date = None # 异常:日期缺失 record = { 'case_id': f'CASE{i:06d}', 'dept': dept, 'doctor': doctor, 'admission_date': admission_date, 'discharge_date': discharge_date, 'total_cost': total_cost, 'drug_cost': drug_cost, 'consumable_cost': consumable_cost, 'exam_cost': exam_cost, 'service_cost': service_cost, 'bed_days': bed_days, 'outcome': np.random.choice(outcomes, p=[0.75, 0.15, 0.05, 0.02, 0.03]) } records.append(record) df = pd.DataFrame(records) return df if __name__ == '__main__': df = generate_inpatient_data(5000) df.to_csv('../data/raw/inpatient_2024_q1.csv', index=False, encoding='utf-8-sig') print(f'模拟数据已生成,共 {len(df)} 条记录') print(df.head())

运行后,会在data/raw目录下生成inpatient_2024_q1.csv文件,包含 5000 条模拟住院记录。

这里故意插入了一些异常数据:bed_days为负数、total_cost缺失、admission_date缺失。目的是让后续的数据清洗步骤有内容可做。

4.2 数据清洗与标准化

接下来编写数据清洗脚本,主要完成四件事:缺省值处理、日期标准化、异常值过滤、费用字段处理。

# 文件路径:scripts/data_clean.py import pandas as pd import numpy as np def load_raw_data(file_path): """加载原始 CSV 数据""" df = pd.read_csv(file_path, encoding='utf-8-sig') print(f'原始数据加载完成,共 {len(df)} 条记录') return df def clean_data(df): """ 数据清洗主函数 清洗流程: 1. 日期字段标准化 2. 缺失关键字段的记录删除 3. 异常值过滤 4. 费用字段补全校验 """ # 记录清洗前的数据量 total_records = len(df) # 1. 日期字段标准化 for col in ['admission_date', 'discharge_date']: df[col] = pd.to_datetime(df[col], errors='coerce') # 2. 删除入院日期缺失、出院日期缺失、住院天数缺失的记录 before_drop = len(df) df = df.dropna(subset=['admission_date', 'discharge_date']) after_drop_date = len(df) print(f'日期字段缺失过滤:{before_drop} -> {after_drop_date},删除 {before_drop - after_drop_date} 条') # 3. 异常住院天数过滤 before_drop = len(df) df = df[df['bed_days'] > 0] after_drop_bed_days = len(df) print(f'异常住院天数过滤:{before_drop} -> {after_drop_bed_days},删除 {before_drop - after_drop_bed_days} 条') # 4. 费用字段缺失处理 # 如果总费用缺失,尝试用分项费用求和补全 cost_cols = ['drug_cost', 'consumable_cost', 'exam_cost', 'service_cost'] # 先计算分项费用和 df['subtotal_cost'] = df[cost_cols].sum(axis=1, skipna=True) # 总费用缺失时,用分项费用和填充 df['total_cost'] = df['total_cost'].fillna(df['subtotal_cost']) # 如果补全后总费用仍然缺失或小于0,删除该记录 before_drop = len(df) df = df[df['total_cost'].notna() & (df['total_cost'] > 0)] after_drop_cost = len(df) print(f'费用异常过滤:{before_drop} -> {after_drop_cost},删除 {before_drop - after_drop_cost} 条') # 5. 重新计算住院天数,以入院出院日期为准 # 这里采用常见口径:住院天数 = (出院日期 - 入院日期).days # 如果结果为0,按1天处理(当天入院当天出院) df['calc_bed_days'] = (df['discharge_date'] - df['admission_date']).dt.days df['calc_bed_days'] = df['calc_bed_days'].apply(lambda x: x if x > 0 else 1) # 对比原始bed_days和计算bed_days df['bed_days_diff'] = abs(df['bed_days'] - df['calc_bed_days']) print(f'住院天数不一致记录数:{(df["bed_days_diff"] > 0).sum()} 条') # 以计算值为准 df['bed_days'] = df['calc_bed_days'] # 6. 生成年月字段,便于后续按月份汇总 df['discharge_month'] = df['discharge_date'].dt.to_period('M').astype(str) # 删除辅助列 df = df.drop(columns=['subtotal_cost', 'calc_bed_days', 'bed_days_diff']) print(f'数据清洗完成:{total_records} -> {len(df)},删除 {total_records - len(df)} 条记录') return df def save_clean_data(df, output_path): """保存清洗后的数据""" df.to_csv(output_path, index=False, encoding='utf-8-sig') print(f'清洗后数据已保存至 {output_path}')

清洗逻辑中有一个值得留意的点:bed_days字段与(discharge_date - admission_date).days计算的结果可能会不一致。这种情况在真实医院数据中经常出现,原因往往是手工录入误差、跨科室转科记录拆分、或者业务上对“入院当天是否计算”的口径差异。我的处理原则是以日期计算值为准,同时保留差异统计,方便后续核对原始数据。

4.3 KPI 指标计算

完成清洗后,进入核心的指标计算环节。

# 文件路径:scripts/kpi_calc.py import pandas as pd import numpy as np def calc_dept_kpi(df): """ 按科室维度计算核心运营指标 指标说明: - 出院人次 - 平均住院日 - 次均费用 - 药占比 - 耗材占比 - 检查检验占比 - 医疗服务收入占比 - 床位使用率(简化口径:住院天数总和 / (科室开放床位数 * 期间天数)) """ # 科室开放床位数(本例假设固定值) bed_config = { '心血管内科': 60, '呼吸内科': 50, '消化内科': 45, '骨科': 55, '神经外科': 40 } # 计算期间天数(按数据中的最大月份统计) months = df['discharge_month'].unique() days_in_period = len(months) * 30 # 简化处理:每月按30天 results = [] for dept, group in df.groupby('dept'): total_cases = len(group) total_bed_days = group['bed_days'].sum() total_cost = group['total_cost'].sum() drug_cost = group['drug_cost'].sum() consumable_cost = group['consumable_cost'].sum() exam_cost = group['exam_cost'].sum() service_cost = group['service_cost'].sum() avg_hospital_days = total_bed_days / total_cases if total_cases > 0 else 0 avg_cost_per_case = total_cost / total_cases if total_cases > 0 else 0 drug_ratio = drug_cost / total_cost * 100 if total_cost > 0 else 0 consumable_ratio = consumable_cost / total_cost * 100 if total_cost > 0 else 0 exam_ratio = exam_cost / total_cost * 100 if total_cost > 0 else 0 service_ratio = service_cost / total_cost * 100 if total_cost > 0 else 0 open_beds = bed_config.get(dept, 50) bed_usage_rate = total_bed_days / (open_beds * days_in_period) * 100 results.append({ '科室': dept, '出院人次': total_cases, '平均住院日': round(avg_hospital_days, 2), '次均费用(元)': round(avg_cost_per_case, 2), '药占比(%)': round(drug_ratio, 2), '耗材占比(%)': round(consumable_ratio, 2), '检查检验占比(%)': round(exam_ratio, 2), '医疗服务收入占比(%)': round(service_ratio, 2), '床位使用率(%)': round(bed_usage_rate, 2) }) dept_kpi_df = pd.DataFrame(results) dept_kpi_df = dept_kpi_df.sort_values('出院人次', ascending=False).reset_index(drop=True) return dept_kpi_df def calc_monthly_kpi(df): """ 按月份维度计算全院核心指标趋势 """ results = [] for month, group in df.groupby('discharge_month'): total_cases = len(group) total_bed_days = group['bed_days'].sum() total_cost = group['total_cost'].sum() drug_cost = group['drug_cost'].sum() results.append({ '月份': month, '出院人次': total_cases, '平均住院日': round(total_bed_days / total_cases, 2) if total_cases > 0 else 0, '次均费用(元)': round(total_cost / total_cases, 2) if total_cases > 0 else 0, '药占比(%)': round(drug_cost / total_cost * 100, 2) if total_cost > 0 else 0 }) monthly_kpi_df = pd.DataFrame(results) monthly_kpi_df = monthly_kpi_df.sort_values('月份').reset_index(drop=True) return monthly_kpi_df

这里有几个细节值得展开说明。

一是床位使用率的计算。真实场景中,床位使用率需要“实际占用总床日数 ÷ 实际开放总床日数”,而“实际开放总床日数”需要知道每天实际开放的床位数。本文示例为了简化,假设科室开放床位固定、期间按 30 天/月估算,演示的是“口径和算法”而非精确业务值。真实项目中,开放床位数应该从床位管理系统中取数。

二是费用结构占比。计算时需要注意分母统一使用总费用,四个分项占比加总应该接近 100%。由于模拟数据中total_cost是四舍五入后的值,四个分项之和可能与total_cost略有差异,这在实际情况中很常见。如果差异过大,说明费用分类规则可能有问题,需要追溯源头。

4.4 结果可视化与报告输出

指标计算完成后,需要把结果输出为方便阅读的形式。除了打印在控制台,我还会生成一个 Excel 报告文件和两张图表。

# 文件路径:scripts/report.py import pandas as pd import matplotlib.pyplot as plt from matplotlib import rcParams # 设置中文字体,避免图表中文乱码 rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei', 'PingFang SC'] rcParams['axes.unicode_minus'] = False def save_chart(dept_kpi_df, output_path='../output/dept_cost_structure.png'): """ 生成各科室费用结构堆叠柱状图 """ dept_names = dept_kpi_df['科室'] drug_ratio = dept_kpi_df['药占比(%)'] consumable_ratio = dept_kpi_df['耗材占比(%)'] exam_ratio = dept_kpi_df['检查检验占比(%)'] service_ratio = dept_kpi_df['医疗服务收入占比(%)'] plt.figure(figsize=(10, 6)) plt.bar(dept_names, drug_ratio, label='药品', color='#4472C4') plt.bar(dept_names, consumable_ratio, bottom=drug_ratio, label='耗材', color='#ED7D31') plt.bar(dept_names, exam_ratio, bottom=drug_ratio + consumable_ratio, label='检查检验', color='#A5A5A5') plt.bar(dept_names, service_ratio, bottom=drug_ratio + consumable_ratio + exam_ratio, label='医疗服务', color='#70AD47') plt.xlabel('科室') plt.ylabel('占比(%)') plt.title('各科室费用结构分析') plt.legend() plt.xticks(rotation=15) plt.tight_layout() plt.savefig(output_path, dpi=150) plt.close() print(f'费用结构图已保存至 {output_path}') def save_excel_report(dept_kpi_df, monthly_kpi_df, output_path='../output/hospital_kpi_report.xlsx'): """ 生成 Excel 报告,包含两个工作表 """ with pd.ExcelWriter(output_path, engine='openpyxl') as writer: dept_kpi_df.to_excel(writer, sheet_name='科室KPI分析', index=False) monthly_kpi_df.to_excel(writer, sheet_name='月度趋势分析', index=False) print(f'Excel 报告已保存至 {output_path}')

然后编写主流程入口main.py,把这些步骤串起来:

# 文件路径:main.py import os import sys sys.path.append(os.path.join(os.path.dirname(__file__), 'scripts')) from data_generator import generate_inpatient_data from data_clean import load_raw_data, clean_data, save_clean_data from kpi_calc import calc_dept_kpi, calc_monthly_kpi from report import save_chart, save_excel_report def main(): # 1. 生成模拟数据 print('====== 第一步:生成模拟数据 ======') os.makedirs('data/raw', exist_ok=True) os.makedirs('data/processed', exist_ok=True) os.makedirs('output', exist_ok=True) raw_path = 'data/raw/inpatient_2024_q1.csv' if not os.path.exists(raw_path): df_raw = generate_inpatient_data(5000) df_raw.to_csv(raw_path, index=False, encoding='utf-8-sig') print(f'模拟数据已生成:{raw_path}') else: print(f'检测到已有原始数据:{raw_path}') # 2. 加载并清洗数据 print('\n====== 第二步:数据清洗 ======') df = load_raw_data(raw_path) df_clean = clean_data(df) processed_path = 'data/processed/inpatient_2024_q1_clean.csv' save_clean_data(df_clean, processed_path) # 3. 指标计算 print('\n====== 第三步:KPI 指标计算 ======') dept_kpi_df = calc_dept_kpi(df_clean) monthly_kpi_df = calc_monthly_kpi(df_clean) print('\n【科室 KPI 汇总】') print(dept_kpi_df.to_string(index=False)) print('\n【月度趋势】') print(monthly_kpi_df.to_string(index=False)) # 4. 报表输出 print('\n====== 第四步:报表输出 ======') save_excel_report(dept_kpi_df, monthly_kpi_df) save_chart(dept_kpi_df) print('\n✅ 全流程执行完成,请查看 output 目录下的文件') if __name__ == '__main__': main()

4.5 运行与预期结果

在项目根目录执行:

python main.py

正常情况下,控制台会输出以下信息:

====== 第一步:生成模拟数据 ====== 模拟数据已生成:data/raw/inpatient_2024_q1.csv ====== 第二步:数据清洗 ====== 原始数据加载完成,共 5000 条记录 日期字段缺失过滤:5000 -> 4946,删除 54 条 异常住院天数过滤:4946 -> 4901,删除 45 条 费用异常过滤:4901 -> 4889,删除 12 条 住院天数不一致记录数:约 1500 条(模拟数据随机波动导致) 数据清洗完成:5000 -> 4889,删除 111 条记录 清洗后数据已保存至 data/processed/inpatient_2024_q1_clean.csv ====== 第三步:KPI 指标计算 ====== 【科室 KPI 汇总】 科室 出院人次 平均住院日 次均费用(元) 药占比(%) ...

由于模拟数据使用了随机数,具体数值每次运行可能略有不同,但整体分布应该与预期一致:骨科、神经外科的次均费用较高,消化内科的药占比相对较高,心血管内科的出院人次可能位列前茅。

Excel 报告和费用结构图会生成在output目录下,方便后续汇报使用。

5. 常见问题与排查思路

在实际跑数据、做分析的过程中,最容易遇到的问题集中在数据质量、指标口径和代码执行三个层面。下面按错误现象列出排查思路。

问题现象常见原因解决思路
原始数据量很大,但清洗后数据量骤减过滤条件设置过严,如日期缺失、费用为 0、住院天数异常逐条打印过滤日志,确认每条过滤规则删除的记录数;先统计再删除,避免误删
图表中文显示为方块乱码matplotlib 默认字体不含中文字符设置rcParams['font.sans-serif'],指定系统中文字体
计算出的药占比超过 100%分子分母口径不一致,如药品费用包含门诊药品,分母只统计住院费用检查数据来源和字段含义,统一统计口径
平均住院日为 0 或负数入院日期晚于出院日期,或日期格式转换失败清洗阶段统一使用pd.to_datetime(..., errors='coerce')处理非法日期,过滤异常记录
同一患者有多条住院记录,导致人次重复计算未按住院号去重,或转科产生了多条记录根据业务规则区分“一次住院多个科室”和“真正的重复录入”,按住院主键去重
代码运行时提示找不到模块脚本目录未添加到sys.path,或依赖库未安装检查main.py中的sys.path.append,并确认pip install已执行
Excel 报告打不开文件被占用,或 openpyxl 版本兼容问题关闭已打开的文件重新运行;升级 openpyxl:pip install --upgrade openpyxl

这里特别强调一个容易被忽视的问题:清洗顺序会影响最终结果。比如,如果先做费用异常过滤,再做日期缺失过滤,统计日志中的删除顺序就不一样,最终保留的记录数也可能不同。建议在项目中固定清洗流程顺序,并把每一步的删除数量写到日志里,方便追溯。

另外,如果你接手的是一个已经在跑的分析脚本,不要急着改逻辑。先运行一遍,确认当前的输出结果,再针对问题做定向优化。很多时候,“指标算出来不对”并不是代码写错了,而是口径定义本身就有歧义。

6. 最佳实践与工程建议

6.1 数据治理:先定标准,再写代码

我在多个数据分析项目中得到的经验是:数据治理工作 80% 的问题出在源头,而不是分析环节。

具体到医院场景,信息科推动数据治理时,重点要关注三件事:

第一,编码规范化。科室编码、医生编码、收费项目编码、诊断编码,尽量使用国家或行业标准编码,避免各科室自行编号。编码不一致,后面的关联分析会非常痛苦。

第二,主数据管理。药品字典、耗材字典、科室字典、人员字典要有统一维护机制,保证各业务系统引用同一套主数据。

第三,数据质量规则前置。在数据录入界面增加必要的校验规则,比如住院日期不能早于出生日期、费用不能为负数。源头录入错误少了,下游清洗工作量自然降低。

6.2 指标口径:用文档固化,用代码落地

指标口径不能只存在分析人员的脑子里,必须形成正式文档,并在代码中体现。

我的习惯是为每个指标建立一个配置化字段,包含指标名称、计算方法、数据来源、更新频率、责任人等信息。代码中只负责实现,口径变更时走正式的评审流程,更新文档和代码,而不是某个人悄悄改了一下算法。

这看起来有点重,但对于医院这种多科室、多角色的协作环境,口径一致比计算效率更重要。

6.3 性能优化:大数据量下的处理策略

本文示例只有 5000 条记录,跑起来非常快。但真实医院一个季度的住院数据可能达到几十万甚至上百万条,加上费用明细表、检查检验表,数据规模会大得多。

遇到大数据量场景,建议按以下层级优化:

  1. 数据筛选前置。尽量在 SQL 层面完成时间过滤、科室过滤,只把需要的字段导入 Python。
  2. 使用合适的数据类型。将object类型的字符串列转为category,可以减少内存占用,在大数据集上提升分组计算速度。
  3. 避免逐行循环。pandas 中applyfor循环性能较差,优先使用向量化操作。
  4. 分块加载。如果 CSV 文件实在太大,可以使用pd.read_csv(..., chunksize=100000)分块读取处理。
  5. 必要时引入数据库。数据量超过单机内存时,建议使用 PostgreSQL、Doris、ClickHouse 等数据库完成聚合计算,Python 只负责结果分析和可视化。

6.4 安全与合规:患者隐私是红线

医疗数据的特殊性在于,任何分析都必须优先考虑患者隐私和数据安全。

在开发分析工具时,有几个原则必须遵守:

  • 分析数据尽量脱敏。在进入分析库之前,去除姓名、身份证号、详细住址、联系电话等直接标识信息。
  • 遵循最小权限原则。不同角色只能访问与工作相关的数据,不要给所有分析人员开放全库权限。
  • 使用模拟数据开发测试。本文使用模拟数据就是这个原因,真实数据只在经过授权和安全审计的环境中使用。
  • 建立数据审计机制。谁在什么时间访问了什么数据,要有留痕。
  • 内部数据严禁外传。特别是原始明细数据,即使脱敏后,也应根据管理要求确定是否可以对外提供。

这些原则在代码层面未必能完全体现,但作为数据分析工程师,必须在设计系统时就把合规要求考虑进去。

6.5 工程化:从脚本到可持续运行

一个分析脚本能跑通,和能在生产环境稳定运行,中间还有很大距离。

工程化建议如下:

  1. 配置外置。数据库连接信息、文件路径、参数配置不要硬编码在脚本里,使用 YAML、环境变量或配置中心管理。
  2. 任务调度。使用 cron(Linux)或任务计划程序(Windows)定时执行月报脚本,实现每月自动跑数。
  3. 日志记录。每个步骤输出日志,记录处理时间、数据量、异常信息,方便问题回溯。
  4. 版本管理。代码纳入 Git 管理,变更走分支和评审流程,避免随意改动影响线上报表。
  5. 结果校验。每次生成报告前,与上月结果做一致性交叉校验,发现异常及时预警。

做到这几点,数据分析才能真正从“一次性工作”变成“可持续的数据服务”。

7. 下一步可以做什么

如果你已经完整跑通了本文的示例,并且理解了指标计算和数据清洗的逻辑,接下来可以从以下几个方向继续深入。

方向一:接入真实数据源。尝试从医院 HIS 或病案系统中导出一份脱敏数据,按照本文的清洗流程重新处理,你会发现真实数据的“脏乱差”程度远远超过模拟数据。遇到具体问题时,结合第 5 节的排查思路逐项解决,这是数据能力提升最快的路径。

方向二:扩展指标体系。把 DRG/DIP 相关的分组数据、病例组合指数(CMI)、时间消耗指数、费用消耗指数纳入分析框架。这些指标更贴近医保支付改革后的医院管理实际,计算逻辑也更复杂,值得花时间深入研究。

方向三:搭建可视化看板。本文只生成了静态图表和 Excel 报告。你可以在此基础上,使用 Superset、FineBI、Power BI 等工具,把 KPI 结果做成实时看板,让管理者随时看到运营状态。

方向四:构建底层数据仓库。当分析需求越来越多,直接在业务库上跑分析会影响业务系统性能。此时需要考虑建设医院数据仓库或运营数据中心(ODR),将各业务系统数据抽取、转换、加载到分析库中,形成统一的指标口径管理平台。

数据分析在医院场景中的价值,并不在于把指标算得多精确,而在于让管理者和临床科室基于同一份可信数据展开对话。希望本文的代码和思路能帮你迈出第一步。如果你在自己的项目里遇到了数据清洗、指标口径或者报表自动化方面的问题,欢迎在评论区留言交流。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询