Skip to content

Add under pressure qualifier#296

Merged
probberechts merged 11 commits intoPySport:masterfrom
my-game-plan:feature/add-under-pressure
Feb 7, 2025
Merged

Add under pressure qualifier#296
probberechts merged 11 commits intoPySport:masterfrom
my-game-plan:feature/add-under-pressure

Conversation

@DriesDeprest
Copy link
Contributor

I want to add UnderPressureQualifier as a possible qualifier for our events and create support for parsing this with StatsBomb.

@JanVanHaaren
Copy link
Collaborator

@probberechts Do you have any feedback on this pull request? Thanks.

@probberechts
Copy link
Contributor

If an event (e.g., a clearance) is executed under pressure and the ball goes out subsequently, the UnderPressureQualifier is added to the resulting Clearance and BallOutEvent. Since the BallOutEvent is not an on-the-ball action, I don't think it should get the UnderPressureQualifier.

Apart from that, this PR looks good to me.

@DriesDeprest
Copy link
Contributor Author

Thanks for the feedback @probberechts! Adjusted the code accordingly.

@DriesDeprest
Copy link
Contributor Author

@koenvo, this should be ready to merge!

# Conflicts:
#	kloppy/infra/serializers/event/statsbomb/specification.py
#	kloppy/tests/test_statsbomb.py
@probberechts
Copy link
Contributor

I've started to question whether a BoolQualifier is the right approach. Both SkillCorner and Stats Perform (via their Opta Vision data) categorize pressure into three levels: High, Medium, and Low. Should we consider supporting this level of granularity? Furthermore, if you quantify the intensity of pressure, would it make more sense to implement it as a statistic instead?

@JanVanHaaren
Copy link
Collaborator

I've started to question whether a BoolQualifier is the right approach. Both SkillCorner and Stats Perform (via their Opta Vision data) categorize pressure into three levels: High, Medium, and Low. Should we consider supporting this level of granularity? Furthermore, if you quantify the intensity of pressure, would it make more sense to implement it as a statistic instead?

I agree, but I think there are two dimensions to consider. The first dimension is whether the action happens under pressure or not. The second dimension is the pressure intensity, given that the action happens under pressure. For instance, StatsBomb event data only provides information on the first dimension, although information on the second dimension could be derived from StatsBomb 360 data, if available.

@probberechts
Copy link
Contributor

Thanks for the input @JanVanHaaren. I agree that there are two dimensions. Whether there is pressure or not should go into a Qualifier; and if there is a scalar measure of pressing intensity it should go into a Statistic. The question is what we want to do with the low/medium/high categorization of pressing intensity that's in between:

  • throw away the information
  • put it in a Qualifier
  • put in in a Statistic

To be precise, it could also be implemented like this:

class PressureLevel(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"

@dataclass
class UnderPressureQualifier(Qualifier):
    value: bool | PressureLevel

    def to_dict(self):
        if isinstance(self.value, bool):
            return {f"is_{self.name}": self.value}
        elif isinstance(self.value, PressureLevel):
            return {f"{self.name}": self.value.value}
        else:
            raise TypeError("Value must be either a boolean or a PressureLevel enum.")
            
@dataclass
class PressingIntensity(ScalarStatistic):
    """Pressing intensity"""

One downside of this approach is that the qualifier value would vary depending on the data provider.

Ultimately, I'm not sure what the best approach is, and I'm fine with the current implementation as well. I just wanted to point out this alternative to encourage further discussion. Let me know your thoughts!

@DriesDeprest
Copy link
Contributor Author

My preference would go to have a distinct UnderPressureQualifier whose value is always a bool and later when relevant introduce a 'PressingIntensity' which is an OrdinalStatistic.

For StatsBomb we would thus only set the UnderPressureQualifier. But if other providers provide both information on whether pressure was applied AND the level of pressure, than both the UnderPressureQualifier and the 'PressingIntensity' can be set.

Wdyt?

class PressureLevel(Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"


@dataclass
class Statistic(ABC):
    name: str = field(init=False)


@dataclass
class OrdinalStatistic(Statistic):
    levels: Type[Enum]  # Enum class defining the levels
    value: Enum         # Current level, an instance of the Enum

    def compare(self, other: "OrdinalStatistic") -> int:
        """
        Compare this statistic with another OrdinalStatistic.
        Returns:
            -1 if this is less than other
             0 if they are equal
             1 if this is greater than other
        """
        if self.levels != other.levels:
            raise ValueError("Cannot compare statistics with different levels")
        return (
            list(self.levels).index(self.value) - list(self.levels).index(other.value)
        )


@dataclass
class PressingIntensity(OrdinalStatistic):
    """Pressing Intensity"""

    def __post_init__(self):
        self.name = "Pressing Intensity"

@DriesDeprest
Copy link
Contributor Author

What do you think about my proposal @probberechts? If you agree, I can work on the implementation.

@probberechts
Copy link
Contributor

Goh, I don't know. Since others do not seem to have an opinion about this, I guess you can decide. 😃

@DriesDeprest
Copy link
Contributor Author

@probberechts okay than I would keep the UnderPressureQualifier as a BoolQualifier and use this in the StatsBomb deserializer.

Once we start adding support with kloppy the level of pressure, then the 'PressingIntensity' could be used for it as described above in my dummy code block.

Code should be ready to merge!

@probberechts
Copy link
Contributor

Since the BallOutEvent is not an on-the-ball action, I don't think it should get the UnderPressureQualifier.

I think you previously fixed this, but it seems it's now adding the UnderPressureQualifier to BallOutEvents again. Or do I miss something?

@DriesDeprest
Copy link
Contributor Author

No, you are right, I reintroduced the issue during resolving of the merge conflicts earlier on.

But I fixed it again, should be ready to merge now!

@probberechts probberechts merged commit d8a71e0 into PySport:master Feb 7, 2025
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants