Position-Specific Analysis: Defenders

Intermediate 10 min read 117 views Nov 25, 2025

Position-Specific Analysis: Defenders

Position-specific analysis for defenders provides detailed evaluation frameworks tailored to the unique responsibilities of center backs and fullbacks in modern football. This analytical approach recognizes that defenders occupy different roles with distinct tactical demands, requiring specialized metrics beyond traditional defensive statistics. Center backs prioritize aerial dominance, positioning, and organization, while fullbacks balance defensive duties with progressive ball movement and attacking support. Understanding position-specific metrics enables more accurate player evaluation, tactical optimization, and recruitment decisions by measuring defenders against role-appropriate benchmarks rather than generic defensive standards.

Key Concepts

Analyzing defenders requires understanding the distinct responsibilities and metrics appropriate to each defensive position:

  • Center Back Metrics: Aerial duel success, interceptions, clearances, progressive passes from deep, positioning quality, and ability to defend in isolation against strikers.
  • Fullback/Wingback Metrics: Crossing accuracy, progressive carries down flanks, defensive actions in wide areas, recovery runs, and offensive contribution through assists and key passes.
  • Defensive Actions Per 90: Standardized counts of tackles, interceptions, blocks, and clearances, accounting for playing time and team possession share.
  • Defensive Duel Success Rates: Win percentages for tackles, aerial duels, and ground duels, indicating individual defending quality.
  • Progressive Defensive Actions: Ball recoveries and interceptions that lead to possession in advanced positions, showing proactive defending.
  • Passing from Defense: Pass completion rates, progressive passing distance, and passes under pressure for defenders in buildup roles.
  • Positioning and Spatial Awareness: Tracking defensive positioning relative to team shape, ability to cover dangerous spaces, and anticipation skills.
  • Error Leading to Shot/Goal: Tracking defensive mistakes that create goal-scoring opportunities or result in goals conceded.
  • Recovery Speed and Tracking: Ability to recover defensively after being bypassed, crucial for high defensive lines.
  • Pressing Contribution: Defensive actions in opponent half, pressures applied, and success rate when pressing.

Mathematical Foundation

Defensive Actions Per 90:

DA/90 = (Tackles + Interceptions + Blocks + Clearances) / (Minutes Played / 90)

Adjusted Defensive Actions (for possession):

Adjusted DA/90 = DA/90 × (50 / Team Possession %)

Adjusts for teams with lower possession having more defensive opportunities

Duel Success Rate:

Duel Success % = (Duels Won / Total Duels Contested) × 100

Aerial Dominance Index:

ADI = (Aerial Duels Won / 90) × (Aerial Success %) / 100

Progressive Passing Ratio (Defenders):

PPR = Progressive Passes / Total Passes Attempted

Defensive Impact Score:

DIS = (Tackles Won × 1.0) + (Interceptions × 1.2) + (Blocks × 1.5) - (Errors × 3.0)

Fullback Offensive Contribution:

FOC = (Crosses × 0.5) + (Key Passes × 1.5) + (Assists × 5.0) + (Progressive Carries × 0.3)

Center Back Ball Playing Index:

CBPI = (Progressive Passes × 1.0) + (Passes into Final Third × 0.8) + (Long Pass Completion % × 0.5)

Defensive Positioning Score:

DPS = (Interceptions × 1.2) / (Tackles × 1.0)

Higher ratio suggests better positioning (anticipating rather than reacting)

Recovery Run Effectiveness:

RRE = Successful Defensive Actions after Being Beaten / Times Beaten in 1v1

Python Implementation


import pandas as pd
import numpy as np
from statsbombpy import sb
from mplsoccer import Pitch, VerticalPitch
import matplotlib.pyplot as plt
import seaborn as sns

# Load match data
matches = sb.matches(competition_id=2, season_id=44)
events = sb.events(match_id=3788741)

