Increasing speed on plotly animation - python

I created a choropleth map using plotly express's function choropleth() Code is below.
fig = px.choropleth(df_countrydate,
locations="Country",
locationmode = "country names",
color="Confirmed",
hover_name="Country",
animation_frame="Date",
color_continuous_scale="Reds"
)
fig.update_layout(
title_text = 'Global Spread of Coronavirus',
title_x = 0.5,
geo=dict(
showframe = False,
showcoastlines = False,
))
iplot(fig)
It's a dynamic map and I was wondering if there was anyway I could speed up the transitions from one date to the next when I hit play.

Change these 2 parameters in your plot:
fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 30
fig.layout.updatemenus[0].buttons[0].args[1]['transition']['duration'] = 5
Time is in milliseconds
I got the answer from the code of this video: https://www.youtube.com/watch?v=RCUrpCpGZ5o&t=1156s&ab_channel=CharmingData
You can find the whole code here: https://github.com/Coding-with-Adam/Dash-by-Plotly/blob/master/Plotly_Graphs/Animated_Scatter/gender_ineq.py
I also found that the resolution of the map (110 vs 50) really impacts the refresh rate of the animation. In my project displaying the evolution of a variable was more relevant than the detail, since my intention was presenting the change over time of said variable. I considered it was less likely for users to try to zoom in the map, so I decided to use the lower resolution map (1:110m vs 1:50m):
fig.update_geos(projection_type="equirectangular", visible=True, resolution=110)
All those options combined resulted in a fast and smooth animation for my map after lowering the frame and animation duration.
With resolution=50, I couldn't see any effect when changing the duration values.

Related

Plotly express animations: Plotting multiple changing traces and background image for each frame

I am currently trying to create a debugging tool for a simulation. For this is am working with the animations of plotly express. What I want to achieve:
An animation with a different background image with different traces (also differing in number) for each frame. Any help would be very much appreciated! (Also if someone knows how to do this using any other library it'd be very much appreciated!)
Thanks!
Adding the background images work perfectly well like this:
fig = px.imshow(img,origin="upper", animation_frame=0, binary_string=True, labels=dict(animation_frame="slice"))
So I only need to figure out how to display the traces.
I tried the following first:
fig = px.imshow(img,origin="upper", animation_frame=0, binary_string=True, labels=dict(animation_frame="slice")) # img consists of |time_step| images (np.array)
## now I'm trying to add the traces
for i in range(len(img)):
time_step = df.iloc[i]
for j in range(len(time_step)):
current = time_step.iloc[j]
x, y = current["x"], current["y"]
if (not current["valid"]):
fig.add_trace(go.Scatter(x=x, y=y, name="{}".format(current["unique_id"]), text="{}".format(current["costs"]), hoverinfo='text+name', line=dict(color="black", dash="dot"), line_shape="spline"))
else:
fig.add_traces(go.Scatter(x=x_list, y=y_list, line=dict(color="black"), line_shape="spline"))
fig.show()
However, I realized that fig.add_trace always adds the trace to all frames of the animation. (When only displaying the first time step it worked though :) )
However, I'd like to add the traces for one time_step only and then the ones for the next time_step.
So I started looking into this approach:
# saving in a list instead of printing immediately
if (not current["feasible"]):
info.append([x_list, y_list, "{}".format(current["unique_id"]),
"{}".format(current["costs"]), dict(color=color, dash="dot")])
else:
info.append([x_list, y_list, "{}".format(current["unique_id"]),
"{}".format(current["costs"]),dict(color=color)])
and added the information to the single frames:
frames.append(
go.Frame(
data=[go.Scatter(
x=information[0],
y=information[1],
name=information[2],
text=information[3],
hoverinfo='text+name',
line=information[4],
line_shape="spline"
)
for information in info],
)
)
figa = go.Figure(data=fig.data, frames=frames, layout=fig.layout)
figa.show()
But it only displays the first background image and once I start the animation it disappears and only shows the first saved Scatter for each time_step.
I'm a little lost as I don't have a set number of traces I can't "hardcode" the Scatters.

