news 2026/10/14 20:20:31

Astropy 与 Matplotlib 深度集成:Quantity 带单位数据与 Time 时间序列绘图实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Astropy 与 Matplotlib 深度集成:Quantity 带单位数据与 Time 时间序列绘图实战
  • 科学计算
  • 科研
  • 数据分析

【免费下载链接】astropy

Astronomy and astrophysics core library

项目地址:https://gitcode.com/gh_mirrors/as/astropy
点击查看免费下载

本文以 Astropy 官方文档 docs/visualization/matplotlib_integration.rst 为核心骨架,系统讲解astropy.visualization为 Matplotlib 提供的两类核心绘图增强:quantity_support()(带单位 Quantity 绘图)与time_support()(天文时间 Time 绘图)。读完本文,你将掌握如何让 Matplotlib 自动识别并转换物理单位、自动格式化天文时间轴刻度,以及如何结合源码理解其底层转换器(converter)与刻度定位/格式化机制,直接用于科研数据可视化与论文图表绘制。

astropy.visualization模块是 Astropy 的通用可视化工具箱,除了本文重点的 Matplotlib 集成外,还涵盖天文图像坐标绘制(wcsaxes)、图像归一化与拉伸、智能直方图、RGB 彩色合成以及自定义绘图样式(详见 docs/visualization/index.rst)。其中,单位与时间的绘图支持由 astropy/visualization/units.py 和 astropy/visualization/time.py 两个模块实现,二者均基于 Matplotlib 的ConversionInterface单位转换框架,在 Matplotlib 的units.registry中注册对应的转换器。

一、Plotting quantities:直接绘制带单位的数据

Quantity(astropy.units.Quantity)是 Astropy 表示“数值 + 物理单位”的核心对象。在不做任何配置时,直接把Quantity传给 Matplotlib 的plot()等函数会报错或得到无意义的裸数值。Astropy 提供了一个显式开启的开关quantity_support():

from astropy.visualization import quantity_support quantity_support()

调用后会返回一个<astropy.visualization.units.MplQuantityConverter ...>转换器实例。此后,Quantity对象就可以直接传给 Matplotlib 的各种绘图函数,且坐标轴标签会自动带上该轴的单位:

from astropy import units as u from astropy.visualization import quantity_support from matplotlib import pyplot as plt quantity_support() fig, ax = plt.subplots(figsize=(5, 3)) ax.plot([1, 2, 3] * u.m)

运行这段代码,y 轴标签会显示为m(米),刻度数值即为数据的原始数值。

1.1 单位自动换算:以“轴上第一个单位”为准

启用quantity_support()后,同一坐标轴上后续绘制的数据会自动换算到该轴已确定的单位。默认单位取自第一次在该轴绘图时遇到的 Quantity 单位。例如下面的代码,第一条线是m,第二条线虽然以cm给出,但会被自动换算为m,y 轴仍然保持m不变:

ax.plot([1, 2, 3] * u.cm) # 100 cm 自动换算为 1 m,y 轴单位仍为 m

这种“首遇单位决定轴单位”的行为由转换器的default_units逻辑决定,见 astropy/visualization/units.py:当数据对象具有unit属性时直接返回该单位;当传入的是“可迭代且首元素带单位”的对象(如[1, 2, 3] * u.m生成的数组)时返回首元素的单位;纯数值列表则返回None。而convert方法(units.py)则调用Quantity.to_value(unit)完成实际换算——这也是单位兼容性检查发生的地方。

1.2 显式锁定轴单位:Axis.set_units()

如果你希望对某个轴的单位拥有完全控制权,可以显式指定:

ax.yaxis.set_units(u.cm)

例如,当你想让 y 轴固定显示为厘米时,执行上面这句即可。轴单位一旦显式设置,就不再受数据本身的单位或绘图顺序影响——无论后续传入m、cm还是km的数据,都会统一换算并显示为cm。这一机制与 Matplotlib 原生matplotlib.axis.Axis.set_units完全一致,Astropy 只是让Quantity接入了这套标准接口。

1.3 单位不兼容时的异常行为

向一个已确定长度单位的轴(如上面的m)绘制质量单位的数据会直接抛出异常:

