From 9a845ade4a6c937649cd58a96c15bd9b52685bc5 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Thu, 27 Apr 2023 16:22:30 -0500 Subject: [PATCH 01/12] create script to reset TOTALS DB --- .../delete_player_totals_data_and_id.py | 17 +++ ...007_alter_playertotalsdata_ast_and_more.py | 128 ++++++++++++++++++ nba_data/models.py | 46 +++---- nba_data/nba_scraper_totals.py | 70 +++++----- 4 files changed, 204 insertions(+), 57 deletions(-) create mode 100644 nba_data/management/commands/delete_player_totals_data_and_id.py create mode 100644 nba_data/migrations/0007_alter_playertotalsdata_ast_and_more.py diff --git a/nba_data/management/commands/delete_player_totals_data_and_id.py b/nba_data/management/commands/delete_player_totals_data_and_id.py new file mode 100644 index 0000000..7dadd74 --- /dev/null +++ b/nba_data/management/commands/delete_player_totals_data_and_id.py @@ -0,0 +1,17 @@ +from django.core.management.base import BaseCommand +from nba_data.models import PlayerTotalsData +from django.db import connection + +class Command(BaseCommand): + # Provides a description for use with the custom django management command (python manage.py help). + help = 'Delete all player TOTALS data and reset primary key sequence in DB' + + def handle(self, *args, **options): + # Delete all player data + PlayerTotalsData.objects.all().delete() + + # Reset the PK sequence + with connection.cursor() as cursor: + cursor.execute('ALTER SEQUENCE nba_data_playertotalsdata_id_seq RESTART WITH 1;') + + self.stdout.write(self.style.SUCCESS('Successfully deleted all players in DB')) \ No newline at end of file diff --git a/nba_data/migrations/0007_alter_playertotalsdata_ast_and_more.py b/nba_data/migrations/0007_alter_playertotalsdata_ast_and_more.py new file mode 100644 index 0000000..e8efec3 --- /dev/null +++ b/nba_data/migrations/0007_alter_playertotalsdata_ast_and_more.py @@ -0,0 +1,128 @@ +# Generated by Django 4.2 on 2023-04-27 20:53 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0006_playeradvanceddata_playertotalsdata'), + ] + + operations = [ + migrations.AlterField( + model_name='playertotalsdata', + name='AST', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='BLK', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='DRB', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='ORB', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='PF', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='PTS', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='STL', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='TOV', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='TRB', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='effect_fg_percent', + field=models.DecimalField(decimal_places=3, max_digits=6, null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='field_attempts', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='field_goals', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='field_percent', + field=models.DecimalField(decimal_places=3, max_digits=6, null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='ft', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='ft_percent', + field=models.DecimalField(decimal_places=3, max_digits=6, null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='fta', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='minutes_played', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='three_attempts', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='three_fg', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='three_percent', + field=models.DecimalField(decimal_places=3, max_digits=6, null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='two_attempts', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='two_fg', + field=models.IntegerField(null=True), + ), + migrations.AlterField( + model_name='playertotalsdata', + name='two_percent', + field=models.DecimalField(decimal_places=3, max_digits=6, null=True), + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index 5a1fc43..4262fe9 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -40,29 +40,29 @@ class PlayerTotalsData(models.Model): age = models.IntegerField(null=True) games = models.IntegerField(null=True) games_started = models.IntegerField(null=True) - minutes_played = models.DecimalField(max_digits=5, decimal_places=2, null=True) - field_goals = models.DecimalField(max_digits=5, decimal_places=2, null=True) - field_attempts = models.DecimalField(max_digits=5, decimal_places=2, null=True) - field_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) - three_fg = models.DecimalField(max_digits=5, decimal_places=2, null=True) - three_attempts = models.DecimalField(max_digits=5, decimal_places=2, null=True) - three_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) - two_fg = models.DecimalField(max_digits=5, decimal_places=3, null=True) - two_attempts = models.DecimalField(max_digits=5, decimal_places=2, null=True) - two_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) - effect_fg_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) - ft = models.DecimalField(max_digits=5, decimal_places=2, null=True) - fta = models.DecimalField(max_digits=5, decimal_places=2, null=True) - ft_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) - ORB= models.DecimalField(max_digits=5, decimal_places=2, null=True) - DRB = models.DecimalField(max_digits=5, decimal_places=2, null=True) - TRB = models.DecimalField(max_digits=5, decimal_places=2, null=True) - AST = models.DecimalField(max_digits=5, decimal_places=2, null=True) - STL = models.DecimalField(max_digits=5, decimal_places=2, null=True) - BLK = models.DecimalField(max_digits=5, decimal_places=2, null=True) - TOV = models.DecimalField(max_digits=5, decimal_places=2, null=True) - PF = models.DecimalField(max_digits=5, decimal_places=2, null=True) - PTS = models.DecimalField(max_digits=5, decimal_places=2, null=True) + minutes_played = models.IntegerField(null=True) + field_goals = models.IntegerField(null=True) + field_attempts = models.IntegerField(null=True) + field_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + three_fg = models.IntegerField(null=True) + three_attempts = models.IntegerField(null=True) + three_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + two_fg = models.IntegerField(null=True) + two_attempts = models.IntegerField(null=True) + two_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + effect_fg_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + ft = models.IntegerField(null=True) + fta = models.IntegerField(null=True) + ft_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + ORB= models.IntegerField(null=True) + DRB = models.IntegerField(null=True) + TRB = models.IntegerField(null=True) + AST = models.IntegerField(null=True) + STL = models.IntegerField(null=True) + BLK = models.IntegerField(null=True) + TOV = models.IntegerField(null=True) + PF = models.IntegerField(null=True) + PTS = models.IntegerField(null=True) team = models.CharField(max_length=30, default='') season = models.IntegerField(null=True) diff --git a/nba_data/nba_scraper_totals.py b/nba_data/nba_scraper_totals.py index 7cf2561..8ca132f 100644 --- a/nba_data/nba_scraper_totals.py +++ b/nba_data/nba_scraper_totals.py @@ -70,40 +70,42 @@ for team in teams: with open(json_output_path, 'w') as jsonfile: json.dump(data, jsonfile) - # # save data to database - # for row in data: - # player_data = PlayerData( - # name=row['Player'], - # age=row['Age'], - # games=row['G'], - # games_started=row['GS'], - # minutes_pg=row['MP'], - # field_goals=row['FG'], - # field_attempts=row['FGA'], - # field_percent=row['FG%'], - # three_fg=row['3P'], - # three_attempts=row['3PA'], - # three_percent=row['3P%'], - # two_fg=row['2P'], - # two_attempts=row['2PA'], - # two_percent=row['2P%'], - # effect_fg_percent=row['eFG%'], - # ft=row['FT'], - # fta=row['FTA'], - # ft_percent=row['FT%'], - # ORB=row['ORB'], - # DRB=row['DRB'], - # TRB=row['TRB'], - # AST=row['AST'], - # STL=row['STL'], - # BLK=row['BLK'], - # TOV=row['TOV'], - # PF=row['PF'], - # PTS=row['PTS'], - # team=row['team'], - # season=row['season'], - # ) - # player_data.save() + # save data to database + for row in data: + player_data = PlayerTotalsData( + player_name = row['Player'], + age = row['Age'], + games = row['G'], + games_started = row['GS'], + minutes_played = row['MP'], + field_goals = row['FG'], + field_attempts = row['FGA'], + field_percent = row['FG%'], + three_fg = row['3P'], + three_attempts = row['3PA'], + three_percent = row['3P%'], + two_fg = row['2P'], + two_attempts = row['2PA'], + two_percent = row['2P%'], + effect_fg_percent = row['eFG%'], + ft = row['FT'], + fta = row['FTA'], + ft_percent = row['FT%'], + ORB= row['ORB'], + DRB = row['DRB'], + TRB = row['TRB'], + AST = row['AST'], + STL = row['STL'], + BLK = row['BLK'], + TOV = row['TOV'], + PF = row['PF'], + PTS = row['PTS'], + team = row['team'], + season = row['season'], + ) + player_data.save() + +print('success') \ No newline at end of file From daf96cd2706fefedd77bb67b2bb178e770070438 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Thu, 27 Apr 2023 17:57:26 -0500 Subject: [PATCH 02/12] advanced and totals data scraped success --- nba_data/nba_scraper_advanced.py | 70 ++++++++--------- nba_data/nba_scraper_totals_single.py | 107 ++++++++++++++++++++++++++ 2 files changed, 141 insertions(+), 36 deletions(-) create mode 100644 nba_data/nba_scraper_totals_single.py diff --git a/nba_data/nba_scraper_advanced.py b/nba_data/nba_scraper_advanced.py index 97eaf93..e8e4808 100644 --- a/nba_data/nba_scraper_advanced.py +++ b/nba_data/nba_scraper_advanced.py @@ -15,11 +15,10 @@ django.setup() from nba_data.models import PlayerAdvancedData +# If season is 2014, then change CHO to CHA teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] -# team = input('What team? ') -# OMIT above. Will now only ask for season and iterate thru teams list. season = input('What season? ') for team in teams: @@ -70,40 +69,39 @@ for team in teams: with open(json_output_path, 'w') as jsonfile: json.dump(data, jsonfile) - # # save data to database - # for row in data: - # player_data = PlayerData( - # name=row['Player'], - # age=row['Age'], - # games=row['G'], - # games_started=row['GS'], - # minutes_pg=row['MP'], - # field_goals=row['FG'], - # field_attempts=row['FGA'], - # field_percent=row['FG%'], - # three_fg=row['3P'], - # three_attempts=row['3PA'], - # three_percent=row['3P%'], - # two_fg=row['2P'], - # two_attempts=row['2PA'], - # two_percent=row['2P%'], - # effect_fg_percent=row['eFG%'], - # ft=row['FT'], - # fta=row['FTA'], - # ft_percent=row['FT%'], - # ORB=row['ORB'], - # DRB=row['DRB'], - # TRB=row['TRB'], - # AST=row['AST'], - # STL=row['STL'], - # BLK=row['BLK'], - # TOV=row['TOV'], - # PF=row['PF'], - # PTS=row['PTS'], - # team=row['team'], - # season=row['season'], - # ) - # player_data.save() + # save data to database + for row in data: + player_data = PlayerAdvancedData( + player_name = row['Player'], + age = row['Age'], + games = row['G'], + minutes_played = row['MP'], + PER = row['PER'], + TS_percent = row['TS%'], + three_p_attempt_rate = row['3PAr'], + ft_attempt_rate = row['FTr'], + orb_percent = row['ORB%'], + drb_percent = row['DRB%'], + trb_percent = row['TRB%'], + ast_percent = row['AST%'], + stl_percent = row['STL%'], + blk_percent = row['BLK%'], + tov_percent = row['TOV%'], + usg_percent = row['USG%'], + ows = row['OWS'], + dws = row['DWS'], + ws = row['WS'], + ws_per_48 = row['WS/48'], + obpm = row['OBPM'], + dbpm = row['DBPM'], + bpm = row['BPM'], + vorp = row['VORP'], + team = row['team'], + season = row['season'], + ) + player_data.save() + +print('success') \ No newline at end of file diff --git a/nba_data/nba_scraper_totals_single.py b/nba_data/nba_scraper_totals_single.py new file mode 100644 index 0000000..f4377c3 --- /dev/null +++ b/nba_data/nba_scraper_totals_single.py @@ -0,0 +1,107 @@ +import os +import sys +import django +import requests +from bs4 import BeautifulSoup +import csv +import json +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nba_stats.settings') +django.setup() + +from nba_data.models import PlayerTotalsData + + +season = input('What season? ') +team = input('What team?') + +url_make = 'https://www.basketball-reference.com/teams/' + team + '/' + season + '.html' +response = requests.get(url_make) + +soup = BeautifulSoup(response.text, "html.parser") + +table = soup.find("table", {"id": "totals"}) + +if table is None: + print(f"Table not found for team {team} and season {season}.") + # sys.exit(1) + # continue + +header_row = table.find("thead").find("tr") +rows = table.find("tbody").find_all("tr") + + +# Extract coloumn headers +headers = [col.text.strip() for col in header_row.find_all("th")][1:] +headers.extend(['team', 'season']) # Add 'team' and 'season' fields to headers + +# Extract rows as dictionaries +data = [] +for row in rows: + cols = row.find_all("td") + cols = [col.text.strip() for col in cols] + row_dict = {header: (None if col == "" else col) for header, col in zip(headers, cols)} + row_dict['team'] = team + row_dict['season'] = season + data.append(row_dict) + + +# locate current directory +script_dir = os.path.dirname(os.path.abspath(__file__)) + +# save as CSV file +csv_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_totals_output.csv') +with open(csv_output_path, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=headers) + writer.writeheader() + for row in data: + writer.writerow(row) + +# Save as JSON +json_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_totals_output.json') +with open(json_output_path, 'w') as jsonfile: + json.dump(data, jsonfile) + +# save data to database +for row in data: + player_data = PlayerTotalsData( + player_name = row['Player'], + age = row['Age'], + games = row['G'], + games_started = row['GS'], + minutes_played = row['MP'], + field_goals = row['FG'], + field_attempts = row['FGA'], + field_percent = row['FG%'], + three_fg = row['3P'], + three_attempts = row['3PA'], + three_percent = row['3P%'], + two_fg = row['2P'], + two_attempts = row['2PA'], + two_percent = row['2P%'], + effect_fg_percent = row['eFG%'], + ft = row['FT'], + fta = row['FTA'], + ft_percent = row['FT%'], + ORB= row['ORB'], + DRB = row['DRB'], + TRB = row['TRB'], + AST = row['AST'], + STL = row['STL'], + BLK = row['BLK'], + TOV = row['TOV'], + PF = row['PF'], + PTS = row['PTS'], + team = row['team'], + season = row['season'], + ) + player_data.save() + +print('success') + + + \ No newline at end of file From cfc67515bcfd51b31c7aa265309881eb9d2c2f92 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Thu, 27 Apr 2023 19:06:49 -0500 Subject: [PATCH 03/12] scraper update --- nba_data/scraper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nba_data/scraper.py b/nba_data/scraper.py index 34bc4dd..a045866 100644 --- a/nba_data/scraper.py +++ b/nba_data/scraper.py @@ -15,11 +15,11 @@ django.setup() from nba_data.models import PlayerData +# If season is 2014 or priot, then change CHO to CHA teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] -# team = input('What team? ') -# OMIT above. Will now only ask for season and iterate thru teams list. + season = input('What season? ') for team in teams: @@ -105,5 +105,7 @@ for team in teams: ) player_data.save() +print('success') + \ No newline at end of file From 9125fec3e66bba2e5eced52d1b99aa1977f7b388 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Fri, 28 Apr 2023 01:28:25 -0500 Subject: [PATCH 04/12] new view to return scatter plot data --- nba_data/urls.py | 3 ++- nba_data/views.py | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/nba_data/urls.py b/nba_data/urls.py index 14a3e00..36a43eb 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData urlpatterns = [ path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), @@ -15,4 +15,5 @@ urlpatterns = [ path('api/top_rebounds_offensive//', TopOffensiveReboundsBySeasonList.as_view(), name='top_rebounds_ORB'), path('api/top_rebounds_defensive//', TopDefensiveReboundsBySeasonList.as_view(), name='top_rebounds_DRB'), path('api/points_per_game_histogram//', PointsPerGameHistogramBySeasonList.as_view(), name='points_per_game_histogram'), + path('api/top_pts_scatter_plot/', TopPtsScatterPlotData.as_view(), name='top_pts_scatter_plot'), ] \ No newline at end of file diff --git a/nba_data/views.py b/nba_data/views.py index c1d3874..3418e5e 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -1,12 +1,12 @@ from django.shortcuts import render from django.http import JsonResponse from django.core import serializers -from .models import PlayerData +from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData from rest_framework import generics from .serializers import PlayerDataSerializer, HistogramDataSerializer from rest_framework.views import APIView from rest_framework.response import Response -from django.db.models import Avg +from django.db.models import Avg, Sum, F, FloatField from collections import Counter import math @@ -174,3 +174,35 @@ class PointsPerGameHistogramBySeasonList(generics.ListAPIView): for k, v in histogram_data.items()] return histogram_list + + +# Scatter Plot - Top 30 players by total PTS across all seasons in DB. Then map those 30 players on a scatter plot against total PTS/WS for each season. + +class TopPtsScatterPlotData(APIView): + def get(self, request, *args, **kwargs): + # Get the top 25 players by overall PTS + top_players = PlayerTotalsData.objects.values('player_name') \ + .annotate(total_pts=Sum(F('PTS'))) \ + .order_by('-total_pts')[:25] + + # Get the PTS and WS for the corresponding seasons of the top 25 players + data = [] + for player in top_players: + player_name = player['player_name'] + player_seasons = PlayerTotalsData.objects.filter(player_name=player_name) \ + .values('season') \ + .annotate(total_pts=Sum(F('PTS'))) \ + .order_by('season') + + # Combine the WS data from PlayerAdvancedData + for season in player_seasons: + season_ws = PlayerAdvancedData.objects.filter(player_name=player_name, season=season['season']) \ + .aggregate(total_ws=Sum('ws')) + season['total_ws'] = season_ws['total_ws'] + + data.append({ + 'player_name': player_name, + 'seasons': list(player_seasons) + }) + + return Response(data) From f4e3a17e6d5b1d3469e6d4599b915f120f856b39 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Fri, 28 Apr 2023 01:35:24 -0500 Subject: [PATCH 05/12] faster route for same query --- nba_data/urls.py | 3 ++- nba_data/views.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/nba_data/urls.py b/nba_data/urls.py index 36a43eb..3c8a049 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast urlpatterns = [ path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), @@ -16,4 +16,5 @@ urlpatterns = [ path('api/top_rebounds_defensive//', TopDefensiveReboundsBySeasonList.as_view(), name='top_rebounds_DRB'), path('api/points_per_game_histogram//', PointsPerGameHistogramBySeasonList.as_view(), name='points_per_game_histogram'), path('api/top_pts_scatter_plot/', TopPtsScatterPlotData.as_view(), name='top_pts_scatter_plot'), + path('api/top_pts_scatter_plot_fast/', TopPtsScatterPlotDataFast.as_view(), name='top_pts_scatter_plot_fast'), ] \ No newline at end of file diff --git a/nba_data/views.py b/nba_data/views.py index 3418e5e..6fbf2ee 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -9,6 +9,7 @@ from rest_framework.response import Response from django.db.models import Avg, Sum, F, FloatField from collections import Counter import math +from django.db import connection # Create your views here. @@ -206,3 +207,45 @@ class TopPtsScatterPlotData(APIView): }) return Response(data) + + +class TopPtsScatterPlotDataFast(APIView): + def get(self, request): + with connection.cursor() as cursor: + cursor.execute(""" + -- The modified query from earlier + WITH player_season_pts AS ( + SELECT player_name, season, SUM("PTS") as season_pts + FROM nba_data_playertotalsdata + WHERE player_name IN (SELECT player_name FROM ( + SELECT player_name, SUM("PTS") as total_pts + FROM nba_data_playertotalsdata + GROUP BY player_name + ORDER BY total_pts DESC + LIMIT 25) as top_25_players) + GROUP BY player_name, season + ), + player_season_ws AS ( + SELECT player_name, season, SUM("ws") as season_ws + FROM nba_data_playeradvanceddata + GROUP BY player_name, season + ) + SELECT player_season_pts.player_name, player_season_pts.season, player_season_pts.season_pts, player_season_ws.season_ws + FROM player_season_pts + JOIN player_season_ws + ON player_season_pts.player_name = player_season_ws.player_name AND player_season_pts.season = player_season_ws.season + ORDER BY player_season_pts.player_name, player_season_pts.season; + """) + rows = cursor.fetchall() + + result = [] + for row in rows: + player_name, season, season_pts, season_ws = row + result.append({ + 'player_name': player_name, + 'season': season, + 'season_pts': season_pts, + 'season_ws': season_ws + }) + + return Response(result) From 58ac01bced6bf49c723c948661a9221f1e51efa6 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 12:56:00 -0500 Subject: [PATCH 06/12] add playoff totals data scraper --- nba_data/models.py | 35 ++++++++ nba_data/nba_scraper_playoffs_totals.py | 111 ++++++++++++++++++++++++ nba_data/views.py | 5 +- 3 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 nba_data/nba_scraper_playoffs_totals.py diff --git a/nba_data/models.py b/nba_data/models.py index 4262fe9..c765bc0 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -101,6 +101,41 @@ class PlayerAdvancedData(models.Model): def __str__(self): return self.player_name + +class PlayerPlayoffTotalsData(models.Model): + player_name = models.CharField(max_length=255, null=True) + age = models.IntegerField(null=True) + games = models.IntegerField(null=True) + games_started = models.IntegerField(null=True) + minutes_played = models.IntegerField(null=True) + field_goals = models.IntegerField(null=True) + field_attempts = models.IntegerField(null=True) + field_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + three_fg = models.IntegerField(null=True) + three_attempts = models.IntegerField(null=True) + three_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + two_fg = models.IntegerField(null=True) + two_attempts = models.IntegerField(null=True) + two_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + effect_fg_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + ft = models.IntegerField(null=True) + fta = models.IntegerField(null=True) + ft_percent = models.DecimalField(max_digits=6, decimal_places=3, null=True) + ORB= models.IntegerField(null=True) + DRB = models.IntegerField(null=True) + TRB = models.IntegerField(null=True) + AST = models.IntegerField(null=True) + STL = models.IntegerField(null=True) + BLK = models.IntegerField(null=True) + TOV = models.IntegerField(null=True) + PF = models.IntegerField(null=True) + PTS = models.IntegerField(null=True) + team = models.CharField(max_length=30, default='') + season = models.IntegerField(null=True) + + def __str__(self): + return self.player_name + diff --git a/nba_data/nba_scraper_playoffs_totals.py b/nba_data/nba_scraper_playoffs_totals.py new file mode 100644 index 0000000..c6030d5 --- /dev/null +++ b/nba_data/nba_scraper_playoffs_totals.py @@ -0,0 +1,111 @@ +import os +import sys +import django +import requests +from bs4 import BeautifulSoup +import csv +import json +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nba_stats.settings') +django.setup() + +from nba_data.models import PlayerPlayoffTotalsData + +# Change CHO to CHA 2014 and prior. + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] +team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] + +season = input('What season? ') + +for team in teams: + url_make = 'https://www.basketball-reference.com/teams/' + team + '/' + season + '.html' + response = requests.get(url_make) + + soup = BeautifulSoup(response.text, "html.parser") + + table = soup.find("table", {"id": "playoffs_totals"}) + + if table is None: + print(f"Table not found for team {team} and season {season}.") + # sys.exit(1) + continue + + header_row = table.find("thead").find("tr") + rows = table.find("tbody").find_all("tr") + + + # Extract coloumn headers + headers = [col.text.strip() for col in header_row.find_all("th")][1:] + headers.extend(['team', 'season']) # Add 'team' and 'season' fields to headers + + # Extract rows as dictionaries + data = [] + for row in rows: + cols = row.find_all("td") + cols = [col.text.strip() for col in cols] + row_dict = {header: (None if col == "" else col) for header, col in zip(headers, cols)} + row_dict['team'] = team + row_dict['season'] = season + data.append(row_dict) + + + # locate current directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # save as CSV file + csv_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_playoffs_totals_output.csv') + with open(csv_output_path, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=headers) + writer.writeheader() + for row in data: + writer.writerow(row) + + # Save as JSON + json_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_playoffs_totals_output.json') + with open(json_output_path, 'w') as jsonfile: + json.dump(data, jsonfile) + + # save data to database + for row in data: + player_data = PlayerPlayoffTotalsData( + player_name = row['Player'], + age = row['Age'], + games = row['G'], + games_started = row['GS'], + minutes_played = row['MP'], + field_goals = row['FG'], + field_attempts = row['FGA'], + field_percent = row['FG%'], + three_fg = row['3P'], + three_attempts = row['3PA'], + three_percent = row['3P%'], + two_fg = row['2P'], + two_attempts = row['2PA'], + two_percent = row['2P%'], + effect_fg_percent = row['eFG%'], + ft = row['FT'], + fta = row['FTA'], + ft_percent = row['FT%'], + ORB= row['ORB'], + DRB = row['DRB'], + TRB = row['TRB'], + AST = row['AST'], + STL = row['STL'], + BLK = row['BLK'], + TOV = row['TOV'], + PF = row['PF'], + PTS = row['PTS'], + team = row['team'], + season = row['season'], + ) + player_data.save() + +print('success') + + + \ No newline at end of file diff --git a/nba_data/views.py b/nba_data/views.py index 6fbf2ee..680787c 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -208,7 +208,7 @@ class TopPtsScatterPlotData(APIView): return Response(data) - +# change lines 223, 227, and 233 where the comments are to alter seasons returned. class TopPtsScatterPlotDataFast(APIView): def get(self, request): with connection.cursor() as cursor: @@ -220,14 +220,17 @@ class TopPtsScatterPlotDataFast(APIView): WHERE player_name IN (SELECT player_name FROM ( SELECT player_name, SUM("PTS") as total_pts FROM nba_data_playertotalsdata + -- WHERE season >= 2021 GROUP BY player_name ORDER BY total_pts DESC LIMIT 25) as top_25_players) + -- AND season >= 2021 GROUP BY player_name, season ), player_season_ws AS ( SELECT player_name, season, SUM("ws") as season_ws FROM nba_data_playeradvanceddata + -- WHERE season >= 2021 GROUP BY player_name, season ) SELECT player_season_pts.player_name, player_season_pts.season, player_season_pts.season_pts, player_season_ws.season_ws From 35d672b1d6d9b9cd42bdcca80e5b463d5d3741d6 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 14:10:33 -0500 Subject: [PATCH 07/12] playoff scraper works w/ modified team names --- .../0008_playerplayofftotalsdata.py | 48 +++++++++++++++++++ nba_data/nba_scraper_playoffs_totals.py | 13 ++++- 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 nba_data/migrations/0008_playerplayofftotalsdata.py diff --git a/nba_data/migrations/0008_playerplayofftotalsdata.py b/nba_data/migrations/0008_playerplayofftotalsdata.py new file mode 100644 index 0000000..8887e80 --- /dev/null +++ b/nba_data/migrations/0008_playerplayofftotalsdata.py @@ -0,0 +1,48 @@ +# Generated by Django 4.2 on 2023-05-01 18:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0007_alter_playertotalsdata_ast_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='PlayerPlayoffTotalsData', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('player_name', models.CharField(max_length=255, null=True)), + ('age', models.IntegerField(null=True)), + ('games', models.IntegerField(null=True)), + ('games_started', models.IntegerField(null=True)), + ('minutes_played', models.IntegerField(null=True)), + ('field_goals', models.IntegerField(null=True)), + ('field_attempts', models.IntegerField(null=True)), + ('field_percent', models.DecimalField(decimal_places=3, max_digits=6, null=True)), + ('three_fg', models.IntegerField(null=True)), + ('three_attempts', models.IntegerField(null=True)), + ('three_percent', models.DecimalField(decimal_places=3, max_digits=6, null=True)), + ('two_fg', models.IntegerField(null=True)), + ('two_attempts', models.IntegerField(null=True)), + ('two_percent', models.DecimalField(decimal_places=3, max_digits=6, null=True)), + ('effect_fg_percent', models.DecimalField(decimal_places=3, max_digits=6, null=True)), + ('ft', models.IntegerField(null=True)), + ('fta', models.IntegerField(null=True)), + ('ft_percent', models.DecimalField(decimal_places=3, max_digits=6, null=True)), + ('ORB', models.IntegerField(null=True)), + ('DRB', models.IntegerField(null=True)), + ('TRB', models.IntegerField(null=True)), + ('AST', models.IntegerField(null=True)), + ('STL', models.IntegerField(null=True)), + ('BLK', models.IntegerField(null=True)), + ('TOV', models.IntegerField(null=True)), + ('PF', models.IntegerField(null=True)), + ('PTS', models.IntegerField(null=True)), + ('team', models.CharField(default='', max_length=30)), + ('season', models.IntegerField(null=True)), + ], + ), + ] diff --git a/nba_data/nba_scraper_playoffs_totals.py b/nba_data/nba_scraper_playoffs_totals.py index c6030d5..202fe41 100644 --- a/nba_data/nba_scraper_playoffs_totals.py +++ b/nba_data/nba_scraper_playoffs_totals.py @@ -16,8 +16,19 @@ django.setup() from nba_data.models import PlayerPlayoffTotalsData # Change CHO to CHA 2014 and prior. +# Change NOP to NOH for 2013 and prior. +# Change OKC to SEA for 2008 and prior. +# Change NOH to NOK for 2007 and 2006 +# Change NOK to NOH for 2005 and prior. +# Change NOH to CHH for 2002 and prior. NOLA did not have a team before 2003. +# Change WAS to WSB for 1997 and prior. +# Change BRK to NJN for 2012 and prior. +# Change MEM to VAN for 2001 and prior. +# Change LAC to SDC for 1984 and prior. + + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'NJN', 'TOR', 'MEM', 'NOH', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] -teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHO'] team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] season = input('What season? ') From 163c0202a2ae2cb4b21dacfde1569b0731ab6f31 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 16:15:08 -0500 Subject: [PATCH 08/12] playoff advanced model --- .../0009_playerplayoffadvanceddata.py | 45 +++++++++++++++++++ nba_data/models.py | 32 +++++++++++++ nba_data/nba_scraper_playoffs_advanced.py | 0 3 files changed, 77 insertions(+) create mode 100644 nba_data/migrations/0009_playerplayoffadvanceddata.py create mode 100644 nba_data/nba_scraper_playoffs_advanced.py diff --git a/nba_data/migrations/0009_playerplayoffadvanceddata.py b/nba_data/migrations/0009_playerplayoffadvanceddata.py new file mode 100644 index 0000000..01274bf --- /dev/null +++ b/nba_data/migrations/0009_playerplayoffadvanceddata.py @@ -0,0 +1,45 @@ +# Generated by Django 4.2 on 2023-05-01 21:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0008_playerplayofftotalsdata'), + ] + + operations = [ + migrations.CreateModel( + name='PlayerPlayoffAdvancedData', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('player_name', models.CharField(max_length=255, null=True)), + ('age', models.IntegerField(null=True)), + ('games', models.IntegerField(null=True)), + ('minutes_played', models.IntegerField(null=True)), + ('PER', models.DecimalField(decimal_places=1, max_digits=5, null=True)), + ('TS_percent', models.DecimalField(decimal_places=3, max_digits=5, null=True)), + ('three_p_attempt_rate', models.DecimalField(decimal_places=3, max_digits=5, null=True)), + ('ft_attempt_rate', models.DecimalField(decimal_places=3, max_digits=5, null=True)), + ('orb_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('drb_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('trb_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('ast_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('stl_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('blk_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('tov_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('usg_percent', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('ows', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('dws', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('ws', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('ws_per_48', models.DecimalField(decimal_places=3, max_digits=5, null=True)), + ('obpm', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('dbpm', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('bpm', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('vorp', models.DecimalField(decimal_places=2, max_digits=5, null=True)), + ('team', models.CharField(default='', max_length=30)), + ('season', models.IntegerField(null=True)), + ], + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index c765bc0..ff6841c 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -135,6 +135,38 @@ class PlayerPlayoffTotalsData(models.Model): def __str__(self): return self.player_name + + +class PlayerPlayoffAdvancedData(models.Model): + player_name = models.CharField(max_length=255, null=True) + age = models.IntegerField(null=True) + games = models.IntegerField(null=True) + minutes_played = models.IntegerField(null=True) + PER = models.DecimalField(max_digits=5, decimal_places=1, null=True) + TS_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) + three_p_attempt_rate = models.DecimalField(max_digits=5, decimal_places=3, null=True) + ft_attempt_rate = models.DecimalField(max_digits=5, decimal_places=3, null=True) + orb_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + drb_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + trb_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + ast_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + stl_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + blk_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + tov_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + usg_percent = models.DecimalField(max_digits=5, decimal_places=2, null=True) + ows = models.DecimalField(max_digits=5, decimal_places=2, null=True) + dws = models.DecimalField(max_digits=5, decimal_places=2, null=True) + ws = models.DecimalField(max_digits=5, decimal_places=2, null=True) + ws_per_48 = models.DecimalField(max_digits=5, decimal_places=3, null=True) + obpm = models.DecimalField(max_digits=5, decimal_places=2, null=True) + dbpm = models.DecimalField(max_digits=5, decimal_places=2, null=True) + bpm = models.DecimalField(max_digits=5, decimal_places=2, null=True) + vorp = models.DecimalField(max_digits=5, decimal_places=2, null=True) + team = models.CharField(max_length=30, default='') + season = models.IntegerField(null=True) + + def __str__(self): + return self.player_name diff --git a/nba_data/nba_scraper_playoffs_advanced.py b/nba_data/nba_scraper_playoffs_advanced.py new file mode 100644 index 0000000..e69de29 From e51e25fe08ff8146bc5fa3add474cf67d63d9947 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 17:19:02 -0500 Subject: [PATCH 09/12] adjust model for outliers --- ...0_alter_playeradvanceddata_per_and_more.py | 23 ++++ ..._playerplayoffadvanceddata_bpm_and_more.py | 29 ++++ nba_data/models.py | 41 +++++- nba_data/nba_scraper_playoffs_advanced.py | 119 ++++++++++++++++ .../nba_scraper_playoffs_advanced_player.py | 127 ++++++++++++++++++ 5 files changed, 334 insertions(+), 5 deletions(-) create mode 100644 nba_data/migrations/0010_alter_playeradvanceddata_per_and_more.py create mode 100644 nba_data/migrations/0011_alter_playerplayoffadvanceddata_bpm_and_more.py create mode 100644 nba_data/nba_scraper_playoffs_advanced_player.py diff --git a/nba_data/migrations/0010_alter_playeradvanceddata_per_and_more.py b/nba_data/migrations/0010_alter_playeradvanceddata_per_and_more.py new file mode 100644 index 0000000..9cad619 --- /dev/null +++ b/nba_data/migrations/0010_alter_playeradvanceddata_per_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 4.2 on 2023-05-01 21:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0009_playerplayoffadvanceddata'), + ] + + operations = [ + migrations.AlterField( + model_name='playeradvanceddata', + name='PER', + field=models.DecimalField(decimal_places=1, max_digits=6, null=True), + ), + migrations.AlterField( + model_name='playerplayoffadvanceddata', + name='PER', + field=models.DecimalField(decimal_places=1, max_digits=6, null=True), + ), + ] diff --git a/nba_data/migrations/0011_alter_playerplayoffadvanceddata_bpm_and_more.py b/nba_data/migrations/0011_alter_playerplayoffadvanceddata_bpm_and_more.py new file mode 100644 index 0000000..c230652 --- /dev/null +++ b/nba_data/migrations/0011_alter_playerplayoffadvanceddata_bpm_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 4.2 on 2023-05-01 21:58 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('nba_data', '0010_alter_playeradvanceddata_per_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='playerplayoffadvanceddata', + name='bpm', + field=models.DecimalField(decimal_places=2, max_digits=5, null=True, validators=[django.core.validators.MinValueValidator(-100), django.core.validators.MaxValueValidator(100)]), + ), + migrations.AlterField( + model_name='playerplayoffadvanceddata', + name='dbpm', + field=models.DecimalField(decimal_places=2, max_digits=5, null=True, validators=[django.core.validators.MinValueValidator(-100), django.core.validators.MaxValueValidator(100)]), + ), + migrations.AlterField( + model_name='playerplayoffadvanceddata', + name='obpm', + field=models.DecimalField(decimal_places=2, max_digits=5, null=True, validators=[django.core.validators.MinValueValidator(-100), django.core.validators.MaxValueValidator(100)]), + ), + ] diff --git a/nba_data/models.py b/nba_data/models.py index ff6841c..f7c39ca 100644 --- a/nba_data/models.py +++ b/nba_data/models.py @@ -1,4 +1,5 @@ from django.db import models +from django.core.validators import MaxValueValidator, MinValueValidator # Create your models here. class PlayerData(models.Model): @@ -75,7 +76,7 @@ class PlayerAdvancedData(models.Model): age = models.IntegerField(null=True) games = models.IntegerField(null=True) minutes_played = models.IntegerField(null=True) - PER = models.DecimalField(max_digits=5, decimal_places=1, null=True) + PER = models.DecimalField(max_digits=6, decimal_places=1, null=True) TS_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) three_p_attempt_rate = models.DecimalField(max_digits=5, decimal_places=3, null=True) ft_attempt_rate = models.DecimalField(max_digits=5, decimal_places=3, null=True) @@ -142,7 +143,7 @@ class PlayerPlayoffAdvancedData(models.Model): age = models.IntegerField(null=True) games = models.IntegerField(null=True) minutes_played = models.IntegerField(null=True) - PER = models.DecimalField(max_digits=5, decimal_places=1, null=True) + PER = models.DecimalField(max_digits=6, decimal_places=1, null=True) TS_percent = models.DecimalField(max_digits=5, decimal_places=3, null=True) three_p_attempt_rate = models.DecimalField(max_digits=5, decimal_places=3, null=True) ft_attempt_rate = models.DecimalField(max_digits=5, decimal_places=3, null=True) @@ -158,13 +159,43 @@ class PlayerPlayoffAdvancedData(models.Model): dws = models.DecimalField(max_digits=5, decimal_places=2, null=True) ws = models.DecimalField(max_digits=5, decimal_places=2, null=True) ws_per_48 = models.DecimalField(max_digits=5, decimal_places=3, null=True) - obpm = models.DecimalField(max_digits=5, decimal_places=2, null=True) - dbpm = models.DecimalField(max_digits=5, decimal_places=2, null=True) - bpm = models.DecimalField(max_digits=5, decimal_places=2, null=True) + obpm = models.DecimalField(max_digits=5, decimal_places=2, null=True, validators=[MinValueValidator(-100), MaxValueValidator(100)]) + dbpm = models.DecimalField(max_digits=5, decimal_places=2, null=True, validators=[MinValueValidator(-100), MaxValueValidator(100)]) + bpm = models.DecimalField(max_digits=5, decimal_places=2, null=True, validators=[MinValueValidator(-100), MaxValueValidator(100)]) vorp = models.DecimalField(max_digits=5, decimal_places=2, null=True) team = models.CharField(max_length=30, default='') season = models.IntegerField(null=True) + def cap_extreme_values(self): + min_value = -100 + max_value = 100 + + obpm = float(self.obpm) + if obpm < min_value: + obpm = min_value + elif obpm > max_value: + obpm = max_value + self.obpm = obpm + + dbpm = float(self.dbpm) + if dbpm < min_value: + dbpm = min_value + elif dbpm > max_value: + dbpm = max_value + self.dbpm = dbpm + + bpm = float(self.bpm) + if bpm < min_value: + bpm = min_value + elif bpm > max_value: + bpm = max_value + self.bpm = bpm + + + def save(self, *args, **kwargs): + self.cap_extreme_values() + super(PlayerPlayoffAdvancedData, self).save(*args, **kwargs) + def __str__(self): return self.player_name diff --git a/nba_data/nba_scraper_playoffs_advanced.py b/nba_data/nba_scraper_playoffs_advanced.py index e69de29..77fee73 100644 --- a/nba_data/nba_scraper_playoffs_advanced.py +++ b/nba_data/nba_scraper_playoffs_advanced.py @@ -0,0 +1,119 @@ +import os +import sys +import django +import requests +from bs4 import BeautifulSoup +import csv +import json +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nba_stats.settings') +django.setup() + +from nba_data.models import PlayerPlayoffAdvancedData + +# Change CHO to CHA 2014 and prior. +# Change NOP to NOH for 2013 and prior. +# Change OKC to SEA for 2008 and prior. +# Change NOH to NOK for 2007 and 2006 +# Change NOK to NOH for 2005 and prior. +# Change NOH to CHH for 2002 and prior. NOLA did not have a team before 2003. +# Change WAS to WSB for 1997 and prior. +# Change BRK to NJN for 2012 and prior. +# Change MEM to VAN for 2001 and prior. +# Change LAC to SDC for 1984 and prior. + + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] + +team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] + +season = input('What season? ') + +for team in teams: + url_make = 'https://www.basketball-reference.com/teams/' + team + '/' + season + '.html' + response = requests.get(url_make) + + soup = BeautifulSoup(response.text, "html.parser") + + table = soup.find("table", {"id": "playoffs_advanced"}) + + if table is None: + print(f"Table not found for team {team} and season {season}.") + # sys.exit(1) + continue + + header_row = table.find("thead").find("tr") + rows = table.find("tbody").find_all("tr") + + + # Extract coloumn headers + headers = [col.text.strip() for col in header_row.find_all("th")][1:] + headers.extend(['team', 'season']) # Add 'team' and 'season' fields to headers + + # Extract rows as dictionaries + data = [] + for row in rows: + cols = row.find_all("td") + cols = [col.text.strip() for col in cols] + row_dict = {header: (None if col == "" else col) for header, col in zip(headers, cols)} + row_dict['team'] = team + row_dict['season'] = season + data.append(row_dict) + + + # locate current directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # save as CSV file + csv_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_playoffs_advanced_output.csv') + with open(csv_output_path, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=headers) + writer.writeheader() + for row in data: + writer.writerow(row) + + # Save as JSON + json_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_playoffs_advanced_output.json') + with open(json_output_path, 'w') as jsonfile: + json.dump(data, jsonfile) + + # save data to database + for row in data: + player_data = PlayerPlayoffAdvancedData( + player_name = row['Player'], + age = row['Age'], + games = row['G'], + minutes_played = row['MP'], + PER = row['PER'], + TS_percent = row['TS%'], + three_p_attempt_rate = row['3PAr'], + ft_attempt_rate = row['FTr'], + orb_percent = row['ORB%'], + drb_percent = row['DRB%'], + trb_percent = row['TRB%'], + ast_percent = row['AST%'], + stl_percent = row['STL%'], + blk_percent = row['BLK%'], + tov_percent = row['TOV%'], + usg_percent = row['USG%'], + ows = row['OWS'], + dws = row['DWS'], + ws = row['WS'], + ws_per_48 = row['WS/48'], + obpm = row['OBPM'], + dbpm = row['DBPM'], + bpm = row['BPM'], + vorp = row['VORP'], + team = row['team'], + season = row['season'], + ) + player_data.save() + +print('success') + + + \ No newline at end of file diff --git a/nba_data/nba_scraper_playoffs_advanced_player.py b/nba_data/nba_scraper_playoffs_advanced_player.py new file mode 100644 index 0000000..403ba3a --- /dev/null +++ b/nba_data/nba_scraper_playoffs_advanced_player.py @@ -0,0 +1,127 @@ +import os +import sys +import django +import requests +from bs4 import BeautifulSoup +import csv +import json +import time + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'nba_stats.settings') +django.setup() + +from nba_data.models import PlayerPlayoffAdvancedData + +# Change CHO to CHA 2014 and prior. +# Change NOP to NOH for 2013 and prior. +# Change OKC to SEA for 2008 and prior. +# Change NOH to NOK for 2007 and 2006 +# Change NOK to NOH for 2005 and prior. +# Change NOH to CHH for 2002 and prior. NOLA did not have a team before 2003. +# Change WAS to WSB for 1997 and prior. +# Change BRK to NJN for 2012 and prior. +# Change MEM to VAN for 2001 and prior. +# Change LAC to SDC for 1984 and prior. + + +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] + +single_team = ['GSW'] + +team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] + +# change player name here +target_player_name = 'Nemanja Nedović' + +season = input('What season? ') + + + +for team in single_team: + url_make = 'https://www.basketball-reference.com/teams/' + team + '/' + season + '.html' + response = requests.get(url_make) + + soup = BeautifulSoup(response.text, "html.parser") + + table = soup.find("table", {"id": "playoffs_advanced"}) + + if table is None: + print(f"Table not found for team {team} and season {season}.") + # sys.exit(1) + continue + + header_row = table.find("thead").find("tr") + rows = table.find("tbody").find_all("tr") + + + # Extract coloumn headers + headers = [col.text.strip() for col in header_row.find_all("th")][1:] + headers.extend(['team', 'season']) # Add 'team' and 'season' fields to headers + + # Extract rows as dictionaries + data = [] + for row in rows: + cols = row.find_all("td") + cols = [col.text.strip() for col in cols] + row_dict = {header: (None if col == "" else col) for header, col in zip(headers, cols)} + row_dict['team'] = team + row_dict['season'] = season + if row_dict['Player'] == target_player_name: + data.append(row_dict) + + + # locate current directory + script_dir = os.path.dirname(os.path.abspath(__file__)) + + # save as CSV file + csv_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_playoffs_advanced_output.csv') + with open(csv_output_path, "w", newline="") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=headers) + writer.writeheader() + for row in data: + writer.writerow(row) + + # Save as JSON + json_output_path = os.path.join(script_dir, '..', 'data', f'{team}_{season}_playoffs_advanced_output.json') + with open(json_output_path, 'w') as jsonfile: + json.dump(data, jsonfile) + + # save data to database + for row in data: + player_data = PlayerPlayoffAdvancedData( + player_name = row['Player'], + age = row['Age'], + games = row['G'], + minutes_played = row['MP'], + PER = row['PER'], + TS_percent = row['TS%'], + three_p_attempt_rate = row['3PAr'], + ft_attempt_rate = row['FTr'], + orb_percent = row['ORB%'], + drb_percent = row['DRB%'], + trb_percent = row['TRB%'], + ast_percent = row['AST%'], + stl_percent = row['STL%'], + blk_percent = row['BLK%'], + tov_percent = row['TOV%'], + usg_percent = row['USG%'], + ows = row['OWS'], + dws = row['DWS'], + ws = row['WS'], + ws_per_48 = row['WS/48'], + obpm = row['OBPM'], + dbpm = row['DBPM'], + bpm = row['BPM'], + vorp = row['VORP'], + team = row['team'], + season = row['season'], + ) + player_data.save() + +print('success') + + + \ No newline at end of file From f92e4cc54e742aa282869a4f0033d32b978dd7c8 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 17:55:44 -0500 Subject: [PATCH 10/12] find duplicates and delete script --- .../remove_duplicates_playoff_advanced.py | 27 +++++++++++++++++++ nba_data/nba_scraper_playoffs_advanced.py | 4 ++- 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 nba_data/management/commands/remove_duplicates_playoff_advanced.py diff --git a/nba_data/management/commands/remove_duplicates_playoff_advanced.py b/nba_data/management/commands/remove_duplicates_playoff_advanced.py new file mode 100644 index 0000000..d2414b5 --- /dev/null +++ b/nba_data/management/commands/remove_duplicates_playoff_advanced.py @@ -0,0 +1,27 @@ +from django.core.management.base import BaseCommand +from nba_data.models import PlayerPlayoffAdvancedData + + +class Command(BaseCommand): + help = 'Remove duplicate records for DAL and BOS in season 2010' + + def handle(self, *args, **options): + season = 2010 + teams = ['DAL', 'BOS'] + + for team in teams: + duplicates = PlayerPlayoffAdvancedData.objects.filter(season=season, team=team).order_by('player_name') + + # Group records by name + players_grouped = {} + for duplicate in duplicates: + if duplicate.player_name not in players_grouped: + players_grouped[duplicate.player_name] = [] + players_grouped[duplicate.player_name].append(duplicate) + + # Delete duplicates while keeping one record per player + for player_name, player_records in players_grouped.items(): + if len(player_records) > 1: + for player_record in player_records[1:]: + player_record.delete() + self.stdout.write(self.style.SUCCESS(f"Deleted duplicate {player_name} for {team} in season {season}")) \ No newline at end of file diff --git a/nba_data/nba_scraper_playoffs_advanced.py b/nba_data/nba_scraper_playoffs_advanced.py index 77fee73..9bb8fc4 100644 --- a/nba_data/nba_scraper_playoffs_advanced.py +++ b/nba_data/nba_scraper_playoffs_advanced.py @@ -27,7 +27,9 @@ from nba_data.models import PlayerPlayoffAdvancedData # Change LAC to SDC for 1984 and prior. -teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'BRK', 'TOR', 'MEM', 'NOP', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] +teams = ['HOU' ,'PHI', 'BOS', 'NYK', 'NJN', 'TOR', 'MEM', 'NOH', 'DAL', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] + +teams2 = ['HOU' ,'PHI', 'NYK', 'NJN', 'TOR', 'MEM', 'NOH', 'SAS', 'DEN', 'MIN', 'OKC', 'UTA', 'POR', 'MIL', 'CLE', 'CHI', 'IND', 'DET', 'SAC', 'PHO', 'GSW', 'LAC', 'LAL', 'MIA', 'ATL', 'WAS', 'ORL', 'CHA'] team_abbreviations = ['ATL', 'BOS', 'BRK', 'CHI', 'CHO', 'CLE', 'DAL', 'DEN', 'DET', 'GSW', 'HOU', 'IND', 'LAC', 'LAL', 'MEM', 'MIA', 'MIL', 'MIN', 'NOP', 'NYK', 'OKC', 'ORL', 'PHI', 'PHO', 'POR', 'SAC', 'SAS', 'TOR', 'UTA', 'WAS'] From 0b18de5a8246865310b69c1719e983ce0270d695 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 22:01:00 -0500 Subject: [PATCH 11/12] route and view to display top 20 players by pts ws --- nba_data/urls.py | 3 ++- nba_data/views.py | 43 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/nba_data/urls.py b/nba_data/urls.py index 3c8a049..2a90c94 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, Top20ScorersPost2009WS urlpatterns = [ path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), @@ -17,4 +17,5 @@ urlpatterns = [ path('api/points_per_game_histogram//', PointsPerGameHistogramBySeasonList.as_view(), name='points_per_game_histogram'), path('api/top_pts_scatter_plot/', TopPtsScatterPlotData.as_view(), name='top_pts_scatter_plot'), path('api/top_pts_scatter_plot_fast/', TopPtsScatterPlotDataFast.as_view(), name='top_pts_scatter_plot_fast'), + path('api/top_20_post_2009_PTS_ws/', Top20ScorersPost2009WS.as_view(), name='top_20_post_2009_PTS_ws') ] \ No newline at end of file diff --git a/nba_data/views.py b/nba_data/views.py index 680787c..bfdaca1 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -1,7 +1,7 @@ from django.shortcuts import render from django.http import JsonResponse from django.core import serializers -from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData +from .models import PlayerData, PlayerTotalsData, PlayerAdvancedData, PlayerPlayoffTotalsData, PlayerPlayoffAdvancedData from rest_framework import generics from .serializers import PlayerDataSerializer, HistogramDataSerializer from rest_framework.views import APIView @@ -252,3 +252,44 @@ class TopPtsScatterPlotDataFast(APIView): }) return Response(result) + + +# Top 20 players in playoffs by PTS X ws for scatter plot in chart.js + +class Top20ScorersPost2009WS(APIView): + def get(self, request): + # Get the top 20 scorers after the 2015 season + top_scorers = PlayerPlayoffTotalsData.objects \ + .filter(season__gt=2009) \ + .values('player_name') \ + .annotate(total_pts=Sum('PTS')) \ + .order_by('-total_pts')[:20] + + # Get the total WS for each of the top 20 scorers after the 2015 season + chart_data = [] + for scorer in top_scorers: + player_name = scorer['player_name'] + total_ws = PlayerPlayoffAdvancedData.objects \ + .filter(player_name=player_name, season__gt=2009) \ + .aggregate(total_ws=Sum('ws'))['total_ws'] + + chart_data.append({ + 'x': scorer['total_pts'], + 'y': total_ws, + 'player_name': player_name + }) + + # Sort the data by total_ws DESC + chart_data = sorted(chart_data, key=lambda x: x['y'], reverse=True) + + # Prepare the response data for chart.js + response_data = { + 'datasets': [ + { + 'data': chart_data + } + ] + } + + return Response(response_data) + From 6995af94b2c2493fdc120541445616fd3247d473 Mon Sep 17 00:00:00 2001 From: nprasad2077 <114438461+nprasad2077@users.noreply.github.com> Date: Mon, 1 May 2023 22:07:35 -0500 Subject: [PATCH 12/12] additional routes for post 2014 and post 2018 eras --- nba_data/urls.py | 6 ++-- nba_data/views.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/nba_data/urls.py b/nba_data/urls.py index 2a90c94..0a9df80 100644 --- a/nba_data/urls.py +++ b/nba_data/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, Top20ScorersPost2009WS +from .views import PlayerDataList, PlayerDataByTeamList, PlayerDataBySeasonList, PlayerDataByNameList, TopScorersbySeasonList, ThreeTwoTopPointTrends, TopAssistsBySeasonList, TopReboundsBySeasonList, TopBlocksBySeasonList, TopStealsBySeasonList, TopOffensiveReboundsBySeasonList, TopDefensiveReboundsBySeasonList, PointsPerGameHistogramBySeasonList, TopPtsScatterPlotData, TopPtsScatterPlotDataFast, Top20ScorersPost2009WS, Top20ScorersPost2014WS, Top20ScorersPost2018WS urlpatterns = [ path('api/playerdata/', PlayerDataList.as_view(), name='playerdata_list'), @@ -17,5 +17,7 @@ urlpatterns = [ path('api/points_per_game_histogram//', PointsPerGameHistogramBySeasonList.as_view(), name='points_per_game_histogram'), path('api/top_pts_scatter_plot/', TopPtsScatterPlotData.as_view(), name='top_pts_scatter_plot'), path('api/top_pts_scatter_plot_fast/', TopPtsScatterPlotDataFast.as_view(), name='top_pts_scatter_plot_fast'), - path('api/top_20_post_2009_PTS_ws/', Top20ScorersPost2009WS.as_view(), name='top_20_post_2009_PTS_ws') + path('api/top_20_post_2009_PTS_ws/', Top20ScorersPost2009WS.as_view(), name='top_20_post_2009_PTS_ws'), + path('api/top_20_post_2014_PTS_ws/', Top20ScorersPost2014WS.as_view(), name='top_20_post_2014_PTS_ws'), + path('api/top_20_post_2018_PTS_ws/', Top20ScorersPost2018WS.as_view(), name='top_20_post_2018_PTS_ws'), ] \ No newline at end of file diff --git a/nba_data/views.py b/nba_data/views.py index bfdaca1..17bdecf 100644 --- a/nba_data/views.py +++ b/nba_data/views.py @@ -292,4 +292,79 @@ class Top20ScorersPost2009WS(APIView): } return Response(response_data) + + +class Top20ScorersPost2014WS(APIView): + def get(self, request): + # Get the top 20 scorers after the 2015 season + top_scorers = PlayerPlayoffTotalsData.objects \ + .filter(season__gt=2014) \ + .values('player_name') \ + .annotate(total_pts=Sum('PTS')) \ + .order_by('-total_pts')[:20] + + # Get the total WS for each of the top 20 scorers after the 2015 season + chart_data = [] + for scorer in top_scorers: + player_name = scorer['player_name'] + total_ws = PlayerPlayoffAdvancedData.objects \ + .filter(player_name=player_name, season__gt=2014) \ + .aggregate(total_ws=Sum('ws'))['total_ws'] + + chart_data.append({ + 'x': scorer['total_pts'], + 'y': total_ws, + 'player_name': player_name + }) + + # Sort the data by total_ws DESC + chart_data = sorted(chart_data, key=lambda x: x['y'], reverse=True) + + # Prepare the response data for chart.js + response_data = { + 'datasets': [ + { + 'data': chart_data + } + ] + } + + return Response(response_data) + +class Top20ScorersPost2018WS(APIView): + def get(self, request): + # Get the top 20 scorers after the 2015 season + top_scorers = PlayerPlayoffTotalsData.objects \ + .filter(season__gt=2018) \ + .values('player_name') \ + .annotate(total_pts=Sum('PTS')) \ + .order_by('-total_pts')[:20] + + # Get the total WS for each of the top 20 scorers after the 2015 season + chart_data = [] + for scorer in top_scorers: + player_name = scorer['player_name'] + total_ws = PlayerPlayoffAdvancedData.objects \ + .filter(player_name=player_name, season__gt=2018) \ + .aggregate(total_ws=Sum('ws'))['total_ws'] + + chart_data.append({ + 'x': scorer['total_pts'], + 'y': total_ws, + 'player_name': player_name + }) + + # Sort the data by total_ws DESC + chart_data = sorted(chart_data, key=lambda x: x['y'], reverse=True) + + # Prepare the response data for chart.js + response_data = { + 'datasets': [ + { + 'data': chart_data + } + ] + } + + return Response(response_data)