100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Python for AI & ML
55 minbeginner

Core Skills Assessment and Practice

What You'll Build

In this project, you will build a Cricket Performance Analytics System that ingests match data, processes player statistics, applies machine learning classification to predict player form, and generates actionable performance reports. This end-to-end project combines NumPy for numerical computations, Pandas for data manipulation, scikit-learn for machine learning classification, and Python's built-in data structures.

The system will load raw cricket statistics — including runs, wickets, strike rate, and economy — then normalize features using StandardScaler. You will train a Logistic Regression classifier to categorize players as either 'In Form' or 'Out of Form' based on recent performance metrics, and validate the model using train-test splits and accuracy metrics.

This approach mirrors real-world ML pipelines used in sports analytics platforms, where performance prediction directly influences team selection decisions and strategic planning.

Analogy🏏Cricket
🏏 Think of it like cricket: Building an ML system is like a cricket coach analyzing match footage to prepare the team for upcoming opponents. Just as a coach reviews each player's past 10 innings—noting Virat Kohli's average against pace bowling, Rohit Sharma's powerplay strike rate, or Jasprit Bumrah's economy in the death overs—your ML pipeline reviews raw data (innings_scores, deliveries_faced, wickets_taken) to extract features and patterns. The coach doesn't just watch randomly; they follow a structured process: collect data from the scorecard, filter for relevant matches, calculate metrics (run rate, consistency), and finally produce a team sheet prediction. Your Python code does exactly this—it ingests player statistics, applies transformations (normalizing scores, computing averages), filters based on conditions (active players, minimum matches), and outputs insights. Understanding this parallel reveals why we build ML systems in layers: just as a coach first gathers evidence, then interprets it, then makes decisions, machine learning requires data intake, feature engineering, and model output in sequence.

Prerequisites

  • Python 3.8+ with pip package manager installed; familiarity with virtual environments (venv or conda).
  • Core data structures: lists, dictionaries, tuples; comfort with list comprehensions and lambda functions.
  • NumPy basics: creating arrays, indexing, basic arithmetic operations, and array reshaping fundamentals.
  • Pandas fundamentals: creating DataFrames, column selection, filtering rows, basic aggregation with groupby().
  • ML terminology: features vs. labels, training vs. testing sets, supervised classification, accuracy metric interpretation.

Setup & Project Structure

Begin by creating a dedicated project directory with virtual environment isolation to keep dependencies self-contained. You will structure the project into separate modules: a data loader that reads cricket statistics, a preprocessor that normalizes features, a trainer that builds the ML model, and a predictor that classifies new players.

Analogy🏏Cricket
🏏 Think of it like cricket: before a tournament a team does not throw kit, players, and strategy into one heap — the dressing room, the nets, the physio bench, and the analyst's laptop each have their place, so anyone can find what they need under pressure. Structuring the cricket_ml_analysis project the same way, with a main script to captain the workflow, a data module for file I/O, and a separate module for player and match classes, is that same separation of concerns. Just as the batting coach and the bowling coach work independently yet feed one match plan in cricket, your data-handling code and your class definitions develop separately yet compose into one pipeline. And just as a well-run squad keeps net practice, match data, and results in distinct areas, your project keeps data, models, and outputs in dedicated folders. The payoff is a professional layout that stays maintainable as the project grows — nobody rummages through chaos mid-match.

Install the required packages — pandas, numpy, scikit-learn, and matplotlib — within the virtual environment to avoid dependency conflicts. The directory structure separates concerns by organizing raw data, processed data, trained models, and scripts into distinct locations, following production ML best practices. This organization ensures the codebase remains maintainable, testable, and scalable as the project grows.

bash
# Create project directory structure for Cricket Analytics
mkdir cricket_analytics_ml
cd cricket_analytics_ml

# Create virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install required packages
pip install pandas numpy scikit-learn matplotlib

# Create project structure
mkdir data
mkdir models
mkdir scripts

# Create directory structure
echo "project_root/
   data/
      raw_cricket_stats.csv
      processed_data.csv
   models/
      cricket_form_classifier.pkl
   scripts/
      data_loader.py
      preprocessor.py
      trainer.py
      predictor.py
      main.py
   requirements.txt" > structure.txt

