Matplotlib - twiny: как выровнять значения двух осей X в одном графике? - PullRequest
0 голосов
/ 24 ноября 2018

Я пытаюсь использовать twiny() в matplotlib для построения кривой с двумя осями X из файла XML, состоящего из следующих блоков данных:

<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()

, который создает следующий график:

enter image description here

Я собираюсь скрыть красную кривую позже(используя visibility=False), здесь я строю график только для того, чтобы увидеть правильное выравнивание значений по осям X, а именно, обе кривые должны (!) фактически перекрываться, потому что значения фазового угла (x_phi) зависятдля соответствующих значений отметок времени (x_ut), но, как вы можете ясно видеть, только начало и конец выровнены правильно, но большая часть промежуточных данных смещена (фазовая кривая сдвинута вправо).

Что я делаю не так?

Первоначально я предполагал, что фазовый угол (x_phi) изменялся во времени нелинейно, так что set_xlim() обеих кривых растягивал их по-разному, ноэто не так, я составил график x_phi против x_ut, и есть очевидное линейное изменение:

enter image description here

Спасибо за любую помощьзаранее!

РЕДАКТИРОВАТЬ: Нелинейность была доказана телом в егоответ ниже.Таким образом, я немного изменяю свой вопрос.

Если я удаляю set_xlim() из обоих подзаговоров ax1 и ax2, то:

1) Верхняя ось x автоматически инвертируется,начиная с наименьшего значения, хотя список x_phi, который дает значения, начинается с наибольшего значения - как можно избежать этой инверсии без использования invert_axis()?(в разных случаях у меня всегда будут только увеличивающиеся или только уменьшающиеся значения в списке x_phi)

2) Всего имеется 3 списка: x_ut, x_phi и y_brightness;и мне нужно на самом деле построить только кривую y_brightness против x_ut и дополнительно, чтобы значения x_phiticker.MultipleLocator(1)) были выровнены с соответствующими значениями моментов времени из x_ut - как я могусделать это?

Моя проблема похожа на эту: Как выровнять линии сетки для двух шкал оси Y с помощью Matplotlib? Но в моем случае между тикамиверхняя ось х, так что я не могу использовать это решение.

Кроме того, этот вопрос касается аналогичной проблемы: проблема с выравниванием тиков для осей matplotlib twinx Но я не знаю, как выразитьотношение между двумя осями X в моем случае, потому что тип данных очень отличается: datetime против float.Единственное отношение между ними однозначно, то есть первое значение из x_ut связано с первым значением из x_phi, вторым со вторым и т. Д .;и это отношение нелинейно.

РЕДАКТИРОВАТЬ 2: Число 1) в моем предыдущем РЕДАКТИРОВАНИИ теперь решено.И для остальной части проблемы, похоже, я должен использовать register_scale(), чтобы масштабировать вторичную ось X относительно первичной оси X.Для этого мне также нужно было бы определить подкласс matplotlib.scale.ScaleBase.До сих пор я нашел только два сложных (для меня) примера того, как это сделать:

https://matplotlib.org/examples/api/custom_scale_example.html
https://stackoverrun.com/es/q/8578801 (на испанском языке, но с английскими комментариями внутри кода)

Я не уверен, смогу ли я реализовать это самостоятельно, поэтому я все еще ищу любую помощь в этом.

Ответы [ 2 ]

0 голосов
/ 27 ноября 2018

Ура!Мне удалось получить искомый результат без определения нового класса шкалы!Вот соответствующие части кода, которые были добавлены / изменены в сценарии из вопроса (переменная 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()

enter image description here

0 голосов
/ 24 ноября 2018

Ваша рутина печати выглядит правильно.Вместо этого, похоже, что проблема в том, что равно (очень малому) отклонению от линейности во взаимосвязи между временем и фазовым углом.Вы можете увидеть это, наложив прямую черную линию на график зависимости времени от угла (нажмите ниже, чтобы открыть в отдельном окне, и увеличьте масштаб, чтобы увидеть его отчетливо):

enter image description here

Вот масштаб, который подчеркивает отклонение:

enter image description here

Единственный способ выровнять две оси X:

  • Манипулировать данными.
  • Манипулировать шкалой второй оси X, чтобы сделать ее несовместимой.Сделайте так, чтобы фактическое расстояние на графике между 49 и 48 градусами отличалось от фактического расстояния на графике между 45 и 44 градусами и т. Д.

Оба из них являются плохой идеей™ ️, и вы не должны делать ни того, ни другого.Вы должны представить данные как есть, а не пытаться скрыть любые несоответствия с помощью хитрых уловок.Все, что меньше, можно считать академической нечестностью.

Ваш единственный вариант - понять, почему время и угол не совпадают, как вы ожидаете.Это может быть проблема в вашем коде, возможно, в формуле, которую вы используете в get_phase_angle.Или это может быть проблемой в вашем базовом наборе данных, например, если в измерениях степени есть некоторый шум.Или может быть так, что на самом деле время и угол не имеют линейной зависимости.В конце концов, земля не идеально круглая.

...