Case Study 1: Identifying an Elite Pass Rusher

Overview

In this case study, you'll evaluate four defensive end prospects using advanced pass rush metrics to determine which players' production is sustainable and which might be statistical outliers. This mirrors the type of analysis NFL teams perform during draft evaluation.


The Scenario

Your team has the 15th pick in the upcoming draft and needs a pass rusher. Four edge defenders are available, each with impressive but different statistical profiles. Your task is to:

  1. Calculate advanced pass rush metrics for each prospect
  2. Identify which stats are skill-based vs. luck-based
  3. Project NFL success
  4. Recommend a draft strategy

Part 1: The Data

import pandas as pd
import numpy as np
from typing import Dict, List

# Edge rusher profiles (college statistics)
prospects = {
    'Chase Dominator': {
        'school': 'SEC University',
        'games': 13,
        'pass_rush_snaps': 412,
        'pressures': 68,
        'sacks': 14.5,
        'hurries': 42,
        'hits': 12,
        'pass_rush_wins': 178,
        'double_teams_faced': 145,
        'double_team_wins': 52,
        'run_snaps': 285,
        'run_stops': 28,
        'tfls': 18.5,
        'avg_time_to_pressure': 2.4
    },
    'Marcus Speed': {
        'school': 'Big Ten State',
        'games': 12,
        'pass_rush_snaps': 385,
        'pressures': 52,
        'sacks': 12.0,
        'hurries': 28,
        'hits': 12,
        'pass_rush_wins': 165,
        'double_teams_faced': 88,
        'double_team_wins': 24,
        'run_snaps': 310,
        'run_stops': 22,
        'tfls': 14.0,
        'avg_time_to_pressure': 2.8
    },
    'Tyler Technique': {
        'school': 'ACC College',
        'games': 13,
        'pass_rush_snaps': 398,
        'pressures': 58,
        'sacks': 8.5,
        'hurries': 38,
        'hits': 12,
        'pass_rush_wins': 188,
        'double_teams_faced': 162,
        'double_team_wins': 68,
        'run_snaps': 295,
        'run_stops': 35,
        'tfls': 16.0,
        'avg_time_to_pressure': 2.6
    },
    'Devon Sack Artist': {
        'school': 'Pac-12 Tech',
        'games': 12,
        'pass_rush_snaps': 355,
        'pressures': 45,
        'sacks': 15.0,
        'hurries': 22,
        'hits': 8,
        'pass_rush_wins': 138,
        'double_teams_faced': 62,
        'double_team_wins': 18,
        'run_snaps': 242,
        'run_stops': 15,
        'tfls': 11.5,
        'avg_time_to_pressure': 3.1
    }
}

# Calculate traditional stats
print("=" * 80)
print("TRADITIONAL STATISTICS")
print("=" * 80)
print(f"{'Prospect':<20} {'Games':>6} {'Sacks':>8} {'TFLs':>8} {'Sacks/G':>10}")
print("-" * 80)

for name, stats in prospects.items():
    sacks_per_game = stats['sacks'] / stats['games']
    print(f"{name:<20} {stats['games']:>6} {stats['sacks']:>8.1f} "
          f"{stats['tfls']:>8.1f} {sacks_per_game:>10.2f}")

Part 2: Advanced Metrics Calculation

