“I’ve always said that LeBron is a physical marvel, but Michael Jordan is the ultimate competitor.”
– Charles Barkley

“Kobe Bryant had an unmatched work ethic, but LeBron’s versatility changes the game.” – Shaquille O’Neal

1 🏀 Introduction

The debate about who is the greatest basketball player of all time (the “GOAT”) has fueled endless discussions among fans, analysts, and players alike.

This report focuses on a statistical comparison of three legends of the game:

Using season and playoff statistics, this analysis compares their scoring, efficiency, accolades, and career trajectories.
The goal is not to provide a definitive answer, but to highlight trends and achievements that inform the GOAT conversation.

data collected from Basketballreference.com

Lets take a look at the data referenced from Basketballreference.com

str(jordan_full_season)
## 'data.frame':    15 obs. of  18 variables:
##  $ season      : chr  "1984-85" "1985-86" "1986-87" "1987-88" ...
##  $ games_played: int  82 18 82 82 81 82 82 80 78 17 ...
##  $ fg_percent  : num  0.515 0.457 0.482 0.535 0.538 0.526 0.539 0.519 0.495 0.411 ...
##  $ fg_3percent : num  0.173 0.167 0.182 0.132 0.276 0.376 0.312 0.27 0.352 0.5 ...
##  $ fg_2percent : num  0.526 0.474 0.491 0.546 0.553 0.548 0.551 0.533 0.514 0.403 ...
##  $ e_fg_percent: num  0.518 0.462 0.484 0.537 0.546 0.55 0.547 0.526 0.515 0.431 ...
##  $ ft_percent  : num  0.845 0.84 0.857 0.841 0.85 0.848 0.851 0.832 0.837 0.801 ...
##  $ apg         : num  5.9 2.9 4.6 5.9 8 6.3 5.5 6.1 5.5 5.3 ...
##  $ rpg         : num  6.5 3.6 5.2 5.5 8 6.9 6 6.4 6.7 6.9 ...
##  $ spg         : num  2.4 2.1 2.9 3.2 2.9 2.8 2.7 2.3 2.8 1.8 ...
##  $ bpg         : num  0.8 1.2 1.5 1.6 0.8 0.7 1 0.9 0.8 0.8 ...
##  $ ppg         : num  28.2 22.7 37.1 35 32.5 33.6 31.5 30.1 32.6 26.9 ...
##  $ game_high   : int  49 33 61 59 53 69 46 51 64 55 ...
##  $ ts_percent  : num  0.592 0.533 0.562 0.603 0.614 0.606 0.605 0.579 0.564 0.493 ...
##  $ per         : num  25.8 27.5 29.8 31.7 31.1 31.2 31.6 27.7 29.7 22.1 ...
##  $ vorp        : num  7.4 1.3 10.6 12.5 11.4 10.6 10.8 9.2 10.2 1.1 ...
##  $ ws          : num  14 1.5 16.9 21.2 19.8 19 20.3 17.7 17.2 2.3 ...
##  $ bpm         : num  7.3 9.7 10.8 13 11.9 11.2 12 9.7 11.2 4.2 ...

#Jordan Full Season

str(jordan_full_playoffs)
## 'data.frame':    13 obs. of  18 variables:
##  $ season      : chr  "1984-85" "1985-86" "1986-87" "1987-88" ...
##  $ games_played: int  4 3 3 10 17 16 17 22 19 10 ...
##  $ fg_percent  : num  0.436 0.505 0.417 0.531 0.51 0.514 0.524 0.499 0.475 0.484 ...
##  $ fg_3percent : num  0.125 1 0.4 0.333 0.286 0.32 0.385 0.386 0.389 0.367 ...
##  $ fg_2perent  : num  0.471 0.5 0.418 0.533 0.532 0.54 0.534 0.508 0.489 0.5 ...
##  $ e_fg_percent: num  0.442 0.511 0.429 0.533 0.523 0.533 0.537 0.514 0.502 0.506 ...
##  $ ft_percent  : num  0.828 0.872 0.897 0.869 0.799 0.836 0.845 0.857 0.805 0.81 ...
##  $ apg         : num  8.5 5.7 6 4.7 7.6 6.8 8.4 5.8 6 4.5 ...
##  $ rpg         : num  5.8 6.3 7 7.1 7 7.2 6.4 6.2 6.7 6.5 ...
##  $ spg         : num  2.8 2.3 2 2.4 2.5 2.8 2.4 2 2.1 2.3 ...
##  $ bpg         : num  1 1.3 2.3 1.1 0.8 0.9 1.4 0.7 0.9 1.4 ...
##  $ ppg         : num  29.3 43.7 35.7 36.3 34.8 36.7 31.1 34.5 35.1 31.5 ...
##  $ game_high   : int  35 63 42 55 50 49 46 56 55 48 ...
##  $ ts_percent  : num  0.565 0.584 0.529 0.598 0.602 0.592 0.6 0.571 0.553 0.557 ...
##  $ per         : num  24.7 30.1 28.1 28.4 29.9 31.7 32 27.2 30.1 24.8 ...
##  $ vorp        : num  0.5 0.4 0.5 1.5 2.5 2.7 2.9 2.8 2.7 1 ...
##  $ ws          : num  0.7 0.5 0.4 2.1 4 4 4.8 4.1 4.4 1.3 ...
##  $ bpm         : num  9.5 11.9 12.7 12.2 12.1 13.7 14.6 9.9 11.6 8 ...

#Jordan Full Playoffs

str(lebron_full_season)
## 'data.frame':    22 obs. of  18 variables:
##  $ season       : chr  "2003-04" "2004-05" "2005-06" "2006-07" ...
##  $ games_played : int  79 80 79 78 75 81 76 79 62 76 ...
##  $ fg_percent   : num  0.417 0.472 0.48 0.476 0.484 0.489 0.503 0.51 0.531 0.565 ...
##  $ fg_3percent  : num  0.29 0.351 0.335 0.319 0.315 0.344 0.333 0.33 0.362 0.406 ...
##  $ fg_2percent  : num  0.438 0.499 0.518 0.513 0.531 0.535 0.56 0.552 0.556 0.602 ...
##  $ e_fg_perecent: num  0.438 0.504 0.515 0.507 0.518 0.53 0.545 0.541 0.554 0.603 ...
##  $ ft_percent   : num  0.754 0.75 0.738 0.698 0.712 0.78 0.767 0.759 0.771 0.753 ...
##  $ rpg          : num  5.5 7.4 7 6.7 7.9 7.6 7.3 7.5 7.9 8 ...
##  $ apg          : num  5.9 7.2 6.6 6 7.2 7.2 8.6 7 6.2 7.3 ...
##  $ spg          : num  1.6 2.2 1.6 1.6 1.8 1.7 1.6 1.6 1.9 1.7 ...
##  $ bpg          : num  0.7 0.7 0.8 0.7 1.1 1.1 1 0.6 0.8 0.9 ...
##  $ ppg          : num  20.9 27.2 31.4 27.3 30 28.4 29.7 26.7 27.1 26.8 ...
##  $ game_high    : int  41 56 52 41 51 55 48 51 41 40 ...
##  $ per          : num  18.3 25.7 28.1 24.5 29.1 31.7 31.1 27.3 30.7 31.6 ...
##  $ ts_percent   : num  0.488 0.554 0.568 0.552 0.568 0.591 0.604 0.594 0.605 0.64 ...
##  $ ws           : num  5.1 14.3 16.3 13.7 15.2 20.3 18.5 15.6 14.5 19.3 ...
##  $ bpm          : num  1.7 8.6 9.1 8.1 10.9 13.2 11.8 8.1 10.9 11.7 ...
##  $ vorp         : num  2.9 9.1 9.4 8.1 9.8 11.8 10.3 7.8 7.6 9.9 ...

