[特殊字符]️ 用 Python 绘制专业风玫瑰图:从气象数据到可视化的全流程指南
在数据可视化的大家族里,有一类图表宛如绽放的玫瑰,用独特的极坐标形态展示方向与数量的分布 —— 这就是风玫瑰图。它不仅是气象学中展示风向风速的经典工具,还广泛应用于城市规划、风能工程、航海航空等领域。今天,我们就来解锁如何用 Python 绘制精美专业的风玫瑰图,让你的数据以最优雅的方式 “旋转绽放”~ 🌹
📊 风玫瑰图:为什么它是方向数据的最佳拍档?
🌍 什么是风玫瑰图?
风玫瑰图(Wind Rose)本质是一种极坐标直方图:
- 角度维度:表示方向(如 0° 为北,90° 为东)
- 径向维度:表示该方向上数据的频率或强度
- 颜色 / 分层:可额外展示第三维度(如风速分段)
🌟 核心优势:
- 方向分布直观化:一眼看出主导风向、风向频率对称性等特征
- 多维度信息整合:同时展示方向、强度(风速)、频率三层信息
- 专业领域必备:气象报告、建筑设计(避开主导风向)、风能选址(寻找高风速区域)等场景的标准图表
🌰 应用场景举例:
- 气象站年度风向统计:帮助机场规划跑道方向
- 城市建筑群设计:确保通风流畅,避免污染堆积
- 风力发电机选址:识别高风速且风向稳定的区域
- 海洋学研究:分析洋流方向与强度分布
🛠️ 代码实现:从 0 到 1 构建风玫瑰图
先奉上完整代码,我们将像拆解精密仪器一样解析每个模块:
import numpy as npimport matplotlib.pyplot as plt# 设置全局字体为宋体,并正确显示负号plt.rcParams['font.sans-serif'] = ['SimSun']plt.rcParams['axes.unicode_minus'] = False# 1. 随机生成数据n = 10000wind_directions = np.random.rand(n) * 360 # 0-360°wind_speeds = np.random.rand(n) * 20 # 0-20 m/s# 2. 定义风向和风速分箱dir_bins = np.arange(0, 361, 30) # 每30°一个扇区speed_bins = [0, 3, 6, 9, 12, 15, 18, np.inf] # 风速分段# 3. 对数据进行分箱计数dir_idx = np.digitize(wind_directions, dir_bins) - 1speed_idx = np.digitize(wind_speeds, speed_bins) - 1num_dirs = len(dir_bins) - 1num_speeds = len(speed_bins)counts = np.zeros((num_speeds, num_dirs))for d, s in zip(dir_idx, speed_idx):if 0 <= d < num_dirs:counts[s, d] += 1# 4. 计算频率(百分比)frequencies = counts / n * 100# 5. 准备极坐标角度angles = np.deg2rad(dir_bins[:-1] + 15) # 扇区中心角# 6. 绘制风玫瑰图fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8, 6))bottom = np.zeros(num_dirs)for i in range(num_speeds):# 构造带上下标和单位的图例标签if i < num_speeds - 1:label = rf'{speed_bins[i]} ≤ $W_s$ < {speed_bins[i+1]} m·s$^{{-1}}$'else:label = rf'$W_s$ ≥ {speed_bins[-2]} m·s$^{{-1}}$'ax.bar(angles, frequencies[i], width=np.deg2rad(30), bottom=bottom, edgecolor='k', label=label)bottom += frequencies[i]# 设置方向和标题ax.set_theta_zero_location('N')ax.set_theta_direction(-1)ax.set_xticks(np.deg2rad([0, 45, 90, 135, 180, 225, 270, 315]))ax.set_xticklabels(['N (0°)', 'NE (45°)', 'SE (135°)', 'S (180°)', 'SW (225°)', 'W (270°)', 'NW (315°)'], fontsize=10)ax.set_title('风玫瑰图\n(随机数据示例)', fontsize=14, pad=20)# 设置径向网格标签格式为“频率 %”ax.yaxis.set_major_formatter(lambda x, pos: f'{x:.1f}%')# 自动寻找最佳位置显示图例,分两列legend = ax.legend(loc='best', fontsize=10, title='风速 (m·s⁻¹)', ncol=2)plt.setp(legend.get_title(), fontsize=11)plt.tight_layout()plt.show()
🔍 代码逐模块解析:玫瑰绽放的秘密
🔧 预处理模块:让中文和符号完美显示
plt.rcParams['font.sans-serif'] = ['SimSun']plt.rcParams['axes.unicode_minus'] = False
- 为什么重要?
风玫瑰图常需标注中文方向(如 “北”“东”)和专业符号(如 m・s⁻¹)。SimSun(宋体)是中文环境下的标准字体,axes.unicode_minus避免负号显示为方块。
- 可选优化:
若需更现代的字体,可改为['Microsoft YaHei'](微软雅黑),或['SimHei'](黑体)。
📊 数据模块:核心替换点与分箱逻辑
# 1. 随机生成数据(用户需替换为实际数据)n = 10000wind_directions = np.random.rand(n) * 360 # 0-360°wind_speeds = np.random.rand(n) * 20 # 0-20 m/s
- ✅ 替换自己数据的关键位置!
这部分生成的是模拟数据,实际使用时需替换为真实风向风速数据,方法如下:
-
- 从 CSV 文件读取:
data = np.loadtxt('wind_data.csv', delimiter=',') # 假设文件格式为[风向, 风速]wind_directions = data[:, 0]wind_speeds = data[:, 1]
-
- 从 pandas DataFrame 获取:
import pandas as pddf = pd.read_excel('气象数据.xlsx')wind_directions = df['风向(°)'].valueswind_speeds = df['风速(m/s)'].values
-
- 手动输入小规模数据:
wind_directions = np.array([225, 180, 315, 45, 90, ...]) # 风向数组wind_speeds = np.array([5.2, 3.7, 8.1, 2.3, 6.5, ...]) # 对应风速数组# 2. 定义风向和风速分箱(可根据需求调整)dir_bins = np.arange(0, 361, 30) # 每30°一个扇区speed_bins = [0, 3, 6, 9, 12, 15, 18, np.inf] # 风速分段
- 分箱策略解析:
-
- dir_bins将 360° 划分为 12 个扇区(30°/ 扇区),符合气象学标准分法;若需更精细,可设为 22.5°(16 个扇区)。
-
- speed_bins按风速强度分段,单位为 m/s,分段点可根据数据特征调整:
-
-
- 微风:0-3m/s,轻风:3-6m/s,中风:6-9m/s...
-
-
-
- 若数据风速普遍较高,可改为 [0, 5, 10, 15, 20, np.inf]
-
🧮 统计模块:从原始数据到频率矩阵
# 3. 对数据进行分箱计数dir_idx = np.digitize(wind_directions, dir_bins) - 1speed_idx = np.digitize(wind_speeds, speed_bins) - 1num_dirs = len(dir_bins) - 1num_speeds = len(speed_bins)counts = np.zeros((num_speeds, num_dirs))for d, s in zip(dir_idx, speed_idx):if 0 <= d < num_dirs:counts[s, d] += 1# 4. 计算频率(百分比)frequencies = counts / n * 100
- 核心统计逻辑:
-
- np.digitize将每个风向 / 风速数据映射到对应分箱的索引
-
- counts矩阵记录每个风速分段在各风向扇区的出现次数
-
- frequencies转换为百分比频率,便于直观比较
🎨 绘制模块:极坐标下的玫瑰绽放
# 5. 准备极坐标角度angles = np.deg2rad(dir_bins[:-1] + 15) # 扇区中心角(如30°扇区的中心为15°)
- 角度处理技巧:
dir_bins[:-1] + 15计算每个扇区的中心角度(如 0-30° 扇区的中心为 15°),np.deg2rad转换为弧度制(极坐标所需单位)。
# 6. 绘制风玫瑰图fig, ax = plt.subplots(subplot_kw={'projection': 'polar'}, figsize=(8, 6))bottom = np.zeros(num_dirs)for i in range(num_speeds):# 构造带上下标和单位的图例标签if i < num_speeds - 1:label = rf'{speed_bins[i]} ≤ $W_s$ < {speed_bins[i+1]} m·s$^{{-1}}$'else:label = rf'$W_s$ ≥ {speed_bins[-2]} m·s$^{{-1}}$'ax.bar(angles, frequencies[i], width=np.deg2rad(30), bottom=bottom, edgecolor='k', label=label)bottom += frequencies[i]
- 3D 堆叠条形的极坐标实现:
-
- projection='polar'激活极坐标模式,ax.bar在此模式下绘制扇形区域
-
- width=np.deg2rad(30)设置每个扇区的角度宽度(30°)
-
- bottom=bottom实现堆叠效果,每层条形基于下层顶部绘制
-
- label使用 LaTeX 语法渲染专业公式(如 m・s⁻¹),rf''确保特殊字符正确显示
🌌 美化模块:专业图表的细节把控
# 设置方向和标题ax.set_theta_zero_location('N') # 0°指向北ax.set_theta_direction(-1) # 角度顺时针增加(符合气象学惯例)ax.set_xticks(np.deg2rad([0, 45, 90, 135, 180, 225, 270, 315]))ax.set_xticklabels(['N (0°)', 'NE (45°)', 'E (90°)', 'SE (135°)', 'S (180°)', 'SW (225°)', 'W (270°)', 'NW (315°)'], fontsize=10)# 设置径向网格标签格式ax.yaxis.set_major_formatter(lambda x, pos: f'{x:.1f}%')# 优化图例legend = ax.legend(loc='best', fontsize=10, title='风速 (m·s⁻¹)', ncol=2)
- 方向设置的专业细节:
-
- set_theta_zero_location('N')将 0° 设为正北方向(符合气象学标准)
-
- set_theta_direction(-1)使角度顺时针增加(传统风向图方向)
- 标签优化:
径向网格显示为百分比(x:.1f%),图例分两列(ncol=2)避免过长,标题使用专业单位(m・s⁻¹)
🎨 高级定制:让玫瑰图独一无二
🌈 颜色方案优化
默认颜色可能不够专业,可自定义为气象学常用配色:
# 定义风速分段颜色(从低到高渐变色)speed_colors = ['#a6cee3', '#1f78b4', '#b2df8a', '#33a02c', '#fb9a99', '#e31a1c', '#fdbf6f']for i in range(num_speeds):ax.bar(..., color=speed_colors[i], ...)
📍 自定义方向标签
若需更详细的方向标注:
# 16方向标注(气象学标准)directions = ['N', 'NNE', 'NE', 'ENE', 'E', 'ESE', 'SE', 'SSE','S', 'SSW', 'SW', 'WSW', 'W', 'WNW', 'NW', 'NNW']angles_16 = np.deg2rad(np.arange(0, 360, 22.5)) # 22.5°间隔ax.set_xticks(angles_16)ax.set_xticklabels(directions, fontsize=8)
💡 透明度与阴影效果
增强多层堆叠的视觉层次感:
ax.bar(..., alpha=0.8, zorder=3) # 增加透明度,zorder控制图层顺序ax.grid(True, linestyle='--', alpha=0.5) # 添加半透明网格线
📊 添加数据标签
在每个扇形上显示频率数值:
for i in range(num_speeds):for d in range(num_dirs):if frequencies[i, d] > 0: # 只标注非零值ax.text(angles[d], bottom[d] - frequencies[i, d]/2, # 垂直居中f'{frequencies[i, d]:.1f}%',ha='center', va='center', fontsize=7)
🌍 实战案例:风玫瑰图的真实应用
🌉 案例 1:城市建筑规划
某沿海城市规划新区,需分析主导风向以优化建筑布局:
# 读取实际气象站数据(假设数据已按[风向, 风速]存储)real_data = np.loadtxt('coastal_wind_data.csv', delimiter=',')wind_directions = real_data[:, 0]wind_speeds = real_data[:, 1]# 调整分箱策略,关注海风特征dir_bins = np.arange(0, 361, 22.5) # 16方向更精细speed_bins = [0, 2, 4, 6, 8, 10, 12, np.inf] # 针对沿海微风较多的特点
成果: 风玫瑰图显示夏季主导东南风(SE),冬季主导西北风(NW),建筑布局应沿主导风向排列,确保通风走廊。
🏭 案例 2:风力发电场选址
某能源公司评估风能资源,需识别高风速且风向稳定区域:
# 计算各方向风速加权平均值(更关注风速强度)weighted_speeds = np.zeros(num_dirs)dir_counts = np.zeros(num_dirs)for d, dir in enumerate(dir_idx):if 0 <= dir < num_dirs:weighted_speeds[dir] += wind_speeds[d]dir_counts[dir] += 1avg_speeds = weighted_speeds / dir_counts # 各方向平均风速# 绘制风速玫瑰图(径向为平均风速,非频率)ax.bar(angles, avg_speeds, width=np.deg2rad(30), edgecolor='k', color='skyblue')
成果: 发现某方向平均风速达 8.5m/s 且频率占比 25%,是理想的风机安装方向。
🚢 案例 3:航海路线规划
某航运公司优化跨洋航线,需避开强风区域:
# 定义强风阈值(如≥12m/s为强风)strong_wind = wind_speeds >= 12strong_dirs = wind_directions[strong_wind]# 单独绘制强风方向玫瑰图strong_dir_bins = np.arange(0, 361, 45) # 更大扇区突出主导方向strong_dir_idx = np.digitize(strong_dirs, strong_dir_bins) - 1strong_counts = np.bincount(strong_dir_idx, minlength=len(strong_dir_bins)-1)strong_freq = strong_counts / len(strong_dirs) * 100
成果: 显示某航线 6-8 月西南方向强风频率达 35%,建议调整航线避开该方向。
⚠️ 避坑指南:风玫瑰图绘制常见问题
1. 数据量不足导致统计偏差
- 现象: 样本数 n<100 时,玫瑰图形态波动大,缺乏代表性。
- 解决方案:
-
- 确保 n≥500,理想 n≥1000
-
- 对短周期数据(如月度),可合并多年数据
-
- 使用平滑处理:scipy.ndimage.gaussian_filter(frequencies, sigma=0.5)
2. 分箱策略不合理
- 现象: 风向扇区过多导致图表杂乱,或风速分段不均掩盖数据特征。
- 解决方案:
-
- 常规场景使用 12 扇区(30°)或 16 扇区(22.5°)
-
- 风速分段建议 5-8 段,每段区间等距或按风速等级(如蒲福风级)
-
- 可通过plt.hist(wind_speeds, bins=10)先观察风速分布再确定分段
3. 极坐标视角误导
- 现象: 径向刻度非线性导致视觉偏差(如外圈区域实际面积更大)。
- 解决方案:
-
- 明确标注径向刻度(如每 10% 一格)
-
- 避免使用 3D 透视效果(ax.set_box_aspect([1,1,1]保持等比例)
-
- 对重要数据添加数值标签(见前文代码)
4. 中文显示异常
- 现象: 方向标签显示为方块或乱码。
- 解决方案:
-
- 确认字体文件存在:fc-list | grep SimSun(Linux/Mac)
-
- Windows 系统可指定['Microsoft YaHei']
-
- Jupyter Notebook 中可添加%matplotlib inline后重启内核
🌟 进阶玩法:让风玫瑰图动起来
🎥 动态时间序列风玫瑰
绘制不同季节的风玫瑰图动画:
from matplotlib.animation import FuncAnimation# 假设data按季节分组:[春季, 夏季, 秋季, 冬季]season_data = [spring_data, summer_data, autumn_data, winter_data]season_names = ['春季', '夏季', '秋季', '冬季']def update(season_idx):ax.clear()# 绘制当前季节风玫瑰图(代码略,同主流程)ax.set_title(f'风玫瑰图 - {season_names[season_idx]}', fontsize=14)return ax,ani = FuncAnimation(fig, update, frames=4, interval=1000, repeat=True)ani.save('season_wind_rose.gif', writer='pillow', dpi=100)
📊 与其他图表联动
创建风玫瑰图与风速分布图的组合仪表盘:
fig, (ax_polar, ax_hist) = plt.subplots(1, 2, figsize=(12, 6),subplot_kw={'projection': 'polar', 'aspect': 'auto'})# 左侧绘制风玫瑰图# 右侧绘制风速直方图ax_hist.hist(wind_speeds, bins=speed_bins, color='skyblue', edgecolor='k')ax_hist.set_xlabel('风速 (m/s)')ax_hist.set_ylabel('频次')
📈 风向风速相关性分析
在玫瑰图上叠加风速均值等值线:
# 计算各方向平均风速avg_speed_per_dir = np.zeros(num_dirs)dir_counts = np.zeros(num_dirs)for d, s in zip(dir_idx, speed_idx):if 0 <= d < num_dirs:dir_counts[d] += 1avg_speed_per_dir[d] += speed_bins[s] # 用分段中值代表该段风速avg_speed_per_dir /= dir_counts# 绘制风速等值线theta = np.linspace(0, 2*np.pi, 100)r = np.interp(theta, angles, avg_speed_per_dir)ax.plot(theta, r, 'r--', linewidth=2, label='平均风速')ax.legend()
🌼 结语:让数据在极坐标中绽放
风玫瑰图不仅是一种可视化工具,更是理解方向数据的思维框架 —— 它将抽象的角度与数值转化为具象的玫瑰形态,让隐藏在数据中的方向规律一目了然。通过今天的教程,你已经掌握了从数据处理到图表美化的全流程,现在只差替换上你自己的专业数据啦!
关键替换步骤回顾:
- 替换wind_directions和wind_speeds为实际风向风速数据
- 根据数据特征调整dir_bins和speed_bins分箱策略
- 修改坐标轴标签和标题,匹配实际应用场景
无论是气象分析、工程设计还是科研研究,风玫瑰图都能为你的数据展示增添专业色彩~🌪️🌹
如果在实践中遇到问题,或者想分享你的创意风玫瑰图案例,欢迎在评论区留言!让我们一起用代码绘制数据世界的美丽玫瑰~😊