rustat-python-api 0.1.2__tar.gz → 0.3.0__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.1.2/rustat_python_api.egg-info → rustat-python-api-0.3.0}/PKG-INFO +1 -1
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api/parser.py +20 -4
- rustat-python-api-0.3.0/rustat_python_api/processing.py +161 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0/rustat_python_api.egg-info}/PKG-INFO +1 -1
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api.egg-info/SOURCES.txt +1 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/setup.py +1 -1
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/LICENSE +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/README.md +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/pyproject.toml +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api/__init__.py +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api/urls.py +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api.egg-info/dependency_links.txt +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api.egg-info/requires.txt +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api.egg-info/top_level.txt +0 -0
- {rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/setup.cfg +0 -0
|
@@ -2,12 +2,20 @@ import requests
|
|
|
2
2
|
import pandas as pd
|
|
3
3
|
from collections import defaultdict
|
|
4
4
|
from tqdm import tqdm
|
|
5
|
+
import time
|
|
5
6
|
|
|
6
7
|
from .urls import URLs
|
|
8
|
+
from .processing import processing
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
class RuStatParser:
|
|
10
|
-
def __init__(
|
|
12
|
+
def __init__(
|
|
13
|
+
self,
|
|
14
|
+
user: str,
|
|
15
|
+
password: str,
|
|
16
|
+
urls: dict = URLs,
|
|
17
|
+
sleep: int = -1
|
|
18
|
+
):
|
|
11
19
|
self.numeric_columns = [
|
|
12
20
|
'id', 'number', 'player_id', 'team_id', 'half', 'second',
|
|
13
21
|
'pos_x', 'pos_y', 'pos_dest_x', 'pos_dest_y', 'len', 'possession_id', 'possession_team_id',
|
|
@@ -18,11 +26,15 @@ class RuStatParser:
|
|
|
18
26
|
self.user = user
|
|
19
27
|
self.password = password
|
|
20
28
|
self.urls = urls
|
|
29
|
+
self.sleep = sleep
|
|
21
30
|
|
|
22
31
|
self.cached_info = {}
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
def resp2data(self, query: str) -> dict:
|
|
34
|
+
|
|
35
|
+
if self.sleep > 0:
|
|
36
|
+
time.sleep(self.sleep)
|
|
37
|
+
|
|
26
38
|
response = requests.get(query)
|
|
27
39
|
return response.json()
|
|
28
40
|
|
|
@@ -84,7 +96,7 @@ class RuStatParser:
|
|
|
84
96
|
for row in data["data"]["row"]
|
|
85
97
|
}
|
|
86
98
|
|
|
87
|
-
def get_events(self, match_id: int) -> pd.DataFrame | None:
|
|
99
|
+
def get_events(self, match_id: int, process: bool = True) -> pd.DataFrame | None:
|
|
88
100
|
data = self.resp2data(
|
|
89
101
|
self.urls["events"].format(
|
|
90
102
|
user=self.user,
|
|
@@ -101,6 +113,10 @@ class RuStatParser:
|
|
|
101
113
|
numeric_columns = [column for column in self.numeric_columns if column in df.columns]
|
|
102
114
|
df[numeric_columns] = df[numeric_columns].apply(pd.to_numeric, errors='coerce')
|
|
103
115
|
|
|
116
|
+
if process:
|
|
117
|
+
df['match_id'] = match_id
|
|
118
|
+
df = processing(df)
|
|
119
|
+
|
|
104
120
|
return df
|
|
105
121
|
|
|
106
122
|
def get_tracking(self, match_id: int) -> pd.DataFrame | None:
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def process_list(x: pd.Series):
|
|
6
|
+
lst = x.dropna().unique().tolist()
|
|
7
|
+
# return str(lst)
|
|
8
|
+
if len(lst) == 1:
|
|
9
|
+
return lst[0]
|
|
10
|
+
elif len(lst) == 0:
|
|
11
|
+
return np.nan
|
|
12
|
+
else:
|
|
13
|
+
return lst
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def gluing(df: pd.DataFrame) -> pd.DataFrame:
|
|
17
|
+
cols = ['player_id', 'half', 'second', 'pos_x', 'pos_y']
|
|
18
|
+
|
|
19
|
+
df_gb = df.groupby(cols).agg(process_list).reset_index()
|
|
20
|
+
df_gb['possession_number'] = df_gb['possession_number'].apply(
|
|
21
|
+
lambda x: max(x) if isinstance(x, list) else x
|
|
22
|
+
)
|
|
23
|
+
df_gb = df_gb.sort_values(by=['half', 'second', 'possession_number']).reset_index(drop=True)
|
|
24
|
+
return df_gb
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def add_reciever(glued_df: pd.DataFrame) -> pd.DataFrame:
|
|
28
|
+
df = glued_df.copy()
|
|
29
|
+
df['receiver_id'] = df['player_id'].shift(1)
|
|
30
|
+
df['receiver_name'] = df['player_name'].shift(1)
|
|
31
|
+
|
|
32
|
+
mask = (
|
|
33
|
+
(df['action_name'] == 'Ball receiving')
|
|
34
|
+
& (df['pos_x'] == df['pos_dest_x'].shift(1))
|
|
35
|
+
& (df['pos_y'] == df['pos_dest_y'].shift(1))
|
|
36
|
+
& (df['team_id'] == df['team_id'].shift(1))
|
|
37
|
+
& (df['player_id'] != df['player_id'].shift(1))
|
|
38
|
+
& (df['possession_number'] == df['possession_number'].shift(1))
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
idx = df[mask].index
|
|
42
|
+
remaining_idx = df.drop(idx-1).index
|
|
43
|
+
|
|
44
|
+
df.loc[remaining_idx, 'receiver_id'] = np.nan
|
|
45
|
+
df.loc[remaining_idx, 'receiver_name'] = np.nan
|
|
46
|
+
|
|
47
|
+
df = df[df['action_name'] != 'Ball receiving'].reset_index(drop=True)
|
|
48
|
+
|
|
49
|
+
return df
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def filter_data(df: pd.DataFrame) -> pd.DataFrame:
|
|
53
|
+
columns = [
|
|
54
|
+
'player_name', 'team_name', 'half', 'second', 'action_name',
|
|
55
|
+
'position_name', 'possession_number', 'pos_x', 'pos_y', 'pos_dest_x', 'pos_dest_y',
|
|
56
|
+
'player_id', 'number', 'team_id', 'standart_name', 'possession_time',
|
|
57
|
+
'opponent_id', 'opponent_name', 'opponent_team_id', 'opponent_team_name',
|
|
58
|
+
'opponent_position_name', 'zone_name', 'zone_dest_name', 'len',
|
|
59
|
+
'possession_team_id', 'possession_team_name', 'possession_name',
|
|
60
|
+
'attack_status_name', 'attack_type_name', 'attack_flang_name',
|
|
61
|
+
'attack_team_id', 'attack_team_name', 'attack_number',
|
|
62
|
+
'body_name', 'gate_x', 'gate_y', 'assistant_id',
|
|
63
|
+
'assistant_name', 'shot_type', 'touches', 'xg',
|
|
64
|
+
'shot_handling', 'match_id', 'receiver_id', 'receiver_name'
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for column in columns:
|
|
68
|
+
if column not in df.columns:
|
|
69
|
+
df[column] = np.nan
|
|
70
|
+
|
|
71
|
+
return df[(~df['possession_number'].isna()) | (df['second'] != 0)][columns].reset_index(drop=True)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def tag2type(tags: list[str]) -> str:
|
|
75
|
+
tags = [tag.lower() for tag in tags]
|
|
76
|
+
tags_str = ', '.join(tags)
|
|
77
|
+
|
|
78
|
+
if 'pass' in tags_str or 'assist' in tags_str:
|
|
79
|
+
pass_tags = [tag for tag in tags if 'pass' in tag and tag != 'pass interception']
|
|
80
|
+
assist_tags = [tag for tag in tags if 'assist' in tag]
|
|
81
|
+
cross_tags = [tag for tag in tags if 'cross' in tag and tag != 'cross interception']
|
|
82
|
+
|
|
83
|
+
if len(pass_tags) > 0 or (len(assist_tags) > 0 and len(cross_tags) == 0):
|
|
84
|
+
return 'pass'
|
|
85
|
+
|
|
86
|
+
if 'cross' in tags_str:
|
|
87
|
+
cross_tags = [tag for tag in tags if 'cross' in tag and tag != 'cross interception']
|
|
88
|
+
pass_tags = [tag for tag in tags if 'pass' in tag and tag != 'pass interception']
|
|
89
|
+
assist_tags = [tag for tag in tags if 'assist' in tag]
|
|
90
|
+
|
|
91
|
+
if len(cross_tags) > 0 or (len(assist_tags) > 0 and len(pass_tags) == 0):
|
|
92
|
+
return 'cross'
|
|
93
|
+
|
|
94
|
+
if 'shot' in tags_str:
|
|
95
|
+
shot_tags = [
|
|
96
|
+
tag for tag in tags
|
|
97
|
+
if 'shot' in tag and tag != 'shot interception' and 'with a shot' not in tag
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
if len(shot_tags) > 0:
|
|
101
|
+
return 'shot'
|
|
102
|
+
|
|
103
|
+
if 'dribbl' in tags_str:
|
|
104
|
+
return 'dribble'
|
|
105
|
+
|
|
106
|
+
if 'interception' in tags_str:
|
|
107
|
+
return 'interception'
|
|
108
|
+
|
|
109
|
+
if 'tackle' in tags_str:
|
|
110
|
+
return 'tackle'
|
|
111
|
+
|
|
112
|
+
if 'clearance' in tags_str:
|
|
113
|
+
return 'clearance'
|
|
114
|
+
|
|
115
|
+
if 'lost ball' in tags_str or 'bad ball control' in tags_str or 'mistake' in tags_str:
|
|
116
|
+
return 'lost ball'
|
|
117
|
+
|
|
118
|
+
if 'recovery' in tags_str:
|
|
119
|
+
return 'recovery'
|
|
120
|
+
|
|
121
|
+
if 'rebound' in tags_str:
|
|
122
|
+
return 'rebound'
|
|
123
|
+
|
|
124
|
+
if 'foul' in tags_str or 'yc, ' in tags_str or 'rc, ' in tags_str or 'rc for 2 yc' in tags_str or 'yc' == tags_str or 'rc' == tags_str:
|
|
125
|
+
return 'foul'
|
|
126
|
+
|
|
127
|
+
if 'challenge' in tags_str:
|
|
128
|
+
return 'challenge'
|
|
129
|
+
|
|
130
|
+
if 'own goal' in tags_str:
|
|
131
|
+
return 'own goal'
|
|
132
|
+
|
|
133
|
+
if 'save' in tags_str:
|
|
134
|
+
return 'save'
|
|
135
|
+
|
|
136
|
+
if 'chance created' in tags_str or 'goal' in tags_str or 'goal-scoring moment' in tags_str:
|
|
137
|
+
goal_tags = [tag for tag in tags if 'goal' == tag or 'goal-scoring moment' in tag or 'chance created' in tag]
|
|
138
|
+
if len(goal_tags) > 0:
|
|
139
|
+
return 'chance'
|
|
140
|
+
|
|
141
|
+
if 'opening' in tags_str:
|
|
142
|
+
return 'opening'
|
|
143
|
+
|
|
144
|
+
return 'other'
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def tagging(df: pd.DataFrame) -> pd.DataFrame:
|
|
148
|
+
df = df.rename(columns={'action_name': 'tags'})
|
|
149
|
+
df['tags'] = df['tags'].apply(lambda x: x if isinstance(x, list) else [x])
|
|
150
|
+
df['action_type'] = df['tags'].apply(tag2type)
|
|
151
|
+
|
|
152
|
+
return df
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def processing(df: pd.DataFrame) -> pd.DataFrame:
|
|
156
|
+
df = gluing(df)
|
|
157
|
+
df = add_reciever(df)
|
|
158
|
+
df = filter_data(df)
|
|
159
|
+
df = tagging(df)
|
|
160
|
+
|
|
161
|
+
return df
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
{rustat-python-api-0.1.2 → rustat-python-api-0.3.0}/rustat_python_api.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|