#Lebron Full Season

str(lebron_full_playoffs)
## 'data.frame':    18 obs. of  18 variables:
##  $ season      : chr  "2005-06" "2006-07" "2007-08" "2008-09" ...
##  $ games_played: int  13 20 13 14 11 21 23 23 20 20 ...
##  $ fg_percent  : num  0.476 0.416 0.411 0.51 0.502 0.466 0.5 0.491 0.565 0.417 ...
##  $ fg_3percent : num  0.333 0.28 0.257 0.333 0.4 0.353 0.259 0.375 0.407 0.227 ...
##  $ fg_2percent : num  0.512 0.448 0.463 0.571 0.534 0.5 0.549 0.524 0.618 0.465 ...
##  $ e_fg_percent: num  0.51 0.442 0.444 0.553 0.55 0.507 0.522 0.532 0.616 0.44 ...
##  $ ft_percent  : num  0.737 0.755 0.731 0.749 0.733 0.763 0.739 0.777 0.806 0.731 ...
##  $ apg         : num  5.8 8 7.6 7.3 7.6 5.9 5.6 6.6 4.8 8.5 ...
##  $ rpg         : num  8.1 8.1 7.8 9.1 9.3 8.4 9.7 8.4 7.1 11.3 ...
##  $ spg         : num  1.4 1.7 1.8 1.6 1.7 1.7 1.9 1.8 1.8 1.7 ...
##  $ bpg         : num  0.7 0.5 1.3 0.9 1.8 1.2 0.7 0.8 0.6 1.1 ...
##  $ ppg         : num  30.8 25.1 28.2 35.3 29.1 23.7 30.3 25.9 27.4 30.1 ...
##  $ game_high   : int  45 48 45 49 40 35 45 37 49 44 ...
##  $ ts_percent  : num  0.557 0.516 0.525 0.618 0.607 0.563 0.576 0.585 0.668 0.487 ...
##  $ per         : num  23.2 23.9 24.3 37.4 28.6 23.7 30.3 28.1 31 25.3 ...
##  $ vorp        : num  1.4 2.2 1.7 2.9 1.6 2.1 3.1 3 2.4 2.1 ...
##  $ ws          : num  1.7 3.7 2.2 4.8 2.3 3.8 5.8 5.2 4.3 3 ...
##  $ bpm         : num  7.5 8.1 10.1 17.5 11.5 7.1 10.5 10.4 10.3 7.9 ...

#Lebron Full Playoffs

str(kobe_full_season)
## 'data.frame':    20 obs. of  18 variables:
##  $ season      : chr  "1996-97" "1997-98" "1998-99" "1999-00" ...
##  $ games_played: int  71 79 50 66 68 80 82 65 66 80 ...
##  $ fg_percent  : num  0.417 0.428 0.465 0.468 0.464 0.469 0.451 0.438 0.433 0.45 ...
##  $ fg_3percent : num  0.375 0.341 0.267 0.319 0.305 0.25 0.383 0.327 0.339 0.347 ...
##  $ fg_2percent : num  0.437 0.456 0.494 0.489 0.489 0.489 0.465 0.463 0.472 0.482 ...
##  $ e_fg_percent: num  0.477 0.469 0.482 0.488 0.484 0.479 0.483 0.468 0.482 0.491 ...
##  $ ft_percent  : num  0.819 0.794 0.839 0.821 0.853 0.829 0.843 0.852 0.816 0.85 ...
##  $ apg         : num  1.3 2.5 3.8 4.9 5 5.5 5.9 5.1 6 4.5 ...
##  $ rpg         : num  1.9 3.1 5.3 6.3 5.9 5.5 6.9 5.5 5.9 5.3 ...
##  $ spg         : num  0.7 0.9 1.4 1.6 1.7 1.5 2.2 1.7 1.3 1.8 ...
##  $ bpg         : num  0.3 0.5 1 0.9 0.6 0.4 0.8 0.4 0.8 0.4 ...
##  $ ppg         : num  7.6 15.4 19.9 22.5 28.5 25.2 30 24 27.6 35.4 ...
##  $ game_high   : int  24 33 38 40 51 56 55 45 48 81 ...
##  $ ts_percent  : num  0.544 0.548 0.549 0.546 0.552 0.544 0.55 0.551 0.563 0.559 ...
##  $ per         : num  14.4 18.5 18.9 21.7 24.5 23.2 26.2 23.7 23.3 28 ...
##  $ vorp        : num  0.5 1.8 1.9 4.5 4.7 5.2 7.7 4.7 4.7 8 ...
##  $ ws          : num  1.8 6.3 5.2 10.6 11.3 12.7 14.9 10.7 8.1 15.3 ...
##  $ bpm         : num  -0.1 1.4 2.1 5.1 4.8 4.6 7.1 5.6 5 7.6 ...

#Kobe Full Season

str(kobe_full_playoffs)
## 'data.frame':    15 obs. of  18 variables:
##  $ season      : chr  "1996-97" "1997-98" "1998-99" "1999-00" ...
##  $ games_played: int  9 11 8 22 16 19 12 22 7 5 ...
##  $ fg_percent  : num  0.382 0.408 0.43 0.442 0.469 0.434 0.432 0.413 0.497 0.462 ...
##  $ fg_3percent : num  0.261 0.214 0.348 0.344 0.324 0.379 0.403 0.247 0.4 0.357 ...
##  $ fg_2percent : num  0.469 0.452 0.445 0.461 0.485 0.442 0.439 0.457 0.527 0.49 ...
##  $ e_fg_percent: num  0.436 0.428 0.458 0.47 0.485 0.459 0.472 0.439 0.545 0.5 ...
##  $ ft_percent  : num  0.867 0.689 0.8 0.754 0.821 0.759 0.827 0.813 0.771 0.919 ...
##  $ apg         : num  1.2 1.5 4.6 4.4 6.1 4.6 5.2 5.5 5.1 4.4 ...
##  $ rpg         : num  1.2 1.9 6.9 4.5 7.3 5.8 5.1 4.7 6.3 5.2 ...
##  $ spg         : num  0.3 0.3 1.9 1.5 1.6 1.4 1.2 1.9 1.1 1 ...
##  $ bpg         : num  0.2 0.7 1.3 1.5 0.8 0.9 0.1 0.3 0.4 0.4 ...
##  $ ppg         : num  8.2 8.7 19.8 21.1 29.4 26.6 32.1 24.5 27.9 32.8 ...
##  $ game_high   : int  22 22 28 35 48 36 39 42 50 45 ...
##  $ ts_percent  : num  0.543 0.501 0.502 0.517 0.555 0.511 0.531 0.506 0.587 0.561 ...
##  $ per         : num  12.5 12.8 19.1 19.3 25 20.5 22.2 21 19.9 24.1 ...
##  $ vorp        : num  0 0 0.5 1.4 1.5 1.3 0.8 1.8 0.3 0.4 ...
##  $ ws          : num  0.1 0.2 0.4 2.1 3.8 2.6 1.5 2.9 0.6 0.5 ...
##  $ bpm         : num  -1.7 -2.2 3.9 4.2 6.5 4.3 3.7 5.3 2.4 6.1 ...

