← All methods

Team Style Profiles

Summary

All other analysis pages answer "how good is this team?" (Elo, xPts, SQI). Style profiles answer "what kind of team is this?" by building per-team-season style vectors from ~20 stored-but-unused stats that describe how a team plays rather than how well they perform.

The feature lives at /analysis/style with two visualizations:

  1. PCA scatter showing every team-season as a dot in 2D style space, with K-means cluster labels identifying playing archetypes
  2. Radar charts showing each team's 5-dimension style fingerprint

Dimensions

Each dimension aggregates related per-game averages into a single composite z-score. Individual stat z-scores are also exposed.

Pressing Height

How high up the pitch a team wins the ball back.

Stat key Direction Description
possWonAtt3rd + Possession won in the attacking third
possWonMid3rd + Possession won in the middle third
possWonDef3rd inverted Possession won in the defensive third

A team that wins the ball back high presses aggressively. A team that wins it deep plays a low block.

Directness

How quickly and directly the team moves the ball forward.

Stat key Direction Description
totalLongBalls + Total long balls attempted
accurateLongBalls + Accurate long balls
fwdPass + Forward passes
totalFinalThirdPasses + Passes in the final third
longPassOwnToOpp + Long passes from own to opponent half
backwardPass inverted Backward passes

High directness = a team that plays forward quickly with long balls. Low directness = patient, backward-passing buildup play.

Width / Penetration

How much a team uses the flanks and how often it enters dangerous areas.

Stat key Direction Description
leftsidePass + Passes on the left side
rightsidePass + Passes on the right side
totalCross + Crosses attempted
accurateCross + Accurate crosses
finalThirdEntries + Entries into the final third
penAreaEntries + Entries into the penalty area

Defensive Activity

How active the team is defensively (tackling, challenging).

Stat key Direction Description
totalTackle + Total tackles
challengeLost + Challenges lost (high = aggressive but imprecise)
possLostAll inverted Total possession lost
possLostCtrl inverted Controlled possession lost

The inversions on possession loss mean that a team which loses the ball a lot scores lower on defensive activity (they're being dispossessed, not actively defending).

Ball Retention

How well the team keeps the ball.

Stat key Direction Description
openPlayPass + Open-play passes
possessionPercentage + Possession percentage
dispossessed inverted Times dispossessed

Normalization

Stats are z-scored within each season. For each stat key, the mean and standard deviation are computed across all teams in that season, and each team's value is transformed to a z-score:

z = (value - season_mean) / season_std

When standard deviation is zero (all teams identical on a stat), the z-score is set to 0.

Within-season normalization means a team is compared to its contemporaries, not to teams from different eras. This handles secular drift in league-wide stats (e.g. if all teams pass more in 2025 than 2019, a 2025 team with average passing doesn't get inflated on the retention dimension).

Minimum matches filter: team-seasons with fewer than 5 matches are excluded to avoid noisy per-game averages. This primarily affects teams that join or leave mid-season.

Dimension Score

Each dimension's composite score is the mean of its constituent z-scores (with inversions applied). A dimension score of +1.0 means the team is roughly one standard deviation above the league average on that dimension. Zero means league average.

PCA

Principal Component Analysis reduces the 5 dimension scores to 2 coordinates for the scatter plot. PCA runs on the full matrix of ALL qualifying team-seasons (across all years), not per-season, so that the 2D projection is globally consistent.

PC1 typically captures the largest source of style variation in the league (often a possession-vs-directness axis). PC2 captures the next largest orthogonal variation.

The variance explained by each component is stored and displayed on the chart axes (e.g. "PC1 (69%)").

Instability note: PCA coordinates change when the dataset changes (new season, updated match data). The scatter plot is exploratory, not a fixed coordinate system. Team-season positions relative to each other are more meaningful than absolute coordinates.

Clustering

K-means with k=4 groups team-seasons into playing archetypes. random_state=42 ensures reproducibility given the same data.

Cluster labels are assigned heuristically from the cluster centroids: the dimension with the highest absolute centroid value names the cluster. A secondary modifier is added when another dimension also has a notable signal (absolute value > 0.3).

Examples of cluster labels:

  • "High Press / Wide" (pressing is the strongest signal, width is also notable)
  • "Possession" (retention dominates)
  • "Direct / Aggressive" (directness dominates, defensive activity is also high)

Stability note: like PCA, cluster assignments can shift when new data is added. Labels are descriptive, not prescriptive. A team switching clusters between runs does not mean it changed style; it may mean the cluster boundary moved.

Data

  • Source: team_season_stats.averages_json (precomputed per-game averages from compute_team_stats.py)
  • Time range: 2019-2025, all matches with match_stats data
  • Minimum: 5 matches per team-season
  • Total rows: ~55 team-seasons across 7 years

Failure Modes

  • Small sample per season. Only 7-8 teams per season means z-scores have high variance. A single team's outlier performance can shift the league mean significantly.
  • Stats are counts, not spatial. totalLongBalls counts long balls but says nothing about where they land or whether they create chances. Two teams with 45 long balls per game could be playing very differently.
  • No formation context. formationUsed is stored but not included in v1 (it's a string, not numeric, and requires separate per-match handling).
  • Clustering is exploratory. With 55 data points and 5 dimensions, K-means with k=4 is a rough grouping, not a statistically validated taxonomy. The labels are convenient shorthands, not rigorous classifications.
  • Cross-season comparison is limited. Because z-scores are within-season, you cannot directly compare a 2019 team's style score to a 2024 team's. The PCA projection allows visual comparison across seasons, but the underlying z-scores are relative to different baselines.

What Would Make It Better

  1. Raw per-game rates mode. An alternative normalization using raw per-game averages instead of z-scores, enabling direct cross-season comparison. Trade-off: susceptible to secular drift.
  2. Formation analysis. formationUsed is stored per match. Win rate by formation, most common formation per team-season, formation as a categorical dimension in the style profile.
  3. UMAP alternative. Better cluster separation than PCA but harder to interpret and non-deterministic without careful seeding.
  4. Interactive radar overlay. Select two teams to overlay their radars for direct comparison.
  5. Style evolution timeline. Show how a team's style dimensions change across seasons as a small-multiples line chart.
  6. Style matchup integration. Do certain style combinations predict match outcomes? E.g. high-press teams vs possession teams. Could feed into the match prediction model.