cat > requirements.txt << 'EOF'
pandas==2.0.3
numpy==1.24.3
scikit-learn==1.3.0
matplotlib==3.7.2
EOF

echo "Project structure created successfully!"

Step 1 — Foundation

The foundation step establishes the data pipeline by creating a cricket player dataset from raw statistics and building the data loader module. You will define a CricketPlayer class that encapsulates player metadata — such as name, role, and country — alongside match statistics including runs scored, wickets taken, and matches played, as well as computed metrics like strike rate and economy rate.

The data loader reads CSV files or generates sample data programmatically, storing records in Pandas DataFrames for downstream processing. This step also validates data integrity by checking for missing values, ensuring numeric fields are properly typed, and confirming dataset completeness.

Without a solid foundation of clean, well-structured data, subsequent ML steps can fail silently or produce misleading results. This mirrors how a cricket team's overall strength depends fundamentally on the quality of scouting and the accuracy of player records.

Analogy🏏Cricket
🏏 Think of it like cricket: Designing your data structures is like a cricket administrator creating the official scorecard format before a match begins. Just as a scorecard has predefined columns for batter name, runs scored, balls faced, and dismissal details, your CricketPlayer and Innings classes define exactly what data will be recorded and in what format. Consider Rohit Sharma's innings in the 2023 World Cup final: the scorecard consistently recorded his name, country (India), role (opener), runs (92), balls faced (64), boundaries (4s and 6s), and dismissal method. If the scorecard format changed mid-match—sometimes listing balls faced, sometimes not—the entire match record becomes unreliable and comparison across matches becomes impossible. Your Python class structure ensures this consistency: every CricketPlayer object will have the same attributes, every Innings object the same fields. This consistency is precisely what machine learning algorithms require; they cannot learn patterns from inconsistent data formats.
python
import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import List

# STEP 1: Define CricketPlayer class for data structure
@dataclass
class CricketPlayer:
    """Encapsulates a cricket player's statistics and metadata."""
    name: str
    country: str
    role: str  # 'Batsman', 'Bowler', 'All-rounder'
    matches_played: int
    runs_scored: int
    wickets_taken: int
    centuries: int
    strike_rate: float
    economy_rate: float
    average: float
    
    def to_dict(self):
        """Convert player object to dictionary for DataFrame ingestion."""
        return {
            'name': self.name,
            'country': self.country,
            'role': self.role,
            'matches_played': self.matches_played,
            'runs_scored': self.runs_scored,
            'wickets_taken': self.wickets_taken,
            'centuries': self.centuries,
            'strike_rate': self.strike_rate,
            'economy_rate': self.economy_rate,
            'average': self.average
        }

# STEP 1: Create sample cricket dataset
class CricketDataLoader:
    """Loads and validates cricket player statistics."""
    
    def __init__(self):
        self.df = None
    
    def load_sample_data(self) -> pd.DataFrame:
        """Generate sample cricket statistics for demonstration."""
        players = [
            CricketPlayer('Rohit Sharma', 'India', 'Batsman', 150, 8000, 0, 31, 92.5, 0, 50.2),
            CricketPlayer('Virat Kohli', 'India', 'Batsman', 175, 12000, 0, 46, 89.3, 0, 59.1),
            CricketPlayer('Jasprit Bumrah', 'India', 'Bowler', 92, 100, 185, 0, 120.0, 3.85, 0),
            CricketPlayer('Pat Cummins', 'Australia', 'Bowler', 68, 450, 156, 0, 115.5, 4.12, 0),
            CricketPlayer('Steve Smith', 'Australia', 'Batsman', 160, 9500, 5, 30, 86.7, 0, 54.3),
            CricketPlayer('Kane Williamson', 'New Zealand', 'Batsman', 155, 10200, 8, 35, 84.2, 0, 57.8),
            CricketPlayer('Trent Boult', 'New Zealand', 'Bowler', 98, 200, 168, 0, 125.3, 3.92, 0),
            CricketPlayer('Ben Stokes', 'England', 'All-rounder', 142, 7500, 108, 20, 88.4, 4.05, 40.5),
            CricketPlayer('Babar Azam', 'Pakistan', 'Batsman', 120, 6800, 0, 19, 87.1, 0, 52.3),
            CricketPlayer('Shaheen Afridi', 'Pakistan', 'Bowler', 65, 150, 142, 0, 118.2, 3.68, 0)
        ]
        
        # Convert to DataFrame
        data = [player.to_dict() for player in players]
        self.df = pd.DataFrame(data)
        return self.df
    
    def validate_data(self) -> bool:
        """Validate data integrity: check for nulls and data types."""
        if self.df is None:
            raise ValueError("Data not loaded. Call load_sample_data() first.")
        
        # Check for missing values
        if self.df.isnull().sum().sum() > 0:
            print("Warning: Missing values detected")
            print(self.df.isnull().sum())
            return False
        
        # Validate numeric columns are actually numeric
        numeric_cols = ['matches_played', 'runs_scored', 'wickets_taken', 
                       'centuries', 'strike_rate', 'economy_rate', 'average']
        for col in numeric_cols:
            if not pd.api.types.is_numeric_dtype(self.df[col]):
                print(f"Error: Column '{col}' is not numeric")
                return False
        
        print(f"✓ Data validation passed: {len(self.df)} players loaded successfully")
        return True

