Build an IPL Auction Squad Predictor

Analyze IPL player stats and predict complete auction squads with prices.

Introduction

30 Second Summary

Every IPL mega auction, millions of fans play armchair analyst. They argue over which players their team should buy, debate whether a franchise overpaid, and predict squad compositions weeks before the gavel drops. What if you could build a system that does this analysis with real data instead of gut feeling?

In this project, you will build a Python notebook that analyzes real IPL player performance data, identifies what each team needs, and predicts complete auction squads with estimated prices for all 10 franchises.

What You'll Build

A Python notebook in Google Colab that acts as an IPL franchise analyst's toolkit, turning raw player stats into actionable auction strategy.

By the end of this project, you'll have:

  • A performance scoring system that rates every IPL player by role using a custom Value Score formula.
  • A team needs analysis showing exactly which roles each of the 10 IPL teams needs to fill based on their retained squad.
  • A full predicted auction squad for all 10 teams with estimated prices, respecting real budget and overseas player constraints.
  • Secret Mission: Build a live auction simulator where teams bid against each other round by round with dynamic price inflation.

Are there any prerequisites?

None. You only need a Google account to access Google Colab (free, runs in your browser). This guide walks you through setting up everything else, including a free Kaggle account for downloading the dataset.

Set Up Google Colab and Kaggle

Building a data analysis project starts with getting the data. The IPL Player Performance Dataset on Kaggle contains match-by-match stats for every player from 2016 to 2025.

Google Colab is a free, browser-based Python notebook. It comes with pandas and matplotlib pre-installed, so you won't need to set up Python on your own machine.

In this step, get ready to:
  • Create a free Kaggle account and download your API token.
  • Set up a Google Colab notebook and configure Kaggle credentials.
  • Download the IPL player performance dataset and verify it loads correctly.
Create a Kaggle account and generate your API token

Kaggle requires an API token so tools like kagglehub can download datasets on your behalf. You'll generate this token from your Kaggle account settings.

  • Open your browser and go to kaggle.com.
  • Click Register in the top right corner.
  • Complete the sign-up using your Google account or email.
  • Once logged in, click your profile icon in the top right corner.
  • Select Settings from the dropdown menu.
  • Scroll down to the API section.
  • Click Create New Token.

A file called kaggle.json downloads to your computer. Keep this file handy for the next substep.

What's inside kaggle.json?

This file contains two values: your Kaggle username and an API key. You'll copy these into Google Colab in the next substep so your notebook can download datasets directly from Kaggle.

Don't see the Create New Token button?

Make sure you're on the Settings page, not your profile page. The API section is towards the bottom of the page.

If you already have a token, you can click Create New Token again to generate a fresh one. Help me find the Kaggle API token page.

Create a Google Colab notebook and configure Kaggle secrets

Google Colab has a built-in Secrets manager that stores credentials securely. This is where you'll store your Kaggle username and key so the notebook can authenticate with Kaggle's servers.

  • Open your browser and go to colab.research.google.com.
  • Sign in with your Google account if prompted.
  • Click New Notebook to create a fresh notebook.
  • Click the notebook title at the top left (it says "Untitled0").
  • Rename it to IPL_Auction_Predictor.

Now you'll add your Kaggle credentials as secrets.

  • In the left sidebar, click the key icon to open Secrets.
  • Click Add new secret.
  • In the Name field, enter KAGGLE_USERNAME.
  • Open kaggle.json in any text editor (TextEdit on macOS, Notepad on Windows).
  • Copy the value next to "username" and paste it into the Value field in Colab.
  • Toggle the Notebook access switch ON for this secret.
  • Click Add new secret again.
  • In the Name field, enter KAGGLE_KEY.
  • Copy the value next to "key" from kaggle.json and paste it into the Value field.
  • Toggle the Notebook access switch ON for this secret too.

Why use Colab Secrets instead of pasting credentials in code?

Secrets keep your credentials out of your notebook code. If you share the notebook, your Kaggle key stays private. Libraries like kagglehub automatically read these secrets when authenticating with Kaggle.

Can't find the Secrets panel?

The key icon is in the left sidebar, below the folder icon (Files) and the search icon. If the sidebar is collapsed, click the arrow at the top left to expand it.

Help me find the Secrets panel in Google Colab.

Download the dataset and verify it loads

With credentials configured, you can now install kagglehub and pull the IPL dataset directly into your Colab environment.

  • Install the kagglehub library by typing this into the first code cell and pressing Shift+Enter to run it:
!pip install kagglehub

You should see installation output ending with "Successfully installed kagglehub" (or "Requirement already satisfied" if it was pre-installed).

  • Click + Code in the toolbar to add a new code cell.
  • Download the IPL dataset by pasting this code into the new cell and pressing Shift+Enter:
# --- Cell 1: Setup and Install ---
import os
import kagglehub

# Download IPL dataset
dataset_path = kagglehub.dataset_download("sunnyyadav754/ipl-player-performance-dataset-20162025")
print("Dataset downloaded to:", dataset_path)

What does this code do?

kagglehub.dataset_download() connects to Kaggle using the secrets you stored, downloads the dataset files, and saves them to a local folder.

The dataset_path variable stores the folder location so you can load the CSV files in the next cell.

You should see output like "Dataset downloaded to: /root/.cache/kagglehub/datasets/..." confirming the download succeeded.

Seeing an authentication error?

This means your Kaggle secrets are not configured correctly. Check that both KAGGLE_USERNAME and KAGGLE_KEY are spelled exactly right in the Secrets panel.

Also confirm the Notebook access toggle is switched ON for both secrets. Help me fix my Kaggle authentication in Colab.

  • Click + Code to add another new code cell.
  • Load the dataset into a pandas DataFrame by pasting this code and pressing Shift+Enter:
# --- Cell 2: Load and Explore Data ---
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# List files in the downloaded dataset
for file in os.listdir(dataset_path):
    print(file)

# Load the CSV file (adjust filename based on what you see above)
df = pd.read_csv(os.path.join(dataset_path, os.listdir(dataset_path)[0]))
print("Shape:", df.shape)
print("Columns:", df.columns.tolist())
df.head()

What does this code do?

  • os.listdir(dataset_path) prints the filenames in the downloaded folder so you can see what's available.
  • pd.read_csv() loads the first CSV file into a DataFrame called df.
  • df.shape tells you how many rows (players) and columns (stats) the dataset has.
  • df.head() displays the first 5 rows as a preview table.

You should see the list of CSV files printed first, then the shape (something like (500, 15)), a list of column names, and a preview table showing player stats.

What if my column names look different from the code later?

The column names printed by df.columns are your source of truth. The code in later steps assumes standard names like player, team, season, runs, strike_rate, and wickets.

If your dataset uses different names (like batsman_runs instead of runs), you'll need to adjust the column references in later steps to match. Take note of the exact column names now.

Seeing a FileNotFoundError?

This means the download in Cell 1 did not complete successfully. Go back to Cell 1 and re-run it. Make sure you see the "Dataset downloaded to:" message before running Cell 2.

Help me debug my dataset loading error in Colab.

✔️ Awesome, I've got everything!

Great. Make sure you've saved your notebook (Colab auto-saves, but you can press Cmd+S or Ctrl+S to be safe).

ⓧ I'd like to double check the full code

Here is all the code you should have across your notebook cells so far. The !pip install kagglehub command runs in its own cell before these two.

# --- Cell 1: Setup and Install ---
import os
import kagglehub

# Download IPL dataset
dataset_path = kagglehub.dataset_download("sunnyyadav754/ipl-player-performance-dataset-20162025")
print("Dataset downloaded to:", dataset_path)

# --- Cell 2: Load and Explore Data ---
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# List files in the downloaded dataset
for file in os.listdir(dataset_path):
    print(file)

# Load the CSV file (adjust filename based on what you see above)
df = pd.read_csv(os.path.join(dataset_path, os.listdir(dataset_path)[0]))
print("Shape:", df.shape)
print("Columns:", df.columns.tolist())
df.head()

Your data pipeline is connected and your IPL dataset is loaded. Next up, you'll build a scoring formula that rates every batter and bowler based on their 2025 season performance.

Build the Player Performance Report

Your dataset is loaded and ready to go. Right now it contains thousands of rows of raw cricket statistics across multiple seasons. An IPL franchise analyst needs a way to distill all of that into a single question: who performed best this season?

In this step, you'll filter the data down to the 2025 season and create two scoring formulas. One rates batters, the other rates bowlers. Each formula combines multiple stats into a single "Value Score" on a 0-100 scale, making it easy to compare players across different strengths.

In this step, get ready to:
  • Filter the dataset to the 2025 season for current form data.
  • Create a Batter Value Score combining runs, strike rate, average, and consistency.
  • Create a Bowler Value Score combining wickets, economy, average, and strike rate.
Filter for the 2025 season

Your dataset spans multiple IPL seasons from 2016 to 2025. For auction predictions, only current form matters. A player who dominated in 2020 but barely played in 2025 is not a useful auction target.

  • Create a new code cell in your notebook by clicking the + Code button at the top.
  • Filter the dataset to only 2025 data by pasting this code:
# --- Cell 3: Filter for Latest Season ---
# Explore available seasons
print("Seasons available:", df['season'].unique())