#Kobe Full Playoffs

str(legends_accomplishments)
## 'data.frame':    3 obs. of  7 variables:
##  $ player          : chr  "Michael Jordan" "Kobe Bryant" "LeBron James"
##  $ titles_won      : int  6 5 4
##  $ finals_MVP      : int  6 2 4
##  $ MVP_award       : int  5 1 4
##  $ All_NBA_team    : int  11 15 19
##  $ All_NBA_def_team: int  9 12 6
##  $ scoring_titles  : int  10 2 1

#Legends Accomplishments

str(rookie_stats)
## 'data.frame':    3 obs. of  20 variables:
##  $ season      : chr  "1984-85" "2003-04" "1996-97"
##  $ games_played: int  82 79 71
##  $ fg_percent  : num  0.515 0.417 0.417
##  $ fg_3percent : num  0.173 0.29 0.375
##  $ fg_2percent : num  0.526 0.438 0.437
##  $ e_fg_percent: num  0.518 0.438 0.477
##  $ ft_percent  : num  0.845 0.754 0.819
##  $ apg         : num  5.9 5.9 1.3
##  $ rpg         : num  6.5 5.5 1.9
##  $ spg         : num  2.4 1.6 0.7
##  $ blk         : num  0.8 NA NA
##  $ ppg         : num  28.2 20.9 7.6
##  $ game_high   : int  49 41 24
##  $ ts_percent  : num  0.592 0.488 0.544
##  $ per         : num  25.8 18.3 14.4
##  $ vorp        : num  7.4 2.9 0.5
##  $ ws          : num  14 5.1 1.8
##  $ bpm         : num  7.3 1.7 -0.1
##  $ player      : chr  "Jordan" "LeBron" "Kobe"
##  $ bpg         : num  NA 0.7 0.3

2 🏀 Glossary

Lets take a minute to take a look at some key metrics and the what they mean. Here is a glossary of the key metrics that we will be using for this analysis.

Terms

Term Meaning
season Season
games_played Games Played during season/playoffs
fg_percent Field Goal Percentage
fg_3percent 3-Point Field Goal Percentage
fg_2percent 2-Point Field Goal Percentage
e_fg_percent Effective Field Goal Percentage
ft_percent Free Throw Percentage
game_high Game high during season/playoffs
apg Assists Per Game
rpg Rebounds Per Game.
bpg Blocks Per Game
spg Steals Per Game
ppg Points per Game

Here are some other advanced terms that we will be using for our analysis;

Advanced Terms

Term Meaning Explanation
ts_percent True Shooting Percentage A measure of shooting efficiency that takes into account 2-point field goals, 3-point field goals, and free throws.
per Player Efficiency Rating A measure of per-minute production standardized such that the league average is 15
vorp Value over Replacement Player A box score estimate of the points per 100 TEAM possessions that a player contributed above a replacement-level.
ws Win Shares An estimate of the number of wins contributed by a player.
bpm Box Plus/Minus A box score of the points per 100 possessions a player contributed over a average player, translated to an average team.

3 🏀📊 Rookie Season Analysis 📊🏀

3.1 Rookie Season Shooting Efficiency Analysis (bar graph)

The interactive chart allows us to compare the rookie-season shooting efficiency of Michael Jordan, Kobe Bryant, and LeBron James across four key metrics:

  • fg_percent (Field Goal Percentage)
  • ft_percent (Free Throw Percentage)
  • fg_3percent (3 Point Percentage)
  • ts_percent (True Shooting Percentage)

The data for each player is filtered to their rookie season: - Michael Jordan → 1984–85
- Kobe Bryant → 1996–97
- LeBron James → 2003–04

library(dplyr)
library(tidyr)
library(ggplot2)
library(ggimage)
library(ggiraph)

# Prepare data with player images
rookie_stats_images <- rookie_stats %>%
  mutate(player = factor(player, levels = c("Jordan", "Kobe", "LeBron"))) %>%
  mutate(image = case_when(
    player == "Jordan" ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jordami01.jpg",
    player == "Kobe"   ~ "https://www.basketball-reference.com/req/202106291/images/headshots/bryanko01.jpg",
    player == "LeBron" ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jamesle01.jpg"
  ))

# Create interactive plot
p <- rookie_stats_images %>%
  select(player, image, fg_percent, fg_3percent, ft_percent, ts_percent) %>%
  pivot_longer(
    cols = c(fg_percent, fg_3percent, ft_percent, ts_percent),
    names_to = "metric", values_to = "value"
  ) %>%
  mutate(metric = case_when(
    metric == "fg_percent"  ~ "FG%",
    metric == "fg_3percent" ~ "3P%",
    metric == "ft_percent"  ~ "FT%",
    metric == "ts_percent"  ~ "TS%"
  )) %>%
  ggplot(aes(x = metric, y = value, fill = player)) +
  geom_col_interactive(
    aes(tooltip = paste0(player, ": ", round(value * 100, 1), "%")),
    position = position_dodge(width = 0.8),
    width = 0.7
  ) +
  geom_image(
    aes(image = image, y = value + 0.06, group = player),
    position = position_dodge(width = 0.8),
    size = 0.06
  ) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.25))) +
  labs(
    title = "Rookie Shooting Efficiency",
    x = "Metric",
    y = "Percentage"
  ) +
  theme_minimal(base_size = 14) +
  theme(
    axis.text.x = element_text(angle = 0, hjust = 0.5),
    legend.title = element_blank(),
    plot.title = element_text(face = "bold", hjust = 0.5)
  )

# Render interactive plot in HTML
girafe(
  ggobj = p,
  width_svg = 8,
  height_svg = 6,
  options = list(
    opts_hover(css = "fill-opacity:0.7;cursor:pointer;"),
    opts_tooltip(css = "background-color:white;color:black;padding:5px;border-radius:5px;")
  )
)