ax.plot([1, 2, 3] * u.kg) # ValueError / ConversionError:质量无法换算为长度

因为kg与m维度不兼容,无法完成to_value()换算。该行为在测试 astropy/visualization/tests/test_units.py 中通过pytest.raises(ConversionError)明确验证。

1.4 临时开启:with上下文管理器

MplQuantityConverter同时实现了ContextDecorator(见 units.py),因此既可以直接调用,也可以作为上下文管理器临时生效。上下文退出时,会自动恢复 Matplotlib 在注册表中的原始转换器(若原来没有则删除注册),保证代码块结束后单位支持被完整关闭:

with quantity_support(): ax.plot([1, 2, 3] * u.m)

它同样可以当作函数装饰器使用。测试 test_units.py 展示了@quantity_support()装饰器用法,并支持嵌套(内层退出后外层转换器自动恢复,见 test_nested)。

1.5 源码剖析:MplQuantityConverter与坐标轴格式化

quantity_support()返回的实例本质是一个MplQuantityConverter子类,其核心工作由三个静态方法完成(units.py):

  • axisinfo(unit, axis, format=None):返回matplotlib.units.AxisInfo,用于定制刻度的定位器(locator)、格式化器(formatter)与轴标签文本;
  • convert(val, unit, axis):将 Quantity 数值换算为轴单位下的纯数值;
  • default_units(x, axis):从第一个数据对象推断轴默认单位。

axisinfo中针对角度单位做了专门的美化处理(units.py):

  • 弧度(radian):主刻度定位器使用MultipleLocator(base=np.pi/2),并配以把π/2、π、3π/2等显示为数学符号的FuncFormatter。测试 test_radian_formatter 验证了刻度标签为["π/2", "π", "3π/2", "2π", ...];
  • 度(degree):使用AutoLocator加FormatStrFormatter("%g°"),刻度自动以°后缀显示;
  • 其他单位:轴标签通过unit.to_string(format)生成。

quantity_support()还接受一个可选参数format,用于控制轴标签的渲染格式(units.py):

quantity_support(format='console') # 轴标签显示为 "erg s^-1 cm^-2" quantity_support(format='latex_inline') # 默认值,显示为 LaTeX 风格

若未指定,默认使用模块级常量_default_format = "latex_inline"(units.py)。测试 test_axisinfo_label 分别验证了None(LaTeX)与'console'两种格式下的标签输出。此外,该支持对Quantity子类(如Angle)同样生效,参见 test_quantity_subclass。

二、Plotting times:直接绘制天文时间Time

Matplotlib 原生提供日期/时间轴绘制机制(通过plot_date数据或在轴上注册 datetime 转换器),但天文科研中大量使用的是 Astropy 的Time对象,其包含丰富的时标(scale)与时间格式(format),Matplotlib 并不认识。为此 Astropy 提供了time_support():

from astropy.visualization import time_support time_support()

调用后会返回<astropy.visualization.units.MplTimeConverter ...>(注意:这是astropy.visualization.time模块内部定义的转换器类,实际类名为MplTimeConverter)。启用后即可直接绘图,且轴标签会自动格式化为 Time 风格的标签:

from matplotlib import pyplot as plt from astropy.time import Time from astropy.visualization import time_support time_support() fig, ax = plt.subplots(figsize=(5, 3)) ax.plot(Time([58000, 59000, 62000], format='mjd'), [1.2, 3.3, 2.3])

2.1 格式与时标:默认取“第一个 Time”,也可显式指定

默认情况下,轴上的时间格式(format)与时标(scale)取自 Matplotlib 在某个坐标轴实例上遇到的第一个Time对象——即Time([58000, 59000, 62000], format='mjd')决定 x 轴按 MJD 格式显示。这一推断发生在MplTimeConverter.default_units中(time.py):当format/scale未显式设置时,从第一个Time对象的x.format与x.scale属性自动读取。

如需对格式与时标进行完全控制,可给time_support显式传参:

time_support(format='mjd', scale='tai') fig, ax = plt.subplots(figsize=(5, 3)) ax.plot(Time([50000, 52000, 54000], format='mjd'), [1.2, 3.3, 2.3])

