Pandas Integration
Bokeh consumes pandas DataFrames directly: ColumnDataSource takes a DataFrame as its input, glyph methods accept DataFrame plus column names, and you can plot straight from analysis code without converting to lists. This makes the pandas -> plot pipeline two lines.
DataFrame as a Source
import pandas as pd
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
df = pd.DataFrame({
"day": pd.date_range("2026-01-01", periods=14, freq="D"),
"sales": [12, 15, 9, 18, 22, 19, 25, 21, 28, 24, 30, 27, 33, 31],
})
source = ColumnDataSource(df)
p = figure(x_axis_type="datetime", title="Daily sales")
p.line("day", "sales", source=source, line_width=2, color="#5b8ff9")
p.circle("day", "sales", source=source, size=5)
Plotting a DataFrame Directly
p = figure()
p.line(df["day"], df["sales"], line_width=2) # Series work directly
p.circle(df["day"], df["sales"], size=6)
Grouped Plots from GroupBy
df["group"] = pd.Series(["A"] * 7 + ["B"] * 7, index=df.index)
p = figure()
for name, grp in df.groupby("group"):
p.line(grp["day"], grp["sales"], legend_label=name, line_width=2)
p.legend.location = "top_left"
Categorical Plots from a DataFrame
summary = df.groupby("group")["sales"].mean().reset_index()
p = figure(x_range=summary["group"].tolist())
p.vbar(x="group", top="sales", width=0.6, source=ColumnDataSource(summary),
fill_color="#d4af37")
Large DataFrames
- Bokeh sends data to the browser, so a 10-million-row DataFrame is too big; downsample or aggregate first.
- Use the CDS stream/patch pattern for incremental loads instead of one big transfer.
- For server-side work, Bokeh server (next chapters) keeps the full DataFrame server-side and syncs only what changed.
Summary
Pass pandas DataFrames straight into ColumnDataSource or glyph methods, plot per-group with groupby loops plus legend labels, and build categorical bar charts from aggregated frames. For large data, aggregate or stream incrementally - Bokeh ships data to the browser, so keep transferred frames small.