Source code for pyinterpolate.semivariogram.experimental.classes.experimental_variogram

from typing import Union, Collection
from numpy.typing import ArrayLike

import numpy as np
from prettytable import PrettyTable

from pyinterpolate.semivariogram.experimental.experimental_covariogram import calculate_covariance
from pyinterpolate.core.data_models.experimental_variogram import \
    ExperimentalVariogramModel
from pyinterpolate.core.data_models.points import VariogramPoints
from pyinterpolate.core.validators.experimental_semivariance import \
    validate_plot_attributes_for_experimental_variogram
from pyinterpolate.semivariogram.experimental.experimental_semivariogram import \
    point_cloud_semivariance, calculate_semivariance


[docs] class ExperimentalVariogram: """ Class calculates Experimental Semivariogram and Experimental Covariogram of a given dataset. Parameters ---------- ds : numpy array ``[x, y, value]`` values : ArrayLike, optional Observation in the i-th geometry (from ``geometries``). Optional parameter, if not given then ``ds`` must be provided. geometries : ArrayLike, optional Array or similar structure with geometries. It must have the same length as ``values``. Optional parameter, if not given then ``ds`` must be provided. Point type geometry. step_size : float The fixed distance between lags grouping point neighbors. max_range : float The maximum distance at which the semivariance is calculated. direction : float, optional Direction of semivariogram, values from 0 to 360 degrees: * 0 or 180: is E-W, * 90 or 270 is N-S, * 45 or 225 is NE-SW, * 135 or 315 is NW-SE. tolerance : float, optional If ``tolerance`` is 0 then points must be placed at a single line with the beginning in the origin of the coordinate system and the direction given by y-axis and direction parameter. If ``tolerance`` is ``> 0`` then the bin is selected as an elliptical area with major axis pointed in the same direction as the line for ``0`` tolerance. * The major axis size == ``step_size``. * The minor axis size is ``tolerance * step_size`` * The baseline point is at a center of the ellipse. * The ``tolerance == 1`` creates an omnidirectional semivariogram. custom_bins : numpy array, optional Custom bins for semivariance calculation. If provided, then parameter ``step_size`` is ignored and ``max_range`` is set to the final bin distance. custom_weights : numpy array, optional Custom weights assigned to points. Only semivariance values are weighted. drop_lags_without_pairs : bool, default=True Drops lags when there are no point pairs within. is_semivariance : bool, default=True Calculate experimental semivariance. is_covariance : bool, default=True Calculate experimental coviariance. as_cloud : bool Calculate semivariance point-pairs cloud. Attributes ---------- semivariances : numpy array 1-D array with semivariances ordered by lags. Methods ------- plot() Shows experimental variances. See Also -------- calculate_covariance : function to calculate experimental covariance calculate_semivariance : function to calculate experimental semivariance Examples -------- >>> import numpy as np >>> from pyinterpolate import ExperimentalVariogram >>> >>> >>> REFERENCE_INPUT = np.array([ ... [0, 0, 8], ... [1, 0, 6], ... [2, 0, 4], ... [3, 0, 3], ... [4, 0, 6], ... [5, 0, 5], ... [6, 0, 7], ... [7, 0, 2], ... [8, 0, 8], ... [9, 0, 9], ... [10, 0, 5], ... [11, 0, 6], ... [12, 0, 3] ... ]) >>> STEP_SIZE = 1 >>> MAX_RANGE = 4 >>> empirical_smv = ExperimentalVariogram( ... values=REFERENCE_INPUT[:, -1], ... geometries=REFERENCE_INPUT[:, :-1], ... step_size=STEP_SIZE, ... max_range=MAX_RANGE ... ) >>> print(empirical_smv) +-----+--------------------+---------------------+ | lag | semivariance | covariance | +-----+--------------------+---------------------+ | 1.0 | 4.625 | -0.5434027777777798 | | 2.0 | 5.2272727272727275 | -0.7954545454545454 | | 3.0 | 6.0 | -1.2599999999999958 | +-----+--------------------+---------------------+ """ def __init__(self, ds: Union[ArrayLike, VariogramPoints] = None, values: ArrayLike = None, geometries: ArrayLike = None, step_size: float = None, max_range: float = None, direction: float = None, tolerance: float = None, custom_bins: Union[np.ndarray, Collection] = None, custom_weights: np.ndarray = None, drop_lags_without_pairs: bool = True, is_semivariance: bool = True, is_covariance: bool = True, as_cloud: bool = False): # Validate points if not isinstance(ds, VariogramPoints): ds = VariogramPoints(points=ds, geometries=geometries, values=values) ds = ds.points else: ds = ds.points self.ds = ds # core structure # Object main attributes if custom_bins is None: self.lags = None else: self.lags = custom_bins self.points_per_lag = None self.semivariances = None self.covariances = None # point cloud self.point_cloud_semivariances = None self.variance = np.var(self.ds[:, -1]) self.step_size = step_size self.max_range = max_range self.custom_weights = custom_weights self.direction = direction self.tolerance = tolerance self.as_cloud = as_cloud self.__drop_lags = drop_lags_without_pairs self.__c_sem = is_semivariance self.__c_cov = is_covariance if is_semivariance: self._calculate_semivariance() if is_covariance: self._calculate_covariance() if as_cloud: self._calculate_semivariance_point_cloud() if drop_lags_without_pairs: self._drop_lags_without_point_pairs() # update base model self.model = self.get_model_params() @property def lag_semivariance_array(self): ls_array = np.vstack((self.lags, self.semivariances)).T return ls_array def get_model_params(self): model_parameters = { "lags": self.lags, "points_per_lag": self.points_per_lag, "semivariances": self.semivariances, "covariances": self.covariances, "variance": self.variance, "direction": self.direction, "tolerance": self.tolerance, "max_range": self.max_range, "step_size": self.step_size, "custom_weights": self.custom_weights } return ExperimentalVariogramModel(**model_parameters)
[docs] def plot(self, semivariance=True, covariance=True, variance=True) -> None: """ Plots semivariance, covariance, and variance. Parameters ---------- semivariance : bool, default=True Show semivariance on a plot. If class attribute ``is_semivariance`` is set to ``False`` then semivariance is not plotted and warning is printed. covariance : bool, default=True Show covariance on a plot. If class attribute ``is_covariance`` is set to ``False`` then covariance is not plotted and warning is printed. variance : bool, default=True Show variance level on a plot. Warns ----- AttributeSetToFalseWarning Warning invoked when plotting parameter for semivariance, covariance or variance is set to ``True`` but class attributes to calculate those indices are set to ``False``. """ import matplotlib.pyplot as plt # Validate parameters validate_plot_attributes_for_experimental_variogram( is_semivar=self.__c_sem, is_covar=self.__c_cov, plot_semivar=semivariance, plot_covar=covariance) # Plot # Cmap - 3 class Set2 # https://colorbrewer2.org/#type=qualitative&scheme=Set2&n=3 # Colorblind friendly # Print friendly legend = [] plt.figure(figsize=(12, 6)) if semivariance and self.__c_sem: plt.scatter(self.lags, self.semivariances, marker='8', c='#66c2a5') legend.append('Experimental Semivariances') if covariance and self.__c_cov: plt.scatter(self.lags, self.covariances, marker='+', c='#8da0cb') legend.append('Experimental Covariances') if variance: var_line = [self.variance for _ in self.lags] plt.plot(self.lags, var_line, '--', color='#fc8d62') legend.append('Variance') plt.legend(legend) plt.xlabel('Distance') plt.ylabel('Variance') plt.show()
def _calculate_covariance(self): """ Method calculates covariance and variance. See : ``calculate_covariance`` function. """ experimental_covariance_array = calculate_covariance( ds=self.ds, step_size=self.step_size, max_range=self.max_range, direction=self.direction, tolerance=self.tolerance, custom_bins=self.lags ) if self.lags is None: self.lags = experimental_covariance_array[:, 0] if self.points_per_lag is None: self.points_per_lag = experimental_covariance_array[:, -1] self.covariances = experimental_covariance_array[:, 1] def _calculate_semivariance_point_cloud(self): """ Method calculates point cloud semivariance. See: ``calculate_semivariance`` function. """ experimental_cloud = point_cloud_semivariance( ds=self.ds, step_size=self.step_size, max_range=self.max_range, direction=self.direction, tolerance=self.tolerance, custom_bins=self.lags ) self.point_cloud_semivariances = experimental_cloud def _calculate_semivariance(self): """ Method calculates semivariance. See: ``calculate_semivariance`` function. """ experimental_semivariance_array = calculate_semivariance( ds=self.ds, step_size=self.step_size, max_range=self.max_range, direction=self.direction, tolerance=self.tolerance, custom_bins=self.lags, custom_weights=self.custom_weights ) if self.lags is None: self.lags = experimental_semivariance_array[:, 0] if self.points_per_lag is None: self.points_per_lag = experimental_semivariance_array[:, -1] self.semivariances = experimental_semivariance_array[:, 1] def _drop_lags_without_point_pairs(self): """ Method removes all semivariances, covariances, and number of points in each lag if value at the specific lag is None or NaN (no point pairs for that lag). """ if self.lags is None: pass else: sem_not_nan = None cov_not_nan = None lags_trimmed = False if self.__c_sem: sem_not_nan = ~np.isnan(self.semivariances) if self.__c_cov: cov_not_nan = ~np.isnan(self.covariances) if sem_not_nan is not None: self.semivariances = self.semivariances[sem_not_nan] self.lags = self.lags[sem_not_nan] self.points_per_lag = self.points_per_lag[sem_not_nan] lags_trimmed = True if cov_not_nan is not None: self.covariances = self.covariances[cov_not_nan] if not lags_trimmed: self.lags = self.lags[cov_not_nan] self.points_per_lag = self.points_per_lag[cov_not_nan] def __repr__(self): """ ds: np.ndarray, step_size: float = None, max_range: float = None, direction: float = None, tolerance: float = None, dir_neighbors_selection_method: str = 't', custom_bins: Union[np.ndarray, Collection] = None, custom_weights: np.ndarray = None, is_semivariance=True, is_covariance=True """ cname = 'ExperimentalVariogram' # NoneType has no tolist() custom_weights = ( self.custom_weights.tolist() if self.custom_weights is not None else "None" ) input_params = (f'ds={self.ds.tolist()},' f'step_size={self.step_size},' f'max_range={self.max_range},' f'direction={self.direction},' f'tolerance={self.tolerance},' f'custom_bins={self.lags.tolist()},' f'custom_weights={custom_weights},' f'is_semivariance={self.__c_sem},' f'is_covariance={self.__c_cov}') repr_val = cname + '(' + input_params + ')' return repr_val def __str__(self): pretty_table = PrettyTable() pretty_table.field_names = ["lag", "semivariance", "covariance"] if not self.__c_sem and not self.__c_cov: return self.__str_empty() else: if self.__c_sem and self.__c_cov: pretty_table.add_rows(self.__str_populate_both()) else: pretty_table.add_rows(self.__str_populate_single()) return pretty_table.get_string() def __str_empty(self): return (f"Variance: {self.variance:.4f}. " f"Other parameters not calculated yet.") def __str_populate_both(self): rows = [] for idx, row in enumerate(self.semivariances): lag = self.lags[idx] smv = row cov = self.covariances[idx] rows.append([lag, smv, cov]) return rows def __str_populate_single(self): rows = [] if self.__c_cov: for idx, row in enumerate(self.covariances): lag = self.lags[idx] cov = row rows.append([lag, np.nan, cov]) else: for idx, row in enumerate(self.semivariances): lag = self.lags[idx] sem = row rows.append([lag, sem, np.nan]) return rows
[docs] def build_experimental_variogram(ds: Union[ArrayLike, VariogramPoints] = None, values: ArrayLike = None, geometries: ArrayLike = None, step_size: float = None, max_range: float = None, direction: float = None, tolerance: float = None, custom_bins: Union[ np.ndarray, Collection ] = None, custom_weights: np.ndarray = None, is_semivariance=True, is_covariance=True, as_cloud=False): """ Function is an alias to ``ExperimentalVariogram()``. Parameters ---------- ds : numpy array ``[x, y, value]`` values : ArrayLike, optional Observation in the i-th geometry (from ``geometries``). Optional parameter, if not given then ``ds`` must be provided. geometries : ArrayLike, optional Array or similar structure with geometries. It must have the same length as ``values``. Optional parameter, if not given then ``ds`` must be provided. Point type geometry. step_size : float The fixed distance between lags grouping point neighbors. max_range : float The maximum distance at which the semivariance is calculated. direction : float, optional Direction of semivariogram, values from 0 to 360 degrees: - 0 or 180: is E-W, - 90 or 270 is N-S, - 45 or 225 is NE-SW, - 135 or 315 is NW-SE. tolerance : float, optional If ``tolerance`` is 0 then points must be placed at a single line with the beginning in the origin of the coordinate system and the direction given by y-axis and direction parameter. If ``tolerance`` is ``> 0`` then the bin is selected as an elliptical area with major axis pointed in the same direction as the line for ``0`` tolerance. * The major axis size == ``step_size``. * The minor axis size is ``tolerance * step_size`` * The baseline point is at a center of the ellipse. * The ``tolerance == 1`` creates an omnidirectional semivariogram. custom_bins : numpy array, optional Custom bins for semivariance calculation. If provided, then parameter ``step_size`` is ignored and ``max_range`` is set to the final bin distance. custom_weights : numpy array, optional Custom weights assigned to points. Only semivariance values are weighted. is_semivariance : bool, default=True Calculate experimental semivariance. is_covariance : bool, default=True Calculate experimental coviariance. as_cloud : bool Calculate semivariance point-pairs cloud. Returns ------- : ExperimentalVariogram Examples -------- >>> import numpy as np >>> from pyinterpolate import build_experimental_variogram >>> >>> >>> REFERENCE_INPUT = np.array([ ... [0, 0, 8], ... [1, 0, 6], ... [2, 0, 4], ... [3, 0, 3], ... [4, 0, 6], ... [5, 0, 5], ... [6, 0, 7], ... [7, 0, 2], ... [8, 0, 8], ... [9, 0, 9], ... [10, 0, 5], ... [11, 0, 6], ... [12, 0, 3] ... ]) >>> STEP_SIZE = 1 >>> MAX_RANGE = 4 >>> empirical_smv = build_experimental_variogram( ... values=REFERENCE_INPUT[:, -1], ... geometries=REFERENCE_INPUT[:, :-1] ... step_size=STEP_SIZE, ... max_range=MAX_RANGE ... ) >>> print(empirical_smv) +-----+--------------------+---------------------+ | lag | semivariance | covariance | +-----+--------------------+---------------------+ | 1.0 | 4.625 | -0.5434027777777798 | | 2.0 | 5.2272727272727275 | -0.7954545454545454 | | 3.0 | 6.0 | -1.2599999999999958 | +-----+--------------------+---------------------+ """ exp_var = ExperimentalVariogram( ds=ds, values=values, geometries=geometries, step_size=step_size, max_range=max_range, direction=direction, tolerance=tolerance, custom_bins=custom_bins, custom_weights=custom_weights, is_semivariance=is_semivariance, is_covariance=is_covariance, as_cloud=as_cloud ) return exp_var