这样无论传入数据的原始格式与时标是什么,轴都会按mjd格式、tai时标显示。time_support的完整签名与参数语义见 time.py:

  • scale:轴使用的时间尺度,默认取第一个 Time 的 scale;
  • format:轴使用的时间格式,默认取第一个 Time 的 format;
  • simplify:尽可能简化刻度标签,例如当所有标签都落在整点时,从 ISO 字符串中去除00:00:00.000时间部分(默认True)。

同样地,可以用with语句实现临时启用,退出后自动恢复注册表状态:

with time_support(format='mjd', scale='tai'): fig, ax = plt.subplots(figsize=(5, 3)) ax.plot(Time([50000, 52000, 54000], format='mjd'))

2.2 支持的格式与限制

time_support对格式的支持有明确边界(time.py):

UNSUPPORTED_FORMATS = ("datetime", "datetime64") YMDHMS_FORMATS = ("fits", "iso", "isot", "yday") STR_FORMATS = YMDHMS_FORMATS + ("byear_str", "jyear_str")
  • 字符串型日历格式fits、iso、isot、yday及byear_str、jyear_str会由AstropyTimeFormatter专门格式化为可读时间文本;
  • 数值型格式(mjd、jd、unix、cxcsec、gps、decimalyear、byear、jyear、plot_date等)则直接以数值刻度呈现;
  • 若显式传入datetime或datetime64,formatsetter 会直接抛出ValueError("time_support does not support format=...")(time.py)。

测试 test_formats 覆盖了上述全部格式的刻度输出,例如:iso输出"2020-01-01 00:00:00.000"、mjd输出"60000"、byear_str输出"B2020.000"、yday输出"2020:001:00:00:00.000"等,并验证轴标签为Time (<format>)。simplify=True时(test_formats_simplify),iso/fits/isot会去掉整点的时分秒后缀,yday会化简为纯年份,使长跨度的图表更清爽。

2.3 源码剖析:刻度定位器与格式化器

MplTimeConverter(time.py)是接入 Matplotlib 的转换器本体,其axisinfo返回自定义的AstropyTimeLocator(刻度定位器)与AstropyTimeFormatter(刻度格式化器),并设置轴标签f"Time ({self.scale})"。convert方法根据目标格式把Time换算成对应的标量或数组:YMDHMS 类格式换算为scaled.mjd(MJD 数值),byear_str/jyear_str分别换算为byear/jyear,其余格式直接取getattr(scaled, self.format)。

AstropyTimeLocator(time.py)继承了 Matplotlib 的MaxNLocator(默认nbins=4),并按时间跨度自适应选择刻度步长:

  • 跨度超过一年:按整年取刻度(步长由select_step_scalar选取“漂亮”的整数);
  • 跨度在月到年之间:按整月取刻度,并处理跨年月份偏移;
  • 跨度在一天到一个月之间:退化为steps=[1, 2, 5, 10]的标准数值刻度;
  • 跨度小于一天:由select_step_hour计算以“小时角”为单位的合理步长(这两个步长选择工具位于 astropy/visualization/wcsaxes/utils.py)。

测试 test_formatter_locator 用 12 组从“几十年”到“亚秒”的典型区间验证了这套定位/格式化组合:例如跨度几十年时刻度为["2020-01-01", "2040-01-01", "2060-01-01"],跨度几小时时刻度精确到分钟级时间戳。

AstropyTimeFormatter(time.py)则负责把刻度数值还原为Time并调用对应的格式方法生成标签文本(getattr(times, format)),同时关闭科学计数法与偏移量(set_useOffset(False)、set_scientific(False)),避免长年份区间出现2e3之类的非时间标签。

三、工程实践要点

  1. 先开启再绘图:quantity_support()与time_support()都是“显式开启”的设计,需要在调用任何 matplotlib 绘图函数之前执行;二者的完整 API 均从astropy.visualization顶层导出(见 astropy/visualization/init.py)。
  2. 优先使用with语句:在交互式会话或长脚本中,用上下文管理器包裹绘图代码可以避免转换器状态泄漏到后续代码;嵌套使用时内部退出会自动恢复外部注册状态(测试 test_nested 验证了嵌套时格式互不干扰)。
  3. 单位与格式的确定性:若想保证图表输出的单位/时间格式完全可控,请显式使用ax.yaxis.set_units(u.cm)、time_support(format=..., scale=...),而不要依赖“首个数据决定”的默认推断。
  4. 依赖要求:两个功能都依赖 Matplotlib 环境。仓库代码中通过HAS_MATPLOTLIB做了条件导入,在未安装 Matplotlib 时MplQuantityConverter会退化为直接抛ImportError的占位类(units.py),相关测试也以pytest.mark.skipif(not HAS_PLT, ...)跳过(见 test_units.py)。

