plotly#

We’ve now seen the basics of plotting with pandas and matplotlib. We saw seaborn. Let’s try another package, called plotly, that lets us create interactive graphics.

Why interactive?#

Interactive graphics let you:

  • Hover over points to see exact values

  • Zoom into specific regions

  • Pan across the data

  • Select subsets of points

  • Toggle series on and off

This makes plotly ideal for exploring data and for presentations where the audience wants to interact. For static reports and papers, stick with matplotlib.

To install the Plotly package, you’ll need to use pip. As before, you can do this inside of a cell in your notebook, or in the terminal.

pip install plotly

Plotly has two main APIs:

  • Plotly Express (plotly.express or px) — Simple, high-level functions for common plots

  • Plotly Graph Objects (plotly.graph_objects or go) — More control for custom visualizations

We’ll use both.

# Set-up

import numpy as np
import pandas as pd

# This brings in all of matplotlib
import matplotlib as mpl 

# This lets us refer to the pyplot part of matplot lib more easily. Just use plt!
import matplotlib.pyplot as plt

# Bring in Plotly Express
import plotly.express as px

# Bring in Plotly graphic objects
import plotly.graph_objects as go

import plotly.offline as py

# Keeps warnings from cluttering up our notebook. 
import warnings
warnings.filterwarnings('ignore')

# Include this to have plots show up in your Jupyter notebook.
%matplotlib inline 

# Read in some eod prices
stocks = pd.read_csv('https://raw.githubusercontent.com/aaiken1/fin-data-analysis-python/main/data/tr_eikon_eod_data.csv',
                  index_col=0, parse_dates=True)  

stocks.dropna(inplace=True)  

from janitor import clean_names

stocks = clean_names(stocks)

