> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fieldfunded.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Analyze Sports Data with Python and Pandas

> Analyze sports odds data with Python and pandas. Fetch live odds, build DataFrames, calculate implied probabilities, and find value bets.

# Analyze Sports Odds Data with Python and Pandas

Turn raw odds data into actionable insights using Python and pandas. This tutorial fetches live odds from the FieldFunded API, loads them into DataFrames, and runs analysis — implied probabilities, margin calculation, and value detection.

## Prerequisites

```bash theme={null}
pip install requests pandas
```

You also need a free API key from [fieldfunded.com/docs](https://fieldfunded.com/docs) (no credit card required).

## Step 1: Fetch Odds into a DataFrame

```python theme={null}
import requests
import pandas as pd

API_KEY = "your_api_key_here"
BASE = "https://api.fieldfunded.com/v1"
H = {"X-API-Key": API_KEY}

# Get Premier League events
events = requests.get(
    f"{BASE}/events",
    headers=H,
    params={"sport": "soccer", "league": "england_epl"}
).json()

# Build a flat list of all match winner odds
rows = []
for event in events["events"][:10]:
    odds = requests.get(
        f"{BASE}/events/{event['id']}/odds", headers=H
    ).json()

    for market in odds["markets"]:
        if "1x2" not in market["name"].lower() and \
           "winner" not in market["name"].lower():
            continue
        for sel in market["selections"]:
            rows.append({
                "match": f"{event['home_team']} vs {event['away_team']}",
                "kickoff": event["commence_time"],
                "selection": sel["name"],
                "odds": sel["odds"],
                "market_id": market["id"],
                "selection_id": sel["id"],
            })

df = pd.DataFrame(rows)
print(df.head(9))
```

Output:

```
                              match              kickoff    selection  odds
0  Manchester United vs Liverpool  2026-05-10T15:00:00Z   Man United  3.40
1  Manchester United vs Liverpool  2026-05-10T15:00:00Z         Draw  3.60
2  Manchester United vs Liverpool  2026-05-10T15:00:00Z    Liverpool  2.10
3        Arsenal vs Aston Villa  2026-05-10T17:30:00Z      Arsenal  1.55
4        Arsenal vs Aston Villa  2026-05-10T17:30:00Z         Draw  4.20
5        Arsenal vs Aston Villa  2026-05-10T17:30:00Z  Aston Villa  5.80
```

## Step 2: Calculate Implied Probabilities

```python theme={null}
# Implied probability = 1 / decimal_odds
df["implied_prob"] = (1 / df["odds"] * 100).round(1)

# Group by match to calculate the margin (overround)
def calc_margin(group):
    total_prob = group["implied_prob"].sum()
    group["margin"] = (total_prob - 100).round(1)
    return group

df = df.groupby("match", group_keys=False).apply(calc_margin)

print(df[["match", "selection", "odds", "implied_prob", "margin"]].head(6))
```

Output:

```
                              match    selection  odds  implied_prob  margin
0  Manchester United vs Liverpool   Man United  3.40          29.4     5.9
1  Manchester United vs Liverpool         Draw  3.60          27.8     5.9
2  Manchester United vs Liverpool    Liverpool  2.10          47.6     5.9
3        Arsenal vs Aston Villa      Arsenal  1.55          64.5     5.1
4        Arsenal vs Aston Villa         Draw  4.20          23.8     5.1
5        Arsenal vs Aston Villa  Aston Villa  5.80          17.2     5.1
```

The margin (5-6%) represents the bookmaker's edge. Lower margins mean better odds for bettors.

## Step 3: Find Value Bets

A "value bet" is when you believe the true probability is higher than the implied probability. Use your own model or estimates:

```python theme={null}
# Example: your model's probability estimates
your_model = {
    "Man United": 32.0,    # Your model says 32%, book says 29.4%
    "Draw": 26.0,          # Model says 26%, book says 27.8%
    "Liverpool": 42.0,     # Model says 42%, book says 47.6%
    "Arsenal": 68.0,       # Model says 68%, book says 64.5%
}

df["model_prob"] = df["selection"].map(your_model)
df["edge"] = (df["model_prob"] - df["implied_prob"]).round(1)

# Value bets: where your model gives higher probability than the book
value_bets = df[df["edge"] > 0].sort_values("edge", ascending=False)
print(value_bets[["match", "selection", "odds", "implied_prob",
                   "model_prob", "edge"]])
```

Output:

```
                              match    selection  odds  implied_prob  model_prob  edge
3        Arsenal vs Aston Villa      Arsenal  1.55          64.5        68.0   3.5
0  Manchester United vs Liverpool   Man United  3.40          29.4        32.0   2.6
```

## Step 4: Track Odds Over Time

Build a historical dataset by polling at intervals:

```python theme={null}
import time
from datetime import datetime

def collect_snapshots(event_id, intervals=10, pause=60):
    """Collect odds snapshots over time."""
    snapshots = []

    for i in range(intervals):
        odds = requests.get(
            f"{BASE}/events/{event_id}/odds", headers=H
        ).json()

        timestamp = datetime.now().isoformat()

        for market in odds["markets"]:
            if "winner" not in market["name"].lower():
                continue
            for sel in market["selections"]:
                snapshots.append({
                    "timestamp": timestamp,
                    "selection": sel["name"],
                    "odds": sel["odds"],
                })

        print(f"Snapshot {i + 1}/{intervals} collected")
        if i < intervals - 1:
            time.sleep(pause)

    return pd.DataFrame(snapshots)

# Collect 10 snapshots, 1 minute apart
# history = collect_snapshots("event_abc123", intervals=10, pause=60)
```

## Step 5: Visualize Odds Movement

```python theme={null}
import matplotlib.pyplot as plt

def plot_odds_history(history_df):
    """Plot odds movement over time for each selection."""
    fig, ax = plt.subplots(figsize=(10, 5))

    for name, group in history_df.groupby("selection"):
        ax.plot(
            range(len(group)),
            group["odds"].values,
            marker="o",
            label=name,
            linewidth=2,
        )

    ax.set_xlabel("Snapshot")
    ax.set_ylabel("Decimal Odds")
    ax.set_title("Odds Movement Over Time")
    ax.legend()
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.savefig("odds_movement.png", dpi=150)
    print("Chart saved to odds_movement.png")

# plot_odds_history(history)
```

## Step 6: Export for Further Analysis

```python theme={null}
# Save to CSV
df.to_csv("epl_odds.csv", index=False)
print(f"Saved {len(df)} rows to epl_odds.csv")

# Save to Excel with multiple sheets
with pd.ExcelWriter("odds_analysis.xlsx") as writer:
    df.to_excel(writer, sheet_name="All Odds", index=False)
    value_bets.to_excel(writer, sheet_name="Value Bets", index=False)
    print("Saved to odds_analysis.xlsx")
```

## Rate Limit Math

| Analysis type                            | Requests | Frequency | Monthly | Plan           |
| ---------------------------------------- | -------- | --------- | ------- | -------------- |
| One-off EPL analysis (10 matches)        | 11       | Once      | 11      | Free           |
| Weekly analysis across 3 leagues         | 90       | 4x/month  | 360     | Free           |
| Daily odds tracking (10 events, hourly)  | 240      | Daily     | 7,200   | Free           |
| Full pipeline with historical collection | 1,000    | Daily     | 30,000  | Starter (\$29) |

Data analysis is typically low-frequency — most workflows fit in the [free tier](/compare/free-sports-api).

***

## Related Guides

* [Get Live Odds with Python](/blog/get-live-odds-python) — simpler Python tutorial for beginners
* [Build a Line Tracker](/guides/build-line-tracker) — track odds movements with a UI
* [Build a Prediction Model](/guides/sports-prediction-model) — use odds data for ML predictions
* [Web Scraping vs API](/blog/scraping-odds-vs-api) — why an API is better than scraping

<CardGroup cols={2}>
  <Card title="Get Your Free API Key" icon="key" href="/quickstart">
    Start analyzing in 5 minutes — 10,000 free requests/month
  </Card>

  <Card title="See Pricing" icon="dollar-sign" href="/compare/pricing">
    All plans compared side by side
  </Card>
</CardGroup>
