Matplotlib -- twiny:如何在一个图中对齐两个 x 轴的值?
Matplotlib -- twiny: how to align values of two x-axes in one plot?
我正在尝试使用 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
并且有一个明显的线性变化:
提前感谢您的帮助!
编辑: 电话在下面的回答中证明了非线性。因此,我稍微改变了我的问题。
如果我从两个子图 ax1
和 ax2
中删除 set_xlim()
,则:
1) 上面的 x 轴自动反转,从最小值开始,尽管给出值的列表 x_phi
是从最大值开始的——如果没有,我如何避免这种反转使用 invert_axis()
? (在不同的情况下,我将始终在 x_phi
列表中增加或减少值)
2) 共有3个列表:x_ut
、x_phi
和y_brightness
;我实际上只需要绘制曲线 y_brightness
与 x_ut
并且另外使 x_phi
的值(与 ticker.MultipleLocator(1)
)与相应的时刻值对齐x_ut
-- 我该怎么做?
我的问题和这个类似:
How do I align gridlines for two y-axis scales using Matplotlib?
但在我的例子中,上部 x 轴的刻度之间没有线性间距,因此我无法使用该解决方案。
此外,这个问题涉及类似的问题:
但我不知道在我的例子中如何表达两个 x 轴之间的关系,因为数据类型非常不同:datetime 与 float。它们之间唯一的关系是一对一的关系,即 x_ut
中的第一个值与 x_phi
中的第一个值相关,第二个值与第二个值相关,依此类推;并且这种关系是非线性的。
EDIT 2: 我之前编辑中的数字 1) 现在已经解决了。对于剩下的问题,看起来我必须使用 register_scale()
才能相对于主 x 轴重新缩放辅助 x 轴。为此,我还必须定义 matplotlib.scale.ScaleBase
的子类。到目前为止,我只找到了两个复杂的(对我而言)示例:
https://matplotlib.org/examples/api/custom_scale_example.html
https://stackoverrun.com/es/q/8578801(西班牙语,但代码中有英文注释)
我不确定我是否能够自己实现这个,所以我仍然寻求任何帮助。
您的绘图例程看起来是正确的。相反,看起来问题是 是 在时间和相位角之间的关系中与线性的(非常小的)偏差。你可以通过在时间与角度图上叠加一条黑色直线来看到它(单击下面在单独的 window 中打开并放大以清楚地看到它):
这里是突出显示偏差的缩放:
使两个 x 轴对齐的唯一方法是:
- 处理数据。
- 操纵第二个x轴的比例使其不一致。使图上 49 到 48 度之间的实际距离与图上 45 到 44 度之间的实际距离不同,依此类推。
这两个都是 A Bad Idea™️,你不应该做任何一个。您需要按原样绘制数据,而不是试图用巧妙的技巧来隐藏任何不一致之处。任何不足都可能被视为学术不诚实。
您唯一的选择是弄清楚为什么时间和角度不符合您的预期。这可能是您的代码中的问题,可能是您在 get_phase_angle
中使用的公式中的问题。或者它可能是您的基础数据集中的问题,例如,如果度数测量中存在一些噪音。或者它可能只是,在现实中,时间和角度没有线性关系。毕竟地球不是正圆的
耶!我设法在没有定义新比例 class 的情况下获得了想要的结果!以下是问题脚本中 added/modified 的相关代码部分(变量 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()
我正在尝试使用 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
并且有一个明显的线性变化:
提前感谢您的帮助!
编辑: 电话在下面的回答中证明了非线性。因此,我稍微改变了我的问题。
如果我从两个子图 ax1
和 ax2
中删除 set_xlim()
,则:
1) 上面的 x 轴自动反转,从最小值开始,尽管给出值的列表 x_phi
是从最大值开始的——如果没有,我如何避免这种反转使用 invert_axis()
? (在不同的情况下,我将始终在 x_phi
列表中增加或减少值)
2) 共有3个列表:x_ut
、x_phi
和y_brightness
;我实际上只需要绘制曲线 y_brightness
与 x_ut
并且另外使 x_phi
的值(与 ticker.MultipleLocator(1)
)与相应的时刻值对齐x_ut
-- 我该怎么做?
我的问题和这个类似: How do I align gridlines for two y-axis scales using Matplotlib? 但在我的例子中,上部 x 轴的刻度之间没有线性间距,因此我无法使用该解决方案。
此外,这个问题涉及类似的问题:
x_ut
中的第一个值与 x_phi
中的第一个值相关,第二个值与第二个值相关,依此类推;并且这种关系是非线性的。
EDIT 2: 我之前编辑中的数字 1) 现在已经解决了。对于剩下的问题,看起来我必须使用 register_scale()
才能相对于主 x 轴重新缩放辅助 x 轴。为此,我还必须定义 matplotlib.scale.ScaleBase
的子类。到目前为止,我只找到了两个复杂的(对我而言)示例:
https://matplotlib.org/examples/api/custom_scale_example.html
https://stackoverrun.com/es/q/8578801(西班牙语,但代码中有英文注释)
我不确定我是否能够自己实现这个,所以我仍然寻求任何帮助。
您的绘图例程看起来是正确的。相反,看起来问题是 是 在时间和相位角之间的关系中与线性的(非常小的)偏差。你可以通过在时间与角度图上叠加一条黑色直线来看到它(单击下面在单独的 window 中打开并放大以清楚地看到它):
这里是突出显示偏差的缩放:
使两个 x 轴对齐的唯一方法是:
- 处理数据。
- 操纵第二个x轴的比例使其不一致。使图上 49 到 48 度之间的实际距离与图上 45 到 44 度之间的实际距离不同,依此类推。
这两个都是 A Bad Idea™️,你不应该做任何一个。您需要按原样绘制数据,而不是试图用巧妙的技巧来隐藏任何不一致之处。任何不足都可能被视为学术不诚实。
您唯一的选择是弄清楚为什么时间和角度不符合您的预期。这可能是您的代码中的问题,可能是您在 get_phase_angle
中使用的公式中的问题。或者它可能是您的基础数据集中的问题,例如,如果度数测量中存在一些噪音。或者它可能只是,在现实中,时间和角度没有线性关系。毕竟地球不是正圆的
耶!我设法在没有定义新比例 class 的情况下获得了想要的结果!以下是问题脚本中 added/modified 的相关代码部分(变量 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()