提供数据

没有要表示的底层数据,就不可能实现数据可视化。在本节中,将解释为绘图提供数据的各种方法,从直接传递数据值到创建 ColumnDataSource 并使用 CDSView .

直接提供数据

在Bokeh中,可以将值列表直接传递到绘图函数中。在下面的示例中,数据, x_valuesy_values ,直接传递给 circle 绘图方法(请参见 使用基本图示符打印 更多示例)。

from bokeh.plotting import figure

x_values = [1, 2, 3, 4, 5]
y_values = [6, 7, 2, 3, 6]

p = figure()
p.circle(x=x_values, y=y_values)

当您传入这样的数据时,Bokeh会在幕后工作以生成 ColumnDataSource 为你。但是要学会创造和使用 ColumnDataSource 将使您能够访问更高级的功能,例如流式数据、在绘图之间共享数据和过滤数据。

ColumnDataSource

这个 ColumnDataSource 是大多数Bokeh图的核心,提供由图的字形显示的数据。和 ColumnDataSource ,很容易在多个绘图和小部件之间共享数据,例如 DataTable . 当相同的时候 ColumnDataSource 用于驱动多个渲染器,数据源的选择也将共享。因此,可以使用选择工具从一个绘图中选择数据点,并在第二个绘图中自动突出显示这些数据点 (链接的选择

在最基本的层面上 ColumnDataSource 只是列名和数据列表之间的映射。这个 ColumnDataSource 采取了 data 参数,它是dict,字符串列名作为键,数据值的列表(或数组)作为值。如果一个位置参数传递给 ColumnDataSource 初始化器,它将被视为 data . 一旦 ColumnDataSource 已经创建,它可以传递到 source 打印方法的参数,允许您传递列名称作为数据值的替代:

from bokeh.plotting import figure
from bokeh.models import ColumnDataSource

data = {'x_values': [1, 2, 3, 4, 5],
        'y_values': [6, 7, 2, 3, 6]}

source = ColumnDataSource(data=data)

p = figure()
p.circle(x='x_values', y='y_values', source=source)

注解

有一个隐含的假设,即 ColumnDataSource 在任何时候都有相同的长度。因此,通常最好更新 .data 数据源的属性“立即全部”。

Pandas

这个 data 参数也可以是熊猫 DataFrameGroupBy 对象。

source = ColumnDataSource(df)

如果A DataFrame 如果使用,则CD的列将与 DataFrame . 的索引 DataFrame 将被重置,因此如果 DataFrame 有一个命名的索引列,那么CDS也会有一个同名的列。但是,如果索引名是 None ,则CD将被分配一个通用名称。会的 index 如果有,以及 level_0 否则。

熊猫多重指数

所有 MultiIndex 列和索引在形成 ColumnsDataSource . 对于索引,将创建元组的索引,并且 MultiIndex 加下划线。列名也将用下划线连接。例如:

df = pd.DataFrame({('a', 'b'): {('A', 'B'): 1, ('A', 'C'): 2},
                   ('b', 'a'): {('A', 'C'): 7, ('A', 'B'): 8},
                   ('b', 'b'): {('A', 'D'): 9, ('A', 'B'): 10}})
cds = ColumnDataSource(df)

将导致一个名为 index 具有 [(A, B), (A, C), (A, D)] 和列名为 a_bb_ab_b . 对于非字符串列名,此过程将失败,因此请将 DataFrame 在这种情况下是手动的。

熊猫群

group = df.groupby(('colA', 'ColB'))
source = ColumnDataSource(group)

如果A GroupBy 对象,则cd将具有与调用结果相对应的列 group.describe() . 这个 describe 方法为统计度量生成列,例如 meancount 对于所有未分组的原始列。结果 DataFrameMultiIndex 具有原始列名称和计算度量值的列,因此将使用上述方案将其扁平化。例如,如果 DataFrame 有列 'year''mpg' . 然后就过去了 df.groupby('year') 到一个CD将导致列,如 'mpg_mean'

注意这种适应能力 GroupBy 物体只能和熊猫一起使用 >=0.20.0 .

流动

ColumnDataSource 流媒体是将新数据添加到CD的有效方法。通过使用 stream 方法时,Bokeh只向浏览器发送新数据,而不发送整个数据集。这个 stream 方法采用 new_data 参数,该参数包含将列名映射到要附加到相应列的数据序列的dict。它还需要一个可选参数 rollover ,这是要保留的数据的最大长度(列开头的数据将被丢弃)。违约 rollover 值None允许数据无限增长。

source = ColumnDataSource(data=dict(foo=[], bar=[]))

# has new, identical-length updates for all columns in source
new_data = {
    'foo' : [10, 20],
    'bar' : [100, 200],
}

source.stream(new_data)

有关使用流式处理的示例,请参阅 examples/app/ohlc .

修补

ColumnDataSource 修补是更新数据源切片的有效方法。通过使用 patch 方法时,Bokeh只需要将新数据发送到浏览器,而不需要将整个数据集发送到浏览器。这个 patch 方法应传递一个dict映射列名到表示要应用的修补程序更改的元组列表。

描述补丁更改的元组的形式如下:

(index, new_value)  # replace a single column value

# or

(slice, new_values) # replace several column values

有关完整示例,请参见 examples/howto/patch_app.py .

转换数据

我们已经在上面看到了如何将数据添加到 ColumnDataSource 去推动博克的阴谋。这可以包括原始数据或我们自己显式转换的数据,例如创建一列颜色来控制散点图中的标记如何着色。也可以指定只在浏览器中发生的转换。这有助于减少代码(即不必手动为地图数据上色)以及必须发送到浏览器中的数据量(仅发送原始数据,在客户端中进行颜色映射)。

在本节中,我们将研究一些可用的不同变换对象。

颜色

要在浏览器中执行线性颜色映射,请使用 linear_cmap() 可以使用函数。它接受一个 ColumnDataSource colormap列、调色板(可以是内置调色板名称或实际颜色列表)以及颜色映射范围的最小/最大值。结果可以传递给glyphs上的color属性:

fill_color=linear_cmap('counts', 'Viridis256', min=0, max=10)

完整的示例如下:

import numpy as np

from bokeh.plotting import figure, show
from bokeh.transform import linear_cmap
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)

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

