Defensive Line and Compactness

Intermediate 10 min read 121 views Nov 25, 2025

Defensive Line and Compactness

Defensive line and compactness analysis examines the spatial organization of a team's defensive structure, focusing on how teams maintain shape, control space, and minimize gaps that opponents can exploit. This analytical domain measures the vertical height of the defensive line, horizontal width between players, and overall compactness during different phases of play. Understanding defensive shape is crucial for evaluating a team's tactical discipline, pressing effectiveness, and vulnerability to different types of attacks. Modern analytics quantify these spatial relationships through metrics measuring distances, areas controlled, and synchronization of defensive movements.

Key Concepts

Analyzing defensive line and compactness requires understanding multiple spatial and temporal dimensions of team organization:

  • Defensive Line Height: The average vertical position of the deepest defenders, measured as distance from their own goal. Higher lines indicate aggressive positioning while deeper lines suggest conservative approaches.
  • Defensive Width: The horizontal spread of defending players, measuring the distance between the widest defenders. Narrower formations control central areas while wider shapes cover more ground.
  • Team Compactness: The area occupied by all outfield players, calculated through metrics like convex hull area or average inter-player distance. More compact teams control smaller spaces intensely.
  • Defensive Block Shape: The geometric formation of defenders, often visualized through polygon analysis connecting defensive players.
  • Line Synchronization: How well defensive players maintain horizontal alignment, crucial for executing offside traps and preventing through balls.
  • Pressing Triggers and Line Movement: Conditions that cause defensive lines to push higher or drop deeper, responding to ball location and opponent positioning.
  • Defensive Distance Between Lines: Vertical spacing between defensive line, midfield line, and attacking line, affecting vulnerability to passes between lines.
  • Recovery Runs: Speed and coordination of defenders dropping back after losing possession, reforming the defensive line.
  • Space Protection: Coverage of dangerous zones, particularly the area between defensive line and goalkeeper (behind the defense).

Mathematical Foundation

Average Defensive Line Height:

DL Height = (1/n) × Σ x_i for defensive players

Where x_i is the x-coordinate (vertical position) of defender i

Defensive Width:

Width = max(y_i) - min(y_i) for all defenders

Where y_i is the y-coordinate (horizontal position)

Team Compactness (Surface Area):

Compactness = Convex Hull Area of all outfield players

Calculated using computational geometry algorithms

Average Inter-Player Distance:

AIPD = (2/(n(n-1))) × Σ Σ d(i,j) for all player pairs

Where d(i,j) = √[(x_i - x_j)² + (y_i - y_j)²]

Line Synchronization (Variance):

Sync = σ² of x-coordinates of defensive line players

Lower variance indicates better synchronization

Defensive Centroid:

Centroid_x = (1/n) × Σ x_i

Centroid_y = (1/n) × Σ y_i

Represents average team position

Distance Between Lines:

Line Distance = |Average(x_defense) - Average(x_midfield)|

Compactness Index:

CI = (Occupied Area / Pitch Area) × 100

Lower percentage indicates more compact team

Defensive Pressing Intensity:

PI = (PPDA × Compactness Index) / 100

Where PPDA = Passes Allowed Per Defensive Action

Python Implementation


import pandas as pd
import numpy as np
from statsbombpy import sb
from mplsoccer import Pitch
import matplotlib.pyplot as plt
from scipy.spatial import ConvexHull, distance
from matplotlib.patches import Polygon as MPLPolygon

# Load tracking or event data
# Note: Full positional tracking data is needed for precise analysis
# This example uses event data with player positions during events

matches = sb.matches(competition_id=2, season_id=44)
events = sb.events(match_id=3788741)

# Extract player positions at specific moments
def extract_player_positions(events_df, team_name, event_types=['Pass', 'Shot', 'Carry']):
    """Extract player positions from events"""
    team_events = events_df[
        (events_df['team'] == team_name) &
        (events_df['type'].isin(event_types))
    ].copy()

    positions = []

    for idx, event in team_events.iterrows():
        if isinstance(event.get('location'), list):
            positions.append({
                'timestamp': event['timestamp'],
                'player': event['player'],
                'x': event['location'][0],
                'y': event['location'][1],
                'type': event['type']
            })

    return pd.DataFrame(positions)

