rustat-python-api 0.5.2__tar.gz → 0.5.4__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.5.2/rustat_python_api.egg-info → rustat-python-api-0.5.4}/PKG-INFO +1 -1
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/parser.py +74 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/pitch_control.py +34 -17
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/urls.py +1 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4/rustat_python_api.egg-info}/PKG-INFO +1 -1
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/setup.py +1 -1
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/LICENSE +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/README.md +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/pyproject.toml +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/__init__.py +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/config.py +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/models_api.py +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api/processing.py +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api.egg-info/SOURCES.txt +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api.egg-info/dependency_links.txt +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api.egg-info/requires.txt +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api.egg-info/top_level.txt +0 -0
- {rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/setup.cfg +0 -0
|
@@ -173,6 +173,80 @@ class RuStatParser:
|
|
|
173
173
|
|
|
174
174
|
return df
|
|
175
175
|
|
|
176
|
+
def get_tracking_30fps(
|
|
177
|
+
self,
|
|
178
|
+
match_id: int, half: int, lang_id: int = 0,
|
|
179
|
+
referee_data: int = 0, ball_data: int = 1
|
|
180
|
+
) -> pd.DataFrame | tuple | None:
|
|
181
|
+
data = self.resp2data(
|
|
182
|
+
self.urls["tracking_30fps"].format(
|
|
183
|
+
user=self.user,
|
|
184
|
+
password=self.password,
|
|
185
|
+
match_id=match_id,
|
|
186
|
+
half=half,
|
|
187
|
+
lang_id=lang_id,
|
|
188
|
+
referee_data=referee_data,
|
|
189
|
+
ball_data=ball_data
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if not data:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
teams_data = data['data']['teams']
|
|
197
|
+
if 'ball' in data['data']:
|
|
198
|
+
ball_data = data['data']['ball']
|
|
199
|
+
if 'referee' in data['data']:
|
|
200
|
+
referee_data = data['data']['referee']
|
|
201
|
+
del data
|
|
202
|
+
|
|
203
|
+
tracking = pd.DataFrame(columns=[
|
|
204
|
+
"half", "second", "pos_x", "pos_y", "team_id", "player_id", "player_name", "distance", "speed"
|
|
205
|
+
])
|
|
206
|
+
|
|
207
|
+
for team_data in tqdm(teams_data):
|
|
208
|
+
team_id = team_data["id"]
|
|
209
|
+
side_1h = team_data["gate_position_half_1"]
|
|
210
|
+
|
|
211
|
+
for player_data in team_data["players"]:
|
|
212
|
+
player_id = player_data["id"]
|
|
213
|
+
player_name = player_data["name"]
|
|
214
|
+
|
|
215
|
+
cur_df = pd.json_normalize(player_data["rows"])
|
|
216
|
+
cur_df = cur_df.apply(pd.to_numeric, errors='coerce')
|
|
217
|
+
cur_df["team_id"] = team_id
|
|
218
|
+
cur_df["player_id"] = player_id
|
|
219
|
+
cur_df["player_name"] = player_name
|
|
220
|
+
cur_df["side_1h"] = side_1h
|
|
221
|
+
|
|
222
|
+
tracking = pd.concat([tracking, cur_df], ignore_index=True)
|
|
223
|
+
|
|
224
|
+
tracking['second'] = tracking['second'].astype(float)
|
|
225
|
+
tracking = tracking.sort_values(by=['half', 'second', 'team_id', 'player_id']).reset_index(drop=True)
|
|
226
|
+
tracking['pos_x'] = tracking['pos_x'] + 105/2
|
|
227
|
+
tracking['team_id'] = tracking['team_id'].astype(str)
|
|
228
|
+
|
|
229
|
+
if referee_data:
|
|
230
|
+
referee = pd.json_normalize(referee_data['rows'])
|
|
231
|
+
referee['second'] = referee['second'].astype(float)
|
|
232
|
+
referee = referee.sort_values(by=['half', 'second']).reset_index(drop=True)
|
|
233
|
+
referee['pos_x'] = referee['pos_x'] + 105/2
|
|
234
|
+
|
|
235
|
+
if ball_data:
|
|
236
|
+
ball = pd.json_normalize(ball_data['rows'])
|
|
237
|
+
ball['second'] = ball['second'].astype(float)
|
|
238
|
+
ball = ball.sort_values(by=['half', 'second']).reset_index(drop=True)
|
|
239
|
+
ball['pos_x'] = ball['pos_x'] + 105/2
|
|
240
|
+
|
|
241
|
+
if referee_data and ball_data:
|
|
242
|
+
return tracking, referee, ball
|
|
243
|
+
elif referee_data:
|
|
244
|
+
return tracking, referee
|
|
245
|
+
elif ball_data:
|
|
246
|
+
return tracking, ball
|
|
247
|
+
else:
|
|
248
|
+
return tracking
|
|
249
|
+
|
|
176
250
|
def get_match_stats(self, match_id: int) -> dict:
|
|
177
251
|
data = self.resp2data(
|
|
178
252
|
self.urls["match_stats"].format(
|
|
@@ -7,7 +7,7 @@ import matplotsoccer as mpl
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class PitchControl:
|
|
10
|
-
def __init__(self, tracking: pd.DataFrame, events: pd.DataFrame):
|
|
10
|
+
def __init__(self, tracking: pd.DataFrame, events: pd.DataFrame, ball_data: pd.DataFrame = None):
|
|
11
11
|
self.team_ids = tracking['team_id'].unique()
|
|
12
12
|
sides = tracking.groupby('team_id')['side_1h'].unique()
|
|
13
13
|
side_by_team = dict(zip(self.team_ids, sides[self.team_ids].apply(lambda x: x[0])))
|
|
@@ -22,10 +22,11 @@ class PitchControl:
|
|
|
22
22
|
|
|
23
23
|
self.locs_home, self.locs_away, self.locs_ball, self.t = self.get_locs(
|
|
24
24
|
tracking,
|
|
25
|
-
events
|
|
25
|
+
events,
|
|
26
|
+
ball_data
|
|
26
27
|
)
|
|
27
28
|
|
|
28
|
-
def get_locs(self, tracking: pd.DataFrame, events: pd.DataFrame) -> tuple:
|
|
29
|
+
def get_locs(self, tracking: pd.DataFrame, events: pd.DataFrame, ball_data: pd.DataFrame | None) -> tuple:
|
|
29
30
|
events = events[[
|
|
30
31
|
'possession_number', 'team_id', 'possession_team_id',
|
|
31
32
|
'half', 'second', 'pos_x', 'pos_y'
|
|
@@ -38,10 +39,11 @@ class PitchControl:
|
|
|
38
39
|
lambda x: self.swap_coords(x, 'y'), axis=1
|
|
39
40
|
)
|
|
40
41
|
|
|
41
|
-
ball_data
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
if ball_data is None:
|
|
43
|
+
ball_data = self.interpolate_ball_data(
|
|
44
|
+
events[['half', 'second', 'pos_x', 'pos_y']],
|
|
45
|
+
tracking
|
|
46
|
+
)
|
|
45
47
|
|
|
46
48
|
locs_home = {
|
|
47
49
|
half: {
|
|
@@ -124,10 +126,21 @@ class PitchControl:
|
|
|
124
126
|
|
|
125
127
|
@staticmethod
|
|
126
128
|
def get_player_data(player_id, half, tracking):
|
|
127
|
-
|
|
129
|
+
timestamps = tracking[tracking['half'] == half]['second'].unique()
|
|
130
|
+
player_data = tracking[
|
|
128
131
|
(tracking['player_id'] == player_id)
|
|
129
132
|
& (tracking['half'] == half)
|
|
130
|
-
|
|
133
|
+
][['second', 'pos_x', 'pos_y']]
|
|
134
|
+
|
|
135
|
+
player_data_full = pd.DataFrame({'second': timestamps})
|
|
136
|
+
player_data_full = player_data_full.merge(player_data, on='second', how='left')
|
|
137
|
+
|
|
138
|
+
return player_data_full[['pos_x', 'pos_y']].values
|
|
139
|
+
|
|
140
|
+
# return tracking[
|
|
141
|
+
# (tracking['player_id'] == player_id)
|
|
142
|
+
# & (tracking['half'] == half)
|
|
143
|
+
# ][['pos_x', 'pos_y']].values
|
|
131
144
|
|
|
132
145
|
def influence_function(
|
|
133
146
|
self,
|
|
@@ -195,11 +208,11 @@ class PitchControl:
|
|
|
195
208
|
locations = np.c_[xx.flatten(),yy.flatten()]
|
|
196
209
|
|
|
197
210
|
for k in self.locs_home[half].keys():
|
|
198
|
-
if len(self.locs_home[half][k]) >= tp:
|
|
199
|
-
|
|
211
|
+
# if len(self.locs_home[half][k]) >= tp:
|
|
212
|
+
Zh += self.influence_function(k, locations, tp, 'h', half)
|
|
200
213
|
for k in self.locs_away[half].keys():
|
|
201
|
-
if len(self.locs_away[half][k]) >= tp:
|
|
202
|
-
|
|
214
|
+
# if len(self.locs_away[half][k]) >= tp:
|
|
215
|
+
Za += self.influence_function(k, locations, tp, 'a', half)
|
|
203
216
|
|
|
204
217
|
Zh = Zh.reshape((dt, dt))
|
|
205
218
|
Za = Za.reshape((dt, dt))
|
|
@@ -225,7 +238,8 @@ class PitchControl:
|
|
|
225
238
|
plt.contourf(xx, yy, pitch_control)
|
|
226
239
|
|
|
227
240
|
for k in self.locs_home[half].keys():
|
|
228
|
-
if len(self.locs_home[half][k]) >= tp:
|
|
241
|
+
# if len(self.locs_home[half][k]) >= tp:
|
|
242
|
+
if np.isfinite(self.locs_home[half][k][tp, :]).all():
|
|
229
243
|
plt.scatter(
|
|
230
244
|
self.locs_home[half][k][tp, 0],
|
|
231
245
|
self.locs_home[half][k][tp, 1],
|
|
@@ -233,7 +247,8 @@ class PitchControl:
|
|
|
233
247
|
)
|
|
234
248
|
|
|
235
249
|
for k in self.locs_away[half].keys():
|
|
236
|
-
if len(self.locs_away[half][k]) >= tp:
|
|
250
|
+
# if len(self.locs_away[half][k]) >= tp:
|
|
251
|
+
if np.isfinite(self.locs_away[half][k][tp, :]).all():
|
|
237
252
|
plt.scatter(
|
|
238
253
|
self.locs_away[half][k][tp, 0],
|
|
239
254
|
self.locs_away[half][k][tp, 1], color='black'
|
|
@@ -274,14 +289,16 @@ class PitchControl:
|
|
|
274
289
|
plt.contourf(xx, yy, pitch_control)
|
|
275
290
|
|
|
276
291
|
for k in self.locs_home[half].keys():
|
|
277
|
-
if len(self.locs_home[half][k]) >= fr:
|
|
292
|
+
# if len(self.locs_home[half][k]) >= fr:
|
|
293
|
+
if np.isfinite(self.locs_home[half][k][fr, :]).all():
|
|
278
294
|
plt.scatter(
|
|
279
295
|
self.locs_home[half][k][fr, 0],
|
|
280
296
|
self.locs_home[half][k][fr, 1],
|
|
281
297
|
color='darkgrey'
|
|
282
298
|
)
|
|
283
299
|
for k in self.locs_away[half].keys():
|
|
284
|
-
if len(self.locs_away[half][k]) >= fr:
|
|
300
|
+
# if len(self.locs_away[half][k]) >= fr:
|
|
301
|
+
if np.isfinite(self.locs_away[half][k][fr, :]).all():
|
|
285
302
|
plt.scatter(
|
|
286
303
|
self.locs_away[half][k][fr, 0],
|
|
287
304
|
self.locs_away[half][k][fr, 1],
|
|
@@ -4,5 +4,6 @@ URLs = {
|
|
|
4
4
|
"match_stats": "http://feeds.rustatsport.ru/?tpl=207&user={user}&key={password}&match_id={match_id}&lang_id=1&format=json",
|
|
5
5
|
"tournament_teams": "http://feeds.rustatsport.ru/?tpl=32&user={user}&key={password}&tournament_id=2&season_id={season_id}&date_start=&date_end=&lang_id=1&format=json",
|
|
6
6
|
"tracking": "https://feeds.rustatsport.ru/?tpl=274&user={user}&key={password}&match_id={match_id}&lang_id=0&format=json",
|
|
7
|
+
"tracking_30fps": "https://feeds.rustatsport.ru/?tpl=1013&user={user}&key={password}&match_id={match_id}&referee_data={referee_data}&ball_data={ball_data}&half={half}&lang_id={lang_id}&format=json",
|
|
7
8
|
"player_match_stats": "http://feeds.rustatsport.ru/?tpl=227&user={user}&key={password}&match_id={match_id}&lang_id=1&format=json"
|
|
8
9
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{rustat-python-api-0.5.2 → rustat-python-api-0.5.4}/rustat_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|