Создание анимации бейсбольного поля

У меня есть фрейм данных, который включает в себя данные о позициях всех 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()

Я хотел бы, чтобы он выводил бейсбольное поле с точками разброса, перемещающимися по мере увеличения времени.

Возможно, сначала используйте print()print(type(...)), print(len(...)) и т. д.), чтобы увидеть, какая часть кода выполняется и что на самом деле у вас есть в переменных. Он называется "print debugging" и помогает увидеть, что на самом деле делает код.

furas 13.06.2024 10:39

это работает намного быстрее, когда я выхожу field.draw(display_range='full') на улицу update(). Но по-прежнему создаются два окна - одно с изображением поля, второе с разбросом и движущимися точками. Я думаю, что, возможно, потребуется использовать ax=ax в draw(), аналогично ответу в вопросе python - Sportypy не видит поле - Переполнение стека. ИЛИ, возможно, потребуется использовать fig = plt.gcf() (получить текущую цифру) вместо pf fig = plt.figure()

furas 13.06.2024 10:54

Пример на главной странице sportypy (в разделе Adding Analyses and Plotting Data) также используйте fig, ax = plt.subplots(1, 1)phf.draw(ax = ax)

furas 13.06.2024 11:01

Другая проблема заключается в том, что plt.clf() удаляет сохраненное изображение и создает нормальное разброс с белым фоном и осями.

furas 13.06.2024 11:07
Почему в 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 может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
4
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

В коде есть несколько проблем:

Используя 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 ко всем мячам.

furas 13.06.2024 13:12

Идеальный. Я поиграюсь с этим и посмотрю, что у меня получится. Я постараюсь устранить следы игроков и мяча, поэтому буду благодарен за ваш вклад. Спасибо. Это огромная помощь.

Newman 13.06.2024 16:02

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