简体   繁体   中英

Plotly: How to animate a bar chart with multiple groups using plotly express?

I have a dataframe that looks like this:

在此处输入图片说明

I want to have one bar for old freq and one for new freq. Currently I have graph that looks like this:

在此处输入图片说明

This is what the code looks like:

freq_df['date'] = pd.to_datetime(freq_df['date'])
freq_df['hour'] = freq_df['hour'].astype(str)

fig = px.bar(freq_df, x="hour", y="old freq",hover_name = "date",
  animation_frame= freq_df.date.dt.day)
fig.update_layout(transition = {'duration': 2000})

How do I add another bar?

Explanation about DF:

It has frequencies relevant to each hour in a specific date.

Edit: One approach could be to create a category column and add old and new freq and assign values in another freq column. How do I do that :p ?

Edit: Here is the DF

    ,date,hour,old freq,new freq
43,2020-09-04,18,273,224.0
44,2020-09-04,19,183,183.0
45,2020-09-04,20,99,111.0
46,2020-09-04,21,130,83.0
47,2020-09-04,22,48,49.0
48,2020-09-04,23,16,16.0
49,2020-09-05,0,8,6.0
50,2020-09-05,1,10,10.0
51,2020-09-05,2,4,4.0
52,2020-09-05,3,7,7.0
53,2020-09-05,4,25,21.0
54,2020-09-05,5,114,53.0
55,2020-09-05,6,284,197.0
56,2020-09-05,7,343,316.0
57,2020-09-05,8,418,419.0
58,2020-09-05,9,436,433.0
59,2020-09-05,10,469,396.0
60,2020-09-05,11,486,300.0
61,2020-09-05,12,377,140.0
62,2020-09-05,13,552,103.0
63,2020-09-05,14,362,117.0
64,2020-09-05,15,512,93.0
65,2020-09-05,16,392,41.0
66,2020-09-05,17,268,31.0
67,2020-09-05,18,223,30.0
68,2020-09-05,19,165,24.0
69,2020-09-05,20,195,15.0
70,2020-09-05,21,90,
71,2020-09-05,22,46,1.0
72,2020-09-05,23,17,1.0

The answer in two steps:

1. Perform a slight transformation of your data using pd.wide_to_long :

df_long = pd.wide_to_long(freq_df, stubnames='freq',
                          i=['date', 'hour'], j='type',
                          sep='_', suffix='\w+').reset_index()

2. Plot two groups of bar traces using:

fig1 = px.bar(df_long, x='hour', y = 'freq', hover_name = "date", color='type',
            animation_frame= 'date', barmode='group')

This is the result for the first frame:

在此处输入图片说明

This is the result for the second frame when you run the animation:

在此处输入图片说明


The details:

If I understand your question correctly, you'd like to animate a bar chart where you've got one bar for each hour for your two frequencies freq_old and freq_new like this:

在此处输入图片说明

If that's the case, then you sample data is no good since your animation critera is hour per date and you've only got four observations (hours) for 2020-09-04 and then 24 observations for 2020-09-05 . But don't worry, since your question triggered my interest I just as well made some sample data that will in fact work the way you seem to want them to.

The only real challenge is that px.bar will not accept y= [freq_old, freq_new] , or something to that effect, to build your two bar series of different categories for you. But you can make px.bar build two groups of bars by providing a color argument. However, you'll need a column to identify your different freqs like this:

0    new
1    old
2    new
3    old
4    new
5    old
6    new
7    old
8    new
9    old

In other words, you'll have to transform your dataframe, which originally has a wide format, to a long format like this:

    date        hour    type    day freq
0   2020-01-01  0       new     1   7.100490
1   2020-01-01  0       old     1   2.219932
2   2020-01-01  1       new     1   7.015528
3   2020-01-01  1       old     1   8.707323
4   2020-01-01  2       new     1   7.673314
5   2020-01-01  2       old     1   2.067192
6   2020-01-01  3       new     1   9.743495
7   2020-01-01  3       old     1   9.186109
8   2020-01-01  4       new     1   3.737145
9   2020-01-01  4       old     1   4.884112

And that's what this snippet does:

df_long = pd.wide_to_long(freq_df, stubnames='freq',
                          i=['date', 'hour'], j='type',
                          sep='_', suffix='\w+').reset_index()

stubnames uses a prefix to identify the columns you'd like to stack into a long format. And that's why I've renamed new_freq and old_freq to freq_new and freq_old , respectively. j='type' simply takes the last parts of your cartegory names using sep='_' and produces the column that we need to tell the freqs from eachother:

