Bokeh教程:基本绘图

目录

本文翻译自 bokeh/bokeh-notebooks 项目,并经过修改。

本节介绍 bokeh.plotting 接口。 该接口是“中级”接口,主要思想可以用以下语句描述:

从简单的默认图形(使用默认工具,网格和轴)开始,添加视觉效果直接与数据相关联的标记和其他形状。

我们将会看到可以自定义和更改所有默认值,但是拥有默认值意味着可以非常快速地启动并运行。

导入和设置

当使用 bokeh.plotting 接口时,有一些常用的导入:

  • 使用 figure 函数创建新的绘图对象
  • 使用函数 output_fileoutput_notebook(可以同时使用)告诉 Bokeh 如何显示或保存图像
  • 执行 showsave 用于显示或保存绘图和布局
import numpy as np # 后面会使用,这里先导入
import pandas as pd

from bokeh.io import output_notebook, show
from bokeh.plotting import figure

本示例中,我们使用 Jupyter notebook,所以下面我们调用 output_notebok()

我们只需要调用一次,随后所有对 show() 的调用都会在 notebook 中内联显示。

output_notebook()

如果一切正常,则应该看到 Bokeh 徽标和类似下面的消息作为输出。

BokehJS 2.0.1 successfully loaded.

实战数据

使用 CSSEGISandData/COVID-19 项目提供的数据集。

为了提高加载速度,本示例默认文档已保存到本地的 data 目录中,请从 URL 下载原始文件。

https://raw.githubusercontent.com/CSSEGISandData/COVID-19/865c933c0f33f8ccaf4fddc45a13abdbe87036ee/csse_covid_19_data/csse_covid_19_time_series/time_series_covid19_confirmed_global.csv

df_confirmed = pd.read_csv(
    "data/time_series_covid19_confirmed_global.csv",
)
df_confirmed.head()

原始数据集示例

筛选数据,删掉不需要的列,并调整索引。

df_confirmed_selected = df_confirmed.loc[
    df_confirmed["Country/Region"] == "China"
].copy()
df_confirmed_selected.index = df_confirmed_selected["Province/State"]
df_confirmed_selected.drop(
    ["Province/State", "Country/Region", "Lat", "Long"], 
    axis=1, 
    inplace=True
)
df_confirmed_selected.columns = [
    pd.to_datetime(col) for col in df_confirmed_selected.columns
]
df_confirmed_selected.head()

调整后的数据集

选取一周的数据。

df_confirmed_week = df_confirmed_selected.loc[
    :, pd.to_datetime("2020-02-17"):pd.to_datetime("2020-02-23")
]
df_confirmed_week.head()

一周的数据

散点图(Scatter Plots)

Bokeh 可以绘制许多类型的视觉形状(称为 glyphs),包括线条(lines),条形(bars),小块(pathes),六角瓦片(hex tiles)等等。 最常见的可视化任务之一是使用小 标记(marker) 符号来代表每个点,以绘制数据散点图。

在本节中,您将看到如何使用 Bokeh 的各种标记符号创建简单的散点图。

基本方法是:

  • 创建一个空白的图形:p = figure(...)
  • 在图形中调用一个 glyph 方法,例如 p.circle
  • 调用 show 显示图形

筛选数据,随机选取 5 个在 2020 年 2 月 17 日确诊人数在 100 人到 500 人之间的地区,并使用数字替换原有的索引值。

data = df_confirmed_week[pd.to_datetime("2020-02-17")].copy()
data = data[data.between(100, 500)].sample(n=5, random_state=1)
data.index = np.arange(len(data))
data
0    146
1    121
2    457
3    238
4    130
Name: 2020-02-17 00:00:00, dtype: int64

执行以下单元格,创建带有圆形(circle)标记的散点图:

# 使用 figure 创建使用默认工具的新图形
p = figure(plot_width=400, plot_height=400)