Michael Jordan stands out with the highest overall efficiency as a rookie. His FG% and TS% are the highest, showing he scored effectively both inside and outside the paint. His 3P% and FT% indicate solid performance from long-range and the free-throw line as well.

Kobe Bryant shows a moderate rookie efficiency. His FG% and TS% are slightly lower than Jordan’s, reflecting a typical adjustment period for a rookie. His 3P% is shorter, but his FT% demonstrates reliable scoring from the line.

LeBron James exhibits strong finishing ability, as reflected by his high FG% and TS%, particularly in close-range shots. His 3P% bar is the lowest among the three, indicating that perimeter shooting was not yet a major strength as a rookie. His FT% is consistent, suggesting steady scoring in controlled situations.

3.2 Rookie Regular Season Per Games Stats (bar graph)

Here we take a look at each players debut season as a Rookie in the NBA. Here are some key per game statistics that we will be comparing;

  • PPG (Points per Game)
  • RPG (Rebounds per Game)
  • APG (Assists per Game)
  • SPG (Steals per Game)
  • BPG (Blocks per Game)

The data for each player is filtered to their rookie season:

  • Michael Jordan → 1984–85
  • Kobe Bryant → 1996–97
  • LeBron James → 2003–04
## Rookie Regular Season Per Game Stats

library(dplyr)
library(tidyr)
library(ggplot2)
library(ggimage)
library(ggiraph)

# Extract rookie regular season for each player
jordan_rookie_reg <- jordan_full_season %>%
  filter(season == "1984-85") %>%   # Jordan's rookie year
  mutate(player = "Michael Jordan") %>%
  select(player, ppg, rpg, apg, spg, bpg)

kobe_rookie_reg <- kobe_full_season %>%
  filter(season == "1996-97") %>%   # Kobe's rookie year
  mutate(player = "Kobe Bryant") %>%
  select(player, ppg, rpg, apg, spg, bpg)

lebron_rookie_reg <- lebron_full_season %>%
  filter(season == "2003-04") %>%   # LeBron's rookie year
  mutate(player = "LeBron James") %>%
  select(player, ppg, rpg, apg, spg, bpg)

# Combine
rookie_regular <- bind_rows(jordan_rookie_reg, kobe_rookie_reg, lebron_rookie_reg)

# Add images
rookie_regular <- rookie_regular %>%
  mutate(image = case_when(
    player == "Michael Jordan" ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jordami01.jpg",
    player == "Kobe Bryant"    ~ "https://www.basketball-reference.com/req/202106291/images/headshots/bryanko01.jpg",
    player == "LeBron James"   ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jamesle01.jpg"
  ))

# Pivot
rookie_regular_long <- rookie_regular %>%
  pivot_longer(cols = c(ppg, rpg, apg, spg, bpg),
               names_to = "stat", values_to = "value")

# Plot
p_regular <- ggplot(rookie_regular_long, aes(x = stat, y = value, fill = player)) +
  geom_col_interactive(
    aes(tooltip = paste0(player, ": ", round(value, 1))),
    position = position_dodge(width = 0.9), width = 0.9
  ) +
  geom_image(
    aes(image = image, y = value + 1.5, group = player),
    position = position_dodge(width = 0.9), size = 0.05
  ) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.25))) +
  labs(title = "Rookie Regular Season Per-Game Stats",
       x = "Statistic", y = "Per-Game Value") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 0, hjust = 0.5),
        legend.title = element_blank(),
        plot.title = element_text(face = "bold", hjust = 0.5))

# Render interactive plot
girafe(ggobj = p_regular, width_svg = 8, height_svg = 6)

Michael Jordan clearly dominated as a rookie. Over the course of the full 82-game season, he averaged nearly 30 points per game, an extraordinary mark for a first-year player. Despite being slightly smaller than LeBron (6’6” vs. 6’8”), Jordan also managed to secure more rebounds per game, showcasing his tenacity and athleticism.

LeBron James, while not matching Jordan’s scoring, still had an outstanding rookie campaign. Averaging 21 points, 6 assists, 5 rebounds, and 2 steals per game, he demonstrated remarkable balance across all facets of the game. His performance was an early indication of the all-around dominance that would define his career.

Kobe Bryant, on the other hand, had a much more modest rookie season. Averaging just 7 points per game, his limited production was largely a result of restricted playing time behind established All-Stars like Shaquille O’Neal and Eddie Jones. Still, flashes of his scoring ability hinted at the superstar he would eventually become.

3.3 Rookie Playoff Per Game Stats (bar graph)

Now, lets highlight some of their early strengths and playing styles during the playoffs. This visualization compares the rookie playoff performance of Michael Jordan, Kobe Bryant, and LeBron James across key per-game statistics:

  • PPG (Points per Game)
  • RPG (Rebounds per Game)
  • APG (Assists per Game)
  • SPG (Steals per Game)
  • BPG (Blocks per Game)

The data for each player is filtered to their rookie playoff season: - Michael Jordan → 1984–85
- Kobe Bryant → 1996–97
- LeBron James → 2005–06

library(dplyr)
library(tidyr)
library(ggplot2)
library(ggiraph)
library(ggimage)

# Extract rookie playoff stats
jordan_rookie <- jordan_full_playoffs %>%
  filter(season == "1984-85") %>%
  mutate(player = "Michael Jordan") %>%
  select(player, ppg, rpg, apg, spg, bpg)

kobe_rookie <- kobe_full_playoffs %>%
  filter(season == "1996-97") %>%
  mutate(player = "Kobe Bryant") %>%
  select(player, ppg, rpg, apg, spg, bpg)

lebron_rookie <- lebron_full_playoffs %>%
  filter(season == "2005-06") %>%
  mutate(player = "LeBron James") %>%
  select(player, ppg, rpg, apg, spg, bpg)

# Combine data
rookie_playoffs <- bind_rows(jordan_rookie, kobe_rookie, lebron_rookie)

# Add player headshots
rookie_playoffs <- rookie_playoffs %>%
  mutate(image = case_when(
    player == "Michael Jordan" ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jordami01.jpg",
    player == "Kobe Bryant"    ~ "https://www.basketball-reference.com/req/202106291/images/headshots/bryanko01.jpg",
    player == "LeBron James"   ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jamesle01.jpg"
  ))

# Pivot data longer
rookie_playoffs_per_game <- rookie_playoffs %>%
  pivot_longer(
    cols = c(ppg, rpg, apg, spg, bpg),
    names_to = "stat", values_to = "value"
  )

