十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

Python地理空间数据分析实战:模拟PM1健康影响与可视化

Python地理空间数据分析实战:模拟PM1健康影响与可视化 最近在环境健康领域看到不少关于空气污染与健康影响的研究报道其中“超细颗粒物”这个专业术语频繁出现它与我们熟知的PM2.5有何不同一项全球性研究更是将每年近200万人的过早死亡归因于它。这不仅仅是环境科学家的课题作为开发者我们同样可以运用技术手段从数据获取、分析到可视化来理解和呈现这一严峻的公共健康问题。本文将从一个数据科学与可视化的实战角度出发手把手带你搭建一个分析框架模拟研究超细颗粒物PM1与健康数据的关联并最终生成直观的可交互图表。无论你是对环境数据感兴趣的数据分析新手还是希望将地理空间分析融入项目的开发者都能从本文获得一套完整的、可复现的代码方案。我们将使用Python生态中的经典工具链涵盖数据获取、清洗、空间分析与可视化全流程。1. 背景与核心概念从PM2.5到PM1在讨论之前我们首先要厘清几个关键概念。空气动力学直径小于或等于2.5微米的颗粒物被称为PM2.5细颗粒物它能够进入人体支气管和肺泡对健康造成显著影响已是公众熟知的污染指标。而超细颗粒物通常指空气动力学直径小于或等于0.1微米即100纳米有时也指PM1即直径≤1微米的颗粒物。与PM2.5相比PM1的粒径更小比表面积更大更容易吸附有毒有害物质如重金属、多环芳烃等。由于其极小的尺寸它们不仅能深入肺部还可能穿过肺泡壁进入血液循环从而对心血管系统、神经系统等产生更直接、更广泛的潜在危害。那项引发关注的全球研究其核心在于建立了长期暴露于PM1或特定来源的超细颗粒物浓度与特定疾病如心肺疾病死亡风险之间的定量关系并利用全球人口、基线死亡率和暴露浓度网格数据估算出归因于该污染的“过早死亡”人数。这是一个典型的环境流行病学与数据科学交叉的课题。作为技术人我们可以聚焦于研究的“后端”过程如何获取和处理相关的环境与健康数据如何进行空间关联分析以及如何将复杂的研究结果以清晰易懂的方式呈现出来。这涉及到数据爬取或使用公开API、地理信息处理、统计分析与可视化等一系列技能。2. 环境准备与版本说明本项目主要使用Python进行数据分析与可视化。以下环境是经过测试的稳定组合建议读者使用类似版本以避免不必要的依赖冲突。操作系统Windows 10/11, macOS, 或 Linux (如Ubuntu 20.04) 均可。Python版本3.8 或 3.93.10及以上版本部分库可能需注意兼容性。IDE推荐VS Code (配合Python插件) 或 Jupyter Notebook/Lab后者非常适合分步执行和即时可视化。核心Python库及版本数据处理pandas1.3.0,numpy1.21.0地理空间处理geopandas0.10.0(核心),rasterio(用于读取栅格数据可选)数据获取requests2.26.0可视化matplotlib3.5.0,plotly5.8.0(用于交互图表),contextily(添加底图)科学计算scipy1.7.0(用于统计)安装命令 建议创建一个新的虚拟环境然后使用pip安装。geopandas的安装可能稍复杂因为它依赖GEOS、GDAL等C库。推荐使用conda安装或通过预编译的wheel文件。# 方法一使用pip (确保已安装对应C库依赖Windows用户可尝试下载对应版本的whl文件) pip install pandas numpy requests matplotlib plotly scipy pip install geopandas # 如果失败请参考方法二 # 方法二使用conda推荐能自动处理地理空间库的复杂依赖 conda create -n air_quality_env python3.9 conda activate air_quality_env conda install -c conda-forge geopandas pandas numpy requests matplotlib plotly scipy jupyterlab项目结构 创建一个清晰的项目文件夹有助于管理代码和数据。ultrafine_particle_analysis/ │ ├── data/ # 存放原始和加工后的数据 │ ├── raw/ # 原始数据如下载的CSV、Shapefile │ └── processed/ # 清洗处理后的数据 │ ├── notebooks/ # Jupyter Notebook文件用于探索性分析 │ └── 01_data_exploration.ipynb │ ├── scripts/ # 可重用的Python脚本 │ ├── data_download.py │ ├── data_processing.py │ └── visualization.py │ ├── outputs/ # 生成的图表、报告 │ └── figures/ │ └── README.md # 项目说明3. 核心技术与原理拆解在开始实战前我们需要理解几个将用到的核心技术模块及其原理。3.1 地理空间数据Geospatial Data环境与健康数据通常与地理位置强相关。我们主要处理两种类型矢量数据用点、线、面等几何图形表示地理特征。例如国家的边界面、城市的位置点。我们使用geopandas库来处理它扩展了pandas使DataFrame能够存储几何列。栅格数据用网格像素表示连续表面每个像素有一个值。例如全球PM2.5浓度分布图就是一个栅格数据集每个像素值代表该位置的浓度。rasterio库常用于读取这类数据。空间连接是核心操作。例如我们有一个包含各国人口数据的矢量文件和一个全球PM2.5浓度的栅格文件。通过空间连接我们可以计算出每个国家范围内的平均PM2.5暴露水平。3.2 数据获取来源对于此类分析公开数据源至关重要环境数据NASA Socioeconomic Data and Applications Center (SEDAC)、世界银行、欧洲中期天气预报中心ECMWF的CAMS等提供全球空气污染栅格数据。健康与人口数据全球疾病负担研究GBD、世界卫生组织WHO、世界银行提供分国家/地区的死亡率、人口统计等数据。地理边界数据Natural Earth提供免费的高质量全球矢量地图数据适合绘制国家、省份边界。我们将以模拟数据和简化流程进行演示重点展示方法学。在实际研究中需要从上述权威来源下载真实数据集。3.3 归因分析的基本思路研究中“归因死亡数”的估算通常采用人口归因分数的概念。简化公式如下归因死亡数 总死亡数 × 人口归因分数(PAF)而PAF的计算依赖于暴露-反应关系如相对风险RR和人群暴露水平。在我们的模拟中我们将用一个简化的线性关系来演示如何将污染暴露与健康影响在空间上关联起来。3.4 可视化选择静态地图使用geopandasmatplotlib绘制专题地图用颜色深浅表示污染浓度或归因死亡风险。交互式地图使用plotly.express或folium库创建可缩放、可悬停查看数据详情的地图用户体验更佳。统计图表使用matplotlib或plotly绘制散点图暴露 vs. 风险、柱状图分区域统计等。4. 完整实战案例模拟全球PM1健康影响分析下面我们开始一个完整的模拟分析流程。我们将创建模拟的全球各国PM1浓度数据、基线死亡数据然后计算一个简化的“健康影响指数”最后进行可视化。4.1 创建模拟数据首先我们创建一个脚本scripts/data_simulation.py来生成模拟数据。我们使用Natural Earth的国界数据作为基础。# scripts/data_simulation.py import geopandas as gpd import pandas as pd import numpy as np # 1. 加载世界国界矢量数据 (可以从Natural Earth下载这里假设已下载到本地) # 下载地址https://www.naturalearthdata.com/downloads/50m-cultural-vectors/ # 我们使用 scale50m, typecountries 的 shapefile world gpd.read_file(./data/raw/ne_50m_admin_0_countries.shp) # 为了演示我们只保留几个关键属性列和几何列 world world[[ADMIN, ISO_A3, POP_EST, geometry]].rename(columns{ADMIN: country, ISO_A3: iso3, POP_EST: population}) # 2. 为每个国家模拟PM1年均浓度 (单位: μg/m³) # 假设浓度大致在5-50之间并给一些大国/工业国更高的浓度 np.random.seed(42) # 确保结果可复现 # 基于人口和随机因素生成浓度 world[pm1_concentration] np.random.uniform(5, 20, len(world)) (world[population] / 1e9) * 10 world[pm1_concentration] world[pm1_concentration].clip(upper50) # 设置上限 # 3. 模拟基线心肺疾病死亡数 (每十万人年) # 假设基线死亡率与发展和污染水平有一定关系 world[baseline_mortality_rate] np.random.uniform(50, 200, len(world)) (world[pm1_concentration] / 50) * 100 # 计算模拟的死亡人数 死亡率 * 人口 / 100000 world[baseline_deaths] (world[baseline_mortality_rate] * world[population] / 100000).astype(int) # 4. 模拟暴露-反应关系计算归因分数和归因死亡数 # 简化模型假设PM1浓度每增加10 μg/m³相对风险(RR)为1.1 # 人口归因分数 PAF (P * (RR - 1)) / (P * (RR - 1) 1)其中P是暴露人口比例这里简化用浓度代替 # 进一步简化直接定义一个“影响系数” beta 0.005 # 假设每μg/m³ PM1导致死亡风险增加0.5% world[attributable_fraction] beta * world[pm1_concentration] world[attributable_fraction] world[attributable_fraction].clip(upper0.3) # 设置上限30% world[attributable_deaths] (world[baseline_deaths] * world[attributable_fraction]).astype(int) # 5. 保存处理后的数据 world.to_file(./data/processed/world_pm1_health_simulated.gpkg, driverGPKG) world[[country, iso3, population, pm1_concentration, baseline_mortality_rate, baseline_deaths, attributable_fraction, attributable_deaths]].to_csv(./data/processed/country_level_data.csv, indexFalse) print(f模拟数据生成完成。共{len(world)}个国家/地区。) print(f模拟全球归因死亡总数: {world[attributable_deaths].sum():,})运行此脚本前请确保已将Natural Earth的国界Shapefile下载并放置于./data/raw/目录下。你也可以先使用geopandas内置的简单数据集进行测试。# 备用方案使用geopandas自带的简单世界地图仅用于测试国别信息少 # world gpd.read_file(gpd.datasets.get_path(naturalearth_lowres))4.2 数据探索与基本分析创建一个Jupyter Notebook (notebooks/01_data_exploration.ipynb) 来初步查看我们的数据。# notebooks/01_data_exploration.ipynb 中的代码单元格 import geopandas as gpd import pandas as pd import matplotlib.pyplot as plt import plotly.express as px # 加载我们生成的模拟数据 gdf gpd.read_file(../data/processed/world_pm1_health_simulated.gpkg) df pd.read_csv(../data/processed/country_level_data.csv) print(数据概览:) print(gdf.info()) print(\n前5行数据:) print(df.head()) print(f\n关键统计量:) print(df[[pm1_concentration, baseline_mortality_rate, attributable_deaths]].describe()) # 绘制PM1浓度全球分布图 (静态) fig, ax plt.subplots(1, 1, figsize(15, 10)) gdf.plot(columnpm1_concentration, axax, legendTrue, legend_kwds{label: Simulated PM1 Concentration (μg/m³), orientation: horizontal}, cmapOrRd, # 橙红色系表示污染 missing_kwds{color: lightgrey, label: Missing data}, edgecolorblack, linewidth0.2) ax.set_title(Simulated Global Distribution of PM1 Concentration) ax.set_axis_off() plt.tight_layout() plt.savefig(../outputs/figures/pm1_global_map.png, dpi300) plt.show() # 绘制归因死亡数前20的国家 (交互式柱状图) top20 df.nlargest(20, attributable_deaths) fig px.bar(top20, xcountry, yattributable_deaths, titleTop 20 Countries by Simulated Attributable Deaths (PM1), labels{attributable_deaths: Attributable Deaths, country: Country}, colorpm1_concentration, color_continuous_scaleOrRd, hover_data[population, baseline_mortality_rate]) fig.update_layout(xaxis_tickangle-45) fig.write_html(../outputs/figures/top20_attributable_deaths.html) fig.show()4.3 深入分析与空间统计我们可以计算一些区域级别的统计量例如按大洲汇总。# 继续在 notebook 中分析 # 假设我们有一个大洲映射文件这里手动创建一个小型映射实际中需要完整映射 continent_mapping { USA: North America, CAN: North America, MEX: North America, BRA: South America, ARG: South America, GBR: Europe, FRA: Europe, DEU: Europe, RUS: Europe, CHN: Asia, IND: Asia, JPN: Asia, AUS: Oceania, ZAF: Africa, NGA: Africa, EGY: Africa } # 注意这是一个极简化的映射真实分析需要完整的ISO3到大洲的映射。 df[continent] df[iso3].map(continent_mapping) # 过滤出有映射的数据 df_continent df.dropna(subset[continent]) continent_summary df_continent.groupby(continent).agg({ population: sum, pm1_concentration: mean, attributable_deaths: sum }).round(2).reset_index() print(按大洲汇总:) print(continent_summary) # 绘制大洲级别的散点图平均浓度 vs 归因死亡数 fig, ax plt.subplots(figsize(10, 6)) scatter ax.scatter(continent_summary[pm1_concentration], continent_summary[attributable_deaths], scontinent_summary[population]/1e7, # 点大小代表人口 alpha0.6, edgecolorsw, linewidth0.5) ax.set_xlabel(Average Simulated PM1 Concentration (μg/m³)) ax.set_ylabel(Total Simulated Attributable Deaths) ax.set_title(Continental Level: PM1 vs. Health Impact (Bubble size Population)) # 添加国家标签 for i, row in continent_summary.iterrows(): ax.annotate(row[continent], (row[pm1_concentration], row[attributable_deaths]), xytext(5,5), textcoordsoffset points, fontsize9) plt.grid(True, linestyle--, alpha0.5) plt.tight_layout() plt.savefig(../outputs/figures/continent_scatter.png, dpi300) plt.show()4.4 创建交互式全球风险地图使用plotly.express可以轻松创建令人印象深刻的交互式地图。# 在 notebook 中创建交互地图 import plotly.express as px fig px.choropleth(gdf, geojsongdf.geometry, locationsgdf.index, # 使用索引作为位置标识 colorattributable_deaths, hover_namecountry, hover_data[pm1_concentration, population, baseline_deaths], color_continuous_scaleOrRd, titleSimulated Global Map of PM1-Attributable Deaths, labels{attributable_deaths: Attributable Deaths}) # 更新地理范围以适应世界地图 fig.update_geos(projection_typenatural earth, showcoastlinesTrue, coastlinecolorBlack, showlandTrue, landcolorlightgray, showoceanTrue, oceancolorlightblue) fig.update_layout(margin{r:0,t:50,l:0,b:0}) fig.write_html(../outputs/figures/interactive_global_risk_map.html) fig.show()5. 常见问题与排查思路在实际操作中你可能会遇到以下问题问题现象可能原因解决思路geopandas安装失败提示缺少GDAL、Fiona等依赖。地理空间库的C语言依赖未正确安装。强烈推荐使用conda安装conda install -c conda-forge geopandas。如果只能用pip请根据操作系统搜索预编译的wheel文件如GDAL‑‑.whl或从 Christoph Gohlke的网站 下载对应版本的whl文件进行安装。读取Shapefile时出错提示Unable to open...。文件路径错误或Shapefile组件.shp, .shx, .dbf, .prj缺失。检查文件路径是否正确。确保Shapefile的所有必要文件至少.shp, .shx, .dbf都在同一目录下。绘制地图时图形空白或只有轮廓。数据坐标系CRS可能有问题或者几何图形为空。检查gdf.crs。全球地图常用EPSG:4326WGS84。可使用gdf gdf.to_crs(epsg4326)进行转换。检查是否有几何图形为空gdf[gdf.is_empty]。plotly交互地图不显示或报错。可能是在非交互环境如某些脚本运行器中尝试显示。使用fig.write_html(map.html)将图保存为HTML文件然后在浏览器中打开。在Jupyter Notebook中确保已安装ipywidgets并启用合适的前端。模拟数据的结果看起来不真实或数值极端。模拟公式过于简化参数如beta系数设置不合理。记住这是教学模拟。真实研究需要基于流行病学队列研究的暴露-反应函数、更精确的暴露评估和混杂因素调整。调整模拟参数使其更合理。内存不足处理全球高分辨率栅格数据时崩溃。栅格数据文件巨大如全球0.1°网格。1. 使用数据分块读取和处理rasterio的窗口读取。2. 在分析前将数据聚合到更低分辨率。3. 使用云平台或更高配置的机器。空间连接操作速度非常慢。矢量或栅格数据过于详细操作复杂度高。1. 简化几何图形如使用.simplify()。2. 确保在操作前设置了合适的空间索引gdf.sindex。3. 如果可能使用更高效的空间连接方法如gpd.sjoin_nearest用于点数据。6. 最佳实践与工程建议将此类环境健康数据分析项目工程化需要考虑以下方面数据版本控制原始数据、处理脚本和最终结果都应进行版本控制。对于大型原始数据使用DVCData Version Control或将其存储在云对象存储如S3中并在代码中记录数据源的精确版本和下载日期。配置化管理将模拟参数如暴露-反应系数beta、文件路径、API密钥等存储在配置文件如config.yaml或.env文件中而不是硬编码在脚本里。# config.yaml 示例 simulation: beta: 0.005 concentration_range: [5, 50] data_paths: raw_countries: ./data/raw/ne_50m_admin_0_countries.shp processed_output: ./data/processed/simulation_output.gpkg模块化代码如我们所示将数据下载、清洗、模拟、分析和可视化拆分成独立的脚本或函数。这提高了代码的可读性、可测试性和复用性。日志记录在脚本中添加日志记录跟踪数据处理步骤、遇到的警告和错误。使用Python的logging模块。import logging logging.basicConfig(levellogging.INFO, format%(asctime)s - %(levelname)s - %(message)s) logger logging.getLogger(__name__) logger.info(开始模拟数据生成...)测试与验证为关键的计算函数编写单元测试特别是暴露-反应关系计算、空间聚合等。使用pytest框架。验证数据范围如浓度不为负、汇总结果的数量级是否合理。可视化规范色彩选择对于连续型数据如浓度使用顺序色板sequential colormap如viridis,plasma,OrRd。避免使用彩虹色因其可能误导对数据顺序的判断。标注清晰地图务必包含图例、比例尺、指北针必要时和数据来源说明。交互性对于面向报告或演示的结果交互式图表Plotly, Bokeh能提供更多洞察。对于论文出版则需提供高质量的静态图Matplotlib, Seaborn。伦理与沟通明确不确定性任何模型包括我们的模拟都有不确定性。在呈现结果时必须说明这是基于简化假设的模拟并非真实估计。真实研究需要讨论置信区间、模型不确定性等。避免因果误导相关不等于因果。在描述时应使用“关联”、“归因于”在特定模型框架下等谨慎的词汇而非直接断言“导致”。数据溯源始终引用所使用的原始数据来源尊重数据许可协议。7. 总结与扩展方向通过本实战项目我们完整走通了一个简化版的“环境污染物健康影响评估”数据分析流程从地理空间数据基础、模拟数据生成、空间统计到静态与交互式可视化。我们使用了geopandas处理矢量数据用pandas进行统计分析并用matplotlib和plotly实现了多层次的可视化。核心技能点回顾使用geopandas管理和操作地理空间矢量数据。基于地理实体进行空间属性模拟与计算。利用plotly.express快速创建专业的交互式地理图表。构建一个模块化的、可复现的数据分析项目结构。下一步可以深入探索的方向使用真实数据从SEDAC下载真实的PM2.5栅格数据从GBD或WHO下载分国别疾病负担数据进行真实的空间叠加分析。更高分辨率分析将分析尺度从国家层面深入到省/市级别甚至结合卫星遥感与地面监测站数据。引入时间维度分析污染物浓度与健康影响的年度变化趋势制作时间序列动画地图。构建简单预测模型基于历史数据使用机器学习模型如随机森林、梯度提升树预测未来浓度或健康风险。开发Web应用使用Dash或Streamlit框架将整个分析流程打包成一个交互式Web应用让用户可以选择参数、区域并实时查看结果。环境健康数据分析是一个充满挑战且意义重大的交叉领域。掌握这些数据科学和地理信息处理技能不仅能帮助你理解像“超细颗粒物导致过早死亡”这类复杂研究背后的方法论更能让你具备解决实际空间数据分析问题的能力。希望本文提供的代码框架能成为你探索这个领域的起点。
返回列表