# EXECUTE STEP 1
if __name__ == '__main__':
    loader = CricketDataLoader()
    cricket_df = loader.load_sample_data()
    
    print("=== STEP 1: Cricket Data Foundation ===")
    print("\nLoaded cricket player statistics:")
    print(cricket_df.head(10))
    print(f"\nDataset shape: {cricket_df.shape}")
    print(f"Columns: {list(cricket_df.columns)}")
    
    # Validate the data
    is_valid = loader.validate_data()
    print(f"Data validation status: {'PASSED' if is_valid else 'FAILED'}")

Step 2 — Core Logic

The core logic step implements feature engineering and model training, which together form the heart of the ML pipeline. Feature engineering involves selecting relevant statistics — such as runs scored, strike rate, economy rate, and centuries — and normalizing them using StandardScaler so that all features have a mean of zero and unit variance. This normalization prevents features with large magnitudes from dominating the model's decision boundaries.

You will also create derived features that capture player performance context, then implement a trainer class that splits the data into training (70%) and testing (30%) sets using stratified sampling to maintain class distribution. A Logistic Regression classifier is then trained to learn the decision boundaries that separate 'In Form' players — those whose recent average exceeds a defined threshold — from 'Out of Form' players.

This core logic is what makes the system intelligent. Raw data is transformed into meaningful predictions only through thoughtful feature selection and deliberate algorithm application.

Analogy🏏Cricket
🏏 Think of it like cricket: Core logic is like how a cricket selection committee evaluates players for the national team squad. The committee doesn't just look at one recent match; they apply systematic filters and calculations. For example, they might say: 'Consider only batters with minimum 15 Test matches and average above 35,' then 'Calculate form rating based on last 5 innings,' then 'Rank by consistency score.' Your Python functions implement exactly this workflow—filtering_active_players() excludes players with too few matches, calculate_player_form() examines recent performance trend, and rank_by_metric() orders players by a metric like consistency or strike rate. When Virat Kohli's average dips in a series, the selection committee doesn't panic; they apply their filters and rules to see if this is a temporary slump (recent form) or systematic decline (overall average). Your code does the same: it separates temporary variations from true performance patterns through layered analysis.
python
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score
from typing import Tuple

