I am working with two dictionaries, with each dictionary having 4 keys. The keys in both dictionaries are the same and correspond to a crop belt in the US.
df_vals.keys()
dict_keys(['corn', 'soybeans', 'winterwheat', 'springwheat'])
df_deltas.keys()
dict_keys(['corn', 'soybeans', 'winterwheat', 'springwheat'])
Within each key there is a dataframe with 5 columns, where the first column is time and the other columns are various variables. An example:
df_vals['corn'].head()
time 2m_temp_prod 2m_temp_area total_precip_prod total_precip_area
0 2020-09-17 00:00:00 299.346777 299.799234 0.000000 0.000000
1 2020-09-17 06:00:00 294.039512 294.443352 0.191070 0.286952
2 2020-09-17 12:00:00 292.959274 293.182931 0.155765 0.216606
3 2020-09-17 18:00:00 301.318046 301.767516 0.421768 0.485691
4 2020-09-18 00:00:00 300.623567 300.979650 0.363572 0.501164
df_deltas['corn'].head()
time 2m_temp_24hdelta_prod 2m_temp_24hdelta_area total_precip_24hdelta_prod total_precip_24hdelta_area
0 2020-09-17 -0.330566 -0.294223 -1.441738 -0.896948
1 2020-09-18 -0.063527 -0.066953 -3.242770 -2.002193
2 2020-09-19 -0.276225 -0.238248 -1.954929 -1.326568
3 2020-09-20 -0.778811 -0.747444 1.080549 0.523297
4 2020-09-21 -0.121823 -0.008793 -2.857210 -1.974432
Notice df_vals
is 6-hourly and df_deltas
is 24 hours. Now, I want to make a plot with a double y-axis, where a variable in df_vals
is plotted as a line and the corresponding variable in df_deltas
is plotted as a bar. This is easy to do and can be done, like so:
fig = make_subplots(specs=[[{"secondary_y": True}]])
time_vals=df_vals['corn']['time']
time_deltas=df_deltas['corn']['time']
temp_prod_vals=df_vals['corn']['2m_temp_prod']
temp_prod_deltas=df_deltas['corn']['2m_temp_24hdelta_prod']
fig.add_trace(go.Scatter(x=time_vals, y=((temp_prod_vals-273)*(9/5)+32),mode='lines', line=dict(color='red', width=4), yaxis='y1', hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Temp: %{y:.2f} F<extra></extra>'),secondary_y=False)
fig.add_trace(go.Bar(x=time_deltas, y=round((temp_prod_deltas*(9/5)),2), marker_color='black', opacity=0.6, yaxis='y2', hovertemplate='Date: %{x|%d %b}<br>Delta: %{y:.2i} F<extra></extra>'),secondary_y=True)
That generates this plot:
Next, we want to add dropdown menus to switch between different crops (switch between the keys in our df_vals
and df_deltas
dicts). That can be done like so:
button1= [dict(method= 'update',
args= [{'y': [((df_vals[i]['2m_temp_prod']-273)*(9/5)+32), round((df_deltas[i]['2m_temp_24hdelta_prod']*(9/5)),2)]}],
label=i.title()) for i, j in list(zip(df_vals.keys(), df_deltas.keys()))]
Great, now we have a plot that goes through each dict key and updates depending on which crop you pick.
Now, what if we want to cycle through other columns (variables) in a particular dataframe? So far we have only been plotting temperatures. Let's plot precipitation now. We need to add a second button to do this like so:
button2= [dict(method= 'update',
args= [{'y': [(df_vals[i]['total_precip_prod'].cumsum())/25.4, round((df_deltas[i]['total_precip_24hdelta_prod']/25.4),2)]}],
label=i.title()) for i, j in list(zip(df_vals.keys(), df_deltas.keys()))]
This button now looks exactly like our first button, where each dropdown option is a key in our dicts. The completed plot looks like this:
This plot works as intended, but is kind of clunky since we have two dropdowns with the same exact options. Now we have arrived at the purpose of this post. Instead of having two buttons for the variables we are plotting (temperature and precip), I want one button for the crop belt and one button for the variable. However, I can't seem to figure out how to orient my code to accomplish this. Any help would be appreciated.
Edit:
df_vals['corn']
time 2m_temp_prod 2m_temp_area total_precip_prod total_precip_area
0 2020-09-18 00:00:00 299.346777 299.799234 0.000000 0.000000
1 2020-09-18 06:00:00 294.039512 294.443352 0.191070 0.286952
2 2020-09-18 12:00:00 292.959274 293.182931 0.155765 0.216606
3 2020-09-18 18:00:00 301.318046 301.767516 0.421768 0.485691
4 2020-09-19 00:00:00 300.623567 300.979650 0.363572 0.501164
... ... ... ... ... ...
56 2020-10-02 00:00:00 301.177141 301.052273 0.371209 0.408515
57 2020-10-02 06:00:00 295.874298 295.720135 0.281793 0.300564
58 2020-10-02 12:00:00 293.838787 293.686738 0.586887 0.549365
59 2020-10-02 18:00:00 302.384474 302.191334 0.492712 0.493798
60 2020-10-03 00:00:00 300.920766 300.817993 0.522374 0.531138
df_deltas['corn']
time 2m_temp_24hdelta_prod 2m_temp_24hdelta_area total_precip_24hdelta_prod total_precip_24hdelta_area
0 2020-09-18 -0.330566 -0.294223 -1.441738 -0.896948
1 2020-09-19 -0.063527 -0.066953 -3.242770 -2.002193
2 2020-09-20 -0.276225 -0.238248 -1.954929 -1.326568
3 2020-09-21 -0.778811 -0.747444 1.080549 0.523297
4 2020-09-22 -0.121823 -0.008793 -2.857210 -1.974432
5 2020-09-23 0.484487 0.258438 -0.603610 -0.475656
6 2020-09-24 -0.779540 -0.865384 0.068153 0.094013
7 2020-09-25 -1.373656 -1.330636 -0.377604 -0.248026
8 2020-09-26 -0.792595 -0.778074 -0.218221 -0.161647
9 2020-09-27 -0.608407 -0.610188 0.143853 0.129186
10 2020-09-28 -0.653274 -0.660259 -0.096111 -0.025385
11 2020-09-29 -0.533418 -0.537711 -0.441704 -0.370940
12 2020-09-30 -0.321460 -0.348938 -0.992614 -0.762149
13 2020-10-01 0.005672 -0.129977 -0.266382 -0.219744
14 2020-10-02 0.294875 0.080317 -0.043508 -0.063877
15 2020-10-03 0.000000 0.000000 0.000000 0.000000
The other 3 keys (soybeans, winterwheat, and springwheat) all have the same structure as this, just different values.
Edit 3: Posting the full code and libraries.
import xarray as xr
import glob
import datetime as dt
import pandas as pd
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
I load in several different netcdf time series files using xarray. These files are forecast model data which run out 15 days from the day the model was run. There are also delta files, which tell you the change of the forecast variable from 24 hours ago (so they only go out 14 days).
corn=glob.glob('/Users/eli.turaskyriskpulse.com/Documents/Python_data/plotly_practice/20200812_00/20200812_00_ec_ens_*'+'corn'+'_timeseries.nc')
soybean=glob.glob('/Users/eli.turaskyriskpulse.com/Documents/Python_data/plotly_practice/20200812_00/20200812_00_ec_ens_*'+'soybeans'+'_timeseries.nc')
winterwheat=glob.glob('/Users/eli.turaskyriskpulse.com/Documents/Python_data/plotly_practice/20200812_00/20200812_00_ec_ens_*'+'winterwheat'+'_timeseries.nc')
springwheat=glob.glob('/Users/eli.turaskyriskpulse.com/Documents/Python_data/plotly_practice/20200812_00/20200812_00_ec_ens_*'+'springwheat'+'_timeseries.nc')
all_files=[corn, soybean,winterwheat,springwheat]
crop_names=['corn', 'soybeans', 'winterwheat', 'springwheat']
data={}
for i in all_files:
data[str(i)]=xr.open_mfdataset(i).load()
This loads in all of the files into a dictionary, with each key representing the crop for which the variables are forecasted for. Each time series is set up in 6 hourly format. However, I want to further adjust this because I need the time for the deltas to be in 24 hour increments, instead of 6 hour.
today=dt.date.today()
df_vals={}
df_deltas={}
for i in data.keys():
df_vals[str(i)]=data[i].to_dataframe().reset_index()
df_vals[i]['time']=pd.date_range((today-dt.timedelta(days=5)), (today+dt.timedelta(days=10)), freq='6H')
df_deltas[i]=df_vals[i].filter(regex='delta')
df_deltas[i]['time']=pd.date_range((today-dt.timedelta(days=5)), (today+dt.timedelta(days=10)), freq='6H')
df_deltas[i]=df_deltas[i].set_index('time')
cols = [c for c in df_deltas[i].columns if "precip" in c or "temp" in c]
aggs = {c:'sum' if 'precip' in c else 'mean' for c in cols}
df_deltas[i]=df_deltas[i].resample('24h').agg(aggs)
df_deltas[i]=df_deltas[i].reset_index().fillna(0)
df_vals[i]=df_vals[i][df_vals[i].columns.drop(list(df_vals[i].filter(regex='delta')))].fillna(value=0)
for i in df_vals.keys():
df_vals[i]=df_vals[i].drop(['snowfall_prod', 'snowfall_area'], 1)
Now I have reached what I wanted. Two dictionaries that contain 4 keys (representing the 4 crops), with df_vals
at 6-hourly time resolution and df_deltas
at 24-hourly time resolution. Then, I plot.
fig = make_subplots(specs=[[{"secondary_y": True}]])
time_vals=df_vals['corn']['time']
time_deltas=df_deltas['corn']['time']
temp_prod_vals=df_vals['corn']['2m_temp_prod']
temp_prod_deltas=df_deltas['corn']['2m_temp_24hdelta_prod']
fig.add_trace(go.Scatter(x=time_vals, y=((temp_prod_vals-273)*(9/5)+32),mode='lines', line=dict(color='red', width=4), yaxis='y1', hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Temp: %{y:.2f} F<extra></extra>'),secondary_y=False)
fig.add_trace(go.Bar(x=time_deltas, y=round((temp_prod_deltas*(9/5)),2), marker_color='black', opacity=0.6, yaxis='y2', hovertemplate='Date: %{x|%d %b}<br>Delta: %{y:.2i} F<extra></extra>'),secondary_y=True)
button1= [dict(method= 'update',
args= [{'y': [((df_vals[i]['2m_temp_prod']-273)*(9/5)+32), round((df_deltas[i]['2m_temp_24hdelta_prod']*(9/5)),2)]}],
label=i.title()) for i, j in list(zip(df_vals.keys(), df_deltas.keys()))]
button2= [dict(method= 'update',
args= [{'y': [(df_vals[i]['total_precip_prod'].cumsum())/25.4, round((df_deltas[i]['total_precip_24hdelta_prod']/25.4),2)]}],
label=i.title()) for i, j in list(zip(df_vals.keys(), df_deltas.keys()))]
fig.update_layout(yaxis2_showgrid=False, title_text='Plot title',showlegend=False,
title_x=0.4,
width=850,
height=450,annotations=
[dict(text='Temps:', x=0.02, xref='paper', y=1.12, yref='paper', align='left', showarrow=False),
dict(text='Precip:', x=0.36, xref='paper', y=1.12, yref='paper', align='left', showarrow=False)],
updatemenus=[dict(active=0,
x=0.1, y=1.18,
pad={"r": 10, "t": 10},
xanchor='left',
yanchor='top',
buttons=button1),
dict(active=0,
x=0.40, y=1.18,
pad={"r": 10, "t": 10},
xanchor='left',
yanchor='top',
buttons=button2)
])