"""
This module contains scatter plot routines for Aerocom data.
"""
import matplotlib.pyplot as plt
import numpy as np
from pyaerocom import const
from pyaerocom._warnings import ignore_warnings
from pyaerocom.helpers import start_stop_str
from pyaerocom.mathutils import exponent
from pyaerocom.stats.stats import calculate_statistics
[docs]
def plot_scatter(x_vals, y_vals, **kwargs):
"""Scatter plot
Currently a wrapper for high-level method plot_scatter_aerocom (same module,
see there for details)
"""
return plot_scatter_aerocom(x_vals, y_vals, **kwargs)
[docs]
def plot_scatter_aerocom(
x_vals,
y_vals,
var_name=None,
var_name_ref=None,
x_name=None,
y_name=None,
start=None,
stop=None,
ts_type=None,
unit=None,
stations_ok=None,
filter_name=None,
lowlim_stats=None,
highlim_stats=None,
loglog=None,
ax=None,
figsize=None,
fontsize_base=11,
fontsize_annot=None,
marker=None,
color=None,
alpha=0.5,
**kwargs,
):
"""Method that performs a scatter plot of data in AEROCOM format
Parameters
----------
y_vals : ndarray
1D array (or list) of model data points (y-axis)
x_vals : ndarray
1D array (or list) of observation data points (x-axis)
var_name : :obj:`str`, optional
name of variable that is plotted
var_name_ref : :obj:`str`, optional
name of variable of reference data
x_name : :obj:`str`, optional
Name of observation network
y_name : :obj:`str`, optional
Name / ID of model
start : :obj:`str` or :obj`datetime` or similar
start time of data
stop : :obj:`str` or :obj`datetime` or similar
stop time of data
ts_type : str
frequency of data
unit : str, optional
unit of data
stations_ok : int, optional
number of stations from which data were generated
filter_name : str, optional
name of filter
lowlim_stats : float, optional
lower value considered for statistical parameters
highlim_stats : float, optional
upper value considered for statistical parameters
loglog : bool, optional
plot log log scale, if None, pyaerocom default is used
ax : Axes
axes into which the data are to be plotted
figsize : tuple
size of figure (if new figure is created, ie ax is None)
fontsize_base : int
basic fontsize, defaults to 11
fontsize_annot : int, optional
fontsize used for annotations
marker : str, optional
marker used for data, if None, '+' is used
color : str, optional
color of markers, default to 'k'
alpha : float, optional
transparency of markers (does not apply to all marker types),
defaults to 0.5.
**kwargs
additional keyword args passed to :func:`ax.plot`
Returns
-------
matplotlib.axes.Axes
plot axes
"""
if marker is None:
marker = "+"
if color is None:
color = "k"
if isinstance(y_vals, list):
y_vals = np.asarray(y_vals)
if isinstance(x_vals, list):
x_vals = np.asarray(x_vals)
try:
var = const.VARS[var_name]
except Exception:
var = const.VARS["DEFAULT"]
try:
var_ref = const.VARS[var_name_ref]
except Exception:
var_ref = const.VARS["DEFAULT"]
if loglog is None:
loglog = var_ref.scat_loglog
xlim = var["scat_xlim"]
ylim = var_ref["scat_ylim"]
if xlim is None or ylim is None:
low = np.min([np.nanmin(x_vals), np.nanmin(y_vals)])
high = np.max([np.nanmax(x_vals), np.nanmax(y_vals)])
xlim = [low, high]
ylim = [low, high]
if ax is None:
if figsize is None:
figsize = (10, 8)
fig, ax = plt.subplots(figsize=figsize)
if var_name is None:
var_name = "n/d"
statistics = calculate_statistics(y_vals, x_vals, lowlim=lowlim_stats, highlim=highlim_stats)
if loglog:
ax.loglog(x_vals, y_vals, ls="none", color=color, marker=marker, alpha=alpha, **kwargs)
else:
ax.plot(x_vals, y_vals, ls="none", color=color, marker=marker, alpha=alpha, **kwargs)
try:
title = start_stop_str(start, stop, ts_type)
if ts_type is not None:
title += f" ({ts_type})"
except Exception:
title = ""
if not loglog:
xlim[0] = 0
ylim[0] = 0
elif any(x[0] < 0 for x in [xlim, ylim]):
low = np.nanmin(y_vals)
if low != 0:
low = 10 ** (float(exponent(abs(low)) - 1))
xlim[0] = low
ylim[0] = low
with ignore_warnings(
UserWarning,
"Attempted to set non-positive left xlim on a log-scaled axis",
"Attempt to set non-positive xlim on a log-scaled axis will be ignored.",
):
ax.set_xlim(xlim)
with ignore_warnings(
UserWarning,
"Attempted to set non-positive bottom ylim on a log-scaled axis",
"Attempt to set non-positive ylim on a log-scaled axis will be ignored.",
):
ax.set_ylim(ylim)
xlbl = f"{x_name}"
if var_name_ref is not None:
xlbl += f" ({var_name_ref})"
ax.set_xlabel(xlbl, fontsize=fontsize_base + 4)
ax.set_ylabel(f"{y_name}", fontsize=fontsize_base + 4)
ax.set_title(title, fontsize=fontsize_base + 4)
ax.tick_params(labelsize=fontsize_base)
ax.plot(xlim, ylim, "--", color="#cccccc")
xypos = {
"var_info": (0.01, 0.95),
"refdata_mean": (0.01, 0.90),
"data_mean": (0.01, 0.86),
"nmb": (0.01, 0.82),
"mnmb": (0.35, 0.82),
"R": (0.01, 0.78),
"rms": (0.35, 0.78),
"R_kendall": (0.01, 0.74),
"fge": (0.35, 0.74),
"ts_type": (0.8, 0.1),
"filter_name": (0.8, 0.06),
}
var_str = var_name
_ndig = abs(exponent(statistics["refdata_mean"]) - 2)
if unit is None:
unit = "N/D"
if not str(unit) in ["1", "no_unit"]:
var_str += f" [{unit}]"
if fontsize_annot is None:
fontsize_annot = fontsize_base
ax.annotate(
f"{var_str} #: {statistics['num_valid']} # st: {stations_ok}",
xy=xypos["var_info"],
xycoords="axes fraction",
fontsize=fontsize_annot + 4,
color="red",
)
ax.annotate(
f"Mean (x-data): {statistics['refdata_mean']:.{_ndig}f}; "
f"Rng: [{np.nanmin(x_vals):.{_ndig}f}, {np.nanmax(x_vals):.{_ndig}f}]",
xy=xypos["refdata_mean"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"Mean (y-data): {statistics['data_mean']:.{_ndig}f}; "
f"Rng: [{np.nanmin(y_vals):.{_ndig}f}, {np.nanmax(y_vals):.{_ndig}f}]",
xy=xypos["data_mean"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"NMB: {statistics['nmb']*100:.1f}%",
xy=xypos["nmb"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"MNMB: {statistics['mnmb']*100:.1f}%",
xy=xypos["mnmb"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"R (Pearson): {statistics['R']:.3f}",
xy=xypos["R"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"RMS: {statistics['rms']:.3f}",
xy=xypos["rms"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"R (Kendall): {statistics['R_kendall']:.3f}",
xy=xypos["R_kendall"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
ax.annotate(
f"FGE: {statistics['fge']:.1f}",
xy=xypos["fge"],
xycoords="axes fraction",
fontsize=fontsize_annot,
color="red",
)
# right lower part
ax.annotate(
f"{ts_type}",
xy=xypos["ts_type"],
xycoords="axes fraction",
ha="center",
fontsize=fontsize_annot,
color="black",
)
ax.annotate(
f"{filter_name}",
xy=xypos["filter_name"],
xycoords="axes fraction",
ha="center",
fontsize=fontsize_annot,
color="black",
)
ax.set_aspect("equal")
return ax