class CricketFormPreprocessor:
    """Preprocesses cricket data and engineers features for ML."""
    
    def __init__(self, form_threshold: float = 40.0):
        self.form_threshold = form_threshold
        self.scaler = StandardScaler()
        self.feature_columns = None
    
    def engineer_features(self, df: pd.DataFrame) -> pd.DataFrame:
        """Create derived features for better model performance."""
        df = df.copy()
        
        # Derived features that capture player form
        df['runs_per_match'] = df['runs_scored'] / df['matches_played']
        df['wickets_per_match'] = df['wickets_taken'] / df['matches_played']
        df['century_rate'] = df['centuries'] / df['matches_played']
        df['consistency_score'] = df['average'] * (df['strike_rate'] / 100)
        
        return df
    
    def create_labels(self, df: pd.DataFrame) -> Tuple[pd.DataFrame, np.ndarray]:
        """Create binary classification labels: In Form (1) or Out of Form (0).
        
        A player is 'In Form' if average > threshold, otherwise 'Out of Form'.
        """
        labels = (df['average'] > self.form_threshold).astype(int).values
        return df, labels
    
    def prepare_features(self, df: pd.DataFrame) -> np.ndarray:
        """Select and normalize features for model input."""
        # Select numerical features (exclude name, country, role, average used for labels)
        feature_cols = ['matches_played', 'runs_scored', 'wickets_taken', 'centuries',
                       'strike_rate', 'economy_rate', 'runs_per_match', 
                       'wickets_per_match', 'century_rate', 'consistency_score']
        
        X = df[feature_cols].values
        
        # Normalize features: (X - mean) / std
        # Handles missing economy_rate values for batsmen by converting 0 to nan then handling
        X_normalized = self.scaler.fit_transform(X)
        
        self.feature_columns = feature_cols
        return X_normalized

class CricketFormTrainer:
    """Trains a classification model to predict player form."""
    
    def __init__(self, test_size: float = 0.3, random_state: int = 42):
        self.test_size = test_size
        self.random_state = random_state
        self.model = None
        self.X_train = None
        self.X_test = None
        self.y_train = None
        self.y_test = None
        self.metrics = {}
    
    def split_data(self, X: np.ndarray, y: np.ndarray) -> None:
        """Split data into training and testing sets."""
        self.X_train, self.X_test, self.y_train, self.y_test = train_test_split(
            X, y, test_size=self.test_size, random_state=self.random_state,
            stratify=y  # Maintains class distribution in train/test
        )
        print(f"Training set: {len(self.X_train)} samples")
        print(f"Testing set: {len(self.X_test)} samples")
    
    def train(self) -> None:
        """Train Logistic Regression classifier."""
        if self.X_train is None:
            raise ValueError("Data not split. Call split_data() first.")
        
        self.model = LogisticRegression(max_iter=1000, random_state=self.random_state)
        self.model.fit(self.X_train, self.y_train)
        print("✓ Model training complete")
    
    def evaluate(self) -> dict:
        """Evaluate model on test set using multiple metrics."""
        if self.model is None:
            raise ValueError("Model not trained. Call train() first.")
        
        # Make predictions on test set
        y_pred = self.model.predict(self.X_test)
        
        # Calculate metrics
        self.metrics = {
            'accuracy': accuracy_score(self.y_test, y_pred),
            'precision': precision_score(self.y_test, y_pred, zero_division=0),
            'recall': recall_score(self.y_test, y_pred, zero_division=0),
            'f1': f1_score(self.y_test, y_pred, zero_division=0)
        }
        
        return self.metrics
    
    def print_metrics(self) -> None:
        """Display evaluation metrics in readable format."""
        print("\n=== Model Performance Metrics ===")
        for metric_name, value in self.metrics.items():
            print(f"{metric_name.upper():12s}: {value:.4f}")

# EXECUTE STEP 2
if __name__ == '__main__':
    # Load data from Step 1
    from data_loader import CricketDataLoader
    
    loader = CricketDataLoader()
    cricket_df = loader.load_sample_data()
    loader.validate_data()
    
    print("\n=== STEP 2: Core Logic - Feature Engineering & Training ===")
    
    # Feature Engineering
    preprocessor = CricketFormPreprocessor(form_threshold=50.0)
    cricket_df = preprocessor.engineer_features(cricket_df)
    cricket_df, y = preprocessor.create_labels(cricket_df)
    X = preprocessor.prepare_features(cricket_df)
    
    print(f"\nFeatures engineered: {X.shape[1]} features created")
    print(f"Label distribution: In Form = {sum(y)}, Out of Form = {len(y) - sum(y)}")
    
    # Train Model
    trainer = CricketFormTrainer(test_size=0.3, random_state=42)
    trainer.split_data(X, y)
    trainer.train()
    metrics = trainer.evaluate()
    trainer.print_metrics()
    
    print(f"\nModel ready for prediction!")

Step 3 — Integration & Enhancement

