Source code for wradlib.verify

#!/usr/bin/env python
# Copyright (c) 2011-2020, wradlib developers.
# Distributed under the MIT License. See LICENSE.txt for more info.

"""
Verification
^^^^^^^^^^^^

Verification mainly refers to the comparison of radar-based precipitation
estimates to ground truth.

.. autosummary::
   :nosignatures:
   :toctree: generated/

   {}
"""
__all__ = ["ErrorMetrics", "PolarNeighbours"]
__doc__ = __doc__.format("\n   ".join(__all__))

import warnings
from pprint import pprint

import numpy as np
from scipy import spatial, stats

from wradlib import util
from wradlib.georef import polar


[docs]class PolarNeighbours: """For a set of projected point coordinates, extract the neighbouring bin \ values from a data set in polar coordinates. Use as follows: First, create an instance of PolarNeighbours by passing all the information needed to georeference the polar radar data to the points of interest (see parameters). Second, use the method *extract* in order to extract the values from a data array which corresponds to the polar coordinates. Parameters ---------- r : :class:`numpy:numpy.ndarray` (see :mod:`wradlib.georef` for documentation) az : :class:`numpy:numpy.ndarray` (see :mod:`wradlib.georef` for documentation) sitecoords : sequence sequence of floats (see :mod:`wradlib.georef` for documentation) proj : :py:class:`gdal:osgeo.osr.SpatialReference` GDAL OSR Spatial Reference Object describing projection (see :mod:`wradlib.georef` for documentation) x : :class:`numpy:numpy.ndarray` array of x coordinates of the points in map projection corresponding to proj y : :class:`numpy:numpy.ndarray` array of y coordinates of the points in map projection corresponding to proj nnear : int number of neighbouring radar bins you would like to find Examples -------- See :ref:`/notebooks/verification/wradlib_verify_example.ipynb`. """
[docs] def __init__(self, r, az, sitecoords, proj, x, y, nnear=9): self.nnear = nnear self.az = az self.r = r self.x = x self.y = y # compute the centroid coordinates in proj bin_coords = polar.spherical_to_centroids(r, az, 0, sitecoords, proj=proj) self.binx = bin_coords[..., 0].ravel() self.biny = bin_coords[..., 1].ravel() # compute the KDTree tree = spatial.KDTree(list(zip(self.binx, self.biny))) # query the tree for nearest neighbours self.dist, self.ix = tree.query(list(zip(x, y)), k=nnear)
def extract(self, vals): """Extracts the values from an array of shape (azimuth angles, \ range gages) which correspond to the indices computed during \ initialisation Parameters ---------- vals : :class:`numpy:numpy.ndarray` array of shape (..., number of azimuth, number of range gates) Returns ------- output : :class:`numpy:numpy.ndarray` array of shape (..., number of points, nnear) """ assert vals.ndim >= 2, ( "Your <vals> array should at least contain an " "azimuth and a range dimension." ) assert tuple(vals.shape[-2:]) == (len(self.az), len(self.r)), ( "The shape of your vals array does not correspond with " "the range and azimuths you provided for your polar data set" ) vals = vals.reshape(vals.shape[:-2] + (len(self.az) * len(self.r),)) return vals[..., self.ix] def get_bincoords(self): """Returns all bin coordinates in map projection Returns ------- output : tuple array of x coordinates, array of y coordinates """ return self.binx, self.biny def get_bincoords_at_points(self): """Returns bin coordinates only in the neighbourhood of points Returns ------- output : tuple array of x coordinates, array of y coordinates """ return self.binx[self.ix], self.biny[self.ix]
[docs]class ErrorMetrics: """Compute quality metrics from a set of observations (``obs``) and \ estimates (``est``). First create an instance of the class using the set of observations and estimates. Then compute quality metrics using the class methods. A dictionary of all available quality metrics is returned using the ``all`` method, or printed to the screen using the ``pprint`` method. The ``ix`` member variable indicates valid pairs of ``obs`` and ``est``, based on NaNs and ``minval``. Parameters ---------- obs: :class:`numpy:numpy.ndarray` array of observations (e.g. rain gage observations) est: :class:`numpy:numpy.ndarray` array of estimates (e.g. radar, adjusted radar, ...) minval : float threshold value in order to compute metrics only for values larger than minval Examples -------- >>> obs = np.random.uniform(0, 10, 100) >>> est = np.random.uniform(0, 10, 100) >>> metrics = ErrorMetrics(obs, est) >>> metrics.all() #doctest: +SKIP >>> metrics.pprint() #doctest: +SKIP >>> metrics.ix #doctest: +SKIP See :ref:`/notebooks/verification/wradlib_verify_example.ipynb` and :ref:`/notebooks/multisensor/wradlib_adjust_example.ipynb`. """
[docs] def __init__(self, obs, est, minval=None): # Check input if len(obs) != len(est): raise ValueError( "WRADLIB: obs and est need to have the " f"same length. len(obs)={len(obs)}, " f"len(est)={len(est)}" ) self.est = est self.obs = obs # remember those pairs which both have valid obs and est self.ix = np.intersect1d( util._idvalid(obs, minval=minval), util._idvalid(est, minval=minval) ) self.n = len(self.ix) if self.n == 0: warnings.warn( "WRADLIB: No valid pairs of observed and " "estimated available for ErrorMetrics!" ) self.resids = self.est[self.ix] - self.obs[self.ix]
def corr(self): """Correlation coefficient""" return np.round(np.corrcoef(self.obs[self.ix], self.est[self.ix])[0, 1], 2) def r2(self): """Coefficient of determination""" return np.round( (np.corrcoef(self.obs[self.ix], self.est[self.ix])[0, 1]) ** 2, 2 ) def spearman(self): """Spearman rank correlation coefficient""" return np.round(stats.spearmanr(self.obs[self.ix], self.est[self.ix])[0], 2) def nash(self): """Nash-Sutcliffe Efficiency""" return np.round(1.0 - (self.mse() / np.var(self.obs[self.ix])), 2) def sse(self): """Sum of Squared Errors""" return np.round(np.sum(self.resids**2), 2) def mse(self): """Mean Squared Error""" return np.round(self.sse() / self.n, 2) def rmse(self): """Root Mean Squared Error""" return np.round(self.mse() ** 0.5, 2) def mas(self): """Mean Absolute Error""" return np.round(np.mean(np.abs(self.resids)), 2) def meanerr(self): """Mean Error""" return np.round(np.mean(self.resids), 2) def ratio(self): """Mean ratio between observed and estimated""" return np.round(np.mean(self.est[self.ix] / self.obs[self.ix]), 2) def pbias(self): """Percent bias""" return np.round(self.meanerr() * 100.0 / np.mean(self.obs[self.ix]), 1) def all(self): """Returns a dictionary of all error metrics""" out = { "corr": self.corr(), "r2": self.r2(), "spearman": self.spearman(), "nash": self.nash(), "sse": self.sse(), "mse": self.mse(), "rmse": self.rmse(), "mas": self.mas(), "meanerr": self.meanerr(), "ratio": self.ratio(), "pbias": self.pbias(), } return out def pprint(self): """Pretty prints a summary of error metrics""" pprint(self.all())
if __name__ == "__main__": print("wradlib: Calling module <verify> as main...")