Python:将计算线添加到具有嵌套分类 x 轴的散点图
Python: Add calculated lines to a scatter plot with a nested categorical x-axis
我想在 Python 中复制这个情节:
这是我的尝试,使用 pandas
和 bokeh
:
进口:
import pandas as pd
from bokeh.io import output_notebook, show, reset_output
from bokeh.palettes import Spectral5, Turbo256
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import Band, Span, FactorRange, ColumnDataSource
创建数据:
fruits = ['Apples', 'Pears']
years = ['2015', '2016']
data = {'fruit' : fruits,
'2015' : [2, 1],
'2016' : [5, 3]}
fruit_df = pd.DataFrame(data).set_index("fruit")
tidy_df = (pd.DataFrame(data)
.melt(id_vars=["fruit"], var_name="year")
.assign(fruit_year=lambda df: list(zip(df['fruit'], df['year'])))
.set_index('fruit_year'))
创建bokeh
情节:
p = figure(x_range=FactorRange(factors=tidy_df.index.unique()),
plot_height=400,
plot_width=400,
tooltips=[('Fruit', '@fruit'), # first string is user-defined; second string must refer to a column
('Year', '@year'),
('Value', '@value')])
cds = ColumnDataSource(tidy_df)
index_cmap = factor_cmap("fruit",
Spectral5[:2],
factors=sorted(tidy_df["fruit"].unique())) # this is a reference back to the dataframe
p.circle(x='fruit_year',
y='value',
size=20,
source=cds,
fill_color=index_cmap,
line_color=None,
)
# how do I add a median just to one categorical section?
median = Span(location=tidy_df.loc[tidy_df["fruit"] == "Apples", "value"].median(), # median value for Apples
#dimension='height',
line_color='red',
line_dash='dashed',
line_width=1.0
)
p.add_layout(median)
# how do I add this standard deviation(ish) band to just the Apples or Pears section?
band = Band(
base='fruit_year',
lower=2,
upper=4,
source=cds,
)
p.add_layout(band)
show(p)
输出:
我反对这个问题吗? https://github.com/bokeh/bokeh/issues/8592
Python 是否有任何其他数据可视化库可以完成此任务? Altair、Holoviews、Matplotlib、Plotly... ?
Band
是一个连接区域,但是您的所需输出图像有两个断开连接的区域。意思是,您实际上需要两个频段。查看此处的示例以更好地理解波段:https://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#bands
通过使用 Band(base='fruit_year', lower=2, upper=4, source=cds)
,您要求 Bokeh 绘制一个波段,其中对于 fruit_year
的每个值,下坐标将为 2,上坐标将为 4。这正是您所看到的在你的散景图上。
有点不相关但仍然是一个错误 - 请注意您的 X 轴与您想要的有何不同。您必须先指定主要类别,因此将 list(zip(df['fruit'], df['year']))
替换为 list(zip(df['year'], df['fruit']))
。
现在,进入 "how to" 部分。由于您需要两个独立的波段,因此您不能为它们提供相同的数据源。这样做的方法是拥有两个额外的数据源——每个波段一个。它最终是这样的:
for year, sd in [('2015', 0.3), ('2016', 0.5)]:
b_df = (tidy_df[tidy_df['year'] == year]
.drop(columns=['year', 'fruit'])
.assign(lower=lambda df: df['value'].min() - sd,
upper=lambda df: df['value'].max() + sd)
.drop(columns='value'))
p.add_layout(Band(base='fruit_year', lower='lower', upper='upper',
source=ColumnDataSource(b_df)))
但是还有两个问题。第一个是微不足道的 - 自动 Y 范围(默认情况下 DataRange1d
class 的实例)不会考虑波段的高度。所以乐队很容易越界并被情节裁剪。这里的解决方案是使用将 SD 值考虑在内的手动测距。
第二个问题是带的宽度受 X 范围因子的限制,这意味着圆将部分位于带之外。这不是那么容易修复。通常一个解决方案是使用 transform
来稍微移动边缘的坐标。但由于这是一个分类轴,我们不能这样做。一种可能的解决方案是创建一个添加偏移量的自定义 Band
模型:
class MyBand(Band):
# language=TypeScript
__implementation__ = """
import {Band, BandView} from "models/annotations/band"
export class MyBandView extends BandView {
protected _map_data(): void {
super._map_data()
const base_sx = this.model.dimension == 'height' ? this._lower_sx : this._lower_sy
if (base_sx.length > 1) {
const offset = (base_sx[1] - base_sx[0]) / 2
base_sx[0] -= offset
base_sx[base_sx.length - 1] += offset
}
}
}
export class MyBand extends Band {
__view_type__: MyBandView
static init_MyBand(): void {
this.prototype.default_view = MyBandView
}
}
"""
只需将上面代码中的 Band
替换为 MyBand
即可。一个警告 - 您将需要安装 Node.js 并且启动时间将延长一两秒,因为自定义模型代码需要编译。另一个警告 - 自定义模型代码了解 BokehJS 的内部结构。意思是,虽然它可以与 Bokeh 2.0.2 一起使用,但我不能保证它可以与任何其他 Bokeh 版本一起使用。
我想在 Python 中复制这个情节:
这是我的尝试,使用 pandas
和 bokeh
:
进口:
import pandas as pd
from bokeh.io import output_notebook, show, reset_output
from bokeh.palettes import Spectral5, Turbo256
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import Band, Span, FactorRange, ColumnDataSource
创建数据:
fruits = ['Apples', 'Pears']
years = ['2015', '2016']
data = {'fruit' : fruits,
'2015' : [2, 1],
'2016' : [5, 3]}
fruit_df = pd.DataFrame(data).set_index("fruit")
tidy_df = (pd.DataFrame(data)
.melt(id_vars=["fruit"], var_name="year")
.assign(fruit_year=lambda df: list(zip(df['fruit'], df['year'])))
.set_index('fruit_year'))
创建bokeh
情节:
p = figure(x_range=FactorRange(factors=tidy_df.index.unique()),
plot_height=400,
plot_width=400,
tooltips=[('Fruit', '@fruit'), # first string is user-defined; second string must refer to a column
('Year', '@year'),
('Value', '@value')])
cds = ColumnDataSource(tidy_df)
index_cmap = factor_cmap("fruit",
Spectral5[:2],
factors=sorted(tidy_df["fruit"].unique())) # this is a reference back to the dataframe
p.circle(x='fruit_year',
y='value',
size=20,
source=cds,
fill_color=index_cmap,
line_color=None,
)
# how do I add a median just to one categorical section?
median = Span(location=tidy_df.loc[tidy_df["fruit"] == "Apples", "value"].median(), # median value for Apples
#dimension='height',
line_color='red',
line_dash='dashed',
line_width=1.0
)
p.add_layout(median)
# how do I add this standard deviation(ish) band to just the Apples or Pears section?
band = Band(
base='fruit_year',
lower=2,
upper=4,
source=cds,
)
p.add_layout(band)
show(p)
输出:
我反对这个问题吗? https://github.com/bokeh/bokeh/issues/8592 Python 是否有任何其他数据可视化库可以完成此任务? Altair、Holoviews、Matplotlib、Plotly... ?
Band
是一个连接区域,但是您的所需输出图像有两个断开连接的区域。意思是,您实际上需要两个频段。查看此处的示例以更好地理解波段:https://docs.bokeh.org/en/latest/docs/user_guide/annotations.html#bands
通过使用 Band(base='fruit_year', lower=2, upper=4, source=cds)
,您要求 Bokeh 绘制一个波段,其中对于 fruit_year
的每个值,下坐标将为 2,上坐标将为 4。这正是您所看到的在你的散景图上。
有点不相关但仍然是一个错误 - 请注意您的 X 轴与您想要的有何不同。您必须先指定主要类别,因此将 list(zip(df['fruit'], df['year']))
替换为 list(zip(df['year'], df['fruit']))
。
现在,进入 "how to" 部分。由于您需要两个独立的波段,因此您不能为它们提供相同的数据源。这样做的方法是拥有两个额外的数据源——每个波段一个。它最终是这样的:
for year, sd in [('2015', 0.3), ('2016', 0.5)]:
b_df = (tidy_df[tidy_df['year'] == year]
.drop(columns=['year', 'fruit'])
.assign(lower=lambda df: df['value'].min() - sd,
upper=lambda df: df['value'].max() + sd)
.drop(columns='value'))
p.add_layout(Band(base='fruit_year', lower='lower', upper='upper',
source=ColumnDataSource(b_df)))
但是还有两个问题。第一个是微不足道的 - 自动 Y 范围(默认情况下 DataRange1d
class 的实例)不会考虑波段的高度。所以乐队很容易越界并被情节裁剪。这里的解决方案是使用将 SD 值考虑在内的手动测距。
第二个问题是带的宽度受 X 范围因子的限制,这意味着圆将部分位于带之外。这不是那么容易修复。通常一个解决方案是使用 transform
来稍微移动边缘的坐标。但由于这是一个分类轴,我们不能这样做。一种可能的解决方案是创建一个添加偏移量的自定义 Band
模型:
class MyBand(Band):
# language=TypeScript
__implementation__ = """
import {Band, BandView} from "models/annotations/band"
export class MyBandView extends BandView {
protected _map_data(): void {
super._map_data()
const base_sx = this.model.dimension == 'height' ? this._lower_sx : this._lower_sy
if (base_sx.length > 1) {
const offset = (base_sx[1] - base_sx[0]) / 2
base_sx[0] -= offset
base_sx[base_sx.length - 1] += offset
}
}
}
export class MyBand extends Band {
__view_type__: MyBandView
static init_MyBand(): void {
this.prototype.default_view = MyBandView
}
}
"""
只需将上面代码中的 Band
替换为 MyBand
即可。一个警告 - 您将需要安装 Node.js 并且启动时间将延长一两秒,因为自定义模型代码需要编译。另一个警告 - 自定义模型代码了解 BokehJS 的内部结构。意思是,虽然它可以与 Bokeh 2.0.2 一起使用,但我不能保证它可以与任何其他 Bokeh 版本一起使用。