# Calculate defensive line height
def calculate_defensive_line_height(positions_df, defensive_players):
    """Calculate average defensive line height"""
    defensive_positions = positions_df[
        positions_df['player'].isin(defensive_players)
    ]

    if len(defensive_positions) == 0:
        return {}

    # Group by timestamp to get snapshots
    line_heights = defensive_positions.groupby('timestamp')['x'].agg(['mean', 'min', 'max', 'std'])

    stats = {
        'avg_line_height': line_heights['mean'].mean(),
        'min_line_height': line_heights['min'].mean(),
        'max_line_height': line_heights['max'].mean(),
        'line_variance': line_heights['std'].mean()
    }

    return stats

# Calculate defensive width
def calculate_defensive_width(positions_df, defensive_players):
    """Calculate defensive width across the pitch"""
    defensive_positions = positions_df[
        positions_df['player'].isin(defensive_players)
    ]

    if len(defensive_positions) == 0:
        return {}

    # Group by timestamp
    widths = defensive_positions.groupby('timestamp')['y'].apply(
        lambda x: x.max() - x.min() if len(x) > 1 else 0
    )

    stats = {
        'avg_width': widths.mean(),
        'min_width': widths.min(),
        'max_width': widths.max(),
        'width_std': widths.std()
    }

    return stats

# Calculate team compactness using convex hull
def calculate_team_compactness(positions_df):
    """Calculate team compactness at different moments"""
    compactness_values = []

    for timestamp, group in positions_df.groupby('timestamp'):
        if len(group) >= 3:  # Need at least 3 points for convex hull
            points = group[['x', 'y']].values

            try:
                hull = ConvexHull(points)
                area = hull.volume  # In 2D, volume is actually area

                # Calculate average inter-player distance
                distances = distance.pdist(points)
                avg_distance = np.mean(distances)

                compactness_values.append({
                    'timestamp': timestamp,
                    'hull_area': area,
                    'avg_inter_player_distance': avg_distance,
                    'num_players': len(group)
                })
            except:
                continue

    if not compactness_values:
        return pd.DataFrame()

    compactness_df = pd.DataFrame(compactness_values)

    return compactness_df

# Analyze defensive shape
def analyze_defensive_shape(positions_df, defensive_players):
    """Comprehensive defensive shape analysis"""
    defensive_pos = positions_df[
        positions_df['player'].isin(defensive_players)
    ]

    shape_metrics = []

    for timestamp, group in defensive_pos.groupby('timestamp'):
        if len(group) >= 2:
            x_coords = group['x'].values
            y_coords = group['y'].values

            # Calculate metrics
            metrics = {
                'timestamp': timestamp,
                'avg_x': np.mean(x_coords),
                'avg_y': np.mean(y_coords),
                'line_height': np.mean(x_coords),
                'width': np.max(y_coords) - np.min(y_coords) if len(y_coords) > 1 else 0,
                'vertical_spread': np.max(x_coords) - np.min(x_coords) if len(x_coords) > 1 else 0,
                'synchronization': np.std(x_coords),  # Lower = better sync
                'num_defenders': len(group)
            }

            shape_metrics.append(metrics)

    return pd.DataFrame(shape_metrics)

# Calculate distance between defensive and midfield lines
def calculate_line_distances(positions_df, defensive_players, midfield_players):
    """Calculate vertical distance between lines"""
    def_pos = positions_df[positions_df['player'].isin(defensive_players)]
    mid_pos = positions_df[positions_df['player'].isin(midfield_players)]

    line_distances = []

    # Find common timestamps
    common_times = set(def_pos['timestamp']).intersection(set(mid_pos['timestamp']))

    for timestamp in common_times:
        def_at_time = def_pos[def_pos['timestamp'] == timestamp]
        mid_at_time = mid_pos[mid_pos['timestamp'] == timestamp]

        if len(def_at_time) > 0 and len(mid_at_time) > 0:
            def_avg_x = def_at_time['x'].mean()
            mid_avg_x = mid_at_time['x'].mean()

            line_distances.append({
                'timestamp': timestamp,
                'line_distance': abs(mid_avg_x - def_avg_x),
                'def_line_height': def_avg_x,
                'mid_line_height': mid_avg_x
            })

    return pd.DataFrame(line_distances)

