Я пытаюсь наложить тепловую карту поверх фигуры matplotlib футбольного поля.
Это изображение шага matplotlib, созданного блоком кода ниже:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.patches as plt_p
import numpy as np
def draw_pitch(ax):
# size of the pitch is 120, 80
#Create figure
#Pitch Outline & Centre Line
plt.plot([0,0],[0,80], color = "black")
plt.plot([0,120],[80,80], color = "black")
plt.plot([120,120],[80,0], color = "black")
plt.plot([120,0],[0,0], color = "black")
plt.plot([60,60],[0,80], color = "black")
#Left Penalty Area
plt.plot([14.6,14.6],[57.8,22.2],color = "black")
plt.plot([0,14.6],[57.8,57.8],color = "black")
plt.plot([0,14.6],[22.2,22.2],color = "black")
#Right Penalty Area
plt.plot([120,105.4],[57.8,57.8],color = "black")
plt.plot([105.4,105.4],[57.8,22.5],color = "black")
plt.plot([120, 105.4],[22.5,22.5],color = "black")
#Left 6-yard Box
plt.plot([0,4.9],[48,48],color = "black")
plt.plot([4.9,4.9],[48,32],color = "black")
plt.plot([0,4.9],[32,32],color = "black")
#Right 6-yard Box
plt.plot([120,115.1],[48,48],color = "black")
plt.plot([115.1,115.1],[48,32],color = "black")
plt.plot([120,115.1],[32,32],color = "black")
#Prepare Circles
centreCircle = plt.Circle((60,40),8.1,color = "black",fill=False)
centreSpot = plt.Circle((60,40),0.71,color = "black")
leftPenSpot = plt.Circle((9.7,40),0.71,color = "black")
rightPenSpot = plt.Circle((110.3,40),0.71,color = "black")
#Draw Circles
ax.add_patch(centreCircle)
ax.add_patch(centreSpot)
ax.add_patch(leftPenSpot)
ax.add_patch(rightPenSpot)
#Prepare Arcs
# arguments for arc
# x, y coordinate of centerpoint of arc
# width, height as arc might not be circle, but oval
# angle: degree of rotation of the shape, anti-clockwise
# theta1, theta2, start and end location of arc in degree
leftArc = plt_p.Arc((9.7,40),height=16.2,width=16.2,angle=0,theta1=310,theta2=50,color = "black")
rightArc = plt_p.Arc((110.3,40),height=16.2,width=16.2,angle=0,theta1=130,theta2=230,color = "black")
#Draw Arcs
ax.add_patch(leftArc)
ax.add_patch(rightArc)
fig=plt.figure()
fig.set_size_inches(7, 5)
ax=fig.add_subplot(1,1,1)
draw_pitch(ax)
plt.axis('off')
plt.show()
Как было рекомендовано в предыдущих постах, я попытался передать аргумент топора в функцию sns.heatmap() и изменить альфа-канал, чтобы повысить прозрачность тепловой карты. Однако тепловая карта по-прежнему покрывает всю фигуру, и футбольное поле не видно.
При запуске приведенного ниже кода я получаю следующий результат:
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import matplotlib.patches as plt_p
import numpy as np
#DUMMY DATA
df_test = pd.DataFrame(np.array([[43.2, 22.4, 0], [-5.1,-53.2,1], [33.5,-19.2,0],
[23.2, 32.4, 1], [-5.3,-53.2,1], [33.5,-69.2,0],
[53.2, -42.4, 0], [-5.4,-53.2,0], [-3.5,-39.2,0],
[63.2, 62.4, 1], [-52,-53.2,0], [37.5,-11.2,1],
[113.2, 72.4, 0], [-34.2,-53.2,0], [42.5,-119.2,1]]),
columns=['x', 'y', 'outcome'])
#CREATES THE HEATMAP OVERLAY ON THE FOOTBALL PITCH
def pass_comp_map(df):
df['x_bands'] = pd.qcut(df['x'],4,labels=False)
df['y_bands'] = pd.qcut(df['y'],3,labels=False)
df_pass = df[['x_bands','y_bands','outcome']]
df_sum = df_pass.groupby(['x_bands','y_bands'], as_index=False).sum() # get total number of completed passes
df_count = df_pass.groupby(['x_bands','y_bands'], as_index=False).count() #get total number passes
df_agg = pd.merge(df_sum, df_count['outcome'].to_frame(), how ='left',left_index=True,right_index=True)
df_agg['pass_comp'] = df_agg['outcome_x'] / df_agg['outcome_y']
data = df_agg[['x_bands','y_bands','pass_comp']]
data_pivot = data.pivot_table(index='y_bands', columns='x_bands', values='pass_comp')
data_pivot = data_pivot.fillna(0)
#OVERLAY FIGIURE CREATED HERE
fig=plt.figure()
fig.set_size_inches(7, 5)
ax=fig.add_subplot(1,1,1)
draw_pitch(ax)
plt.axis('off')
sns.heatmap(data_pivot,cbar=False, xticklabels=False, yticklabels=False,annot=True,alpha = 0.5,ax=ax)
plt.show()
pass_comp_map(df_test)
Как сделать так, чтобы тепловая карта накладывалась на футбольное поле, но футбольное поле все равно было видно?
Короче говоря: не используйте тепловую карту Seaborn. Вместо этого используйте imshow или pcolormesh от matplotlib. Они позволяют определить масштабы сюжета. Затем выберите тот же масштаб, что и ваше футбольное поле.