四、小结

借助quantity_support()与time_support(),Astropy 把“物理单位”和“天文时标”无缝接入 Matplotlib 的单位转换框架:前者让Quantity的轴标签自动带单位、同轴数据自动换算、角度轴获得 π 符号刻度;后者让Time对象按天文人习惯的格式与时标呈现,并配有跨年/跨月/跨天/亚天的多级自适应刻度算法。两者的实现都可在 astropy/visualization/units.py 与 astropy/visualization/time.py 中完整追溯,对应的行为契约则由 test_units.py 与 test_time.py 中的数十个参数化用例精确锁定,可作为日常绘图的可靠参考。

  • 科学计算
  • 科研
  • 数据分析

【免费下载链接】astropy

Astronomy and astrophysics core library

项目地址:https://gitcode.com/gh_mirrors/as/astropy
点击查看免费下载

相关推荐

上一篇:从特征提取到图像嵌入:RepVGG_b2.rvgg_in1k的多场景应用指南
下一篇:如何永久保存微信聊天记录?这个开源工具让你轻松备份和分析所有对话

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 20:19:55

PostgreSQL 18分区表实战指南:键选型、维护与性能调优

如果你正在用 PostgreSQL 18 设计一个新项目&#xff0c;或者刚把一套存量业务库迁移上来&#xff0c;那分区表技术大概率是你躲不开的一个模块。它并不复杂&#xff0c;但陷阱不少。很多人一开始只是冲着“按月归档”去用&#xff0c;结果撞上分区键选型不当、约束同步缺失、查…

作者头像 李华
网站建设 2026/10/14 20:19:41

企业微信二次开发案例:如何实现群成员管理与群运营自动化

我们运营着几十个客户群&#xff0c;靠人工盯成员进出根本不现实——谁进群了没欢迎、哪个群快满员、哪些群一周没人说话&#xff0c;全靠群里翻记录。后来把群成员管理做成自动化&#xff0c;运营只盯一张群健康度表。 群成员这块用的是 Eyun 平台开放的企微 API&#xff0c;…

作者头像 李华
网站建设 2026/10/14 20:19:17

软考 系统架构设计师历年真题集萃(126)

接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(125) 第233题 生产某种产品有两个建厂方案:(1)建大厂,需要初期投资500万元。如果产品销路好,每年可以获利200万元;如果销路不好每年会亏损20万元。(2)建小厂,需要初期投资200万元。如果产品销路好,每年可以…

作者头像 李华
网站建设 2026/10/14 20:18:30

Kafka高性能原理:顺序写、零拷贝、批量压缩与分区并行

先说明一个现象&#xff1a;很多人第一次接触Kafka&#xff0c;都是从“高性能、高吞吐”这六个字开始的。但真正去翻源码、看文档、甚至自己搭集群压测之后&#xff0c;才会意识到一个问题——Kafka的快&#xff0c;不靠堆硬件&#xff0c;不靠缓存硬扛&#xff0c;也不靠把逻…

作者头像 李华
网站建设 2026/10/14 20:16:54

SSM 二手书店

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e;&#x1f496;&#x1f4d5;&#x1f389;&#x1f525; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;欢迎留言讨论&#x1f525;&#x1f525;&am…

作者头像 李华
网站建设 2026/10/14 20:15:51

OCT成像原理详解:从低相干干涉到时域与频域实现

做光学检测的人大概都听过这样一个说法&#xff1a;OCT 就是“光的 B 超”。但真正上手之后你会发现&#xff0c;这句话只说对了一半。光学相干层析成像&#xff08;Optical Coherence Tomography&#xff0c;OCT&#xff09;确实像超声一样靠“回波时间”来定位深度&#xff0…

作者头像 李华