# Identify defensive players by position
def identify_defenders(events_df):
    """Identify center backs and fullbacks from event data"""
    # Get unique player-position combinations
    player_positions = events_df[['player', 'position']].drop_duplicates()

    center_backs = player_positions[
        player_positions['position'].str.contains('Center Back', na=False, case=False)
    ]['player'].tolist()

    fullbacks = player_positions[
        player_positions['position'].str.contains('Back', na=False, case=False) &
        ~player_positions['position'].str.contains('Center', na=False, case=False)
    ]['player'].tolist()

    return {'center_backs': center_backs, 'fullbacks': fullbacks}

# Calculate center back metrics
def analyze_center_back(events_df, player_name):
    """Comprehensive center back performance analysis"""
    player_events = events_df[events_df['player'] == player_name].copy()

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

    # Estimate minutes played (rough approximation)
    minutes_played = 90  # This should come from lineup data in production

    # Defensive actions
    tackles = len(player_events[player_events['type'] == 'Duel'])
    interceptions = len(player_events[player_events['type'] == 'Interception'])
    clearances = len(player_events[player_events['type'] == 'Clearance'])
    blocks = len(player_events[player_events['type'] == 'Block'])

    # Aerial duels
    aerial_duels = player_events[
        (player_events['type'] == 'Duel') &
        (player_events['duel_type'] == 'Aerial Lost') |
        (player_events.get('duel_outcome', '').str.contains('Won', na=False))
    ]
    aerial_won = len(aerial_duels[aerial_duels.get('duel_outcome', '').str.contains('Won', na=False)])
    aerial_total = len(aerial_duels)

    # Passing
    passes = player_events[player_events['type'] == 'Pass']
    completed_passes = len(passes[passes['pass_outcome'].isna()])
    total_passes = len(passes)

    # Progressive passes
    progressive_passes = 0
    for idx, pass_event in passes.iterrows():
        if isinstance(pass_event.get('location'), list) and isinstance(pass_event.get('pass_end_location'), list):
            start_x = pass_event['location'][0]
            end_x = pass_event['pass_end_location'][0]
            if (end_x - start_x) >= 10:  # Significant forward progress
                progressive_passes += 1

    # Calculate metrics
    metrics = {
        'player': player_name,
        'minutes_played': minutes_played,
        'defensive_actions_per_90': ((tackles + interceptions + clearances + blocks) / minutes_played * 90),
        'tackles_per_90': (tackles / minutes_played * 90),
        'interceptions_per_90': (interceptions / minutes_played * 90),
        'clearances_per_90': (clearances / minutes_played * 90),
        'blocks_per_90': (blocks / minutes_played * 90),
        'aerial_duels_won': aerial_won,
        'aerial_duels_total': aerial_total,
        'aerial_success_rate': (aerial_won / aerial_total * 100) if aerial_total > 0 else 0,
        'pass_completion': (completed_passes / total_passes * 100) if total_passes > 0 else 0,
        'progressive_passes': progressive_passes,
        'progressive_pass_ratio': (progressive_passes / total_passes) if total_passes > 0 else 0,
        'positioning_score': (interceptions / tackles) if tackles > 0 else 0
    }

    return metrics

# Calculate fullback metrics
def analyze_fullback(events_df, player_name):
    """Comprehensive fullback/wingback performance analysis"""
    player_events = events_df[events_df['player'] == player_name].copy()

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

    minutes_played = 90

    # Defensive actions
    tackles = len(player_events[player_events['type'] == 'Duel'])
    interceptions = len(player_events[player_events['type'] == 'Interception'])

    # Offensive actions
    passes = player_events[player_events['type'] == 'Pass']
    crosses = len(passes[passes.get('pass_cross', False) == True])
    completed_crosses = len(passes[
        (passes.get('pass_cross', False) == True) &
        (passes['pass_outcome'].isna())
    ])

    # Progressive carries
    carries = player_events[player_events['type'] == 'Carry']
    progressive_carries = 0
    for idx, carry in carries.iterrows():
        if isinstance(carry.get('location'), list) and isinstance(carry.get('carry_end_location'), list):
            start_x = carry['location'][0]
            end_x = carry['carry_end_location'][0]
            if (end_x - start_x) >= 5:  # Forward progress
                progressive_carries += 1

    # Passes into final third
    passes_final_third = 0
    for idx, pass_event in passes.iterrows():
        if isinstance(pass_event.get('pass_end_location'), list):
            end_x = pass_event['pass_end_location'][0]
            if end_x >= 80:  # Final third starts at 80m
                passes_final_third += 1

    # Calculate metrics
    metrics = {
        'player': player_name,
        'minutes_played': minutes_played,
        'defensive_actions_per_90': ((tackles + interceptions) / minutes_played * 90),
        'tackles_per_90': (tackles / minutes_played * 90),
        'interceptions_per_90': (interceptions / minutes_played * 90),
        'crosses_per_90': (crosses / minutes_played * 90),
        'cross_completion': (completed_crosses / crosses * 100) if crosses > 0 else 0,
        'progressive_carries_per_90': (progressive_carries / minutes_played * 90),
        'passes_into_final_third': passes_final_third,
        'offensive_contribution_index': (crosses * 0.5 + passes_final_third * 0.3 + progressive_carries * 0.2)
    }

    return metrics