Взгляните на свои масштабы, поле находится в совершенно другом масштабе, чем тепловая карта. Если вы уменьшите масштаб, вы увидите что-то похожее на это:
Спасибо, а как лучше масштабировать? Я попытался изменить plt.ylim() и plt.xlim(), что сделало тепловую карту больше, но я все еще не мог видеть футбольное поле Тепловая карта
Я думаю, что проще всего было бы изменить вашу подачу так, чтобы вместо [0-120, 0-80] они стали [0-4, 0-3]. Я не знаю, как масштабировать тепловую карту моря, поскольку она отображается в виде категорий. Поэтому вам, возможно, придется рассмотреть другие способы создания тепловой карты (например, pcolormesh).
Как показано в Ответ @simon-rogers, у вас есть проблема масштаба между вашим рисунком поля и вашей тепловой картой.
Тепловая карта Seaborn будет нарисована на оси с ограничениями: [0-количество столбцов] по x и [0-количество строк] по y. Таким образом, в вашем примере кадра данных результирующий график имеет размер 4x3, а ваш шаг - 120x80.
Решение состоит в том, чтобы нарисовать поле в масштабе 4x3. А еще лучше нарисуйте поле с параметризованными размерами, чтобы оно соответствовало тепловым картам различных размеров.
Я начал работать над функцией, но у меня не было времени вычислять соотношения для кругов, оставляю это вам в качестве упражнения.
def draw_pitch(ax, width=120, height=80):
# size of the pitch is width, height
#Create figure
#Pitch Outline & Centre Line
plt.plot([0,0],[0,height], color = "black")
plt.plot([0,width],[height,height], color = "black")
plt.plot([width,width],[height,0], color = "black")
plt.plot([width,0],[0,0], color = "black")
plt.plot([width/2,width/2],[0,height], color = "black")
#Left Penalty Area
plt.plot([width*0.12,width*0.12],[height*0.72,height*0.28],color = "black")
plt.plot([0,width*0.12],[height*0.72,height*0.72],color = "black")
plt.plot([0,width*0.12],[height*0.28,height*0.28],color = "black")
#Right Penalty Area
plt.plot([width,width*0.88],[height*0.72,height*0.72],color = "black")
plt.plot([width*0.88,width*0.88],[height*0.72,height*0.28],color = "black")
plt.plot([width, width*0.88],[height*0.28,height*0.28],color = "black")
#Left 6-yard Box
plt.plot([0,width*0.04],[height*0.6,height*0.6],color = "black")
plt.plot([width*0.04,width*0.04],[height*0.6,height*0.4],color = "black")
plt.plot([0,width*0.04],[height*0.4,height*0.4],color = "black")
#Right 6-yard Box
plt.plot([width,width*0.96],[height*0.6,height*0.6],color = "black")
plt.plot([width*0.96,width*0.96],[height*0.6,height*0.4],color = "black")
plt.plot([width,width*0.96],[height*0.4,height*0.4],color = "black")
#Prepare Circles
centreCircle = plt.Circle((width/2,40),8.1,color = "black",fill=False)
centreSpot = plt.Circle((width/2,40),0.71,color = "black")
leftPenSpot = plt.Circle((9.7,40),0.71,color = "black")
rightPenSpot = plt.Circle((110.3,40),0.71,color = "black")
#Draw Circles
ax.add_patch(centreCircle)
ax.add_patch(centreSpot)
ax.add_patch(leftPenSpot)
ax.add_patch(rightPenSpot)
#Prepare Arcs
# arguments for arc
# x, y coordinate of centerpoint of arc
# width, height as arc might not be circle, but oval
# angle: degree of rotation of the shape, anti-clockwise
# theta1, theta2, start and end location of arc in degree
leftArc = plt_p.Arc((9.7,40),height=16.2,width=16.2,angle=0,theta1=310,theta2=50,color = "black")
rightArc = plt_p.Arc((110.3,40),height=16.2,width=16.2,angle=0,theta1=130,theta2=230,color = "black")
#Draw Arcs
ax.add_patch(leftArc)
ax.add_patch(rightArc)
Зачем подгонять шаг под тепловую карту, а не наоборот? Мне кажется гораздо более естественным; иначе, если у вас будет больше данных, шаг будет расти?!
Было бы легче точно. Я не смог найти способ указать протяженность тепловой карты с помощью Seaborn, поэтому вместо этого я решил масштабировать шаг. Суть в том, чтобы сохранить правильный размер шага для фрейма данных, но это хорошо работает только в том случае, если тепловая карта имеет соотношение 4/3, иначе все будет выглядеть странно.
Как уже отмечалось, я бы рекомендовал не использовать карту seaborn.heatmap, потому что практически невозможно масштабировать ее до размера поля.
Если вы больше не будете вносить изменения в свой код, просто замените строку sns.heatmap(...) на
ax.imshow(data_pivot.values, zorder=0, aspect = "auto", extent=(0,120,0,80),
cmap=sns.cubehelix_palette(light=1, as_cmap=True))
Сюжет уже похож
Вы все еще можете играть с альфой или использовать другую цветовую карту и т. д. Чтобы также аннотировать тепловую карту, вы можете сделать это через
scale = np.array([120,80])
ax.imshow(data_pivot.values, zorder=0, aspect = "auto", extent=(0,scale[0],0,scale[1]),
cmap=sns.cubehelix_palette(light=1, as_cmap=True), origin = "lower")
offs = np.array([scale[0]/data_pivot.values.shape[1], scale[1]/data_pivot.values.shape[0]])
for pos, val in np.ndenumerate(data_pivot.values):
ax.annotate(f"{val:.2f}", xy=np.array(pos)[::-1]*offs+offs/2, ha = "center", va = "center")
ax.invert_yaxis()
Я бы посоветовал вам ознакомиться с различными уровнями абстракции, на которых вы можете взаимодействовать с matplotlib (например, здесь). Когда вы хотите работать с наложениями, очень важно понимать и иметь полный контроль над осями в вашей фигуре.