You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Matplotlib时间轴散点图中控制标记半径的方法

用Matplotlib绘制带双半径散点图的解决方案

问题背景

我想用Matplotlib基于以下Pandas数据绘制散点图:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'year': [2008, 2014, 2019, 2019.25, 2019.5, 2020],
                   'y': [2, 3, 8, 12, 63, 71],
                   'total_students': [800, 1000, 4000, 4500, 11000, 37000],
                   'male_students': [600, 700, 2000, 2100, 5100, 27000]})

需求

  • X轴设为不均匀间隔的year列,Y轴设为y列
  • 每个数据点叠加两个标记,分别按total_students和male_students的数值缩放半径,方便观众估算男女学生比例

遇到的问题

  1. 使用scatter的s参数时,标记缩放不符合预期:比如s=10的标记半径并非s=5的两倍(注:scatter的s参数对应的是面积而非半径)
  2. 使用plt.Circle添加圆形时,设置aspect='equal'会导致X轴严重压缩;不设置则圆形变成椭圆

尝试过的代码

## 方案1:用scatter的s参数
scaling=0.05
plt.scatter(df.year,df.y,marker="o",zorder=1,c="red",s=df.total_students*scaling)
plt.scatter(df.year,df.y,marker="o",zorder=2,c="blue",s=df.male_students*scaling)
plt.show()


## 方案2:添加单个圆形(不设置equal aspect)
fig=plt.figure()
ax=fig.add_subplot(111)
plt.ylim((-5,100))
plt.xlim((2007,2021))

scaling=0.01
ax.add_patch(plt.Circle((2008,2),800*scaling))
plt.show()


## 方案3:添加单个圆形(设置equal aspect)
fig=plt.figure()
ax=fig.add_subplot(111,aspect="equal")
plt.ylim((-5,100))
plt.xlim((2007,2021))

scaling=0.01
ax.add_patch(plt.Circle((2008,2),800*scaling))
plt.show()

解决方案

解决scatter的s参数缩放问题

scatter的s参数控制的是标记的面积,而非半径。如果要让半径和数值成正比,需要将数值转换为面积(面积=πr²,即面积与半径的平方成正比)。调整后的代码如下:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'year': [2008, 2014, 2019, 2019.25, 2019.5, 2020],
                   'y': [2, 3, 8, 12, 63, 71],
                   'total_students': [800, 1000, 4000, 4500, 11000, 37000],
                   'male_students': [600, 700, 2000, 2100, 5100, 27000]})

# 缩放系数,可根据可视化效果调整
scale_factor = 0.001

# 计算面积:面积与半径平方成正比,直接用数值乘以系数作为面积
total_area = df['total_students'] * scale_factor
male_area = df['male_students'] * scale_factor

# 绘制散点:红色为总学生数(底层),蓝色为男学生数(上层)
plt.scatter(df.year, df.y, marker="o", zorder=1, c="red", s=total_area, alpha=0.5)
plt.scatter(df.year, df.y, marker="o", zorder=2, c="blue", s=male_area, alpha=0.5)

# 添加标签与图例
plt.xlabel('Year')
plt.ylabel('Y Value')
plt.title('Student Count by Year (Total vs Male)')
plt.legend(['Total Students', 'Male Students'])
plt.show()

解决plt.Circle的椭圆/轴压缩问题

如果一定要用plt.Circle绘制,需要根据坐标轴的实际范围计算缩放比例,让圆形在非等比例轴上显示为正圆。核心思路是获取X/Y轴的像素单位比例,调整圆形半径:

import pandas as pd
import matplotlib.pyplot as plt

df = pd.DataFrame({'year': [2008, 2014, 2019, 2019.25, 2019.5, 2020],
                   'y': [2, 3, 8, 12, 63, 71],
                   'total_students': [800, 1000, 4000, 4500, 11000, 37000],
                   'male_students': [600, 700, 2000, 2100, 5100, 27000]})

fig, ax = plt.subplots()
ax.set_xlim(2007, 2021)
ax.set_ylim(-5, 100)

# 获取坐标轴的像素与实际单位的比例
x_left, x_right = ax.get_xlim()
y_bottom, y_top = ax.get_ylim()
x_pixel_per_unit = ax.get_window_extent().width / (x_right - x_left)
y_pixel_per_unit = ax.get_window_extent().height / (y_top - y_bottom)
# 计算半径缩放比例,保证圆形显示为正圆
radius_scale = x_pixel_per_unit / y_pixel_per_unit

scale_factor = 0.0001

# 遍历数据绘制双层圆形
for idx, row in df.iterrows():
    # 总学生数圆形(红色,底层)
    total_radius = row['total_students'] * scale_factor
    total_circle = plt.Circle((row['year'], row['y']), total_radius * radius_scale, color='red', alpha=0.5)
    ax.add_patch(total_circle)
    
    # 男学生数圆形(蓝色,上层)
    male_radius = row['male_students'] * scale_factor
    male_circle = plt.Circle((row['year'], row['y']), male_radius * radius_scale, color='blue', alpha=0.5)
    ax.add_patch(male_circle)

# 添加标签与图例
ax.set_xlabel('Year')
ax.set_ylabel('Y Value')
ax.set_title('Student Count by Year (Total vs Male)')
plt.legend([total_circle, male_circle], ['Total Students', 'Male Students'])
plt.show()

方案选择建议

  • 第一种方案(scatter)更简洁高效,适合大多数常规可视化场景;
  • 第二种方案(plt.Circle)更灵活,支持自定义圆形样式,但需要处理坐标轴比例问题,适合有特殊样式需求的场景。

内容的提问来源于stack exchange,提问作者Jer Sto

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.10 21:25:39