Locked learning resources

Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Locked learning resources

This lesson is for members only. Join us and get access to thousands of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Linking Axes

In this lesson you will practice linking the axes of multiple plots. Linking is the process of syncing elements of different visualizations within a layout.

For this example, the visualization will be able to pan to different segments of a team’s schedule and examine various game stats. Each stat will be represented by its own plot in a two-by-two gridplot().

You will start by collecting the data from the team_stats DataFrame within the read_nba_data.py file, selecting the Philadelphia 76ers as the team of interest.

For more on the CategoricalColorMapper, see the Colors section of Handling Categorical Data on Bokeh’s User Guide.

For additional details on linking plots can be found at Linking Plots in the Bokeh User Guide.

File: read_nba_data.py

import pandas as pd 

# Read the csv files
player_stats = pd.read_csv('data/2017-18_playerBoxScore.csv',
team_stats = pd.read_csv('data/2017-18_teamBoxScore.csv',
standings = pd.read_csv('data/2017-18_standings.csv',

# Create west_top_2
west_top_2 = (standings[(standings['teamAbbr'] == 'HOU') | 
              (standings['teamAbbr'] == 'GS')]
              .loc[:, ['stDate', 'teamAbbr', 'gameWon']]
              .sort_values(['teamAbbr', 'stDate']))

# Find players who took at least 1 three-point shot during the season
three_takers = player_stats[player_stats['play3PA'] > 0]

# Clean up the player names, placing them in a single column
three_takers['name'] = [f'{p["playFNm"]} {p["playLNm"]}'
                        for _, p in three_takers.iterrows()]

# Aggregate the total three-point attempts and makes for each player
three_takers = (three_takers.groupby('name')
                            .loc[:,['play3PA', 'play3PM']]
                            .sort_values('play3PA', ascending=False))

# Filter out anyone who didn't take at least 100 three-point shots
three_takers = three_takers[three_takers['play3PA'] >= 100].reset_index()

# Add a column with a calculated three-point percentage (made/attempted)
three_takers['pct3PM'] = three_takers['play3PM'] / three_takers['play3PA']

# Philadelphia 76ers data isolated
phi_gm_stats = (team_stats[(team_stats['teamAbbr'] == 'PHI') & 
                           (team_stats['seasTyp'] == 'Regular')]
                .loc[:, ['gmDate',

# Add game number
phi_gm_stats['game_num'] = range(1, len(phi_gm_stats)+1)

# Derive a win_loss column
win_loss = []
for _, row in phi_gm_stats.iterrows():

    # If the 76ers score more poins, its a win
    if row['teamPTS'] > row['opptPTS']:

# Add the win_loss data to the DataFrame
phi_gm_stats['winLoss'] = win_loss

File: LinkAxes01.py

# Bokeh Libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CategoricalColorMapper, Div
from bokeh.layouts import gridplot, column

# Load in Data
from read_nba_data import phi_gm_stats

# Out to file
            title='76ers Game Log')

# Store the data in a ColumnDataSource
gm_stats_cds = ColumnDataSource(phi_gm_stats)

# Create a CategoricalColorMapper that assigns a color to wins and losses
win_loss_mapper = CategoricalColorMapper(factors = ['W', 'L'],
                                         palette=['green', 'red'])

# Create a dict with the stat name and its corresponding column in the data
stat_names = {'Points': 'teamPTS',
              'Assists': 'teamAST',
              'Rebounds': 'teamTRB',
              'Turnovers': 'teamTO'}

# The figure for each stat will be held in this dict
stat_figs = {}

# For each stat in the dict
for stat_label, stat_col in stat_names.items():

    # Create a figure
    fig = figure(y_axis_label=stat_label,
                 plot_height=200, plot_width=400,
                 x_range=(1, 10), tools=['xpan', 'reset', 'save'])

    # Configure vbar
    fig.vbar(x='game_num', top=stat_col, source=gm_stats_cds, width=0.9,
             color=dict(field='winLoss', transform=win_loss_mapper))

    # Add the figure to stats_figs dict
    stat_figs[stat_label] = fig

# Create layout
grid = gridplot([[stat_figs['Points'], stat_figs['Assists']],
                [stat_figs['Rebounds'], stat_figs['Turnovers']]])

# Link together the x-axes
stat_figs['Points'].x_range = \
    stat_figs['Assists'].x_range = \
    stat_figs['Rebounds'].x_range = \

# Add a title for the entire visualization using Div
html = """<h3>Philadelphia 76ers Game Log</h3>
<b><i>2017-18 Regular Season</i></b>
<i>Wins in green, losses in red</i>
sup_title = Div(text=html)

# Visualize
show(column(sup_title, grid))

00:00 For this next visualization, you’ll get to try out linking axes across multiple visualizations. This is going to focus on team statistics for the Philadelphia 76ers.

00:10 I need you to go back into the read_nba_data file, and you’re going to create a new DataFrame inside of here. So, past your three_takers modifications, this is where you’re going to focus on the Philadelphia 76ers information. Okay, so it’s going to be called phi_gm_stats, and you’re going to take it from team_stats.

00:34 And here you’re going to take team_stats where the team abbreviation is equal to 'PHI', for Philadelphia, and the season type is regular season.

00:54 Next, you’re going to narrow that frame a little bit to just these columns, 'gmDate',

01:03 'teamPTS', 'teamTRB'those are rebounds, team assists—'tmAST', and team turnovers. And then you’re going to take the opposition’s points, opposing teams points.

01:27 And sort these values, by the game date. Great, okay. I’m going to add a couple columns. The first column is going to be the game number that you’re going to add, so phi_gm_stats, a new column 'game_num', starting with a range of 1 to the end, or basically the length of the game_stats that have been gathered, and adding 1 because of the zero start. Okay. And then you’re going to create a new column, which will be a win_loss column. First, you’re going to start with creating a list—an empty one. And then in a for loop, for _, row in phi_gm_stats.iterrows(): this is going to be a quick if statement, if the 76ers score more points—the team points—are greater than the same row’s opposing team’s points, win_loss append a capital 'W' for win, else: win_loss.append('L'). So again, if they score more points, here a greater than (>)… That’s your for loop. And then add the win_loss data to the DataFrame. So phi_gm_stats, a new column 'winLoss' is equal to the values from win_loss. Great.

03:02 All right, let’s check it out. So, you’re saving.

03:11 from read_nba_data import phi_gm_stats. Looks like I made an error here.

03:26 Oh, 'seasType' (season type) doesn’t have an 'e'. It’s just 'seasTyp'. Okay. That’s what’s wrong. Saving again, let me try my import again.

03:42 Okay, 82 rows, 8 columns. Let’s just look at the head of it. Looks good. Here’s the game dates, team points, and the winLoss column you created. Looks good! Again, you got total points, rebounds, assists, turnovers, and then opposing team points, and then the game number. That all looks great.

03:59 It looks like you’re ready to use this data for linking your axes. Now I need to have you create the visualization. To do that, you’re going to use a new file. Call it LinkAxes, and I’ll put a 01 on it (LinkAxes01.py). Okay.

04:15 So, you’re going to start again loading in some Bokeh libraries. From plotting import figure and show, from io output to a static HTML file, from bokeh.models you’ll continue to use the ColumnDataSource. This time you’re going to use something new. It’s called a CategoricalColorMapper, and basically it maps factors to colors.

04:41 You’re going to use that for wins and losses, like the ones you just set up a moment ago. And the other one that is new is you’re going to create a Div, which will just allow us to handle text a little differently.

04:51 If you’ve done HTML development, you’ve probably worked with <div> before. Okay. And from bokeh.layouts, we’re going to use gridplot and column. Great. Let’s load in the data. From read_nba_data in this case, you just need the Philly game stats, so phi_gm_stats. Great. Let’s create the output file to an HTML file and give it a title up top of '76ers Game Log'.

05:23 Now, the day that you just loaded in, you need to create a ColumnDataSource for it. So call it gm_stats_cds, (game stats ColumnDataSource), and for that you’re going to use the phi_gm_stats that you imported a moment ago.

05:37 Okay. The information is going to be shown in little columns and those vertical bars are going to be green or red. You’re going to use a CategoricalColorMapper to do that. Create that, and name it win_loss_mapper.

05:54 And then the factors it’s going to use will be a list. In this case, the letters capital 'W' for win and 'L' for lossthis is what you were setting up in the data earlier.

06:04 And then the palette that it’s going to map to is a list also. In this case, 'green' for win and 'red' for loss. Great! Okay.

06:13 There’s more information about handling colors in Bokeh’s User Guide, and the article this tutorial’s based upon shows more links to that information. You’re going to visualize four stats that you set aside earlier—the team points, the assists, the rebounds, and the turnovers. To do some of that, I’m going to have you create a dictionary with the stats’ names and then the column data that they’re going to show. So, create a dictionary.

06:42 stats_names, 'Points' will be team points, 'teamPTS'. 'Assist' will be 'teamAST'. 'Rebounds' is 'teamTRB', and 'Turnovers' is 'teamTO'.

07:01 Great. So there’s the dictionary.

07:06 You’re going to create four different figures. We can have a for loop walk through all the different stats and kind of create these different figures for us programmatically to save a little bit of effort.

07:18 So to start that, you’re going to create stat_figs as an empty dictionary.

07:30 Okay. For each stat_label, stat_column in stat_names.items() you’re going to create a figure.

07:44 The y_axis_label will be the stat_label that you created earlier.

07:51 Each plot will have a height of 200 and a width of 400. You’re going to set a range that will be shown initially of 1 to 10, even though there will be more games than that.

08:06 And the tools that you’re going to provide the user of your visualization is the 'xpan', 'reset', and 'save'. Great.

08:16 So each fig will get one of these and then each fig will get a .vbar configured. The x for that .vbar() will be the 'game_num' column.

08:28 The top for each of these will be the 'stat_col' value. And the source you’re pulling from is the 'gmStats'. And the width for each bar, set it to 0.9.

08:45 For the color, this is where you’re going to use a dictionary and you’re pulling the 'winLoss' and transforming it using the win_loss_mapper.

09:02 And lastly, you need to add the figure to the stat_figs dictionary. For each stat_label, put in the fig. Great. Now you need to create your layout, grid using gridplot.

09:28 The first stat_figs you’re going to use is 'Points'.

09:36 The second will be 'Assists'. Those are the two that’ll be across the top. Then 'Rebounds' and 'Turnovers'. So again, it’s sort of a matrix, where 'Points' and 'Assists' will be on the top two.

09:54 And then after that list is done—sort of a list of lists, right? So you have this set and then this is the second set. So these will be on the bottom half. Next up, you need to link together the x-axes. Okay.

10:10 # Link together x-axes. To do that, you’ll take stat_figs['Points'] and set the .x_range equal to—and you’re continuing the line—stat_figs['Assists'],

10:30 and it is also equal to 'Rebounds' and 'Turnovers'. So each of these x ranges are equal to each other. The `` (slashes) are allowing you to continue the line.

10:47 And the last step is to set up a title bar. And in this case, this is where you’re going to use that Div element that you imported earlier.

10:56 You’re going to create a <h3>,

11:02 close your heading. I’m just making a really long string here. And then bold and italicized '2017-18 Regular Season'. Close the italics, create a line break. Actually, close the bold on that second line.

11:21 And then this last line you want to italicize also, 'Wins in green, losses in red'.

11:30 Okay. You might need to close your quote. sub_title would be a Div() with its text equal to the html that you created just a moment ago, the variable.

11:40 And last, time to view the visualization. I’m going to create a column that will have sup_title, which is the Div just above the whole grid that you set up.

11:51 So again, just a vertical title, this’ll be on top, and the grid below. All right. Going to save, not too bad. And let’s try it out. Okay. So here you go. Starting at game 1 here, and as you move through game 1 to 10, you can see the wins.

12:10 These are the amount of points for each game. The more points, the more likely to could win, it looks like. It makes sense. And then you’ll see that the axes are linked.

12:19 This is currently using the 'xpan' tool that’s selected. So game 6 was a lower amount of assists, but it’s still a win. Interesting to see which statistics, if any, lead toward a win or a loss.

12:35 Much better end of the season. Pretty neat! And then you could hit this reset here, that’ll take it always back to the beginning, and then pressing the save tool in the toolbar creates a PNG file based on how the visualization is laid out currently. Now that you know how to link axes, next you’ll start linking selections—coming up in the next lesson.

Avatar image for Dawn0fTime

Dawn0fTime on Aug. 8, 2021

When I save the PNG, it only saves the Turnovers plot. Is there a way to get it to grab the entire grid?

Become a Member to join the conversation.