# Visualize defensive line and compactness
def visualize_defensive_shape(positions_df, defensive_players, timestamp):
    """Visualize defensive shape at a specific moment"""
    pitch = Pitch(pitch_type='statsbomb', pitch_color='#22312b', line_color='white')
    fig, ax = pitch.draw(figsize=(14, 10))

    # Get positions at timestamp
    snapshot = positions_df[positions_df['timestamp'] == timestamp]
    defensive_snapshot = snapshot[snapshot['player'].isin(defensive_players)]

    if len(snapshot) > 0:
        # Plot all players
        pitch.scatter(snapshot['x'], snapshot['y'],
                     ax=ax, s=300, c='#00d9ff', edgecolors='white',
                     linewidths=2, zorder=3)

        # Highlight defenders
        if len(defensive_snapshot) > 0:
            pitch.scatter(defensive_snapshot['x'], defensive_snapshot['y'],
                         ax=ax, s=400, c='#ff6b6b', edgecolors='white',
                         linewidths=3, zorder=4, alpha=0.8)

            # Draw defensive line (horizontal line at average height)
            avg_def_x = defensive_snapshot['x'].mean()
            ax.axvline(x=avg_def_x, color='red', linestyle='--',
                      linewidth=2, label=f'Defensive Line (x={avg_def_x:.1f})')

            # Draw defensive width (vertical span)
            if len(defensive_snapshot) > 1:
                min_y = defensive_snapshot['y'].min()
                max_y = defensive_snapshot['y'].max()
                ax.plot([avg_def_x, avg_def_x], [min_y, max_y],
                       color='yellow', linewidth=3, alpha=0.7,
                       label=f'Width: {max_y - min_y:.1f}m')

        # Draw convex hull for compactness
        if len(snapshot) >= 3:
            points = snapshot[['x', 'y']].values
            try:
                hull = ConvexHull(points)
                hull_points = points[hull.vertices]
                hull_polygon = MPLPolygon(hull_points, fill=True, alpha=0.2,
                                         facecolor='blue', edgecolor='blue',
                                         linewidth=2)
                ax.add_patch(hull_polygon)
            except:
                pass

    plt.legend(loc='upper left', fontsize=10)
    plt.title(f'Defensive Shape Analysis', fontsize=16, color='white', pad=20)
    plt.tight_layout()
    return fig

# Analyze defensive line movement over time
def analyze_line_movement(shape_df):
    """Analyze how defensive line moves throughout match"""
    if len(shape_df) == 0:
        return {}

    # Calculate movement metrics
    shape_df = shape_df.sort_values('timestamp')
    shape_df['line_change'] = shape_df['line_height'].diff().abs()

    movement_stats = {
        'avg_line_height': shape_df['line_height'].mean(),
        'line_height_std': shape_df['line_height'].std(),
        'avg_movement': shape_df['line_change'].mean(),
        'max_line_height': shape_df['line_height'].max(),
        'min_line_height': shape_df['line_height'].min(),
        'line_range': shape_df['line_height'].max() - shape_df['line_height'].min()
    }

    return movement_stats

# Plot defensive line over time
def plot_defensive_line_timeline(shape_df):
    """Plot defensive line height over match time"""
    if len(shape_df) == 0:
        return None

    fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8), sharex=True)

    # Plot line height
    ax1.plot(range(len(shape_df)), shape_df['line_height'].values,
            color='#00d9ff', linewidth=2)
    ax1.fill_between(range(len(shape_df)), shape_df['line_height'].values,
                     alpha=0.3, color='#00d9ff')
    ax1.set_ylabel('Defensive Line Height (m)', fontsize=12)
    ax1.set_title('Defensive Line Height Over Time', fontsize=14, fontweight='bold')
    ax1.grid(True, alpha=0.3)

    # Plot width
    ax2.plot(range(len(shape_df)), shape_df['width'].values,
            color='#ff6b6b', linewidth=2)
    ax2.fill_between(range(len(shape_df)), shape_df['width'].values,
                     alpha=0.3, color='#ff6b6b')
    ax2.set_ylabel('Defensive Width (m)', fontsize=12)
    ax2.set_xlabel('Time Progression', fontsize=12)
    ax2.set_title('Defensive Width Over Time', fontsize=14, fontweight='bold')
    ax2.grid(True, alpha=0.3)

    plt.tight_layout()
    return fig

# Example usage (with hypothetical defensive player list)
defensive_players = ['Player A', 'Player B', 'Player C', 'Player D']
midfield_players = ['Player E', 'Player F', 'Player G']

# Extract positions
positions = extract_player_positions(events, 'Arsenal')

# Calculate metrics
line_height_stats = calculate_defensive_line_height(positions, defensive_players)
print("Defensive Line Height:", line_height_stats)