# Visualize defensive actions map
def plot_defensive_actions_map(events_df, player_name):
    """Map all defensive actions by a defender"""
    player_events = events_df[events_df['player'] == player_name]

    defensive_events = player_events[
        player_events['type'].isin(['Duel', 'Interception', 'Clearance', 'Block', 'Tackle'])
    ]

    if len(defensive_events) == 0:
        print(f"No defensive actions found for {player_name}")
        return None

    pitch = Pitch(pitch_type='statsbomb', pitch_color='#22312b', line_color='white')
    fig, ax = pitch.draw(figsize=(14, 10))

    # Color code by action type
    action_colors = {
        'Interception': '#00ff00',
        'Clearance': '#ff6b6b',
        'Duel': '#00d9ff',
        'Block': '#ffd700',
        'Tackle': '#ff00ff'
    }

    for action_type, color in action_colors.items():
        actions = defensive_events[defensive_events['type'] == action_type]
        if len(actions) > 0:
            x_coords = actions['location'].apply(lambda x: x[0] if isinstance(x, list) else 60)
            y_coords = actions['location'].apply(lambda x: x[1] if isinstance(x, list) else 40)

            pitch.scatter(x_coords, y_coords, ax=ax,
                         s=200, c=color, edgecolors='white',
                         linewidths=2, alpha=0.7, label=action_type)

    plt.legend(loc='upper left', fontsize=11)
    plt.title(f'{player_name} - Defensive Actions Map',
             fontsize=16, color='white', pad=20)
    plt.tight_layout()
    return fig

# Compare defenders
def compare_defenders(events_df, defenders_list, position_type='CB'):
    """Compare multiple defenders side by side"""
    comparison_data = []

    for defender in defenders_list:
        if position_type == 'CB':
            metrics = analyze_center_back(events_df, defender)
        else:
            metrics = analyze_fullback(events_df, defender)

        if metrics:
            comparison_data.append(metrics)

    return pd.DataFrame(comparison_data)

