暑假伊始,Coldrain 参加了学校举办的数模集训,集训的过程中,遇到了需要展示 59 个特征与 15 个指标之间的相关性的情况,在常用的图表不大合适的情况下,学到了一些厉害的图表,但是似乎千篇一律都是用 R 语言、MATLAB 和 SPSS 绘制,Python 代码少之又少,遂作此篇,以为模板。

题目地址:
2012 年全国大学生数学建模竞赛 A 题

网络上找到的环形热力图 be like:

在这里插入图片描述

这种图片究竟是如何绘制出来的呢?

接下来,和小生用 Python 手搓一个吧喵 🐱


1. 嵌套饼图(Nested Pie Charts)

一开始,Coldrain 并无一点头绪,于是在 matplotlib 官网上提供的千奇百怪的图表样例里翻找,找到了一个叫做 Nested Pie Charts 的东西,翻译过来叫做嵌套饼图,官网给的嵌套饼图长这个样子:

在这里插入图片描述

官网给出的第一份案例代码如下:

import numpy as np
import matplotlib.pyplot as pltfig, ax = plt.subplots()size = 0.3
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])tab20c = plt.color_sequences["tab20c"]
outer_colors = [tab20c[i] for i in [0, 4, 8]]
inner_colors = [tab20c[i] for i in [1, 2, 5, 6, 9, 10]]ax.pie(vals.sum(axis=1), radius=1, colors=outer_colors,wedgeprops=dict(width=size, edgecolor='w'))ax.pie(vals.flatten(), radius=1-size, colors=inner_colors,wedgeprops=dict(width=size, edgecolor='w'))ax.set(aspect="equal", title='Pie plot with `ax.pie`')
plt.show()

但是!采用这种方法实现嵌套饼图的效率虽然很高,但是灵活性不高,不便于实现精细设计,于是官方又给出了下面这个新的实现代码:

import numpy as np
import matplotlib.pyplot as pltfig, ax = plt.subplots(subplot_kw=dict(projection="polar"))size = 0.3
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])
# Normalize vals to 2 pi
valsnorm = vals/np.sum(vals)*2*np.pi
# Obtain the ordinates of the bar edges
valsleft = np.cumsum(np.append(0, valsnorm.flatten()[:-1])).reshape(vals.shape)cmap = plt.colormaps["tab20c"]
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap([1, 2, 5, 6, 9, 10])ax.bar(x=valsleft[:, 0],width=valsnorm.sum(axis=1), bottom=1-size, height=size,color=outer_colors, edgecolor='w', linewidth=1, align="edge")ax.bar(x=valsleft.flatten(),width=valsnorm.flatten(), bottom=1-2*size, height=size,color=inner_colors, edgecolor='w', linewidth=1, align="edge")ax.set(title="Pie plot with `ax.bar` and polar coordinates")
ax.set_axis_off()
plt.show()

现在,我们认真读一下上面的这段代码。

⚠️ Coldrain 觉得有必要认真读一下。

>> 1.1 创建极坐标图
fig, ax = plt.subplots(subplot_kw=dict(projection="polar"))
  • 首先创建一个子图(fig, ax),并指定为极坐标投影 projection="polar"
  • 所有角度以弧度制表示,从 0 开始,逆时针增加。
>> 1.2 设置参数和数据
size = 0.3
vals = np.array([[60., 32.], [37., 40.], [29., 10.]])
  • size:每一个圆环的厚度(即扇形外圈半径长度减去内圈半径长度)
  • vals:二维数组,每一行表示外圈的一个扇区,每行中两个数字表示该扇区内部的两个子分类(用于内圈)

❓ 看到这个 vals 的形状和对应的饼图形状,你想到了什么?没错,似乎可以通过改变 vals 的维度来实现环形热力图的形状!

>> 1.3 角度归一化
valsnorm = vals / np.sum(vals) * 2 * np.pi
  • 先将 vals 所有数值加起来,然后把每个值按比例映射到 [0, 2π2\pi2π] 的弧度范围(也就是一整圈的弧度)
  • 得到每个子块对应的角度宽度
>> 1.4 计算起始角度(边界)
valsleft = np.cumsum(np.append(0, valsnorm.flatten()[:-1])).reshape(vals.shape)
  • valsnorm.flatten() 把二维数组拉成一维
  • np.cumsum(...) 计算角度的累积和,也就是每个条形的起始角度
  • reshape(vals.shape) 把它还原为原来二维结构
