【问题标题】:Matplotlib -- twiny: how to align values of two x-axes in one plot?Matplotlib -- twiny:如何在一个图中对齐两个 x 轴的值?
【发布时间】:2019-04-26 18:58:19
【问题描述】:

我正在尝试使用 matplotlib 中的 twiny() 从包含这些数据块的 XML 文件中绘制具有两个 x 轴的曲线:

<data>
<meas>
  <utc>2018-11-10T22:27:06.500003</utc>
  <ra_j2000>23.9722686269</ra_j2000>
  <dec_j2000>-1.23845121893</dec_j2000>
  <mag>9.96074403533</mag>
</meas>

<meas>
  <utc>2018-11-10T22:27:54.500002</utc>
  <ra_j2000>23.9930913364</ra_j2000>
  <dec_j2000>-1.03788334773</dec_j2000>
  <mag>11.356437889</mag>
</meas>

<meas>
  <utc>2018-11-10T22:38:36.500002</utc>
  <ra_j2000>0.267638646848</ra_j2000>
  <dec_j2000>1.56055091433</dec_j2000>
  <mag>11.1642458641</mag>
</meas>

<meas>
  <utc>2018-11-10T22:46:18.500000</utc>
  <ra_j2000>0.462353662364</ra_j2000>
  <dec_j2000>3.34334963425</dec_j2000>
  <mag>11.1082534741</mag>
</meas>

<meas>
  <utc>2018-11-10T22:57:18.500001</utc>
  <ra_j2000>0.740393528722</ra_j2000>
  <dec_j2000>5.78641590694</dec_j2000>
  <mag>11.0688955214</mag>
</meas>

<meas>
  <utc>2018-11-10T23:03:06.499995</utc>
  <ra_j2000>0.888541738338</ra_j2000>
  <dec_j2000>7.03265231497</dec_j2000>
  <mag>10.2358937709</mag>
</meas>

<meas>
  <utc>2018-11-10T23:05:42.500002</utc>
  <ra_j2000>0.955591973177</ra_j2000>
  <dec_j2000>7.5832430461</dec_j2000>
  <mag>10.86206725</mag>
</meas>

<meas>
  <utc>2018-11-10T23:06:48.499999</utc>
  <ra_j2000>0.984093767077</ra_j2000>
  <dec_j2000>7.81466175077</dec_j2000>
  <mag>10.3466108708</mag>
</meas>
</data>

我的问题是这些 x 轴上的值未对齐。这是我的 Python 脚本:

import math
import xml.etree.ElementTree as ET
from astropy.time import Time
from astropy.coordinates import get_sun
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
from matplotlib import dates

tree = ET.parse('20181110_10241.xml')
root = tree.getroot()

x_ut = []
x_phi = []
y_brightness = []


def convert_time(obs_time):
    obs_time = str(obs_time)
    d, t = obs_time.split('T')
    year, month, day = map(int, d.split('-'))
    hour, minute, second = t.split(':')
    return datetime(year, month, day, int(hour), int(minute)) + \
        timedelta(seconds=float(second))

def get_sun_coords(obs_time):
    sun_coords = get_sun(obs_time)
    sun_ra = sun_coords.ra.degree
    sun_dec = sun_coords.dec.degree
    return sun_ra, sun_dec

def get_phase_angle(sun_ra, sun_dec, target_ra, target_dec):
    phase_angle = math.degrees(math.acos(-math.sin(math.radians(sun_dec))*math.sin(math.radians(target_dec)) - math.cos(math.radians(sun_dec))*math.cos(math.radians(target_dec))*math.cos(math.radians(sun_ra-target_ra))))
    return phase_angle

for meas in root.findall('meas'):
    obs_time = Time(meas.find('utc').text, format='isot', scale='utc')
    target_ra = float(meas.find('ra_j2000').text)*15
    target_dec = float(meas.find('dec_j2000').text)
    mag = float(meas.find('mag').text)

    sun_ra, sun_dec = get_sun_coords(obs_time)
    phase_angle = get_phase_angle(sun_ra, sun_dec, target_ra, target_dec)

    obs_time = convert_time(obs_time)
    x_ut.append(obs_time)
    x_phi.append(phase_angle)
    y_brightness.append(mag)

fig, ax1 = plt.subplots()

