datafc 2.7.2__tar.gz → 2.9.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.
- datafc-2.9.0/PKG-INFO +193 -0
- datafc-2.9.0/README.md +166 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/__init__.py +5 -1
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/__init__.py +2 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/_core.py +42 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/aio.py +51 -0
- datafc-2.9.0/datafc/sofascore/fetch_rating_breakdown_data.py +66 -0
- datafc-2.9.0/datafc/tff/__init__.py +40 -0
- datafc-2.9.0/datafc/tff/_client.py +396 -0
- datafc-2.9.0/datafc/tff/_parsers.py +1134 -0
- datafc-2.9.0/datafc/tff/fetch_fixtures_data.py +100 -0
- datafc-2.9.0/datafc/tff/fetch_match_cards_data.py +30 -0
- datafc-2.9.0/datafc/tff/fetch_match_details_data.py +28 -0
- datafc-2.9.0/datafc/tff/fetch_match_goals_data.py +31 -0
- datafc-2.9.0/datafc/tff/fetch_match_lineups_data.py +31 -0
- datafc-2.9.0/datafc/tff/fetch_match_staff_data.py +28 -0
- datafc-2.9.0/datafc/tff/fetch_player_cards_data.py +48 -0
- datafc-2.9.0/datafc/tff/fetch_player_career_data.py +33 -0
- datafc-2.9.0/datafc/tff/fetch_player_data.py +33 -0
- datafc-2.9.0/datafc/tff/fetch_player_goals_data.py +45 -0
- datafc-2.9.0/datafc/tff/fetch_player_matches_data.py +49 -0
- datafc-2.9.0/datafc/tff/fetch_player_national_matches_data.py +51 -0
- datafc-2.9.0/datafc/tff/fetch_standings_data.py +60 -0
- datafc-2.9.0/datafc/tff/fetch_team_data.py +28 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_config.py +25 -0
- datafc-2.9.0/datafc.egg-info/PKG-INFO +193 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc.egg-info/SOURCES.txt +18 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc.egg-info/requires.txt +1 -0
- {datafc-2.7.2 → datafc-2.9.0}/pyproject.toml +2 -1
- datafc-2.7.2/PKG-INFO +0 -1186
- datafc-2.7.2/README.md +0 -1160
- datafc-2.7.2/datafc.egg-info/PKG-INFO +0 -1186
- {datafc-2.7.2 → datafc-2.9.0}/LICENSE +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/clubelo/__init__.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/clubelo/_client.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/clubelo/_parsers.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/clubelo/fetch_club_history_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/clubelo/fetch_daily_ranking_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/clubelo/fetch_fixtures_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/__init__.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/_client.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/_parsers.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_country_codes_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_country_matches_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_teams_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_tournament_codes_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_tournament_editions_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_tournament_groups_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/eloratings/fetch_world_ranking_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/exceptions.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/_parsers.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_average_positions_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_coordinates_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_formations_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_goal_networks_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_incidents_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_league_player_stats_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_lineups_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_match_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_match_details_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_match_h2h_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_match_odds_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_match_stats_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_momentum_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_past_matches_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_attribute_overviews_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_career_stats_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_match_log_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_national_team_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_stats_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_player_transfers_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_pregame_form_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_referee_stats_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_search_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_season_rounds_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_seasons_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_shots_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_squad_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_standings_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_substitutions_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_team_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_team_match_history_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_team_stats_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_team_transfers_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/sofascore/fetch_upcoming_matches_data.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/__init__.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_async_client.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_cache.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_client.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_helpers.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_save_files.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_tournament_info.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc/utils/_validate.py +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc.egg-info/dependency_links.txt +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/datafc.egg-info/top_level.txt +0 -0
- {datafc-2.7.2 → datafc-2.9.0}/setup.cfg +0 -0
datafc-2.9.0/PKG-INFO
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: datafc
|
|
3
|
+
Version: 2.9.0
|
|
4
|
+
Summary: Fetch, process, and export structured football data.
|
|
5
|
+
Author-email: Uraz Akgül <urazdev@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/urazakgul/datafc
|
|
8
|
+
Project-URL: Repository, https://github.com/urazakgul/datafc
|
|
9
|
+
Project-URL: Issues, https://github.com/urazakgul/datafc/issues
|
|
10
|
+
Keywords: football,soccer,data,analytics,sofascore,clubelo,eloratings,elo
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas>=1.5
|
|
15
|
+
Requires-Dist: curl_cffi>=0.7
|
|
16
|
+
Requires-Dist: openpyxl>=3.0
|
|
17
|
+
Requires-Dist: beautifulsoup4>=4.11
|
|
18
|
+
Provides-Extra: parquet
|
|
19
|
+
Requires-Dist: pyarrow>=12.0; extra == "parquet"
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
22
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pandas-stubs>=2.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-mock>=3.12; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# datafc v2.9.0
|
|
29
|
+
|
|
30
|
+
Football data from **Sofascore**, **ClubElo**, **eloratings.net** (Elo since 1872) and **tff.org** (Turkish federation) as pandas DataFrames. 60 functions total, each with an async mirror under `datafc.aio`, optional `DiskCache`.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install datafc
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Sofascore — `from datafc import ...`
|
|
39
|
+
|
|
40
|
+
Grouped by the argument each function needs. Chain them: id → root frame → derived frame.
|
|
41
|
+
|
|
42
|
+
### Discovery
|
|
43
|
+
| Function | Purpose |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| `search_data(query, entity_type=None)` | search teams, players, tournaments, managers |
|
|
46
|
+
| `seasons_data(tournament_id)` | every season of a tournament |
|
|
47
|
+
| `season_rounds_data(tournament_id, season_id)` | defined matchweeks |
|
|
48
|
+
|
|
49
|
+
### Root (id-driven)
|
|
50
|
+
| Function | Purpose |
|
|
51
|
+
| --- | --- |
|
|
52
|
+
| `standings_data(tournament_id, season_id)` | Total / Home / Away table |
|
|
53
|
+
| `match_data(tournament_id, season_id, week_number=None, tournament_type=None, tournament_stage=None)` | fixtures + scores |
|
|
54
|
+
| `past_matches_data(...)` | same signature; H2H history per pair |
|
|
55
|
+
| `team_match_history_data(team_id)` | full match history for one team |
|
|
56
|
+
| `league_player_stats_data(tournament_id, season_id, order='-rating', accumulation='total', fields=None, position=None, max_players=100)` | wide-format player leaderboard |
|
|
57
|
+
| `referee_stats_data(referee_id)` | per-tournament career stats |
|
|
58
|
+
|
|
59
|
+
`tournament_type` ∈ `{"uefa", "world_cup", "league_playoff"}` with matching `tournament_stage` (`group_stage_week`, `round_of_16`, `quarterfinals`, `semifinals`, `final`, …).
|
|
60
|
+
|
|
61
|
+
### From `standings_df`
|
|
62
|
+
| Function | Purpose |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `team_data(standings_df)` | profile, kits, manager, venue |
|
|
65
|
+
| `team_stats_data(standings_df, tournament_id, season_id, season=None)` | season team stats (long) |
|
|
66
|
+
| `team_transfers_data(standings_df)` | team transfer log |
|
|
67
|
+
| `player_stats_data(standings_df, tournament_id, season_id)` | top players per team |
|
|
68
|
+
| `squad_data(standings_df)` | full roster |
|
|
69
|
+
| `upcoming_matches_data(standings_df)` | upcoming fixtures |
|
|
70
|
+
|
|
71
|
+
### From `match_df`
|
|
72
|
+
| Function | Purpose |
|
|
73
|
+
| --- | --- |
|
|
74
|
+
| `match_details_data` | referee + venue |
|
|
75
|
+
| `formations_data` | formation strings |
|
|
76
|
+
| `match_stats_data` | team stats per match |
|
|
77
|
+
| `match_odds_data` | 1/X/2 odds |
|
|
78
|
+
| `match_h2h_data` | head-to-head record |
|
|
79
|
+
| `momentum_data` | minute-by-minute momentum |
|
|
80
|
+
| `pregame_form_data` | last-5 form, rating, position, market value |
|
|
81
|
+
| `shots_data` | every shot (xG, xGOT, body part, situation) |
|
|
82
|
+
| `lineups_data` | XI + subs + per-match player stats |
|
|
83
|
+
| `substitutions_data` | substitutions |
|
|
84
|
+
| `incidents_data` | goals, cards, VAR |
|
|
85
|
+
| `average_positions_data` | average pitch position (X/Y) |
|
|
86
|
+
| `goal_networks_data` | coordinates for every action in a goal sequence |
|
|
87
|
+
|
|
88
|
+
### From `lineups_df`
|
|
89
|
+
| Function | Purpose |
|
|
90
|
+
| --- | --- |
|
|
91
|
+
| `coordinates_data(lineups_df)` | touch heatmap per player |
|
|
92
|
+
| `rating_breakdown_data(lineups_df)` | every pass / dribble / defensive action / ball-carry with start-end coordinates and outcome flags |
|
|
93
|
+
|
|
94
|
+
### From `squad_df`
|
|
95
|
+
| Function | Purpose |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `player_data` | profile |
|
|
98
|
+
| `player_attribute_overviews_data` | radar attributes + baselines |
|
|
99
|
+
| `player_transfers_data` | transfer history |
|
|
100
|
+
| `player_career_stats_data` | season-by-season career stats |
|
|
101
|
+
| `player_national_team_data` | international career |
|
|
102
|
+
| `player_match_log_data` | match-by-match in-game stats (wide) |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## ClubElo — `from datafc import clubelo`
|
|
107
|
+
|
|
108
|
+
Club names use ClubElo spelling (`Man City`, `Real Madrid`, `Paris SG`).
|
|
109
|
+
|
|
110
|
+
| Function | Purpose |
|
|
111
|
+
| --- | --- |
|
|
112
|
+
| `daily_ranking_data(date)` | full ranking on a calendar day (`"YYYY-MM-DD"` or `date`) |
|
|
113
|
+
| `club_history_data(club)` | Elo history for one club (pre-1960 values are provisional) |
|
|
114
|
+
| `fixtures_data()` | probability grid for upcoming matches |
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## EloRatings — `from datafc import eloratings`
|
|
119
|
+
|
|
120
|
+
Country arguments are page slugs (`Czech_Republic`, `United_States`).
|
|
121
|
+
|
|
122
|
+
| Function | Purpose |
|
|
123
|
+
| --- | --- |
|
|
124
|
+
| `world_ranking_data()` | current national-team ranking |
|
|
125
|
+
| `country_matches_data(country)` | full international match history |
|
|
126
|
+
| `country_codes_data()` | code → country |
|
|
127
|
+
| `tournament_codes_data()` | code → tournament |
|
|
128
|
+
| `tournament_editions_data()` | edition list with dates + slugs |
|
|
129
|
+
| `tournament_groups_data()` | subcode → group code (e.g. WC qualifiers → `WQT`) |
|
|
130
|
+
| `teams_data()` | legacy country code → ISO |
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## TFF — `from datafc import tff`
|
|
135
|
+
|
|
136
|
+
Süper Lig, 1. Lig, 2. Lig, 3. Lig.
|
|
137
|
+
|
|
138
|
+
### Discovery / league
|
|
139
|
+
| Function | Purpose |
|
|
140
|
+
| --- | --- |
|
|
141
|
+
| `fixtures_data(page_id, week=None, group=None, playoff=False, round=None)` | weekly fixtures or play-off bracket |
|
|
142
|
+
| `standings_data(page_id, week, group=None)` | league table (`group` required for 2. Lig / 3. Lig) |
|
|
143
|
+
| `team_data(club_id)` | club profile |
|
|
144
|
+
|
|
145
|
+
### From `match_id`
|
|
146
|
+
| Function | Purpose |
|
|
147
|
+
| --- | --- |
|
|
148
|
+
| `match_details_data` | teams, score, officials |
|
|
149
|
+
| `match_lineups_data` | XI + bench |
|
|
150
|
+
| `match_staff_data` | head coaches |
|
|
151
|
+
| `match_goals_data` | goals with type (`F`/`H`/`P`/`K`/`KG`) |
|
|
152
|
+
| `match_cards_data` | bookings (yellow / red / second_yellow) |
|
|
153
|
+
|
|
154
|
+
### From `player_id`
|
|
155
|
+
| Function | Purpose |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `player_data` | profile (licence, current club) |
|
|
158
|
+
| `player_career_data` | licence history |
|
|
159
|
+
| `player_national_matches_data` | national-team appearances |
|
|
160
|
+
| `player_goals_data` | every recorded goal |
|
|
161
|
+
| `player_matches_data(player_id, season)` | club matches in a season (`"2025-2026"`) |
|
|
162
|
+
| `player_cards_data(player_id, season)` | bookings in a season |
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Async
|
|
167
|
+
|
|
168
|
+
Every function has a mirror in `datafc.aio` with the same signature:
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
import asyncio
|
|
172
|
+
from datafc import aio
|
|
173
|
+
|
|
174
|
+
async def main():
|
|
175
|
+
tasks = [aio.match_data(52, 63814, week_number=w) for w in range(1, 39)]
|
|
176
|
+
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
177
|
+
|
|
178
|
+
asyncio.run(main())
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Cache
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
from datafc import DiskCache
|
|
185
|
+
|
|
186
|
+
cache = DiskCache(cache_dir=".datafc_cache", ttl_hours=24)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
Pass `cache=cache` to any function. `ttl_hours=0` disables expiry; `cache.clear()` empties it.
|
|
190
|
+
|
|
191
|
+
## Exceptions
|
|
192
|
+
|
|
193
|
+
`DataFCError` (base), `InvalidParameterError`, `APIError`, `RateLimitError`, `ServerError`, `DataNotAvailableError`.
|
datafc-2.9.0/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# datafc v2.9.0
|
|
2
|
+
|
|
3
|
+
Football data from **Sofascore**, **ClubElo**, **eloratings.net** (Elo since 1872) and **tff.org** (Turkish federation) as pandas DataFrames. 60 functions total, each with an async mirror under `datafc.aio`, optional `DiskCache`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install datafc
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Sofascore — `from datafc import ...`
|
|
12
|
+
|
|
13
|
+
Grouped by the argument each function needs. Chain them: id → root frame → derived frame.
|
|
14
|
+
|
|
15
|
+
### Discovery
|
|
16
|
+
| Function | Purpose |
|
|
17
|
+
| --- | --- |
|
|
18
|
+
| `search_data(query, entity_type=None)` | search teams, players, tournaments, managers |
|
|
19
|
+
| `seasons_data(tournament_id)` | every season of a tournament |
|
|
20
|
+
| `season_rounds_data(tournament_id, season_id)` | defined matchweeks |
|
|
21
|
+
|
|
22
|
+
### Root (id-driven)
|
|
23
|
+
| Function | Purpose |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `standings_data(tournament_id, season_id)` | Total / Home / Away table |
|
|
26
|
+
| `match_data(tournament_id, season_id, week_number=None, tournament_type=None, tournament_stage=None)` | fixtures + scores |
|
|
27
|
+
| `past_matches_data(...)` | same signature; H2H history per pair |
|
|
28
|
+
| `team_match_history_data(team_id)` | full match history for one team |
|
|
29
|
+
| `league_player_stats_data(tournament_id, season_id, order='-rating', accumulation='total', fields=None, position=None, max_players=100)` | wide-format player leaderboard |
|
|
30
|
+
| `referee_stats_data(referee_id)` | per-tournament career stats |
|
|
31
|
+
|
|
32
|
+
`tournament_type` ∈ `{"uefa", "world_cup", "league_playoff"}` with matching `tournament_stage` (`group_stage_week`, `round_of_16`, `quarterfinals`, `semifinals`, `final`, …).
|
|
33
|
+
|
|
34
|
+
### From `standings_df`
|
|
35
|
+
| Function | Purpose |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `team_data(standings_df)` | profile, kits, manager, venue |
|
|
38
|
+
| `team_stats_data(standings_df, tournament_id, season_id, season=None)` | season team stats (long) |
|
|
39
|
+
| `team_transfers_data(standings_df)` | team transfer log |
|
|
40
|
+
| `player_stats_data(standings_df, tournament_id, season_id)` | top players per team |
|
|
41
|
+
| `squad_data(standings_df)` | full roster |
|
|
42
|
+
| `upcoming_matches_data(standings_df)` | upcoming fixtures |
|
|
43
|
+
|
|
44
|
+
### From `match_df`
|
|
45
|
+
| Function | Purpose |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `match_details_data` | referee + venue |
|
|
48
|
+
| `formations_data` | formation strings |
|
|
49
|
+
| `match_stats_data` | team stats per match |
|
|
50
|
+
| `match_odds_data` | 1/X/2 odds |
|
|
51
|
+
| `match_h2h_data` | head-to-head record |
|
|
52
|
+
| `momentum_data` | minute-by-minute momentum |
|
|
53
|
+
| `pregame_form_data` | last-5 form, rating, position, market value |
|
|
54
|
+
| `shots_data` | every shot (xG, xGOT, body part, situation) |
|
|
55
|
+
| `lineups_data` | XI + subs + per-match player stats |
|
|
56
|
+
| `substitutions_data` | substitutions |
|
|
57
|
+
| `incidents_data` | goals, cards, VAR |
|
|
58
|
+
| `average_positions_data` | average pitch position (X/Y) |
|
|
59
|
+
| `goal_networks_data` | coordinates for every action in a goal sequence |
|
|
60
|
+
|
|
61
|
+
### From `lineups_df`
|
|
62
|
+
| Function | Purpose |
|
|
63
|
+
| --- | --- |
|
|
64
|
+
| `coordinates_data(lineups_df)` | touch heatmap per player |
|
|
65
|
+
| `rating_breakdown_data(lineups_df)` | every pass / dribble / defensive action / ball-carry with start-end coordinates and outcome flags |
|
|
66
|
+
|
|
67
|
+
### From `squad_df`
|
|
68
|
+
| Function | Purpose |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `player_data` | profile |
|
|
71
|
+
| `player_attribute_overviews_data` | radar attributes + baselines |
|
|
72
|
+
| `player_transfers_data` | transfer history |
|
|
73
|
+
| `player_career_stats_data` | season-by-season career stats |
|
|
74
|
+
| `player_national_team_data` | international career |
|
|
75
|
+
| `player_match_log_data` | match-by-match in-game stats (wide) |
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## ClubElo — `from datafc import clubelo`
|
|
80
|
+
|
|
81
|
+
Club names use ClubElo spelling (`Man City`, `Real Madrid`, `Paris SG`).
|
|
82
|
+
|
|
83
|
+
| Function | Purpose |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `daily_ranking_data(date)` | full ranking on a calendar day (`"YYYY-MM-DD"` or `date`) |
|
|
86
|
+
| `club_history_data(club)` | Elo history for one club (pre-1960 values are provisional) |
|
|
87
|
+
| `fixtures_data()` | probability grid for upcoming matches |
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## EloRatings — `from datafc import eloratings`
|
|
92
|
+
|
|
93
|
+
Country arguments are page slugs (`Czech_Republic`, `United_States`).
|
|
94
|
+
|
|
95
|
+
| Function | Purpose |
|
|
96
|
+
| --- | --- |
|
|
97
|
+
| `world_ranking_data()` | current national-team ranking |
|
|
98
|
+
| `country_matches_data(country)` | full international match history |
|
|
99
|
+
| `country_codes_data()` | code → country |
|
|
100
|
+
| `tournament_codes_data()` | code → tournament |
|
|
101
|
+
| `tournament_editions_data()` | edition list with dates + slugs |
|
|
102
|
+
| `tournament_groups_data()` | subcode → group code (e.g. WC qualifiers → `WQT`) |
|
|
103
|
+
| `teams_data()` | legacy country code → ISO |
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## TFF — `from datafc import tff`
|
|
108
|
+
|
|
109
|
+
Süper Lig, 1. Lig, 2. Lig, 3. Lig.
|
|
110
|
+
|
|
111
|
+
### Discovery / league
|
|
112
|
+
| Function | Purpose |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `fixtures_data(page_id, week=None, group=None, playoff=False, round=None)` | weekly fixtures or play-off bracket |
|
|
115
|
+
| `standings_data(page_id, week, group=None)` | league table (`group` required for 2. Lig / 3. Lig) |
|
|
116
|
+
| `team_data(club_id)` | club profile |
|
|
117
|
+
|
|
118
|
+
### From `match_id`
|
|
119
|
+
| Function | Purpose |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `match_details_data` | teams, score, officials |
|
|
122
|
+
| `match_lineups_data` | XI + bench |
|
|
123
|
+
| `match_staff_data` | head coaches |
|
|
124
|
+
| `match_goals_data` | goals with type (`F`/`H`/`P`/`K`/`KG`) |
|
|
125
|
+
| `match_cards_data` | bookings (yellow / red / second_yellow) |
|
|
126
|
+
|
|
127
|
+
### From `player_id`
|
|
128
|
+
| Function | Purpose |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `player_data` | profile (licence, current club) |
|
|
131
|
+
| `player_career_data` | licence history |
|
|
132
|
+
| `player_national_matches_data` | national-team appearances |
|
|
133
|
+
| `player_goals_data` | every recorded goal |
|
|
134
|
+
| `player_matches_data(player_id, season)` | club matches in a season (`"2025-2026"`) |
|
|
135
|
+
| `player_cards_data(player_id, season)` | bookings in a season |
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Async
|
|
140
|
+
|
|
141
|
+
Every function has a mirror in `datafc.aio` with the same signature:
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
import asyncio
|
|
145
|
+
from datafc import aio
|
|
146
|
+
|
|
147
|
+
async def main():
|
|
148
|
+
tasks = [aio.match_data(52, 63814, week_number=w) for w in range(1, 39)]
|
|
149
|
+
return await asyncio.gather(*tasks, return_exceptions=True)
|
|
150
|
+
|
|
151
|
+
asyncio.run(main())
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Cache
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from datafc import DiskCache
|
|
158
|
+
|
|
159
|
+
cache = DiskCache(cache_dir=".datafc_cache", ttl_hours=24)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Pass `cache=cache` to any function. `ttl_hours=0` disables expiry; `cache.clear()` empties it.
|
|
163
|
+
|
|
164
|
+
## Exceptions
|
|
165
|
+
|
|
166
|
+
`DataFCError` (base), `InvalidParameterError`, `APIError`, `RateLimitError`, `ServerError`, `DataNotAvailableError`.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
__version__ = "2.
|
|
1
|
+
__version__ = "2.9.0"
|
|
2
2
|
|
|
3
3
|
from .sofascore import *
|
|
4
4
|
from . import clubelo
|
|
5
5
|
from . import eloratings
|
|
6
|
+
from . import tff
|
|
6
7
|
from .exceptions import (
|
|
7
8
|
DataFCError,
|
|
8
9
|
InvalidParameterError,
|
|
@@ -50,6 +51,7 @@ __all__ = [
|
|
|
50
51
|
"incidents_data",
|
|
51
52
|
"average_positions_data",
|
|
52
53
|
"coordinates_data",
|
|
54
|
+
"rating_breakdown_data",
|
|
53
55
|
"goal_networks_data",
|
|
54
56
|
"past_matches_data",
|
|
55
57
|
# Player
|
|
@@ -67,6 +69,8 @@ __all__ = [
|
|
|
67
69
|
"clubelo",
|
|
68
70
|
# EloRatings subpackage
|
|
69
71
|
"eloratings",
|
|
72
|
+
# TFF subpackage
|
|
73
|
+
"tff",
|
|
70
74
|
# Exceptions
|
|
71
75
|
"DataFCError",
|
|
72
76
|
"InvalidParameterError",
|
|
@@ -6,6 +6,7 @@ from .fetch_goal_networks_data import goal_networks_data
|
|
|
6
6
|
from .fetch_formations_data import formations_data
|
|
7
7
|
from .fetch_lineups_data import lineups_data
|
|
8
8
|
from .fetch_coordinates_data import coordinates_data
|
|
9
|
+
from .fetch_rating_breakdown_data import rating_breakdown_data
|
|
9
10
|
from .fetch_substitutions_data import substitutions_data
|
|
10
11
|
from .fetch_incidents_data import incidents_data
|
|
11
12
|
from .fetch_match_details_data import match_details_data
|
|
@@ -59,6 +60,7 @@ __all__ = [
|
|
|
59
60
|
# Player-level
|
|
60
61
|
"lineups_data",
|
|
61
62
|
"coordinates_data",
|
|
63
|
+
"rating_breakdown_data",
|
|
62
64
|
"player_career_stats_data",
|
|
63
65
|
"player_transfers_data",
|
|
64
66
|
# League / season-level
|
|
@@ -815,3 +815,45 @@ def heatmap_records(data: dict, row: pd.Series) -> list:
|
|
|
815
815
|
for point in data.get("heatmap", [])
|
|
816
816
|
if isinstance(point, dict) and "x" in point and "y" in point
|
|
817
817
|
]
|
|
818
|
+
|
|
819
|
+
|
|
820
|
+
_RATING_BREAKDOWN_CATEGORIES = ("passes", "dribbles", "defensive", "ball-carries")
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
def rating_breakdown_records(data: dict, row: pd.Series) -> list:
|
|
824
|
+
"""rating_breakdown_data per-player action parser.
|
|
825
|
+
|
|
826
|
+
Flattens the four action buckets returned by the endpoint
|
|
827
|
+
(``passes``, ``dribbles``, ``defensive``, ``ball-carries``) into one row
|
|
828
|
+
per action, carrying start/end coordinates and outcome flags.
|
|
829
|
+
"""
|
|
830
|
+
out = []
|
|
831
|
+
for category in _RATING_BREAKDOWN_CATEGORIES:
|
|
832
|
+
actions = data.get(category) or []
|
|
833
|
+
for action in actions:
|
|
834
|
+
if not isinstance(action, dict):
|
|
835
|
+
continue
|
|
836
|
+
player_coords = action.get("playerCoordinates") or {}
|
|
837
|
+
pass_end = action.get("passEndCoordinates") or {}
|
|
838
|
+
out.append({
|
|
839
|
+
"country": row["country"],
|
|
840
|
+
"tournament": row["tournament"],
|
|
841
|
+
"season": row["season"],
|
|
842
|
+
"week": row["week"],
|
|
843
|
+
"game_id": row["game_id"],
|
|
844
|
+
"team": row["team"],
|
|
845
|
+
"player_id": row["player_id"],
|
|
846
|
+
"player_name": row["player_name"],
|
|
847
|
+
"category": category,
|
|
848
|
+
"event_action_type": action.get("eventActionType"),
|
|
849
|
+
"is_home": action.get("isHome"),
|
|
850
|
+
"outcome": action.get("outcome"),
|
|
851
|
+
"keypass": action.get("keypass"),
|
|
852
|
+
"is_assist": action.get("isAssist"),
|
|
853
|
+
"is_long_ball": action.get("isLongBall"),
|
|
854
|
+
"player_x": player_coords.get("x"),
|
|
855
|
+
"player_y": player_coords.get("y"),
|
|
856
|
+
"pass_end_x": pass_end.get("x"),
|
|
857
|
+
"pass_end_y": pass_end.get("y"),
|
|
858
|
+
})
|
|
859
|
+
return out
|
|
@@ -37,6 +37,7 @@ from datafc.sofascore._core import (
|
|
|
37
37
|
goal_networks_post_process,
|
|
38
38
|
heatmap_records,
|
|
39
39
|
iter_per_match_async,
|
|
40
|
+
rating_breakdown_records,
|
|
40
41
|
match_log_records_from_response,
|
|
41
42
|
needs_world_cup_resolution,
|
|
42
43
|
past_match_record_from_event,
|
|
@@ -513,6 +514,56 @@ async def coordinates_data(
|
|
|
513
514
|
return result_df
|
|
514
515
|
|
|
515
516
|
|
|
517
|
+
async def rating_breakdown_data(
|
|
518
|
+
lineups_df: pd.DataFrame,
|
|
519
|
+
data_source: str = "sofascore",
|
|
520
|
+
rate_limit: float = 2.0,
|
|
521
|
+
cache: Optional[DiskCache] = None,
|
|
522
|
+
enable_json_export: bool = False,
|
|
523
|
+
enable_excel_export: bool = False,
|
|
524
|
+
output_dir: str = ".",
|
|
525
|
+
) -> pd.DataFrame:
|
|
526
|
+
"""Async version of rating_breakdown_data(). Players with no data are silently skipped."""
|
|
527
|
+
validate_source(data_source)
|
|
528
|
+
validate_df(lineups_df, "lineups_df")
|
|
529
|
+
|
|
530
|
+
unique_players = lineups_df[[
|
|
531
|
+
"country", "tournament", "season", "week", "game_id", "team", "player_id", "player_name"
|
|
532
|
+
]].drop_duplicates()
|
|
533
|
+
if unique_players.empty:
|
|
534
|
+
raise InvalidParameterError("No unique players found in lineups_df.")
|
|
535
|
+
|
|
536
|
+
sem = asyncio.Semaphore(1)
|
|
537
|
+
base = API_URLS[data_source]
|
|
538
|
+
|
|
539
|
+
async def _fetch(client, row):
|
|
540
|
+
url = f"{base}/api/v1/event/{row['game_id']}/player/{row['player_id']}/rating-breakdown"
|
|
541
|
+
async with sem:
|
|
542
|
+
try:
|
|
543
|
+
data = await client.get(url)
|
|
544
|
+
except APIError as exc:
|
|
545
|
+
if exc.status_code in (404, 403):
|
|
546
|
+
return []
|
|
547
|
+
raise
|
|
548
|
+
return rating_breakdown_records(data, row)
|
|
549
|
+
|
|
550
|
+
async with AsyncSofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
551
|
+
batches = await asyncio.gather(*[_fetch(client, row) for _, row in unique_players.iterrows()])
|
|
552
|
+
|
|
553
|
+
records = [rec for batch in batches for rec in batch]
|
|
554
|
+
result_df = pd.DataFrame(records)
|
|
555
|
+
if result_df.empty:
|
|
556
|
+
raise DataNotAvailableError("No rating breakdown data found for the specified players.")
|
|
557
|
+
|
|
558
|
+
export_df(
|
|
559
|
+
result_df, fn_name="rating_breakdown_data", data_source=data_source,
|
|
560
|
+
output_dir=output_dir,
|
|
561
|
+
first_row=lineups_df.iloc[0],
|
|
562
|
+
enable_json_export=enable_json_export, enable_excel_export=enable_excel_export,
|
|
563
|
+
)
|
|
564
|
+
return result_df
|
|
565
|
+
|
|
566
|
+
|
|
516
567
|
async def goal_networks_data(
|
|
517
568
|
match_df: pd.DataFrame,
|
|
518
569
|
data_source: str = "sofascore",
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Optional
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from datafc.utils._client import SofascoreClient
|
|
4
|
+
from datafc.utils._config import API_URLS
|
|
5
|
+
from datafc.utils._validate import validate_source, validate_df
|
|
6
|
+
from datafc.sofascore._core import rating_breakdown_records, export_df
|
|
7
|
+
from datafc.exceptions import APIError, DataNotAvailableError, InvalidParameterError
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from datafc.utils._cache import DiskCache
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def rating_breakdown_data(
|
|
14
|
+
lineups_df: pd.DataFrame,
|
|
15
|
+
data_source: str = "sofascore",
|
|
16
|
+
rate_limit: float = 2.0,
|
|
17
|
+
cache: Optional["DiskCache"] = None,
|
|
18
|
+
enable_json_export: bool = False,
|
|
19
|
+
enable_excel_export: bool = False,
|
|
20
|
+
output_dir: str = ".",
|
|
21
|
+
) -> pd.DataFrame:
|
|
22
|
+
"""Fetches rating-breakdown action data for each player in the provided lineup dataset.
|
|
23
|
+
|
|
24
|
+
The endpoint returns four buckets — passes, dribbles, defensive actions and
|
|
25
|
+
ball carries — each with start (and optionally end) coordinates plus outcome
|
|
26
|
+
flags (``outcome``, ``keypass``, ``isAssist``, ``isLongBall``). Rows are
|
|
27
|
+
flattened so each action becomes a single record tagged with ``category``.
|
|
28
|
+
|
|
29
|
+
Players with no rating breakdown data (404/403) are silently skipped.
|
|
30
|
+
"""
|
|
31
|
+
validate_source(data_source)
|
|
32
|
+
validate_df(lineups_df, "lineups_df")
|
|
33
|
+
|
|
34
|
+
unique_players = lineups_df[[
|
|
35
|
+
"country", "tournament", "season", "week", "game_id", "team", "player_id", "player_name"
|
|
36
|
+
]].drop_duplicates()
|
|
37
|
+
|
|
38
|
+
if unique_players.empty:
|
|
39
|
+
raise InvalidParameterError("No unique players found in lineups_df.")
|
|
40
|
+
|
|
41
|
+
records = []
|
|
42
|
+
with SofascoreClient(rate_limit=rate_limit, cache=cache) as client:
|
|
43
|
+
for _, row in unique_players.iterrows():
|
|
44
|
+
url = (
|
|
45
|
+
f"{API_URLS[data_source]}/api/v1/event/{row['game_id']}"
|
|
46
|
+
f"/player/{row['player_id']}/rating-breakdown"
|
|
47
|
+
)
|
|
48
|
+
try:
|
|
49
|
+
data = client.get(url)
|
|
50
|
+
except APIError as exc:
|
|
51
|
+
if exc.status_code in (404, 403):
|
|
52
|
+
continue
|
|
53
|
+
raise
|
|
54
|
+
records.extend(rating_breakdown_records(data, row))
|
|
55
|
+
|
|
56
|
+
result_df = pd.DataFrame(records)
|
|
57
|
+
if result_df.empty:
|
|
58
|
+
raise DataNotAvailableError("No rating breakdown data found for the specified players.")
|
|
59
|
+
|
|
60
|
+
export_df(
|
|
61
|
+
result_df, fn_name="rating_breakdown_data", data_source=data_source,
|
|
62
|
+
output_dir=output_dir,
|
|
63
|
+
first_row=lineups_df.iloc[0],
|
|
64
|
+
enable_json_export=enable_json_export, enable_excel_export=enable_excel_export,
|
|
65
|
+
)
|
|
66
|
+
return result_df
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from datafc.utils._config import (
|
|
2
|
+
get_tff_base_url,
|
|
3
|
+
reset_tff_base_url,
|
|
4
|
+
set_tff_base_url,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
from .fetch_fixtures_data import fixtures_data
|
|
8
|
+
from .fetch_match_cards_data import match_cards_data
|
|
9
|
+
from .fetch_match_details_data import match_details_data
|
|
10
|
+
from .fetch_match_goals_data import match_goals_data
|
|
11
|
+
from .fetch_match_lineups_data import match_lineups_data
|
|
12
|
+
from .fetch_match_staff_data import match_staff_data
|
|
13
|
+
from .fetch_player_cards_data import player_cards_data
|
|
14
|
+
from .fetch_player_career_data import player_career_data
|
|
15
|
+
from .fetch_player_data import player_data
|
|
16
|
+
from .fetch_player_goals_data import player_goals_data
|
|
17
|
+
from .fetch_player_matches_data import player_matches_data
|
|
18
|
+
from .fetch_player_national_matches_data import player_national_matches_data
|
|
19
|
+
from .fetch_standings_data import standings_data
|
|
20
|
+
from .fetch_team_data import team_data
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"fixtures_data",
|
|
24
|
+
"standings_data",
|
|
25
|
+
"match_details_data",
|
|
26
|
+
"match_lineups_data",
|
|
27
|
+
"match_staff_data",
|
|
28
|
+
"match_goals_data",
|
|
29
|
+
"match_cards_data",
|
|
30
|
+
"player_data",
|
|
31
|
+
"player_career_data",
|
|
32
|
+
"player_national_matches_data",
|
|
33
|
+
"player_goals_data",
|
|
34
|
+
"player_matches_data",
|
|
35
|
+
"player_cards_data",
|
|
36
|
+
"team_data",
|
|
37
|
+
"get_tff_base_url",
|
|
38
|
+
"set_tff_base_url",
|
|
39
|
+
"reset_tff_base_url",
|
|
40
|
+
]
|