Skip to content

Speed & Distance

Understanding player movement metrics in AIB Insight.

Distance Metrics

Total Distance

Total meters traveled during tracked period.

{
  "player_id": "0-10",
  "total_distance_m": 10542.5,
  "tracking_duration_sec": 5280.0
}

Distance by Speed Bucket

Distance broken down by movement intensity:

{
  "distance_by_speed_bucket": {
    "walking_m": 3200.0,
    "jogging_m": 4500.0,
    "running_m": 2200.0,
    "sprinting_m": 642.5
  }
}

Speed Buckets

Bucket Speed Range Description
Walking 0-7 km/h Standing, walking, slow movement
Jogging 7-14 km/h Light running, recovery
Running 14-21 km/h Moderate running, active play
Sprinting 21+ km/h High-intensity sprints

Speed Thresholds

SPEED_BUCKETS = {
    'walking': (0, 7),      # km/h
    'jogging': (7, 14),     # km/h
    'running': (14, 21),    # km/h
    'sprinting': (21, 40),  # km/h (40 is max realistic)
}

Speed Statistics

Metric Description Typical Values
avg_speed_kmh Average speed while tracked 6-10 km/h
max_speed_kmh Peak speed reached 28-35 km/h

Context for Max Speed

Max Speed Interpretation
< 25 km/h Limited sprinting
25-30 km/h Normal match sprints
30-35 km/h Fast sprinter
> 35 km/h Elite sprint speed

Typical Values by Position

Position Total Distance Sprint Distance Sprint %
Goalkeeper 5-6 km 0.1-0.3 km 2-5%
Center Back 9-11 km 0.3-0.6 km 3-6%
Full Back 10-12 km 0.5-0.9 km 5-8%
Central Mid 11-13 km 0.5-0.8 km 4-7%
Winger 10-12 km 0.7-1.2 km 7-10%
Striker 9-11 km 0.5-0.9 km 5-8%

Analyzing Player Performance

High-Intensity Running

Combine running + sprinting for high-intensity distance:

def high_intensity_distance(player):
    buckets = player['distance_by_speed_bucket']
    return buckets['running_m'] + buckets['sprinting_m']

# Sort by high-intensity
players_by_intensity = sorted(
    player_stats,
    key=high_intensity_distance,
    reverse=True
)

Sprint Percentage

What portion of distance was sprinting:

def sprint_percentage(player):
    sprint = player['distance_by_speed_bucket']['sprinting_m']
    total = player['total_distance_m']
    return (sprint / total * 100) if total > 0 else 0

# Find best sprinters
for player in player_stats:
    pct = sprint_percentage(player)
    print(f"{player['display_name']}: {pct:.1f}% sprinting")

Work Rate

Distance per minute of play:

def work_rate(player):
    distance = player['total_distance_m']
    duration_min = player['tracking_duration_sec'] / 60
    return distance / duration_min if duration_min > 0 else 0

# Meters per minute
for player in player_stats:
    rate = work_rate(player)
    print(f"{player['display_name']}: {rate:.0f} m/min")

Tracking Limitations

Coverage

  • Not all players are tracked for the full match
  • tracking_duration_sec shows actual tracked time
  • Compare players with similar tracking duration

Accuracy

  • Positions sampled at 25 FPS
  • Speed calculated from position changes
  • Unrealistic speeds (>40 km/h) are filtered out

Missing Data

  • Players may be untracked during:
  • Off-camera moments
  • Crowded situations
  • Substitution periods

Visualization Ideas

Distance Bar Chart

import matplotlib.pyplot as plt

players = sorted(player_stats, key=lambda p: p['total_distance_m'], reverse=True)
names = [p['display_name'] for p in players]
distances = [p['total_distance_m'] / 1000 for p in players]  # Convert to km

plt.barh(names, distances)
plt.xlabel('Distance (km)')
plt.title('Player Distances')
plt.tight_layout()
plt.show()

Speed Bucket Stacked Bar

import matplotlib.pyplot as plt
import numpy as np

players = player_stats[:10]  # Top 10
names = [p['display_name'] for p in players]

walking = [p['distance_by_speed_bucket']['walking_m'] for p in players]
jogging = [p['distance_by_speed_bucket']['jogging_m'] for p in players]
running = [p['distance_by_speed_bucket']['running_m'] for p in players]
sprinting = [p['distance_by_speed_bucket']['sprinting_m'] for p in players]

x = np.arange(len(names))
plt.bar(x, walking, label='Walking')
plt.bar(x, jogging, bottom=walking, label='Jogging')
plt.bar(x, running, bottom=np.array(walking)+np.array(jogging), label='Running')
plt.bar(x, sprinting, bottom=np.array(walking)+np.array(jogging)+np.array(running), label='Sprinting')

plt.xticks(x, names, rotation=45, ha='right')
plt.ylabel('Distance (m)')
plt.legend()
plt.tight_layout()
plt.show()