在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的数值缩放半径,方便观众估算男女学生比例
遇到的问题
- 使用
scatter的s参数时,标记缩放不符合预期:比如s=10的标记半径并非s=5的两倍(注:scatter的s参数对应的是面积而非半径) - 使用
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
相关产品推荐
相关产品推荐