>> 1.5 设置颜色
cmap = plt.colormaps["tab20c"]
outer_colors = cmap(np.arange(3)*4)
inner_colors = cmap([1, 2, 5, 6, 9, 10])
  • 使用 tab20c 调色板。
  • outer_colors:每个外圈段使用不同颜色(间隔选择索引 0、4、8)。
  • inner_colors:内圈颜色从调色板中挑选不同颜色索引。

🎨 关于 tab20c 调色板:

tab20cmatplotlib 中内置的分类调色板,共有 20 种颜色,包括 5 个颜色组(每组 4 个颜色)。其构成如下:

颜色组索引范围颜色说明
组 10-3蓝绿色系(蓝、浅蓝、灰蓝等)
组 24-7橙色系(橙、浅橙、灰橙等)
组 38-11红紫色系(红、粉红、灰红等)
组 412-15绿色系(绿、浅绿、灰绿等)
组 516-19灰紫色系(紫灰、浅紫等)
>> 1.6 绘制外圈(大类)
ax.bar(x=valsleft[:, 0],width=valsnorm.sum(axis=1), bottom=1-size, height=size,color=outer_colors, edgecolor='w', linewidth=1, align="edge")
  • 每个外圈段的起始角度valsleft[:, 0]
  • width=valsnorm.sum(axis=1):每个大类的角度宽度是该行两个值之和。
  • bottom=1-size:外圈从半径 0.7 开始(1-0.3=0.7)
  • height=size:厚度是 0.3
  • align="edge":从 x 角度开始绘制
>> 1.7 绘制内圈(子类)
ax.bar(x=valsleft.flatten(),width=valsnorm.flatten(), bottom=1-2*size, height=size,color=inner_colors, edgecolor='w', linewidth=1, align="edge")
  • 每个内圈段的起始角度为展平后的 valsleft
  • 每段的角度宽度来自展平后的 valsnorm
  • bottom=1-2*size:从半径 0.4 开始
  • 用不同颜色表示不同子类
>> 1.8 清理图像
ax.set(title="Pie plot with `ax.bar` and polar coordinates")
ax.set_axis_off()
  • 设置标题
  • 去掉极坐标轴的刻度、边框等

2. 着手绘制环形热力图

