Time Series & Financial Charts¶
ggplotly provides specialized features for time series visualization, including date axis formatting, interactive range selection, and financial chart types.
import pandas as pd
import numpy as np
from ggplotly import *
dates = pd.date_range('2020-01-01', periods=24, freq='ME')
df = pd.DataFrame({
'date': dates,
'value': np.cumsum(np.random.randn(24)) + 50
})
(ggplot(df, aes(x='date', y='value'))
+ geom_line(color='steelblue', size=2)
+ geom_point(size=5)
+ scale_x_date(date_breaks='3 months', date_labels='%b %Y')
+ labs(title='Monthly Time Series'))
DateTime Axis¶
For data with time components:
timestamps = pd.date_range('2024-01-01 08:00', periods=48, freq='h')
df_hourly = pd.DataFrame({
'timestamp': timestamps,
'value': np.sin(np.linspace(0, 4*np.pi, 48)) + np.random.randn(48) * 0.2
})
(ggplot(df_hourly, aes(x='timestamp', y='value'))
+ geom_line()
+ scale_x_datetime(date_labels='%b %d %H:%M')
+ labs(title='Hourly Data'))
Date Format Codes¶
| Code | Meaning | Example |
|---|---|---|
%Y |
4-digit year | 2024 |
%y |
2-digit year | 24 |
%m |
Month (01-12) | 03 |
%b |
Abbreviated month | Mar |
%B |
Full month | March |
%d |
Day (01-31) | 15 |
%H |
Hour (00-23) | 14 |
%M |
Minute (00-59) | 30 |
%S |
Second (00-59) | 45 |
Interactive Range Selection¶
Range Slider¶
Add a draggable range slider below the chart:
dates = pd.date_range('2020-01-01', periods=365, freq='D')
df = pd.DataFrame({
'date': dates,
'value': np.cumsum(np.random.randn(365))
})
(ggplot(df, aes(x='date', y='value'))
+ geom_line()
+ scale_x_rangeslider()
+ labs(title='Drag the slider below to zoom'))
Range Selector Buttons¶
Add buttons for quick time range selection:
(ggplot(df, aes(x='date', y='value'))
+ geom_line()
+ scale_x_rangeselector(buttons=['1m', '3m', '6m', 'ytd', '1y', 'all'])
+ labs(title='Click buttons to select date range'))
# Generate multi-year daily data
np.random.seed(42)
dates = pd.date_range('2019-01-01', '2025-06-15', freq='D')
temps = []
for d in dates:
seasonal = 55 + 25 * np.sin(2 * np.pi * (d.dayofyear - 80) / 365)
trend = (d.year - 2019) * 0.5
noise = np.random.randn() * 15
temps.append(seasonal + trend + noise)
df_temp = pd.DataFrame({'date': dates, 'temperature': temps})
# 5-year range plot with monthly aggregation
(ggplot(df_temp, aes(x='date', y='temperature'))
+ geom_range(freq='ME')
+ labs(
title='Temperature: 5-Year Historical Range',
subtitle='Gray: 5yr min/max, Black: 5yr avg, Blue: prior year, Red: current year'
))
Weekly Aggregation¶
(ggplot(df_temp, aes(x='date', y='temperature'))
+ geom_range(freq='W-Fri')
+ labs(title='Weekly Temperature Range'))
Show Specific Historical Years¶
(ggplot(df_temp, aes(x='date', y='temperature'))
+ geom_range(freq='ME', show_years=[2020, 2021])
+ labs(title='Temperature with 2020 and 2021 highlighted'))
Range Plots with Facets¶
np.random.seed(789)
cities = ['New York', 'Los Angeles', 'Chicago']
city_data = []
for city in cities:
base_temp = {'New York': 50, 'Los Angeles': 65, 'Chicago': 45}[city]
amplitude = {'New York': 30, 'Los Angeles': 15, 'Chicago': 35}[city]
for d in dates:
seasonal = base_temp + amplitude * np.sin(2 * np.pi * (d.dayofyear - 80) / 365)
noise = np.random.randn() * 15
city_data.append({'date': d, 'temperature': seasonal + noise, 'city': city})
df_cities = pd.DataFrame(city_data)
(ggplot(df_cities, aes(x='date', y='temperature'))
+ geom_range(freq='ME')
+ facet_wrap('city', nrow=1)
+ labs(title='Temperature by City')
+ theme_minimal())
from datetime import datetime, timedelta
# Generate OHLC data
np.random.seed(42)
n = 60
dates = pd.date_range('2024-01-01', periods=n, freq='B')
returns = np.random.normal(0.0005, 0.02, n)
close = 100 * np.cumprod(1 + returns)
open_prices = np.roll(close, 1)
open_prices[0] = 100
high = np.maximum(open_prices, close) + np.abs(np.random.randn(n)) * close * 0.01
low = np.minimum(open_prices, close) - np.abs(np.random.randn(n)) * close * 0.01
df = pd.DataFrame({
'date': dates,
'open': open_prices,
'high': high,
'low': low,
'close': close
})
(ggplot(df, aes(x='date', open='open', high='high', low='low', close='close'))
+ geom_candlestick()
+ labs(title='Stock Price', y='Price ($)'))
Custom Candlestick Colors¶
# TradingView-style colors
(ggplot(df, aes(x='date', open='open', high='high', low='low', close='close'))
+ geom_candlestick(increasing_color='#089981', decreasing_color='#F23645')
+ theme_dark()
+ labs(title='TradingView Style'))
OHLC Bar Charts¶
Alternative to candlesticks using horizontal bars:
(ggplot(df, aes(x='date', open='open', high='high', low='low', close='close'))
+ geom_ohlc()
+ labs(title='OHLC Bar Chart'))
(ggplot(df, aes(x='date', y='close'))
+ geom_line(color='steelblue', size=2)
+ scale_x_rangeslider()
+ scale_x_rangeselector(buttons=['1m', '3m', 'all'])
+ labs(title='Interactive Stock Price'))
Multiple Time Series¶
# Multiple series
df_multi = pd.DataFrame({
'date': np.tile(dates, 2),
'value': np.concatenate([close, close * 0.8 + np.random.randn(n) * 2]),
'series': ['Stock A'] * n + ['Stock B'] * n
})
(ggplot(df_multi, aes(x='date', y='value', color='series'))
+ geom_line(size=2)
+ scale_x_rangeslider()
+ labs(title='Comparing Two Stocks'))
Multi-Series Line Plots¶
For plotting many time series efficiently (e.g., 1000 columns), use geom_lines. This geom uses a single Plotly trace with None separators for optimal performance.
# Generate T×N matrix (100 time points, 50 series)
np.random.seed(42)
df_multi_lines = pd.DataFrame(
np.cumsum(np.random.randn(100, 50), axis=0),
index=pd.date_range('2024-01-01', periods=100, freq='D')
)
# Plot all series with single call - no aes() needed
(ggplot(df_multi_lines) + geom_lines())
With Styling Options¶
(ggplot(df_multi_lines)
+ geom_lines(alpha=0.3, size=0.5, color='steelblue')
+ labs(title='50 Time Series', y='Value'))
With Explicit X Column¶
df_explicit = pd.DataFrame({
'time': np.linspace(0, 10, 100),
**{f'series_{i}': np.sin(np.linspace(0, 4*np.pi, 100) + i*0.2) + np.random.randn(100)*0.1
for i in range(20)}
})
(ggplot(df_explicit)
+ geom_lines(aes(x='time'), alpha=0.5)
+ labs(title='Sine Waves with Noise'))
Multi-Colored Lines¶
Assign different colors to each series:
# Each series gets a different color
(ggplot(df_multi_lines.iloc[:, :10]) # First 10 series for clarity
+ geom_lines(multicolor=True, alpha=0.8)
+ labs(title='Multi-Colored Time Series'))
# With custom color palette
(ggplot(df_multi_lines.iloc[:, :10])
+ geom_lines(multicolor=True, palette='Viridis', alpha=0.8)
+ labs(title='With Viridis Palette'))
geom_lines Parameters¶
| Parameter | Default | Description |
|---|---|---|
color |
theme color | Line color (ignored if multicolor=True) |
alpha |
0.5 | Line transparency |
size |
1 | Line width |
showlegend |
False | Whether to show legend |
columns |
all numeric | Specific columns to plot |
multicolor |
False | Each series gets a different color |
palette |
'Plotly' | Color palette for multicolor mode |
Fan Charts¶
Fan charts display percentile bands showing the distribution across many series. They're useful for visualizing uncertainty in forecasts or simulation results:
# Generate simulation data (100 time points, 1000 simulations)
np.random.seed(42)
df_sims = pd.DataFrame(
np.cumsum(np.random.randn(100, 1000) * 0.5, axis=0),
index=pd.date_range('2024-01-01', periods=100, freq='D')
)
# Basic fan chart - shows 10th-90th and 25th-75th percentile bands
(ggplot(df_sims) + geom_fanchart())
Custom Percentiles¶
# Show wider uncertainty bands (5th-95th percentile)
(ggplot(df_sims)
+ geom_fanchart(percentiles=[5, 25, 50, 75, 95])
+ labs(title='Simulation Fan Chart', y='Value'))
Styled Fan Chart¶
(ggplot(df_sims)
+ geom_fanchart(color='coral', alpha=0.25, median_width=3)
+ labs(title='Forecast with Uncertainty Bands'))
geom_fanchart Parameters¶
| Parameter | Default | Description |
|---|---|---|
percentiles |
[10,25,50,75,90] | Percentile levels for bands |
color |
'steelblue' | Base color for bands |
alpha |
0.2 | Transparency of outer band |
show_median |
True | Show median line |
median_width |
2 | Width of median line |
median_color |
same as color | Color for median line |
columns |
all numeric | Columns to include in calculation |
STL Decomposition¶
STL (Seasonal-Trend decomposition using Loess) breaks down a time series into trend, seasonal, and residual components. Use geom_stl() to create a 4-panel decomposition chart:
# Load RBOB Gasoline futures (RB01) from commodity prices
commodity_prices = data('commodity_prices')
rb01 = commodity_prices[commodity_prices['series'] == 'RB01'].copy()
rb01['date'] = pd.to_datetime(rb01['date'])
# Basic STL decomposition - period=252 for ~yearly seasonality in trading days
(ggplot(rb01, aes(x='date', y='value'))
+ geom_stl(period=252)
+ labs(title='RBOB Gasoline Futures - STL Decomposition', y='Price'))
Robust STL for Outliers¶
Use robust=True for data with outliers or price spikes:
(ggplot(rb01, aes(x='date', y='value'))
+ geom_stl(period=252, robust=True, color='coral')
+ labs(title='Robust STL Decomposition'))
geom_stl Parameters¶
| Parameter | Default | Description |
|---|---|---|
period |
required | Seasonal period (e.g., 12 for monthly, 252 for daily trading data) |
seasonal |
7 | Length of seasonal smoother (must be odd) |
trend |
auto | Length of trend smoother |
robust |
False | Use robust fitting to downweight outliers |
color |
'steelblue' | Line color |
line_width |
1.5 | Width of lines |
rangeslider |
True | Show range slider on bottom subplot |
ACF and PACF Plots¶
Autocorrelation Function (ACF) and Partial Autocorrelation Function (PACF) plots are essential for time series analysis and ARIMA model identification:
# ACF plot
(ggplot(rb01, aes(y='value'))
+ geom_acf(nlags=30)
+ labs(title='ACF - RBOB Gasoline Futures'))
# PACF plot
(ggplot(rb01, aes(y='value'))
+ geom_pacf(nlags=30)
+ labs(title='PACF - RBOB Gasoline Futures'))
ACF/PACF Parameters¶
| Parameter | Default | Description |
|---|---|---|
nlags |
40 | Number of lags to compute |
alpha |
0.05 | Significance level for confidence intervals |
color |
'steelblue' | Color for bars |
ci_alpha |
0.3 | Transparency of confidence band |
bar_width |
0.3 | Width of bars |
method |
'ywm' | PACF method: 'ywm', 'yw', 'ols', 'ld' (PACF only) |
Using stat_stl with Faceting¶
For more control, use stat_stl directly to compute the decomposition, then plot with faceting:
# Use stat_stl for manual decomposition with custom faceting
from ggplotly.stats import stat_stl
# Compute STL decomposition
stat = stat_stl(mapping={'x': 'date', 'y': 'value'}, period=252)
stl_data, _ = stat.compute(rb01)
# Plot with facet_wrap for custom layout
(ggplot(stl_data, aes(x='date', y='value'))
+ geom_line(color='steelblue')
+ facet_wrap('component', ncol=2, scales='free_y')
+ labs(title='STL Components (2x2 Layout)'))
Time Series with Annotations¶
df_ann = pd.DataFrame({
'date': dates,
'value': close
})
(ggplot(df_ann, aes(x='date', y='value'))
+ geom_line(size=2, color='steelblue')
+ geom_point(size=5)
+ annotate('segment', x=dates[20], y=max(close) + 5, xend=dates[20], yend=close[20] + 2,
arrow=True, color='red', size=2)
+ annotate('text', x=dates[20], y=max(close) + 7, label='Important Event', size=12, color='red')
+ labs(title='Time Series with Annotation'))
Using Pandas Index¶
ggplotly automatically handles DatetimeIndex:
# Series with DatetimeIndex - x is automatically the index
dates = pd.date_range('2024-01-01', periods=30)
values = np.cumsum(np.random.randn(30))
ts = pd.Series(values, index=dates, name='Price')
(ggplot(ts) + geom_line())
# DataFrame with named DatetimeIndex
df_indexed = pd.DataFrame(
{'value': np.sin(np.linspace(0, 4*np.pi, 100))},
index=pd.DatetimeIndex(pd.date_range('2024-01-01', periods=100, freq='D'), name='Date')
)
(ggplot(df_indexed, aes(y='value')) + geom_line()) # x automatically uses index