import os
os.environ['QT_QPA_PLATFORM_PLUGIN_PATH'] = 'C:/ProgramData/Anaconda3/Library/plugins/platforms'

Introduction

The NBA has undergone a dramatic transformation since the 1980s. This analysis explores how scoring, playing styles, and player roles have evolved from 1980 to 2017 using data from Basketball Reference. We will examine five visualizations that tell the story of how the modern NBA came to be.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

path = 'U:/'
df = pd.read_csv(path + 'Seasons_Stats.csv', encoding='latin-1')
df_modern = df[df['Year'] >= 1980].copy()
df_modern = df_modern.drop(columns=['blanl', 'blank2'])
df_modern['Pos'] = df_modern['Pos'].str.split('-').str[0]
df_modern['Player'] = df_modern['Player'].str.replace('*', '', regex=False)

Visualization 1 - Average Points by Position Over Time

The following chart tracks how average scoring has changed across all five NBA positions from 1980 to 2017. In the early 1980s, Small Forwards and Shooting Guards led the way offensively, reflecting the era of dominant wings like Larry Bird and Michael Jordan. Over time however, the gap between positions has narrowed considerably, suggesting that the modern NBA has evolved into a more positionless game where all players are expected to contribute offensively.

Two notable dips are visible in the data reflecting the 1998-99 and 2011-12 NBA lockouts, which shortened those seasons to 50 and 66 games respectively. Perhaps most interesting is the steady rise of the Point Guard position over the decades, as the league shifted toward smaller, faster, and more offensively minded lineups.

main_positions = ['PG', 'SG', 'SF', 'PF', 'C']
position_pts = df_modern.groupby(['Year', 'Pos'])['PTS'].mean().reset_index()

fig, ax = plt.subplots(figsize=(12, 6))

colors = {'PG': 'blue', 'SG': 'orange', 'SF': 'green', 'PF': 'red', 'C': 'purple'}

for pos in main_positions:
    data = position_pts[position_pts['Pos'] == pos]
    ax.plot(data['Year'], data['PTS'], marker='o', markersize=3, label=pos, color=colors[pos]);

ax.set_title('Average Points by Position Over Time (1980-2017)', fontsize=14)
ax.set_xlabel('Year')
ax.set_ylabel('Average Points')
ax.legend(title='Position')
plt.tight_layout()
plt.show()

Visualization 2 - Top 10 NBA Scorers (1980-2017)

This chart highlights the ten highest scoring players in the dataset spanning from 1980 to 2017. Karl Malone leads the list despite never winning an NBA championship, a testament to his remarkable consistency over nearly two decades. LeBron James appears seventh in this dataset, which only covers through 2017. As of 2026, LeBron has surpassed all players on this list to become the NBA’s all time leading scorer, making his seventh place ranking here a fascinating snapshot in time.

top_scorers = df_modern.groupby('Player')['PTS'].sum().reset_index()
top_scorers = top_scorers.sort_values('PTS', ascending=False).head(10)

fig, ax = plt.subplots(figsize=(12, 6))

ax.barh(top_scorers['Player'], top_scorers['PTS'], color='steelblue')

for i, v in enumerate(top_scorers['PTS']):
    ax.text(v + 100, i, f'{int(v):,}', va='center');

ax.set_title('Top 10 NBA Scorers (1980-2017)', fontsize=14)
ax.set_xlabel('Total Points')
ax.set_ylabel('Player')
ax.invert_yaxis()
plt.tight_layout()
plt.show()

Visualization 3 - Points vs Assists by Position

This scatter plot explores the relationship between scoring and playmaking across all five positions. As expected, Point Guards show the greatest range in both scoring and assists, reflecting their dual role as both scorers and distributors. Centers consistently cluster in the lower left of the assist axis, reflecting their traditional role close to the basket where passing opportunities are limited. What is perhaps most interesting is the upper right corner of the chart — players with both high scoring and high assist totals. These rare individuals, typically elite Point Guards or versatile forwards, represent the most complete offensive players in the league.

scatter_data = df_modern[['Player', 'PTS', 'AST', 'Pos']].dropna()

colors = {'PG': 'blue', 'SG': 'orange', 'SF': 'green', 'PF': 'red', 'C': 'purple'}

fig, ax = plt.subplots(figsize=(12, 6))

for pos in colors:
    data = scatter_data[scatter_data['Pos'] == pos]
    ax.scatter(data['PTS'], data['AST'], 
               c=colors[pos], label=pos, alpha=0.5, s=10);