p.hex_tile(q="q", r="r", size=0.1, line_color=None, source=bins,
           fill_color=linear_cmap('counts', 'Viridis256', 0, max(bins.counts)))

show(p)

此外 linear_cmap() 还有 log_cmap() 在对数刻度上执行颜色映射,以及 factor_cmap() 到colormap分类数据(参见下面的示例)。

标记

也可以将分类数据映射到标记类型。下面的示例显示了 factor_mark() 在输入数据中显示不同的标记或不同的类别。它还演示了 factor_cmap() 要对这些类别进行颜色映射,请执行以下操作:

from bokeh.plotting import figure, show
from bokeh.sampledata.iris import flowers
from bokeh.transform import factor_cmap, factor_mark

SPECIES = ['setosa', 'versicolor', 'virginica']
MARKERS = ['hex', 'circle_x', 'triangle']

p = figure(title = "Iris Morphology")
p.xaxis.axis_label = 'Petal Length'
p.yaxis.axis_label = 'Sepal Width'

p.scatter("petal_length", "sepal_width", source=flowers, legend_field="species", fill_alpha=0.4, size=12,
          marker=factor_mark('species', MARKERS, SPECIES),
          color=factor_cmap('species', 'Category10_3', SPECIES))

show(p)

注解

这个 factor_mark() 转换主要只对 scatter glyph方法,因为只有 Scatter glyph可以按标记类型参数化。

CustomJSTransform

除了上面的内置变换之外,还有一个 CustomJSTransform 允许指定任意JavaScript代码对ColumnDataSource数据执行转换步骤。一般来说 v_func (对于“矢量化”函数)提供(不太常见的是标量等价物) func 也可能需要)。这个 v_func 代码应该期望变量中有一个输入数组 xs ,并返回一个包含转换值的JavaScript数组:

v_func = """
    const first = xs[0]
    const norm = new Float64Array(xs.length)
    for (let i = 0; i < xs.length; i++) {
        norm[i] = xs[i] / first
    }
    return norm
"""
normalize = CustomJSTransform(v_func=v_func)

