У меня есть фрейм данных, который включает в себя данные о позициях всех 9 игроков на бейсбольном поле, включая нападающего на протяжении всей игры, а также траекторию мяча. Мне нужна помощь, чтобы выяснить, почему моя анимация не работает. Код ниже отображает экземпляр графика, но не отображает непрерывную анимацию. Другими словами, он должен показывать непрерывно движущиеся точки. Вот мой код:
import pandas as pd
from sportypy.surfaces import MiLBField
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
# The dimensions are not exactly like this but this is an example if you need something to go off of
num_rows = 50
data = {
'game_str': ['game_01'] * num_rows,
'play_id': [10] * num_rows,
'timestamp': np.random.randint(180000, 181000, size=num_rows),
'player_position': np.random.randint(1, 11, size=num_rows),
'field_x': np.random.uniform(-150, 150, size=num_rows),
'field_y': np.random.uniform(-150, 150, size=num_rows),
'ball_position_x': np.random.uniform(0.0, 2.0, size=num_rows),
'ball_position_y': np.random.uniform(0.0, 300.0, size=num_rows),
'ball_position_z': np.random.uniform(0.0, 10.0, size=num_rows)
}
df = pd.DataFrame(data).sort_values(by='timestamp')
field = MiLBField()
def update(frame):
frame_data = df[df['timestamp'] <= frame]
players = frame_data[['field_x', 'field_y']]
balls = frame_data[['ball_position_x', 'ball_position_y']]
plt.clf()
field.draw(display_range='full')
p = field.scatter(players['field_x'], players['field_y'])
b = field.scatter(balls['ball_position_x'], balls['ball_position_y'])
return p, b
fig = plt.figure()
ani = FuncAnimation(fig, update, frames=np.linspace(df['timestamp'].min(), df['timestamp'].max(), num=100), blit=True)
plt.show()
Я хотел бы, чтобы он выводил бейсбольное поле с точками разброса, перемещающимися по мере увеличения времени.
это работает намного быстрее, когда я выхожу field.draw(display_range='full') на улицу update(). Но по-прежнему создаются два окна - одно с изображением поля, второе с разбросом и движущимися точками. Я думаю, что, возможно, потребуется использовать ax=ax в draw(), аналогично ответу в вопросе python - Sportypy не видит поле - Переполнение стека. ИЛИ, возможно, потребуется использовать fig = plt.gcf() (получить текущую цифру) вместо pf fig = plt.figure()
Пример на главной странице sportypy (в разделе Adding Analyses and Plotting Data) также используйте fig, ax = plt.subplots(1, 1)phf.draw(ax = ax)
Другая проблема заключается в том, что plt.clf() удаляет сохраненное изображение и создает нормальное разброс с белым фоном и осями.






