Skip to content

Commit

Permalink
Time zone index (#39)
Browse files Browse the repository at this point in the history
* add time zone information to data retrievals

* optional decade average bars on annual averages plot
  • Loading branch information
rileyhales authored Jul 19, 2024
1 parent 52c8ea9 commit 97eb285
Show file tree
Hide file tree
Showing 7 changed files with 86 additions and 33 deletions.
2 changes: 1 addition & 1 deletion geoglows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@
'bias', 'plots', 'data', 'analyze', 'streams', 'tables', 'streamflow',
'get_metadata_table_path', 'set_metadata_table_path',
]
__version__ = '1.6.3'
__version__ = '1.7.0'
__author__ = 'Riley Hales'
__license__ = 'BSD 3-Clause Clear License'
6 changes: 5 additions & 1 deletion geoglows/_download_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ def from_aws(*args, **kwargs):
ds.attrs = attrs
return ds
df = ds.to_dataframe().round(2).reset_index()
df['time'] = pd.to_datetime(df['time'], utc=True)

# rename columns to match the REST API
if isinstance(river_id, int) or isinstance(river_id, np.int64):
Expand Down Expand Up @@ -159,6 +160,7 @@ def from_rest(*args, **kwargs):
if 'datetime' in df.columns:
df['datetime'] = pd.to_datetime(df['datetime'])
df = df.set_index('datetime')
df.index = df.index.tz_localize('UTC')
return df
elif return_format == 'json':
return response.json()
Expand Down Expand Up @@ -204,13 +206,15 @@ def main(*args, **kwargs):
if return_format == 'xarray':
return ds
if product_name == 'retrospective':
return (
df = (
ds
.to_dataframe()
.reset_index()
.set_index('time')
.pivot(columns='rivid', values='Qout')
)
df.index = df.index.tz_localize('UTC')
return df
if product_name == 'return-periods':
rp_methods = {
'gumbel1': 'gumbel1_return_period',
Expand Down
2 changes: 2 additions & 0 deletions geoglows/_plots/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
corrected_month_average,
corrected_day_average,
corrected_scatterplots,

plotly_figure_to_html_plot as plotly_figure_to_html,
)

__all__ = [
Expand Down
13 changes: 13 additions & 0 deletions geoglows/_plots/format_tools.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import datetime

import pytz
from plotly.offline import plot as offline_plot


Expand Down Expand Up @@ -26,3 +29,13 @@ def plotly_figure_to_html_plot(figure, include_plotlyjs: bool = False, ) -> str:
output_type='div',
include_plotlyjs=include_plotlyjs
)


def timezone_label(timezone: str = None):
timezone = str(timezone) if timezone is not None else 'UTC'
# get the number of hours the timezone is offset from UTC
now = datetime.datetime.now(pytz.timezone(timezone))
utc_offset = now.utcoffset().total_seconds() / 3600
# convert float number of hours to HH:MM format
utc_offset = f'{int(utc_offset):+03d}:{int((utc_offset % 1) * 60):02d}'
return f'Datetime ({timezone} {utc_offset})'
24 changes: 16 additions & 8 deletions geoglows/_plots/plotly_forecasts.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pandas as pd
import plotly.graph_objects as go

from .format_tools import build_title
from .format_tools import build_title, timezone_label
from .plotly_helpers import _rperiod_scatters

__all__ = [
Expand Down Expand Up @@ -35,7 +35,7 @@ def forecast(df: pd.DataFrame, *,
),
go.Scatter(
name='Uncertainty Bounds',
x=np.concatenate([df.index.values, df.index.values[::-1]]),
x=np.concatenate([df.index, df.index[::-1]]),
y=np.concatenate([df['flow_uncertainty_upper'], df['flow_uncertainty_lower'][::-1]]),
legendgroup='uncertainty',
showlegend=True,
Expand Down Expand Up @@ -67,7 +67,11 @@ def forecast(df: pd.DataFrame, *,
layout = go.Layout(
title=build_title('Forecasted Streamflow', plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']},
xaxis={'title': 'Date (UTC +0:00)', 'range': [df.index[0], df.index[-1]]},
xaxis={
'title': timezone_label(df.index.tz),
'range': [df.index[0], df.index[-1]],
'hoverformat': '%d %b %Y %X',
},
)

return go.Figure(scatter_traces, layout=layout)
Expand Down Expand Up @@ -179,9 +183,9 @@ def forecast_stats(df: pd.DataFrame, *,
'range': [0, 'auto']
},
xaxis={
'title': 'Date (UTC +0:00)',
'title': timezone_label(df.index.tz),
'range': [startdate, enddate],
'hoverformat': '%b %d %Y',
'hoverformat': '%d %b %Y %X',
'tickformat': '%b %d %Y'
},
)
Expand Down Expand Up @@ -250,9 +254,9 @@ def forecast_ensembles(df: pd.DataFrame, *, rp_df: pd.DataFrame = None, plot_tit
title=build_title('Ensemble Predicted Streamflow', plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']},
xaxis={
'title': 'Date (UTC +0:00)',
'title': timezone_label(df.index.tz),
'range': [startdate, enddate],
'hoverformat': '%b %d %Y',
'hoverformat': '%d %b %Y %X',
'tickformat': '%b %d %Y'
},
)
Expand Down Expand Up @@ -297,6 +301,10 @@ def forecast_records(df: pd.DataFrame, *, rp_df: pd.DataFrame = None, plot_title
layout = go.Layout(
title=build_title('Previous Forecasted Streamflow', plot_titles=plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']},
xaxis={'title': 'Date (UTC +0:00)', 'range': [startdate, enddate]},
xaxis={
'title': timezone_label(df.index.tz),
'range': [startdate, enddate],
'hoverformat': '%d %b %Y %X',
},
)
return go.Figure(scatter_plots, layout=layout)
66 changes: 45 additions & 21 deletions geoglows/_plots/plotly_retrospective.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import numpy as np
import pandas as pd
import plotly.graph_objs as go
import scipy.stats

from .format_tools import build_title
from .format_tools import build_title, timezone_label
from .plotly_helpers import _rperiod_scatters

__all__ = [
Expand All @@ -15,29 +16,27 @@
]


def retrospective(retro: pd.DataFrame, *,
rp_df: pd.DataFrame = None, plot_titles: dict = None, ) -> go.Figure:
def retrospective(df: pd.DataFrame, *, rp_df: pd.DataFrame = None, plot_titles: dict = None, ) -> go.Figure:
"""
Makes the streamflow ensemble data and metadata into a plotly plot
Args:
retro: the csv response from historic_simulation
df: the csv response from historic_simulation
rp_df: the csv response from return_periods
plot_type: either 'json', 'plotly', or 'html' (default plotly)
plot_titles: (dict) Extra info to show on the title of the plot. For example:
{'River ID': 1234567, 'Drainage Area': '1000km^2'}
Return:
plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method
"""
dates = retro.index.tolist()
dates = df.index.tolist()
startdate = dates[0]
enddate = dates[-1]

plot_data = {
'x_datetime': dates,
'y_flow': retro.values.flatten(),
'y_max': retro.values.max(),
'y_flow': df.values.flatten(),
'y_max': df.values.max(),
}
if rp_df is not None:
plot_data.update(rp_df.to_dict(orient='index').items())
Expand All @@ -56,9 +55,9 @@ def retrospective(retro: pd.DataFrame, *,
title=build_title('Retrospective Streamflow Simulation', plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']},
xaxis={
'title': 'Date (UTC +0:00)',
'title': timezone_label(df.index.tz),
'range': [startdate, enddate],
'hoverformat': '%b %d %Y',
'hoverformat': '%d %b %Y',
'tickformat': '%Y'
},
)
Expand Down Expand Up @@ -94,18 +93,19 @@ def daily_averages(dayavg: pd.DataFrame, plot_titles: list = None, plot_type: st
layout = go.Layout(
title=build_title('Daily Average Streamflow (Simulated)', plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']},
xaxis={'title': 'Date (UTC +0:00)', 'hoverformat': '%b %d', 'tickformat': '%b'},
xaxis={'title': 'Date', 'hoverformat': '%b %d', 'tickformat': '%b'},
)
return go.Figure(scatter_plots, layout=layout)


def monthly_averages(monavg: pd.DataFrame, titles: dict = None, plot_titles: list = None, plot_type: str = 'plotly') -> go.Figure:
def monthly_averages(monavg: pd.DataFrame, plot_titles: list = None,
plot_type: 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:
plot_titles: (dict) Extra info to show on the title of the plot. For example:
{'River ID': 1234567, 'Drainage Area': '1000km^2'}
plot_type: either 'plotly', or 'html' (default plotly)
Expand Down Expand Up @@ -133,14 +133,15 @@ def monthly_averages(monavg: pd.DataFrame, titles: dict = None, plot_titles: lis
return go.Figure(scatter_plots, layout=layout)


def annual_averages(df: pd.DataFrame, *, plot_titles: list = None, ) -> go.Figure:
def annual_averages(df: pd.DataFrame, *, plot_titles: list = None, decade_averages: bool = False) -> go.Figure:
"""
Makes the annual_averages data and metadata into a plotly plot
Args:
df: the csv response from annual_averages
plot_titles: (dict) Extra info to show on the title of the plot. For example:
{'River ID': 1234567, 'Drainage Area': '1000km^2'}
decade_averages: (bool) if True, will plot the average flow for each decade
Return:
plotly.GraphObject: plotly object, especially for use with python notebooks and the .show() method
Expand All @@ -153,6 +154,29 @@ def annual_averages(df: pd.DataFrame, *, plot_titles: list = None, ) -> go.Figur
line=dict(color='blue')
),
]

if decade_averages:
# get a list of decades covered by the data in the index
first_year = str(int(df.index[0]))[:-1] + '0'
last_year = str(int(df.index[-1]))[:-1] + '9'
first_year = int(first_year)
last_year = int(last_year)
decades = [decade for decade in range(int(first_year), int(last_year) + 1, 10)]
for idx, decade in enumerate(decades):
decade_values = df[np.logical_and(df.index.astype(int) >= decade, df.index.astype(int) < decade + 10)]
mean_flow = decade_values.values.flatten().mean()
scatter_plots.append(
go.Scatter(
name=f'{decade}s: {mean_flow:.2f} m<sup>3</sup>/s',
x=[decade_values.index[0], decade_values.index[-1]],
y=mean_flow * np.ones(2),
line=dict(color='red'),
hoverinfo='name',
legendgroup='decade_averages',
legendgrouptitle=dict(text='Decade Averages')
)
)

layout = go.Layout(
title=build_title('Annual Average Streamflow (Simulated)', plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)'},
Expand All @@ -161,12 +185,12 @@ def annual_averages(df: pd.DataFrame, *, plot_titles: list = None, ) -> go.Figur
return go.Figure(scatter_plots, layout=layout)


def flow_duration_curve(hist: pd.DataFrame, plot_titles: dict = None, plot_type: str = 'plotly') -> go.Figure:
def flow_duration_curve(df: pd.DataFrame, plot_titles: dict = None, plot_type: str = 'plotly') -> go.Figure:
"""
Makes the streamflow ensemble data and metadata into a plotly plot
Args:
hist: the csv response from historic_simulation
df: the dataframe response from data.retrospective
plot_titles: (dict) Extra info to show on the title of the plot. For example:
{'River ID': 1234567, 'Drainage Area': '1000km^2'}
plot_type: either 'json', 'plotly', or 'html' (default plotly)
Expand All @@ -178,7 +202,7 @@ def flow_duration_curve(hist: pd.DataFrame, plot_titles: dict = None, plot_type:
raise ValueError('invalid plot_type specified. pick json, plotly, plotly_scatters, or html')

# process the hist dataframe to create the flow duration curve
sorted_hist = hist.values.flatten()
sorted_hist = df.values.flatten()
sorted_hist.sort()

# ranks data from smallest to largest
Expand Down Expand Up @@ -212,12 +236,12 @@ def flow_duration_curve(hist: pd.DataFrame, plot_titles: dict = None, plot_type:
return go.Figure(scatter_plots, layout=layout)


def daily_stats(hist: pd.DataFrame, *, plot_titles: dict = None, plot_type: str = 'plotly') -> go.Figure:
def daily_stats(df: pd.DataFrame, *, plot_titles: dict = None, plot_type: str = 'plotly') -> go.Figure:
"""
Plots a graph with statistics for each day of year
Args:
hist: dataframe of values to plot
df: dataframe of values to plot
plot_titles: (dict) Extra info to show on the title of the plot. For example:
{'River ID': 1234567, 'Drainage Area': '1000km^2'}
plot_type: either 'plotly' (python object, default), 'plotly_scatters', or 'html'
Expand All @@ -226,7 +250,7 @@ def daily_stats(hist: pd.DataFrame, *, plot_titles: dict = None, plot_type: str
plot of the graph of the low flows
"""

stats_df = daily_stats(hist)
stats_df = daily_stats(df)

data = [
go.Scatter(
Expand All @@ -241,7 +265,7 @@ def daily_stats(hist: pd.DataFrame, *, plot_titles: dict = None, plot_type: str
layout = go.Layout(
title=build_title('Daily Average Streamflow (Simulated)', plot_titles),
yaxis={'title': 'Streamflow (m<sup>3</sup>/s)', 'range': [0, 'auto']},
xaxis={'title': 'Date (UTC +0:00)', 'hoverformat': '%b %d', 'tickformat': '%b'},
xaxis={'title': timezone_label(df.index.tz), 'hoverformat': '%b %d', 'tickformat': '%b'},
)
return go.Figure(data=data, layout=layout)

Expand Down
6 changes: 4 additions & 2 deletions geoglows/_plots/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,20 +200,22 @@ def monthly_averages(df: pd.DataFrame, *,

def annual_averages(df: pd.DataFrame, *,
plot_type: str = 'plotly',
plot_titles: list = None, ) -> go.Figure:
plot_titles: list = None,
decade_averages: bool = False, ) -> go.Figure:
"""
Makes a plotly figure of the annual average flows
Args:
df: a dataframe of the annual average flows
plot_type: either plotly or html
plot_titles: additional key-value pairs to display in the title of the figure
decade_averages: if True, the figure will include the average flows for each decade
Returns:
go.Figure
"""
if plot_type in ('plotly', 'html'):
figure = plotly_annual_averages(df, plot_titles=plot_titles)
figure = plotly_annual_averages(df, plot_titles=plot_titles, decade_averages=decade_averages)
if plot_type == 'html':
return plotly_figure_to_html_plot(figure)
return figure
Expand Down

0 comments on commit 97eb285

Please sign in to comment.