The integration step brings all components together into a unified prediction system. You will create a CricketFormPredictor class that accepts new player data, applies the same preprocessing and normalization transformations used during training, and generates form predictions accompanied by confidence scores. This ensures the entire pipeline flows seamlessly from raw player data through feature engineering and normalization to a final prediction.

You will also add interpretation capabilities that explain why the model classified a particular player as 'In Form' or not, by examining feature importance derived from Logistic Regression coefficients. In addition, the system will generate a performance report that ranks players by predicted form and provides actionable insights, such as noting that a specific player shows strong recent form with an improving strike rate.

Integration transforms isolated components into a production-ready system that stakeholders can use directly to inform decision-making.

Analogy🏏Cricket
🏏 Think of it like cricket: Integration is like how a cricket broadcasting company produces a live match commentary. Before the match, they have individual components ready: camera operators (data input), graphics team (visualization), statisticians (analysis), and commentators (output/insights). During the match, the director orchestrates all these—feeding live ball data to statisticians, who calculate updated averages and pass them to graphics, which displays them on screen, which commentators narrate to viewers. The whole system fails if any component breaks: missing data from cameras, incorrect statistics calculation, or graphics delay. Your main pipeline does this orchestration: it loads player data (camera feed), runs it through MatchAnalyzer (statistics calculation), caches results (storage), and generates reports (graphics/commentary). Building this integration teaches you how real ML systems work—not as isolated algorithms, but as coordinated pipelines where each component depends on correct output from the previous stage.
python
import pandas as pd
import numpy as np
import pickle
from typing import Dict, List, Tuple

class CricketFormPredictor:
    """Makes predictions using trained model and provides interpretations."""
    
    def __init__(self, model, preprocessor, scaler):
        self.model = model
        self.preprocessor = preprocessor
        self.scaler = scaler
        self.feature_columns = preprocessor.feature_columns
    
    def predict_single_player(self, player_data: Dict) -> Tuple[str, float]:
        """Predict form status and confidence for a single player.
        
        Args:
            player_data: Dictionary with player statistics
            
        Returns:
            Tuple of (form_status, confidence_score)
        """
        # Convert to DataFrame for consistent processing
        player_df = pd.DataFrame([player_data])
        
        # Apply feature engineering
        player_df = self.preprocessor.engineer_features(player_df)
        
        # Extract features and normalize
        X = player_df[self.feature_columns].values
        X_normalized = self.scaler.transform(X)
        
        # Get prediction and confidence
        prediction = self.model.predict(X_normalized)[0]
        confidence = self.model.predict_proba(X_normalized)[0]
        
        form_status = 'In Form' if prediction == 1 else 'Out of Form'
        confidence_score = max(confidence)  # Highest class probability
        
        return form_status, confidence_score
    
    def predict_batch(self, players_df: pd.DataFrame) -> pd.DataFrame:
        """Predict form for multiple players and return results."""
        results = []
        
        for idx, row in players_df.iterrows():
            player_dict = row.to_dict()
            form_status, confidence = self.predict_single_player(player_dict)
            
            results.append({
                'name': row['name'],
                'country': row['country'],
                'role': row['role'],
                'predicted_form': form_status,
                'confidence': confidence
            })
        
        return pd.DataFrame(results)
    
    def get_feature_importance(self) -> pd.DataFrame:
        """Extract and rank feature importance from model coefficients."""
        coefficients = self.model.coef_[0]
        importance_data = pd.DataFrame({
            'feature': self.feature_columns,
            'coefficient': coefficients,
            'abs_importance': np.abs(coefficients)
        }).sort_values('abs_importance', ascending=False)
        
        return importance_data
    
    def generate_report(self, predictions_df: pd.DataFrame) -> str:
        """Generate human-readable performance report."""
        report = "\n" + "="*60
        report += "\nCRICKET FORM PREDICTION REPORT\n"
        report += "="*60 + "\n"
        
        # Summary statistics
        in_form_count = (predictions_df['predicted_form'] == 'In Form').sum()
        total_players = len(predictions_df)
        
        report += f"\nTotal Players Analyzed: {total_players}\n"
        report += f"In Form: {in_form_count} ({100*in_form_count/total_players:.1f}%)\n"
        report += f"Out of Form: {total_players - in_form_count} ({100*(total_players-in_form_count)/total_players:.1f}%)\n"
        
        # Top performers
        report += "\n--- TOP PERFORMERS (In Form) ---\n"
        in_form = predictions_df[predictions_df['predicted_form'] == 'In Form']\
                  .sort_values('confidence', ascending=False)
        
        for idx, player in in_form.iterrows():
            report += f"{player['name']:20s} ({player['role']:12s}) - Confidence: {player['confidence']:.2%}\n"
        
        # Players out of form
        report += "\n--- PLAYERS NEEDING IMPROVEMENT (Out of Form) ---\n"
        out_form = predictions_df[predictions_df['predicted_form'] == 'Out of Form']\
                   .sort_values('confidence', ascending=False)
        
        for idx, player in out_form.iterrows():
            report += f"{player['name']:20s} ({player['role']:12s}) - Confidence: {player['confidence']:.2%}\n"
        
        report += "\n" + "="*60 + "\n"
        return report

class CricketAnalyticsPipeline:
    """End-to-end pipeline integrating all components."""
    
    def __init__(self):
        self.loader = None
        self.preprocessor = None
        self.trainer = None
        self.predictor = None
    
    def run_pipeline(self, cricket_df: pd.DataFrame) -> Tuple[pd.DataFrame, str]:
        """Execute complete pipeline: preprocess → train → predict → report."""
        # Preprocessing
        self.preprocessor = CricketFormPreprocessor(form_threshold=50.0)
        cricket_df = self.preprocessor.engineer_features(cricket_df)
        cricket_df, y = self.preprocessor.create_labels(cricket_df)
        X = self.preprocessor.prepare_features(cricket_df)
        
        # Training
        self.trainer = CricketFormTrainer(test_size=0.3)
        self.trainer.split_data(X, y)
        self.trainer.train()
        self.trainer.evaluate()
        
        # Prediction and reporting
        self.predictor = CricketFormPredictor(
            self.trainer.model,
            self.preprocessor,
            self.preprocessor.scaler
        )
        
        predictions = self.predictor.predict_batch(cricket_df)
        report = self.predictor.generate_report(predictions)
        
        return predictions, report

# EXECUTE STEP 3
if __name__ == '__main__':
    from data_loader import CricketDataLoader
    
    print("\n=== STEP 3: Integration & Enhancement ===")
    
    # Load and execute pipeline
    loader = CricketDataLoader()
    cricket_df = loader.load_sample_data()
    loader.validate_data()
    
    pipeline = CricketAnalyticsPipeline()
    predictions, report = pipeline.run_pipeline(cricket_df)
    
    print(report)
    
    # Display feature importance
    print("\n--- FEATURE IMPORTANCE ---")
    importance = pipeline.predictor.get_feature_importance()
    print(importance[['feature', 'coefficient']].to_string(index=False))
    
    # Save predictions to CSV
    predictions.to_csv('cricket_form_predictions.csv', index=False)
    print("\n✓ Predictions saved to 'cricket_form_predictions.csv'")

Step 4 — Testing & Verification

The testing step verifies that the complete system works end-to-end with real cricket data. You will create a test script that executes the full pipeline, validates output formats, checks prediction consistency, and compares results against known baselines. Verification includes confirming that confidence scores fall between 0% and 100%, that all players receive predictions, and that the report generates without errors.

Analogy🏏Cricket
🏏 Think of it like cricket: before naming the final side, a team plays a full trial match to be sure the whole plan holds up — the batting order runs between the wickets, the bowling changes fire, and the scorers confirm the totals add up correctly from start to finish. That trial is your testing and verification step. Just as a trial match checks every part works together under real conditions in cricket, you run the complete pipeline on sample cricket data and verify the outputs against expected results. And just as a captain deliberately tests how the side copes when a wicket falls early or the pitch misbehaves, you feed invalid inputs to confirm the error handling responds gracefully rather than collapsing. Checking that the system correctly identifies player trends and rankings is like confirming the scoreboard truly reflects the play. The payoff is a pipeline proven reliable end to end, trusted to hold together before it faces the real match.

