Skip to content

Commit ce86017

Browse files
authored
Adding optional data output for seaborn_diag.py (#4144)
1 parent 565232c commit ce86017

3 files changed

Lines changed: 118 additions & 2 deletions

File tree

esmvaltool/diag_scripts/seaborn_diag.py

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,14 @@
7676
indices. This avoids the deletion of coordinate information if different
7777
groups of datasets have different dimensions but increases the memory
7878
footprint of this diagnostic.
79+
write_netcdf: bool, optional (default: False)
80+
Output netCDF file for plotted data. What is written into the file
81+
is decided based on the pandas data frame and the
82+
seaborn kwargs: "x", "y", "hue" and "col".
83+
Because there is no direct way to write panda data frames into netCDF
84+
and to make data CF complient, the data frame is converted to an iris
85+
CubeList first. This is not possible for all data frames and often
86+
reset_index: true is required. Therefore the default is set to False.
7987
savefig_kwargs: dict, optional
8088
Optional keyword arguments for :func:`matplotlib.pyplot.savefig`. By
8189
default, uses ``bbox_inches: tight, dpi: 300, orientation: landscape``.
@@ -108,14 +116,18 @@
108116
import iris
109117
import iris.pandas
110118
import matplotlib.pyplot as plt
119+
import numpy as np
111120
import pandas as pd
112121
import seaborn as sns
122+
from iris.cube import CubeList
113123
from matplotlib.colors import LogNorm, Normalize
114124

115125
from esmvaltool.diag_scripts.shared import (
116126
ProvenanceLogger,
127+
get_diagnostic_filename,
117128
get_plot_filename,
118129
group_metadata,
130+
io,
119131
run_diagnostic,
120132
)
121133

@@ -199,6 +211,10 @@ def _create_plot(
199211
[0.83, pos_joint_ax.y0, 0.07, pos_joint_ax.height]
200212
)
201213

214+
# Save plot data
215+
if cfg["write_netcdf"]:
216+
_save_nc_data(data_frame, cfg)
217+
202218
# Save plot
203219
plot_path = get_plot_filename(cfg["plot_filename"], cfg)
204220
plt.savefig(plot_path, **cfg["savefig_kwargs"])
@@ -213,8 +229,16 @@ def _create_plot(
213229
"authors": ["schlund_manuel"],
214230
"caption": caption,
215231
}
216-
with ProvenanceLogger(cfg) as provenance_logger:
217-
provenance_logger.log(plot_path, provenance_record)
232+
if cfg["write_netcdf"]:
233+
with ProvenanceLogger(cfg) as provenance_logger:
234+
provenance_logger.log(plot_path, provenance_record)
235+
provenance_logger.log(
236+
get_diagnostic_filename(cfg["plot_filename"], cfg),
237+
provenance_record,
238+
)
239+
else:
240+
with ProvenanceLogger(cfg) as provenance_logger:
241+
provenance_logger.log(plot_path, provenance_record)
218242

219243

220244
def _get_grouped_data(cfg: dict) -> dict:
@@ -399,6 +423,7 @@ def _get_default_cfg(cfg: dict) -> dict:
399423
cfg.setdefault("plot_object_methods", {})
400424
cfg.setdefault("plot_filename", f"seaborn_{cfg.get('seaborn_func', '')}")
401425
cfg.setdefault("reset_index", False)
426+
cfg.setdefault("write_netcdf", False)
402427
cfg.setdefault(
403428
"savefig_kwargs",
404429
{
@@ -434,6 +459,13 @@ def _get_plot_func(cfg: dict) -> callable:
434459
return getattr(sns, cfg["seaborn_func"])
435460

436461

462+
def _is_strictly_monotonic(arr):
463+
"""Test if np.array is strictly monotonic."""
464+
result = np.all(np.diff(arr) > 0) | np.all(np.diff(arr) < 0)
465+
466+
return result
467+
468+
437469
def _modify_dataframe(data_frame: pd.DataFrame, cfg: dict) -> pd.DataFrame:
438470
"""Modify data frame according to the option ``data_frame_ops``."""
439471
allowed_funcs = ("query", "eval")
@@ -463,6 +495,76 @@ def _modify_dataframe(data_frame: pd.DataFrame, cfg: dict) -> pd.DataFrame:
463495
return data_frame
464496

465497

498+
def _prepare_cube(cube, cubes_to_aux, cubes_to_coord, cfg):
499+
"""Prepare cube to save data as netCDF."""
500+
cube.attributes.globals["seaborn_func"] = cfg["seaborn_func"]
501+
cube.attributes.globals["seaborn_kwargs"] = _get_str_from_kwargs(
502+
cfg["seaborn_kwargs"]
503+
)
504+
for auxcube in cubes_to_aux:
505+
aux_coord = iris.coords.AuxCoord(
506+
auxcube.data, long_name=auxcube.long_name
507+
)
508+
# Add the auxiliary coordinate to the cube
509+
cube.add_aux_coord(aux_coord, data_dims=0)
510+
511+
for dimcube in cubes_to_coord:
512+
dim_coord = iris.coords.DimCoord(
513+
dimcube.data, long_name=dimcube.long_name
514+
)
515+
# Add the auxiliary coordinate to the cube
516+
cube.add_dim_coord(dim_coord, data_dims=0)
517+
518+
return cube
519+
520+
521+
def _save_nc_data(dframe: pd.DataFrame, cfg) -> None:
522+
"""Save netCDF files for plot."""
523+
cubes_to_save = CubeList()
524+
cubes_to_aux = CubeList()
525+
cubes_to_coord = CubeList()
526+
527+
strings_to_save = []
528+
for key in cfg["seaborn_kwargs"]:
529+
if key in ["x", "y", "hue", "col"]:
530+
strings_to_save.append(cfg["seaborn_kwargs"][key])
531+
532+
for something in dframe:
533+
if something in strings_to_save:
534+
testcube = iris.pandas.as_cubes(dframe[something])[0]
535+
testcube.var_name = something
536+
testcube.remove_coord("unknown")
537+
538+
if something in UNITS:
539+
testcube.units = UNITS[something]
540+
541+
if something in ["shape_id", "dataset", "alias"]:
542+
testcube.data = testcube.data.astype(str)
543+
cubes_to_aux.append(testcube)
544+
elif something in [
545+
"latitude",
546+
"longitude",
547+
"height",
548+
"plev",
549+
"time",
550+
] and _is_strictly_monotonic(testcube.data):
551+
cubes_to_coord.append(testcube)
552+
else:
553+
cubes_to_save.append(testcube)
554+
555+
for cube in cubes_to_save:
556+
cube = _prepare_cube(
557+
cube,
558+
cubes_to_aux,
559+
cubes_to_coord,
560+
cfg,
561+
)
562+
563+
io.iris_save(
564+
cubes_to_save, get_diagnostic_filename(cfg["plot_filename"], cfg)
565+
)
566+
567+
466568
def _set_legend_title(plot_obj, legend_title: str) -> None:
467569
"""Set legend title."""
468570
if hasattr(plot_obj, "get_legend"): # Axes

esmvaltool/recipes/recipe_seaborn.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ diagnostics:
7272
plot:
7373
script: seaborn_diag.py
7474
seaborn_func: relplot
75+
reset_index: true
76+
write_netcdf: true
7577
seaborn_kwargs:
7678
x: latitude
7779
y: zonal_mean_ta
@@ -117,6 +119,8 @@ diagnostics:
117119
plot:
118120
script: seaborn_diag.py
119121
seaborn_func: displot
122+
reset_index: true
123+
write_netcdf: true
120124
seaborn_kwargs:
121125
kind: hist
122126
stat: density

esmvaltool/recipes/ref/recipe_ref_trend_regions.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,8 @@ diagnostics:
174174
plot:
175175
script: seaborn_diag.py
176176
seaborn_func: barplot
177+
reset_index: true
178+
write_netcdf: true
177179
seaborn_kwargs:
178180
x: shape_id
179181
y: tas
@@ -205,6 +207,8 @@ diagnostics:
205207
plot:
206208
script: seaborn_diag.py
207209
seaborn_func: barplot
210+
reset_index: true
211+
write_netcdf: true
208212
seaborn_kwargs:
209213
x: shape_id
210214
y: pr
@@ -236,6 +240,8 @@ diagnostics:
236240
plot:
237241
script: seaborn_diag.py
238242
seaborn_func: barplot
243+
reset_index: true
244+
write_netcdf: true
239245
seaborn_kwargs:
240246
x: shape_id
241247
y: psl
@@ -266,6 +272,8 @@ diagnostics:
266272
plot:
267273
script: seaborn_diag.py
268274
seaborn_func: barplot
275+
reset_index: true
276+
write_netcdf: true
269277
seaborn_kwargs:
270278
x: shape_id
271279
y: ua
@@ -295,6 +303,8 @@ diagnostics:
295303
plot:
296304
script: seaborn_diag.py
297305
seaborn_func: barplot
306+
reset_index: true
307+
write_netcdf: true
298308
seaborn_kwargs:
299309
x: shape_id
300310
y: hus

0 commit comments

Comments
 (0)