Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第四章:数据转换与处理

前置知识:本章需要了解 ColumnDataSource(第二章 2.2)和基础图表绘制(第三章)。

延伸阅读:本章聚焦“数据如何进入图、如何映射到视觉属性、如何按条件筛选”。如果你更关心图表为什么会卡、何时该降采样、何时该考虑 WebGL 或 Datashader,请继续阅读第九章

4.1 为什么需要数据转换?

场景:你有100个数据点,想根据数值大小设置颜色。

笨办法:手动计算每个颜色

colors = []
for v in values:
    if v < 30:
        colors.append('red')
    elif v < 70:
        colors.append('yellow')
    else:
        colors.append('green')

Bokeh方式:使用transform

from bokeh.transform import linear_cmap

p.circle('x', 'y', source=source,
    fill_color=linear_cmap('value', 'RdYlGn10', low=0, high=100))

4.2 颜色映射

线性映射(linear_cmap)

from bokeh.transform import linear_cmap
from bokeh.palettes import Viridis256

# 连续数值映射到颜色
mapper = linear_cmap('value', Viridis256, low=0, high=100)

p.circle('x', 'y', source=source, fill_color=mapper)

分类映射(factor_cmap)

from bokeh.transform import factor_cmap

# 类别映射到颜色
factors = ['A', 'B', 'C']
mapper = factor_cmap('category', 'Category10', factors)

p.vbar(x='category', top='value', source=source, fill_color=mapper)

字段映射(transform)

from bokeh.transform import transform

# 通用映射函数
mapper = transform('value', LinearColorMapper(palette='Viridis256', low=0, high=100))

4.3 位置变换

dodge - 偏移

from bokeh.transform import dodge

# 用于分组柱状图
p.vbar(x=dodge('category', -0.2, range=p.x_range), top='value1', source=source)
p.vbar(x=dodge('category', 0.2, range=p.x_range), top='value2', source=source)

jitter - 抖动

from bokeh.transform import jitter

# 避免点重叠
p.circle(x=jitter('category', width=0.3, range=p.x_range), y='value', source=source)

stack - 堆叠

from bokeh.transform import stack

# 堆叠柱状图
p.vbar(x='category', top=stack('value1', 'value2', 'value3'), source=source)

4.4 数据过滤器

IndexFilter - 索引过滤

from bokeh.models import IndexFilter

# 只显示索引0, 2, 4的数据
view = CDSView(filter=IndexFilter([0, 2, 4]))
p.circle('x', 'y', source=source, view=view)

BooleanFilter - 布尔过滤

from bokeh.models import BooleanFilter

# 只显示值大于50的点
booleans = [v > 50 for v in source.data['value']]
view = CDSView(filter=BooleanFilter(booleans))
p.circle('x', 'y', source=source, view=view)

GroupFilter - 分组过滤

from bokeh.models import GroupFilter, CDSView

# 只显示category为'A'的数据
view = CDSView(filter=GroupFilter(column_name='category', group='A'))
p.circle('x', 'y', source=source, view=view)

组合过滤器

from bokeh.models import IntersectionFilter

# 多条件组合
view = CDSView(filter=IntersectionFilter(filters=[
    GroupFilter(column_name='category', group='A'),
    BooleanFilter([v > 50 for v in source.data['value']])
]))

4.5 自定义表达式

from bokeh.models import CustomJSExpr

# 使用JavaScript表达式计算新列
expr = CustomJSExpr(args=dict(source=source), code='''
    return source.data['value'].map(v => v * 2)
''')

source.data['doubled'] = expr

4.6 GroupBy 数据聚合

import pandas as pd
from bokeh.models import ColumnDataSource

# 创建 DataFrame
df = pd.DataFrame({
    'category': ['A', 'A', 'B', 'B', 'C'],
    'value': [10, 20, 30, 40, 50]
})

# 从 GroupBy 创建 ColumnDataSource
group = df.groupby('category')
source = ColumnDataSource(group)

# source 包含以下列:
# - category_mean, category_std, etc.

4.7 本章与性能优化的边界

到这里,你已经接触了三类非常重要的“数据到视觉”的工具:

  1. 映射linear_cmap()factor_cmap()transform()
    适合把数值或类别映射到颜色、大小等视觉属性。
  2. 位置变换dodge()jitter()stack()
    适合在不改原始数据语义的前提下调整显示位置。
  3. 筛选CDSView + 各类 filter
    适合控制“哪些数据被画出来”。

但要注意:这些工具的目标主要是让已有数据更适合可视化表达,不是替代 pandas / NumPy 的通用数据处理流程,也不是完整的性能优化方案。

如果你遇到的是下面这些问题:

  • 点太多,浏览器渲染明显变慢
  • 每次交互都要传很多数据
  • 实时更新时整张图频繁重绘
  • 想知道什么时候该降采样、聚合、用 stream() / patch()
  • 想评估 WebGL 是否真的适合你的场景

请转到第九章:性能优化
那里会按“先减少数据量,再考虑渲染后端”的顺序系统展开,而不是把 WebGL 当成默认答案。

4.8 本章小结

本章你已经掌握了 Bokeh 中最常见的数据转换思路:

  • linear_cmap()factor_cmap() 把数据映射到颜色
  • dodge()jitter()stack() 调整图形位置
  • CDSView 和过滤器控制显示的数据子集
  • 了解哪些转换适合放在 Bokeh 中做,哪些更适合提前在 pandas / NumPy 中处理

如果你准备继续学习,推荐按下面的顺序前进:

  • 想把多个图组合成一个页面:看第五章
  • 想做联动、控件过滤、前端/服务端回调:看第六章第七章
  • 想进一步理解大数据场景下该如何取舍:看第九章

4.9 常见坑

坑 1:把 transform 当成通用数据处理工具

linear_cmap()factor_cmap()dodge() 这些函数更适合“为了画图而做的转换”。
如果你需要复杂清洗、分组、聚合、时间对齐,通常应该先在 pandas / NumPy 中完成,再交给 Bokeh。

坑 2:过滤条件写对了,但 source 本身的数据就不一致

很多“为什么过滤后图不显示”的问题,根源并不是 filter 本身,而是 ColumnDataSource 中各列长度不一致,或者列名写错。
这类问题请回看第二章ColumnDataSource 的基本约束。

坑 3:过早把问题归结为性能

如果当前问题只是“颜色映射不对”或“分类轴位置不对”,不要立刻跳到 WebGL、降采样这类方案。
先确认:

  • 数据列名是否正确
  • low / high 是否覆盖了数据范围
  • x_range / y_range 是否与变换方式匹配
  • CDSView 的过滤条件是否真的命中了数据

坑 4:在 Bokeh 中做了太多业务逻辑

Bokeh 很擅长把数据变成图,但不适合承担整套业务数据处理流水线。
经验上更稳妥的分工是:

  • pandas / NumPy:清洗、聚合、衍生字段
  • Bokeh:映射、筛选、交互、渲染