ax.set_title('Points vs Assists by Position (1980-2017)', fontsize=14)
ax.set_xlabel('Total Points')
ax.set_ylabel('Total Assists')
ax.legend(title='Position')
plt.tight_layout()
plt.show()

Visualization 4 - Scoring Rankings of NBA Legends Over Time

This bump chart tracks how the scoring rankings of five NBA legends shifted throughout their careers. Michael Jordan’s dominance in the late 1980s and early 1990s is clearly visible, interrupted only by his two retirements. Karl Malone stands out for his remarkable consistency, remaining near the top of the scoring rankings for nearly two decades. As Jordan’s career wound down, Kobe Bryant and Shaquille O’Neal rose to prominence in the early 2000s. LeBron James enters the chart in the mid 2000s and immediately climbs toward the top, foreshadowing the all time scoring record he would eventually break in 2023. The gaps in each line represent seasons where a player was injured, retired, or not yet in the league.

legends = ['Michael Jordan', 'Kobe Bryant', 'LeBron James', 'Karl Malone', 'Shaquille O\'Neal']
legends_df = df_modern[df_modern['Player'].isin(legends)][['Year', 'Player', 'PTS']]
legends_df = legends_df.copy()
legends_df['Rank'] = legends_df.groupby('Year')['PTS'].rank(ascending=False, method='min')

colors = {
    'Michael Jordan': 'red',
    'Kobe Bryant': 'purple',
    'LeBron James': 'gold',
    'Karl Malone': 'green',
    'Shaquille O\'Neal': 'blue'
}

fig, ax = plt.subplots(figsize=(14, 6))

for player in legends:
    data = legends_df[legends_df['Player'] == player].sort_values('Year')
    ax.plot(data['Year'], data['Rank'], marker='o', linewidth=2,
            markersize=6, label=player, color=colors[player]);

ax.invert_yaxis()
ax.set_title('Scoring Rankings of NBA Legends Over Time (1980-2017)', fontsize=14)
ax.set_xlabel('Year')
ax.set_ylabel('Rank (1 = Highest Scorer)')
ax.legend(title='Player')
plt.tight_layout()
plt.show()

Visualization 5 - NBA Scoring Breakdown by Decade

This stacked bar chart illustrates one of the most significant shifts in NBA history — the dramatic rise of the three point shot. In the 1980s, three pointers were rarely attempted, with teams relying almost exclusively on two point field goals and free throws. By the 2010s however, three point attempts had increased by nearly eight times compared to the 1980s, fundamentally changing how the game is played. The dominance of two point scoring gradually declined each decade as teams began to recognize the mathematical advantage of the three point shot. Free throw totals remained relatively stable throughout, suggesting that while the style of scoring changed dramatically, drawing fouls remained a consistent offensive strategy across all eras. Note that the 2010s bar appears smaller overall as the dataset only covers through 2017, representing seven years rather than a full decade.

df_modern['Decade'] = (df_modern['Year'] // 10 * 10).astype(int)
scoring_breakdown = df_modern.groupby('Decade')[['2P', '3P', 'FT']].sum().reset_index()

fig, ax = plt.subplots(figsize=(12, 6))

decades = scoring_breakdown['Decade']
ax.bar(decades, scoring_breakdown['2P'], label='2-Pointers', color='steelblue', width=5)
ax.bar(decades, scoring_breakdown['3P'], bottom=scoring_breakdown['2P'], label='3-Pointers', color='orange', width=5)
ax.bar(decades, scoring_breakdown['FT'], bottom=scoring_breakdown['2P'] + scoring_breakdown['3P'], label='Free Throws', color='green', width=5);

ax.yaxis.set_major_formatter(plt.FuncFormatter(lambda x, p: f'{x/1e6:.1f}M'))
ax.set_title('NBA Scoring Breakdown by Decade (1980-2017)', fontsize=14)
ax.set_xlabel('Decade')
ax.set_ylabel('Total Shots Made (Millions)')
ax.legend(title='Shot Type')
plt.tight_layout()
plt.show()

Conclusion

This analysis of NBA data from 1980 to 2017 reveals a league in constant evolution. The rise of the three point shot has fundamentally transformed how the game is played, while the boundaries between positions have blurred as the league has embraced a more positionless style of basketball. The dominance of legendary scorers like Michael Jordan, Karl Malone, and Kobe Bryant defined their respective eras, while the emergence of LeBron James signaled a new era of versatile, all around players. Perhaps most remarkably, the data suggests that the biggest changes in the NBA were still yet to come — the three point revolution that began in the 2010s has only accelerated since 2017.

The story of the NBA is ultimately a story of adaptation — teams, coaches, and players constantly evolving in pursuit of a competitive edge.