width_stats = calculate_defensive_width(positions, defensive_players)
print("
Defensive Width:", width_stats)

compactness = calculate_team_compactness(positions)
if len(compactness) > 0:
    print(f"
Average Team Compactness: {compactness['hull_area'].mean():.2f} sq meters")
    print(f"Average Inter-Player Distance: {compactness['avg_inter_player_distance'].mean():.2f} meters")

defensive_shape = analyze_defensive_shape(positions, defensive_players)
movement_stats = analyze_line_movement(defensive_shape)
print("
Defensive Line Movement:", movement_stats)

# Visualizations
if len(defensive_shape) > 0:
    timeline_plot = plot_defensive_line_timeline(defensive_shape)
    if timeline_plot:
        plt.show()

R Implementation


library(tidyverse)
library(StatsBombR)
library(ggsoccer)
library(sf)  # For spatial analysis

# Load match data
competitions <- FreeCompetitions()
matches <- FreeMatches(competitions %>% filter(competition_name == "Premier League"))
events <- get.matchFree(matches$match_id[1])

# Extract player positions from events
extract_player_positions <- function(events_data, team_name) {
  events_data %>%
    filter(
      team.name == team_name,
      type.name %in% c("Pass", "Shot", "Carry"),
      !is.na(location.x) & !is.na(location.y)
    ) %>%
    select(timestamp, player.name, location.x, location.y, type.name) %>%
    rename(player = player.name, x = location.x, y = location.y, type = type.name)
}

# Calculate defensive line height
calculate_defensive_line_height <- function(positions_data, defensive_players) {
  positions_data %>%
    filter(player %in% defensive_players) %>%
    group_by(timestamp) %>%
    summarise(
      avg_line_height = mean(x),
      min_line_height = min(x),
      max_line_height = max(x),
      line_variance = var(x),
      .groups = "drop"
    ) %>%
    summarise(
      overall_avg_height = mean(avg_line_height, na.rm = TRUE),
      overall_min_height = mean(min_line_height, na.rm = TRUE),
      overall_max_height = mean(max_line_height, na.rm = TRUE),
      avg_variance = mean(line_variance, na.rm = TRUE)
    )
}

# Calculate defensive width
calculate_defensive_width <- function(positions_data, defensive_players) {
  positions_data %>%
    filter(player %in% defensive_players) %>%
    group_by(timestamp) %>%
    summarise(
      width = max(y) - min(y),
      .groups = "drop"
    ) %>%
    summarise(
      avg_width = mean(width, na.rm = TRUE),
      min_width = min(width, na.rm = TRUE),
      max_width = max(width, na.rm = TRUE),
      width_std = sd(width, na.rm = TRUE)
    )
}

# Calculate team compactness using convex hull
calculate_team_compactness <- function(positions_data) {
  positions_data %>%
    group_by(timestamp) %>%
    filter(n() >= 3) %>%
    summarise(
      compactness_area = {
        points <- st_multipoint(as.matrix(cbind(x, y)))
        hull <- st_convex_hull(points)
        st_area(hull)
      },
      avg_inter_player_distance = {
        coords <- as.matrix(cbind(x, y))
        mean(dist(coords))
      },
      num_players = n(),
      .groups = "drop"
    )
}

# Analyze defensive shape
analyze_defensive_shape <- function(positions_data, defensive_players) {
  positions_data %>%
    filter(player %in% defensive_players) %>%
    group_by(timestamp) %>%
    filter(n() >= 2) %>%
    summarise(
      avg_x = mean(x),
      avg_y = mean(y),
      line_height = mean(x),
      width = max(y) - min(y),
      vertical_spread = max(x) - min(x),
      synchronization = sd(x),  # Lower = better synchronization
      num_defenders = n(),
      .groups = "drop"
    )
}

# Calculate distance between defensive and midfield lines
calculate_line_distances <- function(positions_data, defensive_players, midfield_players) {
  def_positions <- positions_data %>%
    filter(player %in% defensive_players) %>%
    group_by(timestamp) %>%
    summarise(def_line_height = mean(x), .groups = "drop")

  mid_positions <- positions_data %>%
    filter(player %in% midfield_players) %>%
    group_by(timestamp) %>%
    summarise(mid_line_height = mean(x), .groups = "drop")

  inner_join(def_positions, mid_positions, by = "timestamp") %>%
    mutate(
      line_distance = abs(mid_line_height - def_line_height)
    )
}

# Visualize defensive shape at a moment
visualize_defensive_shape <- function(positions_data, defensive_players, target_timestamp) {
  snapshot <- positions_data %>%
    filter(timestamp == target_timestamp)

  defensive_snapshot <- snapshot %>%
    filter(player %in% defensive_players)

  if(nrow(snapshot) == 0) {
    message("No data for this timestamp")
    return(NULL)
  }

  p <- ggplot() +
    annotate_pitch(dimensions = pitch_statsbomb) +
    theme_pitch()

  # All players
  p <- p +
    geom_point(data = snapshot,
               aes(x = x, y = y),
               size = 6, color = "#00d9ff", alpha = 0.8)

  # Highlight defenders
  if(nrow(defensive_snapshot) > 0) {
    p <- p +
      geom_point(data = defensive_snapshot,
                 aes(x = x, y = y),
                 size = 8, color = "#ff6b6b", alpha = 0.8)

    # Defensive line
    avg_def_x <- mean(defensive_snapshot$x)
    p <- p +
      geom_vline(xintercept = avg_def_x,
                 color = "red", linetype = "dashed", linewidth = 1) +
      annotate("text", x = avg_def_x, y = 75,
               label = sprintf("DL: %.1f", avg_def_x),
               color = "red", fontface = "bold")

    # Defensive width
    if(nrow(defensive_snapshot) > 1) {
      min_y <- min(defensive_snapshot$y)
      max_y <- max(defensive_snapshot$y)
      p <- p +
        geom_segment(aes(x = avg_def_x, xend = avg_def_x,
                        y = min_y, yend = max_y),
                    color = "yellow", linewidth = 2, alpha = 0.7) +
        annotate("text", x = avg_def_x - 5, y = (min_y + max_y) / 2,
                label = sprintf("Width: %.1f", max_y - min_y),
                color = "yellow", fontface = "bold")
    }
  }

  p <- p +
    labs(title = "Defensive Shape Analysis",
         subtitle = sprintf("Timestamp: %s", target_timestamp)) +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold"),
      plot.subtitle = element_text(hjust = 0.5)
    )

  return(p)
}

# Analyze defensive line movement over time
analyze_line_movement <- function(shape_data) {
  shape_data %>%
    arrange(timestamp) %>%
    mutate(line_change = abs(line_height - lag(line_height))) %>%
    summarise(
      avg_line_height = mean(line_height, na.rm = TRUE),
      line_height_std = sd(line_height, na.rm = TRUE),
      avg_movement = mean(line_change, na.rm = TRUE),
      max_line_height = max(line_height, na.rm = TRUE),
      min_line_height = min(line_height, na.rm = TRUE),
      line_range = max_line_height - min_line_height
    )
}

# Plot defensive line timeline
plot_defensive_line_timeline <- function(shape_data) {
  if(nrow(shape_data) == 0) return(NULL)

  shape_data <- shape_data %>%
    arrange(timestamp) %>%
    mutate(sequence = row_number())

  p1 <- ggplot(shape_data, aes(x = sequence, y = line_height)) +
    geom_line(color = "#00d9ff", linewidth = 1.2) +
    geom_ribbon(aes(ymin = 0, ymax = line_height),
                fill = "#00d9ff", alpha = 0.3) +
    labs(title = "Defensive Line Height Over Time",
         y = "Line Height (m)", x = NULL) +
    theme_minimal() +
    theme(plot.title = element_text(face = "bold", size = 14))

  p2 <- ggplot(shape_data, aes(x = sequence, y = width)) +
    geom_line(color = "#ff6b6b", linewidth = 1.2) +
    geom_ribbon(aes(ymin = 0, ymax = width),
                fill = "#ff6b6b", alpha = 0.3) +
    labs(title = "Defensive Width Over Time",
         y = "Width (m)", x = "Time Progression") +
    theme_minimal() +
    theme(plot.title = element_text(face = "bold", size = 14))

  gridExtra::grid.arrange(p1, p2, ncol = 1)
}

# Compactness heatmap over match
plot_compactness_heatmap <- function(compactness_data) {
  compactness_data %>%
    arrange(timestamp) %>%
    mutate(sequence = row_number()) %>%
    ggplot(aes(x = sequence, y = 1)) +
    geom_tile(aes(fill = compactness_area), height = 0.5) +
    scale_fill_gradient2(low = "green", mid = "yellow", high = "red",
                        midpoint = median(compactness_data$compactness_area, na.rm = TRUE),
                        name = "Compactness
(area)") +
    labs(title = "Team Compactness Over Match",
         subtitle = "Green = More compact, Red = More spread out",
         x = "Time Progression", y = NULL) +
    theme_minimal() +
    theme(
      plot.title = element_text(face = "bold", size = 14),
      axis.text.y = element_blank(),
      axis.ticks.y = element_blank()
    )
}

# Example execution
defensive_players <- c("Player A", "Player B", "Player C", "Player D")
midfield_players <- c("Player E", "Player F", "Player G")

# Extract positions
positions <- extract_player_positions(events, "Arsenal")

# Calculate metrics
line_height_stats <- calculate_defensive_line_height(positions, defensive_players)
cat("Defensive Line Height:
")
print(line_height_stats)

width_stats <- calculate_defensive_width(positions, defensive_players)
cat("
Defensive Width:
")
print(width_stats)

compactness <- calculate_team_compactness(positions)
if(nrow(compactness) > 0) {
  cat("
Average Team Compactness:
")
  cat(sprintf("Mean area: %.2f sq meters
", mean(compactness$compactness_area)))
  cat(sprintf("Avg inter-player distance: %.2f meters
",
              mean(compactness$avg_inter_player_distance)))
}

defensive_shape <- analyze_defensive_shape(positions, defensive_players)
movement_stats <- analyze_line_movement(defensive_shape)
cat("
Defensive Line Movement:
")
print(movement_stats)

line_distances <- calculate_line_distances(positions, defensive_players, midfield_players)
cat("
Average distance between lines:", mean(line_distances$line_distance, na.rm = TRUE), "meters
")

# Visualizations
if(nrow(defensive_shape) > 0) {
  timeline_plot <- plot_defensive_line_timeline(defensive_shape)
  print(timeline_plot)
}

if(nrow(compactness) > 0) {
  compactness_plot <- plot_compactness_heatmap(compactness)
  print(compactness_plot)
}

Practical Applications

Tactical Planning and System Selection: Defensive line and compactness metrics inform tactical system choices. Teams can determine optimal defensive line heights based on their defenders' speed and recovery abilities. High defensive lines require fast center backs who can track runners, while deeper lines suit slower but positionally intelligent defenders. Compactness analysis helps teams balance between controlling central areas and preventing width exploitation.

Pressing Strategy Development: Understanding defensive line height and team compactness is crucial for implementing effective pressing systems. High pressing teams maintain higher defensive lines and greater compactness to compress space and win the ball quickly. Analysis reveals optimal distances between lines to prevent passes through the structure while maintaining ability to press aggressively.

Opposition Analysis and Exploitation: Analyzing opponent defensive line behavior reveals vulnerabilities to exploit. Teams facing high defensive lines can prepare over-the-top through balls and runs in behind, while teams facing deep blocks need strategies to break down compact defensive structures. Identifying moments when defensive lines are poorly synchronized creates opportunities for penetrating passes.

Player Evaluation and Recruitment: Defensive line metrics help evaluate individual defenders within team contexts. Center backs who maintain good positioning relative to their defensive line demonstrate tactical discipline, while those frequently out of position suggest synchronization issues. Recovery speed metrics identify defenders capable of playing in high defensive lines.

Training and Defensive Organization: Compactness and line synchronization data inform defensive training sessions. Teams can practice maintaining optimal distances between lines, improving communication for line movements, and developing triggers for pushing up or dropping deep. Video analysis combined with spatial metrics provides objective feedback on defensive organization quality.

Key Takeaways

  • Defensive line height must be balanced with defender speed and recovery abilities to prevent exploitation of space behind
  • Team compactness measured by occupied area or inter-player distance reveals defensive intensity and space control
  • Line synchronization is critical for executing offside traps and preventing through balls between defenders
  • Optimal distance between defensive and midfield lines typically ranges from 10-15 meters depending on tactical approach
  • Higher defensive lines correlate with more ball recoveries in attacking areas but increase vulnerability to counter-attacks
  • Defensive width should adapt to ball location, narrowing when ball is central and expanding when ball is wide
  • Compactness naturally varies throughout matches, typically increasing when defending and decreasing when attacking
  • Modern high-pressing systems require exceptionally compact team shapes to reduce distances for pressing triggers
  • Tracking defensive line movement over time reveals tactical discipline and response to game situations
  • Effective defensive organization requires coordinated movement of all lines, not just the defensive line in isolation

Discussion

Have questions or feedback? Join our community discussion on Discord or GitHub Discussions.