ax1.plot(x_ut, y_brightness, marker='o', label='apparent brightness')
ax1.set_xlim(x_ut[0],x_ut[-1])
ax1.xaxis.set_major_locator(dates.MinuteLocator(interval=1))
ax1.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
ax1.tick_params(axis='x', rotation=45)
ax1.minorticks_on()
ax1.legend()
ax1.grid()
ax1.set_xlabel('time [h:m, UT]')
ax1.set_ylabel('apparent brightness [mag, CR]')

ax2 = ax1.twiny()
ax2.plot(x_phi,y_brightness, marker='^', color='red')
ax2.set_xlim(x_phi[0],x_phi[-1])
ax2.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax2.minorticks_on()
ax2.set_xlabel('phase angle (phi) [deg]')

plt.gca().invert_yaxis()
plt.tight_layout(pad=0)
plt.show()

这会产生以下情节:

我打算稍后隐藏红色曲线(通过使用visibility=False),在这里我绘制它只是为了查看x轴值的正确对齐,即实际上两条曲线必须(!)重叠,因为相位角 (x_phi) 值取决于相应的时间戳 (x_ut) 值,但您可以清楚地看到,只有开头和结尾正确对齐,但中间的大部分数据未对齐(相位曲线向右移动)。

我做错了什么?

最初,我猜想,相位角 (x_phi) 是随时间非线性变化的,因此两条曲线的 set_xlim() 拉伸它们的方式不同,但这不是真的,我绘制了 x_phi 反对x_ut 并且有明显的线性变化:

提前感谢您的帮助!

编辑: tel 在下面的回答中已经证明了非线性。因此,我稍微改变一下我的问题。

如果我从两个子图 ax1ax2 中删除 set_xlim(),那么:

1) 上 x 轴自动反转,从最小值开始,虽然列表 x_phi 给出值,从最大值开始 - 我如何避免这种反转而不使用 invert_axis() ? (在不同的情况下,x_phi 列表中的值总是只会增加或减少)

2) 共有3个列表:x_utx_phiy_brightness;我实际上只需要绘制曲线y_brightnessx_ut,另外还要让x_phi(与ticker.MultipleLocator(1))的值与x_ut 的相应时间值对齐——我怎么能这样做?

我的问题与此类似: How do I align gridlines for two y-axis scales using Matplotlib? 但就我而言,上 x 轴的刻度之间没有线性间距,因此我无法使用该解决方案。

此外,这个问题涉及一个类似的问题: trouble aligning ticks for matplotlib twinx axes 但在我的例子中,我不知道如何表达两个 x 轴之间的关系,因为数据类型非常不同:日期时间与浮点数。它们之间唯一的关系是一对一的,即x_ut的第一个值与x_phi的第一个值相关,第二个与第二个相关,以此类推;而且这种关系是非线性的。

EDIT 2:我之前的 EDIT 中的数字 1) 现在已解决。对于剩下的问题,看起来我必须使用register_scale() 才能相对于主 x 轴重新缩放辅助 x 轴。为此,我还必须定义matplotlib.scale.ScaleBase 的子类。到目前为止,我只发现了两个复杂的(对我而言)如何做到这一点的示例:

https://matplotlib.org/examples/api/custom_scale_example.html
https://stackoverrun.com/es/q/8578801(西班牙语,但代码中有英文 cmets)

我不确定我是否能够自己实现这个,所以我仍然寻求任何帮助。

【问题讨论】:

  • 如果你想在这里得到一个具体的答案,你可能想分享一个minimal reproducible example,人们可以使用它。
  • @ImportanceOfBeingErnest 我现在添加了一个更大的底层 XML 示例,并在底部包含了关于非线性的结论。其他一切——我的 Python 脚本——都已完成。我仍然希望得到一些帮助...
  • 如果你想定义一个自定义比例,你需要一个将一个值映射到其他值的函数。由于您不知道此功能,因此您可以对这些值进行插值,例如使用numpy.interp

标签: python matplotlib alignment axes


【解决方案1】:

耶!我已经设法在没有定义新的规模类的情况下获得了所寻求的结果!以下是问题的脚本中添加/修改的相关代码部分(变量step 稍后将从用户命令行输入中读取,或者我可能会找到另一种自动滴答频率设置的方法):

x_ut = []
x_phi = []
x_phi_ticks = []
x_phi_ticklabels = []
y_brightness = []