This step is critical for catching integration bugs early — for example, cases where feature normalization is applied inconsistently or a player's statistics are lost during transformation. Testing with diverse inputs across different player roles, countries, and performance levels ensures system robustness.

Production ML systems require rigorous testing because even small bugs in preprocessing can silently corrupt predictions downstream.

bash
#!/bin/bash
# Test and verify the Cricket Analytics ML System

echo "========================================"
echo "Cricket Analytics ML System - Test Suite"
echo "========================================"

# Activate virtual environment
echo "\n[1/5] Activating virtual environment..."
source venv/bin/activate

# Verify dependencies
echo "\n[2/5] Verifying dependencies..."
python3 << 'EOF'
import sys
try:
    import pandas as pd
    import numpy as np
    from sklearn.preprocessing import StandardScaler
    from sklearn.linear_model import LogisticRegression
    print("✓ All dependencies available")
except ImportError as e:
    print(f"✗ Missing dependency: {e}")
    sys.exit(1)
EOF

# Run main pipeline
echo "\n[3/5] Running main analytics pipeline..."
python3 << 'EOF'
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from dataclasses import dataclass

@dataclass
class CricketPlayer:
    name: str
    country: str
    role: str
    matches_played: int
    runs_scored: int
    wickets_taken: int
    centuries: int
    strike_rate: float
    economy_rate: float
    average: float
    
    def to_dict(self):
        return {
            'name': self.name,
            'country': self.country,
            'role': self.role,
            'matches_played': self.matches_played,
            'runs_scored': self.runs_scored,
            'wickets_taken': self.wickets_taken,
            'centuries': self.centuries,
            'strike_rate': self.strike_rate,
            'economy_rate': self.economy_rate,
            'average': self.average
        }

print("Creating test dataset...")
players = [
    CricketPlayer('Rohit Sharma', 'India', 'Batsman', 150, 8000, 0, 31, 92.5, 0, 50.2),
    CricketPlayer('Virat Kohli', 'India', 'Batsman', 175, 12000, 0, 46, 89.3, 0, 59.1),
    CricketPlayer('Jasprit Bumrah', 'India', 'Bowler', 92, 100, 185, 0, 120.0, 3.85, 0),
    CricketPlayer('Pat Cummins', 'Australia', 'Bowler', 68, 450, 156, 0, 115.5, 4.12, 0),
    CricketPlayer('Steve Smith', 'Australia', 'Batsman', 160, 9500, 5, 30, 86.7, 0, 54.3),
    CricketPlayer('Kane Williamson', 'New Zealand', 'Batsman', 155, 10200, 8, 35, 84.2, 0, 57.8),
    CricketPlayer('Trent Boult', 'New Zealand', 'Bowler', 98, 200, 168, 0, 125.3, 3.92, 0),
    CricketPlayer('Ben Stokes', 'England', 'All-rounder', 142, 7500, 108, 20, 88.4, 4.05, 40.5),
    CricketPlayer('Babar Azam', 'Pakistan', 'Batsman', 120, 6800, 0, 19, 87.1, 0, 52.3),
    CricketPlayer('Shaheen Afridi', 'Pakistan', 'Bowler', 65, 150, 142, 0, 118.2, 3.68, 0)
]

data = [player.to_dict() for player in players]
df = pd.DataFrame(data)

print(f"✓ Loaded {len(df)} cricket players")
print(f"  Columns: {len(df.columns)}")
print(f"  Data shape: {df.shape}")

# Feature engineering
df['runs_per_match'] = df['runs_scored'] / df['matches_played']
df['wickets_per_match'] = df['wickets_taken'] / df['matches_played']
df['century_rate'] = df['centuries'] / df['matches_played']
df['consistency_score'] = df['average'] * (df['strike_rate'] / 100)

# Create labels
y = (df['average'] > 50.0).astype(int).values

print(f"\n✓ Features engineered")
print(f"  Label distribution: In Form={sum(y)}, Out of Form={len(y)-sum(y)}")

# Select features
feature_cols = ['matches_played', 'runs_scored', 'wickets_taken', 'centuries',
               'strike_rate', 'economy_rate', 'runs_per_match', 
               'wickets_per_match', 'century_rate', 'consistency_score']
