def flow_duration_curve(hist: pd.DataFrame, titles: dict = False, outformat: str = 'plotly') -> go.Figure: """ Makes the streamflow ensemble data and metadata into a plotly plot Args: hist: the csv response from historic_simulation titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} outformat: either 'json', 'plotly', or 'plotly_html' (default plotly) Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['json', 'plotly_scatters', 'plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick json, plotly, plotly_scatters, or plotly_html') # process the hist dataframe to create the flow duration curve sorted_hist = hist.values.flatten() sorted_hist.sort() # ranks data from smallest to largest ranks = len(sorted_hist) - scipy.stats.rankdata(sorted_hist, method='average') # calculate probability of each rank prob = [(ranks[i] / (len(sorted_hist) + 1)) for i in range(len(sorted_hist))] plot_data = { 'x_probability': prob, 'y_flow': sorted_hist, 'y_max': sorted_hist[0], } if outformat == 'json': return plot_data scatter_plots = [ go.Scatter( name='Flow Duration Curve', x=plot_data['x_probability'], y=plot_data['y_flow']) ] if outformat == 'plotly_scatters': return scatter_plots layout = go.Layout( title=_build_title('Flow Duration Curve', titles), xaxis={'title': 'Exceedence Probability'}, yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']}, ) figure = go.Figure(scatter_plots, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose json, plotly, plotly_scatters, or plotly_html')
def corrected_day_average(corrected: pd.DataFrame, simulated: pd.DataFrame, observed: pd.DataFrame, merged_sim_obs: pd.DataFrame = False, merged_cor_obs: pd.DataFrame = False, titles: dict = None, outformat: str = 'plotly') -> go.Figure or str: """ Calculates and plots the daily average streamflow. This function uses hydrostats.data.merge_data on the 3 inputs. If you have already computed these because you are doing a full comparison of bias correction, you can provide them to save time Args: corrected: the response from the geoglows.bias.correct_historical_simulation function simulated: the csv response from historic_simulation merged_sim_obs: (optional) if you have already computed it, hydrostats.data.merge_data(simulated, observed) merged_cor_obs: (optional) if you have already computed it, hydrostats.data.merge_data(corrected, observed) observed: the dataframe of observed data. Must have a datetime index and a single column of flow values outformat: either 'plotly' or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Returns: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if corrected is False and simulated is False and observed is False: if merged_sim_obs is not False and merged_cor_obs is not False: pass # if you provided the merged dataframes already, we use those else: # merge the datasets together merged_sim_obs = hd.merge_data(sim_df=simulated, obs_df=observed) merged_cor_obs = hd.merge_data(sim_df=corrected, obs_df=observed) daily_avg = hd.daily_average(merged_sim_obs) daily_avg2 = hd.daily_average(merged_cor_obs) scatters = [ go.Scatter(x=daily_avg.index, y=daily_avg.iloc[:, 1].values, name='Observed Data'), go.Scatter(x=daily_avg.index, y=daily_avg.iloc[:, 0].values, name='Simulated Data'), go.Scatter(x=daily_avg2.index, y=daily_avg2.iloc[:, 0].values, name='Corrected Simulated Data'), ] layout = go.Layout( title=_build_title('Daily Average Streamflow Comparison', titles), xaxis=dict(title='Days'), yaxis=dict(title='Discharge (m<sup>3</sup>/s)', autorange=True), showlegend=True) if outformat == 'plotly': return go.Figure(data=scatters, layout=layout) elif outformat == 'plotly_html': return offline_plot( go.Figure(data=scatters, layout=layout), config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose plotly or plotly_html')
def monthly_averages(monavg: pd.DataFrame, titles: dict = False, outformat: str = 'plotly') -> go.Figure: """ Makes the daily_averages data and metadata into a plotly plot Args: monavg: the csv response from monthly_averages titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} outformat: either 'plotly', or 'plotly_html' (default plotly) Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['plotly_scatters', 'plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick plotly, plotly_scatters, or plotly_html') scatter_plots = [ go.Scatter( name='Average Monthly Flow', x=pd.to_datetime(monavg.index, format='%m').strftime('%B'), y=monavg.values.flatten(), line=dict(color='blue') ), ] if outformat == 'plotly_scatters': return scatter_plots layout = go.Layout( title=_build_title('Monthly Average Streamflow (Simulated)', titles), yaxis={'title': 'Streamflow (m<sup>3</sup>/s)'}, xaxis={'title': 'Month'}, ) figure = go.Figure(scatter_plots, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose plotly, plotly_scatters, or plotly_html')
def plot_and_save(figure, html_file_path, plotly_html_file_path): if html_file_path is not None: print( offline_plot( figure, filename=html_file_path, auto_open=False, show_link=False ) ) if plotly_html_file_path is not None: print( plotly_plot( figure, filename=plotly_html_file_path, file_opt="overwrite", sharing="public", auto_open=False, show_link=False, ) ) iplot(figure, show_link=False)
def animate_3d(image, nb_frames=100, plot=False): if nb_frames > image.shape[2]: nb_frames = image.shape[2] vol = image volume = vol.T r, c = volume[0].shape # Define frames fig = go.Figure(frames=[ go.Frame( data=go.Surface(z=((nb_frames - 1) / 10 - k * 0.1) * np.ones((r, c)), surfacecolor=np.flipud(volume[nb_frames - 1 - k]), cmin=image.min(), cmax=image.max()), name=str(k) # you need to name the frame ) for k in range(nb_frames) ]) # Add data to be displayed before animation starts fig.add_trace( go.Surface(z=(nb_frames - 1) / 10 * np.ones((r, c)), surfacecolor=np.flipud(volume[nb_frames - 1]), colorscale='Gray', cmin=image.min(), cmax=image.max(), colorbar=dict(thickness=20, ticklen=4))) def frame_args(duration): return { "frame": { "duration": duration }, "mode": "immediate", "fromcurrent": True, "transition": { "duration": duration, "easing": "linear" }, } sliders = [{ "pad": { "b": 10, "t": 60 }, "len": 0.9, "x": 0.1, "y": 0, "steps": [{ "args": [[f.name], frame_args(0)], "label": str(k), "method": "animate", } for k, f in enumerate(fig.frames)], }] # Layout fig.update_layout( title='Slices in volumetric data', width=600, height=600, scene=dict( zaxis=dict(range=[-0.1, nb_frames / 10], autorange=False), aspectratio=dict(x=1, y=1, z=1), ), updatemenus=[{ "buttons": [ { "args": [None, frame_args(50)], "label": "▶", # play symbol "method": "animate", }, { "args": [[None], frame_args(0)], "label": "◼", # pause symbol "method": "animate", }, ], "direction": "left", "pad": { "r": 10, "t": 70 }, "type": "buttons", "x": 0.1, "y": 0, }], sliders=sliders) if plot: fig.show() offline_plot(fig) return fig
def forecast_stats(stats: pd.DataFrame, rperiods: pd.DataFrame = None, titles: dict = False, outformat: str = 'plotly') -> go.Figure: """ Makes the streamflow data and optional metadata into a plotly plot Args: stats: the csv response from forecast_stats rperiods: the csv response from return_periods titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} outformat: 'json', 'plotly', 'plotly_scatters', or 'plotly_html' (default plotly) Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['json', 'plotly_scatters', 'plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick json, plotly, plotly_scatters, or plotly_html') # Start processing the inputs dates = stats.index.tolist() startdate = dates[0] enddate = dates[-1] plot_data = { 'x_stats': stats['flow_avg_m^3/s'].dropna(axis=0).index.tolist(), 'x_hires': stats['high_res_m^3/s'].dropna(axis=0).index.tolist(), 'y_max': max(stats['flow_max_m^3/s']), 'flow_max': list(stats['flow_max_m^3/s'].dropna(axis=0)), 'flow_75%': list(stats['flow_75%_m^3/s'].dropna(axis=0)), 'flow_avg': list(stats['flow_avg_m^3/s'].dropna(axis=0)), 'flow_25%': list(stats['flow_25%_m^3/s'].dropna(axis=0)), 'flow_min': list(stats['flow_min_m^3/s'].dropna(axis=0)), 'high_res': list(stats['high_res_m^3/s'].dropna(axis=0)), } if rperiods is not None: plot_data.update(rperiods.to_dict(orient='index').items()) max_visible = max(max(plot_data['flow_75%']), max(plot_data['flow_avg']), max(plot_data['high_res'])) rperiod_scatters = _rperiod_scatters(startdate, enddate, rperiods, plot_data['y_max'], max_visible) else: rperiod_scatters = [] if outformat == 'json': return plot_data scatter_plots = [ # Plot together so you can use fill='toself' for the shaded box, also separately so the labels appear go.Scatter(name='Maximum & Minimum Flow', x=plot_data['x_stats'] + plot_data['x_stats'][::-1], y=plot_data['flow_max'] + plot_data['flow_min'][::-1], legendgroup='boundaries', fill='toself', visible='legendonly', line=dict(color='lightblue', dash='dash')), go.Scatter(name='Maximum', x=plot_data['x_stats'], y=plot_data['flow_max'], legendgroup='boundaries', visible='legendonly', showlegend=False, line=dict(color='darkblue', dash='dash')), go.Scatter(name='Minimum', x=plot_data['x_stats'], y=plot_data['flow_min'], legendgroup='boundaries', visible='legendonly', showlegend=False, line=dict(color='darkblue', dash='dash')), go.Scatter(name='25-75 Percentile Flow', x=plot_data['x_stats'] + plot_data['x_stats'][::-1], y=plot_data['flow_75%'] + plot_data['flow_25%'][::-1], legendgroup='percentile_flow', fill='toself', line=dict(color='lightgreen'), ), go.Scatter(name='75%', x=plot_data['x_stats'], y=plot_data['flow_75%'], showlegend=False, legendgroup='percentile_flow', line=dict(color='green'), ), go.Scatter(name='25%', x=plot_data['x_stats'], y=plot_data['flow_25%'], showlegend=False, legendgroup='percentile_flow', line=dict(color='green'), ), go.Scatter(name='High Resolution Forecast', x=plot_data['x_hires'], y=plot_data['high_res'], line={'color': 'black'}, ), go.Scatter(name='Ensemble Average Flow', x=plot_data['x_stats'], y=plot_data['flow_avg'], line=dict(color='blue'), ), ] scatter_plots += rperiod_scatters if outformat == 'plotly_scatters': return scatter_plots layout = go.Layout( title=_build_title('Forecasted Streamflow', titles), yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']}, xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate], 'hoverformat': '%b %d %Y', 'tickformat': '%b %d %Y'}, ) figure = go.Figure(scatter_plots, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) return
def corrected_volume_compare(corrected: pd.DataFrame, simulated: pd.DataFrame, observed: pd.DataFrame, merged_sim_obs: pd.DataFrame = False, merged_cor_obs: pd.DataFrame = False, titles: dict = None, outformat: str = 'plotly') -> go.Figure or str: """ Calculates and plots the cumulative volume output on each of the 3 datasets provided. This function uses hydrostats.data.merge_data on the 3 inputs. If you have already computed these because you are doing a full comparison of bias correction, you can provide them to save time Args: corrected: the response from the geoglows.bias.correct_historical_simulation function simulated: the csv response from historic_simulation observed: the dataframe of observed data. Must have a datetime index and a single column of flow values merged_sim_obs: (optional) if you have already computed it, hydrostats.data.merge_data(simulated, observed) merged_cor_obs: (optional) if you have already computed it, hydrostats.data.merge_data(corrected, observed) outformat: either 'plotly' or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Returns: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if corrected is False and simulated is False and observed is False: if merged_sim_obs is not False and merged_cor_obs is not False: pass # if you provided the merged dataframes already, we use those else: # merge the datasets together merged_sim_obs = hd.merge_data(sim_df=simulated, obs_df=observed) merged_cor_obs = hd.merge_data(sim_df=corrected, obs_df=observed) sim_array = merged_sim_obs.iloc[:, 0].values obs_array = merged_sim_obs.iloc[:, 1].values corr_array = merged_cor_obs.iloc[:, 0].values sim_volume_dt = sim_array * 0.0864 obs_volume_dt = obs_array * 0.0864 corr_volume_dt = corr_array * 0.0864 sim_volume_cum = [] obs_volume_cum = [] corr_volume_cum = [] sum_sim = 0 sum_obs = 0 sum_corr = 0 for i in sim_volume_dt: sum_sim = sum_sim + i sim_volume_cum.append(sum_sim) for j in obs_volume_dt: sum_obs = sum_obs + j obs_volume_cum.append(sum_obs) for k in corr_volume_dt: sum_corr = sum_corr + k corr_volume_cum.append(sum_corr) observed_volume = go.Scatter(x=merged_sim_obs.index, y=obs_volume_cum, name='Observed', ) simulated_volume = go.Scatter(x=merged_sim_obs.index, y=sim_volume_cum, name='Simulated', ) corrected_volume = go.Scatter(x=merged_cor_obs.index, y=corr_volume_cum, name='Corrected Simulated', ) layout = go.Layout( title=_build_title('Cumulative Volume Comparison', titles), xaxis=dict(title='Datetime', ), yaxis=dict(title='Volume (m<sup>3</sup>)', autorange=True), showlegend=True) if outformat == 'plotly': return go.Figure(data=[observed_volume, simulated_volume, corrected_volume], layout=layout) elif outformat == 'plotly_html': return offline_plot( go.Figure(data=[observed_volume, simulated_volume, corrected_volume], layout=layout), config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose plotly or plotly_html')
def corrected_scatterplots(corrected: pd.DataFrame, simulated: pd.DataFrame, observed: pd.DataFrame, merged_sim_obs: pd.DataFrame = False, merged_cor_obs: pd.DataFrame = False, titles: dict = None, outformat: str = 'plotly') -> go.Figure or str: """ Creates a plot of corrected discharge, observered discharge, and simulated discharge. This function uses hydrostats.data.merge_data on the 3 inputs. If you have already computed these because you are doing a full comparison of bias correction, you can provide them to save time Args: corrected: the response from the geoglows.bias.correct_historical_simulation function simulated: the csv response from historic_simulation observed: the dataframe of observed data. Must have a datetime index and a single column of flow values merged_sim_obs: (optional) if you have already computed it, hydrostats.data.merge_data(simulated, observed) merged_cor_obs: (optional) if you have already computed it, hydrostats.data.merge_data(corrected, observed) outformat: either 'plotly' or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Returns: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if corrected is False and simulated is False and observed is False: if merged_sim_obs is not False and merged_cor_obs is not False: pass # if you provided the merged dataframes already, we use those else: # merge the datasets together merged_sim_obs = hd.merge_data(sim_df=simulated, obs_df=observed) merged_cor_obs = hd.merge_data(sim_df=corrected, obs_df=observed) # get the min/max values for plotting the 45 degree line min_value = min(min(merged_sim_obs.iloc[:, 1].values), min(merged_sim_obs.iloc[:, 0].values)) max_value = max(max(merged_sim_obs.iloc[:, 1].values), max(merged_sim_obs.iloc[:, 0].values)) # do a linear regression on both of the merged dataframes slope, intercept, r_value, p_value, std_err = scipy.stats.linregress(merged_sim_obs.iloc[:, 0].values, merged_sim_obs.iloc[:, 1].values) slope2, intercept2, r_value2, p_value2, std_err2 = scipy.stats.linregress(merged_cor_obs.iloc[:, 0].values, merged_cor_obs.iloc[:, 1].values) scatter_sets = [ go.Scatter( x=merged_sim_obs.iloc[:, 0].values, y=merged_sim_obs.iloc[:, 1].values, mode='markers', name='Original Data', marker=dict(color='#ef553b') ), go.Scatter( x=merged_cor_obs.iloc[:, 0].values, y=merged_cor_obs.iloc[:, 1].values, mode='markers', name='Corrected', marker=dict(color='#00cc96') ), go.Scatter( x=[min_value, max_value], y=[min_value, max_value], mode='lines', name='45 degree line', line=dict(color='black') ), go.Scatter( x=[min_value, max_value], y=[slope * min_value + intercept, slope * max_value + intercept], mode='lines', name=f'Y = {round(slope, 2)}x + {round(intercept, 2)} (Original)', line=dict(color='red') ), go.Scatter( x=[min_value, max_value], y=[slope2 * min_value + intercept2, slope2 * max_value + intercept2], mode='lines', name=f'Y = {round(slope2, 2)}x + {round(intercept2, 2)} (Corrected)', line=dict(color='green') ) ] updatemenus = [ dict(active=0, buttons=[dict(label='Linear Scale', method='update', args=[{'visible': [True, True]}, {'title': 'Linear scale', 'yaxis': {'type': 'linear'}}]), dict(label='Log Scale', method='update', args=[{'visible': [True, True]}, {'title': 'Log scale', 'xaxis': {'type': 'log'}, 'yaxis': {'type': 'log'}}]), ] ) ] layout = go.Layout(title=_build_title('Bias Correction Scatter Plot', titles), xaxis=dict(title='Simulated', ), yaxis=dict(title='Observed', autorange=True), showlegend=True, updatemenus=updatemenus) if outformat == 'plotly': return go.Figure(data=scatter_sets, layout=layout) elif outformat == 'plotly_html': return offline_plot( go.Figure(data=scatter_sets, layout=layout), config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose plotly or plotly_html')
def corrected_historical(corrected: pd.DataFrame, simulated: pd.DataFrame, observed: pd.DataFrame, rperiods: pd.DataFrame = None, titles: dict = None, outformat: str = 'plotly') -> go.Figure or str: """ Creates a plot of corrected discharge, observered discharge, and simulated discharge Args: corrected: the response from the geoglows.bias.correct_historical_simulation function\ simulated: the csv response from historic_simulation observed: the dataframe of observed data. Must have a datetime index and a single column of flow values rperiods: the csv response from return_periods outformat: either 'json', 'plotly', or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Returns: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ startdate = corrected.index[0] enddate = corrected.index[-1] plot_data = { 'x_simulated': corrected.index.tolist(), 'x_observed': observed.index.tolist(), 'y_corrected': corrected.values.flatten(), 'y_simulated': simulated.values.flatten(), 'y_observed': observed.values.flatten(), 'y_max': max(corrected.values.max(), observed.values.max(), simulated.values.max()), } if rperiods is not None: plot_data.update(rperiods.to_dict(orient='index').items()) rperiod_scatters = _rperiod_scatters(startdate, enddate, rperiods, plot_data['y_max'], plot_data['y_max']) else: rperiod_scatters = [] if outformat == 'json': return plot_data scatters = [ go.Scatter( name='Simulated Data', x=plot_data['x_simulated'], y=plot_data['y_simulated'], line=dict(color='red') ), go.Scatter( name='Observed Data', x=plot_data['x_observed'], y=plot_data['y_observed'], line=dict(color='blue') ), go.Scatter( name='Corrected Simulated Data', x=plot_data['x_simulated'], y=plot_data['y_corrected'], line=dict(color='#00cc96') ), ] scatters += rperiod_scatters layout = go.Layout( title=_build_title("Historical Simulation Comparison", titles), yaxis={'title': 'Discharge (m<sup>3</sup>/s)'}, xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate], 'hoverformat': '%b %d %Y', 'tickformat': '%Y'}, ) figure = go.Figure(data=scatters, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose json, plotly, plotly_scatters, or plotly_html')
def historic_simulation(hist: pd.DataFrame, rperiods: pd.DataFrame = None, titles: dict = False, outformat: str = 'plotly') -> go.Figure: """ Makes the streamflow ensemble data and metadata into a plotly plot Args: hist: the csv response from historic_simulation rperiods: the csv response from return_periods outformat: either 'json', 'plotly', or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['json', 'plotly_scatters', 'plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick json, plotly, plotly_scatters, or plotly_html') dates = hist.index.tolist() startdate = dates[0] enddate = dates[-1] plot_data = { 'x_datetime': dates, 'y_flow': hist.values.flatten(), 'y_max': max(hist.values), } if rperiods is not None: plot_data.update(rperiods.to_dict(orient='index').items()) rperiod_scatters = _rperiod_scatters(startdate, enddate, rperiods, plot_data['y_max'], plot_data['y_max']) else: rperiod_scatters = [] if outformat == 'json': return plot_data scatter_plots = [go.Scatter( name='Historic Simulation', x=plot_data['x_datetime'], y=plot_data['y_flow']) ] scatter_plots += rperiod_scatters if outformat == 'plotly_scatters': return scatter_plots layout = go.Layout( title=_build_title('Historic Streamflow Simulation', titles), yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']}, xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate], 'hoverformat': '%b %d %Y', 'tickformat': '%Y'}, ) figure = go.Figure(scatter_plots, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) raise ValueError('Invalid outformat chosen. Choose json, plotly, plotly_scatters, or plotly_html')
def forecast_records(recs: pd.DataFrame, rperiods: pd.DataFrame = None, titles: dict = False, outformat: str = 'plotly') -> go.Figure: """ Makes the streamflow saved forecast data and metadata into a plotly plot Args: recs: the csv response from forecast_records rperiods: the csv response from return_periods outformat: either 'json', 'plotly', or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['json', 'plotly_scatters', 'plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick json, plotly, plotly_scatters, or plotly_html') # Start processing the inputs dates = recs.index.tolist() startdate = dates[0] enddate = dates[-1] plot_data = { 'x_records': dates, 'recorded_flows': recs.dropna(axis=0).values.flatten(), 'y_max': max(recs.values), } if rperiods is not None: plot_data.update(rperiods.to_dict(orient='index').items()) rperiod_scatters = _rperiod_scatters(startdate, enddate, rperiods, plot_data['y_max'], plot_data['y_max']) else: rperiod_scatters = [] if outformat == 'json': return plot_data scatter_plots = [go.Scatter( name='1st day forecasts', x=plot_data['x_records'], y=plot_data['recorded_flows'], line=dict(color='gold'), )] + rperiod_scatters if outformat == 'plotly_scatters': return scatter_plots layout = go.Layout( title=_build_title('Forecasted Streamflow Record', titles), yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']}, xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate]}, ) figure = go.Figure(scatter_plots, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) return
def forecast_ensembles(ensem: pd.DataFrame, rperiods: pd.DataFrame = None, titles: dict = False, outformat: str = 'plotly') -> go.Figure: """ Makes the streamflow ensemble data and metadata into a plotly plot Args: ensem: the csv response from forecast_ensembles rperiods: the csv response from return_periods outformat: either 'json', 'plotly', or 'plotly_html' (default plotly) titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['json', 'plotly_scatters', 'plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick json, plotly, plotly_scatters, or plotly_html') # variables to determine the maximum flow and hold all the scatter plot lines max_flows = [] scatter_plots = [] # determine the threshold values for return periods and the start/end dates so we can plot them dates = ensem.index.tolist() startdate = dates[0] enddate = dates[-1] # process the series' components and store them in a dictionary plot_data = { 'x_1-51': ensem['ensemble_01_m^3/s'].dropna(axis=0).index.tolist(), 'x_52': ensem['ensemble_52_m^3/s'].dropna(axis=0).index.tolist(), } # add a dictionary entry for each of the ensemble members. the key for each series is the integer ensemble number for ensemble in ensem.columns: plot_data[ensemble] = ensem[ensemble].dropna(axis=0).tolist() max_flows.append(max(plot_data[ensemble])) plot_data['y_max'] = max(max_flows) if rperiods is not None: plot_data.update(rperiods.to_dict(orient='index').items()) rperiod_scatters = _rperiod_scatters(startdate, enddate, rperiods, plot_data['y_max']) else: rperiod_scatters = [] if outformat == 'json': return plot_data # create the high resolution line (ensemble 52) scatter_plots.append(go.Scatter( name='High Resolution Forecast', x=plot_data['x_52'], y=plot_data['ensemble_52_m^3/s'], line=dict(color='black') )) # create a line for the rest of the ensembles (1-51) for i in range(1, 52): scatter_plots.append(go.Scatter( name='Ensemble ' + str(i), x=plot_data['x_1-51'], y=plot_data[f'ensemble_{i:02}_m^3/s'], )) scatter_plots += rperiod_scatters if outformat == 'plotly_scatters': return scatter_plots # define a layout for the plot layout = go.Layout( title=_build_title('Ensemble Predicted Streamflow', titles), yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']}, xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate], 'hoverformat': '%b %d %Y', 'tickformat': '%b %d %Y'}, ) figure = go.Figure(scatter_plots, layout=layout) if outformat == 'plotly': return figure if outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False ) return
def hydroviewer(recs: pd.DataFrame, stats: pd.DataFrame, ensem: pd.DataFrame, rperiods: pd.DataFrame = None, record_days: int = 7, outformat: str = 'plotly', titles: dict = False) -> go.Figure: """ Creates the standard plot for a hydroviewer Args: recs: the response from forecast_records stats: the response from forecast_stats ensem: the response from forecast_ensembles rperiods: (optional) the response from return_periods outformat: (optional) either 'plotly' or 'plotly_html' (default plotly) record_days: (optional) number of days of forecast records to show before the start of the forecast titles: (dict) Extra info to show on the title of the plot. For example: {'Reach ID': 1234567, 'Drainage Area': '1000km^2'} Return: plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method """ if outformat not in ['plotly', 'plotly_html']: raise ValueError('invalid outformat specified. pick plotly or plotly_html') # determine the bounds of the plot on the x and y axis stats_dates = stats.index.tolist() # limit the forecast records to 7 days before the start of the forecast recs = recs[recs.index >= pd.to_datetime(stats_dates[0] - datetime.timedelta(days=record_days))] records_dates = recs.index.tolist() if len(records_dates) == 0: startdate = stats_dates[0] enddate = stats_dates[-1] else: startdate = min(records_dates[0], stats_dates[0]) enddate = max(records_dates[-1], stats_dates[-1]) max_flow = max(recs['streamflow_m^3/s'].max(), stats['flow_max_m^3/s'].max()) # start building the plotly graph object figure = forecast_records(recs, outformat='plotly') for new_scatter in forecast_stats(stats, outformat='plotly_scatters'): figure.add_trace(new_scatter) # do the ensembles separately so we can group then and make only 1 legend entry ensemble_data = forecast_ensembles(ensem, outformat='json') figure.add_trace(go.Scatter( x=ensemble_data['x_1-51'], y=ensemble_data['ensemble_01_m^3/s'], visible='legendonly', legendgroup='ensembles', name='Forecast Ensembles', )) for i in range(2, 52): figure.add_trace(go.Scatter( x=ensemble_data['x_1-51'], y=ensemble_data[f'ensemble_{i:02}_m^3/s'], visible='legendonly', legendgroup='ensembles', name=f'Ensemble {i}', showlegend=False, )) if rperiods is not None: max_visible = max(stats['flow_75%_m^3/s'].max(), stats['flow_avg_m^3/s'].max(), stats['high_res_m^3/s'].max(), recs['streamflow_m^3/s'].max()) for rp in _rperiod_scatters(startdate, enddate, rperiods, max_flow, max_visible): figure.add_trace(rp) figure.update_layout( title=_build_title('Forecasted Streamflow', titles), yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']}, xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate]}, ) if outformat == 'plotly': return figure else: # outformat == 'plotly_html': return offline_plot( figure, config={'autosizable': True, 'responsive': True}, output_type='div', include_plotlyjs=False )