altair 中的多个 column/row 面包裹
multiple column/row facet wrap in altair
在 ggplot2
中,可以很容易地创建一个包含横跨行和列的分面的分面图。在 altair
中有 "slick" 的方法吗? facet
documentation
可以在单列中绘制分面图,
import altair as alt
from vega_datasets import data
iris = data.iris
chart = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=180,
height=180
).facet(
row='species:N'
)
并且在一行中,
chart = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=180,
height=180
).facet(
column='species:N'
)
但通常,我只想使用多个 column/row 将它们绘制在一个网格中,即那些排成一个 column/row 的网格并不意味着什么特别的东西。
例如,参见 ggplot2
中的 facet_wrap
:http://www.cookbook-r.com/Graphs/Facets_(ggplot2)/#facetwrap
您可以通过指定 .repeat()
以及 row
和 column
变量列表来执行此操作。这比 facet_wrap()
更接近 ggplot 的 facet_grid()
但 API 非常优雅。 (见讨论 here.) The API is here
iris = data.iris()
alt.Chart(iris).mark_circle().encode(
alt.X(alt.repeat("column"), type='quantitative'),
alt.Y(alt.repeat("row"), type='quantitative'),
color='species:N'
).properties(
width=250,
height=250
).repeat(
row=['petalLength', 'petalWidth'],
column=['sepalLength', 'sepalWidth']
).interactive()
产生:
请注意,整个集合是串联交互的(放大、缩小)。
一定要查看文档中的 RepeatedCharts and FacetedCharts。
创建 facet_wrap()
风格的地块网格
如果您想要一个接一个地布置图表带(不一定将列或行映射到数据框中的变量),您可以通过组合 hconcat()
和 vconcat()
在 Altair 图列表上。
我相信还有更优雅的方法,但我就是这样做的。
以下代码中使用的逻辑:
- 首先,创建一个
base
Altair 图表
- 使用
transform_filter()
将您的数据过滤到多个子图中
- 确定一行中的地块数量并将该列表切分
- 遍历列表列表,一次放置一行。
-
import altair as alt
from vega_datasets import data
from altair.expr import datum
iris = data.iris()
base = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=60,
height=60
)
#create a list of subplots
subplts = []
for pw in iris['petalWidth'].unique():
subplts.append(base.transform_filter(datum.petalWidth == pw))
def facet_wrap(subplts, plots_per_row):
rows = [subplts[i:i+plots_per_row] for i in range(0, len(subplts), plots_per_row)]
compound_chart = alt.hconcat()
for r in rows:
rowplot = alt.vconcat() #start a new row
for item in r:
rowplot |= item #add suplot to current row as a new column
compound_chart &= rowplot # add the entire row of plots as a new row
return compound_chart
compound_chart = facet_wrap(subplts, plots_per_row=6)
compound_chart
生产:
从 Ram 的 开始,使用更实用的方法,您也可以尝试:
import altair as alt
from vega_datasets import data
from altair.expr import datum
iris = data.iris()
base = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
)
# chart factory
def make_chart(base_chart, pw, options):
title = 'Petal Width {:.2f}'.format(pw)
chart = base_chart\
.transform_filter(datum.petalWidth == pw)\
.properties(width=options['width'], height=options['height'], title=title)
return chart
# create all charts
options = {'width': 50, 'height': 60}
charts = [make_chart(base, pw, options) for pw in sorted(iris['petalWidth'].unique())]
# make a single row
def make_hcc(row_of_charts):
hconcat = [chart for chart in row_of_charts]
hcc = alt.HConcatChart(hconcat=hconcat)
return hcc
# take an array of charts and produce a facet grid
def facet_wrap(charts, charts_per_row):
rows_of_charts = [
charts[i:i+charts_per_row]
for i in range(0, len(charts), charts_per_row)]
vconcat = [make_hcc(r) for r in rows_of_charts]
vcc = alt.VConcatChart(vconcat=vconcat)\
.configure_axisX(grid=True)\
.configure_axisY(grid=True)
return vcc
# assemble the facet grid
compound_chart = facet_wrap(charts, charts_per_row=6)
compound_chart.properties(title='My Facet grid')
这样应该很容易调整代码并将一些配置选项传递给所有绘图(例如 show/hide 刻度,为所有绘图设置相同的 bottom/top 限制等) .
这是一个通用的解决方案,可以添加图层。本例中的 DataFrame 有三列,并且是长格式。
numcols=3 # specify the number of columns you want
all_categories=df['Category_Column'].unique() # array of strings to use as your filters and titles
rows=alt.vconcat(data=df)
numrows=int(np.ceil(len(all_categories) / numcols))
pointer=0
for _ in range(numrows):
row=all_categories[pointer:pointer+numcols]
cols=alt.hconcat()
for a_chart in row:
# add your layers here
# line chart
line=alt.Chart().mark_line(point=True).encode(
x='variable',
y='value'
).transform_filter(datum.Category_Column == a_chart).properties(
title=a_chart, height=200, width=200)
# text labels
text=alt.Chart().mark_text().encode(
x='variable',
y='value'
).transform_filter(datum.Category_Column == a_chart)
both = line + text
cols |= both
rows &= cols
pointer += numcols
rows
我发现在任一方向上进行长度大于 2 的串联会导致数据失真并脱离 window。我通过递归地将子图数组分解为象限并进行交替的行和列连接来解决这个问题。如果您没有这个问题,那对您有好处:您可以使用已经发布的更简单的实现之一。但是,如果你这样做了,我希望这对你有所帮助。
def facet_wrap(subplots, plots_per_row):
# base cases
if len(subplots) == 0 or plots_per_row == 0:
return None
if len(subplots) == 1:
return subplots[0]
# split subplots list into quadrants
# we always fill top and left first
quadrants = [[], [], [], []] # tl, tr, bl, br
for subplot_index, subplot in enumerate(subplots):
right_half = (subplot_index % plots_per_row) >= plots_per_row // 2
lower_half = subplot_index >= len(subplots) / 2
quadrants[2 * lower_half + right_half].append(subplot)
# recurse on each quadrant
# we want a single chart or None in place of each quadrant
m = plots_per_row % 2 # if plots_per_row is odd then we need to split it unevenly
quadplots = [
facet_wrap(q, plots_per_row // 2 + m * (0 == (i % 2))) \
for i, q in enumerate(quadrants)
]
# join the quadrants
rows = [quadplots[:2], quadplots[2:]]
colplot = alt.hconcat()
for row in rows:
rowplot = alt.vconcat()
for item in row:
if item != None:
rowplot = rowplot | item
colplot &= rowplot
return colplot
在 Altair 3.1 版或更高版本(2019 年 6 月发布)中,直接在 Altair API 中支持包装面。修改您的虹膜示例,您可以像这样将您的构面包裹在两列中:
import altair as alt
from vega_datasets import data
iris = data.iris()
alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=180,
height=180
).facet(
facet='species:N',
columns=2
)
或者,可以将相同的图表指定为编码方面:
alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N',
facet=alt.Facet('species:N', columns=2)
).properties(
width=180,
height=180,
)
可以类似地为 alt.concat()
和重复图表 alt.Chart.repeat()
中的串联图表指定列参数。
不要在repeat
中使用column
或row
,而是repeat
如下:
import altair as alt
from vega_datasets import data
cars = data.cars.url
alt.Chart(cars, width=200, height=150).mark_bar().encode(
x=alt.X(alt.repeat('repeat'), type='quantitative', bin=alt.Bin(maxbins=20)),
y='count()'
).repeat(
repeat=["Horsepower", "Miles_per_Gallon", "Acceleration", "Displacement"],
columns=2
)
在 ggplot2
中,可以很容易地创建一个包含横跨行和列的分面的分面图。在 altair
中有 "slick" 的方法吗? facet
documentation
可以在单列中绘制分面图,
import altair as alt
from vega_datasets import data
iris = data.iris
chart = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=180,
height=180
).facet(
row='species:N'
)
并且在一行中,
chart = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=180,
height=180
).facet(
column='species:N'
)
但通常,我只想使用多个 column/row 将它们绘制在一个网格中,即那些排成一个 column/row 的网格并不意味着什么特别的东西。
例如,参见 ggplot2
中的 facet_wrap
:http://www.cookbook-r.com/Graphs/Facets_(ggplot2)/#facetwrap
您可以通过指定 .repeat()
以及 row
和 column
变量列表来执行此操作。这比 facet_wrap()
更接近 ggplot 的 facet_grid()
但 API 非常优雅。 (见讨论 here.) The API is here
iris = data.iris()
alt.Chart(iris).mark_circle().encode(
alt.X(alt.repeat("column"), type='quantitative'),
alt.Y(alt.repeat("row"), type='quantitative'),
color='species:N'
).properties(
width=250,
height=250
).repeat(
row=['petalLength', 'petalWidth'],
column=['sepalLength', 'sepalWidth']
).interactive()
产生:
请注意,整个集合是串联交互的(放大、缩小)。
一定要查看文档中的 RepeatedCharts and FacetedCharts。
创建 facet_wrap()
风格的地块网格
如果您想要一个接一个地布置图表带(不一定将列或行映射到数据框中的变量),您可以通过组合 hconcat()
和 vconcat()
在 Altair 图列表上。
我相信还有更优雅的方法,但我就是这样做的。
以下代码中使用的逻辑:
- 首先,创建一个
base
Altair 图表 - 使用
transform_filter()
将您的数据过滤到多个子图中 - 确定一行中的地块数量并将该列表切分
- 遍历列表列表,一次放置一行。
-
import altair as alt
from vega_datasets import data
from altair.expr import datum
iris = data.iris()
base = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=60,
height=60
)
#create a list of subplots
subplts = []
for pw in iris['petalWidth'].unique():
subplts.append(base.transform_filter(datum.petalWidth == pw))
def facet_wrap(subplts, plots_per_row):
rows = [subplts[i:i+plots_per_row] for i in range(0, len(subplts), plots_per_row)]
compound_chart = alt.hconcat()
for r in rows:
rowplot = alt.vconcat() #start a new row
for item in r:
rowplot |= item #add suplot to current row as a new column
compound_chart &= rowplot # add the entire row of plots as a new row
return compound_chart
compound_chart = facet_wrap(subplts, plots_per_row=6)
compound_chart
生产:
从 Ram 的
import altair as alt
from vega_datasets import data
from altair.expr import datum
iris = data.iris()
base = alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
)
# chart factory
def make_chart(base_chart, pw, options):
title = 'Petal Width {:.2f}'.format(pw)
chart = base_chart\
.transform_filter(datum.petalWidth == pw)\
.properties(width=options['width'], height=options['height'], title=title)
return chart
# create all charts
options = {'width': 50, 'height': 60}
charts = [make_chart(base, pw, options) for pw in sorted(iris['petalWidth'].unique())]
# make a single row
def make_hcc(row_of_charts):
hconcat = [chart for chart in row_of_charts]
hcc = alt.HConcatChart(hconcat=hconcat)
return hcc
# take an array of charts and produce a facet grid
def facet_wrap(charts, charts_per_row):
rows_of_charts = [
charts[i:i+charts_per_row]
for i in range(0, len(charts), charts_per_row)]
vconcat = [make_hcc(r) for r in rows_of_charts]
vcc = alt.VConcatChart(vconcat=vconcat)\
.configure_axisX(grid=True)\
.configure_axisY(grid=True)
return vcc
# assemble the facet grid
compound_chart = facet_wrap(charts, charts_per_row=6)
compound_chart.properties(title='My Facet grid')
这样应该很容易调整代码并将一些配置选项传递给所有绘图(例如 show/hide 刻度,为所有绘图设置相同的 bottom/top 限制等) .
这是一个通用的解决方案,可以添加图层。本例中的 DataFrame 有三列,并且是长格式。
numcols=3 # specify the number of columns you want
all_categories=df['Category_Column'].unique() # array of strings to use as your filters and titles
rows=alt.vconcat(data=df)
numrows=int(np.ceil(len(all_categories) / numcols))
pointer=0
for _ in range(numrows):
row=all_categories[pointer:pointer+numcols]
cols=alt.hconcat()
for a_chart in row:
# add your layers here
# line chart
line=alt.Chart().mark_line(point=True).encode(
x='variable',
y='value'
).transform_filter(datum.Category_Column == a_chart).properties(
title=a_chart, height=200, width=200)
# text labels
text=alt.Chart().mark_text().encode(
x='variable',
y='value'
).transform_filter(datum.Category_Column == a_chart)
both = line + text
cols |= both
rows &= cols
pointer += numcols
rows
我发现在任一方向上进行长度大于 2 的串联会导致数据失真并脱离 window。我通过递归地将子图数组分解为象限并进行交替的行和列连接来解决这个问题。如果您没有这个问题,那对您有好处:您可以使用已经发布的更简单的实现之一。但是,如果你这样做了,我希望这对你有所帮助。
def facet_wrap(subplots, plots_per_row):
# base cases
if len(subplots) == 0 or plots_per_row == 0:
return None
if len(subplots) == 1:
return subplots[0]
# split subplots list into quadrants
# we always fill top and left first
quadrants = [[], [], [], []] # tl, tr, bl, br
for subplot_index, subplot in enumerate(subplots):
right_half = (subplot_index % plots_per_row) >= plots_per_row // 2
lower_half = subplot_index >= len(subplots) / 2
quadrants[2 * lower_half + right_half].append(subplot)
# recurse on each quadrant
# we want a single chart or None in place of each quadrant
m = plots_per_row % 2 # if plots_per_row is odd then we need to split it unevenly
quadplots = [
facet_wrap(q, plots_per_row // 2 + m * (0 == (i % 2))) \
for i, q in enumerate(quadrants)
]
# join the quadrants
rows = [quadplots[:2], quadplots[2:]]
colplot = alt.hconcat()
for row in rows:
rowplot = alt.vconcat()
for item in row:
if item != None:
rowplot = rowplot | item
colplot &= rowplot
return colplot
在 Altair 3.1 版或更高版本(2019 年 6 月发布)中,直接在 Altair API 中支持包装面。修改您的虹膜示例,您可以像这样将您的构面包裹在两列中:
import altair as alt
from vega_datasets import data
iris = data.iris()
alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N'
).properties(
width=180,
height=180
).facet(
facet='species:N',
columns=2
)
或者,可以将相同的图表指定为编码方面:
alt.Chart(iris).mark_point().encode(
x='petalLength:Q',
y='petalWidth:Q',
color='species:N',
facet=alt.Facet('species:N', columns=2)
).properties(
width=180,
height=180,
)
可以类似地为 alt.concat()
和重复图表 alt.Chart.repeat()
中的串联图表指定列参数。
不要在repeat
中使用column
或row
,而是repeat
如下:
import altair as alt
from vega_datasets import data
cars = data.cars.url
alt.Chart(cars, width=200, height=150).mark_bar().encode(
x=alt.X(alt.repeat('repeat'), type='quantitative', bin=alt.Bin(maxbins=20)),
y='count()'
).repeat(
repeat=["Horsepower", "Miles_per_Gallon", "Acceleration", "Displacement"],
columns=2
)