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 calendar_plot(sts, ets, data, **kwargs): """Create a plot that looks like a calendar Args: sts (datetime.date): start date of this plot ets (datetime.date): end date of this plot (inclusive) data (dict[dict]): dictionary with keys of dates and dicts for `val` value and optionally `color` for color kwargs (dict): heatmap (bool): background color for cells based on `val`, False cmap (str): color map to use for norm """ bounds = _compute_bounds(sts, ets) # Compute the number of month calendars we need. # We want 'square' boxes for each month's calendar, 4x3 fig = plt.figure(figsize=(10.24, 7.68)) if 'fontsize' not in kwargs: kwargs['fontsize'] = 12 if len(bounds) < 3: kwargs['fontsize'] = 18 elif len(bounds) < 5: kwargs['fontsize'] = 16 elif len(bounds) < 10: kwargs['fontsize'] = 14 if kwargs.get('heatmap', False): kwargs['cmap'] = plt.get_cmap(kwargs.get('cmap', 'viridis')) maxval = -1000 for key in data: if data[key]['val'] > maxval: maxval = data[key]['val'] # Need at least 3 slots maxval = 5 if maxval < 5 else maxval kwargs['norm'] = mpcolors.BoundaryNorm(np.arange(0, maxval), kwargs['cmap'].N) for month in bounds: ax = fig.add_axes(bounds[month]) _do_month(month, ax, data, sts, ets, kwargs) iemlogo(fig) title = kwargs.get('title') if title is not None: fitbox(fig, title, 0.1, 0.99, 0.95, 0.99) subtitle = kwargs.get('subtitle') if subtitle is not None: fitbox(fig, subtitle, 0.1, 0.99, 0.925, 0.945) return fig
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 _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 calendar_plot(sts, ets, data, **kwargs): """Create a plot that looks like a calendar Args: sts (datetime.date): start date of this plot ets (datetime.date): end date of this plot (inclusive) data (dict[dict]): dictionary with keys of dates and dicts for `val` value and optionally `color` for color kwargs (dict): heatmap (bool): background color for cells based on `val`, False cmap (str): color map to use for norm """ bounds = _compute_bounds(sts, ets) # Compute the number of month calendars we need. # We want 'square' boxes for each month's calendar, 4x3 fig = plt.figure(figsize=(10.24, 7.68)) if "fontsize" not in kwargs: kwargs["fontsize"] = 12 if len(bounds) < 3: kwargs["fontsize"] = 18 elif len(bounds) < 5: kwargs["fontsize"] = 16 elif len(bounds) < 10: kwargs["fontsize"] = 14 if kwargs.get("heatmap", False): kwargs["cmap"] = get_cmap(kwargs.get("cmap", "viridis")) maxval = -1000 for key in data: if data[key]["val"] > maxval: maxval = data[key]["val"] # Need at least 3 slots maxval = 5 if maxval < 5 else maxval # Need to have more colors than bins kwargs["norm"] = mpcolors.BoundaryNorm( np.arange(0, maxval, int(maxval / 255.0) + 1), kwargs["cmap"].N) for month in bounds: ax = fig.add_axes(bounds[month]) _do_month(month, ax, data, sts, ets, kwargs) iemlogo(fig) title = kwargs.get("title") if title is not None: fitbox(fig, title, 0.1, 0.99, 0.95, 0.99) subtitle = kwargs.get("subtitle") if subtitle is not None: if subtitle.find("\n") > 0: # Allow more room fitbox(fig, subtitle, 0.1, 0.99, 0.909, 0.949) else: fitbox(fig, subtitle, 0.1, 0.99, 0.925, 0.945) return fig
def plotter(fdict): """ Go """ ctx = get_autoplot_context(fdict, get_description()) typ = ctx["typ"] sort = ctx["sort"] date = ctx["date"] pgconn = get_dbconn("postgis") sts = utc(date.year, date.month, date.day) ets = sts + datetime.timedelta(hours=24) opts = { "W": { "fnadd": "-wfo", "sortby": "wfo ASC, phenomena ASC, eventid ASC", }, "S": { "fnadd": "", "sortby": "size DESC" }, "T": { "fnadd": "-time", "sortby": "issue ASC" }, } phenoms = {"W": ["TO", "SV"], "F": ["FF"], "M": ["MA"]} # Defaults thumbpx = 100 cols = 10 mybuffer = 10000 header = 35 # Find largest polygon either in height or width gdf = read_postgis( """ SELECT wfo, phenomena, eventid, issue, ST_area2d(ST_transform(geom,2163)) as size, (ST_xmax(ST_transform(geom,2163)) + ST_xmin(ST_transform(geom,2163))) /2.0 as xc, (ST_ymax(ST_transform(geom,2163)) + ST_ymin(ST_transform(geom,2163))) /2.0 as yc, ST_transform(geom, 2163) as utmgeom, (ST_xmax(ST_transform(geom,2163)) - ST_xmin(ST_transform(geom,2163))) as width, (ST_ymax(ST_transform(geom,2163)) - ST_ymin(ST_transform(geom,2163))) as height from sbw_""" + str(sts.year) + """ WHERE status = 'NEW' and issue >= %s and issue < %s and phenomena IN %s and eventid is not null ORDER by """ + opts[sort]["sortby"] + """ """, pgconn, params=(sts, ets, tuple(phenoms[typ])), geom_col="utmgeom", index_col=None, ) # For size reduction work df = read_sql( """ SELECT w.wfo, phenomena, eventid, sum(ST_area2d(ST_transform(u.geom,2163))) as county_size from warnings_""" + str(sts.year) + """ w JOIN ugcs u on (u.gid = w.gid) WHERE issue >= %s and issue < %s and significance = 'W' and phenomena IN %s GROUP by w.wfo, phenomena, eventid """, pgconn, params=(sts, ets, tuple(phenoms[typ])), index_col=["wfo", "phenomena", "eventid"], ) # Join the columns gdf = gdf.merge(df, on=["wfo", "phenomena", "eventid"]) gdf["ratio"] = (1.0 - (gdf["size"] / gdf["county_size"])) * 100.0 # Make mosaic image events = len(df.index) rows = int(events / cols) + 1 if events % cols == 0: rows -= 1 if rows == 0: rows = 1 ypixels = (rows * thumbpx) + header fig = plt.figure(figsize=(thumbpx * cols / 100.0, ypixels / 100.0)) plt.axes([0, 0, 1, 1], facecolor="black") imagemap = StringIO() utcnow = utc() imagemap.write("<!-- %s %s -->\n" % (utcnow.strftime("%Y-%m-%d %H:%M:%S"), sort)) imagemap.write("<map name='mymap'>\n") # Write metadata to image mydir = os.sep.join( [os.path.dirname(os.path.abspath(__file__)), "../../../images"]) logo = mpimage.imread("%s/logo_reallysmall.png" % (mydir, )) y0 = fig.get_figheight() * 100.0 - logo.shape[0] - 5 fig.figimage(logo, 5, y0, zorder=3) i = 0 # amount of NDC y space we have for axes plotting ytop = 1 - header / float((rows * 100) + header) dy = ytop / float(rows) ybottom = ytop # Sumarize totals y = ytop dy2 = (1.0 - ytop) / 2.0 for phenomena, df2 in gdf.groupby("phenomena"): car = (1.0 - df2["size"].sum() / df2["county_size"].sum()) * 100.0 fitbox( fig, ("%i %s.W: Avg size %5.0f km^2 CAR: %.0f%%") % (len(df2.index), phenomena, df2["size"].mean() / 1e6, car), 0.8, 0.99, y, y + dy2, color=COLORS[phenomena], ) y += dy2 fitbox( fig, "NWS %s Storm Based Warnings issued %s UTC" % ( " + ".join([VTEC_PHENOMENA[p] for p in phenoms[typ]]), sts.strftime("%d %b %Y"), ), 0.05, 0.79, ytop + dy2, 0.999, color="white", ) fitbox( fig, "Generated: %s UTC, IEM Autplot #203" % (utcnow.strftime("%d %b %Y %H:%M:%S"), ), 0.05, 0.79, ytop, 0.999 - dy2, color="white", ) # We want to reserve 14pts at the bottom and buffer the plot by 10km # so we compute this in the y direction, since it limits us max_dimension = max([gdf["width"].max(), gdf["height"].max()]) yspacing = max_dimension / 2.0 + mybuffer xspacing = yspacing * 1.08 # approx for _, row in gdf.iterrows(): # - Map each polygon x0 = float(row["xc"]) - xspacing x1 = float(row["xc"]) + xspacing y0 = float(row["yc"]) - yspacing - (yspacing * 0.14) y1 = float(row["yc"]) + yspacing - (yspacing * 0.14) col = i % 10 if col == 0: ybottom -= dy ax = plt.axes( [col * 0.1, ybottom, 0.1, dy], facecolor="black", xticks=[], yticks=[], aspect="auto", ) for x in ax.spines: ax.spines[x].set_visible(False) ax.set_xlim(x0, x1) ax.set_ylim(y0, y1) for poly in row["utmgeom"]: xs, ys = poly.exterior.xy color = COLORS[row["phenomena"]] ax.plot(xs, ys, color=color, lw=2) car = "NA" carColor = "white" if not pd.isnull(row["ratio"]): carf = row["ratio"] car = "%.0f" % (carf, ) if carf > 75: carColor = "green" if carf < 25: carColor = "red" # Draw Text! issue = row["issue"] s = "%s.%s.%s.%s" % ( row["wfo"], row["phenomena"], row["eventid"], issue.strftime("%H%M"), ) # (w, h) = font10.getsize(s) # print s, h ax.text( 0, 0, s, transform=ax.transAxes, color="white", va="bottom", fontsize=7, ) s = "%.0f sq km %s%%" % (row["size"] / 1000000.0, car) ax.text( 0, 0.1, s, transform=ax.transAxes, color=carColor, va="bottom", fontsize=7, ) # Image map url = ("/vtec/#%s-O-NEW-K%s-%s-%s-%04i") % ( sts.year, row["wfo"], row["phenomena"], "W", row["eventid"], ) altxt = "Click for text/image" pos = ax.get_position() mx0 = pos.x0 * 1000.0 my = (1.0 - pos.y1) * ypixels imagemap.write( ('<area href="%s" alt="%s" title="%s" ' 'shape="rect" coords="%.0f,%.0f,%.0f,%.0f">\n') % (url, altxt, altxt, mx0, my, mx0 + thumbpx, my + thumbpx)) i += 1 faux = plt.axes([0, 0, 1, 1], facecolor="None", zorder=100) for i in range(1, rows): faux.axhline(i * dy, lw=1.0, color="blue") imagemap.write("</map>") imagemap.seek(0) if gdf.empty: fitbox(fig, "No warnings Found!", 0.2, 0.8, 0.2, 0.5, color="white") df = gdf.drop("utmgeom", axis=1) return fig, df, imagemap.read()
def plotter(fdict): """ Go """ pgconn = get_dbconn('asos') ctx = get_autoplot_context(fdict, get_description()) station = ctx['zstation'] h1 = int(ctx['h1']) h2 = int(ctx['h2']) varname = ctx['v'] tzname = ctx['_nt'].sts[station]['tzname'] df = read_sql(""" WITH data as ( SELECT valid at time zone %s + '10 minutes'::interval as localvalid, date_trunc( 'hour', valid at time zone %s + '10 minutes'::interval) as v, tmpf, dwpf, sknt, drct, alti, relh, random() as r, coalesce(mslp, alti * 33.8639, 1013.25) as slp from alldata where station = %s and report_type = 2 and extract(hour from valid at time zone %s + '10 minutes'::interval) in (%s, %s)), agg as ( select *, extract(hour from v) as hour, rank() OVER (PARTITION by v ORDER by localvalid ASC, r ASC) from data ) SELECT *, date( case when hour = %s then date(v - '1 day'::interval) else date(v) end) from agg WHERE rank = 1 """, pgconn, params=(tzname, tzname, station, tzname, h1, h2, h2 if h2 < h1 else -1), index_col=None) if df.empty: raise NoDataFound("No data was found.") if varname == 'q': df['pressure'] = mcalc.add_height_to_pressure( df['slp'].values * units('millibars'), ctx['_nt'].sts[station]['elevation'] * units('m')).to( units('millibar')) # compute mixing ratio df['q'] = mcalc.mixing_ratio_from_relative_humidity( df['relh'].values * units('percent'), df['tmpf'].values * units('degF'), df['pressure'].values * units('millibars')) * 1000. # pivot df = df.pivot(index='date', columns='hour', values=varname).reset_index() df = df.dropna() df['doy'] = pd.to_numeric(pd.to_datetime(df['date']).dt.strftime("%j")) df['year'] = pd.to_datetime(df['date']).dt.year df['week'] = (df['doy'] / 7).astype(int) df['delta'] = df[h2] - df[h1] (fig, ax) = plt.subplots(1, 1) if ctx['opt'] == 'no': ax.set_xlabel("Plotted lines are smoothed over %.0f days" % (ctx['smooth'], )) ax.set_ylabel( "%s %s Difference" % (PDICT[varname], "Accumulated Sum" if ctx['opt'] == 'yes' else '')) if ctx['opt'] == 'no': # Histogram H, xedges, yedges = np.histogram2d(df['doy'].values, df['delta'].values, bins=(50, 50)) ax.pcolormesh(xedges, yedges, H.transpose(), cmap=plt.get_cmap(ctx['cmap']), alpha=0.5) # Plot an average line gdf = df.groupby('doy').mean().rolling(ctx['smooth'], min_periods=1, center=True).mean() y = gdf['delta'] if ctx['opt'] == 'no' else gdf['delta'].cumsum() ax.plot(gdf.index.values, y, label='Average', zorder=6, lw=2, color='k', linestyle='-.') # Plot selected year for i in range(1, 5): year = ctx.get("y%s" % (i, )) if year is None: continue df2 = df[df['year'] == year] if not df2.empty: gdf = df2.groupby('doy').mean().rolling(ctx['smooth'], min_periods=1, center=True).mean() y = gdf['delta'] if ctx['opt'] == 'no' else gdf['delta'].cumsum() ax.plot(gdf.index.values, y, label=str(year), lw=2, zorder=10) ax.set_xticks((1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 365)) ax.set_xticklabels(calendar.month_abbr[1:]) ax.set_xlim(1, 366) ax.grid(True) ax.legend(loc='best', ncol=5) sts = datetime.datetime(2000, 6, 1, h1) ets = datetime.datetime(2000, 6, 1, h2) title = ("%s [%s] %s Difference (%.0f-%.0f)\n" "%s minus %s (%s) (timezone: %s)") % ( ctx['_nt'].sts[station]['name'], station, PDICT[varname], df['year'].min(), df['year'].max(), ets.strftime("%-I %p"), sts.strftime("%-I %p"), "same day" if h2 > h1 else "previous day", tzname) fitbox(fig, title, 0.05, 0.95, 0.91, 0.99, ha='center') return fig, df
def plotter(fdict): """ Go """ pgconn = get_dbconn('postgis') ctx = get_autoplot_context(fdict, get_description()) wfo = ctx['station'] phenomena = ctx['phenomena'] significance = ctx['significance'] if ctx['season'] == 'all': months = range(1, 13) elif ctx['season'] == 'water_year': months = range(1, 13) elif ctx['season'] == 'spring': months = [3, 4, 5] elif ctx['season'] == 'spring2': months = [4, 5, 6] elif ctx['season'] == 'fall': months = [9, 10, 11] elif ctx['season'] == 'summer': months = [6, 7, 8] elif ctx['season'] == 'winter': months = [12, 1, 2] else: ts = datetime.datetime.strptime("2000-" + ctx['season'] + "-01", '%Y-%b-%d') # make sure it is length two for the trick below in SQL months = [ts.month, 999] nt = NetworkTable("WFO") (fig, ax) = plt.subplots(1, 1) tzname = nt.sts[wfo]['tzname'] df = read_sql(""" WITH data as ( SELECT extract(year from issue) as yr, eventid, min(issue at time zone %s) as minissue, max(expire at time zone %s) as maxexpire from warnings WHERE phenomena = %s and significance = %s and wfo = %s and extract(month from issue) in %s GROUP by yr, eventid), events as ( select count(*) from data), timedomain as ( SELECT generate_series(minissue, least(maxexpire, minissue + '24 hours'::interval) , '1 minute'::interval) as ts from data ), data2 as ( SELECT extract(hour from ts)::int * 60 + extract(minute from ts)::int as minute, count(*) from timedomain GROUP by minute ORDER by minute ASC) select d.minute, d.count, e.count as total from data2 d, events e """, pgconn, params=( tzname, tzname, phenomena, significance, wfo, tuple(months)), index_col='minute') if df.empty: raise ValueError("No Results Found") df['frequency'] = df['count'] / df['total'] * 100. ax.bar(df.index.values, df['frequency'].values, ec='b', fc='b', align='center') ax.grid() if df['frequency'].max() > 70: ax.set_ylim(0, 101) ax.set_xticks(range(0, 25 * 60, 60)) ax.set_xlim(-0.5, 24 * 60 + 1) ax.set_xticklabels(["Mid", "", "", "3 AM", "", "", "6 AM", "", "", '9 AM', "", "", "Noon", "", "", "3 PM", "", "", "6 PM", "", "", "9 PM", "", "", "Mid"]) ax.set_xlabel("Timezone: %s (Daylight or Standard)" % (tzname,)) ax.set_ylabel("Percentage [%%] out of %.0f Events" % (df['total'].max(), )) title = "[%s] %s :: Time of Day Frequency" % (wfo, nt.sts[wfo]['name']) subtitle = "%s (%s.%s) [%s]" % ( vtec.get_ps_string(phenomena, significance), phenomena, significance, MDICT[ctx['season']] ) fitbox(fig, title, 0.05, 0.95, 0.95, 0.99, ha='center') fitbox(fig, subtitle, 0.05, 0.95, 0.91, 0.945, ha='center') return fig, df
def plotter(fdict): """ Go """ pgconn = get_dbconn("asos") ctx = get_autoplot_context(fdict, get_description()) station = ctx["zstation"] date = ctx["date"] opt = ctx["opt"] varname = ctx["v"] tzname = ctx["_nt"].sts[station]["tzname"] # Resolve how to limit the query data limiter = "" if opt == "day": limiter = (f" and to_char(valid at time zone '{tzname}', 'mmdd') = " f"'{date.strftime('%m%d')}' ") subtitle = (f"For Date of {date.strftime('%-d %b')}, " f"{date.strftime('%-d %b %Y')} plotted in bottom panel") datefmt = "%I %p" elif opt == "week": limiter = f" and extract(week from valid) = {date.strftime('%V')} " subtitle = ( f"For ISO Week of {date.strftime('%V')}, " f"week of {date.strftime('%-d %b %Y')} plotted in bottom panel") datefmt = "%-d %b" elif opt == "month": limiter = f" and extract(month from valid) = {date.strftime('%m')} " subtitle = (f"For Month of {date.strftime('%B')}, " f"{date.strftime('%b %Y')} plotted in bottom panel") datefmt = "%-d" else: subtitle = f"All Year, {date.year} plotted in bottom panel" datefmt = "%-d %b" # Load up all the values, since we need pandas to do some heavy lifting obsdf = read_sql( f""" select valid at time zone 'UTC' as utc_valid, extract(year from valid at time zone %s) as year, extract(hour from valid at time zone %s + '10 minutes'::interval)::int as hr, {varname} from alldata WHERE station = %s and {varname} is not null {limiter} and report_type = 2 ORDER by valid ASC """, pgconn, params=(tzname, tzname, station), index_col=None, ) if obsdf.empty: raise NoDataFound("No data was found.") # Assign percentiles obsdf["quantile"] = obsdf[["hr", varname]].groupby("hr").rank(pct=True) # Compute actual percentiles qtile = (obsdf[["hr", varname ]].groupby("hr").quantile(np.arange(0, 1.01, 0.05)).reset_index()) qtile = qtile.rename(columns={"level_1": "quantile"}) (fig, ax) = plt.subplots(2, 1) cmap = get_cmap(ctx["cmap"]) for hr, gdf in qtile.groupby("hr"): ax[0].plot( gdf["quantile"].values * 100.0, gdf[varname].values, color=cmap(hr / 23.0), label=str(hr), ) ax[0].set_xlim(0, 100) ax[0].grid(True) ax[0].set_ylabel(PDICT[varname]) ax[0].set_xlabel("Percentile") ax[0].set_position([0.13, 0.55, 0.71, 0.34]) cax = plt.axes([0.86, 0.55, 0.03, 0.33], frameon=False, yticks=[], xticks=[]) cb = ColorbarBase(cax, cmap=cmap) cb.set_ticks(np.arange(0, 1, 4.0 / 24.0)) cb.set_ticklabels(["Mid", "4 AM", "8 AM", "Noon", "4 PM", "8 PM"]) cb.set_label("Local Hour") thisyear = obsdf[obsdf["year"] == date.year] if not thisyear.empty: ax[1].plot(thisyear["utc_valid"].values, thisyear["quantile"].values * 100.0) ax[1].grid(True) ax[1].set_ylabel("Percentile") ax[1].set_ylim(-1, 101) ax[1].xaxis.set_major_formatter( mdates.DateFormatter(datefmt, tz=pytz.timezone(tzname))) if opt == "day": ax[1].set_xlabel(f"Timezone: {tzname}") title = ("%s %s %s Percentiles\n%s") % ( station, ctx["_nt"].sts[station]["name"], PDICT[varname], subtitle, ) fitbox(fig, title, 0.01, 0.99, 0.91, 0.99, ha="center", va="center") return fig, qtile
def plotter(fdict): """ Go """ pgconn = get_dbconn("asos") ctx = get_autoplot_context(fdict, get_description()) station = ctx["zstation"] month = ctx["month"] varname = ctx["var"] tzname = ctx["_nt"].sts[station]["tzname"] if ctx.get("sdate") and ctx.get("edate"): date_limiter = ( " and (to_char(valid at time zone '%s', 'mmdd') >= '%s'" " %s to_char(valid at time zone '%s', 'mmdd') <= '%s')") % ( tzname, ctx["sdate"].strftime("%m%d"), "or" if ctx["sdate"] > ctx["edate"] else "and", tzname, ctx["edate"].strftime("%m%d"), ) title = "between %s and %s" % ( ctx["sdate"].strftime("%-d %b"), ctx["edate"].strftime("%-d %b"), ) if ctx["sdate"] == ctx["edate"]: date_limiter = ( "and to_char(valid at time zone '%s', 'mmdd') = '%s'") % ( tzname, ctx["sdate"].strftime("%m%d")) title = "on %s" % (ctx["sdate"].strftime("%-d %b"), ) else: 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] date_limiter = ( " and extract(month from valid at time zone '%s') in %s") % ( tzname, tuple(months)) title = MDICT[month] if ctx.get("hour") is not None: date_limiter += ( f" and extract(hour from valid at time zone '{tzname}' " f"+ '10 minutes'::interval) = {ctx['hour']}") dt = datetime.datetime(2000, 1, 1, ctx["hour"]) title += " @" + dt.strftime("%-I %p") (agg, dbvar) = varname.split("_") if agg in ["max", "min"]: titlelabel = "Top" sorder = "DESC" if agg == "max" else "ASC" df = read_sql( f""" WITH data as ( SELECT valid at time zone %s as v, {dbvar} from alldata WHERE station = %s {date_limiter}) SELECT v as valid, {dbvar} from data ORDER by {dbvar} {sorder} NULLS LAST LIMIT 100 """, pgconn, params=(ctx["_nt"].sts[station]["tzname"], station), index_col=None, ) else: titlelabel = "Most Recent" op = ">=" if agg == "above" else "<" threshold = float(ctx.get("threshold", 100)) df = read_sql( f"SELECT valid at time zone %s as valid, {dbvar} from alldata " f"WHERE station = %s {date_limiter} and {dbvar} {op} {threshold} " "ORDER by valid DESC LIMIT 100", pgconn, params=(ctx["_nt"].sts[station]["tzname"], station), index_col=None, ) if df.empty: raise NoDataFound("Error, no results returned!") ylabels = [] fmt = "%.0f" if dbvar in ["tmpf", "dwpf"] else "%.2f" hours = [] y = [] lastval = -99 ranks = [] currentrank = 0 rows2keep = [] for idx, row in df.iterrows(): key = row["valid"].strftime("%Y%m%d%H") if key in hours or pd.isnull(row[dbvar]): continue rows2keep.append(idx) hours.append(key) y.append(row[dbvar]) lbl = fmt % (row[dbvar], ) lbl += " -- %s" % (row["valid"].strftime("%b %d, %Y %-I:%M %p"), ) ylabels.append(lbl) if row[dbvar] != lastval or agg in ["above", "below"]: currentrank += 1 ranks.append(currentrank) lastval = row[dbvar] if len(ylabels) == 10: break if not y: raise NoDataFound("No data found.") fig = plt.figure(figsize=(8, 6)) ax = plt.axes([0.1, 0.1, 0.5, 0.8]) ax.barh( range(len(y), 0, -1), y, 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, len(y) + 1)) ax.set_yticks(range(1, len(y) + 1)) ax.set_yticklabels(["#%s" % (x, ) for x in ranks][::-1]) ax2.set_yticklabels(ylabels[::-1]) ax.grid(True, zorder=11) ax.set_xlabel("%s %s" % (METRICS[varname], UNITS[dbvar])) ab = ctx["_nt"].sts[station]["archive_begin"] if ab is None: raise NoDataFound("Unknown station metadata.") fitbox( fig, ("%s [%s] %s 10 Events\n%s %s (%s) (%s-%s)") % ( ctx["_nt"].sts[station]["name"], station, titlelabel, METRICS[varname], ctx.get("threshold") if agg in ["above", "below"] else "", title, ab.year, datetime.datetime.now().year, ), 0.01, 0.99, 0.91, 0.99, ha="center", ) fig.text( 0.98, 0.03, "Timezone: %s" % (ctx["_nt"].sts[station]["tzname"], ), ha="right", ) return fig, df.loc[rows2keep]
def plotter(fdict): """ Go """ pgconn = get_dbconn("asos") ctx = get_autoplot_context(fdict, get_description()) station = ctx["zstation"] h1 = int(ctx["h1"]) h2 = int(ctx["h2"]) varname = ctx["v"] tzname = ctx["_nt"].sts[station]["tzname"] df = read_sql( """ WITH data as ( SELECT valid at time zone %s + '10 minutes'::interval as localvalid, date_trunc( 'hour', valid at time zone %s + '10 minutes'::interval) as v, tmpf, dwpf, sknt, drct, alti, relh, random() as r, coalesce(mslp, alti * 33.8639, 1013.25) as slp from alldata where station = %s and report_type = 2 and extract(hour from valid at time zone %s + '10 minutes'::interval) in (%s, %s)), agg as ( select *, extract(hour from v) as hour, rank() OVER (PARTITION by v ORDER by localvalid ASC, r ASC) from data ) SELECT *, date( case when hour = %s then date(v - '1 day'::interval) else date(v) end) from agg WHERE rank = 1 """, pgconn, params=( tzname, tzname, station, tzname, h1, h2, h2 if h2 < h1 else -1, ), index_col=None, ) if df.empty: raise NoDataFound("No data was found.") if varname == "q": df["pressure"] = mcalc.add_height_to_pressure( df["slp"].values * units("millibars"), ctx["_nt"].sts[station]["elevation"] * units("m"), ).to(units("millibar")) # compute mixing ratio df["q"] = (mcalc.mixing_ratio_from_relative_humidity( df["relh"].values * units("percent"), df["tmpf"].values * units("degF"), df["pressure"].values * units("millibars"), ) * 1000.0) # pivot df = df.pivot(index="date", columns="hour", values=varname).reset_index() df = df.dropna() df["doy"] = pd.to_numeric(pd.to_datetime(df["date"]).dt.strftime("%j")) df["year"] = pd.to_datetime(df["date"]).dt.year df["week"] = (df["doy"] / 7).astype(int) df["delta"] = df[h2] - df[h1] (fig, ax) = plt.subplots(1, 1) if ctx["opt"] == "no": ax.set_xlabel("Plotted lines are smoothed over %.0f days" % (ctx["smooth"], )) ax.set_ylabel( "%s %s Difference" % (PDICT[varname], "Accumulated Sum" if ctx["opt"] == "yes" else "")) if ctx["opt"] == "no": # Histogram H, xedges, yedges = np.histogram2d(df["doy"].values, df["delta"].values, bins=(50, 50)) ax.pcolormesh( xedges, yedges, H.transpose(), cmap=get_cmap(ctx["cmap"]), alpha=0.5, ) # Plot an average line gdf = (df.groupby("doy").mean().rolling(ctx["smooth"], min_periods=1, center=True).mean()) y = gdf["delta"] if ctx["opt"] == "no" else gdf["delta"].cumsum() ax.plot( gdf.index.values, y, label="Average", zorder=6, lw=2, color="k", linestyle="-.", ) # Plot selected year for i in range(1, 5): year = ctx.get("y%s" % (i, )) if year is None: continue df2 = df[df["year"] == year] if not df2.empty: gdf = (df2.groupby("doy").mean().rolling(ctx["smooth"], min_periods=1, center=True).mean()) y = gdf["delta"] if ctx["opt"] == "no" else gdf["delta"].cumsum() ax.plot(gdf.index.values, y, label=str(year), lw=2, zorder=10) ax.set_xticks((1, 32, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335)) ax.set_xticklabels(calendar.month_abbr[1:]) ax.set_xlim(1, 366) ax.grid(True) ax.legend(loc="best", ncol=5) sts = datetime.datetime(2000, 6, 1, h1) ets = datetime.datetime(2000, 6, 1, h2) title = ("%s [%s] %s Difference (%.0f-%.0f)\n" "%s minus %s (%s) (timezone: %s)") % ( ctx["_nt"].sts[station]["name"], station, PDICT[varname], df["year"].min(), df["year"].max(), ets.strftime("%-I %p"), sts.strftime("%-I %p"), "same day" if h2 > h1 else "previous day", tzname, ) fitbox(fig, title, 0.05, 0.95, 0.91, 0.99, ha="center") return fig, df
def _make_plot( station, df, units, nsector, rmax, hours, months, sname, level, bins, tzname, **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 tzname (str): Time zone this plot is produced in. Returns: matplotlib.Figure """ wu = WINDUNITS[units] # Filters the missing values df2 = df[df["drct"] >= 0] direction = df2["drct"].values * mpunits("degree") if "speed" in df2.columns: speed = df2["speed"].values * wu["units"] else: speed = df2["sknt"].values * mpunits("knots") if not hasattr(bins, "units"): bins = wu["bins"] * wu["units"] if level is not None: bins = RAOB_BINS[units] * wu["units"] if len(df2.index) < 5: wp = WindrosePlot() wp.ax.text( 0.5, 0.5, "Not Enough Data For Plot.", ha="center", transform=wp.ax.transAxes, ) return wp.fig wp = plot(direction, speed, bins=bins, nsector=nsector, rmax=rmax) # Now we put some fancy debugging info on the plot tlimit = "[Time Domain: " if len(hours) == 24 and len(months) == 12: tlimit = "" if len(hours) < 24: if len(hours) > 4: tlimit += "%s-%s" % ( datetime(2000, 1, 1, hours[0]).strftime("%-I %p"), datetime(2000, 1, 1, hours[-1]).strftime("%-I %p"), ) else: for h in hours: tlimit += "%s," % (datetime(2000, 1, 1, h).strftime("%-I %p"),) if len(months) < 12: for h in months: tlimit += "%s," % (datetime(2000, h, 1).strftime("%b"),) if tlimit != "": tlimit += "]" label = ("[%s] %s%s\n" "Windrose Plot %s\n" "Time Bounds: %s") % ( station, sname if sname is not None else "((%s))" % (station,), "" if level is None else " @%s hPa" % (level,), tlimit, _time_domain_string(df, tzname), ) fitbox(wp.fig, label, 0.14, 0.99, 0.92, 0.99, ha="left") label = ("Summary\nobs count: %s\nMissing: %s\nAvg Speed: %.1f %s") % ( len(df.index), len(df.index) - len(df2.index), speed.m.mean(), units, ) wp.fig.text(0.96, 0.11, label, ha="right", fontsize=14) if not kwargs.get("nogenerated", False): wp.fig.text( 0.02, 0.1, "Generated: %s" % (datetime.now().strftime("%d %b %Y"),), verticalalignment="bottom", fontsize=14, ) # Denote the direction blowing from lbl = ("Calm values are < %.1f %s\nArrows indicate wind direction.") % ( bins.m[0], units, ) wp.fig.text(0.02, 0.125, lbl, va="bottom") return wp.fig