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.expressorpx) — Simple, high-level functions for common plotsPlotly Graph Objects (
plotly.graph_objectsorgo) — 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:
xandy: Where the arrow points totext: The label (use<br>for line breaks)showarrow: Whether to show an arrowaxanday: 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.”