分类图#

Bokeh 提供多种方法来处理和可视化分类数据。分类是指可以划分为不同组或类别的数据,这些类别可能具有或不具有自然顺序或数值。例如,表示国家、产品名称或颜色的数据。与表示温度或距离等值的连续数据不同,分类数据与标记和分组有关。

许多数据集包含连续数据和分类数据。例如,一段时期内不同国家不同产品销售数量的数据集。

分类数据也可以为每个类别包含多个值。之前的章节,例如 条形图散点图 已经介绍了一些使用每个类别单个值可视化分类数据的方法。

本章重点介绍更复杂的分类数据,其中每个类别都包含一系列值,以及包含一个或多个分类变量的数据集。

一个分类变量#

带有抖动的分类散点图#

关于散点图的章节 包含可视化每个类别具有单个值的数据的示例。

如果您的数据包含每个类别多个值,您可以使用分类散点图可视化您的数据。例如,如果您有不同日期的系列测量值,这将很有用。

为了避免单个类别的多个散点重叠,请使用 jitter() 函数为每个点提供随机偏移量。

下面的示例显示了 2012 年至 2016 年间 GitHub 用户每次提交时间的散点图。它使用星期几作为类别来对提交进行分组。默认情况下,该图将显示数千个点在每一天的细线上重叠。 jitter 函数可让您区分点以生成有用的图

from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.sampledata.commits import data
from bokeh.transform import jitter

DAYS = ['Sun', 'Sat', 'Fri', 'Thu', 'Wed', 'Tue', 'Mon']

source = ColumnDataSource(data)

p = figure(width=800, height=300, y_range=DAYS, x_axis_type='datetime',
           title="Commits by Time of Day (US/Central) 2012-2016")

p.scatter(x='time', y=jitter('day', width=0.6, range=p.y_range), source=source, alpha=0.3)

p.xaxis.formatter.days = '%Hh'
p.x_range.range_padding = 0
p.ygrid.grid_line_color = None

show(p)

带有偏移量的分类系列#

可视化分类数据的简单示例是使用 条形图 来表示每个类别中的单个值。

但是,如果您想表示每个类别的有序数据系列,可以使用分类偏移量来定位每个类别值的符号。除了 使用 dodge 的视觉偏移量 之外,分类偏移量允许您显式控制“在”类别中的定位。

要显式地为分类位置提供偏移量,请在类别末尾添加数值。例如: ["Jan", 0.2] 为类别“Jan”提供 0.2 的偏移量。

对于多级类别,在现有列表末尾添加值: ["West", "Sales", -0,2]。Bokeh 将任何类别列表末尾的数值解释为偏移量。

“条形图”章节中的水果示例 为例,并通过添加 offsets 列表对其进行修改

fruits = ['Apples', 'Pears', 'Nectarines', 'Plums', 'Grapes', 'Strawberries']

offsets = [-0.5, -0.2, 0.0, 0.3, 0.1, 0.3]

# This results in [ ['Apples', -0.5], ['Pears', -0.2], ... ]
x = list(zip(fruits, offsets))

p.vbar(x=x, top=[5, 3, 4, 2, 4, 6], width=0.8)

这将使每个条形图水平移动相应的偏移量。

下面是一个更复杂的脊线图示例。它使用分类偏移量来指定每个类别的补丁坐标

import colorcet as cc
from numpy import linspace
from scipy.stats import gaussian_kde

from bokeh.models import ColumnDataSource, FixedTicker, PrintfTickFormatter
from bokeh.plotting import figure, show
from bokeh.sampledata.perceptions import probly


def ridge(category, data, scale=20):
    return list(zip([category]*len(data), scale*data))

cats = list(reversed(probly.keys()))

palette = [cc.rainbow[i*15] for i in range(17)]

x = linspace(-20, 110, 500)

source = ColumnDataSource(data=dict(x=x))

p = figure(y_range=cats, width=900, x_range=(-5, 105), toolbar_location=None)

for i, cat in enumerate(reversed(cats)):
    pdf = gaussian_kde(probly[cat])
    y = ridge(cat, pdf(x))
    source.add(y, cat)
    p.patch('x', cat, color=palette[i], alpha=0.6, line_color="black", source=source)

p.outline_line_color = None
p.background_fill_color = "#efefef"

p.xaxis.ticker = FixedTicker(ticks=list(range(0, 101, 10)))
p.xaxis.formatter = PrintfTickFormatter(format="%d%%")

p.ygrid.grid_line_color = None
p.xgrid.grid_line_color = "#dddddd"
p.xgrid.ticker = p.xaxis.ticker

p.axis.minor_tick_line_color = None
p.axis.major_tick_line_color = None
p.axis.axis_line_color = None

p.y_range.range_padding = 0.12

show(p)

斜率图#