# Filter for 2025 season
df_2025 = df[df['season'] == 2025].copy()
print(f"Players in 2025 season: {len(df_2025)}")
df_2025.head()

What does this code do?

  • df['season'].unique() prints all available seasons so you can confirm 2025 exists in the data.
  • df[df['season'] == 2025] filters the DataFrame to only rows where the season column equals 2025.
  • .copy() creates an independent copy so changes to df_2025 don't accidentally modify the original df.
  • Run the cell by pressing Shift+Enter.
  • You should see the available seasons printed, the player count for 2025, and the first 5 rows of your filtered data.

Not seeing 2025 in the seasons list?

If the seasons printed don't include 2025, the dataset may have been updated since this project was written. Use the most recent season shown in the output instead by changing 2025 to that year.

If you see a KeyError mentioning 'season', your dataset uses a different column name. Run df.columns.tolist() to check the exact column names and adjust accordingly.

Help me figure out which column name to use for season filtering.

Column names may vary

This project assumes columns named player, team, season, runs, strike_rate, batting_avg, innings, wickets, economy, bowling_avg, and bowling_sr. If your dataset uses different names, check the output from your Cell 2 exploration and substitute accordingly in the code below.

Create the Batter Value Score

IPL teams don't evaluate batters on a single stat. A player who scores 400 runs at a strike rate of 110 is less valuable than someone with 350 runs at a strike rate of 165. You need a formula that weighs multiple factors and produces one comparable number.

  • Create a new code cell by clicking + Code.
  • Build the Batter Value Score function by pasting this code:
# --- Cell 4: Batter Value Score ---
def calculate_batter_score(row):
    """Calculate a composite batting value score (0-100 scale)"""
    runs_score = min(row['runs'] / 600, 1.0) * 30  # Max 30 points for runs
    sr_score = min(row['strike_rate'] / 180, 1.0) * 25  # Max 25 for strike rate
    avg_score = min(row['batting_avg'] / 50, 1.0) * 25  # Max 25 for average
    consistency = min(row['innings'] / 14, 1.0) * 20  # Max 20 for playing regularly
    return round(runs_score + sr_score + avg_score + consistency, 2)

df_2025['batter_score'] = df_2025.apply(calculate_batter_score, axis=1)

# Top 20 batters
top_batters = df_2025.nlargest(20, 'batter_score')[['player', 'team', 'runs', 'strike_rate', 'batting_avg', 'innings', 'batter_score']]
print("=== TOP 20 BATTERS BY VALUE SCORE ===")
print(top_batters.to_string(index=False))

How does the scoring formula work?

The function scores each batter across 4 dimensions, each capped so no single stat can dominate:

  • Total runs (max 30 pts): 600 runs in a season earns full marks. This rewards volume.
  • Strike rate (max 25 pts): 180 SR earns full marks. This rewards explosive scoring.
  • Batting average (max 25 pts): 50 average earns full marks. This rewards consistency.
  • Innings played (max 20 pts): 14 innings earns full marks. This penalizes players who barely featured.

The min(..., 1.0) pattern caps each metric at 1.0 so a batter with 700 runs doesn't unfairly outscore someone with a better all-round profile.

  • Run the cell by pressing Shift+Enter.
  • You should see "=== TOP 20 BATTERS BY VALUE SCORE ===" followed by a table of 20 players with their runs, strike rate, batting average, innings, and computed batter score.

Seeing a KeyError?

A KeyError: 'runs' (or similar) means the column name in your dataset doesn't match what the code expects. Go back to your Cell 2 output where you printed df.columns.tolist() and find the correct column name for that stat.

Help me match my dataset's column names to the scoring formula.

Create the Bowler Value Score

Bowling metrics work differently from batting. For bowlers, lower numbers are better. A low economy rate and low bowling average mean the bowler is harder to score against. Your formula needs to reward low values instead of high ones.

  • Create a new code cell by clicking + Code.
  • Build the Bowler Value Score function by pasting this code:
# --- Cell 5: Bowler Value Score ---
def calculate_bowler_score(row):
    """Calculate a composite bowling value score (0-100 scale)"""
    wickets_score = min(row['wickets'] / 25, 1.0) * 35  # Max 35 for wickets
    econ_score = max(0, (10 - row['economy']) / 4) * 25  # Max 25 for low economy
    avg_score = max(0, (30 - row['bowling_avg']) / 20) * 20  # Max 20 for low average
    sr_score = max(0, (24 - row['bowling_sr']) / 12) * 20  # Max 20 for low strike rate
    return round(max(0, wickets_score + econ_score + avg_score + sr_score), 2)

df_2025['bowler_score'] = df_2025.apply(calculate_bowler_score, axis=1)

# Top 20 bowlers
top_bowlers = df_2025[df_2025['wickets'] > 0].nlargest(20, 'bowler_score')[['player', 'team', 'wickets', 'economy', 'bowling_avg', 'bowling_sr', 'bowler_score']]
print("=== TOP 20 BOWLERS BY VALUE SCORE ===")
print(top_bowlers.to_string(index=False))

How does the bowler scoring differ?

The key difference is the inversion pattern. For economy, the formula uses (10 - economy) so that a bowler with economy 6.0 scores higher than one with economy 8.5.

  • Wickets (max 35 pts): The highest weight because taking wickets wins matches.
  • Economy rate (max 25 pts): Lower is better. Rewards bowlers who restrict run flow.
  • Bowling average (max 20 pts): Lower is better. Fewer runs conceded per wicket.
  • Bowling strike rate (max 20 pts): Lower is better. Fewer balls needed per wicket.

The filter df_2025['wickets'] > 0 excludes pure batters from the bowler rankings so you only see players who actually bowled.

  • Run the cell by pressing Shift+Enter.
  • You should see "=== TOP 20 BOWLERS BY VALUE SCORE ===" followed by a table of 20 bowlers with their wickets, economy, bowling average, bowling strike rate, and computed bowler score.

Seeing all zeros in the bowler scores?

If every bowler_score is 0.0, the bowling columns might contain null values or zeros for players who didn't bowl. The wickets > 0 filter should handle this, but check that your bowling columns actually contain numeric data by running df_2025[['wickets', 'economy', 'bowling_avg', 'bowling_sr']].describe().

Help me debug why my bowler scores are all showing zero.

Why simple formulas over machine learning?

A machine learning model could predict player value, but you wouldn't be able to explain why a player ranks highly. With a weighted formula, you know exactly which stats drive the score. That transparency is what real analysts need when presenting recommendations to team management.

✔️ Awesome, I've got everything!

Great. Double check you've saved your notebook (Colab auto-saves, but you can also press Ctrl+S or Cmd+S).

ⓧ I'd like to double check the full code

Here is the complete code across all cells up to this point. Compare each cell in your notebook against this:

# --- Cell 1: Setup and Install ---
import os
import kagglehub

# Download IPL dataset
dataset_path = kagglehub.dataset_download("sunnyyadav754/ipl-player-performance-dataset-20162025")
print("Dataset downloaded to:", dataset_path)

# --- Cell 2: Load and Explore Data ---
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# List files in the downloaded dataset
for file in os.listdir(dataset_path):
    print(file)

# Load the CSV file (adjust filename based on what you see above)
df = pd.read_csv(os.path.join(dataset_path, os.listdir(dataset_path)[0]))
print("Shape:", df.shape)
print("Columns:", df.columns.tolist())
df.head()

# --- Cell 3: Filter for Latest Season ---
# Explore available seasons
print("Seasons available:", df['season'].unique())

# Filter for 2025 season
df_2025 = df[df['season'] == 2025].copy()
print(f"Players in 2025 season: {len(df_2025)}")
df_2025.head()

# --- Cell 4: Batter Value Score ---
def calculate_batter_score(row):
    """Calculate a composite batting value score (0-100 scale)"""
    runs_score = min(row['runs'] / 600, 1.0) * 30  # Max 30 points for runs
    sr_score = min(row['strike_rate'] / 180, 1.0) * 25  # Max 25 for strike rate
    avg_score = min(row['batting_avg'] / 50, 1.0) * 25  # Max 25 for average
    consistency = min(row['innings'] / 14, 1.0) * 20  # Max 20 for playing regularly
    return round(runs_score + sr_score + avg_score + consistency, 2)

df_2025['batter_score'] = df_2025.apply(calculate_batter_score, axis=1)

# Top 20 batters
top_batters = df_2025.nlargest(20, 'batter_score')[['player', 'team', 'runs', 'strike_rate', 'batting_avg', 'innings', 'batter_score']]
print("=== TOP 20 BATTERS BY VALUE SCORE ===")
print(top_batters.to_string(index=False))

# --- Cell 5: Bowler Value Score ---
def calculate_bowler_score(row):
    """Calculate a composite bowling value score (0-100 scale)"""
    wickets_score = min(row['wickets'] / 25, 1.0) * 35  # Max 35 for wickets
    econ_score = max(0, (10 - row['economy']) / 4) * 25  # Max 25 for low economy
    avg_score = max(0, (30 - row['bowling_avg']) / 20) * 20  # Max 20 for low average
    sr_score = max(0, (24 - row['bowling_sr']) / 12) * 20  # Max 20 for low strike rate
    return round(max(0, wickets_score + econ_score + avg_score + sr_score), 2)