# Build interactive plot
p_playoffs_per_game <- ggplot(rookie_playoffs_per_game, aes(x = stat, y = value, fill = player)) +
  geom_col_interactive(
    aes(tooltip = paste0(player, ": ", round(value, 1))),
    position = position_dodge(width = 0.9), width = 0.9
  ) +
  geom_image(
    aes(image = image, y = value + 1.5, group = player),
    position = position_dodge(width = 0.8), 
    size = 0.05
  ) +
  scale_y_continuous(expand = expansion(mult = c(0, 0.25))) +
  labs(title = "Rookie Playoff Per-Game Stats",
       x = "Statistic", y = "Per-Game Value") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 0, hjust = 0.5),
        legend.title = element_blank(),
        plot.title = element_text(face = "bold", hjust = 0.5))

# Render interactive plot
girafe(ggobj = p_playoffs_per_game, width_svg = 8, height_svg = 6)

Michael Jordan exploded onto the playoff stage, averaging nearly 30 PPG, which was nothing short of the standard he would go on to set throughout his career. While his other numbers were solid, it was his scoring contribution that stood out the most.

Kobe Bryant had a more modest rookie playoff performance, averaging just 9 PPG while playing behind All-Stars like Eddie Jones and Shaquille O’Neal. His lower numbers were more a reflection of his limited role and playing time rather than his talent.

LeBron James, unlike Kobe, entered the playoffs as the clear centerpiece for the Cavaliers. With an explosive scoring performance comparable to Jordan’s, LeBron paired his offense with a more balanced stat line across rebounds and assists. He also played the most games of the three, making his rookie playoff run especially impressive.

From this comparison what I can say about this rookie analysis is;

Jordan → pure scorer dominance from the start LeBron → all-around force immediately Kobe → slower start due to playing behind Eddie Jones but later became elite

4 🏀 Player Efficiency vs Win Shares (Season and Playoffs)

4.1 Player Efficiency Rating (PER) versus Win Shares (WS) (Line and Scatterplots)

In this scatter plot, we examine Player Efficiency Rating (PER) versus Win Shares (WS) for each player. This visualization allows us to identify the relationship between individual efficiency and contributions to team wins. By comparing players across seasons, we can determine which players are more effective at translating their performance into actual wins. The interactive elements also let us explore specific seasons and players in detail.

Here are the two key metrics that we will be using; - PER (Player Efficiency Rating)
- WS (Win Shares)

library(ggplot2)
library(plotly)

# Create ggplot
p <- ggplot(season_ppg, aes(
  x = per, y = ws, color = type, shape = player,
  text = paste("Player:", player,
               "<br>Season:", season,
               "<br>PER:", per,
               "<br>Win Shares:", ws)
)) +
  geom_point(size = 3, alpha = 0.7) +
  facet_wrap(~ player) +
  labs(title = "Player Efficiency vs Win Shares",
       x = "PER", y = "Win Shares") +
  theme_minimal()

# Convert to interactive plotly chart
ggplotly(p, tooltip = "text")

As we can see from the scatterplot above, there is a clear positive correlation between Player Efficiency Rating (PER) and Win Shares (WS). This indicates that, in general, more efficient players tend to contribute more to their team’s wins.

What is particularly interesting is when we compare LeBron James’ 2003-04 season to Michael Jordan’s 1985-86 season. LeBron recorded a PER of 18.3 and a Win Share of 5.1, whereas Jordan posted a PER of 27.5 but a Win Share of only 1.5. This suggests that although Jordan was highly efficient individually, his contribution to team wins that season was relatively low—likely due to fewer games played or other team dynamics—while LeBron’s performance, though less efficient, translated into a higher impact on his team’s success.

Now, lets jump into a depper analysis with a line plot

In this line plot, we track Player Efficiency Rating (PER) and Win Shares (WS) across seasons for each player. Unlike the scatter plot, which compares the two metrics directly, the line plot highlights how they evolve over time. This allows us to see patterns of consistency, peaks of dominance, and periods of decline throughout a player’s career. By comparing the shape of the lines, we can identify whether a player maintained steady efficiency (PER), consistently contributed to team success (WS), or experienced fluctuations influenced by context such as injuries or roster strength.

Here are the key metrics being visualized:

  • PER (Player Efficiency Rating)
  • WS (Win Shares)
library(ggplot2)
library(dplyr)
library(tidyr)
library(plotly)

# Reshape data for PER and WS
stats_line <- season_ppg %>%
  select(player, season, per, ws) %>%
  rename(PER = per,
         Win_Shares = ws) %>%
  pivot_longer(cols = c(PER, Win_Shares), 
               names_to = "Metric", 
               values_to = "Value")

# Create ggplot with custom hover text
p <- ggplot(stats_line, aes(x = season, y = Value, color = Metric, group = Metric,
                            text = paste("Player:", player,
                                         "<br>Season:", season,
                                         "<br>Metric:", Metric,
                                         "<br>Value:", round(Value, 2)))) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  facet_wrap(~ player, scales = "free_x") +
  labs(title = "PER and Win Shares by Season",
       x = "Season",
       y = "Value") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1),
        legend.position = "bottom")

# Convert to interactive plotly
ggplotly(p, tooltip = "text")

Heres what we gather from the data shown;

  • Michael Jordan consistently posted historic PER values, often over 25 and even peaking above 30, a level rarely reached in NBA history. What makes his profile remarkable is that his Win Shares almost always aligned with this efficiency — in his healthy seasons he regularly posted double-digit WS, showing that his personal dominance directly translated into team wins. The one exception is his 1985–86 season, where an injury held him to just 18 games; his PER was elite (27.5), but his Win Shares were limited (1.5) because of the shortened season.

  • LeBron James began his career with a strong rookie PER of 18.3 and quickly rose to the 25–31 range during his prime years. His Win Shares track closely with these numbers, often landing between 10 and 15+, a reflection of how his efficiency consistently powered his teams’ success across different franchises. What stands out most for LeBron is his longevity — unlike most players, he has been able to sustain elite efficiency and high Win Share production for nearly two decades.

  • Kobe Bryant’s PER tells a slightly different story. Starting lower as a teenager with limited minutes, his prime years saw him peak in the 22–25 range. While still elite, his efficiency levels were more variable than Jordan’s or LeBron’s. This pattern is mirrored in his Win Shares: some seasons show high totals during championship runs, while in other years strong individual PER did not always result in equally strong team-level contributions, particularly during injury-plagued seasons or when the Lakers’ roster was weaker.

In short, Jordan’s profile shows the most direct link between efficiency and team wins, LeBron’s shows unmatched consistency and longevity, and Kobe’s reflects elite play with more variability tied to context.

5 🏀 Distribution of Player stats

5.1 Distribution of Player stats (Density Plots)

In this interactive density plot, we examine the distribution of key stats — Points, Rebounds, Assists, Blocks, and Steals — for each player. This visualization allows us to understand the overall trends in performance, highlighting how consistent a player was, where their peak contributions occurred, and how their output varied across seasons. By comparing players, we can identify differences in playing style, such as scoring focus versus all-around contributions. The interactive elements also let us explore exact values for specific seasons and stats.

Here are the key metrics being visualized:

  • Points (PPG)
  • Rebounds (RPG)
  • Assists (APG)
  • Blocks (BPG)
  • Steals (SPG)