class EdgeRusherEvaluator:
    """Evaluate edge rushers using advanced metrics."""

    def __init__(self, prospects: Dict):
        self.prospects = prospects

    def calculate_metrics(self, name: str) -> Dict:
        """Calculate advanced pass rush metrics."""
        stats = self.prospects[name]

        # Volume
        pass_rush_snaps = stats['pass_rush_snaps']

        # Pressure metrics
        pressure_rate = stats['pressures'] / pass_rush_snaps * 100
        sack_rate = stats['sacks'] / pass_rush_snaps * 100
        hurry_rate = stats['hurries'] / pass_rush_snaps * 100

        # Pass rush win rate
        prwr = stats['pass_rush_wins'] / pass_rush_snaps * 100

        # Sack conversion (sacks / pressures) - higher = more luck
        sack_conversion = stats['sacks'] / stats['pressures'] * 100

        # Double team metrics
        double_team_rate = stats['double_teams_faced'] / pass_rush_snaps * 100
        double_team_win_rate = stats['double_team_wins'] / stats['double_teams_faced'] * 100

        # Run defense
        run_stop_rate = stats['run_stops'] / stats['run_snaps'] * 100

        # Time to pressure (lower is better)
        time_to_pressure = stats['avg_time_to_pressure']

        # Efficiency score
        efficiency = self._calculate_efficiency(
            prwr, pressure_rate, sack_conversion, double_team_win_rate
        )

        return {
            'name': name,
            'games': stats['games'],
            'pass_rush_snaps': pass_rush_snaps,
            'pressure_rate': round(pressure_rate, 1),
            'sack_rate': round(sack_rate, 1),
            'hurry_rate': round(hurry_rate, 1),
            'prwr': round(prwr, 1),
            'sack_conversion': round(sack_conversion, 1),
            'double_team_rate': round(double_team_rate, 1),
            'double_team_win_rate': round(double_team_win_rate, 1),
            'run_stop_rate': round(run_stop_rate, 1),
            'time_to_pressure': round(time_to_pressure, 2),
            'efficiency_score': round(efficiency, 1)
        }

    def _calculate_efficiency(self, prwr: float, pressure_rate: float,
                              sack_conv: float, dt_win: float) -> float:
        """
        Calculate efficiency score.

        Penalizes high sack conversion (unsustainable) and
        rewards PRWR, pressure rate, and double team wins.
        """
        # High sack conversion is often luck - penalize it
        sack_conv_penalty = max(0, (sack_conv - 20) / 10)  # Penalty if > 20%

        prwr_score = min(100, prwr / 55 * 100)
        pressure_score = min(100, pressure_rate / 20 * 100)
        dt_score = min(100, dt_win / 45 * 100)

        base_score = prwr_score * 0.40 + pressure_score * 0.35 + dt_score * 0.25
        adjusted = base_score - sack_conv_penalty * 5

        return max(0, min(100, adjusted))

    def compare_all(self) -> pd.DataFrame:
        """Compare all prospects."""
        results = []
        for name in self.prospects:
            metrics = self.calculate_metrics(name)
            results.append({
                'prospect': name,
                'prwr': metrics['prwr'],
                'pressure%': metrics['pressure_rate'],
                'sack%': metrics['sack_rate'],
                'sack_conv': metrics['sack_conversion'],
                'dt_win%': metrics['double_team_win_rate'],
                'time_to_p': metrics['time_to_pressure'],
                'efficiency': metrics['efficiency_score']
            })

        return pd.DataFrame(results)


# Evaluate prospects
evaluator = EdgeRusherEvaluator(prospects)

print("\n" + "=" * 80)
print("ADVANCED PASS RUSH METRICS")
print("=" * 80)

comparison = evaluator.compare_all()
print(comparison.to_string(index=False))

Part 3: Sustainability Analysis

def analyze_sustainability(evaluator: EdgeRusherEvaluator) -> Dict:
    """
    Analyze which production is sustainable.

    Key indicators of sustainable production:
    - High PRWR (skill)
    - Low sack conversion rate (20% is normal, 30%+ is lucky)
    - High pressure rate
    - Consistent time to pressure
    """
    results = {}

    for name in evaluator.prospects:
        metrics = evaluator.calculate_metrics(name)

        # Sustainability factors
        factors = {
            'prwr_sustainable': metrics['prwr'] >= 42,
            'pressure_sustainable': metrics['pressure_rate'] >= 12,
            'sack_conv_sustainable': metrics['sack_conversion'] <= 25,
            'time_sustainable': metrics['time_to_pressure'] <= 2.8
        }

        sustainable_count = sum(factors.values())

        # Project regression/progression
        if metrics['sack_conversion'] > 28:
            projection = 'REGRESSION likely (sack rate unsustainable)'
        elif metrics['sack_conversion'] < 18 and metrics['pressure_rate'] > 14:
            projection = 'PROGRESSION likely (underconverting pressures)'
        else:
            projection = 'STABLE production expected'

        results[name] = {
            'sustainability_score': sustainable_count,
            'factors': factors,
            'projection': projection,
            'red_flags': self._identify_red_flags(metrics)
        }

    return results


