Copy-paste ready R and Python code for NFL analytics. From data loading to machine learning models.
Fantasy football analysis, projections, and trade evaluations
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# PPR Fantasy Points calculation
# Passing: 0.04 per yard, 4 per TD, -2 per INT
# Rushing: 0.1 per yard, 6 per TD
# Receiving: 0.1 per yard, 6 per TD, 1 per reception
# QB Fantasy Points
qb_fantasy <- pbp %>%
filter(!is.na(passer_player_id)) %>%
group_by(passer_player_id, passer_player_name) %>%
summarize(
pass_yards = sum(passing_yards, na.rm = TRUE),
pass_tds = sum(pass_touchdown, na.rm = TRUE),
ints = sum(interception),
fantasy_pts = pass_yards * 0.04 + pass_tds * 4 - ints * 2,
.groups = "drop"
) %>%
arrange(desc(fantasy_pts))
# RB/WR/TE Fantasy Points (PPR)
skill_fantasy <- pbp %>%
filter(!is.na(rusher_player_id) | !is.na(receiver_player_id)) %>%
mutate(
player_id = coalesce(rusher_player_id, receiver_player_id),
player_name = coalesce(rusher_player_name, receiver_player_name)
) %>%
group_by(player_id, player_name) %>%
summarize(
rush_yards = sum(rushing_yards, na.rm = TRUE),
rush_tds = sum(rush_touchdown, na.rm = TRUE),
receptions = sum(complete_pass, na.rm = TRUE),
rec_yards = sum(receiving_yards, na.rm = TRUE),
rec_tds = sum(pass_touchdown[!is.na(receiver_player_id)], na.rm = TRUE),
fantasy_pts = rush_yards * 0.1 + rush_tds * 6 +
receptions * 1 + rec_yards * 0.1 + rec_tds * 6,
.groups = "drop"
) %>%
filter(fantasy_pts > 50) %>%
arrange(desc(fantasy_pts))
print(skill_fantasy %>% head(30))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
# QB Fantasy Points
qb_plays = pbp[pbp["passer_player_id"].notna()]
qb_fantasy = (qb_plays.groupby(["passer_player_id", "passer_player_name"])
.agg(
pass_yards=("passing_yards", "sum"),
pass_tds=("pass_touchdown", "sum"),
ints=("interception", "sum")
)
.reset_index())
qb_fantasy["fantasy_pts"] = (qb_fantasy["pass_yards"] * 0.04 +
qb_fantasy["pass_tds"] * 4 -
qb_fantasy["ints"] * 2)
qb_fantasy = qb_fantasy.sort_values("fantasy_pts", ascending=False)
# Skill position fantasy points (simplified)
rush_plays = pbp[pbp["rusher_player_id"].notna()]
rush_pts = (rush_plays.groupby(["rusher_player_id", "rusher_player_name"])
.agg(
rush_yards=("rushing_yards", "sum"),
rush_tds=("rush_touchdown", "sum")
)
.reset_index())
rush_pts.columns = ["player_id", "player_name", "rush_yards", "rush_tds"]
rec_plays = pbp[pbp["receiver_player_id"].notna()]
rec_pts = (rec_plays.groupby(["receiver_player_id", "receiver_player_name"])
.agg(
receptions=("complete_pass", "sum"),
rec_yards=("receiving_yards", "sum"),
rec_tds=("pass_touchdown", "sum")
)
.reset_index())
rec_pts.columns = ["player_id", "player_name", "receptions", "rec_yards", "rec_tds"]
# Combine
skill_fantasy = rush_pts.merge(rec_pts, on=["player_id", "player_name"], how="outer").fillna(0)
skill_fantasy["fantasy_pts"] = (skill_fantasy["rush_yards"] * 0.1 +
skill_fantasy["rush_tds"] * 6 +
skill_fantasy["receptions"] * 1 +
skill_fantasy["rec_yards"] * 0.1 +
skill_fantasy["rec_tds"] * 6)
skill_fantasy = skill_fantasy[skill_fantasy["fantasy_pts"] > 50].sort_values("fantasy_pts", ascending=False)
print(skill_fantasy.head(30))
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# Calculate target share by team
target_share <- pbp %>%
filter(play_type == "pass", !is.na(receiver_player_id)) %>%
group_by(posteam) %>%
mutate(team_targets = n()) %>%
group_by(posteam, receiver_player_id, receiver_player_name, team_targets) %>%
summarize(
targets = n(),
air_yards = sum(air_yards, na.rm = TRUE),
receptions = sum(complete_pass),
yards = sum(yards_gained, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(
target_share = targets / team_targets,
air_yard_share = air_yards / sum(air_yards),
wopr = target_share * 1.5 + air_yard_share * 0.7 # Weighted Opportunity Rating
) %>%
arrange(desc(wopr))
# Top target share players
print(target_share %>%
select(receiver_player_name, posteam, target_share,
air_yard_share, wopr) %>%
head(25))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
# Filter to pass plays with receiver
pass_plays = pbp[(pbp["play_type"] == "pass") &
(pbp["receiver_player_id"].notna())]
# Team totals
team_targets = pass_plays.groupby("posteam").agg(
team_targets=("play_id", "count"),
team_air_yards=("air_yards", "sum")
).reset_index()
# Player stats
player_stats = (pass_plays.groupby(
["posteam", "receiver_player_id", "receiver_player_name"])
.agg(
targets=("play_id", "count"),
air_yards=("air_yards", "sum"),
receptions=("complete_pass", "sum"),
yards=("yards_gained", "sum")
)
.reset_index())
# Calculate shares
target_share = player_stats.merge(team_targets, on="posteam")
target_share["target_share"] = target_share["targets"] / target_share["team_targets"]
target_share["air_yard_share"] = target_share["air_yards"] / target_share["team_air_yards"]
target_share["wopr"] = target_share["target_share"] * 1.5 + target_share["air_yard_share"] * 0.7
result = target_share.nlargest(25, "wopr")[
["receiver_player_name", "posteam", "target_share", "air_yard_share", "wopr"]
]
print(result)
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
# Load participation data
participation <- load_participation(2023)
rosters <- fast_scraper_roster(2023)
# Calculate snap counts
snap_counts <- participation %>%
separate_rows(offense_players, sep = ";") %>%
filter(offense_players != "") %>%
group_by(nflverse_game_id, offense_players) %>%
summarize(snaps = n(), .groups = "drop") %>%
rename(gsis_id = offense_players)
# Join with roster for names
snap_counts <- snap_counts %>%
left_join(rosters %>% select(gsis_id, full_name, position, team),
by = "gsis_id")
# Calculate season totals
season_snaps <- snap_counts %>%
group_by(gsis_id, full_name, position, team) %>%
summarize(
games = n(),
total_snaps = sum(snaps),
avg_snaps = mean(snaps),
.groups = "drop"
) %>%
filter(position %in% c("RB", "WR", "TE")) %>%
arrange(desc(total_snaps))
print(season_snaps %>% head(30))
import nfl_data_py as nfl
import pandas as pd
# Load participation data
participation = nfl.import_snap_counts([2023])
rosters = nfl.import_rosters([2023])
# Aggregate by player
season_snaps = (participation.groupby("pfr_player_id")
.agg(
games=("week", "nunique"),
total_off_snaps=("offense_snaps", "sum"),
avg_off_snaps=("offense_snaps", "mean"),
total_def_snaps=("defense_snaps", "sum")
)
.reset_index())
# Join with roster data
roster_subset = rosters[["pfr_id", "player_name", "position", "team"]].drop_duplicates()
season_snaps = season_snaps.merge(roster_subset, left_on="pfr_player_id",
right_on="pfr_id", how="left")
# Filter skill positions
skill_snaps = season_snaps[season_snaps["position"].isin(["RB", "WR", "TE"])]
skill_snaps = skill_snaps.sort_values("total_off_snaps", ascending=False)
print("Top Snap Count Leaders (Skill Positions):")
print(skill_snaps.head(30))
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# Calculate TD rate vs expected
td_analysis <- pbp %>%
filter(play_type == "pass", !is.na(receiver_player_id)) %>%
group_by(receiver_player_id, receiver_player_name) %>%
summarize(
targets = n(),
rz_targets = sum(yardline_100 <= 20, na.rm = TRUE),
tds = sum(pass_touchdown),
expected_td_rate = 0.10 * (rz_targets / targets), # Simplified expected
actual_td_rate = tds / targets,
.groups = "drop"
) %>%
filter(targets >= 50) %>%
mutate(
td_diff = actual_td_rate - expected_td_rate,
regression_direction = case_when(
td_diff > 0.03 ~ "Negative",
td_diff < -0.03 ~ "Positive",
TRUE ~ "Stable"
)
)
# Candidates for positive regression (underperformed)
positive_regression <- td_analysis %>%
filter(regression_direction == "Positive") %>%
arrange(td_diff)
print("Positive Regression Candidates (Due for more TDs):")
print(positive_regression %>% select(receiver_player_name, targets, tds, td_diff))
# Candidates for negative regression (overperformed)
negative_regression <- td_analysis %>%
filter(regression_direction == "Negative") %>%
arrange(desc(td_diff))
print("\nNegative Regression Candidates (TD rate unsustainable):")
print(negative_regression %>% select(receiver_player_name, targets, tds, td_diff))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
# Calculate TD rate analysis
pass_plays = pbp[(pbp["play_type"] == "pass") & (pbp["receiver_player_id"].notna())]
td_analysis = (pass_plays.groupby(["receiver_player_id", "receiver_player_name"])
.agg(
targets=("play_id", "count"),
rz_targets=("yardline_100", lambda x: (x <= 20).sum()),
tds=("pass_touchdown", "sum")
)
.reset_index())
td_analysis = td_analysis[td_analysis["targets"] >= 50]
td_analysis["rz_rate"] = td_analysis["rz_targets"] / td_analysis["targets"]
td_analysis["expected_td_rate"] = 0.10 * td_analysis["rz_rate"]
td_analysis["actual_td_rate"] = td_analysis["tds"] / td_analysis["targets"]
td_analysis["td_diff"] = td_analysis["actual_td_rate"] - td_analysis["expected_td_rate"]
# Positive regression candidates
positive = td_analysis[td_analysis["td_diff"] < -0.03].sort_values("td_diff")
print("Positive Regression Candidates (Due for more TDs):")
print(positive[["receiver_player_name", "targets", "tds", "td_diff"]].head(10))
# Negative regression candidates
negative = td_analysis[td_analysis["td_diff"] > 0.03].sort_values("td_diff", ascending=False)
print("\nNegative Regression Candidates (TD rate unsustainable):")
print(negative[["receiver_player_name", "targets", "tds", "td_diff"]].head(10))
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# Red zone targets analysis
rz_targets <- pbp %>%
filter(play_type == "pass", yardline_100 <= 20, !is.na(receiver_player_id)) %>%
group_by(receiver_player_id, receiver_player_name, posteam) %>%
summarize(
rz_targets = n(),
rz_receptions = sum(complete_pass),
rz_tds = sum(pass_touchdown),
avg_depth = mean(air_yards, na.rm = TRUE),
.groups = "drop"
) %>%
mutate(
rz_catch_rate = rz_receptions / rz_targets * 100,
rz_td_rate = rz_tds / rz_targets * 100
) %>%
arrange(desc(rz_targets))
print("Top Red Zone Target Leaders:")
print(rz_targets %>% head(25))
# Inside 10 yard line
goalline <- pbp %>%
filter(play_type == "pass", yardline_100 <= 10, !is.na(receiver_player_id)) %>%
group_by(receiver_player_name) %>%
summarize(
gl_targets = n(),
gl_tds = sum(pass_touchdown),
.groups = "drop"
) %>%
arrange(desc(gl_targets))
print("\nGoal Line Target Leaders (Inside 10):")
print(goalline %>% head(15))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
# Red zone targets
rz_plays = pbp[(pbp["play_type"] == "pass") &
(pbp["yardline_100"] <= 20) &
(pbp["receiver_player_id"].notna())]
rz_targets = (rz_plays.groupby(["receiver_player_id", "receiver_player_name", "posteam"])
.agg(
rz_targets=("play_id", "count"),
rz_receptions=("complete_pass", "sum"),
rz_tds=("pass_touchdown", "sum"),
avg_depth=("air_yards", "mean")
)
.reset_index())
rz_targets["rz_catch_rate"] = rz_targets["rz_receptions"] / rz_targets["rz_targets"] * 100
rz_targets["rz_td_rate"] = rz_targets["rz_tds"] / rz_targets["rz_targets"] * 100
rz_targets = rz_targets.sort_values("rz_targets", ascending=False)
print("Top Red Zone Target Leaders:")
print(rz_targets.head(25))
# Goal line targets
gl_plays = pbp[(pbp["play_type"] == "pass") &
(pbp["yardline_100"] <= 10) &
(pbp["receiver_player_id"].notna())]
goalline = (gl_plays.groupby("receiver_player_name")
.agg(gl_targets=("play_id", "count"), gl_tds=("pass_touchdown", "sum"))
.reset_index()
.sort_values("gl_targets", ascending=False))
print("\nGoal Line Target Leaders:")
print(goalline.head(15))
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# Calculate fantasy points per game
fantasy_ppg <- pbp %>%
filter(!is.na(receiver_player_id) | !is.na(rusher_player_id)) %>%
mutate(
player_id = coalesce(receiver_player_id, rusher_player_id),
player_name = coalesce(receiver_player_name, rusher_player_name)
) %>%
group_by(player_id, player_name, week) %>%
summarize(
rush_yards = sum(rushing_yards, na.rm = TRUE),
rush_tds = sum(rush_touchdown, na.rm = TRUE),
receptions = sum(complete_pass, na.rm = TRUE),
rec_yards = sum(receiving_yards, na.rm = TRUE),
rec_tds = sum(touchdown[!is.na(receiver_player_id)], na.rm = TRUE),
fantasy_pts = rush_yards * 0.1 + rush_tds * 6 + receptions * 1 +
rec_yards * 0.1 + rec_tds * 6,
.groups = "drop"
)
# Calculate consistency metrics
consistency <- fantasy_ppg %>%
group_by(player_id, player_name) %>%
summarize(
games = n(),
avg_pts = mean(fantasy_pts),
std_pts = sd(fantasy_pts),
floor = quantile(fantasy_pts, 0.1),
ceiling = quantile(fantasy_pts, 0.9),
boom_rate = mean(fantasy_pts > 15) * 100, # "Boom" games
bust_rate = mean(fantasy_pts < 5) * 100, # "Bust" games
.groups = "drop"
) %>%
filter(games >= 8) %>%
mutate(
consistency_score = avg_pts / (std_pts + 1), # Higher is better
upside_score = ceiling / avg_pts # Higher ceiling relative to average
) %>%
arrange(desc(consistency_score))
print("Most Consistent Fantasy Performers:")
print(consistency %>% select(player_name, avg_pts, boom_rate, bust_rate, consistency_score) %>% head(20))
import nfl_data_py as nfl
import pandas as pd
import numpy as np
pbp = nfl.import_pbp_data([2023])
# Calculate weekly fantasy points
skill_plays = pbp[(pbp["receiver_player_id"].notna()) | (pbp["rusher_player_id"].notna())].copy()
skill_plays["player_id"] = skill_plays["receiver_player_id"].fillna(skill_plays["rusher_player_id"])
skill_plays["player_name"] = skill_plays["receiver_player_name"].fillna(skill_plays["rusher_player_name"])
weekly = (skill_plays.groupby(["player_id", "player_name", "week"])
.agg(
rush_yards=("rushing_yards", "sum"),
rush_tds=("rush_touchdown", "sum"),
receptions=("complete_pass", "sum"),
rec_yards=("receiving_yards", "sum"),
rec_tds=("pass_touchdown", "sum")
)
.reset_index())
weekly["fantasy_pts"] = (weekly["rush_yards"] * 0.1 + weekly["rush_tds"] * 6 +
weekly["receptions"] * 1 + weekly["rec_yards"] * 0.1 +
weekly["rec_tds"] * 6)
# Consistency metrics
consistency = (weekly.groupby(["player_id", "player_name"])
.agg(
games=("fantasy_pts", "count"),
avg_pts=("fantasy_pts", "mean"),
std_pts=("fantasy_pts", "std"),
floor=("fantasy_pts", lambda x: x.quantile(0.1)),
ceiling=("fantasy_pts", lambda x: x.quantile(0.9)),
boom_rate=("fantasy_pts", lambda x: (x > 15).mean() * 100),
bust_rate=("fantasy_pts", lambda x: (x < 5).mean() * 100)
)
.reset_index())
consistency = consistency[consistency["games"] >= 8]
consistency["consistency_score"] = consistency["avg_pts"] / (consistency["std_pts"] + 1)
consistency = consistency.sort_values("consistency_score", ascending=False)
print("Most Consistent Fantasy Performers:")
print(consistency[["player_name", "avg_pts", "boom_rate", "bust_rate", "consistency_score"]].head(20))
nflfastR
tidyverse
nfl_data_py
pandas
numpy
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
rosters <- fast_scraper_roster(2023)
# Get opportunity metrics
opportunities <- pbp %>%
filter(!is.na(receiver_player_id) | !is.na(rusher_player_id)) %>%
mutate(
player_id = coalesce(receiver_player_id, rusher_player_id),
player_name = coalesce(receiver_player_name, rusher_player_name),
is_target = !is.na(receiver_player_id),
is_carry = !is.na(rusher_player_id) & play_type == "run"
) %>%
group_by(player_id, player_name, posteam) %>%
summarize(
games = n_distinct(game_id),
targets = sum(is_target),
carries = sum(is_carry),
touches = targets + carries,
fantasy_pts = sum(rushing_yards, na.rm = TRUE) * 0.1 +
sum(rush_touchdown, na.rm = TRUE) * 6 +
sum(complete_pass, na.rm = TRUE) * 1 +
sum(receiving_yards, na.rm = TRUE) * 0.1 +
sum(pass_touchdown[is_target], na.rm = TRUE) * 6,
.groups = "drop"
) %>%
mutate(
touches_per_game = touches / games,
pts_per_touch = fantasy_pts / touches,
pts_per_game = fantasy_pts / games,
# Project remaining games (assuming 17-game season)
games_remaining = 17 - games,
projected_ros_pts = pts_per_game * games_remaining
) %>%
filter(games >= 5, touches >= 30) %>%
arrange(desc(projected_ros_pts))
print("Rest of Season Fantasy Rankings:")
print(opportunities %>%
select(player_name, games, pts_per_game, games_remaining, projected_ros_pts) %>%
head(30))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
# Get opportunity metrics
skill_plays = pbp[(pbp["receiver_player_id"].notna()) | (pbp["rusher_player_id"].notna())].copy()
skill_plays["player_id"] = skill_plays["receiver_player_id"].fillna(skill_plays["rusher_player_id"])
skill_plays["player_name"] = skill_plays["receiver_player_name"].fillna(skill_plays["rusher_player_name"])
skill_plays["is_target"] = skill_plays["receiver_player_id"].notna()
skill_plays["is_carry"] = (skill_plays["rusher_player_id"].notna()) & (skill_plays["play_type"] == "run")
opportunities = (skill_plays.groupby(["player_id", "player_name", "posteam"])
.agg(
games=("game_id", "nunique"),
targets=("is_target", "sum"),
carries=("is_carry", "sum"),
rush_yards=("rushing_yards", "sum"),
rush_tds=("rush_touchdown", "sum"),
receptions=("complete_pass", "sum"),
rec_yards=("receiving_yards", "sum"),
rec_tds=("pass_touchdown", "sum")
)
.reset_index())
opportunities["touches"] = opportunities["targets"] + opportunities["carries"]
opportunities["fantasy_pts"] = (opportunities["rush_yards"] * 0.1 +
opportunities["rush_tds"] * 6 +
opportunities["receptions"] * 1 +
opportunities["rec_yards"] * 0.1 +
opportunities["rec_tds"] * 6)
opportunities["pts_per_game"] = opportunities["fantasy_pts"] / opportunities["games"]
opportunities["games_remaining"] = 17 - opportunities["games"]
opportunities["projected_ros_pts"] = opportunities["pts_per_game"] * opportunities["games_remaining"]
opportunities = opportunities[(opportunities["games"] >= 5) & (opportunities["touches"] >= 30)]
opportunities = opportunities.sort_values("projected_ros_pts", ascending=False)
print("Rest of Season Fantasy Rankings:")
print(opportunities[["player_name", "games", "pts_per_game", "games_remaining", "projected_ros_pts"]].head(30))
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# Calculate player values
player_values <- pbp %>%
filter(!is.na(receiver_player_id) | !is.na(rusher_player_id)) %>%
mutate(
player_id = coalesce(receiver_player_id, rusher_player_id),
player_name = coalesce(receiver_player_name, rusher_player_name)
) %>%
group_by(player_id, player_name, posteam) %>%
summarize(
games = n_distinct(game_id),
fantasy_pts = sum(rushing_yards, na.rm = TRUE) * 0.1 +
sum(rush_touchdown, na.rm = TRUE) * 6 +
sum(complete_pass, na.rm = TRUE) * 1 +
sum(receiving_yards, na.rm = TRUE) * 0.1 +
sum(pass_touchdown[!is.na(receiver_player_id)], na.rm = TRUE) * 6,
.groups = "drop"
) %>%
mutate(ppg = fantasy_pts / games) %>%
filter(games >= 5)
# Create trade value scale (1-100)
player_values <- player_values %>%
mutate(
# Base value on PPG percentile
value_percentile = percent_rank(ppg),
# Scale to 1-100
trade_value = round(value_percentile * 100),
# Tier assignment
tier = case_when(
trade_value >= 90 ~ "Elite (Tier 1)",
trade_value >= 75 ~ "Star (Tier 2)",
trade_value >= 60 ~ "Starter (Tier 3)",
trade_value >= 40 ~ "Flex (Tier 4)",
TRUE ~ "Bench (Tier 5)"
)
) %>%
arrange(desc(trade_value))
print("Fantasy Trade Values:")
print(player_values %>%
select(player_name, ppg, trade_value, tier) %>%
head(40))
import nfl_data_py as nfl
import pandas as pd
import numpy as np
pbp = nfl.import_pbp_data([2023])
# Calculate player values
skill_plays = pbp[(pbp["receiver_player_id"].notna()) | (pbp["rusher_player_id"].notna())].copy()
skill_plays["player_id"] = skill_plays["receiver_player_id"].fillna(skill_plays["rusher_player_id"])
skill_plays["player_name"] = skill_plays["receiver_player_name"].fillna(skill_plays["rusher_player_name"])
player_values = (skill_plays.groupby(["player_id", "player_name", "posteam"])
.agg(
games=("game_id", "nunique"),
rush_yards=("rushing_yards", "sum"),
rush_tds=("rush_touchdown", "sum"),
receptions=("complete_pass", "sum"),
rec_yards=("receiving_yards", "sum"),
rec_tds=("pass_touchdown", "sum")
)
.reset_index())
player_values["fantasy_pts"] = (player_values["rush_yards"] * 0.1 +
player_values["rush_tds"] * 6 +
player_values["receptions"] * 1 +
player_values["rec_yards"] * 0.1 +
player_values["rec_tds"] * 6)
player_values["ppg"] = player_values["fantasy_pts"] / player_values["games"]
player_values = player_values[player_values["games"] >= 5]
# Create trade value scale
player_values["value_percentile"] = player_values["ppg"].rank(pct=True)
player_values["trade_value"] = (player_values["value_percentile"] * 100).round()
def get_tier(val):
if val >= 90: return "Elite (Tier 1)"
elif val >= 75: return "Star (Tier 2)"
elif val >= 60: return "Starter (Tier 3)"
elif val >= 40: return "Flex (Tier 4)"
else: return "Bench (Tier 5)"
player_values["tier"] = player_values["trade_value"].apply(get_tier)
player_values = player_values.sort_values("trade_value", ascending=False)
print("Fantasy Trade Values:")
print(player_values[["player_name", "ppg", "trade_value", "tier"]].head(40))
nflfastR
tidyverse
nfl_data_py
pandas
numpy
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
schedules <- load_schedules(2023)
# Calculate defense vs position stats
def_vs_rb <- pbp %>%
filter(play_type == "run", !is.na(epa)) %>%
group_by(defteam) %>%
summarize(
plays = n(),
rush_epa_allowed = mean(epa),
rush_ypc_allowed = mean(yards_gained),
rush_td_allowed = sum(rush_touchdown),
.groups = "drop"
) %>%
mutate(
rb_rank = rank(rush_epa_allowed) # Lower EPA = better defense
)
def_vs_wr <- pbp %>%
filter(play_type == "pass", !is.na(epa)) %>%
group_by(defteam) %>%
summarize(
plays = n(),
pass_epa_allowed = mean(epa),
ypa_allowed = mean(yards_gained),
pass_td_allowed = sum(pass_touchdown),
.groups = "drop"
) %>%
mutate(
wr_rank = rank(pass_epa_allowed) # Lower EPA = better defense
)
# Combine defensive rankings
def_rankings <- def_vs_rb %>%
select(defteam, rush_epa_allowed, rb_rank) %>%
left_join(def_vs_wr %>% select(defteam, pass_epa_allowed, wr_rank), by = "defteam") %>%
mutate(
rb_matchup = case_when(
rb_rank <= 8 ~ "Tough",
rb_rank <= 24 ~ "Average",
TRUE ~ "Favorable"
),
wr_matchup = case_when(
wr_rank <= 8 ~ "Tough",
wr_rank <= 24 ~ "Average",
TRUE ~ "Favorable"
)
)
print("Defensive Matchup Rankings:")
print(def_rankings %>% arrange(rb_rank))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
schedules = nfl.import_schedules([2023])
# Defense vs RB
rushes = pbp[(pbp["play_type"] == "run") & (pbp["epa"].notna())]
def_vs_rb = (rushes.groupby("defteam")
.agg(
plays=("epa", "count"),
rush_epa_allowed=("epa", "mean"),
rush_ypc=("yards_gained", "mean"),
rush_td=("rush_touchdown", "sum")
)
.reset_index())
def_vs_rb["rb_rank"] = def_vs_rb["rush_epa_allowed"].rank()
# Defense vs WR
passes = pbp[(pbp["play_type"] == "pass") & (pbp["epa"].notna())]
def_vs_wr = (passes.groupby("defteam")
.agg(
plays=("epa", "count"),
pass_epa_allowed=("epa", "mean"),
ypa=("yards_gained", "mean"),
pass_td=("pass_touchdown", "sum")
)
.reset_index())
def_vs_wr["wr_rank"] = def_vs_wr["pass_epa_allowed"].rank()
# Combine
def_rankings = def_vs_rb[["defteam", "rush_epa_allowed", "rb_rank"]].merge(
def_vs_wr[["defteam", "pass_epa_allowed", "wr_rank"]], on="defteam")
def rb_matchup(rank):
if rank <= 8: return "Tough"
elif rank <= 24: return "Average"
else: return "Favorable"
def_rankings["rb_matchup"] = def_rankings["rb_rank"].apply(rb_matchup)
def_rankings["wr_matchup"] = def_rankings["wr_rank"].apply(rb_matchup)
print("Defensive Matchup Rankings:")
print(def_rankings.sort_values("rb_rank"))
nflfastR
tidyverse
nfl_data_py
pandas
library(nflfastR)
library(tidyverse)
pbp <- load_pbp(2023)
# Find players with increasing opportunity
weekly_touches <- pbp %>%
filter(!is.na(receiver_player_id) | !is.na(rusher_player_id)) %>%
mutate(
player_id = coalesce(receiver_player_id, rusher_player_id),
player_name = coalesce(receiver_player_name, rusher_player_name),
is_touch = TRUE
) %>%
group_by(player_id, player_name, week, posteam) %>%
summarize(
touches = n(),
fantasy_pts = sum(rushing_yards, na.rm = TRUE) * 0.1 +
sum(rush_touchdown, na.rm = TRUE) * 6 +
sum(complete_pass, na.rm = TRUE) * 1 +
sum(receiving_yards, na.rm = TRUE) * 0.1 +
sum(pass_touchdown[!is.na(receiver_player_id)], na.rm = TRUE) * 6,
.groups = "drop"
)
# Calculate trend (last 3 weeks vs first 3 weeks)
max_week <- max(weekly_touches$week)
trending <- weekly_touches %>%
mutate(
period = case_when(
week >= max_week - 2 ~ "Recent",
week <= 3 ~ "Early",
TRUE ~ "Middle"
)
) %>%
filter(period %in% c("Recent", "Early")) %>%
group_by(player_id, player_name, posteam, period) %>%
summarize(
avg_touches = mean(touches),
avg_pts = mean(fantasy_pts),
.groups = "drop"
) %>%
pivot_wider(names_from = period, values_from = c(avg_touches, avg_pts)) %>%
filter(!is.na(avg_touches_Recent), !is.na(avg_touches_Early)) %>%
mutate(
touch_trend = avg_touches_Recent - avg_touches_Early,
pts_trend = avg_pts_Recent - avg_pts_Early
) %>%
filter(avg_pts_Recent < 15) %>% # Filter out established stars
arrange(desc(touch_trend))
print("Waiver Wire Gems (Increasing Opportunity):")
print(trending %>%
select(player_name, avg_touches_Early, avg_touches_Recent, touch_trend, pts_trend) %>%
head(15))
import nfl_data_py as nfl
import pandas as pd
pbp = nfl.import_pbp_data([2023])
# Get weekly touches
skill_plays = pbp[(pbp["receiver_player_id"].notna()) | (pbp["rusher_player_id"].notna())].copy()
skill_plays["player_id"] = skill_plays["receiver_player_id"].fillna(skill_plays["rusher_player_id"])
skill_plays["player_name"] = skill_plays["receiver_player_name"].fillna(skill_plays["rusher_player_name"])
weekly = (skill_plays.groupby(["player_id", "player_name", "week", "posteam"])
.agg(
touches=("play_id", "count"),
rush_yards=("rushing_yards", "sum"),
rush_tds=("rush_touchdown", "sum"),
receptions=("complete_pass", "sum"),
rec_yards=("receiving_yards", "sum"),
rec_tds=("pass_touchdown", "sum")
)
.reset_index())
weekly["fantasy_pts"] = (weekly["rush_yards"] * 0.1 + weekly["rush_tds"] * 6 +
weekly["receptions"] * 1 + weekly["rec_yards"] * 0.1 +
weekly["rec_tds"] * 6)
# Calculate trends
max_week = weekly["week"].max()
early = weekly[weekly["week"] <= 3].groupby(["player_id", "player_name"]).agg(
avg_touches_early=("touches", "mean"),
avg_pts_early=("fantasy_pts", "mean")
).reset_index()
recent = weekly[weekly["week"] >= max_week - 2].groupby(["player_id", "player_name"]).agg(
avg_touches_recent=("touches", "mean"),
avg_pts_recent=("fantasy_pts", "mean")
).reset_index()
trending = early.merge(recent, on=["player_id", "player_name"])
trending["touch_trend"] = trending["avg_touches_recent"] - trending["avg_touches_early"]
trending["pts_trend"] = trending["avg_pts_recent"] - trending["avg_pts_early"]
# Filter out established stars
trending = trending[trending["avg_pts_recent"] < 15]
trending = trending.sort_values("touch_trend", ascending=False)
print("Waiver Wire Gems (Increasing Opportunity):")
print(trending[["player_name", "avg_touches_early", "avg_touches_recent", "touch_trend", "pts_trend"]].head(15))
nflfastR
tidyverse
nfl_data_py
pandas
nflfastR - Play-by-play data with EPAnflplotR - NFL team logos & plottingtidyverse - Data manipulation & visualizationggplot2 - Advanced visualizationsnfl_data_py - NFL data (nflverse compatible)pandas - Data manipulationmatplotlib - Visualizationsscikit-learn - Machine learningLearn the theory behind these techniques in our comprehensive tutorial series
Browse Tutorials