斜率图是用于可视化两个或多个数据点之间的相对变化的图。这对于可视化两个类别之间的差异或类别内变量随时间的变化很有用,例如。

在斜率图中,您将单个测量值可视化为排列成两列的点,并通过连接成对的点用线来表示配对关系。每条线的斜率突出显示变化的大小和方向。

以下斜率图可视化了不同国家在一段时间(几年或几十年)内人均二氧化碳排放量的相对变化。它使用 Segment 符号绘制连接成对点的线

from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, show
from bokeh.sampledata.emissions import data

countries = [
    "Trinidad and Tobago",
    "Qatar",
    "United Arab Emirates",
    "Oman",
    "Bahrain",
    "Singapore",
    "Netherlands Antilles",
    "Kazakhstan",
    "Equatorial Guinea",
    "Kuwait",
]

df = data[data["country"].isin(countries)].reset_index(drop=True)
years = (df["year"] == 2000.0) | (df["year"] == 2010.0)

df = df[years].reset_index(drop=True)
df["year"] = df.year.astype(int)
df["year"] = df.year.astype(str)

# create separate columns for the different years
a = df[df["year"] == "2000"]
b = df[df["year"] == "2010"]
new_df = a.merge(b, on="country")

source = ColumnDataSource(new_df)

p = figure(x_range=("2000", "2010"), y_range=(0, 60), x_axis_location="above", y_axis_label="CO2 emissions (tons / person)")

p.scatter(x="year_x", y="emissions_x", source=source, size=7)

p.scatter(x="year_y", y="emissions_y", source=source, size=7)

p.segment(x0="year_x", y0="emissions_x", x1="year_y", y1="emissions_y", source=source, color="black")

p.text(x="year_y", y="emissions_y", text="country", source=source, x_offset=7, y_offset=8, text_font_size="12px")

p.xaxis.major_tick_line_color = None
p.xaxis.major_tick_out = 0
p.xaxis.axis_line_color = None

p.yaxis.minor_tick_out = 0
p.yaxis.major_tick_in = 0
p.yaxis.ticker = [0, 20, 40, 60]

p.grid.grid_line_color = None
p.outline_line_color = None

show(p)

两个或多个分类变量#

分类热图#

可以将值与类别相关联。在这种情况下,将不同的颜色阴影应用于表示类别对的矩形将产生分类热图。这是一个具有两个分类轴的图。

以下图表的 x 轴列出了从 1948 年到 2016 年的年份,y 轴列出了每年的月份。该图的每个矩形对应于一个 (year, month) 对。矩形的颜色表示给定年份的给定月份的失业率。

此示例使用 linear_cmap() 来映射图表的颜色,因为失业率是连续变量。该图还使用 construct_color_bar() 来提供右侧的视觉图例

from math import pi

import pandas as pd

from bokeh.models import BasicTicker, PrintfTickFormatter
from bokeh.plotting import figure, show
from bokeh.sampledata.unemployment1948 import data
from bokeh.transform import linear_cmap

data['Year'] = data['Year'].astype(str)
data = data.set_index('Year')
data.drop('Annual', axis=1, inplace=True)
data.columns.name = 'Month'

years = list(data.index)
months = list(reversed(data.columns))

# reshape to 1D array or rates with a month and year for each row.
df = pd.DataFrame(data.stack(), columns=['rate']).reset_index()

# this is the colormap from the original NYTimes plot
colors = ["#75968f", "#a5bab7", "#c9d9d3", "#e2e2e2", "#dfccce", "#ddb7b1", "#cc7878", "#933b41", "#550b1d"]

TOOLS = "hover,save,pan,box_zoom,reset,wheel_zoom"

p = figure(title=f"US Unemployment ({years[0]} - {years[-1]})",
           x_range=years, y_range=months,
           x_axis_location="above", width=900, height=400,
           tools=TOOLS, toolbar_location='below',
           tooltips=[('date', '@Month @Year'), ('rate', '@rate%')])

p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_text_font_size = "7px"
p.axis.major_label_standoff = 0
p.xaxis.major_label_orientation = pi / 3

r = p.rect(x="Year", y="Month", width=1, height=1, source=df,
           fill_color=linear_cmap("rate", colors, low=df.rate.min(), high=df.rate.max()),
           line_color=None)

p.add_layout(r.construct_color_bar(
    major_label_text_font_size="7px",
    ticker=BasicTicker(desired_num_ticks=len(colors)),
    formatter=PrintfTickFormatter(format="%d%%"),
    label_standoff=6,
    border_line_color=None,
    padding=5,
), 'right')

show(p)

以下元素周期表使用了本章中的几种技术

from bokeh.plotting import figure, show
from bokeh.sampledata.periodic_table import elements
from bokeh.transform import dodge, factor_cmap

periods = ["I", "II", "III", "IV", "V", "VI", "VII"]
groups = [str(x) for x in range(1, 19)]

