如何在 Altair 中正确地将区域图中的基线移动到特定的 y 位置并相应地更改填充颜色?
How to correctly shift the baseline in an area plot to a particular y location and change the fill color correspondingly, in Altair?
我希望能够做这样的事情 -
注意:您看到的水平线不在 y=0,而是在 y=1
但是使用 color
或 fill
编码与 condition
在面积图中并不真正起作用。
我得到的最接近的是在 mark_area
中使用 yOffset
(完美值的命中和试验),但最大的问题是 y 轴保持不变,因此图表有效变得无效。
示例:
(忽略水平连接的图表 - 它只是为了能够为 yOffset
提供一个好的值,因为 y 轴根本没有移动。)
import pandas as pd
data = pd.DataFrame({'date': pd.date_range(start='1/1/2018', end='1/11/2018'), 'stock': [0.1, 0.3, 0.9, 1, 1.5, 1.2, 0.8, 1.1, 0.4, 0.8, 1.6]})
left = alt.Chart(data).mark_area().encode(
x='date:T',
y='stock:Q',
fill = alt.condition(alt.datum.stock<1, alt.value('grey'), alt.value('red'))
)
right = alt.Chart(data).mark_area(yOffset=190, ).encode(
x='date:T',
y='stock:Q',
fill = alt.condition(alt.datum.stock<1, alt.value('grey'), alt.value('red'))
)
left | right
输出
右边的图表非常接近 - y 轴值和颜色是错误的。
有没有办法在 Altair 中做这样的事情?
编辑 1:
我尝试了这个有点相似的 的想法,但它并没有像我想象的那样工作 -
trial1 = alt.Chart(data).mark_area().transform_calculate(below=alt.datum.stock<=1).encode(
x='date:T',
y=alt.Y('stock:Q'),
color = 'below:N'
)
trial2 = alt.Chart(data).mark_area().transform_calculate(below=alt.datum.stock<=1).encode(
x='date:T',
y=alt.Y('stock:Q', impute={'value': 1}),
color = 'below:N'
)
trial1|trial2
输出
您可以通过 y2
参数提供第二个 y 编码来将基线定义为 1。将这种方法用于条形图相对简单:
import pandas as pd
import altair as alt
data = pd.DataFrame(
{'date': pd.date_range(start='1/1/2018', end='1/11/2018'),
'stock': [0.1, 0.3, 0.9, 1, 1.5, 1.2, 0.8, 1.1, 0.4, 0.8, 1.6],
'baseline': [1]*11})
# You could also set the bar width instead of binning
alt.Chart(data).mark_bar().encode(
x=alt.X('monthdate(date):T'),
y='stock:Q',
y2='baseline',
color = alt.condition(alt.datum.stock < 1, alt.value('grey'), alt.value('red')))
这很有效,因为条形图是单独的图形元素,因此它们将单独着色。面积图是一个单一的图形元素,因此只对第一个股票值进行条件比较,然后整个区域都涂上这种颜色。为了获得不同的颜色,我们需要按照您链接的答案对其进行分组,将该区域分成多个标记(这也适用于条形图)。您可以通过事先在数据框中创建分组列或通过 transform_calculate
.
来执行此操作
(alt.Chart(data.reset_index()).mark_area().encode(
x=alt.X('date:T'),
y=alt.Y('stock:Q', impute={'value': 1}),
y2='baseline',
color=alt.Color('negative:N', scale=alt.Scale(range=['red', 'grey'])))
.transform_calculate(negative='datum.stock < 1'))
为什么点之间有重叠?这样做的原因是数据稀疏,并且默认的插值方法是区域和线标记的“线性”。如果将其更改为 mark_area(interpolate='step')
,则区域之间的边界会很清晰:
要在保持其形状的同时实现区域标记围绕基线的急剧过渡,数据需要具有更高的分辨率。借用你链接的答案,你可以看到当数据稀疏时那里的区域也会重叠:
import altair as alt
import pandas as pd
import numpy as np
x = np.linspace(2, 4, 4)
df = pd.DataFrame({'x': x, 'y': np.sin(x)})
(alt.Chart(df).mark_area().encode(
x='x',
y=alt.Y('y', impute={'value': 0}),
color='negative:N')
.transform_calculate(negative='datum.y < 0'))
如果我们将点数增加十倍 (x = np.linspace(2, 4, 40)
),随着插值发生在 space 中较近的点之间,过渡变得更尖锐(将插值从线性更改为单调,可能也有帮助一点点同时保持形状)。
要提高时间序列数据的分辨率,您可以使用 pandas resample
和 interpolate
方法进行上采样。做这样的事情时要担心的是,如果你以有意义的方式人为地改变了你的数据。我发现问问自己操作是否会改变您对数据的结论很有用。
(alt.Chart(data.set_index('date').resample('1h').interpolate().reset_index()).mark_area().encode(
x=alt.X('date:T'),
y=alt.Y('stock:Q', impute={'value': 1}),
y2='baseline',
color=alt.Color('negative:N', scale=alt.Scale(range=['red', 'grey'])))
.transform_calculate(negative='datum.stock < 1'))
在这里,我们对每小时数据点进行上采样,并在原始点之间进行线性插值。对我而言,这不会改变我从研究绘图中得出的结论,因为线性插值保留了区域的块状外观,因此我们不会人为地使数据看起来平滑。我想到的唯一缺点是我们确实向 Altair 发送了不必要的数据量,您可以使用 Altair 中的转换来执行插值,但我不确定如何在我的脑海中。
我希望能够做这样的事情 -
注意:您看到的水平线不在 y=0,而是在 y=1
但是使用 color
或 fill
编码与 condition
在面积图中并不真正起作用。
我得到的最接近的是在 mark_area
中使用 yOffset
(完美值的命中和试验),但最大的问题是 y 轴保持不变,因此图表有效变得无效。
示例:
(忽略水平连接的图表 - 它只是为了能够为 yOffset
提供一个好的值,因为 y 轴根本没有移动。)
import pandas as pd
data = pd.DataFrame({'date': pd.date_range(start='1/1/2018', end='1/11/2018'), 'stock': [0.1, 0.3, 0.9, 1, 1.5, 1.2, 0.8, 1.1, 0.4, 0.8, 1.6]})
left = alt.Chart(data).mark_area().encode(
x='date:T',
y='stock:Q',
fill = alt.condition(alt.datum.stock<1, alt.value('grey'), alt.value('red'))
)
right = alt.Chart(data).mark_area(yOffset=190, ).encode(
x='date:T',
y='stock:Q',
fill = alt.condition(alt.datum.stock<1, alt.value('grey'), alt.value('red'))
)
left | right
输出
有没有办法在 Altair 中做这样的事情?
编辑 1:
我尝试了这个有点相似的
trial1 = alt.Chart(data).mark_area().transform_calculate(below=alt.datum.stock<=1).encode(
x='date:T',
y=alt.Y('stock:Q'),
color = 'below:N'
)
trial2 = alt.Chart(data).mark_area().transform_calculate(below=alt.datum.stock<=1).encode(
x='date:T',
y=alt.Y('stock:Q', impute={'value': 1}),
color = 'below:N'
)
trial1|trial2
输出
您可以通过 y2
参数提供第二个 y 编码来将基线定义为 1。将这种方法用于条形图相对简单:
import pandas as pd
import altair as alt
data = pd.DataFrame(
{'date': pd.date_range(start='1/1/2018', end='1/11/2018'),
'stock': [0.1, 0.3, 0.9, 1, 1.5, 1.2, 0.8, 1.1, 0.4, 0.8, 1.6],
'baseline': [1]*11})
# You could also set the bar width instead of binning
alt.Chart(data).mark_bar().encode(
x=alt.X('monthdate(date):T'),
y='stock:Q',
y2='baseline',
color = alt.condition(alt.datum.stock < 1, alt.value('grey'), alt.value('red')))
这很有效,因为条形图是单独的图形元素,因此它们将单独着色。面积图是一个单一的图形元素,因此只对第一个股票值进行条件比较,然后整个区域都涂上这种颜色。为了获得不同的颜色,我们需要按照您链接的答案对其进行分组,将该区域分成多个标记(这也适用于条形图)。您可以通过事先在数据框中创建分组列或通过 transform_calculate
.
(alt.Chart(data.reset_index()).mark_area().encode(
x=alt.X('date:T'),
y=alt.Y('stock:Q', impute={'value': 1}),
y2='baseline',
color=alt.Color('negative:N', scale=alt.Scale(range=['red', 'grey'])))
.transform_calculate(negative='datum.stock < 1'))
为什么点之间有重叠?这样做的原因是数据稀疏,并且默认的插值方法是区域和线标记的“线性”。如果将其更改为 mark_area(interpolate='step')
,则区域之间的边界会很清晰:
要在保持其形状的同时实现区域标记围绕基线的急剧过渡,数据需要具有更高的分辨率。借用你链接的答案,你可以看到当数据稀疏时那里的区域也会重叠:
import altair as alt
import pandas as pd
import numpy as np
x = np.linspace(2, 4, 4)
df = pd.DataFrame({'x': x, 'y': np.sin(x)})
(alt.Chart(df).mark_area().encode(
x='x',
y=alt.Y('y', impute={'value': 0}),
color='negative:N')
.transform_calculate(negative='datum.y < 0'))
如果我们将点数增加十倍 (x = np.linspace(2, 4, 40)
),随着插值发生在 space 中较近的点之间,过渡变得更尖锐(将插值从线性更改为单调,可能也有帮助一点点同时保持形状)。
要提高时间序列数据的分辨率,您可以使用 pandas resample
和 interpolate
方法进行上采样。做这样的事情时要担心的是,如果你以有意义的方式人为地改变了你的数据。我发现问问自己操作是否会改变您对数据的结论很有用。
(alt.Chart(data.set_index('date').resample('1h').interpolate().reset_index()).mark_area().encode(
x=alt.X('date:T'),
y=alt.Y('stock:Q', impute={'value': 1}),
y2='baseline',
color=alt.Color('negative:N', scale=alt.Scale(range=['red', 'grey'])))
.transform_calculate(negative='datum.stock < 1'))
在这里,我们对每小时数据点进行上采样,并在原始点之间进行线性插值。对我而言,这不会改变我从研究绘图中得出的结论,因为线性插值保留了区域的块状外观,因此我们不会人为地使数据看起来平滑。我想到的唯一缺点是我们确实向 Altair 发送了不必要的数据量,您可以使用 Altair 中的转换来执行插值,但我不确定如何在我的脑海中。