# 添加圆形渲染器,指定 x 和 y 坐标,大小,颜色和透明度
p.circle(
    [0, 1, 2, 3, 4],  # 或使用 data_confirmed.index 代替 
    [146, 121, 457, 238, 130],  # 或使用 data_confirmed 代替
    size=15, 
    line_color="blue", 
    fill_color="green", 
    fill_alpha=0.3
)

show(p) # 显示绘图结果

圆形散点图

在上面的输出中,您可以看到 line_colorfill_alpha 等不同选项的效果。

尝试更改其中一些值并重新执行单元格以更新绘图。

p = figure(plot_width=400, plot_height=400)

p.circle(
    data.index,
    data,
    size=15, 
    line_color="red", 
    fill_color="yellow", 
    fill_alpha=0.8
)

show(p)

修改属性后重新绘制的圆形散点图

所有 Bokeh 散点图的标记都接受size(以屏幕空间单位为单位)作为属性。 圆形标记也有 radius(以“数据”空间单位测量)属性。

想要绘制正方形(square)标记而不是圆形,可以在图形上使用 square 方法。

p = figure(plot_width=400, plot_height=400)

p.square(
    data.index, 
    data, 
    size=[10, 15, 20, 25, 30], 
    color="firebrick", 
    alpha=0.6,
)

show(p)

正方形散点图

注意,在上面的示例中,我们还为每个单独的标记指定了不同的大小。

通常,可以用这种方式将 glyph 的所有属性“矢量化”。

还要注意,我们已经使用 color 属性,以便轻松地同时设置线条和填充颜色。 这是 bokeh.plotting 特有的便利。

Bokeh 中有许多可用的标记类型,您可以在单击下面列出的参考指南条目,查看所有标记的详细信息和示例:

练习

使用 CSSEGISandData/COVID-19 项目提供的另一个数据集。

为了提高加载速度,本示例默认文档已保存到本地的 data 目录中,请从 URL 下载原始文件。

https://raw.githubusercontent.com/CSSEGISandData/COVID-19/master/csse_covid_19_data/csse_covid_19_daily_reports/02-17-2020.csv

df_daily = pd.read_csv("data/02-17-2020.csv")
df_daily.head()

对数据进行过滤,选择确诊人数在 100 到 500 的地区。

df_daily_a = df_daily[df_daily["Country/Region"] == "Mainland China"]
df_daily_selected = df_daily_a[df_daily_a["Confirmed"].between(100, 500)]
df_daily_selected.head()

筛选后的数据

绘制确诊人数和治愈人数的散点图。

p = figure(
    plot_width=400, 
    plot_height=400,
)

p.square(
    df_daily_selected["Confirmed"], 
    df_daily_selected["Recovered"],  
    color="blue", 
    size=8,
    alpha=0.5,
)

show(p)

线条图

另一个常见的可视化任务是绘制线条图。 可以在 Bokeh 中通过调用 p.line(...) glyph 方法来实现,如下所示。

筛选数据,选择某地区一周的累积确诊人数。

data = df_confirmed_week.loc["Beijing"]
data.index = np.arange(len(data))
data
0    381
1    387
2    393
3    395
4    396
5    399
6    399
Name: Beijing, dtype: int64
p = figure(
    plot_width=400, 
    plot_height=400, 
    title="线条图"
)

# 添加线条渲染器
p.line(
    [0, 1, 2, 3, 4, 5, 6],  # 或使用 data.index 代替
    [381, 387, 393, 395, 396, 399, 399],  # 或使用 data 代替
    line_width=2
)

show(p)

线条图

除了 line_width 外,还有其他选项可以设置,例如 line_colorline_dash

尝试设置 line 的其他一些属性,然后重新运行上面的单元格。

p = figure(
    plot_width=400, 
    plot_height=400, 
    title="使用点划线的线条图"
)

p.line(
    data.index, 
    data, 
    line_width=2,
    line_color="green",
    line_dash="dotdash",
)

show(p)

使用点划线的线条图

日期时间坐标轴

时间序列数据通常由画线展示。

例如,示例数据的列索引都是日期时间类型,我们将其转置,这样每行代表一天,每列代表一个地区。

data = df_confirmed_selected.T
data.head()

转置后的示例数据表

我们想绘制此数据的一个子集,并且搭配一个合适的日期时间坐标轴。 我们可以通过向 figure 函数调用传递 x_axis_type ="datetime" 来向 Bokeh 请求日期时间轴。 下面代码中还配置了一些其他选项,例如图形尺寸,轴标题和网格线属性。

# 选取一个月的数据
month = data.loc[pd.to_datetime('2020-02-01'):pd.to_datetime('2020-02-29')]

p = figure(
    x_axis_type="datetime", 
    title="Confirmed", 
    plot_height=350, 
    plot_width=800
)
p.xgrid.grid_line_color=None
p.ygrid.grid_line_alpha=0.5
p.xaxis.axis_label = 'Date'
p.yaxis.axis_label = 'Case'

p.line(month.index, month.Beijing)

show(p)

日期为坐标轴的线条图

练习

绘制多条曲线。

p = figure(
    x_axis_type="datetime", 
    title="Plot", 
    plot_height=350, 
    plot_width=800
)
p.xgrid.grid_line_color=None
p.ygrid.grid_line_alpha=0.5
p.xaxis.axis_label = 'Date'
p.yaxis.axis_label = 'Case'

p.line(
    month.index, 
    month.Beijing,
    color="green"
)
p.line(
    month.index, 
    month.Shanghai,
    color="red",
)
p.line(
    month.index, 
    month.Tianjin,
    color="blue",
)

show(p)

多个线条图

六角瓦片(Hex Tiling)

Bokeh 支持使用轴向坐标hex_tile 方法绘制低级 hex tilings,如用户指南的 Hex Tiles 部分所述。 但是,六角瓦片的最常见用途之一是可视化分箱。 Bokeh 将此通用操作封装在 hexbin 函数中,该函数的输出可以直接传递到 hex_tile,如下所示。

from bokeh.palettes import Viridis256
from bokeh.util.hex import hexbin

n = 50000
x = np.random.standard_normal(n)
y = np.random.standard_normal(n)

bins = hexbin(x, y, 0.1)

# 手动为分箱分配颜色,后续将会看到如何使用 linear_cmap 
color = [Viridis256[int(i)] for i in bins.counts/max(bins.counts)*255]

# match_aspect 确保无论尺寸大小,任何纬度都不会压扁
p = figure(
    tools="wheel_zoom,reset", 
    match_aspect=True, 
    background_fill_color='#440154'
)
p.grid.visible = False

p.hex_tile(
    bins.q, 
    bins.r, 
    size=0.1, 
    line_color=None, 
    fill_color=color
)

show(p)
# 练习: 使用 hexbin 的 size 参数进行实验,并使用不同的数据作为输入
from bokeh.palettes import Viridis256
from bokeh.util.hex import hexbin

n = 50000
x = np.random.standard_normal(n)
y = np.random.standard_normal(n)

bins = hexbin(x, y, 0.2)

color = [Viridis256[int(i)] for i in bins.counts/max(bins.counts)*255]

p = figure(
    tools="wheel_zoom,reset", 
    match_aspect=True, 
    background_fill_color='#440154'
)
p.grid.visible = False

p.hex_tile(
    bins.q, 
    bins.r, 
    size=0.1, 
    line_color=None, 
    fill_color=color
)

show(p)

图像

另一个常见任务是显示图像,该图像可能表示热度图或某种传感器数据。

Bokeh 提供了两种显示图像的 glyph 方法:

  • image 可以与调色板一起使用,在绘图中显示带有颜色映射的二维数据
  • image_rgba 可用于在绘图中显示原始RGBA像素数据

下面的第一个示例显示了如何使用二维数组和调色板调用 figure

N = 500
x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)

img = np.sin(xx)*np.cos(yy)

p = figure(x_range=(0, 10), y_range=(0, 10))

# 必须为 image 参数指定矢量格式的图像数据
p.image(
    image=[img], 
    x=0, 
    y=0, 
    dw=10, 
    dh=10, 
    palette="Spectral11"
)