由于数据采用的是小生本地的数据,所以这部分代码应该只能用作学习、讲解,如果你想要开袋即食的函数,可以根据下面的代码进行调整(

具体讲解咱们以注释的形式写在代码块里喵:

'''
Part1 导入库
'''
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.cm import get_cmap, ScalarMappable
import pandas as pd
from matplotlib.colors import Normalize, mcolors # 用于标准化颜色映射和自定义 colormap
from mpl_toolkits.axes_grid1.inset_locator import inset_axes   # 在极坐标图中嵌入色条
import matplotlib.font_manager as fm      # 支持中文字体加载'''
Part2 读取数据   
这里小生用的是自己的数据,如果需要参考的话,请务必替
换成自己的数据
(其实这部分不需要关注,直接跳转到 Part3 即可)
'''
red_results = pd.read_excel('red_results.xlsx')
df_doc2_1 = pd.read_excel('doc2.xls')
unprocessed_categories = df_doc2_1.columns.tolist()
categories = [item for item in unprocessed_categories if 'Unnamed' not in item][1:]
color = categories.pop(-1)
for i in ['L', 'a', 'b', 'H', 'c']:categories.append(color+i)df_doc2_2 = pd.read_excel('doc2.xls', sheet_name='葡萄酒')unprocessed_categories = df_doc2_2.columns.tolist()
categories_red = [item for item in unprocessed_categories if 'Unnamed' not in item][1:]
color = categories_red.pop(-1)
for i in ['L', 'a', 'b', 'H', 'c']:categories_red.append(color+i)categories_white = deepcopy(categories_red[1:])unprocessed_grape_features = red_results.iloc[:,0].to_list()
grape_features = []
for i in unprocessed_grape_features:if i not in grape_features:grape_features.append(i)unprocessed_wine_features = red_results.iloc[:,1].to_list()
wine_features = []
for i in unprocessed_wine_features:if i not in wine_features:wine_features.append(i)'''
Part3 将相关系数填入 59*15 大小的列表中
(这里只需要生成你自己的数据即可)
'''
feature_value_map = [[0.0 for i in range(59)] for j in range(15)]for i in range(145):line = red_results.iloc[i,:].to_list()# print(line)n_col = categories.index(line[0])n_row = categories_red.index(line[1])# print(n_row, n_col)feature_value_map[n_row][n_col] = line[2]'''
Part4 图片绘制
'''
def truncate_colormap(cmap, minval=0.2, maxval=0.8, n=256):"""这个函数用来实现 cmap 的截取,具体 cmap 操作可参考matplotlib 官网"""new_cmap = mcolors.LinearSegmentedColormap.from_list(f'trunc({cmap.name}, {minval:.2f}, {maxval:.2f})',cmap(np.linspace(minval, maxval, n)))return new_cmap# 手动添加中文字体(请根据实际路径更改)
font_path = '/usr/share/fonts/noto-cjk/NotoSansCJK-Medium.ttc'
my_font = fm.FontProperties(fname=font_path)# 参数设置
num_rings = 15       # 行数(饼图圈数)
num_segments = 59    # 列数(每圈有多少小格)
ring_width = 0.5 / num_rings  # 控制总半径范围在 [0.5, 1]
angle_width = (1.75 * np.pi) / num_segments      # 这里如果调成 2*np.pi 的话是一个完整的圆
angles = np.linspace(0.5 * np.pi, 2.25 * np.pi, num_segments, endpoint=False)       # 设置起始角度和结束角度# 采用蓝-白-红渐变的配色(请根据个人喜好自行调整)
cmap = get_cmap("RdBu").reversed()      # 这里对 cmap 进行取反操作
cmap = truncate_colormap(cmap, minval=0.1, maxval=0.9)
norm = Normalize(vmin=-1, vmax=1)# 创建画布
fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(polar=True))
ax.set_axis_off()    # 将坐标轴隐藏
ax.set_title("酿酒红葡萄理化指标与红葡萄酒理化指标之间的关系热力图", fontsize=14, fontproperties=my_font)       # 设置标题# 绘制所有圈(从外向内)
for i in range(num_rings):bottom = 0.8 - (i + 1) * ring_width   # 每一圈的起始位置height = ring_widthfor j in range(num_segments):color = cmap((feature_value_map[i][j] + 1)/2)   # 颜色映射theta = angles[j]   # 当前段(单元格)中心角度radius = bottom + height / 2      # 填入数值的位置(方格上界和下界中间的位置)# 对每个单元格执行操作ax.bar(x=angles[j],    # 中心角度width=angle_width,     # 扇形的角度宽度bottom=bottom,  # 环的底部半径height=height,  # 环的厚度color=color,    # 采用的颜色edgecolor="black",     # 设置分割线颜色linewidth=0.3,  # 设置分割线宽度align="edge"    # 对齐方式(从角度边缘开始))if np.abs(feature_value_map[i][j]) > 0:ax.text(theta + angle_width / 2,  # 移到扇形中间radius,f"{feature_value_map[i][j]:.2f}",            # 保留两位小数ha='center', va='center',   # 水平/垂直居中(horizontal/verticle)fontsize=4.5,color='black' if abs(feature_value_map[i][j]) < 0.7 else 'white',  # 自适应颜色rotation=0  # 不旋转文本)# 在最外圈插入指标名称
label_radius = (0.3 + num_rings * ring_width + 0.02)  # 最外圈外一点点
indicator_labels = [f'HG{i}' for i in range(1, num_segments + 1)]
for j in range(num_segments):theta = angles[j] + angle_width / 2  # 扇形中间角度label = indicator_labels[j]ax.text(theta,label_radius,label,fontsize=8,ha='center',va='center',rotation=np.degrees(theta - np.pi / 2),rotation_mode='anchor')# 在圆环缺口处添加文字
theta_gap = np.deg2rad(90)  # 可调
ring_width = 0.5 / num_rings
for i in range(num_rings):radius = 0.3 + i * ring_width + ring_width / 2ax.text(theta_gap,radius,f"  HW{15-i}",              # 或者你自定义的 label[i]fontsize=6.5,ha='left',               # 靠左对齐,文字朝外va='center',rotation=0,rotation_mode='anchor',color='black')# 在极坐标图的中间嵌入一个小长条色带(纵向)
cbar_ax = inset_axes(ax,width="4%",   # 相对于父图宽度height="25%",  # 相对于父图高度loc='center'   # 放在图中心
)sm = ScalarMappable(cmap=cmap, norm=norm)
sm.set_array([])
cbar = plt.colorbar(sm, cax=cbar_ax, orientation='vertical')
cbar.set_label("相关系数 R", fontsize=10, fontproperties=my_font)# 保存图片
plt.tight_layout()
# plt.show()
plt.savefig("my_figure2.png", dpi=300, bbox_inches='tight')

运行之后,得到的效果图如下所示:
在这里插入图片描述

效果图的配色等设计可能有欠缺的地方,但由于时间紧迫,并没有太多时间用于色彩、样式设计…


3. 参考

[1] matplotlib 官网嵌套饼图教学(Nested pie charts)

[2] matplotlib 官网 colormaps 一览

[3] Coldrain 最初遇到的环形热力图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95303.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95303.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/95303.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【序列晋升】27 Spring Cloud Sleuth给分布式系统装上透视镜

Spring Cloud Sleuth作为微服务架构中的核心监控组件&#xff0c;通过轻量级的无侵入式跟踪机制&#xff0c;解决了分布式系统中请求路径复杂、问题定位困难的痛点。它自动为每个服务请求创建唯一的Trace ID&#xff0c;并为每个服务间调用生成Span ID&#xff0c;形成完整的调…

Linux(2)|入门的开始:Linux基本指令(2)

一、基本指令介绍 回顾上篇博客Linux(1)|入门的开始&#xff1a;Linux基本指令-CSDN博客&#xff0c;我们已经学习了mkdir目录的创建&#xff0c;touch普通文件的创建&#xff0c;光有创建肯定是不行的&#xff0c;接下来就介绍我们的删除指令 1、rmdir指令&&rm指令 …

sv中forever如何结束

在 SystemVerilog 中&#xff0c;forever 循环本身无法自我结束。它的设计初衷就是创建一个永不终止的循环。 因此&#xff0c;要结束一个 forever 循环&#xff0c;必须从外部强制中断它。主要有以下两种方法&#xff1a;1. 使用 disable 语句&#xff08;最常用和推荐的方法&…

关于熵减 - 从法拉第圆盘到SEG

我们清楚的知道法拉第圆盘发电机的原理。当导线切割磁感线的时候&#xff0c;会产生电流&#xff0c;当然电流产生需要的是电动势&#xff0c;也就是&#xff0c;这里写 不写 &#xff0c;避免和电场强度混淆。根据上面的分析&#xff0c;我们知道磁场强度特斯拉 的单位&#x…

【机器学习】实战:市场增长点分析挖掘项目

在电商行业激烈竞争的背景下&#xff0c;精准挖掘市场增长点是企业保持竞争力的关键。本文基于拜耳官方旗舰店驱虫剂市场分析项目&#xff0c;先对原文核心内容进行梳理与解读&#xff0c;再续写关键的竞争分析模块&#xff0c;形成完整的市场增长点挖掘闭环&#xff0c;为企业…

【Day 18】21.合并两个有序链表 2.两数相加

文章目录21.合并两个有序链表题目&#xff1a;思路&#xff1a;迭代代码实现&#xff08;Go&#xff09;&#xff1a;2.两数相加题目&#xff1a;思路&#xff1a;代码实现&#xff08;Go&#xff09;&#xff1a;21.合并两个有序链表 题目&#xff1a; 将两个升序链表合并为…

Vue 3 WebSocket通信方案:从原理到实践

Vue 3 WebSocket通信方案&#xff1a;从原理到实践 在现代Web应用开发中&#xff0c;实时通信已成为许多应用的核心需求。从即时聊天到实时数据更新&#xff0c;用户对应用响应速度的期望越来越高。本文将深入剖析一个Vue 3环境下的WebSocket通信方案&#xff0c;包括基础封装与…

Windows 电源管理和 Shutdown 命令详解

一、Windows 电源管理概述 Windows 操作系统通过其内置的电源管理框架&#xff0c;为用户提供了多种电源状态和配置选项&#xff0c;以在性能、能耗和数据安全之间找到最佳平衡点。以下是 Windows 系统中常见的电源状态及其特点&#xff1a; 1. 睡眠&#xff08;Sleep&#xff…

Selenium WebUI 自动化“避坑”指南——从常用 API 到 10 大高频问题

目录 一、为什么 90% 的 UI 自动化脚本活不过 3 个月&#xff1f; 二、Selenium必会 API 速查 三、实践 四、10 大高频异常“症状 → 病因 → 处方” 五、可复用的工具函数 六、面试高频追问&#xff08;附标准答案&#xff09; 一、为什么 90% 的 UI 自动化脚本活不过 …

【微信小程序】微信小程序基于双token的API请求封装与无感刷新实现方案

文章目录前言一、设计思路二、执行流程三、核心模块3.1 全局配置3.2 request封装3.2.1 request方法配置参数3.2.2 请求预处理3.2.3 核心请求流程3.3 刷新accessToken3.4 辅助方法四、api封装示例总结前言 现代前后端分离的模式中&#xff0c;一般都是采用token的方式实现API的…

基于单片机醉酒驾驶检测系统/酒精检测/防疲劳驾驶设计

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 该设计基于单片机开发&#xff0c;旨在通过实时检测驾驶员酒精浓度&#xff0c;预防酒后驾驶行为…

第6章:垃圾回收分析与调优

1. 垃圾回收基础 1.1 Java 垃圾回收概述 垃圾回收&#xff08;Garbage Collection&#xff0c;GC&#xff09;是 Java 虚拟机自动内存管理的核心机制。理解 GC 的工作原理对于 Java 应用性能调优至关重要。 1.1.1 垃圾回收的目标 自动内存管理&#xff1a;无需手动释放内存防止…

ROS2核心模块-动作通信、参数服务

动作通信 机器人导航到某个目标点,此过程需要一个节点A发布目标信息&#xff0c;然后一个节点B接收到请求并控制移动&#xff0c;最终响应目标达成状态信息。 乍一看&#xff0c;这好像是服务通信实现&#xff0c;因为需求中要A发送目标&#xff0c;B执行并返回结果&#xff0c…

word文档封面中文件编号等标题和内容无法对齐

问题 word文档封面中文件编号等标题和内容无法对齐&#xff0c;因为标题使用的是底纹不是文件内容。 解决办法 字体大小、行距两者配合就可以解决。

163起融资,梅卡曼德融资额夺冠,钉钉、百度智能云10周年,汉桑科技IPO| 2025年8月人工智能投融资观察 · 极新月报

“ 二级的活跃会传导到一级吗&#xff1f;”文&#xff5c;云舒&小鱼编辑 | 小白出品&#xff5c;极新8月重点关注&#xff1a;1、八月人工智能领域投融资事件163起&#xff0c;披露金额76.8亿人民币。2、亿级人民币以上金额的投资事件共20起 。3、八月人工智能领域发生一起…

微信小程序预览和分享文件

预览文档previewFile(val) { let item val.currentTarget.dataset.item wx.downloadFile({url: item.filePath, // 替换为实际的文件地址success: function (res) {let filePath ${wx.env.USER_DATA_PATH}/${item.fileName}|| res.tempFilePath //查看的文件名wx.openDocumen…

开源 C++ QT Widget 开发(十二)图表--环境监测表盘

文章的目的为了记录使用C 进行QT Widget 开发学习的经历。临时学习&#xff0c;完成app的开发。开发流程和要点有些记忆模糊&#xff0c;赶紧记录&#xff0c;防止忘记。 相关链接&#xff1a; 开源 C QT Widget 开发&#xff08;一&#xff09;工程文件结构-CSDN博客 开源…

ARMv8架构01 - ARM64架构寄存器基础

一 、ARM64架构基础 1 ARMv8 A 架构介绍 ARMv8 - A是ARM公司发布的第一代支持64位处理器的指令集和架构。它在扩充64位寄存器的同时提供对上一代架构指令集的兼容&#xff0c;因而能同时提供运行 32位 和 64位应用程序的执行环境。 超大物理地址空间&#xff08;large Physical…

flutter专栏--深入剖析你的第一个flutter应用

使用fvm管理flutter版本 如果你有使用多版本flutter的需求&#xff0c;那么fvm将会给你提供较大的帮助。下面我列举一下mac flutter3.35.2的版本的操作命令&#xff0c;完成之后&#xff0c;你将可以随意切换flutter版本 # 下载fvm相关的依赖 brew tap leoafarias/fvm brew …

MongoDB 聚合查询超时:索引优化与分片策略的踩坑记录

人们眼中的天才之所以卓越非凡&#xff0c;并非天资超人一等而是付出了持续不断的努力。1万小时的锤炼是任何人从平凡变成超凡的必要条件。———— 马尔科姆格拉德威尔 &#x1f31f; Hello&#xff0c;我是Xxtaoaooo&#xff01; &#x1f308; “代码是逻辑的诗篇&#xff…