Search “apex legends tier list” today and you’ll get five different answers. One site puts Octane in S-tier. Another ranks Loba first because raw play data says so. A third still lists Bloodhound near the bottom, weeks after Respawn reworked the character. None of these sites are wrong exactly. They’re just measuring different things, and most of them won’t tell you which metric they used.
This tutorial walks you through building your own Apex Legends tier list tracker instead of trusting someone else’s snapshot. You’ll write a Python script that ingests Season 30 “Marked” pick-rate data, scores every legend against a formula you control, and outputs a tier list you can defend with numbers. By the end you’ll have a working project: a source CSV, a scoring engine, a formatted console output, and a scheduled job that refreshes the list after every patch.
Build time is roughly 30 to 40 minutes if Python is already on your machine. Twelve steps, six code blocks, one complete script you can run today, and a real dataset pulled from Season 30 Marked ranked lobbies.
Why a Personal Tracker Beats a Static Apex Legends Tier List
Every published apex legends tier list is a photograph, and photographs go out of date the moment they’re taken. Respawn ships balance passes roughly every six to eight weeks, sometimes with a mid-cycle hotfix squeezed in between, and a static ranking published on patch day can’t account for how the playerbase actually adapts over the following month. A tracker you own and can re-run solves that problem structurally instead of asking you to remember to check for an updated article.
There’s a second, quieter reason to build this yourself: transparency. When a site tells you Octane is S-tier, you have no way to check their math. When your own script tells you Axle is S-tier, you can open scoring.py and see exactly why, adjust the weighting if you disagree, and rerun it in seconds. That’s a meaningfully different relationship with the data than scrolling a listicle and hoping the author’s judgment matches your playstyle. It also means the tracker keeps working long after this specific article is out of date, since Step 10’s automation pulls in whatever CSV you feed it next patch.
Prerequisites: What You Need Before You Start
Nothing here is exotic. If you’ve written any Python before, you already have most of this installed. The full stack is three widely used libraries and a folder structure, no paid API keys and no account signups required to follow along.
- Python 3.11 or newer (3.12 recommended) installed and available on your PATH
- pip, which ships with modern Python installs
- pandas 2.2 or later for handling the dataset as a dataframe
- tabulate 0.9 or later for readable console tables
- A text editor: VS Code, Vim, or whatever you already use
- About 30 minutes and the Season 30 Marked patch notes open in a browser tab
- Optional: an account on a stats tracker like apexlegendsstatus.com if you want to swap in your own rank-bracket data later
Step 1: Understand How Apex Legends Tier Lists Are Actually Built
Before writing a line of code, it helps to know why an apex legends tier list from one site can contradict another published the same week. Most tier lists blend three separate signals: win rate, pick rate, and editorial judgment about a legend’s ceiling in the hands of a good player. Sites that lean on win rate alone tend to overrate niche legends played only by specialists, because a small sample of skilled mains inflates the number. Sites that lean on pick rate alone measure popularity, not strength, and popularity lags behind patch notes by weeks.
Season 30 Marked is a clean example of that lag. EA’s own patch notes flagged the gap directly when explaining the Bloodhound rework:
“Despite changes to Bloodhound’s kit in Season 28, they remain nearly invisible at middle-to-high tiers and have the lowest pick rate once you get to Gold tier and above.”
EA, Apex Legends Marked Patch Notes, August 3, 2026
Some community tier lists jumped Bloodhound straight to S-tier the day the patch dropped, crediting the rework on paper. Meanwhile, tracked ranked data from Master and Predator lobbies in August 2026 still shows Bloodhound sitting at a 0.5% pick rate, dead last among all 28 legends, according to Esports Tales’ pick-rate tracker. Both numbers are real. They just measure different moments: patch-day sentiment versus actual play weeks later. A tracker you build yourself lets you see both and decide which one matters for your own goals, whether that’s climbing ranked solo or drafting for a scrim team.
It also helps to think in roles rather than just individual names. Apex Legends groups its 28 legends into five archetypes: Skirmisher, Assault, Recon, Support, and Controller. Season 30 Marked’s meta skews hard toward Skirmishers and mobility-first Support kits, which is why Axle, Pathfinder, Loba, and Octane dominate the top of the pick-rate table while trap-and-hold Controllers like Wattson, Rampart, and Caustic sit near the bottom. A tier list that only ranks names hides that pattern. A tier list that carries a role column, like the one you’re about to build, makes the pattern obvious at a glance.
Step 2: Set Up Your Project Folder
Create a dedicated folder and an isolated virtual environment so the tracker’s dependencies don’t collide with anything else on your system.
mkdir apex-tier-tracker
cd apex-tier-tracker
python3 -m venv .venv
source .venv/bin/activate # on Windows: .venv\Scripts\activate
mkdir data logs
The data folder holds your meta CSV files, one per patch. The logs folder captures output from the scheduled refresh you’ll set up in Step 10. Keeping patches in separate CSVs (season30_marked.csv, season30_hotfix.csv, and so on) means you can diff a legend’s trajectory across an entire season instead of overwriting history every time you rerun the script.
Step 3: Install Dependencies
With the virtual environment active, install the three packages the tracker needs.
pip install pandas>=2.2 tabulate>=0.9 requests>=2.31
pip freeze > requirements.txt
Freezing the requirements file now means anyone else on your team, or future you on a new machine, can rebuild the exact same environment with pip install -r requirements.txt. This matters more than it sounds like: pandas API behavior has shifted enough between major versions that a tracker built on 1.x can throw warnings or silently miscalculate on 2.x.
Step 4: Build Your Season 30 Marked Meta Dataset
A tier list tracker is only as good as the data feeding it. Rather than starting from nothing, use the table below as your seed dataset. It’s built from Master and Apex Predator ranked lobbies tracked in August 2026, Season 30 Marked, as published by Esports Tales. Pick rate reflects real play, not patch-day hype, which is exactly the signal a lot of “reworked and suddenly S-tier” claims are missing.
| Legend | Role | Master/Pred Pick Rate |
|---|---|---|
| Axle | Skirmisher | 25.4% |
| Loba | Support | 12.4% |
| Pathfinder | Skirmisher | 10.5% |
| Seer | Recon | 7.8% |
| Mad Maggie | Assault | 6.8% |
| Valkyrie | Skirmisher | 3.8% |
| Octane | Skirmisher | 3.5% |
| Alter | Skirmisher | 3.5% |
| Revenant | Skirmisher | 3.4% |
| Bangalore | Assault | 3.4% |
| Horizon | Skirmisher | 2.4% |
| Wraith | Skirmisher | 2.1% |
| Vantage | Recon | 1.8% |
| Fuse | Assault | 1.8% |
| Gibraltar | Support | 1.7% |
| Ash | Skirmisher | 0.9% |
| Wattson | Controller | 0.8% |
| Lifeline | Support | 0.8% |
| Rampart | Controller | 0.6% |
| Mirage | Skirmisher | 0.6% |
| Caustic | Controller | 0.6% |
| Crypto | Recon | 0.6% |
| Newcastle | Support | 0.6% |
| Bloodhound | Recon | 0.5% |
| Catalyst | Controller | 0.5% |
Save that as data/season30_marked.csv with headers legend,role,pick_rate. If you have access to your own tracker.gg or apexlegendsstatus.com export, swap in win rate and KP (kill participation) columns too. The scoring formula in Step 6 works with pick rate alone, but it accepts extra columns if you have them.
Step 5: Write the Data Loader Script
Create load_data.py. This function reads your CSV, validates that the required columns exist, and hands back a sorted dataframe. Failing loudly on a malformed CSV here saves you a confusing stack trace three functions later.
import pandas as pd
REQUIRED_COLUMNS = {"legend", "role", "pick_rate"}
def load_meta_dataset(csv_path: str) -> pd.DataFrame:
df = pd.read_csv(csv_path)
missing = REQUIRED_COLUMNS - set(df.columns)
if missing:
raise ValueError(f"CSV is missing required columns: {missing}")
df["pick_rate"] = df["pick_rate"].astype(float)
if "win_rate" not in df.columns:
df["win_rate"] = None
return df.sort_values("pick_rate", ascending=False).reset_index(drop=True)
if __name__ == "__main__":
meta = load_meta_dataset("data/season30_marked.csv")
print(meta.head(10))
Run python load_data.py and you should see the top 10 legends by pick rate printed to the console, with Axle sitting at the top. If you get a FileNotFoundError, double-check you saved the CSV inside the data folder created in Step 2.
Step 6: Build the Tier-Scoring Formula
Create scoring.py. This is the core of the tracker: a formula that converts pick rate (and, optionally, win rate) into a single normalized score from 0 to 100. The weights are yours to tune. The version below leans 70% on pick rate because that’s the column with real, verified Season 30 data behind it, and treats win rate as a secondary boost only when you’ve supplied it yourself.
def compute_tier_score(row, pick_weight=0.7, winrate_weight=0.3, top_pick_rate=25.4):
pick_component = min(row["pick_rate"] / top_pick_rate, 1.0)
win_rate = row.get("win_rate")
if win_rate is None or (isinstance(win_rate, float) and win_rate != win_rate):
# no win rate supplied — fall back to pick rate only
return round(pick_component * 100, 1)
winrate_component = max(min((win_rate - 40) / 20, 1.0), 0.0)
score = (pick_component * pick_weight) + (winrate_component * winrate_weight)
return round(score * 100, 1)
top_pick_rate=25.4 anchors the scale to Axle’s Season 30 pick rate, the highest of any legend in Master and Predator lobbies this patch. That number will change next season, so update the constant (or better, compute it dynamically with df["pick_rate"].max()) every time you refresh the dataset.
Step 7: Classify Legends Into S Through D Tiers
A raw score from 0 to 100 isn’t a tier list yet. You need thresholds. These are a starting point, not gospel, adjust them once you see how your own dataset distributes.
def classify_tier(score: float) -> str:
if score >= 40:
return "S"
elif score >= 20:
return "A"
elif score >= 8:
return "B"
elif score >= 3:
return "C"
return "D"
meta["tier_score"] = meta.apply(compute_tier_score, axis=1)
meta["tier"] = meta["tier_score"].apply(classify_tier)
Because pick rate is heavily concentrated at the top in Apex (Axle alone accounts for a quarter of all Master/Pred picks), the thresholds above are deliberately skewed low. If you flatten your weighting or add win rate for every legend, you’ll want to re-run a distribution check and adjust the cutoffs so S-tier doesn’t end up with 15 legends in it.
Step 8: Generate a Formatted Tier List Output
Now turn the scored dataframe into something readable. This is where tabulate earns its place in requirements.txt.
from tabulate import tabulate
def print_tier_list(df):
ordered = df.sort_values(["tier", "tier_score"], ascending=[True, False])
table = ordered[["tier", "legend", "role", "pick_rate", "tier_score"]]
print(tabulate(table, headers="keys", tablefmt="github", showindex=False))
print_tier_list(meta)
Running the full pipeline against the Season 30 Marked dataset from Step 4 produces output like this:
| tier | legend | role | pick_rate | tier_score |
|--------|------------|-------------|-------------|--------------|
| S | Axle | Skirmisher | 25.4 | 100.0 |
| A | Loba | Support | 12.4 | 48.8 |
| A | Pathfinder | Skirmisher | 10.5 | 41.3 |
| B | Seer | Recon | 7.8 | 30.7 |
| B | Mad Maggie | Assault | 6.8 | 26.8 |
| C | Valkyrie | Skirmisher | 3.8 | 15.0 |
| C | Octane | Skirmisher | 3.5 | 13.8 |
| D | Bloodhound | Recon | 0.5 | 2.0 |
Notice Bloodhound lands in D-tier here, in direct tension with the community sentiment list in Step 11 that ranks the reworked character in S-tier on patch-day judgment. Both are legitimate apex legends tier list outputs. Yours is grounded in what’s actually being played, theirs is grounded in what the patch notes promised. Knowing the difference is the entire point of building this yourself.
Step 9: Filter by Role or Rank Bracket
A flat 28-legend list is useful for an overview, but most players want to know the best pick within their role, or a filtered view for a specific rank bracket. Add a helper function to scoring.py.
def filter_by_role(df, role: str):
matches = df[df["role"].str.lower() == role.lower()]
if matches.empty:
raise ValueError(f"No legends found for role '{role}'. Check spelling and case.")
return matches.sort_values("tier_score", ascending=False)
# example: best supports in the current meta
print_tier_list(filter_by_role(meta, "Support"))
If you’re maintaining separate CSVs per rank bracket (Bronze-Silver play looks very different from Master/Predator in Apex), you can run the entire pipeline once per bracket and compare S-tier lists side by side. That’s often more useful for a solo-queue grinder than the pro-facing lists most sites publish.
Step 10: Automate Weekly Refreshes
Meta data goes stale fast, especially in the weeks right after a big patch like Marked. Rather than remembering to rerun the script, schedule it. On Linux or macOS, cron works fine for a personal tracker.
# crontab -e — refresh every Monday at 09:00, after weekend ranked data settles
0 9 * * 1 /path/to/apex-tier-tracker/.venv/bin/python /path/to/apex-tier-tracker/build_tier_list.py >> /path/to/apex-tier-tracker/logs/run.log 2>&1
If you’d rather not manage a personal cron job, a GitHub Actions workflow on a schedule trigger does the same thing without needing a machine to stay powered on. Either way, keep every run’s output as a dated file (tier_list_2026-08-16.md, for example) instead of overwriting one file each week. That history is what lets you chart how a legend’s pick rate moves after a buff, which is far more interesting than any single week’s snapshot.
Step 11: Validate Your Tier List Against Community Sources
Before you trust your own output, sanity-check it against a couple of independently published lists. The table below is a Season 30 Marked community tier list published August 3, 2026, the same day the patch went live, built primarily on patch-note judgment rather than tracked pick-rate data.
| Tier | Legends |
|---|---|
| S | Octane, Alter, Lifeline, Conduit, Bloodhound |
| A | Axle, Wraith, Newcastle, Mad Maggie, Pathfinder, Loba, Rampart |
| B | Revenant, Valkyrie, Seer, Sparrow, Caustic, Fuse, Catalyst |
| C | Crypto, Ballistic, Wattson |
| D | Ash, Mirage, Vantage |
Compare that against your pick-rate-driven output and you’ll see immediate friction: this list has Lifeline and Bloodhound in S-tier, while your tracker (built on real Master/Pred play) puts both near the bottom on pick rate. Neither list is broken. The community list reflects what a rework theoretically enables, your tracker reflects what players have actually chosen to run in the two weeks since. If you’re drafting for a scrim or ALGS-style competitive setting, weight patch-day judgment higher. If you’re deciding what to queue into ranked tonight, trust the pick-rate data, because it reflects what your actual lobbies look like.
Step 12: Ship the Complete Working Project
Here’s the full script, build_tier_list.py, combining every piece from Steps 5 through 9 into one runnable file. Drop this in your project root alongside the data folder from Step 4.
import sys
import pandas as pd
from tabulate import tabulate
REQUIRED_COLUMNS = {"legend", "role", "pick_rate"}
def load_meta_dataset(csv_path: str) -> pd.DataFrame:
df = pd.read_csv(csv_path)
missing = REQUIRED_COLUMNS - set(df.columns)
if missing:
raise ValueError(f"CSV is missing required columns: {missing}")
df["pick_rate"] = df["pick_rate"].astype(float)
if "win_rate" not in df.columns:
df["win_rate"] = None
return df.sort_values("pick_rate", ascending=False).reset_index(drop=True)
def compute_tier_score(row, pick_weight=0.7, winrate_weight=0.3, top_pick_rate=25.4):
pick_component = min(row["pick_rate"] / top_pick_rate, 1.0)
win_rate = row.get("win_rate")
if win_rate is None or (isinstance(win_rate, float) and win_rate != win_rate):
return round(pick_component * 100, 1)
winrate_component = max(min((win_rate - 40) / 20, 1.0), 0.0)
score = (pick_component * pick_weight) + (winrate_component * winrate_weight)
return round(score * 100, 1)
def classify_tier(score: float) -> str:
if score >= 40:
return "S"
elif score >= 20:
return "A"
elif score >= 8:
return "B"
elif score >= 3:
return "C"
return "D"
def print_tier_list(df):
ordered = df.sort_values(["tier", "tier_score"], ascending=[True, False])
table = ordered[["tier", "legend", "role", "pick_rate", "tier_score"]]
print(tabulate(table, headers="keys", tablefmt="github", showindex=False))
def main(csv_path="data/season30_marked.csv"):
meta = load_meta_dataset(csv_path)
top_rate = float(meta["pick_rate"].max())
meta["tier_score"] = meta.apply(compute_tier_score, axis=1, top_pick_rate=top_rate)
meta["tier"] = meta["tier_score"].apply(classify_tier)
print_tier_list(meta)
if __name__ == "__main__":
csv_arg = sys.argv[1] if len(sys.argv) > 1 else "data/season30_marked.csv"
main(csv_arg)
Run it with python build_tier_list.py, or point it at a different CSV with python build_tier_list.py data/season30_hotfix.csv once the next patch lands. That’s a complete, working apex legends tier list tracker: real data in, a transparent formula, a formatted table out, and a scheduled job keeping it current.
Your finished project folder should look like this once every step above is done:
apex-tier-tracker/
├── .venv/
├── data/
│ ├── season30_marked.csv
│ └── season30_hotfix.csv # added after the next patch
├── logs/
│ └── run.log
├── load_data.py
├── scoring.py
├── build_tier_list.py # the complete script from Step 12
└── requirements.txt
Everything in that tree is plain text, so it version-controls cleanly with git if you want a full history of how your tier list moved over the season, which the advanced tips section below covers in more detail.
Common Pitfalls When Building an Apex Legends Tier List
- Mixing rank brackets in one dataset. A legend’s Bronze pick rate and its Predator pick rate can point in opposite directions, since low-elo lobbies favor forgiving, low-skill-floor kits while high-elo lobbies reward mechanically demanding ones. Keep separate CSVs per bracket rather than averaging them into a number that describes no real lobby.
- Hardcoding the top pick rate. The
top_pick_rate=25.4constant only holds for Season 30 Marked. Compute it dynamically from the loaded dataframe so the script doesn’t silently miscalculate next patch when a new legend or meta shift produces a different ceiling. - Treating pick rate as the whole story. A legend can be heavily picked because it’s strong, or because it’s easy and forgiving, or simply because it just released and novelty is driving adoption. Cross-reference with win rate whenever you can get it, and don’t be surprised when a high-pick, mediocre-win-rate legend turns up in your data.
- Ignoring patch-day lag. As the Bloodhound example shows, a rework doesn’t move pick rate overnight. Player habits, YouTube guides, and pro-player endorsements all take time to filter down to the ladder. Wait at least a week or two of tracked data before trusting a post-buff number.
- Overwriting your only historical record. Saving each run as
latest.csvdestroys your ability to chart trends. Date-stamp every export, even if it feels like unnecessary file clutter in week one. - Copying thresholds from someone else’s formula without checking the distribution. The tier cutoffs in Step 7 fit this specific dataset’s skew, where one legend dominates pick rate. Pull in a season with a flatter meta and those same cutoffs might put every legend in B-tier. Always eyeball a histogram of your scores before trusting the tier labels.
Troubleshooting
| Problem | Likely Cause and Fix |
|---|---|
| ModuleNotFoundError: No module named ‘pandas’ | Your virtual environment isn’t active. Run source .venv/bin/activate before installing or running anything. |
| FileNotFoundError on the CSV path | Check the path is relative to where you’re running the script, not to the script file’s own location. |
| KeyError: ‘pick_rate’ | Your CSV headers don’t match exactly. Column names are case-sensitive; use lowercase pick_rate, not Pick_Rate. |
| Every legend lands in D-tier | Your top_pick_rate constant is set too high relative to your actual data’s maximum. Recompute it with df["pick_rate"].max(). |
| tabulate output looks misaligned in your terminal | Switch tablefmt="github" to tablefmt="grid" for terminals that don’t render Markdown pipe tables well. |
| Cron job never runs | Cron uses a minimal environment with no virtualenv activation. Always call the venv’s Python binary directly by full path, as shown in Step 10. |
| win_rate column throws a comparison error | Empty CSV cells load as NaN, not None. The row.get("win_rate") check in Step 6 handles this, but only if your CSV column is actually named win_rate. |
| Scores don’t match the example output | You’re likely running against a newer or different CSV than the Step 4 dataset. Scores are relative to whatever data you load, so this is expected once the meta shifts. |
Advanced Tips for Power Users
Once the basic pipeline works, a few upgrades make the tracker genuinely useful instead of just a personal exercise.
- Add a decay factor. Weight the most recent week’s data higher than data from three weeks ago, so a legend riding a fading buff doesn’t stay artificially inflated in your list. A simple exponential decay, halving the influence of each prior week, keeps the tracker responsive without throwing away history entirely.
- Separate ranked and competitive datasets. If you’re tracking for a competitive team rather than solo queue, pull ALGS-style pro-play data separately from ranked ladder data, since the two diverge for the reasons covered in the section above on ranked versus competitive play.
- Export to JSON for downstream tools. A console table is fine for personal use, but if you want to feed the tier list into a Discord bot or an OBS overlay for stream, add
meta.to_json("tier_list.json", orient="records")right after the scoring step. Most Discord bot frameworks and browser-source overlays can consume that JSON directly. - Version-control your CSVs. A private git repo costs nothing and gives you a full commit history of exactly how the meta moved, patch by patch, all season. Six months from now,
git log -p data/season30_marked.csvis a far better record than trying to remember which week Loba’s pick rate started climbing. - Add a confidence flag for small samples. If you’re building rank-bracket-specific datasets and a bracket has very few tracked matches for a given legend, a raw pick rate can be noisy. Flag any row under a sample-size threshold you’re comfortable with, so a fluke week doesn’t quietly rewrite your S-tier.
Season 30 Marked: What the Numbers Say About the Current Meta
A few patterns stand out once you run the real Season 30 Marked data through the tracker. Axle’s 25.4% pick rate in Master and Predator lobbies isn’t close, it’s more than double the next-highest legend, Loba at 12.4%, according to Esports Tales’ tracked data. That kind of concentration at the top is unusual even for Apex, where a handful of legends historically dominate high-elo pick rates. Loba’s own rise is the bigger story of the patch: the character jumped from roughly 1.9% pick rate to 17.7% in some tracked samples, a swing large enough that multiple independent tier lists now treat Loba as an S-tier support rather than the situational pick she was through most of 2025. On the other end, ten legends, including Bloodhound, Catalyst, Newcastle, Crypto, and Caustic, sit below a 1% pick rate in the same lobbies, which tells you setup-dependent kits are struggling against Season 30’s faster ranked tempo and trimmed loot pool.
Apex Legends still carries an estimated 20 to 22 million monthly active players across platforms as of early 2026, per cross-platform tracker estimates and EA’s own earnings commentary, so a meta shift like this one affects a genuinely large player base, not a niche. On PC specifically, Steam tracking puts average concurrent players at roughly 104,455 and June 2026 peak concurrency at about 254,117, a useful reminder that console and Origin/EA App players still make up most of the base and won’t show up in Steam-only stats. If you’re building a tracker for the long haul, expect these numbers, and the pick-rate table that drives your tier list, to keep moving with every mid-season update EA ships.
One more pattern worth building into your own analysis: Season 30 Marked shipped with a trimmed loot pool and a faster overall tempo, and that structural change explains more of the current apex legends tier list shakeup than any single legend buff does. Legends whose kits depend on setup time, think Wattson’s fences, Caustic’s gas traps, or Rampart’s deployable cover, lose value when matches resolve faster and players have less downtime to prep a position. That’s a mechanical, patch-driven cause, not a popularity contest, and it’s exactly the kind of context a bare S-to-D list won’t give you but your own annotated dataset will.
Ranked Ladder vs ALGS: Why Competitive Drafts Look Different
Everything in the dataset above comes from ranked ladder play, and that’s worth flagging explicitly, because Apex’s competitive scene doesn’t always mirror it. ALGS-format matches run on a smaller map pool, reward zone control and third-party discipline over pure aggression, and are played by rosters that scrim against each other for hours before a tournament, which changes what “strong” means. A legend can be a top-five ranked pick because it wins solo-queue duels, and still see almost no competitive draft time because it doesn’t fit a team’s positioning plan. The reverse happens too: a Controller with a weak ranked pick rate can still be a mandatory competitive pick if its zone-denial tools matter more in a bracket setting than in a 20-team pubs-adjacent lobby.
If your goal is to climb solo or duo ranked, the tracker you just built is already tuned for that, since it’s built entirely on ranked ladder pick rate. If you’re coaching or playing on a team preparing for a tournament bracket, treat the ranked output as a starting point and layer in scrim results and opponent tendencies on top of it. The two datasets answer different questions, and conflating them is one of the more common mistakes newer competitive teams make when they draft straight off a public tier list instead of their own scrim data.
Frequently Asked Questions
What is the best legend in Apex Legends right now?
By raw pick rate in Master and Predator ranked lobbies during Season 30 Marked, Axle is the most-played legend at 25.4%, according to tracked data from Esports Tales. Whether that makes Axle the “best” depends on whether you weight popularity or win rate, which is exactly why this tutorial has you build a tracker rather than take one number at face value.
Why do different apex legends tier list sites disagree so much?
Most disagreements come down to methodology, not opinion. Some lists are built from tracked pick rate and win rate data pulled from ranked lobbies. Others are built from editorial judgment about a legend’s kit right after a patch, before real play data exists yet. Step 11 above shows both side by side for Season 30 Marked.
Do I need an official Apex Legends API to build this tracker?
No. This tutorial uses a CSV you populate yourself from published pick-rate data or your own tracker.gg exports. There’s no requirement to hit a live API, which keeps the project simple and avoids dealing with rate limits or authentication.
How often should I refresh the tier list data?
Weekly is a reasonable default, which is why Step 10 sets up a Monday-morning cron job. Refresh sooner after a major balance patch, since pick rate can swing meaningfully within days of a big rework, even if the underlying player adoption takes longer to stabilize.
Can I use this same approach for a different game’s tier list?
Yes. The scoring and classification logic in Steps 6 and 7 doesn’t reference anything Apex-specific beyond column names. Swap in a dataset for another character-based competitive game and the same pipeline works, provided you can source real pick-rate or win-rate data.
Why does my tier list put reworked legends lower than community lists?
Because your tracker is built on pick rate, which lags behind patch notes. A rework can change a legend’s kit instantly, but player habits and team compositions take longer to catch up. That gap is normal and is covered directly in Step 1 and Step 11.
What Python version should I use for this project?
Python 3.11 or newer works well, though 3.12 is recommended for the latest performance improvements. Both are fully compatible with pandas 2.2 and tabulate 0.9, the versions used throughout this tutorial. You can check your installed version with python3 --version.
Should I trust ranked pick rate or a competitive ALGS-style tier list?
It depends on what you’re optimizing for. Ranked pick rate, which is what this tutorial’s tracker uses, reflects what actually wins games in ladder lobbies and is the better signal for solo or duo queue. Competitive-format tier lists weight map control and team synergy more heavily and matter more if you’re drafting for a scrim team or tournament bracket. The section above on ranked ladder versus ALGS covers the distinction in more detail.
What You’ve Built
Twelve steps in, you have more than a static apex legends tier list. You have a project with a source dataset built on real Season 30 Marked pick-rate numbers, a transparent scoring formula you can tune, a classifier that turns scores into tiers, a role and rank-bracket filter, a scheduled refresh, and a full script that ties all of it together. None of it depends on a paid API or a subscription, and every number in the seed dataset traces back to a cited source rather than a guess.
The real payoff shows up next patch, not this one. When Respawn ships the next balance pass, you won’t be refreshing a browser tab waiting for someone else to publish an updated list. You’ll drop a new CSV into the data folder, rerun build_tier_list.py, and have your own answer in seconds, with the receipts to back it up.
Related Coverage
- Rainbow Six Siege Ranks Guide: 40 Ranks, 12 Steps [2026]
- Rainbow Six Siege Stats Tracker Setup: 10 Steps, 30 Min [2026]
- R6 Siege Ranked 3.0 vs Valorant: 40 Ranks vs 25 [2026]
- CS2 Ranks vs ESEA vs FACEIT: 25M vs 3M Players [2026]
- CS2 Premier vs Competitive Rank: 7 Tiers vs 18 Ranks [2026]
- Call of Duty Ranked Play Drops Hidden MMR, Flat SR [2026]
- More Esports Coverage