plot.line(x='aapl_date', y=transform('aapl_close', normalize), line_width=2,
          color='#cf3c4d', alpha=0.6,legend="Apple", source=aapl_source)

上面的代码将原始价格数据转换为相对于第一个数据点的标准化返回序列。完整结果如下:

过滤数据

通常需要将重点放在从较大的数据集中进行子采样或过滤的数据部分。Bokeh允许您指定表示数据子集的数据源视图。通过查看数据源,不需要更改基础数据,并且可以在绘图中共享。视图由一个或多个筛选器组成,这些筛选器选择应绑定到特定glyph的数据源行。

要使用数据子集打印,可以创建 CDSView 把它作为 view 在上添加方法的呈现器参数 Figure ,如 figure.circle . 这个 CDSView 有两个属性, sourcefilters . sourceColumnDataSource 与视图关联的。 filters 是一个列表 Filter 对象,如下所列和描述。

from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CDSView

source = ColumnDataSource(some_data)
view = CDSView(source=source, filters=[filter1, filter2])

p = figure()
p.circle(x="x", y="y", source=source, view=view)

IndexFilter

这个 IndexFilter 是最简单的过滤器类型。它有一个 indices 属性,它是要包含在绘图中的数据的索引的整数列表。

from bokeh.layouts import gridplot
from bokeh.models import CDSView, ColumnDataSource, IndexFilter
from bokeh.plotting import figure, show

source = ColumnDataSource(data=dict(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]))
view = CDSView(source=source, filters=[IndexFilter([0, 2, 4])])

tools = ["box_select", "hover", "reset"]
p = figure(plot_height=300, plot_width=300, tools=tools)
p.circle(x="x", y="y", size=10, hover_color="red", source=source)

p_filtered = figure(plot_height=300, plot_width=300, tools=tools)
p_filtered.circle(x="x", y="y", size=10, hover_color="red", source=source, view=view)

show(gridplot([[p, p_filtered]]))

BooleanFilter

A BooleanFilter 通过数据源中的真值或假值列表从数据源中选择行 booleans 财产。

from bokeh.layouts import gridplot
from bokeh.models import BooleanFilter, CDSView, ColumnDataSource
from bokeh.plotting import figure, show

source = ColumnDataSource(data=dict(x=[1, 2, 3, 4, 5], y=[1, 2, 3, 4, 5]))
booleans = [True if y_val > 2 else False for y_val in source.data['y']]
view = CDSView(source=source, filters=[BooleanFilter(booleans)])

tools = ["box_select", "hover", "reset"]
p = figure(plot_height=300, plot_width=300, tools=tools)
p.circle(x="x", y="y", size=10, hover_color="red", source=source)

p_filtered = figure(plot_height=300, plot_width=300, tools=tools,
                    x_range=p.x_range, y_range=p.y_range)
p_filtered.circle(x="x", y="y", size=10, hover_color="red", source=source, view=view)

show(gridplot([[p, p_filtered]]))

GroupFilter

这个 GroupFilter 允许您从数据集中为类别变量选择具有特定值的行。这个 GroupFilter 有两个属性, column_name ,中列的名称 ColumnDataSourcegroup ,要为其选择的列的值。

在下面的例子中, flowers 包含分类变量 species 哪一个呢 setosaversicolorvirginica .

from bokeh.layouts import gridplot
from bokeh.models import CDSView, ColumnDataSource, GroupFilter
from bokeh.plotting import figure, show
from bokeh.sampledata.iris import flowers

source = ColumnDataSource(flowers)
view1 = CDSView(source=source, filters=[GroupFilter(column_name='species', group='versicolor')])

plot_size_and_tools = {'plot_height': 300, 'plot_width': 300,
                        'tools':['box_select', 'reset', 'help']}

p1 = figure(title="Full data set", **plot_size_and_tools)
p1.circle(x='petal_length', y='petal_width', source=source, color='black')

p2 = figure(title="Setosa only", x_range=p1.x_range, y_range=p1.y_range, **plot_size_and_tools)
p2.circle(x='petal_length', y='petal_width', source=source, view=view1, color='red')

