Using matplotlib, we can "trivially" fill the area between two vertical lines using fill_between() as in the example:
https://matplotlib.org/3.2.1/gallery/lines_bars_and_markers/fill_between_demo.html#selectively-marking-horizontal-regions-across-the-whole-axes
Using matplotlib, I can make what I need:
We have two signals, and I''m computing the rolling/moving Pearson's and Spearman's correlation. When the correlations go either below -0.5 or above 0.5, I want to shade the period (blue for Pearson's and orange for Spearman's). I also darken the weekends in gray in all plots.
However, I'm finding a hard time to accomplish the same using Plotly. And it will also be helpful to know how to do it between two horizontal lines.
Note that I'm using Plotly and Dash to speed up the visualization of several plots. Users asked for a more "dynamic type of thing." However, I'm not a GUI guy and cannot spend time on this, although I need to feed them with initial results.
BTW, I tried Bokeh in the past, and I gave up for some reason I cannot remember. Plotly looks good since I can use either from Python or R, which are my main development tools.
Thanks,
Carlos
I don't think there is any built-in Plotly method that that is equivalent to matplotlib's fill_between() method. However you can draw shapes so a possible workaround is to draw a grey rectangle and set the the parameter layer="below" so that the signal is still visible. You can also set the coordinates of the rectangle outside of your axis range to ensure the rectangle extends to the edges of the plot.
You can fill the area in between horizontal lines by drawing a rectangle and setting the axes ranges in a similar manner.
import numpy as np
import plotly.graph_objects as go
x = np.arange(0, 4 * np.pi, 0.01)
y = np.sin(x)
fig = go.Figure()
fig.add_trace(go.Scatter(
x=x,
y=y
))
# hard-code the axes
fig.update_xaxes(range=[0, 4 * np.pi])
fig.update_yaxes(range=[-1.2, 1.2])
# specify the corners of the rectangles
fig.update_layout(
shapes=[
dict(
type="rect",
xref="x",
yref="y",
x0="4",
y0="-1.3",
x1="5",
y1="1.3",
fillcolor="lightgray",
opacity=0.4,
line_width=0,
layer="below"
),
dict(
type="rect",
xref="x",
yref="y",
x0="9",
y0="-1.3",
x1="10",
y1="1.3",
fillcolor="lightgray",
opacity=0.4,
line_width=0,
layer="below"
),
]
)
fig.show()
You haven't provided a data sample so I'm going to use a synthetical time-series to show you how you can add a number of shapes with defined start and stop dates for several different categories using a custom function bgLevel
Two vertical lines with a fill between them very quickly turns into a rectangle. And rectangles can easily be added as shapes using fig.add_shape. The example below will show you how to find start and stop dates for periods given by a certain critera. In your case these criteria are whether or not the value of a variable is higher or lower than a certain level.
Using shapes instead of traces with fig.add_trace() will let you define the position with regards to plot layers using layer='below'. And the shapes outlines can easily be hidden using line=dict(color="rgba(0,0,0,0)).
Plot 1: Time series figure with random data:
Plot 2: Background is set to an opaque grey when A > 100 :
Plot 2: Background is also set to an opaque red when D < 60
Complete code:
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
import datetime
pd.set_option('display.max_rows', None)
# data sample
nperiods = 200
np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(nperiods, 4)),
columns=list('ABCD'))
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),periods=nperiods).tolist()
df['dates'] = datelist
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum().reset_index()
# function to set background color for a
# specified variable and a specified level
# plotly setup
fig = px.line(df, x='dates', y=df.columns[1:])
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(0,0,255,0.1)')
def bgLevels(fig, variable, level, mode, fillcolor, layer):
"""
Set a specified color as background for given
levels of a specified variable using a shape.
Keyword arguments:
==================
fig -- plotly figure
variable -- column name in a pandas dataframe
level -- int or float
mode -- set threshold above or below
fillcolor -- any color type that plotly can handle
layer -- position of shape in plotly fiugre, like "below"
"""
if mode == 'above':
m = df[variable].gt(level)
if mode == 'below':
m = df[variable].lt(level)
df1 = df[m].groupby((~m).cumsum())['dates'].agg(['first','last'])
for index, row in df1.iterrows():
#print(row['first'], row['last'])
fig.add_shape(type="rect",
xref="x",
yref="paper",
x0=row['first'],
y0=0,
x1=row['last'],
y1=1,
line=dict(color="rgba(0,0,0,0)",width=3,),
fillcolor=fillcolor,
layer=layer)
return(fig)
fig = bgLevels(fig = fig, variable = 'A', level = 100, mode = 'above',
fillcolor = 'rgba(100,100,100,0.2)', layer = 'below')
fig = bgLevels(fig = fig, variable = 'D', level = -60, mode = 'below',
fillcolor = 'rgba(255,0,0,0.2)', layer = 'below')
fig.show()
I think that fig.add_hrect() and fig.add_vrect() are the simplest approaches to reproducing the MatPlotLib fill_between functionality in this case:
https://plotly.com/python/horizontal-vertical-shapes/
For your example, add_vrect() should do the trick.
The goal is to highlight the entire line when hovering anywhere (not just at the data points) on the line.
Imports:
from IPython.display import display
import pandas as pd
import altair as alt
Data:
data = '{"Date":{"5":1560643200000,"18":1560643200000,"22":1560643200000,"24":1560643200000,"59":1560643200000,"65":1561248000000,"78":1561248000000,"82":1561248000000,"84":1561248000000,"119":1561248000000,"125":1561852800000,"138":1561852800000,"142":1561852800000,"144":1561852800000,"179":1561852800000,"185":1562457600000,"198":1562457600000,"202":1562457600000,"204":1562457600000,"239":1562457600000,"245":1563062400000,"258":1563062400000,"262":1563062400000,"264":1563062400000,"299":1563062400000,"305":1563667200000,"318":1563667200000,"322":1563667200000,"324":1563667200000,"359":1563667200000,"365":1564272000000,"378":1564272000000,"382":1564272000000,"384":1564272000000,"419":1564272000000,"425":1564876800000,"438":1564876800000,"442":1564876800000,"444":1564876800000,"479":1564876800000,"485":1565481600000,"498":1565481600000,"502":1565481600000,"504":1565481600000,"539":1565481600000,"545":1566086400000,"558":1566086400000,"562":1566086400000,"564":1566086400000,"599":1566086400000,"605":1566691200000,"618":1566691200000,"622":1566691200000,"624":1566691200000,"659":1566691200000,"665":1567296000000,"678":1567296000000,"682":1567296000000,"684":1567296000000,"719":1567296000000,"725":1567900800000,"738":1567900800000,"742":1567900800000,"744":1567900800000,"779":1567900800000,"785":1568505600000,"798":1568505600000,"802":1568505600000,"804":1568505600000,"839":1568505600000,"845":1569110400000,"858":1569110400000,"862":1569110400000,"864":1569110400000,"899":1569110400000,"905":1569715200000,"918":1569715200000,"922":1569715200000,"924":1569715200000,"959":1569715200000,"965":1570320000000,"978":1570320000000,"982":1570320000000,"984":1570320000000,"1019":1570320000000,"1025":1570924800000,"1038":1570924800000,"1042":1570924800000,"1044":1570924800000,"1079":1570924800000,"1085":1571529600000,"1098":1571529600000,"1102":1571529600000,"1104":1571529600000,"1139":1571529600000,"1145":1572134400000,"1158":1572134400000,"1162":1572134400000,"1164":1572134400000,"1199":1572134400000,"1205":1572739200000,"1218":1572739200000,"1222":1572739200000,"1224":1572739200000,"1259":1572739200000,"1265":1573344000000,"1278":1573344000000,"1282":1573344000000,"1284":1573344000000,"1319":1573344000000,"1325":1573948800000,"1338":1573948800000,"1342":1573948800000,"1344":1573948800000,"1379":1573948800000,"1385":1574553600000,"1398":1574553600000,"1402":1574553600000,"1404":1574553600000,"1439":1574553600000,"1445":1575158400000,"1458":1575158400000,"1462":1575158400000,"1464":1575158400000,"1499":1575158400000,"1505":1575763200000,"1518":1575763200000,"1522":1575763200000,"1524":1575763200000,"1559":1575763200000,"1565":1576368000000,"1578":1576368000000,"1582":1576368000000,"1584":1576368000000,"1619":1576368000000},"Store":{"5":"store1","18":"store2","22":"store3","24":"store4","59":"store5","65":"store1","78":"store2","82":"store3","84":"store4","119":"store5","125":"store1","138":"store2","142":"store3","144":"store4","179":"store5","185":"store1","198":"store2","202":"store3","204":"store4","239":"store5","245":"store1","258":"store2","262":"store3","264":"store4","299":"store5","305":"store1","318":"store2","322":"store3","324":"store4","359":"store5","365":"store1","378":"store2","382":"store3","384":"store4","419":"store5","425":"store1","438":"store2","442":"store3","444":"store4","479":"store5","485":"store1","498":"store2","502":"store3","504":"store4","539":"store5","545":"store1","558":"store2","562":"store3","564":"store4","599":"store5","605":"store1","618":"store2","622":"store3","624":"store4","659":"store5","665":"store1","678":"store2","682":"store3","684":"store4","719":"store5","725":"store1","738":"store2","742":"store3","744":"store4","779":"store5","785":"store1","798":"store2","802":"store3","804":"store4","839":"store5","845":"store1","858":"store2","862":"store3","864":"store4","899":"store5","905":"store1","918":"store2","922":"store3","924":"store4","959":"store5","965":"store1","978":"store2","982":"store3","984":"store4","1019":"store5","1025":"store1","1038":"store2","1042":"store3","1044":"store4","1079":"store5","1085":"store1","1098":"store2","1102":"store3","1104":"store4","1139":"store5","1145":"store1","1158":"store2","1162":"store3","1164":"store4","1199":"store5","1205":"store1","1218":"store2","1222":"store3","1224":"store4","1259":"store5","1265":"store1","1278":"store2","1282":"store3","1284":"store4","1319":"store5","1325":"store1","1338":"store2","1342":"store3","1344":"store4","1379":"store5","1385":"store1","1398":"store2","1402":"store3","1404":"store4","1439":"store5","1445":"store1","1458":"store2","1462":"store3","1464":"store4","1499":"store5","1505":"store1","1518":"store2","1522":"store3","1524":"store4","1559":"store5","1565":"store1","1578":"store2","1582":"store3","1584":"store4","1619":"store5"},"Rank":{"5":1.0,"18":1.0,"22":1.0,"24":1.0,"59":1.0,"65":2.0,"78":2.0,"82":2.0,"84":2.0,"119":2.0,"125":2.0,"138":2.0,"142":2.0,"144":2.0,"179":2.0,"185":2.0,"198":2.0,"202":2.0,"204":2.0,"239":2.0,"245":2.0,"258":2.0,"262":2.0,"264":2.0,"299":2.0,"305":2.0,"318":2.0,"322":2.0,"324":2.0,"359":2.0,"365":2.0,"378":2.0,"382":2.0,"384":1.0,"419":2.0,"425":3.0,"438":1.0,"442":3.0,"444":2.0,"479":3.0,"485":4.0,"498":1.0,"502":4.0,"504":3.0,"539":4.0,"545":4.0,"558":1.0,"562":3.0,"564":3.0,"599":4.0,"605":5.0,"618":1.0,"622":2.0,"624":4.0,"659":5.0,"665":6.0,"678":1.0,"682":2.0,"684":5.0,"719":6.0,"725":7.0,"738":1.0,"742":2.0,"744":5.0,"779":7.0,"785":8.0,"798":1.0,"802":2.0,"804":6.0,"839":8.0,"845":8.0,"858":1.0,"862":2.0,"864":5.0,"899":8.0,"905":8.0,"918":1.0,"922":2.0,"924":4.0,"959":8.0,"965":8.0,"978":1.0,"982":2.0,"984":4.0,"1019":8.0,"1025":10.0,"1038":1.0,"1042":2.0,"1044":5.0,"1079":10.0,"1085":10.0,"1098":1.0,"1102":2.0,"1104":5.0,"1139":10.0,"1145":11.0,"1158":1.0,"1162":2.0,"1164":5.0,"1199":11.0,"1205":12.0,"1218":1.0,"1222":2.0,"1224":6.0,"1259":12.0,"1265":13.0,"1278":2.0,"1282":1.0,"1284":7.0,"1319":13.0,"1325":13.0,"1338":2.0,"1342":1.0,"1344":6.0,"1379":13.0,"1385":14.0,"1398":2.0,"1402":1.0,"1404":6.0,"1439":14.0,"1445":3.0,"1458":2.0,"1462":1.0,"1464":6.0,"1499":8.0,"1505":3.0,"1518":2.0,"1522":1.0,"1524":6.0,"1559":4.0,"1565":3.0,"1578":2.0,"1582":1.0,"1584":8.0,"1619":5.0}}'
Dataframe:
df_slim = pd.read_json(data)
Chart:
highlight = alt.selection(type='single', on='mouseover',
fields=['Store'], nearest=True, empty="none")
chart = alt.Chart(df_slim).mark_line().encode(
x='Date',
y='Rank',
#color='Store',
strokeDash='Store',
color=alt.condition(highlight, 'Store', alt.value("lightgray")),
tooltip=['Rank','Store']
).properties(
width=800,
height=600,
title='Bump Chart: Store Ranking'
).configure_title(
fontSize=30,
font='Courier',
anchor='start',
color='gray'
).add_selection(
highlight
)
display(chart)
Output:
Any help? Not sure what went wrong here.
Good question! It turns out this is one of the current limitations of Vega-Lite. I found this note in the VL docs on Nearest Value
The nearest transform is not supported for continuous mark types (i.e., line and area). For these mark types, consider layering a discrete mark type (e.g., point) with a 0-value opacity
So for your example I would do something like this
highlight = alt.selection(type='single', on='mouseover',
fields=['Store'], nearest=True, empty="none")
chart = alt.Chart(df_slim).mark_line().encode(
x='Date',
y='Rank',
#color='Store',
strokeDash='Store',
color=alt.condition(highlight, 'Store', alt.value("lightgray")),
tooltip=['Rank','Store']
)
points = alt.Chart(df_slim).mark_point(opacity=0).encode(
x='Date',
y='Rank',).add_selection(
highlight
)
chart + points
For some reason, the Y-axis while plotting with altair seems to be inverted (would expect values to go from lower (bottom) to higher (top) of the plot). Also, I would like to be able to change the ticks frequency. With older versions I could use ticks=n_ticks but it seems now this argument can take only boolean.
import altair as alt
alt.renderers.enable('notebook')
eff_metals = pd.read_excel(filename, sheet_name='summary_eff_metals')
points = alt.Chart(eff_metals, height=250, width=400).mark_circle().encode(
x=alt.X('Temperature:Q',axis=alt.Axis(title='Temperature (°C)'),
scale=alt.Scale(zero=False, padding=50)),
y=alt.Y('Efficiency:N',axis=alt.Axis(title='Efficiency (%)'),
scale=alt.Scale(zero=False, padding=1)),
color=alt.Color('Element:N'),
)
text = points.mark_text(align='right', dx=0, dy=-5).encode(
text='Element:N'
)
chart = alt.layer(points, text, data=eff_metals,
width=600, height=300)
chart
And the figure:
I don't have your data, so difficult to write working code.
But here's an example of an inverted scale with additional ticks that is similar to the example scatter with tooltips example. See here for it in the vega editor.
import altair as alt
from vega_datasets import data
iris = data.iris()
alt.Chart(iris).mark_point().encode(
x='petalWidth',
y=alt.Y('petalLength', scale=alt.Scale(domain=[7,0]), axis=alt.Axis(tickCount=100)),
color='species'
).interactive()
This might work with your data:
eff_metals = pd.read_excel(filename, sheet_name='summary_eff_metals')
points = alt.Chart(eff_metals, height=250, width=400).mark_circle().encode(
x=alt.X('Temperature:Q',axis=alt.Axis(title='Temperature (°C)'),
scale=alt.Scale(zero=False, padding=50)),
y=alt.Y('Efficiency:N',axis=alt.Axis(title='Efficiency (%)'),
scale=alt.Scale(zero=False, padding=1, domain=[17,1])),
color=alt.Color('Element:N'),
)
text = points.mark_text(align='right', dx=0, dy=-5).encode(
text='Element:N'
)
chart = alt.layer(points, text, data=eff_metals,
width=600, height=300)
chart
However, I think it's possible that you've might just have the wrong type on your efficiency variable. You could try and replace 'Efficiency:N' with `'Efficiency:Q' and that might do it?
While it's possible to reverse the domain manually, that requires hardcoding the bounds.
Instead we can just pass Scale(reverse=True) to the axis encoding, e.g.:
from vega_datasets import data
alt.Chart(data.wheat().head()).mark_bar().encode(
x='wheat:Q',
y=alt.Y('year:O', scale=alt.Scale(reverse=True)),
)
Here it's been passed to alt.Y, so the years are inverted (left) vs the default y='year:O' (right):