File size: 4,679 Bytes
d6ea71e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 |
"""Implements the formula of the Atomic-VAEP framework."""
import pandas as pd
from pandera.typing import DataFrame, Series
from socceraction.atomic.spadl import AtomicSPADLSchema
def _prev(x: pd.Series) -> pd.Series:
prev_x = x.shift(1)
prev_x[:1] = x.values[0]
return prev_x
def offensive_value(
actions: DataFrame[AtomicSPADLSchema], scores: Series[float], concedes: Series[float]
) -> Series[float]:
r"""Compute the offensive value of each action.
VAEP defines the *offensive value* of an action as the change in scoring
probability before and after the action.
.. math::
\Delta P_{score}(a_{i}, t) = P^{k}_{score}(S_i, t) - P^{k}_{score}(S_{i-1}, t)
where :math:`P_{score}(S_i, t)` is the probability that team :math:`t`
which possesses the ball in state :math:`S_i` will score in the next 10
actions.
Parameters
----------
actions : pd.DataFrame
SPADL action.
scores : pd.Series
The probability of scoring from each corresponding game state.
concedes : pd.Series
The probability of conceding from each corresponding game state.
Returns
-------
pd.Series
he ffensive value of each action.
"""
sameteam = _prev(actions.team_id) == actions.team_id
prev_scores = _prev(scores) * sameteam + _prev(concedes) * (~sameteam)
# if the previous action was too long ago, the odds of scoring are now 0
# toolong_idx = (
# abs(actions.time_seconds - _prev(actions.time_seconds)) > _samephase_nb
# )
# prev_scores[toolong_idx] = 0
# if the previous action was a goal, the odds of scoring are now 0
prevgoal_idx = _prev(actions.type_name).isin(["goal", "owngoal"])
prev_scores[prevgoal_idx] = 0
return scores - prev_scores
def defensive_value(
actions: DataFrame[AtomicSPADLSchema], scores: Series[float], concedes: Series[float]
) -> Series[float]:
r"""Compute the defensive value of each action.
VAEP defines the *defensive value* of an action as the change in conceding
probability.
.. math::
\Delta P_{concede}(a_{i}, t) = P^{k}_{concede}(S_i, t) - P^{k}_{concede}(S_{i-1}, t)
where :math:`P_{concede}(S_i, t)` is the probability that team :math:`t`
which possesses the ball in state :math:`S_i` will concede in the next 10
actions.
Parameters
----------
actions : pd.DataFrame
SPADL action.
scores : pd.Series
The probability of scoring from each corresponding game state.
concedes : pd.Series
The probability of conceding from each corresponding game state.
Returns
-------
pd.Series
The defensive value of each action.
"""
sameteam = _prev(actions.team_id) == actions.team_id
prev_concedes = _prev(concedes) * sameteam + _prev(scores) * (~sameteam)
# if the previous action was too long ago, the odds of scoring are now 0
# toolong_idx = (
# abs(actions.time_seconds - _prev(actions.time_seconds)) > _samephase_nb
# )
# prev_concedes[toolong_idx] = 0
# if the previous action was a goal, the odds of conceding are now 0
prevgoal_idx = _prev(actions.type_name).isin(["goal", "owngoal"])
prev_concedes[prevgoal_idx] = 0
return -(concedes - prev_concedes)
def value(
actions: DataFrame[AtomicSPADLSchema], Pscores: Series[float], Pconcedes: Series[float]
) -> pd.DataFrame:
r"""Compute the offensive, defensive and VAEP value of each action.
The total VAEP value of an action is the difference between that action's
offensive value and defensive value.
.. math::
V_{VAEP}(a_i) = \Delta P_{score}(a_{i}, t) - \Delta P_{concede}(a_{i}, t)
Parameters
----------
actions : pd.DataFrame
SPADL action.
Pscores : pd.Series
The probability of scoring from each corresponding game state.
Pconcedes : pd.Series
The probability of conceding from each corresponding game state.
Returns
-------
pd.DataFrame
The 'offensive_value', 'defensive_value' and 'vaep_value' of each action.
See Also
--------
:func:`~socceraction.vaep.formula.offensive_value`: The offensive value
:func:`~socceraction.vaep.formula.defensive_value`: The defensive value
"""
v = pd.DataFrame()
v["offensive_value"] = offensive_value(actions, Pscores, Pconcedes)
v["defensive_value"] = defensive_value(actions, Pscores, Pconcedes)
v["vaep_value"] = v["offensive_value"] + v["defensive_value"]
return v
|