df_2025['bowler_score'] = df_2025.apply(calculate_bowler_score, axis=1)

# Top 20 bowlers
top_bowlers = df_2025[df_2025['wickets'] > 0].nlargest(20, 'bowler_score')[['player', 'team', 'wickets', 'economy', 'bowling_avg', 'bowling_sr', 'bowler_score']]
print("=== TOP 20 BOWLERS BY VALUE SCORE ===")
print(top_bowlers.to_string(index=False))

You now have a performance report that ranks every batter and bowler in the 2025 season by a single comparable score. Next up, you'll classify players into specific roles like Opener, Finisher, and Pacer so you can match them to each team's needs.

Classify Players by Role and Rank Them

Your batter and bowler scores tell you how good each player performed last season. But an IPL franchise at the auction table doesn't just want "the best" players. They need specific roles filled.

A team missing a death bowler doesn't care about the world's best opener. In this step, you'll classify every player into a role category and tag them as Indian or Overseas. This gives the auction predictor the vocabulary it needs to match players to team needs.

In this step, get ready to:
  • Classify every player into a role (Opener, Finisher, Pacer, Spinner, All-Rounder, etc.) based on their stats.
  • Rank the top performers in each role category by their overall value score.
  • Tag each player as Indian or Overseas to prepare for the overseas slot constraint.
Write the role classification function

Each player's stats tell a story about what role they play. A batter with a strike rate above 160 is likely a finisher. A bowler with economy under 7.5 is probably a spinner. You'll use these thresholds to sort every player into a category.

  • Create a new code cell in your notebook and add the role classification function:
# --- Cell 6: Classify Players by Role ---
def assign_role(row):
    is_bowler = row['wickets'] >= 8
    is_batter = row['runs'] >= 150
    if is_bowler and is_batter:
        if row['wickets'] >= 12:
            return 'Bowling All-Rounder'
        else:
            return 'Batting All-Rounder'
    elif is_bowler:
        if row['economy'] <= 7.5:
            return 'Spinner'
        else:
            return 'Pacer'
    elif is_batter:
        if row['strike_rate'] >= 160:
            return 'Finisher'
        elif row['batting_avg'] >= 35:
            return 'Middle-Order'
        else:
            return 'Opener'
    else:
        return 'Squad Player'

df_2025['role'] = df_2025.apply(assign_role, axis=1)
df_2025['overall_score'] = df_2025[['batter_score', 'bowler_score']].max(axis=1)

What does this classification logic do?

The function uses a decision tree based on thresholds:

  • Players with 8+ wickets AND 150+ runs are all-rounders. If they have 12+ wickets, they lean bowling. Otherwise, batting.
  • Pure bowlers (8+ wickets, under 150 runs) split into Spinners (economy 7.5 or below) and Pacers (above 7.5).
  • Pure batters (150+ runs, under 8 wickets) split into Finishers (SR 160+), Middle-Order (average 35+), or Openers.
  • Anyone below both thresholds becomes a Squad Player.

The overall_score column picks the higher of each player's batter and bowler score. This gives every player a single number for ranking regardless of their role.

Rank and display the top players per role

Now that every player has a role, you can see who dominates each category. This ranking becomes the auction's "shopping catalogue" for teams.

  • Add this code below the previous lines in the same cell:
print("=== PLAYER ROLE DISTRIBUTION ===")
print(df_2025['role'].value_counts())

for role in ['Opener', 'Middle-Order', 'Finisher', 'Pacer', 'Spinner', 'Batting All-Rounder', 'Bowling All-Rounder']:
    role_players = df_2025[df_2025['role'] == role].nlargest(5, 'overall_score')
    print(f"\n--- Top 5 {role}s ---")
    print(role_players[['player', 'team', 'overall_score']].to_string(index=False))

What does this output show?

  • The role distribution tells you how many players fall into each category. This reveals supply imbalances (e.g., more Pacers than Finishers means finishers will be in higher demand at auction).
  • The top 5 per role shows you who the marquee picks are. These are the players teams will fight over.
  • Run the cell by pressing the play button or hitting Shift+Enter.
  • You should see a role distribution count (showing how many players are in each category) followed by a "Top 5" table for each role.

Seeing all zeros or empty tables?

  • Make sure you ran the previous cells first. The df_2025 DataFrame with batter_score and bowler_score columns must already exist.
  • If you see a KeyError, your dataset may use different column names. Check df_2025.columns.tolist() and adjust the column references in assign_role to match.
  • Why are all my players showing as Squad Player?
Tag each player as Indian or Overseas

IPL squads can have a maximum of 8 overseas players. Your auction predictor needs to know each player's nationality to enforce this constraint later.

  • Create a new code cell below Cell 6 and add this code:
# --- Cell 7: Tag Indian vs Overseas ---
overseas_players = [
    'Travis Head', 'Pat Cummins', 'Heinrich Klaasen', 'Rashid Khan',
    'Nicholas Pooran', 'Shimron Hetmyer', 'Tristan Stubbs',
    'Matheesha Pathirana', 'Andre Russell', 'Sunil Narine',
    'Mitchell Starc', 'Trent Boult', 'David Miller', 'Phil Salt',
    'Jos Buttler', 'Liam Livingstone', 'Marcus Stoinis',
    'Glenn Maxwell', 'Kagiso Rabada', 'Anrich Nortje',
    'Lockie Ferguson', 'Josh Hazlewood', 'Sam Curran'
]

df_2025['is_overseas'] = df_2025['player'].isin(overseas_players)
print(f"Overseas players identified: {df_2025['is_overseas'].sum()}")
print(f"Indian players: {(~df_2025['is_overseas']).sum()}")

Why a manual list instead of a dataset column?

The dataset may not include a nationality column. A manual list of known overseas players is a reliable fallback. The isin() method checks each player's name against this list and returns True or False.

This list covers the most prominent overseas players in IPL 2025. Some lesser-known overseas players may not be tagged, but the major auction targets are all accounted for.

  • Run the cell.
  • You should see two lines printed showing the count of overseas players identified and the count of Indian players.

Seeing 0 overseas players identified?

  • The player names in the list must match the dataset exactly. Check capitalization and spelling by running df_2025['player'].head(20) to see how names are formatted.
  • If names in your dataset use a different format (e.g., "T Head" instead of "Travis Head"), you'll need to adjust the list to match.
  • Help me debug why my overseas player tagging isn't working.

✔️ Awesome, I've got everything!

Great. Double check you've saved your notebook before moving on.

ⓧ I'd like to double check the full code

Here is the complete notebook code up to this point:

# --- Cell 1: Setup and Install ---
import os
import kagglehub

# Download IPL dataset
dataset_path = kagglehub.dataset_download("sunnyyadav754/ipl-player-performance-dataset-20162025")
print("Dataset downloaded to:", dataset_path)

# --- Cell 2: Load and Explore Data ---
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

# List files in the downloaded dataset
for file in os.listdir(dataset_path):
    print(file)

# Load the CSV file (adjust filename based on what you see above)
df = pd.read_csv(os.path.join(dataset_path, os.listdir(dataset_path)[0]))
print("Shape:", df.shape)
print("Columns:", df.columns.tolist())
df.head()

# --- Cell 3: Filter for Latest Season ---
print("Seasons available:", df['season'].unique())
df_2025 = df[df['season'] == 2025].copy()
print(f"Players in 2025 season: {len(df_2025)}")
df_2025.head()

# --- Cell 4: Batter Value Score ---
def calculate_batter_score(row):
    runs_score = min(row['runs'] / 600, 1.0) * 30
    sr_score = min(row['strike_rate'] / 180, 1.0) * 25
    avg_score = min(row['batting_avg'] / 50, 1.0) * 25
    consistency = min(row['innings'] / 14, 1.0) * 20
    return round(runs_score + sr_score + avg_score + consistency, 2)

df_2025['batter_score'] = df_2025.apply(calculate_batter_score, axis=1)
top_batters = df_2025.nlargest(20, 'batter_score')[['player', 'team', 'runs', 'strike_rate', 'batting_avg', 'innings', 'batter_score']]
print("=== TOP 20 BATTERS BY VALUE SCORE ===")
print(top_batters.to_string(index=False))

# --- Cell 5: Bowler Value Score ---
def calculate_bowler_score(row):
    wickets_score = min(row['wickets'] / 25, 1.0) * 35
    econ_score = max(0, (10 - row['economy']) / 4) * 25
    avg_score = max(0, (30 - row['bowling_avg']) / 20) * 20
    sr_score = max(0, (24 - row['bowling_sr']) / 12) * 20
    return round(max(0, wickets_score + econ_score + avg_score + sr_score), 2)

df_2025['bowler_score'] = df_2025.apply(calculate_bowler_score, axis=1)
top_bowlers = df_2025[df_2025['wickets'] > 0].nlargest(20, 'bowler_score')[['player', 'team', 'wickets', 'economy', 'bowling_avg', 'bowling_sr', 'bowler_score']]
print("=== TOP 20 BOWLERS BY VALUE SCORE ===")
print(top_bowlers.to_string(index=False))

