Skip to content

Commit 5562837

Browse files
author
UnravelSports [JB]
committed
merge conflict
2 parents 0b2439b + 19faf69 commit 5562837

File tree

105 files changed

+10855
-494
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+10855
-494
lines changed

kloppy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,4 @@
1313
# )
1414
# from .domain.services.state_builder import add_state
1515

16-
__version__ = "3.15.0"
16+
__version__ = "3.16.0"

kloppy/_providers/opta.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def load(
1919
2020
Parameters:
2121
f7_data: F7 xml feed containing the lineup information
22-
f24_data: F24 xml feed containing the events
22+
f24_data: F24 or F73 xml feed containing the events
2323
event_types:
2424
coordinates:
2525
event_factory:

kloppy/_providers/secondspectrum.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from typing import Optional
2-
import contextlib
32

43
from kloppy.domain import TrackingDataset
54
from kloppy.infra.serializers.tracking.secondspectrum import (

kloppy/config.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
import os
22
from contextlib import contextmanager
33
from copy import copy
4-
from typing import Any, Optional, Union
4+
from typing import Any, Optional, TypedDict, Union
55

66
from kloppy.domain import EventFactory
77

8-
try:
9-
from typing import TypedDict
10-
except ImportError:
11-
from mypy_extensions import TypedDict
12-
138
try:
149
from typing import Literal
1510
except ImportError:

kloppy/datafactory.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
from ._providers.datafactory import load
2+
3+
__all__ = ["load"]

kloppy/domain/models/common.py

Lines changed: 104 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,50 +5,45 @@
55
from datetime import datetime, timedelta
66
from enum import Enum, Flag
77
from typing import (
8+
Any,
9+
Callable,
810
Dict,
11+
Generic,
12+
Iterable,
913
List,
14+
Literal,
15+
NewType,
1016
Optional,
11-
Callable,
12-
Union,
13-
Any,
1417
TypeVar,
15-
Generic,
16-
NewType,
18+
Union,
1719
overload,
18-
Iterable,
1920
)
2021

21-
from .position import PositionType
22-
2322
from ...utils import deprecated, snake_case
24-
25-
if sys.version_info >= (3, 8):
26-
from typing import Literal
27-
else:
28-
from typing_extensions import Literal
23+
from .position import PositionType
2924

3025
if sys.version_info >= (3, 11):
3126
from typing import Self
3227
else:
3328
from typing_extensions import Self
3429

30+
from ...exceptions import (
31+
InvalidFilterError,
32+
KloppyParameterError,
33+
OrientationError,
34+
)
35+
from .formation import FormationType
3536
from .pitch import (
36-
PitchDimensions,
37-
Unit,
3837
Dimension,
39-
NormalizedPitchDimensions,
40-
MetricPitchDimensions,
4138
ImperialPitchDimensions,
39+
MetricPitchDimensions,
40+
NormalizedPitchDimensions,
4241
OptaPitchDimensions,
42+
PitchDimensions,
43+
Unit,
4344
WyscoutPitchDimensions,
4445
)
45-
from .formation import FormationType
46-
from .time import Time, Period, TimeContainer
47-
from ...exceptions import (
48-
OrientationError,
49-
InvalidFilterError,
50-
KloppyParameterError,
51-
)
46+
from .time import Period, Time, TimeContainer
5247

5348

5449
@dataclass
@@ -264,7 +259,10 @@ def get_player_by_jersey_number(self, jersey_no: int):
264259
def get_player_by_position(self, position: PositionType, time: Time):
265260
for player in self.players:
266261
if player.positions.items:
267-
player_position = player.positions.value_at(time)
262+
try:
263+
player_position = player.positions.value_at(time)
264+
except KeyError: # player that is subbed in later
265+
continue
268266
if player_position and player_position == position:
269267
return player
270268

@@ -928,6 +926,85 @@ class DatasetFlag(Flag):
928926
BALL_STATE = 2
929927

930928

929+
@dataclass
930+
class Statistic(ABC):
931+
name: str = field(init=False)
932+
933+
934+
@dataclass
935+
class ScalarStatistic(Statistic):
936+
value: float
937+
938+
939+
@dataclass
940+
class ExpectedGoals(ScalarStatistic):
941+
"""Expected goals"""
942+
943+
def __post_init__(self):
944+
self.name = "xG"
945+
946+
947+
@dataclass
948+
class PostShotExpectedGoals(ScalarStatistic):
949+
"""Post-shot expected goals"""
950+
951+
def __post_init__(self):
952+
self.name = "PSxG"
953+
954+
955+
@dataclass
956+
class ActionValue(Statistic):
957+
"""Action value"""
958+
959+
name: str
960+
action_value_scoring_before: Optional[float] = field(default=None)
961+
action_value_scoring_after: Optional[float] = field(default=None)
962+
action_value_conceding_before: Optional[float] = field(default=None)
963+
action_value_conceding_after: Optional[float] = field(default=None)
964+
965+
@property
966+
def offensive_value(self) -> Optional[float]:
967+
return (
968+
None
969+
if None
970+
in (
971+
self.action_value_scoring_before,
972+
self.action_value_scoring_after,
973+
)
974+
else self.action_value_scoring_after
975+
- self.action_value_scoring_before
976+
)
977+
978+
@property
979+
def defensive_value(self) -> Optional[float]:
980+
return (
981+
None
982+
if None
983+
in (
984+
self.action_value_conceding_before,
985+
self.action_value_conceding_after,
986+
)
987+
else self.action_value_conceding_after
988+
- self.action_value_conceding_before
989+
)
990+
991+
@property
992+
def value(self) -> Optional[float]:
993+
if None in (
994+
self.action_value_scoring_before,
995+
self.action_value_scoring_after,
996+
self.action_value_conceding_before,
997+
self.action_value_conceding_after,
998+
):
999+
return None
1000+
return (
1001+
self.action_value_scoring_after - self.action_value_scoring_before
1002+
) - (
1003+
self.action_value_conceding_after
1004+
- self.action_value_conceding_before
1005+
)
1006+
1007+
9311008
@dataclass
9321009
class DataRecord(ABC):
9331010
"""
@@ -945,6 +1022,7 @@ class DataRecord(ABC):
9451022
next_record: Optional["DataRecord"] = field(init=False)
9461023
period: Period
9471024
timestamp: timedelta
1025+
statistics: List[Statistic]
9481026
ball_owning_team: Optional[Team]
9491027
ball_state: Optional[BallState]
9501028

@@ -1118,6 +1196,7 @@ def _init_player_positions(self):
11181196
start_of_match = self.metadata.periods[0].start_time
11191197
for team in self.metadata.teams:
11201198
for player in team.players:
1199+
player.positions.reset()
11211200
if player.starting:
11221201
player.set_position(
11231202
start_of_match,

kloppy/domain/models/event.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,12 +1066,22 @@ def _update_formations_and_positions(self):
10661066
"""Update team formations and player positions based on Substitution and TacticalShift events."""
10671067
max_leeway = timedelta(seconds=60)
10681068

1069+
for team in self.metadata.teams:
1070+
team.formations.reset()
1071+
10691072
for event in self.events:
10701073
if isinstance(event, SubstitutionEvent):
1074+
if event.replacement_player.starting_position:
1075+
replacement_player_position = (
1076+
event.replacement_player.starting_position
1077+
)
1078+
else:
1079+
replacement_player_position = event.player.positions.last(
1080+
default=PositionType.Unknown
1081+
)
10711082
event.replacement_player.set_position(
10721083
event.time,
1073-
event.replacement_player.starting_position
1074-
or event.player.positions.last(default=None),
1084+
replacement_player_position,
10751085
)
10761086
event.player.set_position(event.time, None)
10771087

kloppy/domain/models/formation.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class FormationType(Enum):
8181
FIVE_TWO_TWO_ONE = "5-2-2-1"
8282
FIVE_THREE_TWO = "5-3-2"
8383
FIVE_FOUR_ONE = "5-4-1"
84+
UNKNOWN = "Unknown"
8485

8586
def __str__(self):
8687
return self.value

kloppy/domain/models/time.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
from dataclasses import dataclass, field
2-
from datetime import timedelta, datetime
2+
from datetime import datetime, timedelta
33
from typing import (
4-
overload,
5-
Union,
6-
Optional,
7-
TypeVar,
84
Generic,
95
List,
10-
Tuple,
11-
NamedTuple,
126
Literal,
7+
Optional,
8+
Tuple,
9+
TypeVar,
10+
Union,
11+
overload,
1312
)
1413

1514
from sortedcontainers import SortedDict
@@ -273,3 +272,7 @@ def __repr__(self):
273272
item_type = type(self.items.values()[0]).__name__
274273
str_items = {str(key): value for key, value in self.items.items()}
275274
return f"TimeContainer[{item_type}]({str_items})"
275+
276+
def reset(self):
277+
"""Clears all items from the container."""
278+
self.items.clear()

kloppy/domain/services/__init__.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from kloppy.domain import AttackingDirection, Frame, Ground
44

5-
from .transformers import DatasetTransformer, DatasetTransformerBuilder
65
from .event_factory import EventFactory, create_event
6+
from .transformers import DatasetTransformer, DatasetTransformerBuilder
77

88
# NOT YET: from .enrichers import TrackingPossessionEnricher
99

@@ -35,3 +35,12 @@ def attacking_direction_from_frame(frame: Frame) -> AttackingDirection:
3535
return AttackingDirection.LTR
3636
else:
3737
return AttackingDirection.RTL
38+
39+
40+
__all__ = [
41+
"DatasetTransformer",
42+
"DatasetTransformerBuilder",
43+
"EventFactory",
44+
"create_event",
45+
"attacking_direction_from_frame",
46+
]

0 commit comments

Comments
 (0)