type
old
new
old
...

suffix='\\w+' tells pd.wide_to_long that we're using non-integers as suffixes. And that's it!

Complete code:

# imports
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import random

# sample data
observations = 24*5
np.random.seed(5); cols = list('a')
freq_old = np.random.uniform(low=-1, high=1, size=observations).tolist()
freq_new = np.random.uniform(low=-1, high=1, size=observations).tolist()
date = [t[:10] for t in pd.date_range('2020', freq='H', periods=observations).format()]
hour = [int(t[11:13].lstrip()) for t in pd.date_range('2020', freq='H', periods=observations).format()]

# sample dataframe of a wide format such as yours
freq_df=pd.DataFrame({'date': date, 
                'hour':hour,
                 'freq_new':freq_new,
                 'freq_old':freq_old})
freq_df['day']=pd.to_datetime(freq_df['date']).dt.day

# attempt to make my random data look a bit
# like your real world data.
# but don't worry too much about that...
freq_df.freq_new = abs(freq_df.freq_new.cumsum())
freq_df.freq_old = abs(freq_df.freq_old.cumsum())

# sample dataframe of a long format that px.bar likes
df_long = pd.wide_to_long(freq_df, stubnames='freq',
                          i=['date', 'hour'], j='type',
                          sep='_', suffix='\w+').reset_index()

# plotly express bar chart with multiple bar groups.
fig = px.bar(df_long, x='hour', y = 'freq', hover_name = "date", color='type',
            animation_frame= 'date', barmode='group')

# set up a sensible range for the y-axis
fig.update_layout(yaxis=dict(range=[df_long['freq'].min()*0.8,df_long['freq'].max()*1.2]))
fig.show()

I was able to create the bars for both the old and new frequencies, however using a separate plot for each day (Plotly Express Bar Charts don't seem to have support for multiple series). Here is the code for doing so:

# Import packages
import pandas as pd
import numpy as np

import plotly.graph_objs as go
import plotly
import plotly.express as px
from plotly.offline import init_notebook_mode, plot, iplot, download_plotlyjs
init_notebook_mode(connected=True)
plotly.offline.init_notebook_mode(connected=True)

# Start formatting data
allDates = np.unique(df.date)
numDates = allDates.shape[0]
print(numDates)

for i in range(numDates):
    df = original_df.loc[original_df.date == allDates[i]]
    
    oldFreqData = go.Bar(x=df["hour"].to_numpy(), y=df["old_freq"].to_numpy(), name="Old Frequency")
    newFreqData = go.Bar(x=df["hour"].to_numpy(), y=df["new_freq"].to_numpy(), name="New Frequency")

    fig = go.Figure(data=[oldFreqData,newFreqData])
    
    fig.update_layout(title=allDates[i])
    fig.update_xaxes(title='Hour')
    fig.update_yaxes(title='Frequency')
    
    fig.show()

where df is the dataframe DF from your question.

Here is the output: Plotly_Multiple_Series

However, if you prefer the use of the animation frame from Plotly Express, you can have two separate plots: one for old frequencies and one for new using this code:

# Reformat data
df = original_df
dates = pd.to_datetime(np.unique(df.date)).strftime('%Y-%m-%d')
numDays = dates.shape[0]
print(numDays)

hours = np.arange(0,24)
numHours = hours.shape[0]

allDates = []
allHours = []
oldFreqs = []
newFreqs = []
for i in range(numDays):
    for j in range(numHours): 
        allDates.append(dates[i])
        allHours.append(j)
        if (df.loc[df.date == dates[i]].loc[df.hour == j].shape[0] != 0):       # If data not missing
            oldFreqs.append(df.loc[df.date == dates[i]].loc[df.hour == j].old_freq.to_numpy()[0])
            newFreqs.append(df.loc[df.date == dates[i]].loc[df.hour == j].new_freq.to_numpy()[0])
        else:
            oldFreqs.append(0)
            newFreqs.append(0)
    
d = {'Date': allDates, 'Hour': allHours, 'Old_Freq': oldFreqs, 'New_Freq': newFreqs, 'Comb': combined}
df2 = pd.DataFrame(data=d)

# Create px plot with animation
fig = px.bar(df2, x="Hour", y="Old_Freq", hover_data=["Old_Freq","New_Freq"], animation_frame="Date")
fig.show()

fig2 = px.bar(df2, x="Hour", y="New_Freq", hover_data=["Old_Freq","New_Freq"], animation_frame="Date")
fig2.show()

and here is the plot from that code: 情节动画帧

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM