Source code for pygeostat.statistics.utils

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Various tools for calculating statistics
"""
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
import numpy as np


[docs] def weighted_mean(var, wts): """Calculates the weighted mean""" return np.average(var, weights=wts)
[docs] def weighted_variance(var, wts): """Calculates the weighted variance""" return np.average((var - weighted_mean(var, wts))**2, weights=wts)
[docs] def weighted_skew(var, wts): """Calculates the weighted skewness""" return (np.average((var - weighted_mean(var, wts))**3, weights=wts) / weighted_variance(var, wts)**(1.5))
[docs] def weighted_kurtosis(var, wts): """Calculates the weighted skewness""" return (np.average((var - weighted_mean(var, wts))**4, weights=wts) / weighted_variance(var, wts)**(2))
[docs] def weighted_covariance(x, y, wt): """Calculates the weighted covariance""" return (np.average((x - weighted_mean(x, wt)) * (y - weighted_mean(y, wt)), weights=wt))
[docs] def weighted_correlation(x, y, wt): """Calculates the weighted correlation""" return (weighted_covariance(x, y, wt) / (np.sqrt(weighted_variance(x, wt)) * np.sqrt(weighted_variance(y, wt))))
[docs] def weighted_correlation_rank(x, y, wt): """Calculatees the weighted spearman rank correlation""" from scipy.stats import rankdata x = rankdata(x) y = rankdata(y) return weighted_correlation(x, y, wt)
[docs] def near_positive_definite(input_matrix): """This function uses R to calculate the nearest positive definite matrix within python. An installation of R with the library "Matrix" is required. The module rpy2 is also needed The only requirement is an input matrix. Can be either a pandas dataframe or numpy-array. Parameters: input_matrix: input numpy array or pandas dataframe, not numpy matrix Returns: (np.array): Nearest positive definite matrix as a numpy-array """ import pandas as pd import numpy as np from ..utility.logging import printerr import pygeostat as gs # Try and load r2py try: import rpy2.robjects as robjects from rpy2.robjects import r from rpy2.robjects.packages import importr except ImportError: printerr(("near_positive_definite could not be loaded. Please install the r2py library" " and the software R with the library 'Matrix' to enable it. Installation" " instructions can be found within pygeostat's documentation."), errtype='error') return # Convert input matrix to a numpy array if it is a pd.DataFrame if isinstance(input_matrix, pd.DataFrame): input_matrix = input_matrix.as_matrix() # Determine matrix shape dim = input_matrix.shape # Call matrix R library matcalc = importr("Matrix") # Convert numpy array to RObject then to R matrix pdmat = robjects.FloatVector(input_matrix.reshape((input_matrix.size))) pdmat = robjects.r.matrix(pdmat, nrow=dim[0], ncol=dim[1], byrow=True) # Calculate nearest positive definite matrix pdmat = matcalc.near_positive_definite(pdmat) # Convert calculated matrix to python string pdmat = pdmat[0] # Extract near_positive_definite matrix from R object pdmat = r.toString(pdmat) # Convert R binary object to a string pdmat = pdmat.r_repr() # Convert R string to python string pdmat = pdmat.replace('"', "") # Clean up string pdmat = pdmat.replace(' ', "") # Clean up string # Convert comma delimited string to list then to np array pdmat = [float(x) for x in pdmat.split(',')] pdmat = np.array(pdmat) pdmat = np.reshape(pdmat, dim) # Restore near_positive_definite matrix to the original input shape return pdmat
[docs] def accsim(truth, reals, pinc=0.05): """ Calculates the proportion of locations where the true value falls within symmetric p-PI intervals when completing a jackknife study. A portion of the data is excluded from the conditioning dataset and the excluded sample locations simulated values are then checked. .. seealso:: Pyrcz, M. J., & Deutsch, C. V. (2014). Geostatistical Reservoir Modeling (2nd ed.). New York, NY: Oxford University Press, p. 350-351. Arguments: truth: Tidy (long-form) 1D data where a single column containing the true values. A pandas dataframe/series or numpy array can be passed reals: Tidy (long-form) 2D data where a single column contains values from a single realizations and each row contains the simulated values from a single truth location. A pandas dataframe or numpy matrix can be passed Keyword Arguments: pinc (float): Increments between the probability intervals to calculate within (0, 1) Returns: propavg (pd.DataFrame): Dataframe with the calculated probability intervals and the fraction within the interval Returns: sumstats (dict): Dictionary containing the average variance (U), mean squared error (MSE), accuracy measure (acc), precision measure (pre), and a goodness measure (goo) """ import pandas as pd import pygeostat as gs # Handle input if isinstance(truth, pd.Series): truth = truth.values elif isinstance(truth, pd.DataFrame): truth = truth.values elif not isinstance(truth, np.ndarray): raise ValueError("The argument `truth` must be a pd.DataFrame, pd.Series, or np.matrix") if isinstance(truth, np.ndarray) and len(truth.shape) == 1: truth = np.reshape(truth, (truth.shape[0], 1)) if isinstance(reals, pd.DataFrame): reals = reals.values elif not isinstance(reals, np.ndarray): raise ValueError("The argument `reals` must be a pd.DataFrame or np.matrix") try: data = np.concatenate((truth, reals), axis=1) data = pd.DataFrame(data=data) except: raise ValueError("The `truth` and `reals` data could not be coerced into a pd.DataFrame") # Initialize some variables pints = np.arange(pinc, 1, pinc) propindic = dict([pint, []] for pint in pints) variances = [] acc = dict([pint, 0] for pint in pints) pre = dict([pint, 0] for pint in pints) goo = dict([pint, 0] for pint in pints) # Calculate the indicator responses and local variances for i, values in data.iterrows(): cdf = gs.cdf(values[1:].values) variances.append(np.var(values[1:].values)) for pint in pints: if cdf[0][0] <= values[0] <= cdf[0][-1]: p = gs.z_percentile(values[0], cdf[0], cdf[1]) plower = 0.5 - (pint / 2) pupper = 0.5 + (pint / 2) if plower <= p <= pupper: indic = 1 else: indic = 0 else: indic = 0 propindic[pint].append(indic) # Calculate the average proportions and average variance propavg = [] for pint in pints: avg = np.average(propindic[pint]) propavg.append([pint, avg]) propavg = pd.DataFrame(propavg, columns=['ProbInt', 'FracIn']) # Calculate the summary statistics avgvar = np.average(variances) mse = ((propavg['ProbInt'].values - propavg['FracIn'].values) ** 2).mean() acc = 0 pre = 0 goo = 0 for i, values in propavg.iterrows(): if values.iloc[1] >= values.iloc[0]: acc = acc + 1 pre = pre + (values.iloc[1] - values.iloc[0]) goo = goo + (values.iloc[1] - values.iloc[0]) else: goo = goo + (2 * (values.iloc[0] - values.iloc[1])) acc = acc / len(propavg) pre = 1 - ((2 * pre) / len(propavg)) goo = 1 - (goo / len(propavg)) sumstats = {'avgvar': avgvar, 'mse': mse, 'acc': acc, 'pre': pre, 'goo': goo} return propavg, sumstats
[docs] def accmik(truth, thresholds, mikprobs, pinc=0.05): """ Similar to accsim but accepting mik distributions instead Mostly pulled from accsim Parameters ---------- truth: np.ndarray Tidy (long-form) 1D data where a single column containing the true values. A pandas dataframe/series or numpy array can be passed thresholds: np.ndarray 1D array of thresholds where each CDF is defined by these thresholds and the probability given in the mikprobs array for each location. mikprobs: np.ndarray Tidy (long-form) 2D data where a single column contains values from a single MIK cutoff and each row contains the simulated values for the corresponding single truth location. A pandas dataframe or numpy matrix can be passed pinc: float Increments between the probability intervals to calculate within (0, 1) Returns ------- propavg: pd.DataFrame Dataframe with the calculated probability intervals and the fraction within the interval sumstats: dict Dictionary containing the average variance (U), mean squared error (MSE), accuracy measure (acc), precision measure (pre), and a goodness measure (goo) """ import pandas as pd # Handle input if isinstance(truth, pd.Series): truth = truth.values elif isinstance(truth, pd.DataFrame): truth = truth.values elif not isinstance(truth, np.ndarray): raise ValueError("The argument `truth` must be a pd.DataFrame, pd.Series, or np.matrix") if isinstance(truth, np.ndarray) and len(truth.shape) == 1: truth = np.reshape(truth, (truth.shape[0], 1)) if isinstance(mikprobs, pd.DataFrame): mikprobs = mikprobs.values elif not isinstance(mikprobs, np.ndarray): raise ValueError("The argument `mikprobs` must be a pd.DataFrame or np.matrix") # Initialize some variables pints, propindic, variances = _interval_responses(truth, mikprobs, pinc, cdf_x=thresholds) # Calculate the average proportions and average variance propavg = [] for pint in pints: avg = np.average(propindic[pint]) propavg.append([pint, avg]) propavg = pd.DataFrame(propavg, columns=['ProbInt', 'FracIn']) # Calculate the summary statistics avgvar = np.average(variances) mse = ((propavg['ProbInt'].values - propavg['FracIn'].values) ** 2).mean() acc = 0 pre = 0 goo = 0 for i, values in propavg.iterrows(): if values[1] >= values[0]: acc = acc + 1 pre = pre + (values[1] - values[0]) goo = goo + (values[1] - values[0]) else: goo = goo + (2 * (values[0] - values[1])) acc = acc / len(propavg) pre = 1 - ((2 * pre) / len(propavg)) goo = 1 - (goo / len(propavg)) sumstats = {'avgvar': avgvar, 'mse': mse, 'acc': acc, 'pre': pre, 'goo': goo} return propavg, sumstats
def _interval_responses(truth, reals, pinc, cdf_x=None): """ When cdf_x is None, reals contains the simulated values from which a cdf should be computed. Otherwise the ``'reals'`` contains the distribution F(cdf_x) values for each location (nloc, nquant) Mostly pulled from the original accsim Parameters: truth: np.ndarray tidy 1D array of truth values reals: np.ndarray tidy 2D array of `reals` where if cdf_x is None these are the realizations from which a cdf is built, otherwise cdf_x defines the z-values and each row of reals contains the corresponding probabilites defining the local cdf's pinc: float the incremement of the probability intervals cdf_x: np.ndarray, optional contains the z-values when ``reals`` contains the F(z) Returns: pints: np.ndarray a range of pinc spaced probability intervals propindic: dict the dictionary used in accsim and accmik functions variances: list the list of variances """ from .cdf import variance_from_cdf if not isinstance(truth, np.ndarray): truth = np.array(truth) isjagged = False if isinstance(reals, list) or reals.dtype == "O": # assume reals is `jagged` (nested lists), each location has a different # simulated vals isjagged = True elif not isinstance(reals, np.ndarray): reals = np.array(reals) if truth.shape[0] != reals.shape[0]: raise ValueError('`truth` and `reals` must have the same dimension along the first axis!') # initializse the variables pints = np.arange(pinc, 1, pinc) propindic = {pint: [] for pint in pints} variances = [] if cdf_x is not None and reals[0, 0] != 0: reals = np.c_[np.ones(truth.shape[0]), reals] cdf_x = np.insert(cdf_x, [0], cdf_x[0] - (cdf_x[1] - cdf_x[0])) # Calculate the indicator responses and local variances for i in range(truth.shape[0]): if cdf_x is None: if isjagged: ecdf = cdf(reals[i]) # each element in reals is a list of sim vals v = np.var(reals[i]) else: ecdf = cdf(reals[i, :]) # reals is a 2D array with standard size that can be sliced v = np.var(reals[i, :]) variances.append(v) else: ecdf = (cdf_x, reals[i, :]) variances.append(variance_from_cdf(ecdf[0], ecdf[1])) for pint in pints: if ecdf[0][0] <= truth[i] <= ecdf[0][-1]: p = z_percentile(truth[i], ecdf[0], ecdf[1]) plower = 0.5 - (pint / 2) pupper = 0.5 + (pint / 2) if plower <= p <= pupper: indic = 1 else: indic = 0 else: indic = 0 propindic[pint].append(indic) return pints, propindic, variances