library(ggplot2)
library(dplyr)
library(tidyr)
library(plotly)

# Reshape data to long format
stats_long <- season_ppg %>%
  select(player, ppg, apg, rpg, bpg, spg) %>%
  rename(Points = ppg,
         Assists = apg,
         Rebounds = rpg,
         Blocks = bpg,
         Steals = spg) %>%
  pivot_longer(cols = Points:Steals, names_to = "Stat", values_to = "Value")

# Create ggplot with custom hover text
p <- ggplot(stats_long, aes(x = Value, color = player, fill = player,
                            text = paste("Player:", player,
                                         "<br>Stat:", Stat,
                                         "<br>Value:", round(Value, 2)))) +
  geom_density(alpha = 0.3) +
  facet_wrap(~ Stat, scales = "free") +
  labs(title = "Distribution of Player Stats",
       x = "Value",
       y = "Density") +
  theme_minimal()

# Convert to interactive plotly
ggplotly(p, tooltip = "text")

The plot shows which player was most balanced vs. specialized, who was most consistent, and how each contributed across all key stats over their careers.

Jordan: Dominant scorer with strong Peaks in Points; moderate contributions in Rebounds and Assists; smaller impact in defensive stats.

LeBron: All-around contributor; consistently high across Points, Rebounds, and Assists.

Kobe: High scoring focus with more variation in other stats, reflecting volume scoring style.

5.2 Player Stats Heatmap by season (Horizontal Layout)

In this interactive horizontal heatmap, we examine player stats across seasons — Points, Rebounds, Assists, Blocks, Steals, PER, and Win Shares for each player. This visualization allows us to quickly identify trends and patterns in performance over time. We can see which seasons were peak performance years, where players were consistent, and how their contributions varied across different metrics. By comparing players side by side, we can understand differences in playing style, strengths, and overall impact. The interactive elements let us hover over each tile to explore exact values for specific seasons and stats.

Here are the key metrics being visualized:

  • Points (PPG)
  • Rebounds (RPG)
  • Assists (APG)
  • Blocks (BPG)
  • Steals (SPG)
  • PER (Player Efficiency Rating)
  • Win Shares (WS)
library(dplyr)
library(tidyr)
library(ggplot2)
library(plotly)

# Step 1: Clean and rename columns explicitly
heatmap_data <- season_ppg %>%
  select(player, season, ppg, rpg, apg, bpg, spg, per, ws) %>%
  rename(
    Points = ppg,
    Rebounds = rpg,
    Assists = apg,
    Blocks = bpg,
    Steals = spg,
    PER = per,
    Win_Shares = ws
  ) %>%
  arrange(player, season)

# Replace NA with 0
heatmap_data[is.na(heatmap_data)] <- 0

# Step 2: Pivot to long format
heatmap_long <- heatmap_data %>%
  pivot_longer(
    cols = Points:Win_Shares,
    names_to = "Stat",
    values_to = "Value"
  ) %>%
  mutate(
    season = factor(season, levels = sort(unique(season))),
    Stat = factor(Stat, levels = c("Points","Rebounds","Assists","Blocks","Steals","PER","Win_Shares"))
  )

# Step 3: Create horizontal heatmap
p <- ggplot(heatmap_long, aes(x = Stat, y = season, fill = Value,
                              text = paste(
                                "Player:", player,
                                "<br>Season:", season,
                                "<br>Stat:", Stat,
                                "<br>Value:", round(Value,2)
                              ))) +
  geom_tile(color = "white") +
  facet_wrap(~ player, scales = "free_y") +
  scale_fill_viridis_c(option = "plasma") +
  labs(title = "Player Stats Heatmap by Season (Horizontal Layout)",
       x = "Statistic",
       y = "Season") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1),
        strip.text = element_text(size = 12))

# Step 4: Make interactive
ggplotly(p, tooltip = "text")

This interactive heatmap shows player stats across seasons:

Darker tiles indicate higher values in a particular stat, highlighting seasons where a player excelled (e.g., Jordan’s scoring peaks, LeBron’s all-around contributions).

Lighter, consistent shades indicate steady performance, while variability shows seasons with fluctuations in output.

What the Heatmap tells us: Jordan: Dominant in Points and scoring stats, with some defensive contributions. LeBron: Balanced across multiple metrics, consistently strong in Points, Rebounds, and Assists. Kobe: High scoring peaks with moderate contributions in other stats, reflecting a volume scoring style.

6 🏀 DEFENSE 👏👏 DEFENSE 👏👏 DEFENSE 👏👏

6.1 Defensive Metrics Comparison: (bar graph)

Lets dive into the defensive metric comparison of the three players.

library(dplyr)
library(tidyr)
library(ggplot2)
library(ggimage)

# Player colors
player_info <- data.frame(
  Player = c("Jordan", "LeBron", "Kobe"),
  Color  = c("#1f77b4", "#ff7f0e", "#2ca02c")
)

# Metrics for X-axis
metrics <- c("Steals", "Blocks", "Rebounds")  # replace with your actual metrics

# Image parameters
image_size <- 0.5
image_offset <- 0.1

ggplot(defense_long, aes(x = X_Pos, y = Value, fill = Player)) +
  geom_col(width = 0.25, position = position_dodge(width = 0.4)) +
  geom_image(
    aes(image = Image, y = Value + 1.5),  # move offset inside aes()
    size = 0.1,
    position = position_dodge(width = 0.4)
  ) +
  scale_fill_manual(values = setNames(player_info$Color, player_info$Player)) +
  scale_x_continuous(breaks = 1:3, labels = metrics) +
  scale_y_continuous(limits = c(0, 14)) +
  labs(
    title = "Defensive Metrics Comparison: Jordan vs LeBron vs Kobe",
    x = "Metric",
    y = "Average per Game"
  ) +
  theme_minimal(base_size = 14) +
  theme(plot.title = element_text(face = "bold", hjust = 0.5))
## Warning: `position_dodge()` requires non-overlapping x intervals.

From the defensive metrics comparison of Jordan, LeBron, and Kobe:

  • Michael Jordan clearly stands out as the most effective overall defensive player, leading in steals (SPG) and blocks (BPG). This confirms his reputation as a dominant perimeter defender and disruptor.

  • LeBron James, while slightly behind Jordan in steals and blocks, excels in rebounds (RPG), showcasing his strength in controlling the glass and impacting the game inside.

  • Kobe Bryant performs solidly across all defensive metrics, but slightly below Jordan and LeBron, reinforcing that while he was a strong defender, he was not as statistically dominant as Jordan.

Overall, Jordan’s defensive versatility and disruptive ability make him the superior defensive player, whereas LeBron’s rebounding prowess highlights his strength in interior defense and overall court coverage.

7 🏀 VORP (Value Over Replacement Player) Season/Playoffs

7.1 What Is VORP?