X = df[feature_cols].values

# Normalize
scaler = StandardScaler()
X_normalized = scaler.fit_transform(X)

print(f"✓ Data normalized: {X_normalized.shape}")

# Train-test split
X_train, X_test, y_train, y_test = train_test_split(
    X_normalized, y, test_size=0.3, random_state=42, stratify=y
)

print(f"✓ Data split: train={len(X_train)}, test={len(X_test)}")

# Train model
model = LogisticRegression(max_iter=1000, random_state=42)
model.fit(X_train, y_train)
train_accuracy = model.score(X_train, y_train)
test_accuracy = model.score(X_test, y_test)

print(f"\n✓ Model trained successfully")
print(f"  Training accuracy: {train_accuracy:.4f}")
print(f"  Testing accuracy: {test_accuracy:.4f}")

# Make predictions on all data
predictions = model.predict(X_normalized)
confidences = model.predict_proba(X_normalized).max(axis=1)

print(f"\n✓ Predictions generated for {len(predictions)} players")
print(f"  Confidence range: [{confidences.min():.4f}, {confidences.max():.4f}]")

# Verify output format
results = []
for i, player in enumerate(df.itertuples()):
    results.append({
        'name': player.name,
        'predicted_form': 'In Form' if predictions[i] == 1 else 'Out of Form',
        'confidence': confidences[i]
    })

results_df = pd.DataFrame(results)
print(f"\n✓ Results DataFrame created: {results_df.shape}")
print("\n=== PREDICTIONS ===")
print(results_df.to_string(index=False))

EOF

echo "\n[4/5] Verifying output format..."
python3 << 'EOF'
import pandas as pd

# Simulate predictions output
test_data = {
    'name': ['Rohit Sharma', 'Virat Kohli', 'Jasprit Bumrah'],
    'predicted_form': ['In Form', 'In Form', 'Out of Form'],
    'confidence': [0.87, 0.92, 0.61]
}

results = pd.DataFrame(test_data)
print("✓ Output format verified")
print(f"  Columns: {list(results.columns)}")
print(f"  Row count: {len(results)}")
print(f"  Data types: {dict(results.dtypes)}")

EOF

echo "\n[5/5] System ready for use!"
echo ""
echo "========================================"
echo "All tests PASSED ✓"
echo "========================================"
echo ""
echo "Next steps:"
echo "  python scripts/main.py  # Run full pipeline"
echo "  python scripts/predictor.py  # Make predictions on new data"

Warning: Feature Normalization Order Matters — A common mistake is fitting the StandardScaler on the combined training + test data before splitting. This causes data leakage: the scaler learns statistics (mean, std) from test data, making test accuracy artificially high. The correct approach: split data first, fit scaler ONLY on training data, then transform both train and test using the fitted scaler. Always fit transformers on training data only, never on the full dataset. If you later deploy the model with new player data, use the saved scaler fitted during training, not a new scaler fitted on the new data.

Extension Challenge: Extend the system to handle time-series form tracking — instead of a single 'In Form' label, create rolling windows of player statistics. For example, track form across the last 5 matches, 10 matches, and 20 matches separately. Train separate classifiers for each time window (short-term form vs. long-term form) and combine their predictions. Add confidence weighting: recent matches should count more heavily than distant ones. This mirrors how cricket analysts mentally weight recent form more than career averages. Finally, visualize form trends over time using matplotlib line plots showing 'form trajectory' (improving, declining, stable) for each player.

  • Data foundation requires clean, validated datasets with proper structure and no missing values; quality input ensures reliable predictions.
  • Feature engineering amplifies model performance by creating domain-relevant derived features (runs_per_match, consistency_score) capturing player form context.
  • StandardScaler normalization prevents high-magnitude features from dominating; all features should have similar scales before ML algorithms process them.
  • Train-test split with stratification ensures class distribution is preserved and prevents overfitting by testing on unseen data.
  • Logistic Regression learns decision boundaries separating classes; coefficients reveal feature importance, making predictions interpretable for stakeholders.
  • End-to-end pipeline integration transforms isolated components into production-ready systems; comprehensive testing catches subtle preprocessing bugs before deployment.
Lesson 18 of 35
0% complete