df = elements.copy()
df["atomic mass"] = df["atomic mass"].astype(str)
df["group"] = df["group"].astype(str)
df["period"] = [periods[x-1] for x in df.period]
df = df[df.group != "-"]
df = df[df.symbol != "Lr"]
df = df[df.symbol != "Lu"]

cmap = {
    "alkali metal"         : "#a6cee3",
    "alkaline earth metal" : "#1f78b4",
    "metal"                : "#d93b43",
    "halogen"              : "#999d9a",
    "metalloid"            : "#e08d49",
    "noble gas"            : "#eaeaea",
    "nonmetal"             : "#f1d4Af",
    "transition metal"     : "#599d7A",
}

TOOLTIPS = [
    ("Name", "@name"),
    ("Atomic number", "@{atomic number}"),
    ("Atomic mass", "@{atomic mass}"),
    ("Type", "@metal"),
    ("CPK color", "$color[hex, swatch]:CPK"),
    ("Electronic configuration", "@{electronic configuration}"),
]

p = figure(title="Periodic Table (omitting LA and AC Series)", width=1000, height=450,
           x_range=groups, y_range=list(reversed(periods)),
           tools="hover", toolbar_location=None, tooltips=TOOLTIPS)

r = p.rect("group", "period", 0.95, 0.95, source=df, fill_alpha=0.6, legend_field="metal",
           color=factor_cmap('metal', palette=list(cmap.values()), factors=list(cmap.keys())))

text_props = dict(source=df, text_align="left", text_baseline="middle")

x = dodge("group", -0.4, range=p.x_range)

p.text(x=x, y="period", text="symbol", text_font_style="bold", **text_props)

p.text(x=x, y=dodge("period", 0.3, range=p.y_range), text="atomic number",
       text_font_size="11px", **text_props)

p.text(x=x, y=dodge("period", -0.35, range=p.y_range), text="name",
       text_font_size="7px", **text_props)

p.text(x=x, y=dodge("period", -0.2, range=p.y_range), text="atomic mass",
       text_font_size="7px", **text_props)

p.text(x=["3", "3"], y=["VI", "VII"], text=["LA", "AC"], text_align="center", text_baseline="middle")

p.outline_line_color = None
p.grid.grid_line_color = None
p.axis.axis_line_color = None
p.axis.major_tick_line_color = None
p.axis.major_label_standoff = 0
p.legend.orientation = "horizontal"
p.legend.location ="top_center"
p.hover.renderers = [r] # only hover element boxes

show(p)

相关图#

当您每个类别有超过三到四个定量变量时,量化变量对之间的关联程度并可视化该数量可能比可视化原始数据更有用。一种常见的做法是计算相关系数。相关系数的可视化称为相关图。

以下相关图是本章中另一个很好的示例。

该图将相关性显示为彩色圆圈。圆圈的大小对应于相关系数的绝对值。这样,低相关性就会被抑制,而高相关性会更加突出。

此示例使用 linear_cmap() 来映射图表的颜色,以突出显示元素对之间的相关性。该映射器还使用 construct_color_bar() 来提供下面的视觉图例

from itertools import combinations

import numpy as np
import pandas as pd

from bokeh.models import ColumnDataSource, FixedTicker
from bokeh.plotting import figure, show
from bokeh.sampledata.forensic_glass import data as df
from bokeh.transform import linear_cmap

elements = ("Mg", "Ca", "Fe", "K", "Na", "Al", "Ba")
pairs = list(combinations(elements, 2))

correlations = []
for x, y in pairs:
    matrix = np.corrcoef(df[x], df[y])
    correlations.append(matrix[0, 1])

x, y = list(zip(*pairs))

new_df = pd.DataFrame({
    "oxide_1": x,
    "oxide_2": y,
    "correlation": correlations,
    "dot_size": [(1+ 10 * abs(corr)) * 10 for corr in correlations],
})

x_range = new_df["oxide_1"].unique()
y_range = list(new_df["oxide_2"].unique())

source = ColumnDataSource(new_df)

p = figure(x_axis_location="above", toolbar_location=None, x_range=x_range, y_range=y_range, background_fill_color="#fafafa")

c = p.scatter(x="oxide_1", y="oxide_2", size="dot_size", source=source, fill_color=linear_cmap("correlation", "RdYlGn9", -0.5, 0.5), line_color="#202020")

color_bar = c.construct_color_bar(
    location=(200, 0),
    ticker=FixedTicker(ticks=[-0.5, 0.0, 0.5]),
    title="correlation",
    major_tick_line_color=None,
    width=150,
    height=20,
)

p.add_layout(color_bar, "below")

p.axis.major_tick_line_color = None
p.axis.major_tick_out = 0
p.axis.axis_line_color = None
p.grid.grid_line_color = None
p.outline_line_color = None

show(p)