VORP stands for Value Over Replacement Player. It’s an advanced basketball statistic used to estimate how much value a player contributes to their team compared to a “replacement-level” player — basically, a bench or freely available player who could easily be substituted in.

It’s kind of like saying: > “How much better is LeBron (or any player) than an average backup if he were replaced?”

7.2 Regular Season vs Playoffs (line plot)

In this interactive line chart, we examine VORP (Value Over Replacement Player) across seasons for each player, separated by Regular Season and Playoffs. This visualization allows us to track how each player’s overall impact evolved over their career and compare performance between different contexts. By observing the trends, we can identify peak seasons, periods of consistency, and how each player performed under playoff pressure. The interactive elements let us hover over each point to explore exact VORP values for specific seasons and contexts.

Key metric being visualized: VORP (Value Over Replacement Player)

library(dplyr)
library(ggplot2)
library(plotly)

# Step 1: Add player and type labels, then combine datasets
jordan_season <- jordan_full_season %>%
  mutate(player = "Jordan", type = "RegularSeason") %>%
  select(player, season, type, vorp)

jordan_playoffs <- jordan_full_playoffs %>%
  mutate(player = "Jordan", type = "Playoffs") %>%
  select(player, season, type, vorp)

lebron_season <- lebron_full_season %>%
  mutate(player = "LeBron", type = "RegularSeason") %>%
  select(player, season, type, vorp)

lebron_playoffs <- lebron_full_playoffs %>%
  mutate(player = "LeBron", type = "Playoffs") %>%
  select(player, season, type, vorp)

kobe_season <- kobe_full_season %>%
  mutate(player = "Kobe", type = "RegularSeason") %>%
  select(player, season, type, vorp)

kobe_playoffs <- kobe_full_playoff %>%
  mutate(player = "Kobe", type = "Playoffs") %>%
  select(player, season, type, vorp)

# Combine all data
vorp_data <- bind_rows(jordan_season, jordan_playoffs,
                       lebron_season, lebron_playoffs,
                       kobe_season, kobe_playoffs) %>%
  mutate(season = factor(season, levels = sort(unique(season))),
         type = factor(type, levels = c("RegularSeason", "Playoffs")))

# Step 2: Create interactive line chart (showing every other year on x-axis)
p <- ggplot(vorp_data, aes(x = season, y = vorp, color = type, group = type,
                           text = paste(
                             "Player:", player,
                             "<br>Season:", season,
                             "<br>Type:", type,
                             "<br>VORP:", round(vorp,2)
                           ))) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  facet_wrap(~ player, scales = "free_x") +
  scale_x_discrete(breaks = levels(vorp_data$season)[c(TRUE, FALSE)]) +  # skip every other year
  labs(title = "VORP by Season: Regular Season vs Playoffs",
       x = "Season",
       y = "VORP") +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1),
        legend.position = "bottom")

# Step 3: Make interactive
ggplotly(p, tooltip = "text")

7.3 How to Interpret VORP

VORP Value Meaning
0.0 Replacement-level player (average bench player)
1.0–2.0 Solid rotation player
3.0–5.0 All-Star level
6.0+ MVP-level season

So, for example:

  • LeBron James (peak seasons) often posts VORP > 8, meaning he’s adding eight more wins worth of value than a typical replacement player.
  • Kobe Bryant and Michael Jordan also have high VORPs in their prime seasons, which is one reason they’re all considered GOAT candidates.

Let’s examine VORP from a different perspective. Using a stacked bar plot, we can more clearly compare the contributions of each season to a player’s total VORP. Unlike a line chart, which emphasizes trends over time, the stacked bars allow us to directly see the magnitude of each season’s impact and make side-by-side comparisons between players.

library(dplyr)
library(ggplot2)
library(plotly)

# Step 1: Prepare data
vorp_all <- vorp_data %>%
  mutate(
    label_text = paste0(
      "Player: ", player,
      "<br>Season: ", season,
      "<br>Type: ", type,
      "<br>VORP: ", round(vorp, 2)
    ),
    season = factor(season, levels = sort(unique(season)))  # ensures proper order
  )

# Step 2: Horizontal bar chart
p <- ggplot(vorp_all, aes(x = vorp, y = season, fill = player, text = label_text)) +
  geom_col(position = "dodge", color = "black") +
  facet_wrap(~ type, scales = "free_y") +
  labs(
    title = "VORP by Season for Each Player",
    x = "VORP",
    y = "Season"
  ) +
  theme_minimal() +
  theme(
    axis.text.y = element_text(size = 8),
    legend.position = "bottom"
  )

# Step 3: Make interactive
ggplotly(p, tooltip = "text")

Examining VORP across the full careers of these players reveals distinct career trajectories:

  • Michael Jordan: Showed peak dominance with several standout seasons. While there were dips due to injuries, his overall impact remained elite throughout his career.
  • LeBron James: Exhibited remarkable consistency and all-around excellence from the start, maintaining high VORP in both the regular season and playoffs over two decades.
  • Kobe Bryant: Experienced gradual growth early in his career, reaching elite VORP levels during his peak seasons, followed by moderate decline in later years due to aging and injuries.

7.4 First Four Seasons VORP (interactive bar chart)

In this interactive bar chart, we examine the Value Over Replacement Player (VORP) for the first four seasons of Michael Jordan, LeBron James, and Kobe Bryant. This visualization allows us to track how each player’s early-career impact evolved, highlighting which seasons contributed most relative to a replacement-level player. By observing the bars, we can compare early-career performance across players and identify patterns in their contributions. The interactive elements let us hover over each bar to explore exact VORP values for specific seasons, while the headshots on top of each bar provide a visual reference for the player.

library(dplyr)
library(readr)
library(ggplot2)
library(ggimage)
library(ggiraph)

# Load data (adjust file paths if needed)
jordan <- read_csv("jordan_full_season.csv") %>% mutate(player = "Michael Jordan")
## Rows: 15 Columns: 18
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): season
## dbl (17): games_played, fg_percent, fg_3percent, fg_2percent, e_fg_percent, ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
lebron <- read_csv("lebron_full_season.csv") %>% mutate(player = "LeBron James")
## Rows: 22 Columns: 18
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): season
## dbl (17): games_played, fg_percent, fg_3percent, fg_2percent, e_fg_perecent,...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
kobe   <- read_csv("kobe_full_season.csv")   %>% mutate(player = "Kobe Bryant")
## Rows: 20 Columns: 18
## ── Column specification ────────────────────────────────────────────────────────
## Delimiter: ","
## chr  (1): season
## dbl (17): games_played, fg_percent, fg_3percent, fg_2percent, e_fg_percent, ...
## 
## ℹ Use `spec()` to retrieve the full column specification for this data.
## ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
# Combine & keep first 4 years
vorp_data <- bind_rows(jordan, lebron, kobe) %>%
  select(player, season, vorp) %>%
  group_by(player) %>%
  slice_head(n = 4) %>%
  ungroup()