stocks.info()
<class 'pandas.DataFrame'>
DatetimeIndex: 2138 entries, 2010-01-04 to 2018-06-29
Data columns (total 12 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   aapl_o  2138 non-null   float64
 1   msft_o  2138 non-null   float64
 2   intc_o  2138 non-null   float64
 3   amzn_o  2138 non-null   float64
 4   gs_n    2138 non-null   float64
 5   spy     2138 non-null   float64
 6   _spx    2138 non-null   float64
 7   _vix    2138 non-null   float64
 8   eur=    2138 non-null   float64
 9   xau=    2138 non-null   float64
 10  gdx     2138 non-null   float64
 11  gld     2138 non-null   float64
dtypes: float64(12)
memory usage: 217.1 KB

Let’s make a simple line graph of prices for Apple. As a reminder, our date is our index in this DataFrame.

fig = px.line(stocks, x=stocks.index, y='aapl_o', title='Apple Price History')
fig.show()

Not bad! You can create what plotly calls graphic objects. You than add traces, similar to axes, and start to layer things together. Here, we’ll create our “blank” figure and then add three more price sequences.

# Create traces
fig = go.Figure()
fig.add_trace(go.Scatter(x=stocks.index, y=stocks.aapl_o,
                    mode='lines',
                    name='AAPL'))
fig.add_trace(go.Scatter(x=stocks.index, y=stocks.msft_o,
                    mode='lines',
                    name='MSFT'))
fig.show()

We can create a histogram of returns too. I added a rug on the top which helps you see the distribution and outliers better. I am also showing the percentage of observations in a bin, not a count.

And, I made a bunch of other changes, just to give you an idea of the syntax.

stocks['aapl_ret'] = np.log(stocks.aapl_o / stocks.aapl_o.shift(1))  

fig = px.histogram(stocks, x='aapl_ret', 
                   marginal="rug", # That thing at the top!
                   histnorm='percent', 
                   opacity=0.75, # alpha
                   width=600, #pixels
                   height=400,
                   template="simple_white")

fig.update_layout(
    title_text='Apple Return Distribution', # title of plot
    xaxis_title_text='Return', # xaxis label
    yaxis_title_text='Percent', # yaxis label
    bargap=0.2, # gap between bars of adjacent location coordinates
    bargroupgap=0.1 # gap between bars of the same location coordinates
)


fig.show()

Range sliders for time series#

One of plotly’s most useful features for finance is the range slider. It lets you zoom into specific time periods by dragging a slider below the chart. This is essential when exploring long time series.

# Add a range slider to a line chart
fig = px.line(stocks, x=stocks.index, y='aapl_o', title='Apple Price History (Use the slider below!)')

# This one line adds the range slider
fig.update_xaxes(rangeslider_visible=True)

fig.show()

Drag the handles on the slider to zoom into specific time periods. You can also click and drag on the main chart to pan around. Double-click to reset the view.

Candlestick charts#

Candlestick charts are essential for finance. Each “candle” shows a trading period (usually a day) with four prices:

  • Open: Where the price started

  • High: The highest price reached

  • Low: The lowest price reached

  • Close: Where the price ended

The color tells you the direction:

  • Green (or hollow): Price went up (close > open)

  • Red (or filled): Price went down (close < open)

Our current data only has closing prices, so we need OHLC data. Let’s use yfinance to get it.

import yfinance as yf

# Download OHLC data for Apple - just a few months to keep it readable
aapl_ohlc = yf.download('AAPL', start='2024-01-01', end='2024-06-30', progress=False)
aapl_ohlc.head()
Price Close High Low Open Volume
Ticker AAPL AAPL AAPL AAPL AAPL
Date
2024-01-02 183.731293 186.502507 181.999286 185.225762 82488700
2024-01-03 182.355591 183.968836 181.544015 182.325900 58414500
2024-01-04 180.039658 181.207518 179.020249 180.277180 71983600
2024-01-05 179.317139 180.880895 178.317529 180.118823 62379700
2024-01-08 183.652130 183.691727 179.633876 180.217806 59144500
# Create the candlestick chart
fig = go.Figure(data=[go.Candlestick(
    x=aapl_ohlc.index,
    open=aapl_ohlc['Open'],
    high=aapl_ohlc['High'],
    low=aapl_ohlc['Low'],
    close=aapl_ohlc['Close']
)])

fig.update_layout(
    title='AAPL Candlestick Chart',
    yaxis_title='Price ($)',
    xaxis_title='Date'
)

fig.show()

Hover over a candle to see all four prices. The vertical “wicks” show the high and low, while the body shows the open and close. Green candles mean the stock went up that day; red means it went down.

Notice that plotly automatically adds a range slider to candlestick charts. You can turn this off with fig.update_layout(xaxis_rangeslider_visible=False) if you prefer.

Price and volume together#

A common finance chart shows price on top with volume (shares traded) below. This helps you see whether price moves are accompanied by heavy or light trading. We use make_subplots to create stacked charts that share the same x-axis.

from plotly.subplots import make_subplots

# Create subplots: price on top (70% height), volume below (30% height)
fig = make_subplots(rows=2, cols=1, shared_xaxes=True,
                    vertical_spacing=0.03, 
                    row_heights=[0.7, 0.3])

# Add price line to top panel
fig.add_trace(go.Scatter(x=aapl_ohlc.index, y=aapl_ohlc['Close'], 
                         name='Price', line=dict(color='blue')), 
              row=1, col=1)

# Add volume bars to bottom panel
fig.add_trace(go.Bar(x=aapl_ohlc.index, y=aapl_ohlc['Volume'], 
                     name='Volume', marker_color='gray'), 
              row=2, col=1)

fig.update_layout(
    title='AAPL Price and Volume',
    yaxis_title='Price ($)',
    yaxis2_title='Volume',
    showlegend=False,
    height=500
)

fig.show()

The shared_xaxes=True keeps the two panels aligned. When you zoom in on the price chart, the volume chart zooms to the same time period. This is exactly what you’d see on a financial news website.

Annotations — telling stories with data#

Good visualizations don’t just show data; they tell a story. Annotations let you highlight important events, explain patterns, or draw attention to specific points.

# Create a line chart with annotations
fig = px.line(stocks, x=stocks.index, y='aapl_o', title='Apple Stock Price with Key Events')

# Add annotations for notable dates
# August 24, 2015 - "Black Monday" flash crash
fig.add_annotation(
    x='2015-08-24', 
    y=stocks.loc['2015-08-24', 'aapl_o'],
    text='Flash Crash<br>(Aug 24, 2015)',
    showarrow=True,
    arrowhead=2,
    ax=0,
    ay=-40
)

# Apple's lowest point in early 2016
fig.add_annotation(
    x='2016-05-12', 
    y=stocks.loc['2016-05-12', 'aapl_o'],
    text='2016 Low',
    showarrow=True,
    arrowhead=2,
    ax=30,
    ay=30
)

fig.update_layout(yaxis_title='Price ($)')
fig.show()

The annotation parameters:

  • x and y: Where the arrow points to

  • text: The label (use <br> for line breaks)

  • showarrow: Whether to show an arrow

  • ax and ay: Offset for the text from the arrow point (in pixels)

Use annotations sparingly — too many clutters the chart. Focus on the 2-3 most important events that support your narrative.

Customizing hover information#

By default, plotly shows basic information when you hover. You can customize this to show exactly what viewers need to see.

# Create returns for scatter plot
stocks['msft_ret'] = np.log(stocks.msft_o / stocks.msft_o.shift(1))

# Scatter plot with custom hover
fig = px.scatter(stocks.dropna(), x='aapl_ret', y='msft_ret',
                 title='AAPL vs MSFT Daily Returns')

# Customize what appears on hover
fig.update_traces(
    hovertemplate='<b>Date</b>: %{customdata}<br>' +
                  '<b>AAPL Return</b>: %{x:.2%}<br>' +
                  '<b>MSFT Return</b>: %{y:.2%}<extra></extra>',
    customdata=stocks.dropna().index.strftime('%Y-%m-%d')
)

fig.update_layout(
    xaxis_title='AAPL Daily Return',
    yaxis_title='MSFT Daily Return'
)

fig.show()

Hover over any point to see the custom format. The hovertemplate uses:

  • %{x} and %{y} for the axis values

  • :.2% to format as percentages with 2 decimal places

  • <b> tags for bold text

  • <br> for line breaks

  • <extra></extra> removes the default trace name box

This is especially useful when your raw data doesn’t have nice labels — you can format everything to be readable.

Tip

When asking Claude or Copilot for plotly charts, be specific about what you want in the hover. For example: “Create a scatter plot of returns with hover showing the date and both returns as percentages.”