简介本资源是一套面向数据分析初学者与可视化实践者的Python疫情分析项目源码围绕中美两国疫情数据展开处理与展示适合用于课程设计、练手项目或数据分析入门练习。压缩包共19个文件约145KB其中7个HTML页面承担可视化展示6个Python脚本负责数据清洗、预测与对比分析4个Excel与1个CSV文件提供中美及分省疫情数据另有说明文档辅助理解项目结构。项目覆盖国内疫情、全球疫情、中美对比以及中国与美国未来14天新增预测等模块读者可借此学习用Python读取表格数据、完成趋势预测并输出网页图表掌握从数据处理到前端呈现的完整链路。目前已有296人学习体量轻便便于快速运行与二次修改。1. 中美疫情数据可视化从原始 CSV 到可交互看板的完整落地路径手里拿到一份中美疫情数据 CSV字段散、日期格式乱、两国统计口径还不一样想把它做成一张能讲清楚趋势的图这件事比想象中麻烦。这个标题指向的是一套完整的 Python 数据分析与可视化方案用 pandas 清洗两国疫情时间序列用 matplotlib 和 plotly 分别产出静态图和交互看板最终交付一份可复现的源码结构。它适合两类人——刚学完 pandas 基础、想找一个真实数据集练手的入门者以及需要快速搭出数据展示原型、不想从零写清洗逻辑的从业者。核心难点不在画图而在两国数据的对齐与口径统一这一步做不好后面所有图表都是错的。下面按数据获取、清洗、分析、可视化、避坑的顺序拆开讲。2. 数据获取与字段对齐中美疫情数据从哪来、怎么统一口径2.1 数据源的常见选择与字段差异做中美疫情对比数据源通常有两类一类是约翰斯·霍普金斯大学JHU的全球时间序列 CSV另一类是国内公开的省级每日通报汇总。JHU 的数据以Confirmed、Deaths、Recovered三列为主按Country/Region和Province/State两级索引日期是宽表格式——每一列是一个日期。国内数据常见的是长表格式每行一条记录字段包括省份、日期、新增确诊、累计确诊、新增无症状等。两者最直接的冲突是美国数据按州拆分中国数据按省拆分但字段名、日期格式、累计与新增的定义都不一致。我一般会先把两边都转成统一的长表结构字段固定为country、region、date、cum_confirmed、new_confirmed、cum_deaths再往下做分析。这样后面无论画图还是算增长率都只面对一套字段。注意JHU 数据在 2023 年后更新频率下降做演示项目够用但如果要接实时数据需要换用其他持续维护的接口。2.2 用 pandas 读取并统一两国数据的实操步骤先装好依赖这一步别跳过版本确认pandas 2.x 和 1.x 在read_csv的parse_dates行为上有差异。pip install pandas matplotlib plotly openpyxl python -c import pandas; print(pandas.__version__)读取 JHU 宽表并转长表import pandas as pd # JHU 确认病例宽表第一列是 Province/State第二列是 Country/Region jhu pd.read_csv(time_series_covid19_confirmed_global.csv) # 宽表转长表把日期列全部 melt 成行 date_cols jhu.columns[4:] # 前四列是 Province/State, Country/Region, Lat, Long jhu_long jhu.melt( id_vars[Province/State, Country/Region], value_varsdate_cols, var_namedate, value_namecum_confirmed ) jhu_long[date] pd.to_datetime(jhu_long[date], format%m/%d/%y) jhu_long jhu_long.rename(columns{ Province/State: region, Country/Region: country }) # 只保留美国和中国 jhu_long jhu_long[jhu_long[country].isin([US, China])]这段代码的关键在melt的参数id_vars保留标识列value_vars指定要折叠的日期列var_name和value_name决定折叠后新列的名字。日期格式%m/%d/%y对应 JHU 的1/22/20这种写法写错会导致to_datetime报错或产生 NaT。region列在美国数据里是州名在中国数据里是省名后续聚合时要注意区分。国内数据如果是长表直接对齐字段即可cn pd.read_csv(china_province_daily.csv) cn cn.rename(columns{ 省份: region, 日期: date, 累计确诊: cum_confirmed }) cn[country] China cn[date] pd.to_datetime(cn[date]) cn cn[[country, region, date, cum_confirmed]]合并时用pd.concat不要用merge因为两边字段已经对齐直接纵向拼接更安全combined pd.concat([jhu_long, cn], ignore_indexTrue) combined combined.sort_values([country, region, date]) combined[new_confirmed] combined.groupby([country, region])[cum_confirmed].diff().fillna(0)diff()算每日新增fillna(0)处理每个地区第一天的空值。这里有个坑如果某个地区中间某天数据缺失diff()会把缺失前后的差值算成一天的新增导致尖峰。稳妥做法是先按日期重采样补全再算 diff。3. 数据清洗与特征构造让两国数据能放在同一张图上比3.1 处理缺失值、异常值和统计口径差异中美数据放在一起比最大的障碍是人口基数差异。美国 3 亿多人口中国 14 亿直接比累计确诊数没有意义。常见做法是构造每百万人的指标population { US: 331_000_000, China: 1_412_000_000 } combined[pop] combined[country].map(population) combined[cum_per_million] combined[cum_confirmed] / combined[pop] * 1_000_000异常值方面JHU 数据偶尔会出现累计数回退某天数字比前一天小这通常是数据修正导致的。处理方式是检测回退并修正def fix_regression(group): group group.sort_values(date) # 累计数不应下降下降则用前值填充 group[cum_confirmed] group[cum_confirmed].cummax() return group combined combined.groupby([country, region], group_keysFalse).apply(fix_regression)cummax()保证累计数单调不减这是处理疫情数据回退最省事的办法。代价是如果某天数据确实被大幅下修你会保留一个偏高的值但对趋势展示影响不大。3.2 构造增长率与七日滚动平均日新增数据波动大直接画图全是锯齿。标准做法是算七日滚动平均combined[new_7d_avg] ( combined.groupby([country, region])[new_confirmed] .transform(lambda x: x.rolling(7, min_periods1).mean()) )rolling(7)窗口是 7 天min_periods1保证开头几天也有值不会产生 NaN。transform保证返回的索引和原表一致方便直接赋值。增长率用pct_changecombined[growth_rate] ( combined.groupby([country, region])[cum_confirmed] .pct_change(periods7) )periods7表示和 7 天前比避免周末效应导致的虚假波动。这一步算出来的增长率是小数画图时乘以 100 转成百分比。提示如果要做国家级汇总先按country和date聚合再算滚动平均和增长率顺序反了会导致各州/省的数据被平均两次。4. 可视化实现静态图与交互看板的两套代码4.1 用 matplotlib 画中美趋势对比图matplotlib 适合出报告用的静态图。下面这段代码画两张子图累计确诊每百万人对比和七日平均新增对比。import matplotlib.pyplot as plt import matplotlib.dates as mdates # 按国家聚合到国家级 national combined.groupby([country, date]).agg( cum_per_million(cum_per_million, sum), new_7d_avg(new_7d_avg, sum) ).reset_index() fig, axes plt.subplots(2, 1, figsize(12, 8), sharexTrue) for country, color in [(US, #1f77b4), (China, #d62728)]: subset national[national[country] country] axes[0].plot(subset[date], subset[cum_per_million], labelcountry, colorcolor) axes[1].plot(subset[date], subset[new_7d_avg], labelcountry, colorcolor) axes[0].set_ylabel(累计确诊每百万人) axes[1].set_ylabel(七日平均新增) axes[1].xaxis.set_major_formatter(mdates.DateFormatter(%Y-%m)) axes[1].xaxis.set_major_locator(mdates.MonthLocator(interval3)) plt.xticks(rotation45) axes[0].legend() plt.tight_layout() plt.savefig(us_china_trend.png, dpi150)sharexTrue让两张图共用 x 轴缩放时同步。DateFormatter和MonthLocator控制日期显示密度这是解决「python画图横坐标太密集」的标准手段。dpi150保证导出图清晰度够用。如果横坐标还是挤把interval从 3 改成 6。4.2 用 plotly 做可交互看板plotly 的优势是鼠标悬停能看具体数值适合做展示。下面用plotly.express快速出图import plotly.express as px fig px.line( national, xdate, ynew_7d_avg, colorcountry, title中美七日平均新增确诊对比, labels{new_7d_avg: 七日平均新增, date: 日期, country: 国家} ) fig.update_layout( hovermodex unified, xaxisdict( rangeselectordict( buttonslist([ dict(count1, label1月, stepmonth, stepmodebackward), dict(count6, label6月, stepmonth, stepmodebackward), dict(stepall, label全部) ]) ) ) ) fig.write_html(dashboard.html)hovermodex unified让鼠标移到某个日期时同时显示两国的值对比更直观。rangeselector加了时间范围按钮这是交互看板的基本配置。write_html导出单个 HTML 文件不依赖服务器发给别人直接能打开。如果要更细的省级/州级下钻用px.line的facet_row或facet_col按地区分面但中美地区数量差异大建议分开做两个看板不要硬塞进一张图。5. 避坑与排查中美疫情数据可视化里最容易翻车的 5 个点5.1 日期解析失败导致整列 NaT现象pd.to_datetime之后大量日期变成NaT后续按日期排序全乱。 原因JHU 日期格式是1/22/20国内数据可能是2020-01-22或2020/1/22格式不统一时to_datetime推断失败。 解决显式指定format参数JHU 用%m/%d/%y国内用%Y-%m-%d解析完检查df[date].isna().sum()是否为 0。5.2 累计数回退导致新增出现负值现象diff()算出的new_confirmed出现负数画图时曲线突然往下掉。 原因数据源修正了历史累计数导致某天累计值比前一天小。 解决先对累计列做cummax()修正再算 diff或者在 diff 后把负值截断为 0但截断会丢失修正信息优先用 cummax。5.3 两国人口基数不同导致对比失真现象中国累计确诊曲线远高于美国但实际每百万人指标可能相反。 原因直接用了绝对数值没有做人均归一化。 解决构造cum_per_million字段用各国人口总数做分母。人口数据要写死在代码里或用配置文件不要每次手动改。5.4 滚动平均窗口设置不当掩盖真实趋势现象七日平均曲线太平滑看不出短期爆发。 原因窗口太大或太小。窗口太小如 3 天去噪不够太大如 14 天会滞后。 解决默认用 7 天这是公共卫生领域常用窗口。如果需要看短期变化同时画原始日新增和 7 日平均两条线不要只画一条。5.5 中文标签在 matplotlib 里显示为方块现象图例和轴标签里的中文变成方框。 原因matplotlib 默认字体不支持中文。 解决在代码开头设置字体plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei, Arial Unicode MS] plt.rcParams[axes.unicode_minus] FalseSimHei是 Windows 自带黑体macOS 用Arial Unicode MSLinux 服务器上如果没有中文字体需要先安装或改用英文标签。6. 进阶技巧把看板做成可复用的数据管道前面几章的代码是线性的每次换数据都要改路径和字段名。实际项目里我习惯把它拆成一个配置驱动的管道核心思路是把「数据源路径、字段映射、人口数据、输出路径」全部抽到一个config.yaml里主脚本只负责按配置执行。import yaml def load_config(pathconfig.yaml): with open(path, r, encodingutf-8) as f: return yaml.safe_load(f) def run_pipeline(config): # 按配置读取数据源 frames [] for source in config[sources]: df pd.read_csv(source[path]) df df.rename(columnssource[column_map]) df[country] source[country] frames.append(df) combined pd.concat(frames, ignore_indexTrue) # 后续清洗、分析、可视化按配置参数执行 combined clean(combined, config[cleaning]) combined enrich(combined, config[population]) plot_static(combined, config[output][static]) plot_interactive(combined, config[output][interactive])这样换数据源只需要改 YAML不用动 Python 代码。验证管道是否正确的办法是写一个最小测试拿 10 行手工构造的数据跑一遍检查输出的cum_per_million和new_7d_avg是否和手算一致。我一般会在tests/下放一个test_pipeline.py用pytest跑每次改清洗逻辑先跑测试。另一个实用技巧是给看板加一个「数据截止日期」标注。疫情数据有滞后性图上看不到截止日期容易让人误判趋势。在 plotly 里用fig.add_annotation在右下角标上f数据截止{combined[date].max().date()}成本很低但能避免很多误读。这套方案我从头跑过几遍最大的教训是不要一上来就调图表样式先把两国数据的日期和累计口径对齐否则后面每张图都要返工。清洗阶段多花半小时可视化阶段能省两小时。希望帮到你。本文还有配套的精品资源点击获取