Skip to content

Commit

Permalink
Improved CI and boxplot rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
pstradio committed Jun 4, 2021
1 parent c24f156 commit 9a97ad6
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 120 deletions.
171 changes: 65 additions & 106 deletions src/qa4sm_reader/plot_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import matplotlib.gridspec as gridspec
from matplotlib.patches import PathPatch
from matplotlib.patches import PathPatch, Patch
from cartopy import config as cconfig
import cartopy.feature as cfeature
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
Expand Down Expand Up @@ -403,7 +403,6 @@ def make_watermark(fig, placement=globals.watermark_pos, for_map=False, offset=0
else:
raise NotImplementedError


def _make_cbar(fig, im, cax, ref_short:str, metric:str, label=None):
"""
Make colorbar to use in plots
Expand Down Expand Up @@ -440,52 +439,6 @@ def _make_cbar(fig, im, cax, ref_short:str, metric:str, label=None):

return fig, im, cax

def _adjust_box_widths(
fig,
widths=[1, 0.4]
):
"""
Adjust the withs of a seaborn-generated boxplot for boxplots with CIs. Adapted from:
https://stackoverflow.com/questions/56838187/how-to-create-spacing-between-same-subgroup-in-seaborn-boxplot
Parameters
----------
fig: plt.Figure
widths: list
list with values of boxplot triplets widths (central width, CIs widths)
"""
# iterating through Axes instances
for ax in fig.axes:
n = 0
# iterating through axes artists:
for c in ax.get_children():
# searching for PathPatches
if isinstance(c, PathPatch):
# different width whether it's the metric or the CIs
if n in np.arange(1, 100, 3):
width = widths[0]
else:
width = widths[1]
# getting current width of box:
p = c.get_path()
verts = p.vertices
verts_sub = verts[:-1]
xmin = np.min(verts_sub[:, 0])
xmax = np.max(verts_sub[:, 0])
xmid = 0.5*(xmin+xmax)
xhalf = 0.5*(xmax - xmin)
# setting new width of box
xmin_new = xmid - width*xhalf
xmax_new = xmid + width*xhalf
verts_sub[verts_sub[:, 0] == xmin, 0] = xmin_new
verts_sub[verts_sub[:, 0] == xmax, 0] = xmax_new

# setting new width of median line
for l in ax.lines:
if np.all(l.get_xdata() == [xmin, xmax]):
l.set_xdata([xmin_new, xmax_new])
n += 1

def _CI_difference(fig, ax, ci):
"""
Insert the median value of the upper and lower CI difference
Expand Down Expand Up @@ -517,16 +470,31 @@ def _CI_difference(fig, ax, ci):
diff = ci_df["upper"] - ci_df["lower"]
ci_range = float(diff.mean())
ypos = float(ci_df["lower"].min())
ax.annotate("Mean CI\nRange:\n {:.2g}".format(ci_range),
xy = (xmin - 0.2, ypos),
horizontalalignment="center")
ax.annotate(
"Mean CI\nRange:\n {:.2g}".format(ci_range),
xy = (xmin - 0.2, ypos),
horizontalalignment="center"
)

def patch_styling(
box_dict,
facecolor
) -> None:
"""Define style of the boxplots"""
for n, (patch, median) in enumerate(zip(box_dict["boxes"], box_dict["medians"])):
patch.set(color="grey", facecolor=facecolor, linewidth=1.6, alpha=0.7)
median.set(color="grey", linewidth=1.6)
for (whis, caps) in zip(box_dict["whiskers"], box_dict["caps"]):
whis.set(color="grey", linewidth=1.6)
caps.set(color="grey", linewidth=1.6)

def boxplot(
df,
ci=None,
label=None,
figsize=None,
dpi=100,
spacing=0.35,
**kwargs
):
"""
Expand All @@ -543,12 +511,13 @@ def boxplot(
ci: list
list of Dataframes containing "upper" and "lower" CIs
label : str, optional
Label of the y axis, describing the metric. If None, a label is autogenerated from metadata.
The default is None.
Label of the y axis, describing the metric. The default is None.
figsize : tuple, optional
Figure size in inches. The default is globals.map_figsize.
dpi : int, optional
Resolution for raster graphic output. The default is globals.dpi.
spacing: float, optional.
Space between the central boxplot and the CIs. Default is 0.3
Returns
-------
Expand All @@ -558,69 +527,59 @@ def boxplot(
"""
values = df.copy()
# make plot
sns.set_style("whitegrid")
fig, ax = plt.subplots(figsize=figsize, dpi=dpi)
center_pos = np.arange(len(values.columns))*2
# styling
ticklabels = values.columns
if kwargs is None:
kwargs = {}
kwargs.update(patch_artist=True, return_type="dict")
# changes necessary to have confidence intervals in the plot
if ci: # todo: create overlapping plots rather than doing all with seaborn
# iterate the variables (by caption) and linearize the dataframe
to_concat = []
for Var, intervals in zip(df, ci):
display_order = []
var_values = df[Var].to_frame("values")
var_values["caption"] = Var
var_values["type"] = "Metric values"
display_order.insert(1, var_values)
# collect the upper/lower bound of CIs
for bound in intervals:
if bound == "upper":
type, pos = "Upper CI", 2
elif bound == "lower":
type, pos = "Lower CI", 0
int_values = intervals[bound].rename("values")
int_values = int_values.to_frame()
int_values["caption"] = Var
int_values["type"] = type
display_order.insert(pos, int_values)
to_concat.extend(display_order)
# create df with columns "values", "caption", "type"
df = pd.concat(to_concat, axis=0)
# add the kwywords to plot in triplets
triples = {
"x": "caption",
"y": "values",
"hue": "type",
"palette": (
"skyblue",
"white",
"tomato"
)
}
if kwargs:
kwargs.update(triples)
else:
kwargs = triples
if ci:
upper, lower = [], []
for n, intervals in enumerate(ci):
lower.append(intervals["lower"])
upper.append(intervals["upper"])
lower = pd.concat(lower, ignore_index=True, axis=1)
upper = pd.concat(upper, ignore_index=True, axis=1)
low = lower.boxplot(
positions=center_pos - spacing,
showfliers=False,
widths=0.15,
**kwargs
)
up = upper.boxplot(
positions=center_pos + spacing,
showfliers=False,
widths=0.15,
**kwargs
)
patch_styling(low, 'skyblue')
patch_styling(up, 'tomato')
# create plot
ax = sns.boxplot(
data=df,
ax=ax,
cen = values.boxplot(
positions=center_pos,
showfliers=False,
color='white',
width=0.15,
widths=0.3,
**kwargs
)
# improve boxplot appearence
ax.set(xticklabels=values.columns)
sns.despine() # remove ugly spines (=border around plot) right and top.
patch_styling(cen, 'white')
plt.xticks(center_pos, ticklabels)
if ci:
plt.setp(ax.artists, alpha=.7)
low_ci = Patch(color='skyblue', alpha=0.7, label='Lower CI')
up_ci = Patch(color='tomato', alpha=0.7, label='Upper CI')
#_CI_difference(fig, ax, ci)
_adjust_box_widths(fig)
ax.legend(bbox_to_anchor=(1, 1), fontsize=8)
ax.legend_.set_title(None)
ax.set(xlabel=None)
plt.legend(
handles=[low_ci, up_ci],
fontsize=8,
loc="best"
)
# provide y label
if label is not None:
ax.set_ylabel(label, weight='normal')
plt.ylabel(label, weight='normal')
plt.grid(axis='x')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)

return fig, ax

Expand Down
34 changes: 20 additions & 14 deletions src/qa4sm_reader/plotter.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def _box_stats(ds:pd.Series, med:bool=True, iqr:bool=True, count:bool=True) -> s

met_str = []
if med:
met_str.append('median: {:.3g}'.format(ds.median()))
met_str.append('Median: {:.3g}'.format(ds.median()))
if iqr:
met_str.append('IQR: {:.3g}'.format(iqr))
if count:
Expand Down Expand Up @@ -570,11 +570,13 @@ def boxplot_tc( # todo: set limits to show confidence intervals
if save_files:
return fnames

def mapplot_var(self, Var,
out_name:str=None,
out_types:str='png',
save_files:bool=False,
**plotting_kwargs) -> list:
def mapplot_var(
self, Var,
out_name:str=None,
out_types:str='png',
save_files:bool=False,
**plotting_kwargs
) -> list:
"""
Plots values to a map, using the values as color. Plots a scatterplot for
ISMN and a image plot for other input values.
Expand Down Expand Up @@ -633,10 +635,12 @@ def mapplot_var(self, Var,
else:
return fig, ax

def mapplot_metric(self, metric:str,
out_types:str='png',
save_files:bool=False,
**plotting_kwargs) -> list:
def mapplot_metric(
self, metric:str,
out_types:str='png',
save_files:bool=False,
**plotting_kwargs
) -> list:
"""
Mapplot for all variables for a given metric in the loaded file.
Expand Down Expand Up @@ -675,7 +679,12 @@ def mapplot_metric(self, metric:str,
if fnames:
return fnames

def plot_metric(self, metric:str, out_types:str='png', save_all:bool=True, **plotting_kwargs) -> tuple:
def plot_metric(
self, metric:str,
out_types:str='png',
save_all:bool=True,
**plotting_kwargs
) -> tuple:
"""
Plot and save boxplot and mapplot for a certain metric
Expand Down Expand Up @@ -706,6 +715,3 @@ def plot_metric(self, metric:str, out_types:str='png', save_all:bool=True, **plo
**plotting_kwargs)

return fnames_bplot, fnames_mapplot

# im = QA4SMImg("../../../../shares/home/Data4projects/qa4sm-reader/CIs/nc_from_TC_validation.nc")
# pl = QA4SMPlotter(im)

0 comments on commit 9a97ad6

Please sign in to comment.