rustat-python-api 0.4.12__tar.gz → 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {rustat-python-api-0.4.12/rustat_python_api.egg-info → rustat-python-api-0.5.1}/PKG-INFO +16 -4
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/README.md +15 -3
- rustat-python-api-0.5.1/rustat_python_api/__init__.py +5 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api/parser.py +5 -1
- rustat-python-api-0.5.1/rustat_python_api/pitch_control.py +311 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1/rustat_python_api.egg-info}/PKG-INFO +16 -4
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api.egg-info/SOURCES.txt +1 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api.egg-info/requires.txt +3 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/setup.py +5 -2
- rustat-python-api-0.4.12/rustat_python_api/__init__.py +0 -4
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/LICENSE +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/pyproject.toml +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api/config.py +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api/models_api.py +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api/processing.py +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api/urls.py +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api.egg-info/dependency_links.txt +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api.egg-info/top_level.txt +0 -0
- {rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rustat-python-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: A Python wrapper for RuStat API
|
|
5
5
|
Home-page: https://github.com/dailydaniel/rustat-python-api
|
|
6
6
|
Author: Daniel Zholkovsky
|
|
@@ -23,7 +23,7 @@ pip install rustat-python-api
|
|
|
23
23
|
```
|
|
24
24
|
1. Usage:
|
|
25
25
|
```python
|
|
26
|
-
from rustat_python_api import RuStatParser
|
|
26
|
+
from rustat_python_api import RuStatParser, DynamoLab, PitchControl
|
|
27
27
|
|
|
28
28
|
user = "your_login"
|
|
29
29
|
password = "your_password"
|
|
@@ -39,8 +39,20 @@ keys = list(schedule.keys())
|
|
|
39
39
|
match_id = keys[-1]
|
|
40
40
|
|
|
41
41
|
events, subs = parser.get_events(match_id, process=True, return_subs=True)
|
|
42
|
-
|
|
43
42
|
stats = parser.get_match_stats(match_id)
|
|
44
|
-
|
|
45
43
|
tracking = parser.get_tracking(match_id)
|
|
44
|
+
|
|
45
|
+
host = "http://localhost:8001/"
|
|
46
|
+
client = DynamoLab(host)
|
|
47
|
+
client.run_model(
|
|
48
|
+
model="xT",
|
|
49
|
+
data=events,
|
|
50
|
+
inplace=True,
|
|
51
|
+
inplace_column=model
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
pc = PitchControl(tracking, events)
|
|
55
|
+
pc.draw_pitch_control(half=1, tp=100, save=True, filename="pitch_control")
|
|
56
|
+
# ffmpeg required for animation
|
|
57
|
+
pc.animate_pitch_control(half=1, tp=100, frames=30, filename="pitch_control")
|
|
46
58
|
```
|
|
@@ -8,7 +8,7 @@ pip install rustat-python-api
|
|
|
8
8
|
```
|
|
9
9
|
1. Usage:
|
|
10
10
|
```python
|
|
11
|
-
from rustat_python_api import RuStatParser
|
|
11
|
+
from rustat_python_api import RuStatParser, DynamoLab, PitchControl
|
|
12
12
|
|
|
13
13
|
user = "your_login"
|
|
14
14
|
password = "your_password"
|
|
@@ -24,8 +24,20 @@ keys = list(schedule.keys())
|
|
|
24
24
|
match_id = keys[-1]
|
|
25
25
|
|
|
26
26
|
events, subs = parser.get_events(match_id, process=True, return_subs=True)
|
|
27
|
-
|
|
28
27
|
stats = parser.get_match_stats(match_id)
|
|
29
|
-
|
|
30
28
|
tracking = parser.get_tracking(match_id)
|
|
29
|
+
|
|
30
|
+
host = "http://localhost:8001/"
|
|
31
|
+
client = DynamoLab(host)
|
|
32
|
+
client.run_model(
|
|
33
|
+
model="xT",
|
|
34
|
+
data=events,
|
|
35
|
+
inplace=True,
|
|
36
|
+
inplace_column=model
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
pc = PitchControl(tracking, events)
|
|
40
|
+
pc.draw_pitch_control(half=1, tp=100, save=True, filename="pitch_control")
|
|
41
|
+
# ffmpeg required for animation
|
|
42
|
+
pc.animate_pitch_control(half=1, tp=100, frames=30, filename="pitch_control")
|
|
31
43
|
```
|
|
@@ -167,7 +167,11 @@ class RuStatParser:
|
|
|
167
167
|
|
|
168
168
|
df = pd.concat([df, cur_df], ignore_index=True)
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
df = df.sort_values(by=['half', 'second', 'team_id', 'player_id']).reset_index(drop=True)
|
|
171
|
+
df['pos_x'] = df['pos_x'] + 105/2
|
|
172
|
+
df['second'] = df['second'].astype(int)
|
|
173
|
+
|
|
174
|
+
return df
|
|
171
175
|
|
|
172
176
|
def get_match_stats(self, match_id: int) -> dict:
|
|
173
177
|
data = self.resp2data(
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
from scipy.stats import multivariate_normal as mvn
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.animation as animation
|
|
6
|
+
import matplotsoccer as mpl
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PitchControl:
|
|
10
|
+
def __init__(self, tracking: pd.DataFrame, events: pd.DataFrame):
|
|
11
|
+
self.team_ids = tracking['team_id'].unique()
|
|
12
|
+
sides = tracking.groupby('team_id')['side_1h'].unique()
|
|
13
|
+
side_by_team = dict(zip(self.team_ids, sides[self.team_ids].apply(lambda x: x[0])))
|
|
14
|
+
self.side_by_half = {
|
|
15
|
+
1: side_by_team,
|
|
16
|
+
2:
|
|
17
|
+
{
|
|
18
|
+
team: 'left' if side == 'right' else 'right'
|
|
19
|
+
for team, side in side_by_team.items()
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
self.locs_home, self.locs_away, self.locs_ball, self.t = self.get_locs(
|
|
24
|
+
tracking,
|
|
25
|
+
events
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def get_locs(self, tracking: pd.DataFrame, events: pd.DataFrame) -> tuple:
|
|
29
|
+
events = events[[
|
|
30
|
+
'possession_number', 'team_id', 'possession_team_id',
|
|
31
|
+
'half', 'second', 'pos_x', 'pos_y'
|
|
32
|
+
]]
|
|
33
|
+
|
|
34
|
+
events.loc[:, 'pos_x'] = events.apply(
|
|
35
|
+
lambda x: self.swap_coords(x, 'x'), axis=1
|
|
36
|
+
)
|
|
37
|
+
events.loc[:, 'pos_y'] = events.apply(
|
|
38
|
+
lambda x: self.swap_coords(x, 'y'), axis=1
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
ball_data = self.interpolate_ball_data(
|
|
42
|
+
events[['half', 'second', 'pos_x', 'pos_y']],
|
|
43
|
+
tracking
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
locs_home = {
|
|
47
|
+
half: {
|
|
48
|
+
player_id: self.get_player_data(player_id, half, tracking)
|
|
49
|
+
for player_id in tracking[tracking['side_1h'] == 'left']['player_id'].unique()
|
|
50
|
+
}
|
|
51
|
+
for half in tracking['half'].unique()
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
locs_away = {
|
|
55
|
+
half: {
|
|
56
|
+
player_id: self.get_player_data(player_id, half, tracking)
|
|
57
|
+
for player_id in tracking[tracking['side_1h'] == 'right']['player_id'].unique()
|
|
58
|
+
}
|
|
59
|
+
for half in tracking['half'].unique()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
locs_ball = {
|
|
63
|
+
half: ball_data[ball_data['half'] == half][['pos_x', 'pos_y']].values
|
|
64
|
+
for half in tracking['half'].unique()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
t = {
|
|
68
|
+
half: ball_data[ball_data['half'] == 1]['second'].values
|
|
69
|
+
for half in tracking['half'].unique()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return locs_home, locs_away, locs_ball, t
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def swap_coords(self, row, how: str = 'x'):
|
|
76
|
+
half = row['half']
|
|
77
|
+
team_id = row['team_id']
|
|
78
|
+
possession_team_id = row['possession_team_id']
|
|
79
|
+
x = row['pos_x']
|
|
80
|
+
y = row['pos_y']
|
|
81
|
+
|
|
82
|
+
if isinstance(possession_team_id, list):
|
|
83
|
+
current_side = 'left' if team_id in possession_team_id else 'right'
|
|
84
|
+
real_side = self.side_by_half[half][str(int(team_id))]
|
|
85
|
+
else:
|
|
86
|
+
current_side = 'left' if team_id == possession_team_id else 'right'
|
|
87
|
+
real_side = self.side_by_half[half][str(int(team_id))]
|
|
88
|
+
|
|
89
|
+
if current_side != real_side:
|
|
90
|
+
if how == 'x':
|
|
91
|
+
x = 105 - x
|
|
92
|
+
else:
|
|
93
|
+
y = 68 - y
|
|
94
|
+
|
|
95
|
+
return x if how == 'x' else y
|
|
96
|
+
|
|
97
|
+
@staticmethod
|
|
98
|
+
def interpolate_ball_data(
|
|
99
|
+
ball_data: pd.DataFrame,
|
|
100
|
+
player_data: pd.DataFrame
|
|
101
|
+
) -> pd.DataFrame:
|
|
102
|
+
ball_data = ball_data.drop_duplicates(subset=['second', 'half'])
|
|
103
|
+
|
|
104
|
+
interpolated_data = []
|
|
105
|
+
for half in ball_data['half'].unique():
|
|
106
|
+
ball_half = ball_data[ball_data['half'] == half]
|
|
107
|
+
player_half = player_data[player_data['half'] == half]
|
|
108
|
+
|
|
109
|
+
player_times = player_half['second'].unique()
|
|
110
|
+
|
|
111
|
+
ball_half = ball_half.sort_values(by='second')
|
|
112
|
+
interpolated_half = pd.DataFrame({'second': player_times})
|
|
113
|
+
interpolated_half['pos_x'] = np.interp(
|
|
114
|
+
interpolated_half['second'], ball_half['second'], ball_half['pos_x']
|
|
115
|
+
)
|
|
116
|
+
interpolated_half['pos_y'] = np.interp(
|
|
117
|
+
interpolated_half['second'], ball_half['second'], ball_half['pos_y']
|
|
118
|
+
)
|
|
119
|
+
interpolated_half['half'] = half
|
|
120
|
+
interpolated_data.append(interpolated_half)
|
|
121
|
+
|
|
122
|
+
interpolated_ball_data = pd.concat(interpolated_data, ignore_index=True)
|
|
123
|
+
return interpolated_ball_data
|
|
124
|
+
|
|
125
|
+
@staticmethod
|
|
126
|
+
def get_player_data(player_id, half, tracking):
|
|
127
|
+
return tracking[
|
|
128
|
+
(tracking['player_id'] == player_id)
|
|
129
|
+
& (tracking['half'] == half)
|
|
130
|
+
][['pos_x', 'pos_y']].values
|
|
131
|
+
|
|
132
|
+
def influence_function(
|
|
133
|
+
self,
|
|
134
|
+
player_index: str,
|
|
135
|
+
location: np.ndarray,
|
|
136
|
+
time_index: int,
|
|
137
|
+
home_or_away: str,
|
|
138
|
+
half: int
|
|
139
|
+
):
|
|
140
|
+
if home_or_away == 'h':
|
|
141
|
+
data = self.locs_home[half].copy()
|
|
142
|
+
elif home_or_away == 'a':
|
|
143
|
+
data = self.locs_away[half].copy()
|
|
144
|
+
else:
|
|
145
|
+
raise ValueError("Enter either 'h' or 'a'.")
|
|
146
|
+
|
|
147
|
+
locs_ball = self.locs_ball[half].copy()
|
|
148
|
+
t = self.t[half].copy()
|
|
149
|
+
|
|
150
|
+
if (
|
|
151
|
+
np.all(np.isfinite(data[player_index][[time_index, time_index + 1], :]))
|
|
152
|
+
& np.all(np.isfinite(locs_ball[time_index, :]))
|
|
153
|
+
):
|
|
154
|
+
jitter = 1e-10 ## to prevent identically zero covariance matrices when velocity is zero
|
|
155
|
+
## compute velocity by fwd difference
|
|
156
|
+
s = (
|
|
157
|
+
np.linalg.norm(
|
|
158
|
+
data[player_index][time_index + 1,:]
|
|
159
|
+
- data[player_index][time_index,:] + jitter
|
|
160
|
+
)
|
|
161
|
+
/ (t[time_index + 1] - t[time_index])
|
|
162
|
+
)
|
|
163
|
+
## velocities in x,y directions
|
|
164
|
+
sxy = (
|
|
165
|
+
(data[player_index][time_index + 1, :] - data[player_index][time_index, :] + jitter)
|
|
166
|
+
/ (t[time_index + 1] - t[time_index])
|
|
167
|
+
)
|
|
168
|
+
## angle between velocity vector & x-axis
|
|
169
|
+
theta = np.arccos(sxy[0] / np.linalg.norm(sxy))
|
|
170
|
+
## rotation matrix
|
|
171
|
+
R = np.array([[np.cos(theta), -np.sin(theta)],[np.sin(theta), np.cos(theta)]])
|
|
172
|
+
mu = data[player_index][time_index, :] + sxy * 0.5
|
|
173
|
+
Srat = (s / 13) ** 2
|
|
174
|
+
Ri = np.linalg.norm(locs_ball[time_index, :] - data[player_index][time_index, :])
|
|
175
|
+
## don't think this function is specified in the paper but looks close enough to fig 9
|
|
176
|
+
Ri = np.minimum(4 + Ri ** 3/ (18 ** 3 / 6), 10)
|
|
177
|
+
S = np.array([[(1 + Srat) * Ri / 2, 0], [0, (1 - Srat) * Ri / 2]])
|
|
178
|
+
Sigma = np.matmul(R, S)
|
|
179
|
+
Sigma = np.matmul(Sigma, S)
|
|
180
|
+
Sigma = np.matmul(Sigma, np.linalg.inv(R)) ## this is not efficient, forgive me.
|
|
181
|
+
out = mvn.pdf(location, mu, Sigma) / mvn.pdf(data[player_index][time_index, :], mu, Sigma)
|
|
182
|
+
else:
|
|
183
|
+
print("Data is not finite.")
|
|
184
|
+
out = np.zeros(location.shape[0])
|
|
185
|
+
return out
|
|
186
|
+
|
|
187
|
+
def fit(self, half: int, tp: int, dt: int) -> tuple:
|
|
188
|
+
x = np.linspace(0, 105, dt)
|
|
189
|
+
y = np.linspace(0, 68, dt)
|
|
190
|
+
xx, yy = np.meshgrid(x, y)
|
|
191
|
+
|
|
192
|
+
Zh = np.zeros(dt*dt)
|
|
193
|
+
Za = np.zeros(dt*dt)
|
|
194
|
+
|
|
195
|
+
locations = np.c_[xx.flatten(),yy.flatten()]
|
|
196
|
+
|
|
197
|
+
for k in self.locs_home[half].keys():
|
|
198
|
+
if len(self.locs_home[half][k]) >= tp:
|
|
199
|
+
Zh += self.influence_function(k, locations, tp, 'h', half)
|
|
200
|
+
for k in self.locs_away[half].keys():
|
|
201
|
+
if len(self.locs_away[half][k]) >= tp:
|
|
202
|
+
Za += self.influence_function(k, locations, tp, 'a', half)
|
|
203
|
+
|
|
204
|
+
Zh = Zh.reshape((dt, dt))
|
|
205
|
+
Za = Za.reshape((dt, dt))
|
|
206
|
+
result = 1 / (1 + np.exp(-Za + Zh))
|
|
207
|
+
|
|
208
|
+
return result, xx, yy
|
|
209
|
+
|
|
210
|
+
def draw_pitch_control(
|
|
211
|
+
self,
|
|
212
|
+
half: int,
|
|
213
|
+
tp: int,
|
|
214
|
+
pitch_control: tuple = None,
|
|
215
|
+
save: bool = False,
|
|
216
|
+
dt: int = 200,
|
|
217
|
+
filename: str = 'pitch_control'
|
|
218
|
+
):
|
|
219
|
+
if pitch_control is None:
|
|
220
|
+
pitch_control, xx, yy = self.fit(half, tp, dt)
|
|
221
|
+
|
|
222
|
+
fig, ax = plt.subplots(figsize=(10.5, 6.8))
|
|
223
|
+
mpl.field("white", show=False, ax=ax)
|
|
224
|
+
|
|
225
|
+
plt.contourf(xx, yy, pitch_control)
|
|
226
|
+
|
|
227
|
+
for k in self.locs_home[half].keys():
|
|
228
|
+
if len(self.locs_home[half][k]) >= tp:
|
|
229
|
+
plt.scatter(
|
|
230
|
+
self.locs_home[half][k][tp, 0],
|
|
231
|
+
self.locs_home[half][k][tp, 1],
|
|
232
|
+
color='darkgrey'
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
for k in self.locs_away[half].keys():
|
|
236
|
+
if len(self.locs_away[half][k]) >= tp:
|
|
237
|
+
plt.scatter(
|
|
238
|
+
self.locs_away[half][k][tp, 0],
|
|
239
|
+
self.locs_away[half][k][tp, 1], color='black'
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
plt.scatter(
|
|
243
|
+
self.locs_ball[half][tp, 0],
|
|
244
|
+
self.locs_ball[half][tp, 1],
|
|
245
|
+
color='red'
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if save:
|
|
249
|
+
plt.savefig(f'{filename}.png', dpi=300)
|
|
250
|
+
else:
|
|
251
|
+
plt.show()
|
|
252
|
+
|
|
253
|
+
def animate_pitch_control(
|
|
254
|
+
self,
|
|
255
|
+
half: int,
|
|
256
|
+
tp: int,
|
|
257
|
+
filename: str = "pitch_control_animation",
|
|
258
|
+
dt: int = 200,
|
|
259
|
+
frames: int = 30,
|
|
260
|
+
interval: int = 1000
|
|
261
|
+
):
|
|
262
|
+
"""
|
|
263
|
+
ffmpeg should be installed on your machine.
|
|
264
|
+
"""
|
|
265
|
+
fig, ax = plt.subplots(figsize=(10.5, 6.8))
|
|
266
|
+
|
|
267
|
+
def animate(i):
|
|
268
|
+
fr = tp + i
|
|
269
|
+
pitch_control, xx, yy = self.fit(half, tp, dt)
|
|
270
|
+
|
|
271
|
+
mpl.field("white", show=False, ax=ax)
|
|
272
|
+
ax.axis('off')
|
|
273
|
+
|
|
274
|
+
plt.contourf(xx, yy, pitch_control)
|
|
275
|
+
|
|
276
|
+
for k in self.locs_home[half].keys():
|
|
277
|
+
if len(self.locs_home[half][k]) >= fr:
|
|
278
|
+
plt.scatter(
|
|
279
|
+
self.locs_home[half][k][fr, 0],
|
|
280
|
+
self.locs_home[half][k][fr, 1],
|
|
281
|
+
color='darkgrey'
|
|
282
|
+
)
|
|
283
|
+
for k in self.locs_away[half].keys():
|
|
284
|
+
if len(self.locs_away[half][k]) >= fr:
|
|
285
|
+
plt.scatter(
|
|
286
|
+
self.locs_away[half][k][fr, 0],
|
|
287
|
+
self.locs_away[half][k][fr, 1],
|
|
288
|
+
color='black'
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
plt.scatter(
|
|
292
|
+
self.locs_ball[half][fr, 0],
|
|
293
|
+
self.locs_ball[half][fr, 1],
|
|
294
|
+
color='red'
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
return ax
|
|
298
|
+
|
|
299
|
+
x = np.linspace(0, 105, dt)
|
|
300
|
+
y = np.linspace(0, 68, dt)
|
|
301
|
+
xx, yy = np.meshgrid(x, y)
|
|
302
|
+
|
|
303
|
+
ani = animation.FuncAnimation(
|
|
304
|
+
fig=fig,
|
|
305
|
+
func=animate,
|
|
306
|
+
frames=min(frames, len(self.locs_ball[half]) - tp),
|
|
307
|
+
interval=interval,
|
|
308
|
+
blit=False
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
ani.save(f'{filename}.mp4', writer='ffmpeg')
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rustat-python-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.1
|
|
4
4
|
Summary: A Python wrapper for RuStat API
|
|
5
5
|
Home-page: https://github.com/dailydaniel/rustat-python-api
|
|
6
6
|
Author: Daniel Zholkovsky
|
|
@@ -23,7 +23,7 @@ pip install rustat-python-api
|
|
|
23
23
|
```
|
|
24
24
|
1. Usage:
|
|
25
25
|
```python
|
|
26
|
-
from rustat_python_api import RuStatParser
|
|
26
|
+
from rustat_python_api import RuStatParser, DynamoLab, PitchControl
|
|
27
27
|
|
|
28
28
|
user = "your_login"
|
|
29
29
|
password = "your_password"
|
|
@@ -39,8 +39,20 @@ keys = list(schedule.keys())
|
|
|
39
39
|
match_id = keys[-1]
|
|
40
40
|
|
|
41
41
|
events, subs = parser.get_events(match_id, process=True, return_subs=True)
|
|
42
|
-
|
|
43
42
|
stats = parser.get_match_stats(match_id)
|
|
44
|
-
|
|
45
43
|
tracking = parser.get_tracking(match_id)
|
|
44
|
+
|
|
45
|
+
host = "http://localhost:8001/"
|
|
46
|
+
client = DynamoLab(host)
|
|
47
|
+
client.run_model(
|
|
48
|
+
model="xT",
|
|
49
|
+
data=events,
|
|
50
|
+
inplace=True,
|
|
51
|
+
inplace_column=model
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
pc = PitchControl(tracking, events)
|
|
55
|
+
pc.draw_pitch_control(half=1, tp=100, save=True, filename="pitch_control")
|
|
56
|
+
# ffmpeg required for animation
|
|
57
|
+
pc.animate_pitch_control(half=1, tp=100, frames=30, filename="pitch_control")
|
|
46
58
|
```
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='rustat-python-api',
|
|
5
|
-
version='0.
|
|
5
|
+
version='0.5.1',
|
|
6
6
|
description='A Python wrapper for RuStat API',
|
|
7
7
|
long_description=open('README.md').read(),
|
|
8
8
|
long_description_content_type='text/markdown',
|
|
@@ -14,7 +14,10 @@ setup(
|
|
|
14
14
|
install_requires=[
|
|
15
15
|
'requests==2.32.3',
|
|
16
16
|
'pandas==2.2.3',
|
|
17
|
-
'tqdm==4.66.5'
|
|
17
|
+
'tqdm==4.66.5',
|
|
18
|
+
'scipy==1.14.1',
|
|
19
|
+
'matplotlib',
|
|
20
|
+
'matplotsoccer'
|
|
18
21
|
],
|
|
19
22
|
classifiers=[
|
|
20
23
|
'Programming Language :: Python :: 3',
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{rustat-python-api-0.4.12 → rustat-python-api-0.5.1}/rustat_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|