def _identify_red_flags(metrics: Dict) -> List[str]:
    """Identify concerning factors."""
    red_flags = []

    if metrics['sack_conversion'] > 30:
        red_flags.append(f"Very high sack conversion ({metrics['sack_conversion']}%) - regression likely")

    if metrics['double_team_rate'] < 25:
        red_flags.append(f"Rarely double teamed ({metrics['double_team_rate']}%) - production may decline vs better tackles")

    if metrics['time_to_pressure'] > 3.0:
        red_flags.append(f"Slow time to pressure ({metrics['time_to_pressure']}s) - may not translate to NFL")

    if metrics['run_stop_rate'] < 6:
        red_flags.append(f"Poor run defense ({metrics['run_stop_rate']}%) - one-dimensional")

    return red_flags


# Analyze sustainability
print("\n" + "=" * 80)
print("SUSTAINABILITY ANALYSIS")
print("=" * 80)

sustainability = analyze_sustainability(evaluator)

for name, analysis in sustainability.items():
    metrics = evaluator.calculate_metrics(name)
    print(f"\n{name}:")
    print(f"  Sustainability Score: {analysis['sustainability_score']}/4")
    print(f"  Projection: {analysis['projection']}")

    if analysis['red_flags']:
        print("  Red Flags:")
        for flag in analysis['red_flags']:
            print(f"    - {flag}")
    else:
        print("  Red Flags: None")

Part 4: Scouting Reports

def generate_scouting_report(evaluator: EdgeRusherEvaluator, name: str) -> str:
    """Generate detailed scouting report."""
    metrics = evaluator.calculate_metrics(name)
    stats = evaluator.prospects[name]

    # Identify strengths
    strengths = []
    if metrics['prwr'] >= 45:
        strengths.append(f"Elite pass rush win rate ({metrics['prwr']}%)")
    if metrics['pressure_rate'] >= 15:
        strengths.append(f"High-volume pressure generator ({metrics['pressure_rate']}%)")
    if metrics['double_team_win_rate'] >= 40:
        strengths.append(f"Wins against double teams ({metrics['double_team_win_rate']}%)")
    if metrics['time_to_pressure'] <= 2.5:
        strengths.append(f"Quick first step ({metrics['time_to_pressure']}s)")
    if metrics['run_stop_rate'] >= 10:
        strengths.append(f"Strong run defender ({metrics['run_stop_rate']}%)")

    # Identify concerns
    concerns = []
    if metrics['sack_conversion'] > 28:
        concerns.append(f"Inflated sack numbers ({metrics['sack_conversion']}% conversion)")
    if metrics['double_team_rate'] < 25:
        concerns.append(f"Not respected (only {metrics['double_team_rate']}% double teams)")
    if metrics['prwr'] < 40:
        concerns.append(f"Below average win rate ({metrics['prwr']}%)")
    if metrics['run_stop_rate'] < 7:
        concerns.append(f"Limited run defense ({metrics['run_stop_rate']}%)")

    # NFL projection
    if metrics['efficiency_score'] >= 75 and len(concerns) == 0:
        nfl_projection = "Pro Bowl caliber - likely Day 1 impact"
    elif metrics['efficiency_score'] >= 65:
        nfl_projection = "Quality starter - should contribute Year 1"
    elif metrics['efficiency_score'] >= 55:
        nfl_projection = "Rotational player - development needed"
    else:
        nfl_projection = "Project - significant development required"

    report = f"""
╔══════════════════════════════════════════════════════════════════════════════╗
║               SCOUTING REPORT: {name:<30}                ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ {stats['school']:<76} ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ PRODUCTION                                                                   ║
║   Games: {stats['games']:<5}  Sacks: {stats['sacks']:<6}  TFLs: {stats['tfls']:<6}                          ║
║   Pass Rush Snaps: {stats['pass_rush_snaps']:<5}  Pressures: {stats['pressures']:<5}                           ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ ADVANCED METRICS                                                             ║
║   PRWR: {metrics['prwr']}%     Pressure Rate: {metrics['pressure_rate']}%     Sack Rate: {metrics['sack_rate']}%        ║
║   Sack Conversion: {metrics['sack_conversion']}%     Time to Pressure: {metrics['time_to_pressure']}s                  ║
║   Double Team Win Rate: {metrics['double_team_win_rate']}%                                            ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ EFFICIENCY SCORE: {metrics['efficiency_score']}/100                                               ║
╠══════════════════════════════════════════════════════════════════════════════╣
║ STRENGTHS                                                                    ║"""

    for s in strengths[:4]:
        report += f"\n║   • {s:<72} ║"
    if not strengths:
        report += "\n║   • No elite traits identified                                              ║"

    report += """
╠══════════════════════════════════════════════════════════════════════════════╣
║ CONCERNS                                                                     ║"""

    for c in concerns[:4]:
        report += f"\n║   • {c:<72} ║"
    if not concerns:
        report += "\n║   • No significant concerns                                                 ║"

    report += f"""
╠══════════════════════════════════════════════════════════════════════════════╣
║ NFL PROJECTION                                                               ║
║   {nfl_projection:<74} ║
╚══════════════════════════════════════════════════════════════════════════════╝"""

    return report


