ARTICLE DETAIL

资讯详情

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

Python慢性病数据追踪与可视化系统:从SQLite到交互式大屏

Python慢性病数据追踪与可视化系统:从SQLite到交互式大屏 简介一份面向慢性病管理场景的Web系统课程报告资源适合医疗信息化方向的学生或开发者借鉴完整设计与实现流程。压缩包共包含3个文件分别为PDF报告、Markdown源文件与HTML可视化演示页整体仅1.28MB便于阅读、编辑与直接查看图表效果。系统采用Flask、Pandas与PyEcharts分层架构涵盖血糖血压数据采集、缺失值与异常值清洗、统计分析与基于规则的预警引擎例如连续三天空腹血糖高于7.0 mmol/L即触发高血糖提醒同时通过趋势折线图和箱线图展示健康变化。文档按摘要、绪论、相关技术、需求分析、总体设计、详细实现、系统测试与总结展望等章节组织并附带实现指南可完整还原开发脉络其中还涉及基于角色的访问控制保障数据安全。资源已有63人学习可作为课程报告范例或慢性病管理系统开发的重要参考。1. 慢性病管理数据追踪与可视化系统从课程报告到能演示的完整项目如果你正在为课程报告或毕设选题发愁这套基于 Python 的慢性病数据追踪与可视化系统是我拆过最省心的方向之一。它不依赖复杂的硬件设备也不需要真实的医院数据接口——用公开的模拟数据就能跑通全流程。系统核心是围绕血压、血糖、心率等关键指标完成从数据采集、清洗、趋势追踪到可视化看板的闭环最终产出一份能直接在答辩现场演示的交互式大屏。很多学生卡在「数据有了但不知道怎么讲故事」这一步而这个项目恰好补上了这个缺口。它会帮你建立一套完整的数据处理思维数据规范是根基、趋势算法是中坚、可视化是门面。不管你未来是做数据分析还是后端开发这套流程都能复用。下面按我实际动手的顺序把每个环节的参数、代码和坑位都摊开讲。2. 数据模型设计先定字段再谈分析2.1 为什么选 SQLite 而不是 MySQL课程报告场景下我强烈建议用 SQLite 而不是 MySQL。原因很简单SQLite 是文件型数据库不需要单独安装服务代码里连接一下就能用交作业时把.db文件一起打包就行。MySQL 需要配置账号密码、处理端口占用很多同学在环境搭建阶段就劝退了。另一个理由是数据量。慢性病追踪系统单机演示的数据量级基本在几万条以内SQLite 的读写性能完全够用。你在课程报告里写「选用轻量级嵌入式数据库降低部署复杂度」这句话在答辩时是加分项。2.2 三张核心表的结构设计这套系统的数据模型我拆成三张表患者信息表、健康记录表、用药记录表。健康记录表是核心每行对应一次测量关联患者 ID 和记录时间。-- patients: 患者基础信息 CREATE TABLE patients ( patient_id TEXT PRIMARY KEY, name TEXT NOT NULL, age INTEGER, gender TEXT CHECK(gender IN (M, F)), diagnosis TEXT, created_at TEXT DEFAULT (datetime(now)) ); -- health_records: 核心健康指标记录 CREATE TABLE health_records ( record_id INTEGER PRIMARY KEY AUTOINCREMENT, patient_id TEXT NOT NULL, record_date TEXT NOT NULL, systolic INTEGER, diastolic INTEGER, heart_rate INTEGER, blood_glucose REAL, spo2 INTEGER, note TEXT, FOREIGN KEY (patient_id) REFERENCES patients(patient_id) ); -- medications: 用药记录 CREATE TABLE medications ( record_id INTEGER PRIMARY KEY AUTOINCREMENT, patient_id TEXT NOT NULL, med_name TEXT NOT NULL, dosage REAL, frequency TEXT, start_date TEXT, end_date TEXT, FOREIGN KEY (patient_id) REFERENCES patients(patient_id) );字段设计的核心理念是「宁宽勿窄」——额外的note字段用来记特殊情况比如「测量前运动了」「空腹血糖」这在你分析异常值时非常有帮助。spo2是血氧饱和度疫情期间很多慢病管理项目都加了这项如果你不需要可以留空但字段先占位。关键参数说明blood_glucose用REAL而不是INTEGER因为血糖值经常出现 5.6、7.8 这样的带小数数值用整型会直接截断数据就废了。systolic和diastolic是收缩压和舒张压用INTEGER没问题血压计读出来就是整数。2.3 索引查询性能的隐形加速器数据量小的时候你可能感受不到索引的价值但当你演示时筛选某个患者的全部记录或者按日期范围聚合数据没有索引的表会全表扫描体感上会卡顿。CREATE INDEX idx_health_patient_date ON health_records(patient_id, record_date); CREATE INDEX idx_health_date ON health_records(record_date); CREATE INDEX idx_med_patient ON medications(patient_id);复合索引(patient_id, record_date)是最关键的查询场景永远是「先定位患者再按时间排序」。我把日期放第二列是因为实际查询中过滤患者的频率远高于过滤日期这个顺序能让索引最大化命中。3. 数据清洗与追踪逻辑脏数据是常态不是异常3.1 模拟数据生成的正确姿势课程报告最怕的是「数据不够分析深度」。我见过太多人只造了 30 条数据画出来的折线图毫无说服力。我的习惯是模拟生成至少 3 个患者、每人 60 天、每天 1-3 条记录总量在 500 条以上这样趋势分析才有意义。import sqlite3 import random import pandas as pd from datetime import datetime, timedelta random.seed(42) conn sqlite3.connect(chronic.db) cursor conn.cursor() base_date datetime(2024, 1, 1) patients [ (P001, Zhang, 58, M, 糖尿病), (P002, Li, 62, F, 高血压), (P003, Wang, 55, M, 冠心病) ] for pid, name, age, gender, diag in patients: cursor.execute( INSERT INTO patients VALUES (?,?,?,?,?, datetime(now)), (pid, name, age, gender, diag) ) # 每个患者回归一个基准血压/血糖加随机波动 base_sbp {P001: 125, P002: 145, P003: 135}[pid] base_dbp {P001: 78, P002: 92, P003: 85}[pid] base_glu {P001: 7.2, P002: 5.8, P003: 5.5}[pid] for day in range(60): d (base_date timedelta(daysday)).strftime(%Y-%m-%d) for _ in range(random.randint(1, 3)): sbp base_sbp random.randint(-8, 10) dbp base_dbp random.randint(-5, 6) hr random.randint(58, 92) glu round(base_glu random.uniform(-0.8, 1.2), 1) spo2 random.randint(94, 99) cursor.execute( INSERT INTO health_records (patient_id, record_date, systolic, diastolic, heart_rate, blood_glucose, spo2) VALUES (?,?,?,?,?,?,?), (pid, d, sbp, dbp, hr, glu, spo2) ) conn.commit()这段代码的关键设计是「基准值 随机波动」模式。如果没有基准值血压会在 80-180 之间完全随机跳动画出来的图像波形一样毫无规律可循加了基准值后P002 的血压稳定在 145 附近波动P001 的血糖稳定在 7.2 附近这样才能让追踪和趋势分析有素材。random.seed(42)这行非常关键加了种子后无论你运行多少次生成的数据完全一致。这意味着你的课程报告结果可复现导师问「你这个数据哪来的」时你可以当场重新生成一遍给他看。3.2 空值与异常值的清理策略模拟数据不会自动产生空值但真实数据一定会。我建议在清洗代码里故意构造一些脏数据场景来演示你的清洗逻辑——这在答辩时是亮点。def clean_health_records(df): 清洗健康记录处理空值、逻辑错误、异常范围 df df.copy() # 1. 缺失值处理记录日期与空腹血糖强相关日期缺失直接删除 before len(df) df df.dropna(subset[record_date, patient_id]) print(f删除日期/患者缺失记录: {before} - {len(df)}) # 2. 血压范围检查收缩压 70-200舒张压 40-120 df df[(df[systolic].between(70, 200)) (df[diastolic].between(40, 120))] # 3. 心率范围 40-130超出视为测量误差 df df[df[heart_rate].between(40, 130)] # 4. 血糖范围 2.0-20.0 mmol/L df df[df[blood_glucose].between(2.0, 20.0)] # 5. 血氧 85-100低于 85 属于严重异常保留但标记 df[spo2_abnormal] df[spo2] 90 return df这里的边界值不是随便写的。收缩压下限 70 是临床上严重低血压的阈值低于这个值人已经休克了血糖上限 20 是血糖仪的常见量程上限超过说明设备可能已经测不准了。这些阈值在课程报告里写清楚依据是体现专业度的方式。我对缺失值的处理原则是「能留不留废」日期和患者 ID 缺失的记录直接删因为这两项没法推断数值字段缺失则用前向填充慢病数据具有连续性今天的血压和昨天大概率接近。3.3 趋势追踪算法移动平均与变化率判定追踪的「追」字核心在于判断指标在变好还是变差。单个点的波动毫无意义需要滑动窗口看趋势。我用的方案是 7 日移动平均加上变化率阈值。def trend_analysis(df, window7, threshold5): 计算移动平均与趋势方向 df df.sort_values(record_date) df[sbp_ma7] df[systolic].rolling(windowwindow, min_periods1).mean() # 变化率: (今日均值 - 窗口起始均值) / 窗口起始均值 * 100 df[change_pct] df[sbp_ma7].pct_change(periodswindow) * 100 # 趋势判定: 变化率超过正向阈值 - 恶化; 低于负向阈值 - 好转 df[trend] stable df.loc[df[change_pct] threshold, trend] worsening df.loc[df[change_pct] -threshold, trend] improving return dfpct_change(periodswindow)计算的是「第 n 天的 7 日均值 相对 第 n-7 天的 7 日均值」的变化百分比。如果连续两周血压升高超过 5%系统标记为「恶化」这个标记会同步到可视化看板上变色图块。阈值 5% 不是拍脑袋定的。血压 130 的 5% 就是 6.5mmHg而临床指南通常认为收缩压波动超过 10mmHg 才需要调整用药我取半量作为预警线灵敏度更高。如果你处理的指标是血糖阈值可以改到 10%因为血糖的日内波动比血压大得多。4. 可视化方案设计与实现让数据替你把话说清楚4.1 图表选型逻辑不是所有数据都配得上折线图课程报告和答辩演示里最常见的扣分项是图表选型错误。柱状图硬画成折线图、多指标挤在一张图里——这些问题一眼就能看出来。我按「时间序列、分布、组成、相关」四类场景来做选型决策血压/血糖/心率随时间变化折线图加移动平均趋势线各指标分布区间箱线图能看出中位数、四分位距和离群点一日内多次测量的波动规律热力图纵轴是时间点横轴是日期舒张压与收缩压相关性散点图能直观看到二者联动这套选型逻辑各自独立又能互相印证。折线图讲趋势箱线图讲分布特征答辩时导师问「为什么这里用箱线图」你回答「为了展示数据分布形态并识别离群点」这就是专业术语。4.2 pyecharts 交互式看板的代码实现我在课程设计里选 pyecharts 而不是 matplotlib核心原因是交互性。matplotlib 输出的是静态图而 pyecharts 渲染成 HTML鼠标悬停能看到具体数值还能缩放时间范围——答辩演示的观感差距是碾压性的。另一个原因是 pyecharts 图表种类丰富地图、仪表盘、热力图开箱即用。from pyecharts.charts import Line, Bar, Tab, Grid from pyecharts import options as opts import pandas as pd def build_dashboard(db_pathchronic.db): 构建数据可视化看板 conn sqlite3.connect(db_path) df pd.read_sql_query( SELECT h.record_date, h.systolic, h.diastolic, h.blood_glucose, h.heart_rate, p.name, p.diagnosis FROM health_records h JOIN patients p ON h.patient_id p.patient_id ORDER BY h.record_date, conn ) # 按患者分组计算日均值 df[record_date] pd.to_datetime(df[record_date]) daily df.groupby([name, record_date])[[systolic, diastolic]].mean().reset_index() p001 daily[daily[name] Zhang] # 图表1: 血压趋势 移动平均 line (Line() .add_xaxis(p001[record_date].dt.strftime(%m-%d).tolist()) .add_yaxis(收缩压, p001[systolic].round(1).tolist(), is_smoothTrue, symbolcircle, symbol_size6) .add_yaxis(舒张压, p001[diastolic].round(1).tolist(), is_smoothTrue, symbolcircle, symbol_size6) .set_global_opts( title_optsopts.TitleOpts(titleZhang 血压趋势追踪), tooltip_optsopts.TooltipOpts(triggeraxis), datazoom_opts[opts.DataZoomOpts(range_start20, range_end100)], yaxis_optsopts.AxisOpts( namemmHg, min_70, max_200, splitline_optsopts.SplitLineOpts(is_showTrue) ) )) line.render(dashboard/templates/patient_trend.html) build_dashboard()代码里的关键参数有三个is_smoothTrue让折线平滑化避免锯齿感观感更专业datazoom_opts加了一个拖动条允许查看任意时段细节splitline_opts加了横向网格线方便读取具体数值。我提到的Grid和Tab是 pyecharts 里的布局容器。单个 HTML 里放多个图表用Grid做上下分栏多个页面切换用Tab做标签页。课程设计中我做的是一个Tab总页面每个患者一个标签页点一下就能切换演示效果非常好。4.3 大屏布局的 CSS 网格方案如果内容是「数据可视化大屏」就涉及布局问题。pyecharts 本身只负责生成单个图表 HTML多个图表拼成一个大屏需要用 HTML 模板做网格布局。!DOCTYPE html html head meta charsetutf-8 style .grid-container { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 300px 300px; gap: 12px; padding: 12px; background: #0f1420; } .grid-item { background: #1a2130; border-radius: 8px; padding: 8px; border: 1px solid #2a3441; } .grid-item iframe { width: 100%; height: 100%; border: none; } .header { color: #e6e9ef; text-align: center; font-size: 22px; padding: 16px; background: #1a2130; font-weight: 600; } /style /head body div classheader慢性病管理数据追踪看板/div div classgrid-container div classgrid-item iframe srcpatient_trend.html/iframe /div !-- 其他图表 iframe 嵌入 -- /div /body /htmliframe 嵌入方案是我总结出来的最优解。pyecharts 的 render API 本身就是生成完整 HTML你直接在网格的每个单元格里嵌 iframe 指向对应 HTML 文件不用手动改 pyecharts 模板源码——那个模板改起来又长又容易出错。颜色配比上深色背景配亮色图表这是数据可视化大屏的通用做法能让图表的主体色彩更突出。后台开着python app.py浏览器打开本地服务地址是一个完整系统该有的样子。5. 数据追踪与可视化系统的避坑指南五个高频翻车点5.1 中文乱码与字体缺失图表标题变方块现象图表标题、轴标签里的中文全部显示为方块或问号导出的 HTML 在答辩电脑上也一样。原因pyecharts 渲染时默认用的字体不含中文字形或者操作系统没安装对应的中文字体。在未安装中文字体的 Linux 服务器上尤其常见学校实验室的电脑有些精简系统也有这个问题。解决在图表配置里显式指定中文字体二选一即可。一是本地安装字体后配置opts.TitleOpts(title血压趋势, title_textstyle_optsopts.TextStyleOpts(font_familyMicrosoft YaHei))二是确保系统有可用中文字体。我在代码里统一用font_familyMicrosoft YaHeiWindows 和 macOS 都有这个字体是兼容性最好的方案。5.2 SQLite 时间用文本存储日期排序错乱现象ORDER BY record_date排序后数据乱序比如 2024-02-01 排到了 2024-01-31 前面或者同一天的数据顺序不对。原因如果你把日期存成了TEXT类型并且格式不统一有的是2024-1-1有的是2024-01-01文本排序按字符逐个比较2024-1-1会排在2024-01-02后面。解决所有日期统一用YYYY-MM-DD格式存储零填充是必须的。我在建表时用datetime(now)生成的默认值天然就是标准格式但插入模拟数据时用strftime(%Y-%m-%d)显式格式化日期确保格式一致。5.3 pandas 读入 SQLite 后日期自动多 8 小时现象读取数据后record_date从2024-01-01变成了2024-01-01 08:00:00做时间序列分析时按日聚合会出错。原因SQLite 的 TEXT 日期被 pandas 识别后自动推断为 datetime64默认按本地时区东八区解析。由于 TEXT 里没有时区信息pandas 会补一个默认 UTC 的假定时区再转换导致偏移。解决读取 SQL 时不用 pandas 自动推断而是手动指定解析格式df pd.read_sql_query(sql, conn) df[record_date] pd.to_datetime(df[record_date], format%Y-%m-%d %H:%M:%S)这里显式告诉 pandas 日期字符串的完整格式它就不会自作主张补时区。如果你在 pandas 2.0 以上版本遇到报错可以加errorscoerce把无法解析的日期置为 NaT用完再 dropna。5.4 滚动平均出现 NaN图表前半段空白现象计算 7 日移动平均后前 6 天的数值是 NaN折线图上开头一段是空的看起来像数据缺损。原因rolling(window7)默认要求窗口内有 7 个完整数据点才计算。某天有 1 次测量、有时 3 次如果某天缺失记录窗口凑不齐 7 个点就会一直 NaN。解决rolling有三个方案。其一用min_periods1我在前面代码里用的窗口内有 1 个数据就算均值其二用min_periods3至少 3 个点参与计算平衡稳定性和空值其三先用resample(D).mean()把数据重采样成按天的规整序列再滚动平均。我实际用的是第三种因为重采样后数据结构更规整后续其他分析也方便。5.5 pyecharts 图表在 Jupyter 里不显示但 HTML 文件正常现象同样的代码render(x.html)能正常打开但写在 Jupyter Notebook 里运行时图表区域空白或者只有一行提示文字。原因pyecharts 在 Jupyter 里走的是内联渲染inline render需要加载相应的 JS 资源如果你的网络被限制、无法访问 CDN 资源图表就放不出来。解决换render_notebook()无效就先排查网络。更稳妥的方案是调整资源加载方式from pyecharts.globals import CurrentConfig CurrentConfig.ONLINE_HOST https://cdn.jsdelivr.net/npm/echarts5/dist/把远程资源指向 jsdelivr它比默认的 bootcdn 稳定性更好。如果对离线环境有要求就把echarts.min.js下载到本地设置CurrentConfig.ONLINE_HOST ./static/js/。我最后是用 HTML 展示而不是 Jupyter省掉所有依赖问题。6. 进阶把分析流程装进 Flask让系统变成可演示的产品前面所有分析都是脚本级别的但课程设计答辩时脚本和系统的区别很直观。我给你一套把追踪和可视化整合到 Flask 服务的方案让整个系统可以交互式使用。6.1 核心服务模块与路由设计from flask import Flask, render_template, jsonify, request import sqlite3 import pandas as pd app Flask(__name__) DB_PATH chronic.db def get_records(patient_idNone, startNone, endNone): 查询健康记录支持患者和时间范围过滤 conn sqlite3.connect(DB_PATH) sql SELECT * FROM health_records WHERE 11 params [] if patient_id: sql AND patient_id ? params.append(patient_id) if start: sql AND record_date ? params.append(start) if end: sql AND record_date ? params.append(end) sql ORDER BY record_date df pd.read_sql_query(sql, conn, paramsparams) conn.close() return df app.route(/) def index(): 看板首页渲染大屏模板 return render_template(dashboard.html) app.route(/api/trend/patient_id) def api_trend(patient_id): 趋势数据接口返回患者时间序列 df get_records(patient_id) df trend_analysis(df) return jsonify({ dates: df[record_date].tolist(), systolic: df[systolic].tolist(), sbp_ma7: df[sbp_ma7].round(2).tolist(), trend: df[trend].tolist() }) if __name__ __main__: app.run(debugTrue, port5000)这段代码把前面所有分析逻辑串成 HTTP 接口。/api/trend/patient_id接口接受患者 ID 参数返回该患者的血压时间序列和趋势判定前端可以动态渲染图表。WHERE 11这种写法是动态构建 SQL 的经典技巧——先写一个恒真条件后面拼接的过滤条件都用AND连接省去了判断「是否第一个条件」的麻烦。虽然有点 hack但在课程设计场景下完全没问题也不会造成性能消耗。6.2 动态传参的图表渲染方法接口就绪后前端图表用 JavaScript 发请求拿数据再喂给 ECharts而不是像之前那样在 Python 端写死数据。async function loadTrend(patientId) { const res await fetch(/api/trend/${patientId}); const data await res.json(); const chart echarts.init(document.getElementById(trend-chart)); chart.setOption({ tooltip: { trigger: axis }, xAxis: { type: category, data: data.dates }, yAxis: { type: value, name: mmHg, min: 70, max: 200 }, series: [ { name: 收缩压, type: line, data: data.systolic, smooth: true }, { name: 7日均值, type: line, data: data.sbp_ma7, smooth: true, lineStyle: { type: dashed } } ] }); }前端用fetch请求后端接口拿到 JSON 后setOption动态渲染。这套前后端分离的写法比单纯在 Python 端生成静态图表高一个层次——它意味着系统真的具有了「追数据」的能力新增一条记录后刷新页面就能看到最新追踪结果。我后来给学生改课程设计时每次都会让页面筛选器加患者下拉框而不是一个患者一个页面。一来代码重复度低二来答辩时可以直接演示系统响应。从那以后我每写一个可视化项目都强制把数据层和展示层拆开——先定义接口再做页面这套流程希望帮到你。本文还有配套的精品资源点击获取
返回列表