尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Python疫情数据可视化:中美CSV清洗与看板实战

Python疫情数据可视化:中美CSV清洗与看板实战 简介这份资源面向具备Python基础、希望练习数据分析与可视化实战的学生和开发者围绕中美两国疫情数据展开解决从原始表格到网页图表展示的完整流程问题。压缩包共19个文件、约145KB包含6个Python脚本负责数据读取、预测与对比分析7个HTML页面用于呈现全球、国内及中美对比等可视化结果另有4个xlsx与1个csv提供中美及分省疫情数据1个txt作说明整体结构清晰、便于按模块查阅。已有296人学习下载适合作为课程设计或练手项目参考。读者可借此掌握用Python处理多源疫情数据、生成未来14天新增预测、并输出可交互网页图表的思路同时获得一套可直接运行和二次修改的代码与数据模板快速理解数据清洗、分析与展示的衔接方式。1. 中美疫情数据可视化从两份 CSV 到一套能跑起来的 Python 看板手头拿到两份疫情时间序列 CSV——一份是国内各省份每日新增一份是美国各州每日新增——字段名不统一、日期格式不一致、缺失值散落各处。直接丢进 matplotlib 画出来的图x 轴挤成一团图例盖住半张画布领导看了一眼说“这什么东西”。这不是绘图库的问题是数据没洗干净就上桌的问题。这套方案要解决的就是这件事用 Python 把中美两国的疫情数据从原始 CSV 清洗成统一结构再用 pyecharts 或 matplotlib 输出可交互或可嵌入报告的可视化图表。适合有 Python 基础、需要快速交付一份数据分析展示的从业者也适合正在做课程设计或企业级数据看板原型的开发者。核心链路只有四步读数据、清洗对齐、选图型、渲染输出。下面按这条链路拆开讲。2. 数据加载与清洗pandas 读进来之后先别急着画2.1 两份数据源的字段差异与对齐策略中美疫情数据最常见的来源是 JHU CSSE 的 time_series 格式和国内卫健委/丁香园整理的省份日增表。前者是宽表——每列一个日期每行一个地区后者通常是长表——每行一条“日期-省份-新增”记录。两种结构不统一直接合并会翻车。我一般先把两份数据都转成统一的长表结构date、region、country、new_cases、cumulative_cases五列。宽转长用pd.melt()长表直接用列重命名对齐。关键点是日期列必须统一成datetime64类型否则后面按时间聚合时排序会出玄学问题。import pandas as pd # 读取国内省份数据假设是长表格式 cn_df pd.read_csv(china_province_daily.csv) cn_df cn_df.rename(columns{ 日期: date, 省份: region, 新增确诊: new_cases, 累计确诊: cumulative_cases }) cn_df[country] China cn_df[date] pd.to_datetime(cn_df[date], format%Y-%m-%d) # 读取美国州级数据假设是 JHU 宽表格式 us_raw pd.read_csv(us_states_time_series.csv) us_df us_raw.melt( id_vars[Province_State, Country_Region], var_namedate, value_namecumulative_cases ) us_df us_df.rename(columns{ Province_State: region, Country_Region: country }) us_df[date] pd.to_datetime(us_df[date], format%m/%d/%y) us_df us_df.sort_values([region, date]) us_df[new_cases] us_df.groupby(region)[cumulative_cases].diff().fillna(0) us_df[new_cases] us_df[new_cases].clip(lower0) # 修正数据回退导致的负值 # 合并 combined pd.concat([cn_df, us_df], ignore_indexTrue) combined combined[[date, region, country, new_cases, cumulative_cases]]这段代码的核心逻辑是先把两份异构数据统一到同一套列名和数据类型再合并。pd.melt()把宽表转长表groupby().diff()从累计值反推每日新增clip(lower0)处理数据修正导致的累计值回退——这是疫情数据里最常见的脏数据形态不处理的话新增曲线会出现莫名其妙的负值尖刺。参数上需要注意pd.to_datetime的format参数必须和源数据严格匹配美国数据里%m/%d/%y和%Y-%m-%d混用是高频翻车点。如果源数据日期格式不统一先用pd.to_datetime(df[date], infer_datetime_formatTrue)兜底再检查有没有解析失败的 NaT。2.2 缺失值处理与异常值修正的三种策略疫情数据里缺失值分三类整行缺失某天某省没上报、累计值缺失但新增有值、新增缺失但累计可推算。三种情况处理方式不同。第一种直接dropna(subset[date, region])删掉因为缺日期或地区的记录没有分析价值。第二种用累计值做前向填充再差分df[cumulative_cases].ffill()然后重新算 diff。第三种用累计值差分补新增或者用前后两天的均值插值。# 策略一删除关键字段缺失的行 combined combined.dropna(subset[date, region, country]) # 策略二按地区分组后对累计值前向填充再重算新增 combined combined.sort_values([country, region, date]) combined[cumulative_cases] combined.groupby( [country, region] )[cumulative_cases].ffill() # 策略三对新增值的缺失用线性插值 combined[new_cases] combined.groupby( [country, region] )[new_cases].transform( lambda s: s.interpolate(methodlinear, limit3) ) # 异常值单日新增超过该地区历史均值的 10 倍标记但不删除 stats combined.groupby(region)[new_cases].agg([mean, std]) combined combined.merge(stats, onregion, suffixes(, _stat)) combined[is_outlier] combined[new_cases] ( combined[mean] 5 * combined[std] )这里interpolate的limit3表示最多连续插值 3 天超过 3 天的空缺说明数据源本身有问题强行插值会引入虚假趋势。异常值标记而不删除是因为疫情数据里的“暴增”往往是真实的集中排查结果删掉反而丢失信息。注意ffill()之前必须按日期排序否则填充的是错误方向的值。这个坑我在第一次做的时候踩过填充完发现 3 月的数据被 5 月的值覆盖了。3. 可视化选型什么数据配什么图别拿饼图画时间序列3.1 时间序列用折线、地区对比用柱状、占比用堆叠面积中美疫情数据能回答的问题无非几类趋势变化每日新增走势、地区对比各省/各州累计排名、构成占比某国占全球比例、地理分布地图热力。每种问题对应的图型选择有明确边界。时间序列只有折线图一个正确答案别用柱状图画 365 天的日增数据柱子会密到看不清。地区对比用横向柱状图地区名放 y 轴避免 x 轴标签旋转。占比用堆叠面积图或饼图——但饼图只在类别少于 6 个时用中美两国对比可以用三十个省份就别用了。import matplotlib.pyplot as plt import matplotlib.dates as mdates plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 按国家聚合每日新增 daily combined.groupby([date, country])[new_cases].sum().reset_index() fig, ax plt.subplots(figsize(14, 6)) for country, group in daily.groupby(country): ax.plot(group[date], group[new_cases], labelcountry, linewidth1.5) ax.xaxis.set_major_locator(mdates.MonthLocator(interval2)) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) ax.set_xlabel(日期) ax.set_ylabel(每日新增确诊) ax.set_title(中美每日新增确诊趋势对比) ax.legend() ax.grid(axisy, alpha0.3) plt.tight_layout() plt.savefig(trend_comparison.png, dpi150)MonthLocator(interval2)控制 x 轴刻度每两个月一个避免日期标签重叠。dpi150是嵌入报告的最低清晰度要求屏幕展示用 100 就够打印用 300。tight_layout()解决图例被裁切的问题——这个函数不调用的话保存的图片经常缺一块。3.2 pyecharts 交互式看板的配置要点如果交付物是网页或需要交互鼠标悬停看具体数值、点击图例筛选matplotlib 就不够了换 pyecharts。它的核心优势是输出 HTML可以直接嵌入 Flask 或 Django 项目也符合企业级数据可视化看板的常见技术栈。from pyecharts.charts import Line, Bar, Grid from pyecharts import options as opts # 准备中美两国按月的汇总数据 monthly combined.copy() monthly[month] monthly[date].dt.to_period(M).astype(str) monthly_agg monthly.groupby([month, country])[new_cases].sum().reset_index() cn_data monthly_agg[monthly_agg[country] China] us_data monthly_agg[monthly_agg[country] United States] line ( Line() .add_xaxis(cn_data[month].tolist()) .add_yaxis( 中国, cn_data[new_cases].tolist(), is_smoothTrue, linestyle_optsopts.LineStyleOpts(width2), ) .add_yaxis( 美国, us_data[new_cases].tolist(), is_smoothTrue, linestyle_optsopts.LineStyleOpts(width2), ) .set_global_opts( title_optsopts.TitleOpts(title中美月度新增确诊对比), xaxis_optsopts.AxisOpts(name月份, axislabel_optsopts.LabelOpts(rotate45)), yaxis_optsopts.AxisOpts(name新增确诊), tooltip_optsopts.TooltipOpts(triggeraxis), legend_optsopts.LegendOpts(pos_top5%), ) ) line.render(covid_trend.html)is_smoothTrue让折线平滑但数据点少于 10 个时不建议开会扭曲趋势。triggeraxis让 tooltip 跟随 x 轴显示所有系列的值比默认的item模式更适合对比场景。rotate45处理月份标签过长的问题。pyecharts 的坑在于它生成的 HTML 依赖 CDN 加载 echarts.js内网环境或离线演示时会白屏。解决办法是在render()时指定cdn为本地路径或者用snapshot模式输出静态图片。这个在交付前一定要测。4. 避坑与排查那些让我加班到凌晨的细节4.1 日期解析翻车%y和%Y差一个世纪现象美国数据里1/22/20被解析成 1920 年图表 x 轴范围从 1920 拉到 2020。原因pd.to_datetime的%y是两位年份%Y是四位。JHU 数据用两位年份如果格式串写错pandas 默认映射到 1969-2068 区间20会被解释成 2020 还是 1920 取决于具体实现。解决统一用format%m/%d/%y解析两位年份解析后检查df[date].dt.year.min()是否合理。更稳妥的做法是解析后手动修正df.loc[df[date].dt.year 2000, date] pd.DateOffset(years100)。4.2 中文标签显示为方块现象matplotlib 图表里所有中文变成□□□。原因matplotlib 默认字体不支持中文且axes.unicode_minus未关闭导致负号也异常。解决plt.rcParams[font.sans-serif] [SimHei]加上plt.rcParams[axes.unicode_minus] False。Linux 环境下 SimHei 可能不存在换成WenQuanYi Micro Hei或Noto Sans CJK SC。用matplotlib.font_manager.findfont(SimHei)确认字体路径是否存在。4.3 累计值差分出现负新增现象new_cases列出现 -300 这样的值折线图出现向下尖刺。原因数据源修正了历史累计值导致后一天累计小于前一天。或者跨地区合并时排序错误把不同地区的累计值做了差分。解决差分前必须groupby(region)差分后clip(lower0)。如果负值频繁出现检查数据源是否有“核减”字段——部分省份会把核减数单独列出需要从新增里减去。4.4 pyecharts 输出 HTML 在浏览器打开空白现象render()生成的 HTML 文件双击打开后页面全白控制台报echarts is not defined。原因pyecharts 默认从 CDN 加载 echarts.js网络不通或 CDN 被墙时加载失败。解决安装pyecharts时确认版本在render()中指定cdn参数为本地 echarts.min.js 路径或者用pyecharts.globals.CurrentConfig.ONLINE_HOST改成内网镜像地址。离线场景建议直接用 matplotlib 出静态图。4.5 数据量过大导致渲染卡死现象把 3000 行日增数据直接丢给 pyecharts 折线图浏览器标签页卡死。原因前端渲染上万个数据点会触发性能瓶颈尤其是开启了is_smooth和tooltip的情况下。解决按周或按月聚合后再渲染数据点控制在 200 个以内。如果必须展示日粒度用dataZoom组件做区间缩放默认只显示最近 90 天。5. 从静态图到可复用脚本把清洗逻辑封装成函数前面四章的代码散落在各处每次换一份数据就要重新改列名、改格式。实际交付时我一般会把清洗和绘图封装成两个函数输入原始 CSV 路径输出图表文件和一份清洗后的中间数据。def clean_covid_data(cn_path, us_path): 读取中美疫情 CSV返回统一结构的长表 DataFrame cn pd.read_csv(cn_path).rename(columns{ 日期: date, 省份: region, 新增确诊: new_cases, 累计确诊: cumulative_cases }) cn[country] China cn[date] pd.to_datetime(cn[date]) us pd.read_csv(us_path) us us.melt( id_vars[Province_State, Country_Region], var_namedate, value_namecumulative_cases ).rename(columns{Province_State: region, Country_Region: country}) us[date] pd.to_datetime(us[date], format%m/%d/%y) us us.sort_values([region, date]) us[new_cases] us.groupby(region)[cumulative_cases].diff().fillna(0).clip(lower0) df pd.concat([cn, us], ignore_indexTrue) df df.dropna(subset[date, region]) df df.sort_values([country, region, date]) df[cumulative_cases] df.groupby([country, region])[cumulative_cases].ffill() return df[[date, region, country, new_cases, cumulative_cases]] def plot_trend(df, output_pathtrend.png): 按国家聚合每日新增并输出折线图 daily df.groupby([date, country])[new_cases].sum().reset_index() fig, ax plt.subplots(figsize(14, 6)) for country, group in daily.groupby(country): ax.plot(group[date], group[new_cases], labelcountry, linewidth1.5) ax.xaxis.set_major_locator(mdates.MonthLocator(interval2)) ax.xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) ax.set_xlabel(日期); ax.set_ylabel(每日新增确诊) ax.set_title(中美每日新增确诊趋势对比) ax.legend(); ax.grid(axisy, alpha0.3) plt.tight_layout(); plt.savefig(output_path, dpi150); plt.close()封装之后换数据源只需要改clean_covid_data里的列名映射绘图逻辑完全复用。plt.close()不能省——批量生成几十张图时不关 figure 会吃光内存。验证清洗结果是否正确我习惯做三个检查df[date].min()和max()是否覆盖预期时间范围df.groupby(country)[new_cases].sum()的量级是否合理中国累计约 100 万级别美国约 1 亿级别df[new_cases].describe()看有没有极端异常值。这套脚本我前后改了七八版最大的教训是别在绘图阶段修数据。图不对八成是清洗没做干净回头查groupby的维度、diff的顺序、ffill的方向。把清洗和绘图彻底分开排查问题时能省一半时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表