# Create radar chart for defender
def plot_defender_radar(metrics_dict, player_name, position_type='CB'):
    """Create radar chart visualizing defender attributes"""
    if position_type == 'CB':
        categories = ['Tackles
per 90', 'Interceptions
per 90', 'Clearances
per 90',
                     'Aerial
Success', 'Pass
Completion', 'Progressive
Passes']
        values = [
            metrics_dict.get('tackles_per_90', 0),
            metrics_dict.get('interceptions_per_90', 0),
            metrics_dict.get('clearances_per_90', 0),
            metrics_dict.get('aerial_success_rate', 0),
            metrics_dict.get('pass_completion', 0),
            metrics_dict.get('progressive_passes', 0)
        ]
    else:  # Fullback
        categories = ['Tackles
per 90', 'Interceptions
per 90', 'Crosses
per 90',
                     'Cross
Completion', 'Progressive
Carries', 'Offensive
Contribution']
        values = [
            metrics_dict.get('tackles_per_90', 0),
            metrics_dict.get('interceptions_per_90', 0),
            metrics_dict.get('crosses_per_90', 0),
            metrics_dict.get('cross_completion', 0),
            metrics_dict.get('progressive_carries_per_90', 0),
            metrics_dict.get('offensive_contribution_index', 0)
        ]

    # Normalize values to 0-100 scale
    max_values = [10, 10, 15, 100, 100, 50]  # Reasonable max for each metric
    normalized_values = [min(v / m * 100, 100) for v, m in zip(values, max_values)]

    # Number of variables
    num_vars = len(categories)

    # Compute angle for each axis
    angles = [n / float(num_vars) * 2 * np.pi for n in range(num_vars)]
    normalized_values += normalized_values[:1]
    angles += angles[:1]

    # Create plot
    fig, ax = plt.subplots(figsize=(10, 10), subplot_kw=dict(projection='polar'))

    # Draw the plot
    ax.plot(angles, normalized_values, 'o-', linewidth=2, color='#00d9ff')
    ax.fill(angles, normalized_values, alpha=0.25, color='#00d9ff')

    # Fix axis to go in the right order
    ax.set_xticks(angles[:-1])
    ax.set_xticklabels(categories, size=11)

    # Set y-axis limits
    ax.set_ylim(0, 100)

    plt.title(f'{player_name} - {"Center Back" if position_type == "CB" else "Fullback"} Profile',
             size=16, pad=20, fontweight='bold')
    plt.tight_layout()
    return fig

# Season-long defender analysis
def analyze_defender_season(matches_df, player_name):
    """Aggregate defender metrics across multiple matches"""
    season_totals = {
        'matches': 0,
        'minutes': 0,
        'tackles': 0,
        'interceptions': 0,
        'clearances': 0,
        'blocks': 0,
        'passes': 0,
        'completed_passes': 0,
        'progressive_passes': 0
    }

    for _, match in matches_df.iterrows():
        try:
            match_events = sb.events(match_id=match['match_id'])
            player_events = match_events[match_events['player'] == player_name]

            if len(player_events) > 0:
                season_totals['matches'] += 1
                season_totals['minutes'] += 90  # Simplified

                season_totals['tackles'] += len(player_events[player_events['type'] == 'Duel'])
                season_totals['interceptions'] += len(player_events[player_events['type'] == 'Interception'])
                season_totals['clearances'] += len(player_events[player_events['type'] == 'Clearance'])
                season_totals['blocks'] += len(player_events[player_events['type'] == 'Block'])

                passes = player_events[player_events['type'] == 'Pass']
                season_totals['passes'] += len(passes)
                season_totals['completed_passes'] += len(passes[passes['pass_outcome'].isna()])

        except Exception as e:
            continue

    # Calculate per 90 stats
    if season_totals['minutes'] > 0:
        minutes_factor = season_totals['minutes'] / 90

        season_stats = {
            'player': player_name,
            'matches': season_totals['matches'],
            'minutes': season_totals['minutes'],
            'tackles_per_90': season_totals['tackles'] / minutes_factor,
            'interceptions_per_90': season_totals['interceptions'] / minutes_factor,
            'clearances_per_90': season_totals['clearances'] / minutes_factor,
            'blocks_per_90': season_totals['blocks'] / minutes_factor,
            'pass_completion': (season_totals['completed_passes'] / season_totals['passes'] * 100) if season_totals['passes'] > 0 else 0
        }

        return season_stats

    return None

# Example usage
defenders = identify_defenders(events)
print("Center Backs:", defenders['center_backs'])
print("Fullbacks:", defenders['fullbacks'])

# Analyze specific defender
if len(defenders['center_backs']) > 0:
    cb_name = defenders['center_backs'][0]
    cb_metrics = analyze_center_back(events, cb_name)
    print(f"
Center Back Analysis - {cb_name}:")
    for key, value in cb_metrics.items():
        print(f"  {key}: {value}")

    # Visualize
    defensive_map = plot_defensive_actions_map(events, cb_name)
    if defensive_map:
        plt.show()

    radar_chart = plot_defender_radar(cb_metrics, cb_name, 'CB')
    plt.show()

# Compare multiple defenders
if len(defenders['center_backs']) >= 2:
    comparison = compare_defenders(events, defenders['center_backs'][:2], 'CB')
    print("
Defender Comparison:")
    print(comparison)

R Implementation


library(tidyverse)
library(StatsBombR)
library(ggsoccer)
library(fmsb)  # For radar charts

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

# Identify defenders
identify_defenders <- function(events_data) {
  player_positions <- events_data %>%
    select(player.name, position.name) %>%
    distinct()

  list(
    center_backs = player_positions %>%
      filter(str_detect(position.name, "Center Back")) %>%
      pull(player.name) %>%
      unique(),

    fullbacks = player_positions %>%
      filter(str_detect(position.name, "Back") &
             !str_detect(position.name, "Center")) %>%
      pull(player.name) %>%
      unique()
  )
}

# Analyze center back
analyze_center_back <- function(events_data, player_name) {
  player_events <- events_data %>% filter(player.name == player_name)

  if(nrow(player_events) == 0) return(NULL)

  minutes_played <- 90  # Simplified

  # Defensive actions
  tackles <- nrow(player_events %>% filter(type.name == "Duel"))
  interceptions <- nrow(player_events %>% filter(type.name == "Interception"))
  clearances <- nrow(player_events %>% filter(type.name == "Clearance"))
  blocks <- nrow(player_events %>% filter(type.name == "Block"))

  # Aerial duels
  aerial_duels <- player_events %>%
    filter(type.name == "Duel" & !is.na(duel.type.name))
  aerial_won <- nrow(aerial_duels %>% filter(str_detect(duel.outcome.name, "Won")))
  aerial_total <- nrow(aerial_duels)

  # Passing
  passes <- player_events %>% filter(type.name == "Pass")
  completed_passes <- nrow(passes %>% filter(is.na(pass.outcome.name)))
  total_passes <- nrow(passes)

  # Progressive passes
  progressive_passes <- passes %>%
    filter((pass.end_location.x - location.x) >= 10) %>%
    nrow()

  tibble(
    player = player_name,
    minutes_played = minutes_played,
    defensive_actions_per_90 = (tackles + interceptions + clearances + blocks) / minutes_played * 90,
    tackles_per_90 = tackles / minutes_played * 90,
    interceptions_per_90 = interceptions / minutes_played * 90,
    clearances_per_90 = clearances / minutes_played * 90,
    blocks_per_90 = blocks / minutes_played * 90,
    aerial_duels_won = aerial_won,
    aerial_duels_total = aerial_total,
    aerial_success_rate = ifelse(aerial_total > 0, aerial_won / aerial_total * 100, 0),
    pass_completion = ifelse(total_passes > 0, completed_passes / total_passes * 100, 0),
    progressive_passes = progressive_passes,
    progressive_pass_ratio = ifelse(total_passes > 0, progressive_passes / total_passes, 0),
    positioning_score = ifelse(tackles > 0, interceptions / tackles, 0)
  )
}

# Analyze fullback
analyze_fullback <- function(events_data, player_name) {
  player_events <- events_data %>% filter(player.name == player_name)

  if(nrow(player_events) == 0) return(NULL)

  minutes_played <- 90

  # Defensive actions
  tackles <- nrow(player_events %>% filter(type.name == "Duel"))
  interceptions <- nrow(player_events %>% filter(type.name == "Interception"))

  # Offensive actions
  passes <- player_events %>% filter(type.name == "Pass")
  crosses <- nrow(passes %>% filter(pass.cross == TRUE))
  completed_crosses <- nrow(passes %>% filter(pass.cross == TRUE & is.na(pass.outcome.name)))

  # Progressive carries
  carries <- player_events %>% filter(type.name == "Carry")
  progressive_carries <- carries %>%
    filter((carry.end_location.x - location.x) >= 5) %>%
    nrow()

  # Passes into final third
  passes_final_third <- passes %>%
    filter(pass.end_location.x >= 80) %>%
    nrow()

  tibble(
    player = player_name,
    minutes_played = minutes_played,
    defensive_actions_per_90 = (tackles + interceptions) / minutes_played * 90,
    tackles_per_90 = tackles / minutes_played * 90,
    interceptions_per_90 = interceptions / minutes_played * 90,
    crosses_per_90 = crosses / minutes_played * 90,
    cross_completion = ifelse(crosses > 0, completed_crosses / crosses * 100, 0),
    progressive_carries_per_90 = progressive_carries / minutes_played * 90,
    passes_into_final_third = passes_final_third,
    offensive_contribution_index = crosses * 0.5 + passes_final_third * 0.3 + progressive_carries * 0.2
  )
}

# Plot defensive actions map
plot_defensive_actions_map <- function(events_data, player_name) {
  defensive_events <- events_data %>%
    filter(
      player.name == player_name,
      type.name %in% c("Duel", "Interception", "Clearance", "Block", "Tackle")
    )

  if(nrow(defensive_events) == 0) {
    message(paste("No defensive actions found for", player_name))
    return(NULL)
  }

  ggplot(defensive_events) +
    annotate_pitch(dimensions = pitch_statsbomb) +
    theme_pitch() +
    geom_point(
      aes(x = location.x, y = location.y, color = type.name),
      size = 4,
      alpha = 0.7
    ) +
    scale_color_manual(
      values = c(
        "Interception" = "#00ff00",
        "Clearance" = "#ff6b6b",
        "Duel" = "#00d9ff",
        "Block" = "#ffd700",
        "Tackle" = "#ff00ff"
      ),
      name = "Action Type"
    ) +
    labs(title = paste(player_name, "- Defensive Actions Map")) +
    theme(
      plot.title = element_text(hjust = 0.5, size = 16, face = "bold")
    )
}

# Compare defenders
compare_defenders <- function(events_data, defenders_list, position_type = "CB") {
  map_df(defenders_list, function(defender) {
    if(position_type == "CB") {
      analyze_center_back(events_data, defender)
    } else {
      analyze_fullback(events_data, defender)
    }
  })
}

# Create radar chart
plot_defender_radar <- function(metrics_data, player_name, position_type = "CB") {
  if(position_type == "CB") {
    radar_data <- data.frame(
      Tackles = metrics_data$tackles_per_90,
      Interceptions = metrics_data$interceptions_per_90,
      Clearances = metrics_data$clearances_per_90,
      Aerial = metrics_data$aerial_success_rate,
      Passing = metrics_data$pass_completion,
      Progressive = metrics_data$progressive_passes
    )

    # Set max and min
    max_vals <- c(10, 10, 15, 100, 100, 50)
    min_vals <- c(0, 0, 0, 0, 0, 0)

  } else {  # Fullback
    radar_data <- data.frame(
      Tackles = metrics_data$tackles_per_90,
      Interceptions = metrics_data$interceptions_per_90,
      Crosses = metrics_data$crosses_per_90,
      CrossCompletion = metrics_data$cross_completion,
      ProgCarries = metrics_data$progressive_carries_per_90,
      Offensive = metrics_data$offensive_contribution_index
    )

    max_vals <- c(10, 10, 15, 100, 10, 50)
    min_vals <- c(0, 0, 0, 0, 0, 0)
  }

  # Normalize to 0-100
  normalized_data <- sweep(radar_data, 2, max_vals, "/") * 100
  normalized_data[normalized_data > 100] <- 100

  # Prepare for fmsb radar chart
  radar_df <- rbind(rep(100, ncol(normalized_data)),
                   rep(0, ncol(normalized_data)),
                   normalized_data)

  # Create radar chart
  radarchart(
    radar_df,
    axistype = 1,
    pcol = "#00d9ff",
    pfcol = rgb(0, 0.85, 1, 0.3),
    plwd = 2,
    cglcol = "grey",
    cglty = 1,
    axislabcol = "black",
    caxislabels = seq(0, 100, 25),
    title = paste(player_name, "-",
                 ifelse(position_type == "CB", "Center Back", "Fullback"),
                 "Profile")
  )
}

# Season-long analysis
analyze_defender_season <- function(matches_data, events_list, player_name) {
  season_data <- map_df(seq_len(length(events_list)), function(i) {
    match_events <- events_list[[i]]
    player_events <- match_events %>% filter(player.name == player_name)

    if(nrow(player_events) == 0) return(NULL)

    tibble(
      match_num = i,
      tackles = nrow(player_events %>% filter(type.name == "Duel")),
      interceptions = nrow(player_events %>% filter(type.name == "Interception")),
      clearances = nrow(player_events %>% filter(type.name == "Clearance")),
      blocks = nrow(player_events %>% filter(type.name == "Block"))
    )
  })

  # Aggregate
  season_totals <- season_data %>%
    summarise(
      matches = n(),
      minutes = matches * 90,
      tackles_per_90 = sum(tackles) / matches,
      interceptions_per_90 = sum(interceptions) / matches,
      clearances_per_90 = sum(clearances) / matches,
      blocks_per_90 = sum(blocks) / matches
    )

  return(season_totals)
}

# Execute analysis
defenders <- identify_defenders(events)
cat("Center Backs:", paste(defenders$center_backs, collapse = ", "), "
")
cat("Fullbacks:", paste(defenders$fullbacks, collapse = ", "), "
")

# Analyze specific defender
if(length(defenders$center_backs) > 0) {
  cb_name <- defenders$center_backs[1]
  cb_metrics <- analyze_center_back(events, cb_name)

  cat(sprintf("
Center Back Analysis - %s:
", cb_name))
  print(cb_metrics)

  # Visualize
  defensive_map <- plot_defensive_actions_map(events, cb_name)
  if(!is.null(defensive_map)) print(defensive_map)

  # Radar chart
  plot_defender_radar(cb_metrics, cb_name, "CB")
}

# Compare multiple defenders
if(length(defenders$center_backs) >= 2) {
  comparison <- compare_defenders(events, defenders$center_backs[1:2], "CB")
  cat("
Defender Comparison:
")
  print(comparison)
}

Practical Applications

Player Recruitment and Scouting: Position-specific metrics enable more accurate defender evaluation by comparing players to role-appropriate benchmarks. Scouts can identify center backs with strong ball-playing abilities for possession-based systems or fullbacks with high offensive contribution for teams requiring attacking width. This targeted approach prevents mismatches between player profiles and tactical requirements.

Tactical System Optimization: Understanding defender strengths and weaknesses helps coaches design tactical systems that maximize player capabilities. Teams with center backs strong in progressive passing can build from the back, while teams with defensively dominant but less technical center backs might employ more direct approaches. Similarly, fullbacks with strong crossing can support wide attacking patterns.

Performance Evaluation and Development: Position-specific metrics provide defenders with clear performance benchmarks and development targets. Center backs can focus on improving aerial dominance or progressive passing based on tactical demands, while fullbacks can work on balancing defensive solidity with offensive contribution. Objective metrics guide training priorities.

Match Preparation and Opposition Analysis: Analyzing opponent defenders reveals tactical opportunities. Teams can target fullbacks weak in defensive duels with direct wingers, or exploit center backs poor in aerial duels with crossing strategies. Understanding defensive positioning tendencies helps attackers create mismatches.

Squad Balance and Formation Selection: Comparing defender metrics across the squad helps coaches select optimal formations and partnerships. Pairing a ball-playing center back with a defensive-minded partner creates balance, while understanding fullback capabilities determines whether systems requiring advanced fullback positioning are viable.

Key Takeaways

  • Defender evaluation must be position-specific as center backs and fullbacks have fundamentally different role demands
  • Modern center backs require both defensive solidity and ball-playing ability to support team buildup
  • Aerial dominance remains a crucial center back attribute, especially for teams defending crosses frequently
  • Positioning metrics (interception-to-tackle ratio) often reveal defensive intelligence better than volume statistics
  • Fullbacks in modern systems must balance defensive responsibilities with offensive contribution through crosses and progressive carries
  • Defensive actions per 90 should be adjusted for team possession share to fairly compare defenders across different tactical systems
  • Error tracking is critical as a small number of mistakes can disproportionately impact defensive performance
  • Progressive passing from defenders has become increasingly important in possession-based football
  • Recovery speed and ability to track runners are essential for defenders in high defensive line systems
  • Fullback offensive contribution should include crosses, key passes, progressive carries, and passes into dangerous areas

Discussion

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