ploting a "timetable" with grouped bars for defined hours/timeboxes

I want to track my mobile devices at home so I can plot a "at home" and "not at home" diagram.
I collect the data as follows:
ip,device_name,start,end,length,date
192.168.178.123,aaa,2022-04-16 00:33:01.395443,2022-04-16 00:37:06.843443
192.168.178.123,aaa,2022-04-16 08:55:24.911787,2022-04-16 08:56:39.197196
192.168.178.123,aaa,2022-04-20 21:49:25.660712,2022-04-20 21:50:25.660712
192.168.178.123,aaa,2022-04-24 14:42:14.781557,2022-04-24 14:44:56.519343
192.168.178.234,bbb,2022-04-16 08:22:37.763442,2022-04-16 08:23:37.763442
192.168.178.234,bbb,2022-04-16 10:05:09.613899,2022-04-16 10:06:09.613899
Each entry of my csv-File represents the status of a device being not at home.
I want to have a diagram as shown
I can't figure out how to do this in plotly. I tried to find a way using time series (https://plotly.com/python/time-series/) but I think there is nothing that helps me to do what I want.
This code brings me to an output which is quite near to what I want but I can not bring the y-axis to hours and show the gaps.
data_frame = pd.read_csv("awaytimes.csv", parse_dates=['start', 'end'])
data_frame['length'] = (data_frame['end'] - data_frame['start']) / pd.Timedelta(hours=1)
fig = px.bar(data_frame,
x="date",
y="length",
color='device_name',
barmode='group',
height=400)
fig.show()
I hope one of you can give me a hint.

Adding X-Y offsets to data points

I'm looking for a way to specify an X-Y offset to plotted data points. I'm just getting into Altair, so please bear with me.
The situation: I have a dataset recording daily measurements for 30 people. Every person can register several different types of measurements every day.
Example dataset & plot, with 2 people and 2 measurement types:
import pandas as pd
df = pd.DataFrame.from_dict({"date": pd.to_datetime(pd.date_range("2019-12-01", periods=5).repeat(4)),
"person": pd.np.tile(["Bob", "Amy"], 10),
"measurement_type": pd.np.tile(["score_a", "score_a", "score_b", "score_b"], 5),
"value": 20.0*np.random.random(size=20)})
import altair as alt
alt.Chart(df, width=600, height=100) \
.mark_circle(size=150) \
.encode(x = "date",
y = "person",
color = alt.Color("value"))
This gives me this graph:
In the example above, the 2 measurement types are plotted on top of each other. I would like to add an offset to the circles depending on the "measurement_type" column, so that they can all be made visible around the date-person location in the graph.
Here's a mockup of what I want to achieve:
I've been searching the docs but haven't figured out how to do this - been experimenting with the "stack" option, with the dx and dy options, ...
I have a feeling this should just be another encoding channel (offset or alike), but that doesn't exist.
Can anyone point me in the right direction?
There is currently no concept of an offset encoding in Altair, so the best approach to this will be to combine a column encoding with a y encoding, similar to the Grouped Bar Chart example in Altair's documentation:
alt.Chart(df,
width=600, height=100
).mark_circle(
size=150
).encode(
x = "date",
row='person',
y = "measurement_type",
color = alt.Color("value")
)
You can then fine-tune the look of the result using standard chart configuration settings:
alt.Chart(df,
width=600, height=alt.Step(25)
).mark_circle(
size=150
).encode(
x = "date",
row='person',
y = alt.Y("measurement_type", title=None),
color = alt.Color("value")
).configure_facet(
spacing=10
).configure_view(
strokeOpacity=0
)
Well I don't know what result you are getting up until know, but maybe write a function whith parameters likedef chart(DotsOnXAxis, FirstDotsOnYAxis, SecondDotsOnYAxis, OffsetAmount)
and then put those variables on the right place.
If you want an offset with the dots maybe put in a system like: SecondDotsOnYAxis = FirstDotsOnYAxis + OffsetAmount

