Меня попросили создать несколько графиков, используемых в дизайне множественного базового исследования, который представляет собой специальный тип графиков. Я воспользовался этим как возможностью узнать немного больше о 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()
Мой сюжет до сих пор (результат приведенного выше кода):
Пример графика для контекста:
Ну да, но, в частности, мне нужна пунктирная линия из примера сюжета «вне» области подграфиков и создание бесшовной линии. Я могу сделать вертикальные линии на каждом участке, что тоже будет работать, но мне было любопытно, есть ли способ нарисовать это, как в примере.
Мой инстинкт для такого рода задач — нарисовать линию в координатах фигуры. Единственная проблема, с которой я столкнулся, заключалась в том, чтобы найти положение центральной области между последовательными осями. Мой код уродлив, но он работает и не зависит от относительного размера каждой оси или расстояния между осями, как показано ниже:
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()
Вау большое спасибо! Это именно то, что я искал. Ваш комментарий также хорошо написан и поясняет, а не является фрагментом кода «волшебного дерева».
Нет проблем — рад, что мой ответ оказался полезным!
Похоже, вы хотите добавить аннотации к своему графику, такие как вертикальные или горизонтальные пунктирные линии, а также текст?
matplotlib
есть такие аннотации, и я был бы рад указать вам правильное направление или привести пример