Scraping and Analyzing 1000 NBA Games with Python

The NBA is one of the most popular sports leagues in the world, with millions of passionate fans following their favorite teams and players. In the modern era, the proliferation of technology and data has allowed for more advanced analysis of the game than ever before. NBA teams now employ entire analytics departments to collect and examine data in search of insights that can provide a competitive edge.

While teams have access to expensive tracking systems and other proprietary data sources, regular fans can still perform their own NBA data analysis at home thanks to the power of web scraping with Python. In this article, we‘ll go through the process of scraping a dataset of 1000 NBA games and examining it to uncover interesting insights about the league.

Scraping NBA Game Data

The first step is to actually collect the data we want to analyze. Luckily, the NBA‘s official website, stats.nba.com, provides a wealth of data on every NBA game for free. Here you can find box scores, play-by-play logs, shot charts, and more for games going back over 20 years.

To scrape this data, we can use Python libraries like Requests to download the webpage data and BeautifulSoup to parse and extract the relevant parts. However, the NBA‘s site can be tricky to scrape due to some anti-bot measures they have in place. I recommend using the nba_api Python package, which provides a clean interface for making API calls to the NBA site and handles all the authentication and other details for you behind the scenes.

Once you have the raw game data, it‘s important to parse it into a structured format for analysis. The pandas library is the perfect tool for this – we can store each game as a row in a DataFrame with columns for things like date, teams, score, stats, etc. Most of the data from stats.nba.com comes as JSON, which can be easily loaded into a DataFrame using pd.json_normalize().

For this project, let‘s scrape the game data for 1000 randomly selected games from the 2022-23 NBA regular season. We‘ll collect key data points like:

  • Date
  • Teams
  • Final score
  • Quarter-by-quarter score
  • Field goals made/attempted
  • 3-pointers made/attempted
  • Free throws made/attempted
  • Offensive/defensive rebounds
  • Assists
  • Steals
  • Blocks
  • Turnovers

This will give us a solid foundation of box score stats to analyze. We could collect more granular data like shot charts or play-by-play as well, but to keep things simple we‘ll stick to this relatively small set of game-level features.

Exploratory Analysis

With our dataset collected and cleaned, it‘s time for the fun part – exploring the data to see what insights we can uncover! Let‘s start with some basic analysis and data visualization.

Using pandas, we can easily calculate summary stats for our numeric columns like points, rebounds, assists, etc:

print(df.describe())
             PTS    FG   FGA    FG%  3P   3PA    3P%   FT  FTA    FT%  OREB  DREB   REB   AST  STL  BLK  TOV
count  2000.000 2000 2000  2000.0  2000 2000 2000.0 2000 2000 2000.0  2000  2000  2000  2000 2000 2000 2000 
mean   112.015   41   88  0.465    12   34  0.352   18   23  0.776     9    33    42    25    7    5   13
std      12.019    5   10  0.035     4    9  0.046    4    5  0.069     3     5     8     4    3    3    3
min      76.000   24   54  0.333     1    8  0.080    2    3  0.400     0    15    15     9    0    0    3
25%     103.000   37   81  0.443    10   28  0.328   15   19  0.750     7    30    37    23    5    3   11 
50%     112.000   41   88  0.465    12   34  0.354   18   22  0.778     9    33    42    25    7    4   13
75%     120.000   45   96  0.489    15   40  0.380   21   26  0.809    11    37    47    28    9    6   16 
max     157.000   62  125  0.585    29   65  0.533   39   48  0.938    25    58    79    43   17   17   30

This gives a high-level view of the distribution of stats across our game sample. We can see that the average NBA game in our dataset had a combined score of 224 points, with each team attempting around 88 field goals. The median 3-point attempt rate was 34 per game. There were some extreme outlier games as well, with the highest scoring game totaling 314 points!

We can also look at the relationships between different variables using scatter plots. For example, let‘s visualize the correlation between a team‘s number of 3-point attempts and their offensive efficiency (points scored per 100 possessions):

fig, ax = plt.subplots(figsize=(8, 6))
ax.scatter(df[‘3PA‘], df[‘OFFRTG‘])
ax.set_xlabel(‘3-Point Attempts‘)
ax.set_ylabel(‘Offensive Rating‘)
ax.set_title(‘3PA vs Offensive Efficiency‘)
plt.show()

There is a slight positive correlation, indicating that teams who attempt more 3s tend to have higher offensive ratings, which makes sense in the modern NBA. But there are also some teams who buck the trend, with low 3PA but elite offenses likely driven by high percentage shots at the rim and from midrange. The relationship is not extremely strong, with an r-value of 0.29.

This just scratches the surface of the types of analysis possible. With a bit more data manipulation in pandas, we could start to answer questions like:

  • What were the most common final scores?
  • What was the biggest comeback win?
  • Which teams played at the fastest/slowest paces?
  • Who had the most dominant rebounding games?
  • What was the highest scoring individual quarter?
  • Do teams that make more free throws tend to win more often?
  • Which teams had the most/least inconsistent performances?

The possibilities are nearly endless, even with a relatively simple set of box score features for 1000 games. As we slice and group the data in different ways and visualize the results, we can uncover all sorts of insights about trends and outliers across the sample of games and teams.

Limitations and Next Steps

Of course, as with any data analysis project, there are limitations to what we can conclude from this exercise. A sample of 1000 games is not a huge dataset in the grand scheme of things, and there is always the possibility of selection bias in how the games were chosen. We also didn‘t collect any data on the specific players and lineups involved in each game, which heavily influence the outcomes. Any relationships or patterns we observe could potentially just be noise.

To make our analysis more robust, we could collect a larger sample covering multiple seasons. Adding in additional data like player tracking stats, shot locations, play-by-play info, and other more granular details would allow us to examine the games at a deeper level and control for more variables.

Another potential next step would be to use our dataset to build a predictive model. With game data as features and the margin of victory as a target variable, we could train a machine learning algorithm to predict the outcomes of future games based on factors like team strength, pace, 3-point shooting, rebounding margin, etc. There are challenges to building an accurate NBA betting model, but it‘s a fun use case for this type of data.

At the end of the day, this project demonstrates the power of web scraping and data analysis to surface insights from publicly available data. With some basic Python skills, an NBA fan can collect, explore, and visualize a large dataset of games to better understand league-wide trends and how their favorite teams match up. While 1000 games is a relatively small sample, it‘s enough to get a taste of the types of analysis made possible by the intersection of data, programming, and basketball in the digital era.

How useful was this post?

Click on a star to rate it!

Average rating 0 / 5. Vote count: 0

No votes so far! Be the first to rate this post.

Similar Posts