show(gridplot([[p1, p2]]))

CustomJSFilter

您还可以创建 CustomJSFilter 有你自己的功能。为此,请使用JavaScript或TypeScript编写代码,返回表示过滤子集的索引列表或布尔值列表。这个 ColumnDataSourceCDSView 添加到的此过滤器将在呈现时与变量一起使用 source .

JavaScript

创建一个 CustomJSFilter 对于用JavaScript编写的自定义功能,将JavaScript代码作为字符串传递给参数 code

custom_filter = CustomJSFilter(code='''
var indices = [];

// iterate through rows of data source and see if each satisfies some constraint
for (var i = 0; i < source.get_length(); i++){
    if (source.data['some_column'][i] == 'some_value'){
        indices.push(true);
    } else {
        indices.push(false);
    }
}
return indices;
''')

AjaxDataSource

Bokeh服务器应用程序使更新和流式传输数据到数据源变得很简单,但有时希望在独立文档中具有类似的功能。这个 AjaxDataSource 提供此功能。

这个 AjaxDataSource 配置了REST端点的URL和轮询间隔。在浏览器中,数据源将按指定的间隔从端点请求数据,并在本地更新数据。现有数据可以完全替换或附加到(最多可配置 max_size ). 提供的端点应返回与标准匹配的JSON dict ColumnDataSource 格式:

{
    'x' : [1, 2, 3, ...],
    'y' : [9, 3, 2, ...]
}

否则,使用 AjaxDataSource 与使用标准相同 ColumnDataSource

source = AjaxDataSource(data_url='http://some.api.com/data',
                        polling_interval=100)

# Use just like a ColumnDataSource
p.circle('x', 'y', source=source)

完整的示例(如下所示)可以在 examples/howto/ajax_source.py

../../_images/ajax_streaming.gif

链接的选择

使用相同的 ColumnDataSource 在下面的两个图中,允许共享他们的选择。

from bokeh.io import output_file, show
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure

output_file("brushing.html")

x = list(range(-20, 21))
y0 = [abs(xx) for xx in x]
y1 = [xx**2 for xx in x]

# create a column data source for the plots to share
source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1))

TOOLS = "box_select,lasso_select,help"

# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle('x', 'y0', source=source)

# create another new plot and add a renderer
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
right.circle('x', 'y1', source=source)

p = gridplot([[left, right]])

show(p)

带筛选数据的链接选择

由于能够指定用于每个glyph渲染器的数据子集,即使绘图使用不同的数据子集,也很容易在绘图之间共享数据。通过使用相同的 ColumnDataSource ,该数据源的选择和悬停检查将自动共享。

在下面的示例中,a CDSView 为指定y值大于250或小于100的数据子集的第二个绘图创建。两个图中的选择都会自动反映到另一个图中。悬停在一个图中的某个点上,将高亮显示另一个图中的相应点(如果存在)。

from bokeh.layouts import gridplot
from bokeh.models import BooleanFilter, CDSView, ColumnDataSource
from bokeh.plotting import figure, output_file, show

output_file("linked_selection_subsets.html")

x = list(range(-20, 21))
y0 = [abs(xx) for xx in x]
y1 = [xx**2 for xx in x]

# create a column data source for the plots to share
source = ColumnDataSource(data=dict(x=x, y0=y0, y1=y1))

# create a view of the source for one plot to use
view = CDSView(source=source, filters=[BooleanFilter([True if y > 250 or y < 100 else False for y in y1])])

TOOLS = "box_select,lasso_select,hover,help"

# create a new plot and add a renderer
left = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
left.circle('x', 'y0', size=10, hover_color="firebrick", source=source)

# create another new plot, add a renderer that uses the view of the data source
right = figure(tools=TOOLS, plot_width=300, plot_height=300, title=None)
right.circle('x', 'y1', size=10, hover_color="firebrick", source=source, view=view)

p = gridplot([[left, right]])

show(p)

其他数据类型

Bokeh还具有渲染网络图形数据和地理数据的能力。有关如何为这些类型的绘图设置数据的详细信息,请参见 可视化网络图测绘地理数据 .