# populate lists for the phase angle ticks and labels

i = 0
step = 15
while i <= (len(x_ut)-step):
    x_phi_ticks.append(x_ut[i])
    x_phi_ticklabels.append(x_phi[i])
    i += step
x_phi_ticks.append(x_ut[-1])
x_phi_ticklabels.append(x_phi[-1])

# plot'em all

fig, ax1 = plt.subplots()

ax1.plot(x_ut, y_brightness, marker='o', label='apparent brightness')
ax1.xaxis.set_major_locator(dates.MinuteLocator(interval=1))
ax1.xaxis.set_major_formatter(dates.DateFormatter('%H:%M'))
ax1.tick_params(axis='x', rotation=45)
ax1.minorticks_on()
ax1.legend()
ax1.grid(which='major', linestyle='-', color='#000000')
ax1.grid(which='minor', linestyle='--')
ax1.set_xlabel('time [h:m, UT]')
ax1.set_ylabel('apparent brightness [mag, CR]')

ax2 = ax1.twiny()
ax2.set_xlim(ax1.get_xlim())
ax2.set_xticks(x_phi_ticks)
ax2.set_xticklabels(x_phi_ticklabels)
ax2.set_xlabel('phase angle (phi) [deg]')

plt.gca().invert_yaxis()
plt.tight_layout(pad=0)
plt.show()

【讨论】:

    【解决方案2】:

    您的绘图程序看起来是正确的。相反,问题似乎在于时间和相位角之间的关系存在与线性度的(非常小的)偏差。您可以通过在时间与角度图上叠加一条黑色直线来查看它(点击下方可在单独的窗口中打开并放大以清晰查看):

    这是一个突出显示偏差的缩放:

    让两个 x 轴对齐的唯一方法是:

    • 处理数据。
    • 操作第二个 x 轴的比例以使其不一致。使图上 49 到 48 度之间的实际距离与图上 45 到 44 度之间的实际距离不同,依此类推。

    这两个都是一个坏主意™️,你不应该做任何一个。您需要按原样绘制数据,而不是试图用巧妙的技巧隐藏任何不一致。任何不足都可能被视为学术不诚实。

    您唯一的选择是找出时间和角度与您预期不一致的原因。这可能是您的代码中的问题,也可能是您在get_phase_angle 中使用的公式中的问题。或者它可能是您的基础数据集中的问题,例如,如果度数测量中有一些噪音。或者它可能只是,实际上,时间和角度没有线性关系。毕竟,地球并不是完美的圆。

    【讨论】:

    • 好的,假设存在一些非线性;然后,我想,我必须稍微改变我的问题:如何设置上部 x 轴(相位角),使相位角值正确缩放并与时间值对齐?如果我只是删除 set_xlim(),就会发生奇怪的事情:上 x 轴从 ~30 的最小值开始自动恢复,尽管它应该从 ~49 开始。
    • @sergiuspro 从根本上说,您必须给第二个 x 轴一个不一致的比例,以便图上的实际距离(例如 49 度和 48 度)与实际距离不同距离在 45 到 44 度之间。你确定这就是你想要的吗?
    • 是的,上 x 轴的刻度会分布在不同的(逐渐增加或减少)距离,这绝对没问题。在我的情节中,我想一次演示两件事:给定时刻的亮度(实际上只应该绘制这个)和给定相位角的相同亮度,而相应的相位角与相应的时刻对齐时间。
    • @sergiuspro 不要那样做,这是个糟糕的主意。我在回答中添加了一条注释,解释了原因。
    • 我仍然不明白为什么不可能按照我需要的方式绘制它。我们有一个时间序列。在每一时刻,物体都有一些亮度并且位于某个相位角。这两件事 - 亮度和相位角 - 绑定(!)到时间。相角计算公式正确。亮度和相位角都随时间变化。它是一种 3 元素元组。正如您在我的 Python 脚本中看到的,有 3 个列表,因此对于每个 x_ut 值,都有一个 y_brightness 和一个 x_phi 值。如何绘制 y_brightness 与 x_ut 并且仅在 x_ut 处显示 x_phi?
    猜你喜欢
    • 1970-01-01
    • 2018-11-08
    • 1970-01-01
    • 1970-01-01
    • 2010-11-27
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    相关资源
    最近更新 更多