# --- Cell 6: Classify Players by Role ---
def assign_role(row):
    is_bowler = row['wickets'] >= 8
    is_batter = row['runs'] >= 150
    if is_bowler and is_batter:
        if row['wickets'] >= 12:
            return 'Bowling All-Rounder'
        else:
            return 'Batting All-Rounder'
    elif is_bowler:
        if row['economy'] <= 7.5:
            return 'Spinner'
        else:
            return 'Pacer'
    elif is_batter:
        if row['strike_rate'] >= 160:
            return 'Finisher'
        elif row['batting_avg'] >= 35:
            return 'Middle-Order'
        else:
            return 'Opener'
    else:
        return 'Squad Player'

df_2025['role'] = df_2025.apply(assign_role, axis=1)
df_2025['overall_score'] = df_2025[['batter_score', 'bowler_score']].max(axis=1)

print("=== PLAYER ROLE DISTRIBUTION ===")
print(df_2025['role'].value_counts())

for role in ['Opener', 'Middle-Order', 'Finisher', 'Pacer', 'Spinner', 'Batting All-Rounder', 'Bowling All-Rounder']:
    role_players = df_2025[df_2025['role'] == role].nlargest(5, 'overall_score')
    print(f"\n--- Top 5 {role}s ---")
    print(role_players[['player', 'team', 'overall_score']].to_string(index=False))

# --- Cell 7: Tag Indian vs Overseas ---
overseas_players = [
    'Travis Head', 'Pat Cummins', 'Heinrich Klaasen', 'Rashid Khan',
    'Nicholas Pooran', 'Shimron Hetmyer', 'Tristan Stubbs',
    'Matheesha Pathirana', 'Andre Russell', 'Sunil Narine',
    'Mitchell Starc', 'Trent Boult', 'David Miller', 'Phil Salt',
    'Jos Buttler', 'Liam Livingstone', 'Marcus Stoinis',
    'Glenn Maxwell', 'Kagiso Rabada', 'Anrich Nortje',
    'Lockie Ferguson', 'Josh Hazlewood', 'Sam Curran'
]

df_2025['is_overseas'] = df_2025['player'].isin(overseas_players)
print(f"Overseas players identified: {df_2025['is_overseas'].sum()}")
print(f"Indian players: {(~df_2025['is_overseas']).sum()}")

Every player now has a role label and a nationality tag. Next up, you'll define each team's retained squad and figure out exactly which roles they still need to fill at the auction.

Analyze Team Needs Based on Retained Squads

You now have every player ranked by role with a performance score. The next question a franchise analyst asks is: which roles does my team actually need to fill?

In this step, you will define each team's retained squad, create a blueprint for a balanced team, and produce a shopping list of exactly which roles each franchise needs to buy at auction.

In this step, get ready to:
  • Define the retained players, budgets, and overseas counts for all 10 IPL teams.
  • Create an ideal squad template and map each team's retained players to roles.
  • Compare each team's roster against the template to produce a role-gap shopping list.
Define the retained players for each team with their roles

Before the IPL mega auction, each franchise retains a core group of players. These retentions eat into the team's INR 120 crore total purse, leaving a different remaining budget for each team.

  • Create a new code cell in your notebook and add the retained squads data by pasting this code:
# --- Cell 8: Define Retained Players and Team Budgets ---
retained_squads = {
    'CSK': {'players': ['Ruturaj Gaikwad', 'Ravindra Jadeja', 'Matheesha Pathirana', 'Shivam Dube', 'MS Dhoni'], 'budget_remaining': 55, 'overseas_retained': 1},
    'MI': {'players': ['Jasprit Bumrah', 'Hardik Pandya', 'Suryakumar Yadav', 'Rohit Sharma', 'Tilak Varma'], 'budget_remaining': 45, 'overseas_retained': 0},
    'RCB': {'players': ['Virat Kohli', 'Rajat Patidar', 'Yash Dayal'], 'budget_remaining': 83, 'overseas_retained': 0},
    'KKR': {'players': ['Rinku Singh', 'Andre Russell', 'Sunil Narine', 'Varun Chakravarthy', 'Harshit Rana', 'Ramandeep Singh'], 'budget_remaining': 51, 'overseas_retained': 2},
    'DC': {'players': ['Axar Patel', 'Kuldeep Yadav', 'Tristan Stubbs', 'Abishek Porel'], 'budget_remaining': 73, 'overseas_retained': 1},
    'GT': {'players': ['Shubman Gill', 'Rashid Khan', 'Sai Sudharsan', 'Shahrukh Khan', 'Rahul Tewatia'], 'budget_remaining': 69, 'overseas_retained': 1},
    'LSG': {'players': ['Nicholas Pooran', 'Ravi Bishnoi', 'Mayank Yadav', 'Ayush Badoni', 'Mohsin Khan'], 'budget_remaining': 69, 'overseas_retained': 1},
    'PBKS': {'players': ['Shashank Singh', 'Prabhsimran Singh'], 'budget_remaining': 110.5, 'overseas_retained': 0},
    'RR': {'players': ['Sanju Samson', 'Yashasvi Jaiswal', 'Riyan Parag', 'Shimron Hetmyer', 'Dhruv Jurel', 'Sandeep Sharma'], 'budget_remaining': 41, 'overseas_retained': 1},
    'SRH': {'players': ['Heinrich Klaasen', 'Pat Cummins', 'Abhishek Sharma', 'Travis Head', 'Nitish Kumar Reddy'], 'budget_remaining': 45, 'overseas_retained': 3},
}

print("=== TEAM BUDGETS ===")
for team, info in sorted(retained_squads.items(), key=lambda x: x[1]['budget_remaining'], reverse=True):
    print(f"{team}: INR {info['budget_remaining']} Cr | Retained: {len(info['players'])} | Overseas: {info['overseas_retained']}")

What does this code do?

The retained_squads dictionary stores three pieces of information for each of the 10 IPL teams.

  • players lists the names of players the team chose to retain before the auction.
  • budget_remaining is how much money (in crore) the team has left to spend at auction after paying retention costs.
  • overseas_retained tracks how many of those retained players are overseas (max 8 allowed in a squad).

The print loop sorts teams by budget (highest first) so you can see at a glance who has the most spending power.

  • Run the cell by pressing Shift+Enter.
  • You should see a sorted table showing PBKS at the top with INR 110.5 Cr (they retained only 2 players) and RR at the bottom with INR 41 Cr (they retained 6 players).

Why hardcode the retained squads?

The Kaggle performance dataset tracks how players performed during the season. It does not include front-office decisions like retentions and budgets. By hardcoding verified retention data from official IPL sources, your predictor works regardless of what columns the dataset happens to include.

Seeing a syntax error?

The dictionary uses nested curly braces. Check that every opening { has a matching closing }. Also check that each team entry ends with a comma except the last one.

help me fix a syntax error in my retained_squads dictionary

Define an ideal squad template and map current roles

To find what each team is missing, you need two things: a blueprint for what a balanced squad looks like, and a mapping of what roles the retained players already fill.

  • Create a new code cell below Cell 8 and add the squad template and role mappings by pasting this code:
# --- Cell 9: Team Needs Analysis ---
ideal_squad_template = {
    'Opener': 3, 'Middle-Order': 3, 'Finisher': 2, 'Pacer': 5,
    'Spinner': 3, 'Batting All-Rounder': 2, 'Bowling All-Rounder': 2,
}