plotly - multiple traces using a shared slider variable

As the title hints, I'm struggling to create a plotly chart that has multiple lines that are functions of the same slider variable.
I hacked something together using bits and pieces from the documentation: https://pastebin.com/eBixANqA. This works for one line.
Now I want to add more lines to the same chart, but this is where I'm struggling. https://pastebin.com/qZCMGeAa.
I'm getting a PlotlyListEntryError: Invalid entry found in 'data' at index, '0'
Path To Error: ['data'][0]
Can someone please help?
It looks like you were using https://plot.ly/python/sliders/ as a reference, unfortunately I don't have time to test with your code, but this should be easily adaptable. If you create each trace you want to plot in the same way that you have been:
trace1 = [dict(
type='scatter',
visible = False,
name = "trace title",
mode = 'markers+lines',
x = x[0:step],
y = y[0:step]) for step in range(len(x))]
where I note in my example my data is coming from pre-defined lists, where you are using a function, that's probably the only change you'll really need to make besides your own step size etc.
If you create a second trace in the same way, for example
trace2 = [dict(
type='scatter',
visible = False,
name = "trace title",
mode = 'markers+lines',
x = x2[0:step],
y = y2[0:step]) for step in range(len(x2))]`
Then you can put all your data together with the following
all_traces = trace1 + trace2
then you can just go ahead and plot it provided you have your layout set up correctly (it should remain unchanged from your single trace example):
fig = py.graph_objs.Figure(data=all_traces, layout=layout)
py.offline.iplot(fig)
Your slider should control both traces provided you were following https://plot.ly/python/sliders/ to get the slider working. You can combine multiple data dictionaries this way in order to have multiple plots controlled by the same slider.
I do note that if your lists of dictionaries containing data are of different length, that this gets topsy-turvy.

Auto set vbar line_width based on x_range in Bokeh

I have a vbar tied to a ColumnDataSource which gets updated based on some widget selections. If I start with line_width=5 it looks great with my initial data. However, when I update the graph, the x_range gets updated to fit the updated data and causes the relative width of the bars to change.
Ideally, the width should always be proportional to the number of bars displayed. I tried looking at the various properties on the x_range and xaxis to see if I could get the range and try to calculate the width myself but I haven't found anything that helps. Been looking around and the documentation and nothing. Any thoughts?
I finally figured this out with help from #bigreddot. It turns out that I was using the wrong property. Instead of using line_width I needed to use width. Since my x_range is a datetime range, and datetimes are expressed in milliseconds, I needed a large enough width to display correctly. This takes care of setting a proportional width when zooming in, since the width represents a specific period on the x_axis.
Since I have a function to change the freq of how I group my columns and update my ColumnDataSource.data, I just need to re-calculate the width when I update it.
Here's the working code:
def get_data(freq='MS'):
return pd.DataFrame(srs.groupby(pd.Grouper(freq=freq)).mean())
source = ColumnDataSource(data=ColumnDataSource.from_df(get_data()))
def get_width():
mindate = min(source.data['date'])
maxdate = max(source.data['date'])
return 0.8 * (maxdate-mindate).total_seconds()*1000 / len(source.data['date'])
f = figure(plot_width=550, plot_height=400, x_axis_type="datetime")
f.x_range.set(bounds='auto')
r = f.vbar(source=source, top='volume', x='date', width=get_width())
bar_glyph = f.renderers[-1].glyph
handle = show(f, notebook_handle=True)
and my update function:
def update_data(freq={'Quarter': 'QS', 'Month': 'MS', 'Week': 'W'}):
source.data = ColumnDataSource.from_df(get_data(freq))
r.glyph.width = get_width()
push_notebook()
i = interact(update_data)

Categories