Нарисуйте разделительную линию между подзаговорами в matplotlib

Меня попросили создать несколько графиков, используемых в дизайне множественного базового исследования, который представляет собой специальный тип графиков. Я воспользовался этим как возможностью узнать немного больше о Matplotlib и Pandas, но одна вещь, с которой я борюсь, — это разделительная линия, которая разделяет BASE и INTERVENTION. Мне нужно, чтобы он продолжался через несколько сюжетных линий, а также хорошо масштабировался. Есть ли способ сделать это? Я пробовал экспериментировать с Lines.Line2D, а также с ConnectionPatch, но я застрял в правильном масштабировании и определении положения.

Мой (простой) код до сих пор.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

y = np.array([0,1,2,3,4])
fig, axs = plt.subplots(3, sharex=True, sharey=True)
fig.suptitle("I1 - Reakce na změnu prvku")
axs[0].plot(df.index,df['A'], color='lightblue', label = "A")
axs[1].plot(df.index,df['N'], color='darkblue', label = "N")
axs[2].plot(df.index,df['P'], color='blue', label = "P")

plt.yticks(np.arange(y.min(), y.max(), 1))
plt.show()

Мой сюжет до сих пор (результат приведенного выше кода):

Пример графика для контекста:

Похоже, вы хотите добавить аннотации к своему графику, такие как вертикальные или горизонтальные пунктирные линии, а также текст? matplotlib есть такие аннотации, и я был бы рад указать вам правильное направление или привести пример

Derek O 20.12.2020 21:07

Ну да, но, в частности, мне нужна пунктирная линия из примера сюжета «вне» области подграфиков и создание бесшовной линии. Я могу сделать вертикальные линии на каждом участке, что тоже будет работать, но мне было любопытно, есть ли способ нарисовать это, как в примере.

Matěj Groman 20.12.2020 21:17
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python.
Некоторые методы, о которых вы не знали, что они существуют в Python.
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
5
2
886
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Мой инстинкт для такого рода задач — нарисовать линию в координатах фигуры. Единственная проблема, с которой я столкнулся, заключалась в том, чтобы найти положение центральной области между последовательными осями. Мой код уродлив, но он работает и не зависит от относительного размера каждой оси или расстояния между осями, как показано ниже:

from matplotlib.lines import Line2D
def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    from itertools import zip_longest
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)




xconn = [0.15, 0.35, 0.7]  # position of the vertical lines in each subplot, in data coordinates


fig, axs = plt.subplots(3,1, gridspec_kw=dict(hspace=0.6, height_ratios=[2,0.5,1]))



#
# Draw the separation line, should be done at the very end when the limits of the axes have been set etc.
#
# convert the value of xconn in each axis to figure coordinates
xconn = [fig.transFigure.inverted().transform(ax.transData.transform([x,0]))[0] for x,ax in zip(xconn,axs)]
yconn = []  # y-values of the connecting lines, in figure coordinates
for ax in axs:
    bbox = ax.get_position()
    yconn.extend([bbox.y1, bbox.y0])
# replace each pairs of values corresponding to the bottom and top of each pairs of axes by the average
yconn[1:-1] = np.ravel([[np.mean(ys)]*2 for ys in grouper(yconn[1:-1], 2)]).tolist()

l = Line2D(np.repeat(xconn,2), yconn, transform=fig.transFigure, ls='--', lw=1, c='k')
fig.add_artist(l)

Ответ принят как подходящий

Заимствуя из полезного ответа Пабло, кажется, что использование fig.transFigure может получить доступ к координатам в каждом подзаговоре, и вы можете рисовать линии между всеми этими координатами. Это, вероятно, лучший метод, так как он позволяет легко определить начальную и конечную точки. Поскольку ваши x-координаты удобно находятся в диапазоне от 1 до 12, вы также можете разделить каждый подграфик на две части, чтобы оставить зазор между точками для прохождения линии аннотации.

import numpy as np
import pandas as pd

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.patches import ConnectionPatch

y = np.array([0,1,2,3,4])

## recreate your data
df = pd.DataFrame({
    'A':[0, 1, 1, 1, 2, 2, 3, 2, 3] + [float("nan")]*3,
    'N':[1, 0, 0, 2, 1, 1, 2, 3, 3, 3, 3, 3],
    'P':[0, 1, 1, 1, 2, 1, 1, 1, 2, 3, 3, 3],
    },  
    index=range(1,13)
)


fig, axs = plt.subplots(3, sharex=True, sharey=True)
fig.suptitle("I1 - Reakce na změnu prvku")

## create a gap in the line
axs[0].plot(df.index[0:3],df['A'][0:3], color='lightblue', label = "A", marker='.')
axs[0].plot(df.index[3:12],df['A'][3:12], color='lightblue', label = "A", marker='.')

## create a gap in the line
axs[1].plot(df.index[0:8],df['N'][0:8], color='darkblue', label = "N", marker='.')
axs[1].plot(df.index[8:12],df['N'][8:12], color='darkblue', label = "N", marker='.')

## create a gap in the line
axs[2].plot(df.index[0:10],df['P'][0:10], color='blue', label = "P", marker='.')
axs[2].plot(df.index[10:12],df['P'][10:12], color='blue', label = "P", marker='.')

plt.yticks(np.arange(y.min(), y.max(), 1))


transFigure = fig.transFigure.inverted()

## Since your subplots have a ymax value of 3, setting the end y-coordinate
## of each line to just above that value should help it display outside of the figure

coord1 = transFigure.transform(axs[0].transData.transform([3.5,3]))
coord2 = transFigure.transform(axs[1].transData.transform([3.5,3.5]))
coord3 = transFigure.transform(axs[1].transData.transform([8.5,3.5]))
coord4 = transFigure.transform(axs[2].transData.transform([8.5,3.5]))
coord5 = transFigure.transform(axs[2].transData.transform([10.5,3.5]))
coord6 = transFigure.transform(axs[2].transData.transform([10.5,0]))

## add a vertical dashed line
line1 = matplotlib.lines.Line2D((coord1[0],coord2[0]),(coord1[1],coord2[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a horizontal dashed line
line2 = matplotlib.lines.Line2D((coord2[0],coord3[0]),(coord2[1],coord3[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a vertical dashed line
line3 = matplotlib.lines.Line2D((coord3[0],coord4[0]),(coord3[1],coord4[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a horizontal dashed line
line4 = matplotlib.lines.Line2D((coord4[0],coord5[0]),(coord4[1],coord5[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

## add a vertical dashed line
line5 = matplotlib.lines.Line2D((coord5[0],coord6[0]),(coord5[1],coord6[1]),
                               transform=fig.transFigure,
                               ls='--',
                               color='grey')

fig.lines.extend([line1, line2, line3, line4, line5])
plt.show()

Вау большое спасибо! Это именно то, что я искал. Ваш комментарий также хорошо написан и поясняет, а не является фрагментом кода «волшебного дерева».

Matěj Groman 21.12.2020 11:26

Нет проблем — рад, что мой ответ оказался полезным!

Derek O 21.12.2020 16:58

Другие вопросы по теме