retained_roles = {
    'CSK': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 1},
    'MI': {'Opener': 1, 'Middle-Order': 2, 'Finisher': 0, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'RCB': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 0, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'KKR': {'Opener': 0, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 2, 'Pacer': 1, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'DC': {'Opener': 0, 'Middle-Order': 1, 'Finisher': 0, 'Spinner': 1, 'Pacer': 0, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'GT': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 1, 'Pacer': 0, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'LSG': {'Opener': 0, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 1, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'PBKS': {'Opener': 1, 'Middle-Order': 0, 'Finisher': 1, 'Spinner': 0, 'Pacer': 0, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'RR': {'Opener': 2, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'SRH': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
}

What does this code do?

  • ideal_squad_template defines how many players of each role a balanced IPL squad needs. For example, 5 pacers covers both new-ball and death bowling across all matches.
  • retained_roles maps how many of each role the team's retained players already cover. For example, CSK retained 1 Opener (Ruturaj Gaikwad) and 1 Bowling All-Rounder (Ravindra Jadeja).

You will not see any output yet. The next chunk uses these two dictionaries to calculate the gaps.

Compare each team's retained players against the template

Now you can subtract what each team already has from what they need. The result is a shopping list of exactly which roles each franchise must buy at auction.

  • In the same Cell 9, add the comparison loop below the retained_roles dictionary by pasting this code:
print("=== TEAM NEEDS ANALYSIS (SHOPPING LIST) ===")
team_needs = {}
for team in retained_squads:
    needs = {}
    for role, required in ideal_squad_template.items():
        have = retained_roles[team].get(role, 0)
        gap = max(0, required - have)
        if gap > 0:
            needs[role] = gap
    team_needs[team] = needs
    print(f"\n{team} needs:")
    for role, count in needs.items():
        print(f"  - {count}x {role}")

What does this code do?

  • For each team, the loop walks through every role in the ideal_squad_template.
  • It subtracts how many the team already has (retained_roles) from how many are needed. If the gap is greater than zero, that role goes on the shopping list.
  • The team_needs dictionary stores only the roles with unfilled gaps. You will feed this directly into the auction predictor in the next step.
  • Run Cell 9 by pressing Shift+Enter.
  • You should see a shopping list for each team. For example, PBKS (who retained only 2 players) should have the longest list, needing 5x Pacers, 3x Spinners, 3x Middle-Order, and more.

Seeing a NameError for retained_squads?

This means Cell 8 has not been run yet. Go back and run Cell 8 first, then run Cell 9 again. In Colab, each cell depends on previous cells being executed.

help me fix a NameError in my team needs analysis cell

✔️ Awesome, I've got everything!

Great. Double check you have both Cell 8 and Cell 9 run successfully with output visible.

ⓧ I'd like to double check the full code

Here is the complete notebook code up to this point. Compare your cells against this reference.

# --- Cell 1: Setup and Install ---
import os
import kagglehub

dataset_path = kagglehub.dataset_download("sunnyyadav754/ipl-player-performance-dataset-20162025")
print("Dataset downloaded to:", dataset_path)

# --- Cell 2: Load and Explore Data ---
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

for file in os.listdir(dataset_path):
    print(file)

df = pd.read_csv(os.path.join(dataset_path, os.listdir(dataset_path)[0]))
print("Shape:", df.shape)
print("Columns:", df.columns.tolist())
df.head()

# --- Cell 3: Filter for Latest Season ---
print("Seasons available:", df['season'].unique())
df_2025 = df[df['season'] == 2025].copy()
print(f"Players in 2025 season: {len(df_2025)}")
df_2025.head()

# --- Cell 4: Batter Value Score ---
def calculate_batter_score(row):
    runs_score = min(row['runs'] / 600, 1.0) * 30
    sr_score = min(row['strike_rate'] / 180, 1.0) * 25
    avg_score = min(row['batting_avg'] / 50, 1.0) * 25
    consistency = min(row['innings'] / 14, 1.0) * 20
    return round(runs_score + sr_score + avg_score + consistency, 2)

df_2025['batter_score'] = df_2025.apply(calculate_batter_score, axis=1)
top_batters = df_2025.nlargest(20, 'batter_score')[['player', 'team', 'runs', 'strike_rate', 'batting_avg', 'innings', 'batter_score']]
print("=== TOP 20 BATTERS BY VALUE SCORE ===")
print(top_batters.to_string(index=False))

# --- Cell 5: Bowler Value Score ---
def calculate_bowler_score(row):
    wickets_score = min(row['wickets'] / 25, 1.0) * 35
    econ_score = max(0, (10 - row['economy']) / 4) * 25
    avg_score = max(0, (30 - row['bowling_avg']) / 20) * 20
    sr_score = max(0, (24 - row['bowling_sr']) / 12) * 20
    return round(max(0, wickets_score + econ_score + avg_score + sr_score), 2)

df_2025['bowler_score'] = df_2025.apply(calculate_bowler_score, axis=1)
top_bowlers = df_2025[df_2025['wickets'] > 0].nlargest(20, 'bowler_score')[['player', 'team', 'wickets', 'economy', 'bowling_avg', 'bowling_sr', 'bowler_score']]
print("=== TOP 20 BOWLERS BY VALUE SCORE ===")
print(top_bowlers.to_string(index=False))

# --- Cell 6: Classify Players by Role ---
def assign_role(row):
    is_bowler = row['wickets'] >= 8
    is_batter = row['runs'] >= 150
    if is_bowler and is_batter:
        if row['wickets'] >= 12:
            return 'Bowling All-Rounder'
        else:
            return 'Batting All-Rounder'
    elif is_bowler:
        if row['economy'] <= 7.5:
            return 'Spinner'
        else:
            return 'Pacer'
    elif is_batter:
        if row['strike_rate'] >= 160:
            return 'Finisher'
        elif row['batting_avg'] >= 35:
            return 'Middle-Order'
        else:
            return 'Opener'
    else:
        return 'Squad Player'

df_2025['role'] = df_2025.apply(assign_role, axis=1)
df_2025['overall_score'] = df_2025[['batter_score', 'bowler_score']].max(axis=1)

print("=== PLAYER ROLE DISTRIBUTION ===")
print(df_2025['role'].value_counts())

for role in ['Opener', 'Middle-Order', 'Finisher', 'Pacer', 'Spinner', 'Batting All-Rounder', 'Bowling All-Rounder']:
    role_players = df_2025[df_2025['role'] == role].nlargest(5, 'overall_score')
    print(f"\n--- Top 5 {role}s ---")
    print(role_players[['player', 'team', 'overall_score']].to_string(index=False))

# --- Cell 7: Tag Indian vs Overseas ---
overseas_players = [
    'Travis Head', 'Pat Cummins', 'Heinrich Klaasen', 'Rashid Khan',
    'Nicholas Pooran', 'Shimron Hetmyer', 'Tristan Stubbs',
    'Matheesha Pathirana', 'Andre Russell', 'Sunil Narine',
    'Mitchell Starc', 'Trent Boult', 'David Miller', 'Phil Salt',
    'Jos Buttler', 'Liam Livingstone', 'Marcus Stoinis',
    'Glenn Maxwell', 'Kagiso Rabada', 'Anrich Nortje',
    'Lockie Ferguson', 'Josh Hazlewood', 'Sam Curran'
]

df_2025['is_overseas'] = df_2025['player'].isin(overseas_players)
print(f"Overseas players identified: {df_2025['is_overseas'].sum()}")
print(f"Indian players: {(~df_2025['is_overseas']).sum()}")

# --- Cell 8: Define Retained Players and Team Budgets ---
retained_squads = {
    'CSK': {'players': ['Ruturaj Gaikwad', 'Ravindra Jadeja', 'Matheesha Pathirana', 'Shivam Dube', 'MS Dhoni'], 'budget_remaining': 55, 'overseas_retained': 1},
    'MI': {'players': ['Jasprit Bumrah', 'Hardik Pandya', 'Suryakumar Yadav', 'Rohit Sharma', 'Tilak Varma'], 'budget_remaining': 45, 'overseas_retained': 0},
    'RCB': {'players': ['Virat Kohli', 'Rajat Patidar', 'Yash Dayal'], 'budget_remaining': 83, 'overseas_retained': 0},
    'KKR': {'players': ['Rinku Singh', 'Andre Russell', 'Sunil Narine', 'Varun Chakravarthy', 'Harshit Rana', 'Ramandeep Singh'], 'budget_remaining': 51, 'overseas_retained': 2},
    'DC': {'players': ['Axar Patel', 'Kuldeep Yadav', 'Tristan Stubbs', 'Abishek Porel'], 'budget_remaining': 73, 'overseas_retained': 1},
    'GT': {'players': ['Shubman Gill', 'Rashid Khan', 'Sai Sudharsan', 'Shahrukh Khan', 'Rahul Tewatia'], 'budget_remaining': 69, 'overseas_retained': 1},
    'LSG': {'players': ['Nicholas Pooran', 'Ravi Bishnoi', 'Mayank Yadav', 'Ayush Badoni', 'Mohsin Khan'], 'budget_remaining': 69, 'overseas_retained': 1},
    'PBKS': {'players': ['Shashank Singh', 'Prabhsimran Singh'], 'budget_remaining': 110.5, 'overseas_retained': 0},
    'RR': {'players': ['Sanju Samson', 'Yashasvi Jaiswal', 'Riyan Parag', 'Shimron Hetmyer', 'Dhruv Jurel', 'Sandeep Sharma'], 'budget_remaining': 41, 'overseas_retained': 1},
    'SRH': {'players': ['Heinrich Klaasen', 'Pat Cummins', 'Abhishek Sharma', 'Travis Head', 'Nitish Kumar Reddy'], 'budget_remaining': 45, 'overseas_retained': 3},
}

print("=== TEAM BUDGETS ===")
for team, info in sorted(retained_squads.items(), key=lambda x: x[1]['budget_remaining'], reverse=True):
    print(f"{team}: INR {info['budget_remaining']} Cr | Retained: {len(info['players'])} | Overseas: {info['overseas_retained']}")

# --- Cell 9: Team Needs Analysis ---
ideal_squad_template = {
    'Opener': 3, 'Middle-Order': 3, 'Finisher': 2, 'Pacer': 5,
    'Spinner': 3, 'Batting All-Rounder': 2, 'Bowling All-Rounder': 2,
}

retained_roles = {
    'CSK': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 1},
    'MI': {'Opener': 1, 'Middle-Order': 2, 'Finisher': 0, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'RCB': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 0, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'KKR': {'Opener': 0, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 2, 'Pacer': 1, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'DC': {'Opener': 0, 'Middle-Order': 1, 'Finisher': 0, 'Spinner': 1, 'Pacer': 0, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'GT': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 1, 'Pacer': 0, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
    'LSG': {'Opener': 0, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 1, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'PBKS': {'Opener': 1, 'Middle-Order': 0, 'Finisher': 1, 'Spinner': 0, 'Pacer': 0, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'RR': {'Opener': 2, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 0, 'Bowling All-Rounder': 0},
    'SRH': {'Opener': 1, 'Middle-Order': 1, 'Finisher': 1, 'Spinner': 0, 'Pacer': 1, 'Batting All-Rounder': 1, 'Bowling All-Rounder': 0},
}

print("=== TEAM NEEDS ANALYSIS (SHOPPING LIST) ===")
team_needs = {}
for team in retained_squads:
    needs = {}
    for role, required in ideal_squad_template.items():
        have = retained_roles[team].get(role, 0)
        gap = max(0, required - have)
        if gap > 0:
            needs[role] = gap
    team_needs[team] = needs
    print(f"\n{team} needs:")
    for role, count in needs.items():
        print(f"  - {count}x {role}")

You now have a complete picture of supply (ranked players by role) and demand (each team's shopping list). Next up, you will estimate what each player should cost at auction based on their performance score and how many teams want their role.

Estimate Auction Base Prices

You now know exactly which roles each team needs to fill. The next question is: how much will they have to pay?

In a real IPL auction, player prices depend on three factors: their performance level, their role's scarcity, and how many teams are competing for them. In this step, you will build a pricing engine that models all three dynamics.

In this step, get ready to:
  • Create price tiers that map Value Scores to base auction prices in crore.
  • Apply role-demand multipliers and an overseas discount to refine prices.
  • Add a demand factor that inflates prices when multiple teams need the same role.
Create the price estimation function

Auction prices start with a base price determined by how good the player is. Then that base gets adjusted for role scarcity and nationality constraints.

  • Create a new code cell in your notebook (Cell 10) and add this function:
# --- Cell 10: Estimate Auction Prices ---
def estimate_base_price(score, role, is_overseas):
    # Score-based price tiers (in crore)
    if score >= 70:
        base = 16.0
    elif score >= 55:
        base = 10.0
    elif score >= 40:
        base = 6.0
    elif score >= 25:
        base = 3.0
    else:
        base = 1.0
    # Role demand multiplier - scarce roles cost more
    role_multipliers = {
        'Finisher': 1.3, 'Pacer': 1.2, 'Batting All-Rounder': 1.25,
        'Bowling All-Rounder': 1.15, 'Opener': 1.1, 'Middle-Order': 1.0,
        'Spinner': 1.0, 'Squad Player': 0.7,
    }
    base *= role_multipliers.get(role, 1.0)
    # Overseas players get a slight discount (slot constraint limits demand)
    if is_overseas:
        base *= 0.9
    return round(base, 2)

What does this function do?

The function uses three layers of pricing logic:

  • Score tiers set the base price. A player with a score of 70+ is worth 16 crore, while a score below 25 means a budget pick at 1 crore.
  • Role multipliers adjust for scarcity. Finishers (1.3x) and fast bowlers (1.2x) command premiums because fewer elite players fill those roles.
  • The overseas discount (0.9x) reflects the reality that teams can only field 4 overseas players in a match. This limits demand.

Why a discount for overseas players?

IPL rules cap overseas players at 8 in a squad and 4 in the playing XI. Even an elite overseas player competes for limited slots, so teams are slightly less aggressive bidding for them compared to an equally-skilled Indian player with no slot restriction.

Calculate the demand factor for bidding wars

A player's final price depends on how many teams are competing for them. If 7 teams all need a pacer, the bidding war drives prices up significantly.

  • Add this function directly below the previous one in the same cell:
def calculate_demand_factor(role, team_needs):
    """Count how many teams need this role and return a price multiplier"""
    demand = sum(1 for needs in team_needs.values() if role in needs and needs[role] > 0)
    if demand >= 7:
        return 1.5
    elif demand >= 5:
        return 1.3
    elif demand >= 3:
        return 1.15
    else:
        return 1.0

What does this function do?

This function counts how many of the 10 teams have the given role listed in their shopping list. The more teams that need a particular role, the higher the demand multiplier. A role needed by 7+ teams gets a 50% price bump, simulating the fierce bidding wars that happen in real auctions.

Apply pricing to available players

Now you need to identify which players are actually available for auction (not retained) and apply the pricing model to each one.

  • Add this code below the two functions in the same cell:
# Build list of all retained players (not available for auction)
all_retained = []
for team_info in retained_squads.values():
    all_retained.extend(team_info['players'])

# Filter to only available players
available_players = df_2025[~df_2025['player'].isin(all_retained)].copy()

# Apply pricing model
available_players['base_price'] = available_players.apply(
    lambda row: estimate_base_price(row['overall_score'], row['role'], row['is_overseas']), axis=1
)
available_players['demand_factor'] = available_players['role'].apply(
    lambda role: calculate_demand_factor(role, team_needs)
)
# Final predicted price = base price * demand factor
available_players['predicted_price'] = (available_players['base_price'] * available_players['demand_factor']).round(2)

print("=== PRICE PREDICTIONS (Top 20 Most Expensive) ===")
top_expensive = available_players.nlargest(20, 'predicted_price')[['player', 'role', 'overall_score', 'is_overseas', 'predicted_price']]
print(top_expensive.to_string(index=False))

What does this code do?

  • First it collects all retained player names and removes them from the pool. Retained players are off the market.
  • Then it applies the base price function to each available player using their score, role, and overseas status.
  • The demand factor multiplies the base price up based on how many teams need that player's role.
  • The final predicted_price column gives you the estimated auction price for every available player.
  • Run the cell by pressing the play button or hitting Shift+Enter.

You should see a table of the 20 most expensive predicted auction picks, showing their name, role, overall score, overseas status, and predicted price in crore.

Seeing a NameError for team_needs?

Make sure you ran Cell 9 (Team Needs Analysis) before this cell. The team_needs dictionary is created there and used by calculate_demand_factor in this cell.

Help me debug a NameError in my pricing cell

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook before moving on.

ⓧ I'd like to double check the full code

Here is the complete code for Cell 10. Make sure your cell matches this exactly:

# --- Cell 10: Estimate Auction Prices ---
def estimate_base_price(score, role, is_overseas):
    if score >= 70:
        base = 16.0
    elif score >= 55:
        base = 10.0
    elif score >= 40:
        base = 6.0
    elif score >= 25:
        base = 3.0
    else:
        base = 1.0
    role_multipliers = {
        'Finisher': 1.3, 'Pacer': 1.2, 'Batting All-Rounder': 1.25,
        'Bowling All-Rounder': 1.15, 'Opener': 1.1, 'Middle-Order': 1.0,
        'Spinner': 1.0, 'Squad Player': 0.7,
    }
    base *= role_multipliers.get(role, 1.0)
    if is_overseas:
        base *= 0.9
    return round(base, 2)

def calculate_demand_factor(role, team_needs):
    demand = sum(1 for needs in team_needs.values() if role in needs and needs[role] > 0)
    if demand >= 7:
        return 1.5
    elif demand >= 5:
        return 1.3
    elif demand >= 3:
        return 1.15
    else:
        return 1.0

all_retained = []
for team_info in retained_squads.values():
    all_retained.extend(team_info['players'])

available_players = df_2025[~df_2025['player'].isin(all_retained)].copy()
available_players['base_price'] = available_players.apply(
    lambda row: estimate_base_price(row['overall_score'], row['role'], row['is_overseas']), axis=1
)
available_players['demand_factor'] = available_players['role'].apply(
    lambda role: calculate_demand_factor(role, team_needs)
)
available_players['predicted_price'] = (available_players['base_price'] * available_players['demand_factor']).round(2)

print("=== PRICE PREDICTIONS (Top 20 Most Expensive) ===")
top_expensive = available_players.nlargest(20, 'predicted_price')[['player', 'role', 'overall_score', 'is_overseas', 'predicted_price']]
print(top_expensive.to_string(index=False))

Your pricing engine is ready. Next up, you will use these prices to predict complete auction squads for all 10 teams.

Predict Complete Squads for All 10 Teams

You have player scores, role classifications, team needs, and price estimates. Now it is time to put it all together into a complete squad prediction engine.

The challenge is that 10 teams are competing for the same pool of players. When one team buys a player, that player is gone for everyone else. Your predictor needs to simulate this by processing teams in order and removing bought players from the pool after each pick.

In this step, get ready to:
  • Build the squad prediction engine that fills gaps while respecting budget, overseas, and squad size constraints.
  • Run the predictor for all 10 teams and display predicted squads.
  • Calculate and display total spend vs. remaining purse for each team.
Build the squad prediction engine

A real IPL franchise has three hard constraints during the auction: a budget cap, a maximum of 8 overseas players in the squad, and a total squad size between 18-25 players. Your prediction function needs to respect all three while filling each team's role gaps in priority order.

  • Create a new code cell in your notebook (Cell 11) and add the following prediction function:
# --- Cell 11: Predict Complete Squads ---
def predict_squad(team, team_info, team_need, available, max_overseas=8, max_squad=20):
    budget = team_info['budget_remaining']
    overseas_count = team_info['overseas_retained']
    squad = []
    remaining_pool = available.copy()

    # Build a flat list of roles to fill, repeating roles that need multiple players
    priority_needs = []
    for role, count in team_need.items():
        for i in range(count):
            priority_needs.append(role)

    # Fill each role slot one at a time
    for role in priority_needs:
        # Stop if budget is too low or squad is full
        if budget <= 1 or len(squad) >= max_squad - len(team_info['players']):
            break

        # Filter candidates by role
        candidates = remaining_pool[remaining_pool['role'] == role].copy()

        # Enforce overseas limit
        if overseas_count >= max_overseas:
            candidates = candidates[~candidates['is_overseas']]

        # Only consider players within budget
        candidates = candidates[candidates['predicted_price'] <= budget]

        if len(candidates) == 0:
            continue

        # Pick the highest-scoring player the team can afford
        best = candidates.nlargest(1, 'overall_score').iloc[0]

        squad.append({'player': best['player'], 'role': role, 'price': best['predicted_price'], 'score': best['overall_score'], 'overseas': best['is_overseas']})
        budget -= best['predicted_price']
        if best['is_overseas']:
            overseas_count += 1

        # Remove bought player from the pool
        remaining_pool = remaining_pool[remaining_pool['player'] != best['player']]

    return squad, budget

What does this code do?

This function uses a greedy algorithm. It processes each role gap one at a time and picks the best available player that fits within the constraints.

  • priority_needs flattens the team's role needs into a list. If a team needs 3 pacers, "Pacer" appears 3 times.
  • For each slot, it filters candidates by role, overseas eligibility, and budget before selecting the highest-scoring option.
  • After each pick, the bought player is removed from the remaining pool so no other team can select them.

Why a greedy algorithm instead of full optimization?

A true optimal solution would use integer linear programming (libraries like PuLP or scipy). For this project, a greedy approach gives you the same educational insight while keeping the code readable.

You can see exactly why each player was picked. That transparency matters more than mathematical perfection when you are learning how constraint-based allocation works.

  • Run the cell by pressing Shift+Enter.

No output appears yet. The function is defined and ready to use in the next chunk.

Seeing a syntax error?

  • Check that the indentation is consistent. Every line inside the function must be indented with spaces, not a mix of tabs and spaces.
  • Make sure the dictionary inside squad.append() has all keys separated by commas with no trailing comma after the last key-value pair.
  • help me fix a syntax error in my predict_squad function
Run the predictor for all 10 teams

Teams with the smallest budgets pick first. This simulates a real auction where budget-constrained teams must secure key targets early before wealthier teams outbid them.

  • In the same cell, below the function definition, add the following execution code:
# Run predictions for all 10 teams
print("=" * 60)
print("=== PREDICTED AUCTION SQUADS FOR ALL 10 TEAMS ===")
print("=" * 60)

all_predictions = {}
available_pool = available_players.copy()

# Process teams from smallest budget to largest
sorted_teams = sorted(retained_squads.items(), key=lambda x: x[1]['budget_remaining'])

for team, info in sorted_teams:
    predicted_squad, remaining_budget = predict_squad(team, info, team_needs[team], available_pool)
    all_predictions[team] = predicted_squad

    # Remove bought players so other teams can't pick them
    bought_names = [p['player'] for p in predicted_squad]
    available_pool = available_pool[~available_pool['player'].isin(bought_names)]

    # Display the predicted squad
    print(f"\n{'='*50}")
    print(f"  {team} | Budget: {info['budget_remaining']} Cr")
    print(f"{'='*50}")
    print(f"  Retained: {', '.join(info['players'])}")
    print(f"\n  Predicted Buys:")
    total_spend = 0
    for p in predicted_squad:
        overseas_tag = " [OS]" if p['overseas'] else ""
        print(f"    {p['player']}{overseas_tag} - {p['role']} - INR {p['price']} Cr")
        total_spend += p['price']
    print(f"\n  Total Spend: INR {total_spend:.2f} Cr")
    print(f"  Remaining Purse: INR {info['budget_remaining'] - total_spend:.2f} Cr")

What does this code do?

  • sorted_teams orders teams by ascending budget so budget-constrained teams get first pick at available players.
  • available_pool shrinks after each team's picks. Once a player is assigned, no other team can select them.
  • all_predictions stores every team's predicted squad for later use in visualizations.
  • The total spend and remaining purse are calculated per team so you can see which franchises have money left for the later rounds.
  • Run the cell by pressing Shift+Enter.

You should see a full printout for all 10 teams. Each team shows its retained core, predicted buys with roles and prices, total spend, and remaining purse.

Seeing a NameError or empty predictions?

  • If you see NameError: name 'available_players' is not defined, go back and run Cell 10 (the pricing cell) first. Colab cells must run in order.
  • If every team shows 0 predicted buys, check that your team_needs dictionary is populated. Run Cell 9 again to regenerate it.
  • help me debug why my squad predictions are empty or throwing an error
Review the spend analysis

Scroll through the output and look at the Total Spend and Remaining Purse lines for each team. Teams with large budgets like PBKS should have significant remaining funds, while budget-tight teams like RR should be nearly spent out.

What patterns should I notice?

  • Teams processed first (smallest budgets) get first pick of the player pool, but can only afford cheaper options.
  • Teams processed last (biggest budgets) have more money but a depleted pool. The best players may already be taken.
  • Overseas-heavy teams like SRH (3 overseas retained) have fewer overseas slots available, so they rely more on Indian players in the auction.

✔️ Awesome, I've got everything!

Great. Double check you have saved your notebook before moving on.

ⓧ I'd like to double check the full code

Here is the complete Cell 11 code. Compare it against yours:

# --- Cell 11: Predict Complete Squads ---
def predict_squad(team, team_info, team_need, available, max_overseas=8, max_squad=20):
    budget = team_info['budget_remaining']
    overseas_count = team_info['overseas_retained']
    squad = []
    remaining_pool = available.copy()
    priority_needs = []
    for role, count in team_need.items():
        for i in range(count):
            priority_needs.append(role)
    for role in priority_needs:
        if budget <= 1 or len(squad) >= max_squad - len(team_info['players']):
            break
        candidates = remaining_pool[remaining_pool['role'] == role].copy()
        if overseas_count >= max_overseas:
            candidates = candidates[~candidates['is_overseas']]
        candidates = candidates[candidates['predicted_price'] <= budget]
        if len(candidates) == 0:
            continue
        best = candidates.nlargest(1, 'overall_score').iloc[0]
        squad.append({'player': best['player'], 'role': role, 'price': best['predicted_price'], 'score': best['overall_score'], 'overseas': best['is_overseas']})
        budget -= best['predicted_price']
        if best['is_overseas']:
            overseas_count += 1
        remaining_pool = remaining_pool[remaining_pool['player'] != best['player']]
    return squad, budget

print("=" * 60)
print("=== PREDICTED AUCTION SQUADS FOR ALL 10 TEAMS ===")
print("=" * 60)

all_predictions = {}
available_pool = available_players.copy()
sorted_teams = sorted(retained_squads.items(), key=lambda x: x[1]['budget_remaining'])

for team, info in sorted_teams:
    predicted_squad, remaining_budget = predict_squad(team, info, team_needs[team], available_pool)
    all_predictions[team] = predicted_squad
    bought_names = [p['player'] for p in predicted_squad]
    available_pool = available_pool[~available_pool['player'].isin(bought_names)]
    print(f"\n{'='*50}")
    print(f"  {team} | Budget: {info['budget_remaining']} Cr")
    print(f"{'='*50}")
    print(f"  Retained: {', '.join(info['players'])}")
    print(f"\n  Predicted Buys:")
    total_spend = 0
    for p in predicted_squad:
        overseas_tag = " [OS]" if p['overseas'] else ""
        print(f"    {p['player']}{overseas_tag} - {p['role']} - INR {p['price']} Cr")
        total_spend += p['price']
    print(f"\n  Total Spend: INR {total_spend:.2f} Cr")
    print(f"  Remaining Purse: INR {info['budget_remaining'] - total_spend:.2f} Cr")

You now have complete predicted auction squads for all 10 IPL franchises. Next up, you will visualize these results with charts that reveal spending patterns and value picks at a glance.

Visualize the Results

In the last step, you predicted complete auction squads for all 10 teams with estimated prices. The output was a wall of text that's hard to scan for patterns.

Charts can reveal things that tables hide. In this step, you'll use matplotlib to turn your predictions into three visual comparisons that show spending patterns, squad balance, and value picks at a glance.

In this step, get ready to:
  • Create a bar chart comparing predicted spend across all 10 teams.
  • Create a stacked bar chart showing squad composition by role.
  • Create a scatter plot identifying bargain picks and overpays.
Create the team spending bar chart

The first chart answers: which teams are predicted to spend the most at auction? A horizontal bar chart makes it easy to compare 10 teams at once.

  • Create a new code cell at the bottom of your notebook (click + Code in the toolbar).
  • Add the following code to set up a 3-chart figure and create the spending bar chart:
# --- Cell 12: Visualizations ---
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

# Chart 1: Total spend per team
teams = list(all_predictions.keys())
spends = [sum(p['price'] for p in all_predictions[t]) for t in teams]
axes[0].barh(teams, spends, color='#1f77b4')
axes[0].set_xlabel('Predicted Spend (INR Crore)')
axes[0].set_title('Total Predicted Auction Spend by Team')

What does this code do?

`plt.subplots(1, 3, figsize=(18, 6))` creates a single row of 3 empty chart areas, each accessible via `axes[0]`, `axes[1]`, and `axes[2]`.

The list comprehension calculates total spend for each team by summing all predicted player prices. Then `barh` draws a horizontal bar chart so the team names are readable on the y-axis.

  • Run the cell by pressing Shift+Enter.
  • You should see a horizontal bar chart showing predicted spend per team, with two empty subplot areas to the right.

Seeing a NameError?

If you see `NameError: name 'all_predictions' is not defined`, make sure you ran Cell 11 (the squad prediction cell) first. All previous cells must be executed in order.

help me fix a NameError in my matplotlib visualization cell

Create the squad composition stacked bar chart

A stacked bar chart shows how each team's predicted purchases break down by role. This reveals whether a team is investing heavily in pace, spin, or batting.

  • Add the following code to the same cell, directly below the Chart 1 code:
# Chart 2: Squad composition by role
roles_list = ['Opener', 'Middle-Order', 'Finisher', 'Pacer', 'Spinner', 'Batting All-Rounder', 'Bowling All-Rounder']
role_counts = {team: {role: 0 for role in roles_list} for team in teams}
for team in teams:
    for p in all_predictions[team]:
        if p['role'] in role_counts[team]:
            role_counts[team][p['role']] += 1

bottom = np.zeros(len(teams))
colors = ['#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f']
for i, role in enumerate(roles_list):
    values = [role_counts[t][role] for t in teams]
    axes[1].bar(teams, values, bottom=bottom, label=role, color=colors[i])
    bottom += np.array(values)
axes[1].set_title('Predicted Squad Composition by Role')
axes[1].legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8)
axes[1].tick_params(axis='x', rotation=45)

What does this code do?

  • `role_counts` tallies how many players of each role were predicted for each team.
  • The `bottom` array tracks where each segment starts, so bars stack vertically instead of overlapping.
  • Each role gets a unique color from the `colors` list, with a legend placed outside the chart area so it does not overlap the bars.
  • Run the cell again with Shift+Enter.
  • You should now see both the spending bar chart and a colorful stacked bar chart showing how many players of each role were assigned to each team.

Team names overlapping on the x-axis?

The `tick_params(axis='x', rotation=45)` line rotates the labels. If they still overlap, make sure you have `figsize=(18, 6)` in the `plt.subplots` call at the top of the cell.

help me fix overlapping labels on my matplotlib chart

Create the value vs price scatter plot

The final chart plots every predicted buy with their Value Score on one axis and predicted price on the other. Players below the "fair value" line are bargains. Players above it are overpays driven by demand.

  • Add the following code to the same cell, below the Chart 2 code:
# Chart 3: Value vs Price scatter
all_bought = []
for team in teams:
    for p in all_predictions[team]:
        all_bought.append(p)
scores = [p['score'] for p in all_bought]
prices = [p['price'] for p in all_bought]
axes[2].scatter(scores, prices, alpha=0.6, c=['red' if p['overseas'] else 'blue' for p in all_bought])
axes[2].set_xlabel('Player Value Score')
axes[2].set_ylabel('Predicted Price (INR Crore)')
axes[2].set_title('Value Score vs Predicted Price')
axes[2].axline((0, 0), slope=0.3, color='gray', linestyle='--', alpha=0.5, label='Fair value line')
axes[2].legend()

plt.tight_layout()
plt.savefig('ipl_auction_analysis.png', dpi=150, bbox_inches='tight')
plt.show()

print("\nAnalysis complete! Charts saved as 'ipl_auction_analysis.png'")

What does this code do?

  • `all_bought` flattens every predicted buy across all teams into a single list for plotting.
  • Red dots are overseas players. Blue dots are Indian players. This lets you spot whether overseas players tend to cluster in the "overpay" zone.
  • `axline((0, 0), slope=0.3)` draws a diagonal reference line. Players above it cost more than their score suggests. Players below it are value picks.
  • `plt.tight_layout()` prevents the charts from overlapping, and `savefig` exports the figure as a PNG image.
  • Run the cell with Shift+Enter.
  • You should see all three charts displayed side by side, plus the message `Analysis complete! Charts saved as 'ipl_auction_analysis.png'` printed below.

Charts look squished or overlapping?

Make sure `figsize=(18, 6)` is in your `plt.subplots` call. If the legend still overlaps Chart 2, confirm `bbox_to_anchor=(1.05, 1)` is set on the legend call.

help me fix my matplotlib chart layout

How to read the scatter plot

The dashed gray line represents "fair value" where price scales proportionally with performance. Dots above the line are players predicted to go for more than their stats alone justify. That premium comes from demand (multiple teams needing the same role).

Dots below the line are bargain picks. In a real auction, these are the players a smart franchise targets in later rounds.

✔️ Awesome, I've got everything!

Great. Double check you see all three charts rendering cleanly side by side.

ⓧ I'd like to double check the full code

Here is the complete Cell 12 code for comparison:

# --- Cell 12: Visualizations ---
fig, axes = plt.subplots(1, 3, figsize=(18, 6))

# Chart 1: Total spend per team
teams = list(all_predictions.keys())
spends = [sum(p['price'] for p in all_predictions[t]) for t in teams]
axes[0].barh(teams, spends, color='#1f77b4')
axes[0].set_xlabel('Predicted Spend (INR Crore)')
axes[0].set_title('Total Predicted Auction Spend by Team')

# Chart 2: Squad composition by role
roles_list = ['Opener', 'Middle-Order', 'Finisher', 'Pacer', 'Spinner', 'Batting All-Rounder', 'Bowling All-Rounder']
role_counts = {team: {role: 0 for role in roles_list} for team in teams}
for team in teams:
    for p in all_predictions[team]:
        if p['role'] in role_counts[team]:
            role_counts[team][p['role']] += 1

bottom = np.zeros(len(teams))
colors = ['#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f']
for i, role in enumerate(roles_list):
    values = [role_counts[t][role] for t in teams]
    axes[1].bar(teams, values, bottom=bottom, label=role, color=colors[i])
    bottom += np.array(values)
axes[1].set_title('Predicted Squad Composition by Role')
axes[1].legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=8)
axes[1].tick_params(axis='x', rotation=45)

# Chart 3: Value vs Price scatter
all_bought = []
for team in teams:
    for p in all_predictions[team]:
        all_bought.append(p)
scores = [p['score'] for p in all_bought]
prices = [p['price'] for p in all_bought]
axes[2].scatter(scores, prices, alpha=0.6, c=['red' if p['overseas'] else 'blue' for p in all_bought])
axes[2].set_xlabel('Player Value Score')
axes[2].set_ylabel('Predicted Price (INR Crore)')
axes[2].set_title('Value Score vs Predicted Price')
axes[2].axline((0, 0), slope=0.3, color='gray', linestyle='--', alpha=0.5, label='Fair value line')
axes[2].legend()

plt.tight_layout()
plt.savefig('ipl_auction_analysis.png', dpi=150, bbox_inches='tight')
plt.show()

print("\nAnalysis complete! Charts saved as 'ipl_auction_analysis.png'")

Your IPL auction predictor is complete. You have gone from raw performance data to scored rankings, team needs analysis, predicted squads, and now visual insights that any franchise analyst would find useful. Next up is an optional Secret Mission where you can take things further with a live auction simulator.

Secret mission

Live Auction Simulator

Your predictions assume each team gets their top pick without a fight. Real auctions are chaotic. Multiple franchises want the same player, prices skyrocket, and budgets evaporate. Can you simulate the drama?

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely in Google Colab, so there are no ongoing costs. Decide whether to keep your notebook and data accessible, pause by closing the runtime, or delete everything entirely.

Resources you used:

  • Google Colab notebook (IPL_Auction_Predictor).
  • Kaggle account and API token.
  • Downloaded IPL Player Performance Dataset.
  • Generated visualization chart (ipl_auction_analysis.png).

Keep everything running

No action needed. Choose this if you want to keep experimenting with your auction predictor or tweak the scoring formulas.

  • Your Colab notebook stays saved in your Google Drive automatically.
  • Your Kaggle API token remains stored in Colab Secrets for future use.
  • The dataset will be re-downloaded each time you restart the runtime, which is normal.

Pause - I'll come back to this later

Disconnect the Colab runtime to free up resources. Your notebook stays saved in Google Drive.

  • In Google Colab, click Runtime in the top menu bar.
  • Click Disconnect and delete runtime.
  • Your code and outputs remain saved in the notebook file. When you return, click Runtime > Run all to re-execute everything.

Delete - I don't want to use this again

Remove the notebook and revoke your Kaggle credentials completely.

  • In Google Drive, find your IPL_Auction_Predictor notebook and move it to Trash.
  • In Google Colab, click the key icon in the left sidebar to open Secrets. Delete KAGGLE_USERNAME and KAGGLE_KEY.
  • Optionally, revoke your Kaggle API token at kaggle.com under Account Settings > API.

That's a Wrap!

That's a Wrap!

Nice work! You have built a complete IPL auction intelligence system from scratch using Python and real performance data.

Here is what you learned:

  • Built a performance scoring system that rates every IPL player by role using weighted formulas combining runs, strike rate, wickets, and economy.
  • Analyzed all 10 teams' retained squads against an ideal template to produce a shopping list of exact role gaps per franchise.
  • Predicted complete auction squads with realistic price estimates using constraint-based optimization that respects budget, overseas limits, and squad size.
  • Built a live auction simulator modeling real bidding dynamics where scarcity drives price inflation and teams compete round by round.

Ready to quiz yourself?