# Generate reports
for name in prospects:
    print(generate_scouting_report(evaluator, name))

Part 5: Final Rankings and Recommendation

def create_draft_rankings(evaluator: EdgeRusherEvaluator) -> pd.DataFrame:
    """Create final draft rankings."""
    results = []

    for name in evaluator.prospects:
        m = evaluator.calculate_metrics(name)

        # Composite with sustainability adjustments
        base_score = m['efficiency_score']

        # Penalty for unsustainable sack conversion
        if m['sack_conversion'] > 28:
            base_score -= 10
        elif m['sack_conversion'] > 25:
            base_score -= 5

        # Bonus for double team resistance
        if m['double_team_rate'] > 35:
            base_score += 5

        # Run defense bonus
        if m['run_stop_rate'] >= 10:
            base_score += 3

        results.append({
            'prospect': name,
            'sacks': evaluator.prospects[name]['sacks'],
            'prwr': m['prwr'],
            'pressure%': m['pressure_rate'],
            'adjusted_score': round(base_score, 1)
        })

    df = pd.DataFrame(results)
    df['rank'] = df['adjusted_score'].rank(ascending=False).astype(int)
    return df.sort_values('rank')


rankings = create_draft_rankings(evaluator)

print("\n" + "=" * 80)
print("FINAL DRAFT RANKINGS")
print("=" * 80)
print(rankings.to_string(index=False))

print("""

DRAFT RECOMMENDATION:
=====================

1. TYLER TECHNIQUE (Rank 1)
   - Highest PRWR despite facing most double teams
   - Low sack conversion = room for MORE sacks
   - Complete player (run defense + pass rush)
   - RECOMMENDATION: Target at #15 if available

2. CHASE DOMINATOR (Rank 2)
   - Elite pressure production
   - Quick first step (2.4s time to pressure)
   - Minor concern: somewhat high sack conversion
   - RECOMMENDATION: Excellent pick at #15

3. MARCUS SPEED (Rank 3)
   - Solid all-around player
   - Doesn't face many double teams (concern)
   - Production may decline vs. NFL tackles
   - RECOMMENDATION: Consider if top 2 unavailable

4. DEVON SACK ARTIST (Rank 4)
   - SACK TOTAL IS MISLEADING
   - Highest sack conversion = unsustainable luck
   - Slow time to pressure won't translate
   - Rarely double teamed = not respected
   - RECOMMENDATION: AVOID - production will regress
""")

Summary

This case study demonstrated how to evaluate pass rushers beyond sack totals:

  1. Pass Rush Win Rate (PRWR): Most stable predictor of future success
  2. Sack Conversion Rate: High rates indicate luck, expect regression
  3. Double Team Rate: How opponents respect the rusher
  4. Time to Pressure: Speed of winning affects NFL translation
  5. Run Defense: Complete players are more valuable

Key Finding:

Devon Sack Artist's 15 sacks are misleading—his 33% sack conversion rate is unsustainable. Tyler Technique, despite fewer sacks, projects better due to elite PRWR and sustainable production profile.