def make_plot(huc12, scenario): """Make the map""" import seaborn as sns os.chdir("/i/%s/slp/%s/%s" % (scenario, huc12[:8], huc12[8:])) res = [] for fn in glob.glob("*.slp"): slp = read_slp(fn) bulk = (slp[-1]['y'][-1]) / slp[-1]['x'][-1] length = slp[-1]['x'][-1] if bulk < -1: print("Greater than 100%% slope, %s %s" % (fn, bulk)) continue res.append([(0 - bulk) * 100., length]) data = np.array(res) g = sns.jointplot( data[:, 1], data[:, 0], s=40, stat_func=None, zorder=1, color='tan' ).plot_joint(sns.kdeplot, n_levels=6) g.ax_joint.set_xlabel("Slope Length [m]") g.ax_joint.set_ylabel("Bulk Slope [%]") g.fig.subplots_adjust(top=.8, bottom=0.2, left=0.15) g.ax_joint.grid() g.ax_marg_x.set_title(( "HUC12 %s DEP Hillslope\n" "Kernel Density Estimate (KDE) Overlain") % (huc12, ), fontsize=10) ram = BytesIO() plt.gcf().set_size_inches(3.6, 2.4) plt.savefig(ram, format='png', dpi=100) ram.seek(0) return ram.read()
def make_plot(huc12, scenario): """Make the map""" import seaborn as sns os.chdir("/i/%s/slp/%s/%s" % (scenario, huc12[:8], huc12[8:])) res = [] for fn in glob.glob("*.slp"): slp = read_slp(fn) bulk = (slp[-1]['y'][-1]) / slp[-1]['x'][-1] length = slp[-1]['x'][-1] if bulk < -1: print("Greater than 100%% slope, %s %s" % (fn, bulk)) continue res.append([(0 - bulk) * 100., length]) data = np.array(res) g = sns.jointplot(data[:, 1], data[:, 0], s=40, stat_func=None, zorder=1, color='tan').plot_joint(sns.kdeplot, n_levels=6) g.ax_joint.set_xlabel("Slope Length [m]") g.ax_joint.set_ylabel("Bulk Slope [%]") g.fig.subplots_adjust(top=.8, bottom=0.2, left=0.15) g.ax_joint.grid() g.ax_marg_x.set_title( ("HUC12 %s DEP Hillslope\n" "Kernel Density Estimate (KDE) Overlain") % (huc12, ), fontsize=10) ram = BytesIO() plt.gcf().set_size_inches(3.6, 2.4) plt.savefig(ram, format='png', dpi=100) ram.seek(0) return ram.read()
def plotter(fdict): """ Go """ ctx = get_autoplot_context(fdict, get_description()) get_data(ctx) df = ctx["df"] fig, ax = plt.subplots(1, 1) colname = f"{ctx['var']}_{ctx['year']}" x = df.index.values ax.plot(x, df[colname].values, label=str(ctx["year"]), zorder=4, color="r") ax.fill_between( x, df[ctx["var"], "min"].values, df[ctx["var"], "max"].values, zorder=1, color="lightblue", ) ax.fill_between( x, df[ctx["var"], "25%"].values, df[ctx["var"], "75%"].values, zorder=2, color="tan", label="25-75 %tile", ) ax.plot(x, df[ctx["var"], "mean"].values, color="k", zorder=3, label="Avg") ax.legend() ax.set_ylabel(PDICT3[ctx["var"]]) plt.gcf().text(0.5, 0.9, ctx["title"], ha="center", va="bottom") ax.grid(True) return fig, df
def main(argv): """Go Main Go.""" huc12 = argv[1] year = int(argv[2]) prop_cycle = plt.rcParams["axes.prop_cycle"] colors = prop_cycle.by_key()["color"] pgconn = get_dbconn("idep") df = read_sql( """ SELECT scenario, huc_12, avg_delivery, valid from results_by_huc12 WHERE scenario >= 59 and scenario < 70 and extract(year from valid) = %s and huc_12 = %s ORDER by valid ASC """, pgconn, params=(year, huc12), ) df["valid"] = pd.to_datetime(df["valid"]) ax = plt.axes([0.2, 0.1, 0.75, 0.75]) baseline = df[df["scenario"] == 59].copy().set_index("valid") yticklabels = [] col = "avg_delivery" for scenario in range(60, 70): color = colors[scenario - 60] date = datetime.date(2000, 4, 15) + datetime.timedelta(days=(scenario - 60) * 5) scendata = df[df["scenario"] == scenario].copy().set_index("valid") delta = scendata[col] - baseline[col] delta = delta[delta != 0] total = ((scendata[col].sum() - baseline[col].sum()) / baseline[col].sum()) * 100.0 yticklabels.append("%s %4.2f%%" % (date.strftime("%b %d"), total)) x = delta.index.to_pydatetime() # res = ax.scatter(x, delta.values + (scenario - 60)) for idx, val in enumerate(delta): ax.arrow( x[idx], scenario - 60, 0, val * 10.0, head_width=4, head_length=0.1, fc=color, ec=color, ) ax.axhline(scenario - 60, color=color) ax.set_xlim(datetime.date(year, 1, 1), datetime.date(year + 1, 1, 1)) ax.set_ylim(-0.5, 10) ax.xaxis.set_major_locator(mdates.DayLocator([1])) ax.xaxis.set_major_formatter(mdates.DateFormatter("%b")) ax.set_title("huc12: %s \n%s Daily Change in Delivery vs Apr 10 Planting" % (huc12, year)) ax.grid(axis="x") ax.set_yticks(range(10)) ax.set_yticklabels(yticklabels) plt.gcf().savefig("test.png")
def main(argv): """Go Main Go.""" huc12 = argv[1] fpath = argv[2] year = int(argv[3]) prop_cycle = plt.rcParams["axes.prop_cycle"] colors = prop_cycle.by_key()["color"] data = {} for scenario in range(59, 70): data[scenario] = read_env( "/i/%s/env/%s/%s/%s_%s.env" % (scenario, huc12[:8], huc12[8:], huc12, fpath)).set_index("date") print(data[scenario]["av_det"].sum()) ax = plt.axes([0.2, 0.1, 0.75, 0.75]) baseline = data[59][data[59].index.year == year] yticklabels = [] for scenario in range(60, 70): color = colors[scenario - 60] date = datetime.date(2000, 4, 15) + datetime.timedelta(days=(scenario - 60) * 5) scendata = data[scenario][data[scenario].index.year == year] delta = scendata["sed_del"] - baseline["sed_del"] delta = delta[delta != 0] total = ((scendata["sed_del"].sum() - baseline["sed_del"].sum()) / baseline["sed_del"].sum()) * 100.0 yticklabels.append("%s %4.2f%%" % (date.strftime("%b %d"), total)) x = delta.index.to_pydatetime() # res = ax.scatter(x, delta.values + (scenario - 60)) for idx, val in enumerate(delta): ax.arrow( x[idx], scenario - 60, 0, val, head_width=0.5, head_length=0.1, fc=color, ec=color, ) ax.axhline(scenario - 60, color=color) ax.set_xlim(datetime.date(year, 1, 1), datetime.date(year + 1, 1, 1)) ax.set_ylim(-0.5, 10) ax.xaxis.set_major_locator(mdates.DayLocator([1])) ax.xaxis.set_major_formatter(mdates.DateFormatter("%b")) ax.set_title( "huc12: %s fpath: %s\n%s Daily Change in Delivery vs Apr 10 Planting" % (huc12, fpath, year)) ax.grid(axis="x") ax.set_yticks(range(10)) ax.set_yticklabels(yticklabels) plt.gcf().savefig("test.png")
def plotter(fdict): """ Go """ ctx = get_context(fdict) fig = plt.figure(figsize=(6, 7.2), facecolor="w", edgecolor="w") rect = [0.08, 0.1, 0.8, 0.8] ax = WindroseAxes(fig, rect, facecolor="w") fig.add_axes(ax) ax.bar( ctx["df"]["drct"].values, ctx["df"]["smph"].values, normed=True, bins=[0, 2, 5, 7, 10, 15, 20], opening=0.8, edgecolor="white", nsector=18, ) handles = [] for p in ax.patches_list: color = p.get_facecolor() handles.append( Rectangle((0, 0), 0.1, 0.3, facecolor=color, edgecolor="black")) legend = fig.legend( handles, ("2-5", "5-7", "7-10", "10-15", "15-20", "20+"), loc=(0.01, 0.03), ncol=6, title="Wind Speed [%s]" % ("mph", ), mode=None, columnspacing=0.9, handletextpad=0.45, ) plt.setp(legend.get_texts(), fontsize=10) plt.gcf().text(0.5, 0.99, ctx["plottitle"], fontsize=16, ha="center", va="top") plt.gcf().text( 0.95, 0.12, "n=%s" % (len(ctx["df"].index), ), verticalalignment="bottom", ha="right", ) return fig, ctx["df"]
def main(argv): """Go Main Go.""" huc12 = argv[1] fpath = argv[2] year = int(argv[3]) prop_cycle = plt.rcParams["axes.prop_cycle"] colors = prop_cycle.by_key()["color"] data = {} for scenario in range(59, 70): df = read_crop("/i/%s/crop/%s/%s/%s_%s.crop" % (scenario, huc12[:8], huc12[8:], huc12, fpath)) data[scenario] = df[df["ofe"] == 1].set_index("date") ax1 = plt.axes([0.15, 0.5, 0.85, 0.35]) ax2 = plt.axes([0.15, 0.1, 0.85, 0.35]) baseline = data[59][data[59].index.year == year] for scenario in range(60, 70): color = colors[scenario - 60] date = datetime.date(2000, 4, 15) + datetime.timedelta(days=(scenario - 60) * 5) scendata = data[scenario][data[scenario]["year"] == year] delta = scendata["canopy_percent"] - baseline["canopy_percent"] x = delta.index.to_pydatetime() ax1.plot( x, scendata["canopy_percent"] * 100.0, label=date.strftime("%b %d"), color=color, ) ax2.plot(x, delta.values * 100.0, color=color) ax1.set_xlim(datetime.date(year, 4, 15), datetime.date(year, 7, 15)) ax2.set_xlim(datetime.date(year, 4, 15), datetime.date(year, 7, 15)) ax1.xaxis.set_major_locator(mdates.DayLocator([1])) ax1.xaxis.set_major_formatter(mdates.DateFormatter("%b")) ax2.xaxis.set_major_locator(mdates.DayLocator([1])) ax2.xaxis.set_major_formatter(mdates.DateFormatter("%b")) ax1.set_ylabel("Coverage [%]") ax2.set_ylabel("Absolute Differnece from Apr 10 [%]") ax2.set_ylim(-101, 0) ax1.set_title("huc12: %s fpath: %s\n%s Canopy Coverage by Planting Date" % (huc12, fpath, year)) ax1.grid() ax2.grid() ax1.legend(loc=2, ncol=2) plt.gcf().savefig("test.png")
def _do_month(month, axes, data, in_sts, in_ets, kwargs): """Place data on this axes""" axes.get_xaxis().set_visible(False) axes.get_yaxis().set_visible(False) pos = axes.get_position() ndcheight = (pos.y1 - pos.y0) ndcwidth = (pos.x1 - pos.x0) fitbox(plt.gcf(), month.strftime("%B %Y"), pos.x0, pos.x1, pos.y1, pos.y1 + 0.028, ha='center') axes.add_patch( Rectangle((0., 0.90), 1, 0.1, zorder=2, facecolor='tan', edgecolor='tan')) sts = datetime.date(month.year, month.month, 1) ets = (sts + datetime.timedelta(days=35)).replace(day=1) calendar.setfirstweekday(calendar.SUNDAY) weeks = len(calendar.monthcalendar(month.year, month.month)) now = sts row = 0 dy = 0.9 / float(weeks) dx = 1. / 7. for i, dow in enumerate(['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']): axes.text(1. / 7. * (i + 0.5), 0.94, dow, fontsize=fontscale(ndcwidth / 7. * 0.4), ha='center', va='center') while now < ets: # Is this Sunday? if now.weekday() == 6 and now != sts: row += 1 if now < in_sts or now > in_ets: now += datetime.timedelta(days=1) continue offx = (now.weekday() + 1) if now.weekday() != 6 else 0 axes.text(offx * dx + 0.01, 0.9 - row * dy - 0.01, str(now.day), fontsize=fontscale(ndcheight / 5. * 0.25), color='tan', va='top') _do_cell(axes, now, data, row, dx, dy, kwargs) now += datetime.timedelta(days=1)
def plotter(fdict): """ Go """ ctx = get_context(fdict) fig = plt.figure(figsize=(6, 7.2), facecolor='w', edgecolor='w') rect = [0.08, 0.1, 0.8, 0.8] ax = WindroseAxes(fig, rect, facecolor='w') fig.add_axes(ax) ax.bar(ctx['df']['drct'].values, ctx['df']['smph'].values, normed=True, bins=[0, 2, 5, 7, 10, 15, 20], opening=0.8, edgecolor='white', nsector=18) handles = [] for p in ax.patches_list: color = p.get_facecolor() handles.append( Rectangle((0, 0), 0.1, 0.3, facecolor=color, edgecolor='black')) legend = fig.legend(handles, ('2-5', '5-7', '7-10', '10-15', '15-20', '20+'), loc=(0.01, 0.03), ncol=6, title='Wind Speed [%s]' % ('mph', ), mode=None, columnspacing=0.9, handletextpad=0.45) plt.setp(legend.get_texts(), fontsize=10) plt.gcf().text(0.5, 0.99, ctx['plottitle'], fontsize=16, ha='center', va='top') plt.gcf().text(0.95, 0.12, "n=%s" % (len(ctx['df'].index), ), verticalalignment="bottom", ha='right') return fig, ctx['df']
def plotter(fdict): """ Go """ pgconn = get_dbconn("coop") ctx = get_autoplot_context(fdict, get_description()) station = ctx["station"] table = "alldata_%s" % (station[:2], ) df = read_sql( """ WITH data as ( SELECT sday, day, year, rank() OVER (PARTITION by sday ORDER by high DESC) as max_high_rank, rank() OVER (PARTITION by sday ORDER by high ASC) as min_high_rank, rank() OVER (PARTITION by sday ORDER by low DESC) as max_low_rank, rank() OVER (PARTITION by sday ORDER by low ASC) as min_low_rank from """ + table + """ WHERE station = %s and high is not null and low is not null) SELECT *, extract(doy from ('2000-'||substr(sday, 1, 2)||'-'||substr(sday, 3, 2))::date) as doy from data WHERE max_high_rank = 1 or min_high_rank = 1 or max_low_rank = 1 or min_low_rank = 1 ORDER by day ASC """, pgconn, params=(station, ), index_col=None, ) if df.empty: raise NoDataFound("No Data Found.") fig = plt.figure(figsize=(12, 6)) fig.text( 0.5, 0.95, ("[%s] %s Year of Daily Records, ties included") % (station, ctx["_nt"].sts[station]["name"]), ha="center", fontsize=16, ) ax = plt.axes([0.04, 0.55, 0.35, 0.35]) magic(ax, df, "max_high_rank", "Maximum High (warm)", ctx) ax = plt.axes([0.04, 0.1, 0.35, 0.35]) magic(ax, df, "min_high_rank", "Minimum High (cold)", ctx) ax = plt.axes([0.54, 0.55, 0.35, 0.35]) magic(ax, df, "max_low_rank", "Maximum Low (warm)", ctx) ax = plt.axes([0.54, 0.1, 0.35, 0.35]) magic(ax, df, "min_low_rank", "Minimum Low (cold)", ctx) return plt.gcf(), df
def _do_month(month, axes, data, in_sts, in_ets, kwargs): """Place data on this axes""" axes.get_xaxis().set_visible(False) axes.get_yaxis().set_visible(False) pos = axes.get_position() ndcheight = (pos.y1 - pos.y0) ndcwidth = (pos.x1 - pos.x0) fitbox( plt.gcf(), month.strftime("%B %Y"), pos.x0, pos.x1, pos.y1, pos.y1 + 0.028, ha='center' ) axes.add_patch(Rectangle((0., 0.90), 1, 0.1, zorder=2, facecolor='tan', edgecolor='tan')) sts = datetime.date(month.year, month.month, 1) ets = (sts + datetime.timedelta(days=35)).replace(day=1) calendar.setfirstweekday(calendar.SUNDAY) weeks = len(calendar.monthcalendar(month.year, month.month)) now = sts row = 0 dy = 0.9 / float(weeks) dx = 1. / 7. for i, dow in enumerate(['SUN', 'MON', 'TUE', 'WED', 'THU', 'FRI', 'SAT']): axes.text( 1. / 7. * (i + 0.5), 0.94, dow, fontsize=fontscale(ndcwidth / 7. * 0.4), ha='center', va='center') while now < ets: # Is this Sunday? if now.weekday() == 6 and now != sts: row += 1 if now < in_sts or now > in_ets: now += datetime.timedelta(days=1) continue offx = (now.weekday() + 1) if now.weekday() != 6 else 0 axes.text( offx * dx + 0.01, 0.9 - row * dy - 0.01, str(now.day), fontsize=fontscale(ndcheight / 5. * 0.25), color='tan', va='top' ) _do_cell(axes, now, data, row, dx, dy, kwargs) now += datetime.timedelta(days=1)
def plotter(fdict): """ Go """ pgconn = get_dbconn('coop') ctx = get_autoplot_context(fdict, get_description()) station = ctx['station'] table = "alldata_%s" % (station[:2], ) nt = NetworkTable("%sCLIMATE" % (station[:2], )) df = read_sql(""" WITH data as ( SELECT sday, day, year, rank() OVER (PARTITION by sday ORDER by high DESC) as max_high_rank, rank() OVER (PARTITION by sday ORDER by high ASC) as min_high_rank, rank() OVER (PARTITION by sday ORDER by low DESC) as max_low_rank, rank() OVER (PARTITION by sday ORDER by low ASC) as min_low_rank from """ + table + """ WHERE station = %s and high is not null and low is not null) SELECT *, extract(doy from ('2000-'||substr(sday, 1, 2)||'-'||substr(sday, 3, 2))::date) as doy from data WHERE max_high_rank = 1 or min_high_rank = 1 or max_low_rank = 1 or min_low_rank = 1 ORDER by day ASC """, pgconn, params=(station, ), index_col=None) fig = plt.figure(figsize=(12, 6)) fig.text(0.5, 0.95, ("[%s] %s Year of Daily Records, ties included") % (station, nt.sts[station]['name']), ha='center', fontsize=16) ax = plt.axes([0.04, 0.55, 0.35, 0.35]) magic(ax, df, 'max_high_rank', 'Maximum High (warm)', ctx) ax = plt.axes([0.04, 0.1, 0.35, 0.35]) magic(ax, df, 'min_high_rank', 'Minimum High (cold)', ctx) ax = plt.axes([0.54, 0.55, 0.35, 0.35]) magic(ax, df, 'max_low_rank', 'Maximum Low (warm)', ctx) ax = plt.axes([0.54, 0.1, 0.35, 0.35]) magic(ax, df, 'min_low_rank', 'Minimum Low (cold)', ctx) return plt.gcf(), df
def _do_cell(axes, now, data, row, dx, dy, kwargs): """Do what work is necessary within the cell""" val = data.get(now, dict()).get("val") cellcolor = ("None" if kwargs.get("norm") is None or val is None else kwargs["cmap"](kwargs["norm"]([val]))[0]) offx = (now.weekday() + 1) if now.weekday() != 6 else 0 cellcolor = data.get(now, dict()).get("cellcolor", cellcolor) rect = Rectangle( (offx * dx, 0.9 - (row + 1) * dy), dx, dy, zorder=(2 if val is None else 3), facecolor=cellcolor, edgecolor="tan" if val is None else "k", ) axes.add_patch(rect) if val is None: return color = "k" if not isinstance(cellcolor, str): # this is a string comp here color = ("k" if (cellcolor[0] * 256 * 0.299 + cellcolor[1] * 256 * 0.587 + cellcolor[2] * 256 * 0.114) > 186 else "white") color = data[now].get("color", color) # We need to translate the axes NDC coordinates back to the figure coords bbox = axes.get_position() sdx = (bbox.x1 - bbox.x0) * dx sdy = (bbox.y1 - bbox.y0) * dy x0 = bbox.x0 + offx * sdx ytop = bbox.y0 + (bbox.y1 - bbox.y0) * 0.9 y0 = ytop - (row + 1) * sdy fitbox( plt.gcf(), val, x0, x0 + sdx, y0, y0 + sdy * 0.55, ha="center", va="center", color=color, )
def plotter(fdict): """ Go """ ctx = get_autoplot_context(fdict, get_description()) station = ctx['station'] varname = ctx['var'] network = 'RAOB' ts = ctx['date'] hour = int(ctx['hour']) ts = datetime.datetime(ts.year, ts.month, ts.day, hour) ts = ts.replace(tzinfo=pytz.utc) which = ctx['which'] vlimit = '' if which == 'month': vlimit = (" and extract(month from f.valid) = %s ") % (ts.month, ) nt = NetworkTable(network) name = nt.sts[station]['name'] stations = [ station, ] if station.startswith("_"): name = nt.sts[station]['name'].split("--")[0] stations = nt.sts[station]['name'].split("--")[1].strip().split(" ") pgconn = get_dbconn('postgis') df = read_sql(""" with data as ( select f.valid, p.pressure, count(*) OVER (PARTITION by p.pressure), min(valid) OVER () as min_valid, max(valid) OVER () as max_valid, p.tmpc, rank() OVER (PARTITION by p.pressure ORDER by p.tmpc ASC) as tmpc_rank, min(p.tmpc) OVER (PARTITION by p.pressure) as tmpc_min, max(p.tmpc) OVER (PARTITION by p.pressure) as tmpc_max, p.dwpc, rank() OVER (PARTITION by p.pressure ORDER by p.dwpc ASC) as dwpc_rank, min(p.dwpc) OVER (PARTITION by p.pressure) as dwpc_min, max(p.dwpc) OVER (PARTITION by p.pressure) as dwpc_max, p.height as hght, rank() OVER ( PARTITION by p.pressure ORDER by p.height ASC) as hght_rank, min(p.height) OVER (PARTITION by p.pressure) as hght_min, max(p.height) OVER (PARTITION by p.pressure) as hght_max, p.smps, rank() OVER (PARTITION by p.pressure ORDER by p.smps ASC) as smps_rank, min(p.smps) OVER (PARTITION by p.pressure) as smps_min, max(p.smps) OVER (PARTITION by p.pressure) as smps_max from raob_flights f JOIN raob_profile p on (f.fid = p.fid) WHERE f.station in %s and extract(hour from f.valid at time zone 'UTC') = %s """ + vlimit + """ and p.pressure in (925, 850, 700, 500, 400, 300, 250, 200, 150, 100, 70, 50, 10)) select * from data where valid = %s ORDER by pressure DESC """, pgconn, params=(tuple(stations), hour, ts), index_col='pressure') if df.empty: raise ValueError(("Sounding for %s was not found!") % (ts.strftime("%Y-%m-%d %H:%M"), )) for key in PDICT3.keys(): df[key + '_percentile'] = df[key + '_rank'] / df['count'] * 100. # manual hackery to get 0 and 100th percentile df.loc[df[key] == df[key + '_max'], key + '_percentile'] = 100. df.loc[df[key] == df[key + '_min'], key + '_percentile'] = 0. ax = plt.axes([0.1, 0.12, 0.65, 0.75]) bars = ax.barh(range(len(df.index)), df[varname + '_percentile'], align='center') y2labels = [] fmt = '%.1f' if varname not in [ 'hght', ] else '%.0f' for i, mybar in enumerate(bars): ax.text(mybar.get_width() + 1, i, '%.1f' % (mybar.get_width(), ), va='center', bbox=dict(color='white')) y2labels.append((fmt + ' (' + fmt + ' ' + fmt + ')') % (df.iloc[i][varname], df.iloc[i][varname + "_min"], df.iloc[i][varname + "_max"])) ax.set_yticks(range(len(df.index))) ax.set_yticklabels(['%.0f' % (a, ) for a in df.index.values]) ax.set_ylim(-0.5, len(df.index) - 0.5) ax.set_xlabel("Percentile [100 = highest]") ax.set_ylabel("Mandatory Pressure Level (hPa)") plt.gcf().text( 0.5, 0.9, ("%s %s %s Sounding\n" "(%s-%s) Percentile Ranks (%s) for %s") % (station, name, ts.strftime("%Y/%m/%d %H UTC"), df.iloc[0]['min_valid'].year, df.iloc[0]['max_valid'].year, ("All Year" if which == 'none' else calendar.month_name[ts.month]), PDICT3[varname]), ha='center', va='bottom') ax.grid(True) ax.set_xticks([0, 5, 10, 25, 50, 75, 90, 95, 100]) ax.set_xlim(0, 110) ax.text(1.02, 1, 'Ob (Min Max)', transform=ax.transAxes) ax2 = ax.twinx() ax2.set_ylim(-0.5, len(df.index) - 0.5) ax2.set_yticks(range(len(df.index))) ax2.set_yticklabels(y2labels) return plt.gcf(), df
def plotter(fdict): """ Go """ pgconn = get_dbconn('coop') ctx = get_autoplot_context(fdict, get_description()) station = ctx['station'] network = ctx['network'] month1 = ctx['month1'] month2 = ctx['month2'] highlight = ctx['highlight'] varname = ctx['var'] p1 = ctx.get('p1') p2 = ctx.get('p2') days = ctx['days'] opt = ctx['opt'] table = "alldata_%s" % (station[:2], ) nt = NetworkTable(network) m1data, y1, y2 = get_data(pgconn, table, station, month1, p1, varname, days, opt) m2data, y3, y4 = get_data(pgconn, table, station, month2, p2, varname, days, opt) pc1 = np.percentile(m1data, range(0, 101, 1)) pc2 = np.percentile(m2data, range(0, 101, 1)) df = pd.DataFrame({ '%s_%s_%s_%s' % (MDICT[month1], varname, y1, y2): pd.Series(pc1), '%s_%s_%s_%s' % (MDICT[month2], varname, y3, y4): pd.Series(pc2), 'quantile': pd.Series(range(0, 101, 5)) }) s_slp, s_int, s_r, _, _ = stats.linregress(pc1, pc2) fig = plt.gcf() fig.set_size_inches(10.24, 7.68) ax = plt.axes([0.1, 0.11, 0.4, 0.76]) ax.scatter(pc1[::5], pc2[::5], s=40, marker='s', color='b', zorder=3) ax.plot(pc1, pc1 * s_slp + s_int, lw=3, color='r', zorder=2, label=r"Fit R$^2$=%.2f" % (s_r**2, )) ax.axvline(highlight, zorder=1, color='k') y = highlight * s_slp + s_int ax.axhline(y, zorder=1, color='k') ax.text(pc1[0], y, "%.0f $^\circ$F" % (y, ), va='center', bbox=dict(color='white')) ax.text(highlight, pc2[0], "%.0f $^\circ$F" % (highlight, ), ha='center', rotation=90, bbox=dict(color='white')) t2 = PDICT[varname] if days > 1: t2 = "%s %s over %s days" % (ODICT[opt], PDICT[varname], days) fig.suptitle(("[%s] %s\n%s (%s-%s) vs %s (%s-%s)\n%s") % (station, nt.sts[station]['name'], MDICT[month2], y1, y2, MDICT[month1], y3, y4, t2)) ax.set_xlabel("%s (%s-%s) %s $^\circ$F" % (MDICT[month1], y1, y2, PDICT[varname])) ax.set_ylabel("%s (%s-%s) %s $^\circ$F" % (MDICT[month2], y3, y4, PDICT[varname])) ax.text(0.95, 0.05, "Quantile - Quantile Plot", transform=ax.transAxes, ha='right') ax.grid(True) ax.legend(loc=2) # Second ax = plt.axes([0.55, 0.18, 0.27, 0.68]) ax.set_title("Distribution") v1 = ax.violinplot(m1data, positions=[ 0, ], showextrema=True, showmeans=True) b = v1['bodies'][0] m = np.mean(b.get_paths()[0].vertices[:, 0]) b.get_paths()[0].vertices[:, 0] = np.clip(b.get_paths()[0].vertices[:, 0], -np.inf, m) b.set_color('r') for l in ['cmins', 'cmeans', 'cmaxes']: v1[l].set_color('r') v1 = ax.violinplot(m2data, positions=[ 0, ], showextrema=True, showmeans=True) b = v1['bodies'][0] m = np.mean(b.get_paths()[0].vertices[:, 0]) b.get_paths()[0].vertices[:, 0] = np.clip(b.get_paths()[0].vertices[:, 0], m, np.inf) b.set_color('b') for l in ['cmins', 'cmeans', 'cmaxes']: v1[l].set_color('b') pr0 = plt.Rectangle((0, 0), 1, 1, fc="r") pr1 = plt.Rectangle((0, 0), 1, 1, fc="b") ax.legend( (pr0, pr1), ("%s (%s-%s), $\mu$=%.1f" % (MDICT[month1], y1, y2, np.mean(m1data)), "%s (%s-%s), $\mu$=%.1f" % (MDICT[month2], y3, y4, np.mean(m2data))), ncol=1, loc=(0.5, -0.15)) ax.set_ylabel("%s $^\circ$F" % (PDICT[varname], )) ax.grid() # Third monofont = FontProperties(family='monospace') y = 0.86 x = 0.83 col1 = "%s_%s_%s_%s" % (MDICT[month1], varname, y1, y2) col2 = "%s_%s_%s_%s" % (MDICT[month2], varname, y3, y4) fig.text(x, y + 0.04, 'Percentile Data Diff') for percentile in [ 100, 99, 98, 97, 96, 95, 92, 90, 75, 50, 25, 10, 8, 5, 4, 3, 2, 1 ]: row = df.loc[percentile] fig.text(x, y, "%3i" % (percentile, ), fontproperties=monofont) fig.text(x + 0.025, y, "%5.1f" % (row[col1], ), fontproperties=monofont, color='r') fig.text(x + 0.07, y, "%5.1f" % (row[col2], ), fontproperties=monofont, color='b') fig.text(x + 0.11, y, "%5.1f" % (row[col2] - row[col1], ), fontproperties=monofont) y -= 0.04 return fig, df
def plotter(fdict): """ Go """ pgconn = get_dbconn("coop") ctx = get_autoplot_context(fdict, get_description()) station = ctx["station"] month = ctx["month"] varname = ctx["var"] days = ctx["days"] table = "alldata_%s" % (station[:2], ) if month == "all": months = range(1, 13) elif month == "fall": months = [9, 10, 11] elif month == "winter": months = [12, 1, 2] elif month == "spring": months = [3, 4, 5] elif month == "summer": months = [6, 7, 8] elif month == "octmar": months = [10, 11, 12, 1, 2, 3] else: ts = datetime.datetime.strptime("2000-" + month + "-01", "%Y-%b-%d") # make sure it is length two for the trick below in SQL months = [ts.month, 999] sorder = "ASC" if varname in ["min_greatest_low"] else "DESC" df = read_sql( """WITH data as ( SELECT month, day, day - '%s days'::interval as start_date, count(*) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as count, sum(precip) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as total_precip, min(high) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as max_least_high, max(low) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as min_greatest_low from """ + table + """ WHERE station = %s) SELECT day as end_date, start_date, """ + varname + """ from data WHERE month in %s and extract(month from start_date) in %s and count = %s ORDER by """ + varname + """ """ + sorder + """ LIMIT 10 """, pgconn, params=( days - 1, days - 1, days - 1, days - 1, days - 1, station, tuple(months), tuple(months), days, ), index_col=None, ) if df.empty: raise NoDataFound("Error, no results returned!") ylabels = [] fmt = "%.2f" if varname in ["total_precip"] else "%.0f" for _, row in df.iterrows(): # no strftime support for old days, so we hack at it lbl = fmt % (row[varname], ) if days > 1: sts = row["end_date"] - datetime.timedelta(days=(days - 1)) if sts.month == row["end_date"].month: lbl += " -- %s %s-%s, %s" % ( calendar.month_abbr[sts.month], sts.day, row["end_date"].day, sts.year, ) else: lbl += " -- %s %s, %s to\n %s %s, %s" % ( calendar.month_abbr[sts.month], sts.day, sts.year, calendar.month_abbr[row["end_date"].month], row["end_date"].day, row["end_date"].year, ) else: lbl += " -- %s %s, %s" % ( calendar.month_abbr[row["end_date"].month], row["end_date"].day, row["end_date"].year, ) ylabels.append(lbl) ax = plt.axes([0.1, 0.1, 0.5, 0.8]) plt.gcf().set_size_inches(8, 6) ax.barh( range(10, 0, -1), df[varname], ec="green", fc="green", height=0.8, align="center", ) ax2 = ax.twinx() ax2.set_ylim(0.5, 10.5) ax.set_ylim(0.5, 10.5) ax2.set_yticks(range(1, 11)) ax.set_yticks(range(1, 11)) ax.set_yticklabels(["#%s" % (x, ) for x in range(1, 11)][::-1]) ax2.set_yticklabels(ylabels[::-1]) ax.grid(True, zorder=11) ax.set_xlabel(("Precipitation [inch]" if varname in ["total_precip"] else r"Temperature $^\circ$F")) ab = ctx["_nt"].sts[station]["archive_begin"] if ab is None: raise NoDataFound("Unknown station metadata.") ax.set_title( ("%s [%s] Top 10 Events\n" "%s [days=%s] (%s) " "(%s-%s)") % ( ctx["_nt"].sts[station]["name"], station, METRICS[varname], days, MDICT[month], ab.year, datetime.datetime.now().year, ), size=12, ) return plt.gcf(), df
def plotter(fdict): """ Go """ pgconn = get_dbconn("coop") ctx = get_autoplot_context(fdict, get_description()) station = ctx["station"] month1 = ctx["month1"] month2 = ctx["month2"] highlight = ctx["highlight"] varname = ctx["var"] p1 = ctx.get("p1") p2 = ctx.get("p2") days = ctx["days"] opt = ctx["opt"] table = "alldata_%s" % (station[:2], ) m1data, y1, y2 = get_data(pgconn, table, station, month1, p1, varname, days, opt) m2data, y3, y4 = get_data(pgconn, table, station, month2, p2, varname, days, opt) pc1 = np.percentile(m1data, range(0, 101, 1)) pc2 = np.percentile(m2data, range(0, 101, 1)) df = pd.DataFrame({ "%s_%s_%s_%s" % (MDICT[month1], varname, y1, y2): pd.Series(pc1), "%s_%s_%s_%s" % (MDICT[month2], varname, y3, y4): pd.Series(pc2), "quantile": pd.Series(range(0, 101, 5)), }) s_slp, s_int, s_r, _, _ = stats.linregress(pc1, pc2) fig = plt.gcf() fig.set_size_inches(10.24, 7.68) ax = plt.axes([0.1, 0.11, 0.4, 0.76]) ax.scatter(pc1[::5], pc2[::5], s=40, marker="s", color="b", zorder=3) ax.plot( pc1, pc1 * s_slp + s_int, lw=3, color="r", zorder=2, label=r"Fit R$^2$=%.2f" % (s_r**2, ), ) ax.axvline(highlight, zorder=1, color="k") y = highlight * s_slp + s_int ax.axhline(y, zorder=1, color="k") ax.text( pc1[0], y, r"%.0f $^\circ$F" % (y, ), va="center", bbox=dict(color="white"), ) ax.text( highlight, pc2[0], r"%.0f $^\circ$F" % (highlight, ), ha="center", rotation=90, bbox=dict(color="white"), ) t2 = PDICT[varname] if days > 1: t2 = "%s %s over %s days" % (ODICT[opt], PDICT[varname], days) fig.suptitle(("[%s] %s\n%s (%s-%s) vs %s (%s-%s)\n%s") % ( station, ctx["_nt"].sts[station]["name"], MDICT[month2], y1, y2, MDICT[month1], y3, y4, t2, )) ax.set_xlabel(r"%s (%s-%s) %s $^\circ$F" % (MDICT[month1], y1, y2, PDICT[varname])) ax.set_ylabel(r"%s (%s-%s) %s $^\circ$F" % (MDICT[month2], y3, y4, PDICT[varname])) ax.text( 0.95, 0.05, "Quantile - Quantile Plot", transform=ax.transAxes, ha="right", ) ax.grid(True) ax.legend(loc=2) # Second ax = plt.axes([0.55, 0.18, 0.27, 0.68]) ax.set_title("Distribution") v1 = ax.violinplot(m1data, positions=[0], showextrema=True, showmeans=True) b = v1["bodies"][0] m = np.mean(b.get_paths()[0].vertices[:, 0]) b.get_paths()[0].vertices[:, 0] = np.clip(b.get_paths()[0].vertices[:, 0], -np.inf, m) b.set_color("r") for lbl in ["cmins", "cmeans", "cmaxes"]: v1[lbl].set_color("r") v1 = ax.violinplot(m2data, positions=[0], showextrema=True, showmeans=True) b = v1["bodies"][0] m = np.mean(b.get_paths()[0].vertices[:, 0]) b.get_paths()[0].vertices[:, 0] = np.clip(b.get_paths()[0].vertices[:, 0], m, np.inf) b.set_color("b") for lbl in ["cmins", "cmeans", "cmaxes"]: v1[lbl].set_color("b") pr0 = plt.Rectangle((0, 0), 1, 1, fc="r") pr1 = plt.Rectangle((0, 0), 1, 1, fc="b") ax.legend( (pr0, pr1), ( r"%s (%s-%s), $\mu$=%.1f" % (MDICT[month1], y1, y2, np.mean(m1data)), r"%s (%s-%s), $\mu$=%.1f" % (MDICT[month2], y3, y4, np.mean(m2data)), ), ncol=1, loc=(0.5, -0.15), ) ax.set_ylabel(r"%s $^\circ$F" % (PDICT[varname], )) ax.grid() # Third monofont = FontProperties(family="monospace") y = 0.86 x = 0.83 col1 = "%s_%s_%s_%s" % (MDICT[month1], varname, y1, y2) col2 = "%s_%s_%s_%s" % (MDICT[month2], varname, y3, y4) fig.text(x, y + 0.04, "Percentile Data Diff") for percentile in [ 100, 99, 98, 97, 96, 95, 92, 90, 75, 50, 25, 10, 8, 5, 4, 3, 2, 1, ]: row = df.loc[percentile] fig.text(x, y, "%3i" % (percentile, ), fontproperties=monofont) fig.text( x + 0.025, y, "%5.1f" % (row[col1], ), fontproperties=monofont, color="r", ) fig.text( x + 0.07, y, "%5.1f" % (row[col2], ), fontproperties=monofont, color="b", ) fig.text( x + 0.11, y, "%5.1f" % (row[col2] - row[col1], ), fontproperties=monofont, ) y -= 0.04 return fig, df
def make_map(huc, ts, ts2, scenario, v, form): """Make the map""" projection = EPSG[5070] plt.close() # suggested for runoff and precip if v in ["qc_precip", "avg_runoff"]: # c = ['#ffffa6', '#9cf26d', '#76cc94', '#6399ba', '#5558a1'] cmap = james() # suggested for detachment elif v in ["avg_loss"]: # c =['#cbe3bb', '#c4ff4d', '#ffff4d', '#ffc44d', '#ff4d4d', '#c34dee'] cmap = dep_erosion() # suggested for delivery elif v in ["avg_delivery"]: # c =['#ffffd2', '#ffff4d', '#ffe0a5', '#eeb74d', '#ba7c57', '#96504d'] cmap = dep_erosion() pgconn = get_dbconn("idep") cursor = pgconn.cursor() title = "for %s" % (ts.strftime("%-d %B %Y"),) if ts != ts2: title = "for period between %s and %s" % ( ts.strftime("%-d %b %Y"), ts2.strftime("%-d %b %Y"), ) if "averaged" in form: title = "averaged between %s and %s (2008-2017)" % ( ts.strftime("%-d %b"), ts2.strftime("%-d %b"), ) # Check that we have data for this date! cursor.execute( "SELECT value from properties where key = 'last_date_0'", ) lastts = datetime.datetime.strptime(cursor.fetchone()[0], "%Y-%m-%d") floor = datetime.date(2007, 1, 1) if ts > lastts.date() or ts2 > lastts.date() or ts < floor: plt.text( 0.5, 0.5, "Data Not Available\nPlease Check Back Later!", fontsize=20, ha="center", ) ram = BytesIO() plt.savefig(ram, format="png", dpi=100) plt.close() ram.seek(0) return ram.read(), False if huc is None: huclimiter = "" elif len(huc) == 8: huclimiter = " and substr(i.huc_12, 1, 8) = '%s' " % (huc,) elif len(huc) == 12: huclimiter = " and i.huc_12 = '%s' " % (huc,) if "iowa" in form: huclimiter += " and i.states ~* 'IA' " if "mn" in form: huclimiter += " and i.states ~* 'MN' " if "averaged" in form: # 11 years of data is standard # 10 years is for the switchgrass one-off with get_sqlalchemy_conn("idep") as conn: df = read_postgis( f""" WITH data as ( SELECT huc_12, sum({v}) / 10. as d from results_by_huc12 WHERE scenario = %s and to_char(valid, 'mmdd') between %s and %s and valid between '2008-01-01' and '2018-01-01' GROUP by huc_12) SELECT simple_geom as geom, coalesce(d.d, 0) * %s as data from huc12 i LEFT JOIN data d ON (i.huc_12 = d.huc_12) WHERE i.scenario = %s {huclimiter} """, conn, params=( scenario, ts.strftime("%m%d"), ts2.strftime("%m%d"), V2MULTI[v], 0, ), geom_col="geom", ) else: with get_sqlalchemy_conn("idep") as conn: df = read_postgis( f""" WITH data as ( SELECT huc_12, sum({v}) as d from results_by_huc12 WHERE scenario = %s and valid between %s and %s GROUP by huc_12) SELECT simple_geom as geom, coalesce(d.d, 0) * %s as data from huc12 i LEFT JOIN data d ON (i.huc_12 = d.huc_12) WHERE i.scenario = %s {huclimiter} """, conn, params=( scenario, ts.strftime("%Y-%m-%d"), ts2.strftime("%Y-%m-%d"), V2MULTI[v], 0, ), geom_col="geom", ) minx, miny, maxx, maxy = df["geom"].total_bounds buf = 10000.0 # 10km m = MapPlot( axisbg="#EEEEEE", logo="dep", sector="custom", south=miny - buf, north=maxy + buf, west=minx - buf, east=maxx + buf, projection=projection, title="DEP %s by HUC12 %s" % (V2NAME[v], title), titlefontsize=16, caption="Daily Erosion Project", ) if ts == ts2: # Daily bins = RAMPS["english"][0] else: bins = RAMPS["english"][1] norm = mpcolors.BoundaryNorm(bins, cmap.N) for _, row in df.iterrows(): p = Polygon( row["geom"].exterior.coords, fc=cmap(norm([row["data"]]))[0], ec="k", zorder=5, lw=0.1, ) m.ax.add_patch(p) label_scenario(m.ax, scenario, pgconn) lbl = [round(_, 2) for _ in bins] if huc is not None: m.drawcounties() m.drawcities() m.draw_colorbar( bins, cmap, norm, units=V2UNITS[v], clevlabels=lbl, spacing="uniform" ) if "progressbar" in form: fig = plt.gcf() avgval = df["data"].mean() fig.text( 0.01, 0.905, "%s: %4.1f T/a" % (ts.year if "averaged" not in form else "Avg", avgval), fontsize=14, ) bar_width = 0.758 # yes, a small one off with years having 366 days proportion = (ts2 - ts).days / 365.0 * bar_width rect1 = Rectangle( (0.15, 0.905), bar_width, 0.02, color="k", zorder=40, transform=fig.transFigure, figure=fig, ) fig.patches.append(rect1) rect2 = Rectangle( (0.151, 0.907), proportion, 0.016, color=cmap(norm([avgval]))[0], zorder=50, transform=fig.transFigure, figure=fig, ) fig.patches.append(rect2) if "cruse" in form: # Crude conversion of T/a to mm depth depth = avgval / 5.0 m.ax.text( 0.9, 0.92, "%.2fmm" % (depth,), zorder=1000, fontsize=24, transform=m.ax.transAxes, ha="center", va="center", bbox=dict(color="k", alpha=0.5, boxstyle="round,pad=0.1"), color="white", ) ram = BytesIO() plt.savefig(ram, format="png", dpi=100) plt.close() ram.seek(0) return ram.read(), True
def plotter(fdict): """ Go """ font0 = FontProperties() font0.set_family('monospace') font0.set_size(16) font1 = FontProperties() font1.set_size(16) pgconn = get_dbconn('asos') ctx = get_autoplot_context(fdict, get_description()) varname = ctx['var'] varname2 = varname.split("_")[1] if varname2 in ['dwpf', 'tmpf', 'feel']: varname2 = "i" + varname2 month = ctx['month'] network = ctx['network'] station = ctx['zstation'] nt = NetworkTable(network) if month == 'all': months = range(1, 13) elif month == 'fall': months = [9, 10, 11] elif month == 'winter': months = [12, 1, 2] elif month == 'spring': months = [3, 4, 5] elif month == 'summer': months = [6, 7, 8] elif month == 'gs': months = [5, 6, 7, 8, 9] else: ts = datetime.datetime.strptime("2000-" + month + "-01", '%Y-%b-%d') # make sure it is length two for the trick below in SQL months = [ts.month] df = read_sql(""" WITH obs as ( SELECT (valid + '10 minutes'::interval) at time zone %s as ts, tmpf::int as itmpf, dwpf::int as idwpf, feel::int as ifeel, mslp, alti from alldata where station = %s and extract(month from valid at time zone %s) in %s), agg1 as ( SELECT extract(hour from ts) as hr, max(idwpf) as max_dwpf, max(itmpf) as max_tmpf, min(idwpf) as min_dwpf, min(itmpf) as min_tmpf, min(ifeel) as min_feel, max(ifeel) as max_feel, max(alti) as max_alti, min(alti) as min_alti, max(mslp) as max_mslp, min(mslp) as min_mslp from obs GROUP by hr) SELECT o.ts, a.hr::int as hr, a.""" + varname + """ from agg1 a JOIN obs o on (a.hr = extract(hour from o.ts) and a.""" + varname + """ = o.""" + varname2 + """) ORDER by a.hr ASC, o.ts DESC """, pgconn, params=(nt.sts[station]['tzname'], station, nt.sts[station]['tzname'], tuple(months)), index_col=None) y0 = 0.1 yheight = 0.8 dy = (yheight / 24.) (fig, ax) = plt.subplots(1, 1, figsize=(8, 8)) ax.set_position([0.12, y0, 0.57, yheight]) ax.barh(df['hr'], df[varname], align='center') ax.set_ylim(-0.5, 23.5) ax.set_yticks([0, 4, 8, 12, 16, 20]) ax.set_yticklabels(['Mid', '4 AM', '8 AM', 'Noon', '4 PM', '8 PM']) ax.grid(True) ax.set_xlim([df[varname].min() - 5, df[varname].max() + 5]) ax.set_ylabel("Local Time %s" % (nt.sts[station]['tzname'], ), fontproperties=font1) fig.text(0.5, 0.93, ("%s [%s] %s-%s\n" "%s [%s]") % (nt.sts[station]['name'], station, nt.sts[station]['archive_begin'].year, datetime.date.today().year, PDICT[varname], MDICT[month]), ha='center', fontproperties=font1) ypos = y0 + (dy / 2.) for hr in range(24): sdf = df[df['hr'] == hr] if sdf.empty: continue row = sdf.iloc[0] fig.text(0.7, ypos, "%3.0f: %s%s" % (row[varname], row['ts'].strftime("%d %b %Y"), ("*" if len(sdf.index) > 1 else '')), fontproperties=font0, va='center') ypos += dy ax.set_xlabel("%s %s, * denotes ties" % (PDICT[varname], UNITS[varname]), fontproperties=font1) return plt.gcf(), df
def plotter(fdict): """ Go """ font0 = FontProperties() font0.set_family("monospace") font0.set_size(16) font1 = FontProperties() font1.set_size(16) pgconn = get_dbconn("asos") ctx = get_autoplot_context(fdict, get_description()) varname = ctx["var"] varname2 = varname.split("_")[1] if varname2 in ["dwpf", "tmpf", "feel"]: varname2 = "i" + varname2 month = ctx["month"] station = ctx["zstation"] if month == "all": months = range(1, 13) elif month == "fall": months = [9, 10, 11] elif month == "winter": months = [12, 1, 2] elif month == "spring": months = [3, 4, 5] elif month == "summer": months = [6, 7, 8] elif month == "gs": months = [5, 6, 7, 8, 9] else: ts = datetime.datetime.strptime("2000-" + month + "-01", "%Y-%b-%d") # make sure it is length two for the trick below in SQL months = [ts.month] df = read_sql( """ WITH obs as ( SELECT (valid + '10 minutes'::interval) at time zone %s as ts, tmpf::int as itmpf, dwpf::int as idwpf, feel::int as ifeel, mslp, alti from alldata where station = %s and extract(month from valid at time zone %s) in %s), agg1 as ( SELECT extract(hour from ts) as hr, max(idwpf) as max_dwpf, max(itmpf) as max_tmpf, min(idwpf) as min_dwpf, min(itmpf) as min_tmpf, min(ifeel) as min_feel, max(ifeel) as max_feel, max(alti) as max_alti, min(alti) as min_alti, max(mslp) as max_mslp, min(mslp) as min_mslp from obs GROUP by hr) SELECT o.ts, a.hr::int as hr, a.""" + varname + """ from agg1 a JOIN obs o on (a.hr = extract(hour from o.ts) and a.""" + varname + """ = o.""" + varname2 + """) ORDER by a.hr ASC, o.ts DESC """, pgconn, params=( ctx["_nt"].sts[station]["tzname"], station, ctx["_nt"].sts[station]["tzname"], tuple(months), ), index_col=None, ) if df.empty: raise NoDataFound("No Data was found.") y0 = 0.1 yheight = 0.8 dy = yheight / 24.0 (fig, ax) = plt.subplots(1, 1, figsize=(8, 8)) ax.set_position([0.12, y0, 0.57, yheight]) ax.barh(df["hr"], df[varname], align="center") ax.set_ylim(-0.5, 23.5) ax.set_yticks([0, 4, 8, 12, 16, 20]) ax.set_yticklabels(["Mid", "4 AM", "8 AM", "Noon", "4 PM", "8 PM"]) ax.grid(True) ax.set_xlim([df[varname].min() - 5, df[varname].max() + 5]) ax.set_ylabel( "Local Time %s" % (ctx["_nt"].sts[station]["tzname"],), fontproperties=font1, ) ab = ctx["_nt"].sts[station]["archive_begin"] if ab is None: raise NoDataFound("Unknown station metadata") fig.text( 0.5, 0.93, ("%s [%s] %s-%s\n" "%s [%s]") % ( ctx["_nt"].sts[station]["name"], station, ab.year, datetime.date.today().year, PDICT[varname], MDICT[month], ), ha="center", fontproperties=font1, ) ypos = y0 + (dy / 2.0) for hr in range(24): sdf = df[df["hr"] == hr] if sdf.empty: continue row = sdf.iloc[0] fig.text( 0.7, ypos, "%3.0f: %s%s" % ( row[varname], row["ts"].strftime("%d %b %Y"), ("*" if len(sdf.index) > 1 else ""), ), fontproperties=font0, va="center", ) ypos += dy ax.set_xlabel( "%s %s, * denotes ties" % (PDICT[varname], UNITS[varname]), fontproperties=font1, ) return plt.gcf(), df
def plotter(fdict): """ Go """ pgconn = get_dbconn('coop') ctx = get_autoplot_context(fdict, get_description()) station = ctx['station'] network = ctx['network'] month = ctx['month'] varname = ctx['var'] days = ctx['days'] nt = NetworkTable(network) table = "alldata_%s" % (station[:2], ) if month == 'all': months = range(1, 13) elif month == 'fall': months = [9, 10, 11] elif month == 'winter': months = [12, 1, 2] elif month == 'spring': months = [3, 4, 5] elif month == 'summer': months = [6, 7, 8] elif month == 'octmar': months = [10, 11, 12, 1, 2, 3] else: ts = datetime.datetime.strptime("2000-" + month + "-01", '%Y-%b-%d') # make sure it is length two for the trick below in SQL months = [ts.month, 999] sorder = 'ASC' if varname in [ 'min_greatest_low', ] else 'DESC' df = read_sql("""WITH data as ( SELECT month, day, day - '%s days'::interval as start_date, count(*) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as count, sum(precip) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as total_precip, min(high) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as max_least_high, max(low) OVER (ORDER by day ASC ROWS BETWEEN %s preceding and current row) as min_greatest_low from """ + table + """ WHERE station = %s) SELECT day as end_date, start_date, """ + varname + """ from data WHERE month in %s and extract(month from start_date) in %s and count = %s ORDER by """ + varname + """ """ + sorder + """ LIMIT 10 """, pgconn, params=(days - 1, days - 1, days - 1, days - 1, days - 1, station, tuple(months), tuple(months), days), index_col=None) if df.empty: raise ValueError('Error, no results returned!') ylabels = [] fmt = '%.2f' if varname in [ 'total_precip', ] else '%.0f' for _, row in df.iterrows(): # no strftime support for old days, so we hack at it lbl = fmt % (row[varname], ) if days > 1: sts = row['end_date'] - datetime.timedelta(days=(days - 1)) if sts.month == row['end_date'].month: lbl += " -- %s %s-%s, %s" % (calendar.month_abbr[sts.month], sts.day, row['end_date'].day, sts.year) else: lbl += " -- %s %s, %s to\n %s %s, %s" % ( calendar.month_abbr[sts.month], sts.day, sts.year, calendar.month_abbr[row['end_date'].month], row['end_date'].day, row['end_date'].year) else: lbl += " -- %s %s, %s" % ( calendar.month_abbr[row['end_date'].month], row['end_date'].day, row['end_date'].year) ylabels.append(lbl) ax = plt.axes([0.1, 0.1, 0.5, 0.8]) plt.gcf().set_size_inches(8, 6) ax.barh(range(10, 0, -1), df[varname], ec='green', fc='green', height=0.8, align='center') ax2 = ax.twinx() ax2.set_ylim(0.5, 10.5) ax.set_ylim(0.5, 10.5) ax2.set_yticks(range(1, 11)) ax.set_yticks(range(1, 11)) ax.set_yticklabels(["#%s" % (x, ) for x in range(1, 11)][::-1]) ax2.set_yticklabels(ylabels[::-1]) ax.grid(True, zorder=11) ax.set_xlabel(("Precipitation [inch]" if varname in ['total_precip'] else r'Temperature $^\circ$F')) ax.set_title(("%s [%s] Top 10 Events\n" "%s [days=%s] (%s) " "(%s-%s)") % (nt.sts[station]['name'], station, METRICS[varname], days, MDICT[month], nt.sts[station]['archive_begin'].year, datetime.datetime.now().year), size=12) return plt.gcf(), df
def _make_plot(station, df, units, nsector, rmax, hours, months, sname, level, bins, **kwargs): """Generate a matplotlib windrose plot Args: station (str): station identifier df (pd.DataFrame): observations drct (list): list of wind directions units (str): units of wind speed nsector (int): number of bins to use for windrose rmax (float): radius of the plot hours (list): hour limit for plot month (list): month limit for plot sname (str): station name level (int): RAOB level in hPa of interest bins (list): values for binning the wind speeds Returns: matplotlib.Figure """ # Generate figure fig = plt.figure(figsize=(8, 8), dpi=100, facecolor='w', edgecolor='w') rect = [0.12, 0.12, 0.76, 0.76] ax = WindroseAxes(fig, rect, facecolor='w', rmax=rmax) fig.add_axes(ax) wu = WINDUNITS[units] if level is None else RAOB_WINDUNITS[units] if bins: wu['bins'] = bins wu['binlbl'] = [] for i, mybin in enumerate(bins[1:-1]): wu['binlbl'].append("%g-%g" % (mybin, bins[i + 2])) wu['binlbl'].append("%g+" % (bins[-1], )) # Filters the missing values df2 = df[df['drct'] >= 0] try: # Unsure why this bombs out sometimes ax.bar(df2['drct'].values, df2['speed'].values, normed=True, bins=wu['bins'], opening=0.8, edgecolor='white', nsector=nsector) except Exception as exp: sys.stderr.write(str(exp)) # Figure out the shortest bar mindir = ax._info['dir'][np.argmin(np.sum(ax._info['table'], axis=0))] ax.set_rlabel_position((450 - mindir) % 360 - 15) # Adjust the limits so to get a empty center rmin, rmax = ax.get_ylim() ax.set_rorigin(0 - (rmax - rmin) * 0.2) # Make labels have % formatters ax.yaxis.set_major_formatter(FormatStrFormatter('%.1f%%')) handles = [] for p in ax.patches_list: color = p.get_facecolor() handles.append( plt.Rectangle((0, 0), 0.1, 0.3, facecolor=color, edgecolor='black')) legend = fig.legend(handles, wu['binlbl'], bbox_to_anchor=(0.01, 0.01, 0.98, 0.09), loc='center', ncol=6, title='Wind Speed [%s]' % (wu['abbr'], ), mode=None, columnspacing=0.9, handletextpad=0.45, fontsize=14) plt.setp(legend.get_texts(), fontsize=10) # Now we put some fancy debugging info on the plot tlimit = "Time Domain: " if len(hours) == 24 and len(months) == 12: tlimit = "All Year" if len(hours) < 24: if len(hours) > 4: tlimit += "%s-%s" % ( datetime.datetime(2000, 1, 1, hours[0]).strftime("%-I %p"), datetime.datetime(2000, 1, 1, hours[-1]).strftime("%-I %p")) else: for h in hours: tlimit += "%s," % (datetime.datetime(2000, 1, 1, h).strftime("%-I %p"), ) if len(months) < 12: for h in months: tlimit += "%s," % (datetime.datetime(2000, h, 1).strftime("%b"), ) label = """[%s] %s%s Windrose Plot [%s] Period of Record: %s - %s""" % ( station, sname if sname is not None else "((%s))" % (station, ), "" if level is None else " @%s hPa" % (level, ), tlimit, df['valid'].min().strftime("%d %b %Y"), df['valid'].max().strftime("%d %b %Y")) plt.gcf().text(0.14, 0.99, label, va='top', fontsize=14) plt.gcf().text( 0.5, 0.5, "Calm\n%.1f%%" % (len(df[df['sknt'] == 0].index) / float(len(df2.index)) * 100., ), ha='center', va='center', fontsize=14) plt.gcf().text( 0.96, 0.11, ("Summary\nobs count: %s\nMissing: %s\nAvg Speed: %.1f %s") % (len(df.index), len(df.index) - len(df2.index), df['speed'].mean(), wu['abbr']), ha='right', fontsize=14) if not kwargs.get('nogenerated', False): plt.gcf().text(0.02, 0.1, "Generated: %s" % (datetime.datetime.now().strftime("%d %b %Y"), ), verticalalignment="bottom", fontsize=14) # Denote the direction blowing from plt.gcf().text(0.02, 0.125, "Direction is where the wind is\nblowing from, not toward.", va='bottom') # Make a logo im = mpimage.imread('%s/%s' % (DATADIR, 'logo.png')) plt.figimage(im, 10, 735) return fig
def plotter(fdict): """ Go """ ctx = get_autoplot_context(fdict, get_description()) station = ctx["station"] if station not in ctx["_nt"].sts: # This is needed. raise NoDataFound("Unknown station metadata.") varname = ctx["var"] ts = ctx["date"] hour = int(ctx["hour"]) ts = utc(ts.year, ts.month, ts.day, hour) which = ctx["which"] vlimit = "" if which == "month": vlimit = (" and extract(month from f.valid) = %s ") % (ts.month, ) name = ctx["_nt"].sts[station]["name"] stations = [station] if station.startswith("_"): name = ctx["_nt"].sts[station]["name"].split("--")[0] stations = ( ctx["_nt"].sts[station]["name"].split("--")[1].strip().split(" ")) pgconn = get_dbconn("postgis") df = read_sql( """ with data as ( select f.valid, p.pressure, count(*) OVER (PARTITION by p.pressure), min(valid at time zone 'UTC') OVER () as min_valid, max(valid at time zone 'UTC') OVER () as max_valid, p.tmpc, rank() OVER (PARTITION by p.pressure ORDER by p.tmpc ASC) as tmpc_rank, min(p.tmpc) OVER (PARTITION by p.pressure) as tmpc_min, max(p.tmpc) OVER (PARTITION by p.pressure) as tmpc_max, p.dwpc, rank() OVER (PARTITION by p.pressure ORDER by p.dwpc ASC) as dwpc_rank, min(p.dwpc) OVER (PARTITION by p.pressure) as dwpc_min, max(p.dwpc) OVER (PARTITION by p.pressure) as dwpc_max, p.height as hght, rank() OVER ( PARTITION by p.pressure ORDER by p.height ASC) as hght_rank, min(p.height) OVER (PARTITION by p.pressure) as hght_min, max(p.height) OVER (PARTITION by p.pressure) as hght_max, p.smps, rank() OVER (PARTITION by p.pressure ORDER by p.smps ASC) as smps_rank, min(p.smps) OVER (PARTITION by p.pressure) as smps_min, max(p.smps) OVER (PARTITION by p.pressure) as smps_max from raob_flights f JOIN raob_profile p on (f.fid = p.fid) WHERE f.station in %s and extract(hour from f.valid at time zone 'UTC') = %s """ + vlimit + """ and p.pressure in (925, 850, 700, 500, 400, 300, 250, 200, 150, 100, 70, 50, 10)) select * from data where valid = %s ORDER by pressure DESC """, pgconn, params=(tuple(stations), hour, ts), index_col="pressure", ) if df.empty: raise NoDataFound(("Sounding for %s was not found!") % (ts.strftime("%Y-%m-%d %H:%M"), )) df = df.drop("valid", axis=1) for key in PDICT3.keys(): df[key + "_percentile"] = df[key + "_rank"] / df["count"] * 100.0 # manual hackery to get 0 and 100th percentile df.loc[df[key] == df[key + "_max"], key + "_percentile"] = 100.0 df.loc[df[key] == df[key + "_min"], key + "_percentile"] = 0.0 ax = plt.axes([0.1, 0.12, 0.65, 0.75]) bars = ax.barh(range(len(df.index)), df[varname + "_percentile"], align="center") y2labels = [] fmt = "%.1f" if varname not in ["hght"] else "%.0f" for i, mybar in enumerate(bars): ax.text( mybar.get_width() + 1, i, "%.1f" % (mybar.get_width(), ), va="center", bbox=dict(color="white"), ) y2labels.append((fmt + " (" + fmt + " " + fmt + ")") % ( df.iloc[i][varname], df.iloc[i][varname + "_min"], df.iloc[i][varname + "_max"], )) ax.set_yticks(range(len(df.index))) ax.set_yticklabels(["%.0f" % (a, ) for a in df.index.values]) ax.set_ylim(-0.5, len(df.index) - 0.5) ax.set_xlabel("Percentile [100 = highest]") ax.set_ylabel("Mandatory Pressure Level (hPa)") plt.gcf().text( 0.5, 0.9, ("%s %s %s Sounding\n" "(%s-%s) Percentile Ranks (%s) for %s at %sz") % ( station, name, ts.strftime("%Y/%m/%d %H UTC"), pd.Timestamp(df.iloc[0]["min_valid"]).year, pd.Timestamp(df.iloc[0]["max_valid"]).year, ("All Year" if which == "none" else calendar.month_name[ts.month]), PDICT3[varname], hour, ), ha="center", va="bottom", ) ax.grid(True) ax.set_xticks([0, 5, 10, 25, 50, 75, 90, 95, 100]) ax.set_xlim(0, 110) ax.text(1.02, 1, "Ob (Min Max)", transform=ax.transAxes) ax2 = ax.twinx() ax2.set_ylim(-0.5, len(df.index) - 0.5) ax2.set_yticks(range(len(df.index))) ax2.set_yticklabels(y2labels) return plt.gcf(), df
def plotter(fdict): """ Go """ pgconn = get_dbconn("coop") ccursor = pgconn.cursor(cursor_factory=psycopg2.extras.DictCursor) ctx = get_autoplot_context(fdict, get_description()) station = ctx["station"] year = ctx["year"] gdd1 = ctx["gdd1"] gdd2 = ctx["gdd2"] table = "alldata_%s" % (station[:2], ) nt = network.Table("%sCLIMATE" % (station[:2], )) ccursor.execute( """ SELECT day, gddxx(%s, %s, high, low) as gdd from """ + table + """ WHERE year = %s and station = %s ORDER by day ASC """, (ctx["gddbase"], ctx["gddceil"], year, station), ) days = [] gdds = [] for row in ccursor: gdds.append(float(row["gdd"])) days.append(row["day"]) yticks = [] yticklabels = [] jan1 = datetime.datetime(year, 1, 1) for i in range(110, 330): ts = jan1 + datetime.timedelta(days=i) if ts.day == 1 or ts.day % 12 == 1: yticks.append(i) yticklabels.append(ts.strftime("%-d %b")) gdds = np.array(gdds) sts = datetime.datetime(year, 4, 1) ets = datetime.datetime(year, 6, 10) now = sts sz = len(gdds) days2 = [] starts = [] heights = [] success = [] rows = [] while now < ets: idx = int(now.strftime("%j")) - 1 running = 0 while idx < sz and running < gdd1: running += gdds[idx] idx += 1 idx0 = idx while idx < sz and running < gdd2: running += gdds[idx] idx += 1 success.append(running >= gdd2) idx1 = idx days2.append(now) starts.append(idx0) heights.append(idx1 - idx0) rows.append( dict( plant_date=now, start_doy=idx0, end_doy=idx1, success=success[-1], )) now += datetime.timedelta(days=1) if True not in success: raise NoDataFound("No data, pick lower GDD values") df = pd.DataFrame(rows) heights = np.array(heights) success = np.array(success) starts = np.array(starts) cmap = get_cmap(ctx["cmap"]) bmin = min(heights[success]) - 1 bmax = max(heights[success]) + 1 bins = np.arange(bmin, bmax + 1.1) norm = mpcolors.BoundaryNorm(bins, cmap.N) ax = plt.axes([0.125, 0.125, 0.75, 0.75]) bars = ax.bar(days2, heights, bottom=starts, fc="#EEEEEE") for i, mybar in enumerate(bars): if success[i]: mybar.set_facecolor(cmap(norm([heights[i]])[0])) ax.grid(True) ax.set_yticks(yticks) ax.set_yticklabels(yticklabels) ax.set_ylim(min(starts) - 7, max(starts + heights) + 7) ax.xaxis.set_major_formatter(mdates.DateFormatter("%-d\n%b")) ax.set_xlabel("Planting Date") ax.set_title(("%s [%s] %s GDD [base=%s,ceil=%s]\n" "Period between GDD %s and %s, gray bars incomplete") % ( nt.sts[station]["name"], station, year, ctx["gddbase"], ctx["gddceil"], gdd1, gdd2, )) ax2 = plt.axes([0.92, 0.1, 0.07, 0.8], frameon=False, yticks=[], xticks=[]) ax2.set_xlabel("Days") for i, mybin in enumerate(bins): ax2.text(0.52, i, "%g" % (mybin, ), ha="left", va="center", color="k") # txt.set_path_effects([PathEffects.withStroke(linewidth=2, # foreground="k")]) ax2.barh( np.arange(len(bins[:-1])), [0.5] * len(bins[:-1]), height=1, color=cmap(norm(bins[:-1])), ec="None", ) ax2.set_xlim(0, 1) return plt.gcf(), df
def plotter(fdict): """ Go """ ctx = get_autoplot_context(fdict, get_description()) station = ctx['station'] varname = ctx['var'] network = 'RAOB' hour = int(ctx['hour']) month = ctx['month'] level = ctx['level'] agg = ctx['agg'] offset = 0 if month == 'all': months = range(1, 13) elif month == 'fall': months = [9, 10, 11] elif month == 'winter': months = [12, 1, 2] offset = 32 elif month == 'spring': months = [3, 4, 5] elif month == 'mjj': months = [5, 6, 7] elif month == 'summer': months = [6, 7, 8] else: ts = datetime.datetime.strptime("2000-" + month + "-01", '%Y-%b-%d') # make sure it is length two for the trick below in SQL months = [ts.month] nt = NetworkTable(network, only_online=False) name = nt.sts[station]['name'] stations = [ station, ] if station.startswith("_"): name = nt.sts[station]['name'].split("--")[0] stations = nt.sts[station]['name'].split("--")[1].strip().split(" ") pgconn = get_dbconn('postgis') dfin = read_sql(""" select extract(year from f.valid + '%s days'::interval) as year, avg(p.""" + varname + """) as avg_""" + varname + """, min(p.""" + varname + """) as min_""" + varname + """, max(p.""" + varname + """) as max_""" + varname + """, count(*) from raob_profile p JOIN raob_flights f on (p.fid = f.fid) WHERE f.station in %s and p.pressure = %s and extract(hour from f.valid at time zone 'UTC') = %s and extract(month from f.valid) in %s GROUP by year ORDER by year ASC """, pgconn, params=(offset, tuple(stations), level, hour, tuple(months)), index_col='year') # need quorums df = dfin[dfin['count'] > ((len(months) * 28) * 0.75)] if df.empty: raise ValueError("No data was found!") colname = "%s_%s" % (agg, varname) fig, ax = plt.subplots(1, 1) avgv = df[colname].mean() bars = ax.bar(df.index.values, df[colname], align='center') for i, _bar in enumerate(bars): val = df.iloc[i][colname] if val < avgv: _bar.set_color('blue') else: _bar.set_color('red') ax.set_xlim(df.index.min() - 1, df.index.max() + 1) rng = df[colname].max() - df[colname].min() ax.set_ylim(df[colname].min() - rng * 0.1, df[colname].max() + rng * 0.1) ax.axhline(avgv, color='k') ax.text(df.index.values[-1] + 2, avgv, "Avg:\n%.1f" % (avgv, )) ax.set_xlabel("Year") ax.set_ylabel("%s %s @ %s hPa" % (PDICT4[agg], PDICT3[varname], level)) plt.gcf().text(0.5, 0.9, ("%s %s %02i UTC Sounding\n" "%s %s @ %s hPa over %s") % (station, name, hour, PDICT4[agg], PDICT3[varname], level, MDICT[month]), ha='center', va='bottom') ax.grid(True) return fig, df
def plotter(fdict): """ Go """ ctx = get_autoplot_context(fdict, get_description()) station = ctx["station"] if station not in ctx["_nt"].sts: # This is needed. raise NoDataFound("Unknown station metadata.") varname = ctx["var"] hour = int(ctx["hour"]) month = ctx["month"] level = ctx["level"] agg = ctx["agg"] offset = 0 if month == "all": months = range(1, 13) elif month == "fall": months = [9, 10, 11] elif month == "winter": months = [12, 1, 2] offset = 32 elif month == "spring": months = [3, 4, 5] elif month == "mjj": months = [5, 6, 7] elif month == "summer": months = [6, 7, 8] else: ts = datetime.datetime.strptime("2000-" + month + "-01", "%Y-%b-%d") # make sure it is length two for the trick below in SQL months = [ts.month] name = ctx["_nt"].sts[station]["name"] stations = [station] if station.startswith("_"): name = ctx["_nt"].sts[station]["name"].split("--")[0] stations = ( ctx["_nt"].sts[station]["name"].split("--")[1].strip().split(" ") ) pgconn = get_dbconn("postgis") if varname in ["tmpc", "dwpc", "height", "smps"]: leveltitle = " @ %s hPa" % (level,) dfin = read_sql( """ select extract(year from f.valid + '%s days'::interval) as year, avg(""" + varname + """) as avg_""" + varname + """, min(""" + varname + """) as min_""" + varname + """, max(""" + varname + """) as max_""" + varname + """, count(*) from raob_profile p JOIN raob_flights f on (p.fid = f.fid) WHERE f.station in %s and p.pressure = %s and extract(hour from f.valid at time zone 'UTC') = %s and extract(month from f.valid) in %s GROUP by year ORDER by year ASC """, pgconn, params=(offset, tuple(stations), level, hour, tuple(months)), index_col="year", ) else: leveltitle = "" dfin = read_sql( """ select extract(year from f.valid + '%s days'::interval) as year, count(*), avg(""" + varname + """) as avg_""" + varname + """, min(""" + varname + """) as min_""" + varname + """, max(""" + varname + """) as max_""" + varname + """ from raob_flights f WHERE f.station in %s and extract(hour from f.valid at time zone 'UTC') = %s and extract(month from f.valid) in %s GROUP by year ORDER by year ASC """, pgconn, params=(offset, tuple(stations), hour, tuple(months)), index_col="year", ) # need quorums df = dfin[dfin["count"] > ((len(months) * 28) * 0.75)] if df.empty: raise NoDataFound("No data was found!") colname = "%s_%s" % (agg, varname) fig, ax = plt.subplots(1, 1) avgv = df[colname].mean() bars = ax.bar(df.index.values, df[colname], align="center") for i, _bar in enumerate(bars): val = df.iloc[i][colname] if val < avgv: _bar.set_color("blue") else: _bar.set_color("red") ax.set_xlim(df.index.min() - 1, df.index.max() + 1) rng = df[colname].max() - df[colname].min() ax.set_ylim(df[colname].min() - rng * 0.1, df[colname].max() + rng * 0.1) ax.axhline(avgv, color="k") ax.text(df.index.values[-1] + 2, avgv, "Avg:\n%.1f" % (avgv,)) ax.set_xlabel("Year") ax.set_ylabel("%s %s%s" % (PDICT4[agg], PDICT3[varname], leveltitle)) plt.gcf().text( 0.5, 0.9, ("%s %s %02i UTC Sounding\n" "%s %s%s over %s") % ( station, name, hour, PDICT4[agg], PDICT3[varname], leveltitle, MDICT[month], ), ha="center", va="bottom", ) ax.grid(True) return fig, df