В коде есть несколько проблем:
Используя field.draw() внутри update(), он пытается создать множество сюжетов, что замедляет всю программу. Но в matplotlib создать его можно только один раз — снаружи update()
Он создает два графика: один с полями, второй с графиком и данными. И даже пример на домашней странице sportypy (в разделе «Добавление данных анализа и построения графиков») использует
fig, ax = plt.subplots(1, 1)
phf.draw(ax=ax)
который создает fig и ax перед draw(), поэтому он может использовать это ax в draw()
Но для меня это создает белый фон с зеленым треугольником вместо зеленого фона с зеленым треугольником - и решением было gcf() (получить текущую фигуру), которое fig создается (автоматически) field.draw()
field.draw(display_range='full')
fig = plt.gcf()
Последняя проблема — plt.clf(), которая удаляет все из графика — поэтому она удаляет field и рисует обычный разброс с белым фоном и осью. Я удалил его, и теперь он показывает данные в поле.
Полный рабочий код:
Я добавил ani.save('animation.gif', writer='imagemagick', fps=2)
записать это в анимированном GIF. Требуется внешняя программа imagemagick
Я добавил np.random.seed(0), чтобы все тестировали код с одинаковыми значениями.
Я добавил цвета игрокам, используя player_position, и red всем шарам.
Для теста я использовал num=10 вместо num=100 в FuncAnimation(), чтобы запустить его быстрее.
import pandas as pd
from sportypy.surfaces import MiLBField
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
# The dimensions are not exactly like this but this is an example if you need something to go off of
num_rows = 50
np.random.seed(0) # it will always generate the same data - so it is simpler to compare them
data = {
'game_str': ['game_01'] * num_rows,
'play_id': [10] * num_rows,
'timestamp': np.random.randint(180000, 181000, size=num_rows),
'player_position': np.random.randint(1, 11, size=num_rows),
'field_x': np.random.uniform(-150, 150, size=num_rows),
'field_y': np.random.uniform(-150, 150, size=num_rows),
'ball_position_x': np.random.uniform(0.0, 2.0, size=num_rows),
'ball_position_y': np.random.uniform(0.0, 300.0, size=num_rows),
'ball_position_z': np.random.uniform(0.0, 10.0, size=num_rows)
}
df = pd.DataFrame(data).sort_values(by='timestamp')
field = MiLBField()
# it shows white background with green triangle
#fig, ax = plt.subplots(1, 1) # get figure before drawing
#field.draw(display_range='full', ax=ax)
# it shows green background with green triangle
field.draw(display_range='full') # without ax=
fig = plt.gcf() # get figure after drawing
def update(frame):
print(f'frame: {frame:.2f}')
frame_data = df[ df['timestamp'] <= frame ]
#frame_data = df[ df['timestamp'] <= frame ].drop_duplicates(subset=['player_position'], keep='last')
print('len(frame_data):', len(frame_data))
players = frame_data # no need [['field_x', 'field_y']]
balls = frame_data # no need [['ball_position_x', 'ball_position_y']]
#players = frame_data.drop_duplicates(subset=['player_position'], keep='last')
print('len(players), len(balls):', len(players), len(balls))
players_colors = players['player_position']
balls_colors = ['red'] * len(balls)
p = field.scatter(players['field_x'], players['field_y'], c=players_colors)
b = field.scatter(balls['ball_position_x'], balls['ball_position_y'], c=balls_colors)
return p, b
ani = FuncAnimation(fig, update, frames=np.linspace(df['timestamp'].min(), df['timestamp'].max(), num=10), blit=True)
ani.save('animation.gif', writer='imagemagick', fps=2)
plt.show()
Но здесь есть еще одна "проблема" - df['timestamp'] <= frame - все позиции отображаются с самого начала. Возможно, потребуется использовать previous_frame <= df['timestamp'] <= frame, чтобы показывать только последние позиции. Но при этом удаляются все объекты, когда между previous_frame, frame нет данных.
previous_frame = None
def update(frame):
global previous_frame
print(f'frame: {frame:.2f}')
frame_data = df[ df['timestamp'] <= frame ]
if previous_frame is None or previous_frame > frame:
mask1 = (df['timestamp'] <= frame)
frame_data = df[ mask1 ]
else:
mask1 = (df['timestamp'] <= frame)
mask2 = (df['timestamp'] > previous_frame)
frame_data = df[ mask1 & mask2 ]
previous_frame = frame
players = frame_data # no need [['field_x', 'field_y']]
balls = frame_data # no need [['ball_position_x', 'ball_position_y']]
p = field.scatter(players['field_x'], players['field_y'])
b = field.scatter(balls['ball_position_x'], balls['ball_position_y'])
return p, b
Возможно, потребуется фильтровать данные по play_id или player_position и сохранять только последнюю позицию для каждого play_id или player_position.
Что-то вроде:
frame_data = df[ df['timestamp'] <= frame ].drop_duplicates(subset=['player_position'], keep='last')
Или, может быть, отфильтровать только игроков, но сохранить все мячи
frame_data = df[ df['timestamp'] <= frame ]
players = frame_data.drop_duplicates(subset=['player_position'], keep='last') # no need [['field_x', 'field_y']]
balls = frame_data # no need [['ball_position_x', 'ball_position_y']]
Я добавил цвета игрокам, используя player_position, и добавил red ко всем мячам.
Идеальный. Я поиграюсь с этим и посмотрю, что у меня получится. Я постараюсь устранить следы игроков и мяча, поэтому буду благодарен за ваш вклад. Спасибо. Это огромная помощь.
Возможно, сначала используйте
print()(иprint(type(...)),print(len(...))и т. д.), чтобы увидеть, какая часть кода выполняется и что на самом деле у вас есть в переменных. Он называется"print debugging"и помогает увидеть, что на самом деле делает код.