# Add headshot images
vorp_data <- vorp_data %>%
  mutate(image = case_when(
    player == "Michael Jordan" ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jordami01.jpg",
    player == "LeBron James"   ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jamesle01.jpg",
    player == "Kobe Bryant"    ~ "https://www.basketball-reference.com/req/202106291/images/headshots/bryanko01.jpg"
  ))

# Build interactive chart
p <- ggplot(vorp_data, aes(x = season, y = vorp, fill = player)) +
  geom_col_interactive(aes(
    tooltip = paste0(player, " (", season, "): VORP ", vorp)
  ), width = 0.7) +
  geom_image(aes(image = image, y = vorp + 1), size = 0.08) +  # 👈 on top of bars
  facet_wrap(~player, nrow = 1, scales = "free_x") +
  theme_minimal(base_size = 15) +
  labs(
    title = "First 4 Seasons: VORP Comparison",
    y = "VORP",
    x = "Season"
  ) +
  theme(
    legend.position = "none",
    axis.text.x = element_text(angle = 45, hjust = 1)
  )

girafe(ggobj = p)

Why the First Four Years of VORP

We focus on the first four seasons to examine each player’s early-career impact. Michael Jordan was immediately dominant, LeBron James shows steady growth, reflecting his development as a versatile scorer and playmaker, and Kobe Bryant started lower but quickly increased his contributions. This period highlights how each player adapted to the NBA and established themselves as key contributors.

Early-Career VORP Analysis

  • Jordan – High and consistent VORP from the start; immediately impactful.
  • LeBron – Strong rookie VORP with gradual growth; shows versatility.
  • Kobe – Starts lower, then rises sharply; rapid adaptation.

8 🏀 Points-to-Assist Ratio

8.1 Points-to-Assist Ratio by Season (line plot)

In this interactive line chart, we examine the Points-to-Assists (P/A) ratio across seasons for each player. This visualization allows us to track how scoring and playmaking balance evolved throughout their careers. By observing the trends, we can identify scoring-focused seasons, periods of more balanced play, and differences in playing style between players. The interactive elements let us hover over each point to explore exact points, assists, and P/A ratio for specific seasons.

Here is the key metric being visualized: Points-to-Assists Ratio (PPG / APG)

library(dplyr)
library(ggplot2)
library(plotly)

# Step 1: Calculate Points-to-Assists ratio
pa_data <- season_ppg %>%
  mutate(
    PA_ratio = ppg / apg,
    label_text = paste0(
      "Player: ", player,
      "<br>Season: ", season,
      "<br>Points: ", ppg,
      "<br>Assists: ", apg,
      "<br>P/A Ratio: ", round(PA_ratio,2)
    )
  )

# Step 2: Line chart
p <- ggplot(pa_data, aes(x = season, y = PA_ratio, color = player, group = player, text = label_text)) +
  geom_line(size = 1) +
  geom_point(size = 2) +
  labs(
    title = "Points-to-Assists Ratio by Season",
    x = "Season",
    y = "Points per Assist"
  ) +
  theme_minimal() +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

# Step 3: Make interactive
ggplotly(p, tooltip = "text")

Player Comparisons

*Michael Jordan Jordan’s P/A is generally high**, reflecting his role as a primary scorer. While he was capable of facilitating, his unmatched value came from his scoring ability.

*LeBron James LeBron shows the most balanced P/A ratio of the three, consistently scoring at a high level while also being a strong playmaker. His ratio demonstrates his dual-threat ability** to create offense both for himself and for others.

Kobe Bryant Kobe’s P/A ratio trends closer to Jordan’s — scoring-focused. His passing improved later in his career, but overall he leaned toward being a primary scorer first.

Spotlight: Michael Jordan, 1994–95 Season

In the 1994–95 season, after returning mid-season from his first retirement, Jordan’s Points-to-Assists ratio spiked.

  • Points per game (PPG): 26.9
  • Assists per game (APG): 3.6
  • P/A ratio: 26.9 ÷ 3.6 ≈ 7.5

This means for every assist Jordan recorded, he scored about 7.5 points.

This unusually high ratio highlights that upon his return, Jordan was focused heavily on scoring while still contributing some playmaking.

9 🏀 NBA title Won by Legends

library(ggplot2)
library(ggimage)
library(plotly)
library(ggiraph)

legends_accomplishments %>% 
  distinct(player)
##           player
## 1 Michael Jordan
## 2    Kobe Bryant
## 3   LeBron James
legend_accomplishments_images <- legends_accomplishments %>%
  filter(player %in% c("Michael Jordan", "LeBron James", "Kobe Bryant")) %>%
  mutate(image = case_when(
    player == "Michael Jordan" ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jordami01.jpg",
    player == "LeBron James"    ~ "https://www.basketball-reference.com/req/202106291/images/headshots/jamesle01.jpg",
    player == "Kobe Bryant"     ~ "https://www.basketball-reference.com/req/202106291/images/headshots/bryanko01.jpg"
  ))

p <- ggplot(legend_accomplishments_images,
            aes(x = player, y = titles_won, fill = player)) +
  # interactive bars
  geom_col_interactive(aes(tooltip = paste0(player, ": ", titles_won, " titles")),
                       width = 0.7) +
  # static images (no interactivity here)
  geom_image(aes(image = image, y = titles_won + 1.5), size = 0.16) +
  labs(x = "Players", y = "Titles", title = "NBA Titles Won by Legends") +
  expand_limits(y = max(legend_accomplishments_images$titles_won) + 4) +
  theme_minimal()

girafe(ggobj = p)

10 🏀 Conclusion: The Legacy of the Three GOATs

Through our analysis of Michael Jordan, Kobe Bryant, and LeBron James, we’ve gained a deeper appreciation of what makes each player legendary. By examining advanced metrics like VORP, offensive balance through Points-to-Assists ratios, and defensive impact via Steals and Blocks, we’ve uncovered the different paths each took to greatness.

Michael Jordan redefined dominance with his explosive scoring, relentless competitiveness, and ability to immediately elevate his team. His legacy rests on peak performance and an unmatched will to win.

Kobe Bryant embodied growth, dedication, and mastery. From a gradual rise to superstardom, his career tells the story of perseverance, relentless work ethic, and the mentality of a pure scorer.

LeBron James stands as the model of consistency and versatility, maintaining elite production across two decades. His rare ability to both score and facilitate makes him a complete all-around player.

What these visualizations ultimately show is that greatness in basketball cannot be defined by a single metric. Each of these legends brought a unique style, strength, and impact to the game: Jordan as the ultimate competitor, Kobe as the relentless scorer, and LeBron as the all-around force.

Together, they represent three different versions of basketball excellence, and their legacies will continue to shape the game for generations to come.

I appreciate your time and consideration, and I welcome any feedback or recommendations for improvement. Im looking to make more imrovements as i go along my journey. Please, any feedback is apprecaited, Thank You!

11 🏀 References