show(p) 

调色板可以是任何颜色列表,也可以是已命名的内置调色板之一,可以在bokeh.palettes参考指南中看到。

尝试更改调色板或数组数据,然后重新运行上面的单元格。

N = 500
x = np.linspace(0, 10, N)
y = np.linspace(0, 10, N)
xx, yy = np.meshgrid(x, y)

img = np.sin(xx)*np.cos(yy)

p = figure(x_range=(0, 10), y_range=(0, 10))

p.image(
    image=[img], 
    x=0, 
    y=0, 
    dw=10, 
    dh=10, 
    palette="Plasma256"
)

show(p)

下一个示例介绍如何使用 image_rgba 方法显示原始RGBA数据(在NumPy的帮助下创建)。

from __future__ import division
import numpy as np
 
N = 20
img = np.empty((N,N), dtype=np.uint32) 

# 使用数组视图分别设置每个RGBA通道
view = img.view(dtype=np.uint8).reshape((N, N, 4))
for i in range(N):
    for j in range(N):
        view[i, j, 0] = int(i/N*255) # red
        view[i, j, 1] = 158          # green
        view[i, j, 2] = int(j/N*255) # blue
        view[i, j, 3] = 255          # alpha

# 使用 figure 创建一个新的绘图,使用固定的范围
p = figure(x_range=[0,10], y_range=[0,10])

# 添加一个 RGBA 图像渲染器
p.image_rgba(image=[img], x=[0], y=[0], dw=[10], dh=[10])

show(p)

尝试更改RGBA数据并重新运行上面的单元格。

from __future__ import division
import numpy as np
 
N = 20
img = np.empty((N,N), dtype=np.uint32) 

view = img.view(dtype=np.uint8).reshape((N, N, 4))
for i in range(N):
    for j in range(N):
        view[i, j, 0] = int(i/N*255) # red
        view[i, j, 1] = int(j/N*255) # green
        view[i, j, 2] = 158          # blue
        view[i, j, 3] = 255          # alpha

p = figure(x_range=[0,10], y_range=[0,10])

p.image_rgba(image=[img], x=[0], y=[0], dw=[10], dh=[10])

show(p) 

其它种类的 Glyphs

Bokeh 支持许多其他种类的glyphs。 您可以单击下面的用户指南链接,查看如何使用 bokeh.plotting 接口创建使用这些 glyphs 的图。

在本教程的条形图和分类数据图一章中,我们将更广泛地使用条形图和矩形图来介绍各种条形图(例如堆叠和分组)。

# 练习:跟随用户指南中的示例,使用其他种类 glyph 绘图。

from math import pi

p = figure(
    plot_width=400, 
    plot_height=400
)

p.oval(
    x=[1, 2, 3], 
    y=[1, 2, 3], 
    width=0.2, 
    height=40, 
    color="#CAB2D6",
    angle=pi/3, 
    height_units="screen"
)

show(p)

使用多种 glyphs 绘图

最后,应注意可以在同一个图形上组合使用多个 glyphs。 当在单个图形上多次调用 glyphs 方法时,glyphs 按调用顺序绘制,如下所示。

# 创建一些数据
x = [1, 2, 3, 4, 5]
y = [4, 8, 2, 6, 3]

# 使用 figure 创建一个新的图形
p = figure(
    plot_width=400,
    plot_height=400,
)

# 添加折线图和圆点图
p.line(x, y, line_width=2)
p.circle(x, y, fill_color="white", size=8)

show(p)
# 练习: 使用多种 glyphs创建自己的图形

x = [1, 2, 3, 4, 5]
y = [6, 7, 8, 7, 3]

p = figure(
    plot_width=400, 
    plot_height=400
)

p.vbar(
    x=x, 
    width=0.5, 
    bottom=0,
    top=y,
)
p.circle(x, y, fill_color="white", size=8)

show(p)

参考

https://bokeh.org

https://github.com/bokeh/bokeh-notebooks