#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
This module Contains basic data class to read data in different formats including GeoEAS, csv and hdf5
'''
#-----------------------------------------------------------------------------
# Boilerplate
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
import csv
import os
import numpy as np
import pandas as pd
from . import iotools as iotools
from ..utility.logging import printerr
from .. pygeostat_parameters import Parameters
# Private dictionary of checks that are used in setcol for the specialized column attributes
_CHECKS = {'dh': ['dh', 'drillhole', 'drill hole', 'dhid', 'drillholeid', 'drill hole id', 'Drillhole Number',
'well', 'wellid', 'well id', 'holeid', 'hole id', 'id', 'ID'],
'ifrom': ['ifrom', 'from', 'from depth', 'ifrom(m)', 'from(m)', 'from depth(m)'],
'ito': ['ito', 'to', 'to depth', 'ito(m)', 'to(m)',
'To(m)', 'TO(m)', 'to depth(m)', 'To Depth(m)', 'TO DEPTH(m)'],
'x': ['x', 'east', 'easting', 'x(m)', 'east(m)', 'easting(m)', 'xlocation'],
'y': ['y', 'north', 'northing', 'y(m)', 'north(m)', 'northing(m)', 'ylocation'],
'z': ['z', 'elev', 'elevation', 'z(m)', 'elev(m)', 'elevation(m)', 'zlocation'],
'cat': ['cat', 'category', 'categories', 'rt', 'rocktype', 'rock type', 'fac',
'facies', 'lithofacies', 'lithology', 'Facies Code'],
'weights': ['wt', 'wts', 'weights', 'weight', 'declusteringweight', 'declustering weight']}
[docs]
class DataFile:
"""
This class stores geostatistical data values and metadata.
DataFile classes may be created on initialization, or generated
using pygeostat functions. This is the primary class for pygeostat
and is used for reading and writing GSLIB, CSV, VTK, and HDF5 file formats.
Parameters:
flname (str): Path (or name) of file to read
readfl (bool): True if the data file should be read on class initialization
fltype (str): Type of data file: either ``csv``, ``gslib`` or ``hdf5`` or ``gsb``
dftype (str): Data file type as either 'point' or 'grid' used for writing out VTK files
for visualization
data (pandas.DataFrame): Pandas dataframe containing array of data values
dicts (List[dict] or dict): List of dictionaries or dictionary for converting alphanumeric
to numeric data
null (float): Null value for missing values in the data file
title (str): Title, or name, of the data file
griddef (pygeostat.GridDef): Grid definition for a gridded data file
dh (str): Name of drill hole variable
x (str): Name of X coordinate column
y (str): Name of Y coordinate column
z (str): Name of Z coordinate column
ifrom (str): Name of 'from' columns
ito (str): Name of 'to' columns
weights (str or list): Name of declustering weight column(s)
cat (str): Name of categorical (e.g., rock type or facies) column
catdict (dict): Set a dictionary for the categories, which should be formatted as:
``catdict = {catcode:catname}``
variables (str or list): Name of continuous variable(s), which if unspecified, are the
columns not assigned to the above attributes (via kwargs or inference)
notvariables (str or list): Name of column(s) to exclude from variables
delimiter (str): Delimiter used in data file (ie: comma or space)
headeronly (bool): True to just read header + 1 line of data file This is useful for
getting column numbers of large files OR if reading hdf5 files will only read in the
hdf5 store information
h5path (str): Forward slash (/) delimited path through the group hierarchy you wish to
read the dataset(s) specified by the argument ``datasets`` from. The dataset name
cannot be passed using this argument, it is interpreted as a group name only. A
value of ``None`` places the dataset into the root directory of the HDF5 file. A value
of ``False`` loads a blank pd.DataFrame().
h5datasets (str or list): Name of the dataset(s) to read from the group specified by
``h5path``. Does nothing if ``h5path`` points to a dataset.
columns (list): List of column labels to use for the resulting ``data`` pd.DataFrame
nreals (int): number of realizations to read in. -1 will read all
tmin (float): If a number is provided, values less than this number (e.g., trimmed
or null values) are convernted to NaN. May be useful since NaN's are
more easily handled within python, matplotlib and pandas. Set to None
to disable.
Examples:
Quickly reading in a GeoEAS data file:
.. code-block:: python
data_file = gs.DataFile(flname='../data/oilsands.dat')
To read in a GeoEAS datafile and assign attributes
.. code-block:: python
# Point Data Example
data_file = gs.DataFile(flname='../data/oilsands.dat',readfl=True,dh='Drillhole Number', x='East',y='North',z='Elevation')
.. code-block:: python
# Gridded Data Example
griddef = gs.GridDef('''10 0.5 1
10 0.5 1
10 0.5 1''')
data_file = gs.DataFile(flname='../data/3DDecor.dat', griddef=griddef)
# To view grid definition string
print(data_file.griddef)
# Access some Grid Deffinition attributes
data_file.griddef.count() # returns number of blocks in grid
data_file.griddef.extents() # returns an array of the extents for all directions
data_file.griddef.nx # returns nunmber of blocks in x direction
**HDF5**
Using the HDF5 file format has its own positive features. For one it reads and writes much
faster then using the ASCII format. Attributes (like the grid definition) can also be saved
within the file. All files for a single project can also be saved in the same file. Please
refer to the :ref:`introduction on HDF5 files <hdf5>` for more information
This class currently only searches for and loads a grid definition.
Examples:
HDF5 file simple read example:
.. code-block:: python
data_file = gs.DataFile(flname='../data/oilsands_out.hdf5')
To view the HDF5 header information (tables stored in the file):
.. code-block:: python
data_file.store
If you have a HDF5 file with multiple tables and you just want to read in the file
information to view what tables are in the file and any attributes saved to the file you
can do a header style only read:
.. code-block:: python
data_file = gs.DataFile(flname='../data/oilsands_out.hdf5', dftype='hdf5', headeronly=True)
Then to see what tables are written in the hdf5 file:
.. code-block:: python
data_file.store
"""
def __init__(self, flname=None, readfl=None, fltype=None, dftype=None, data=None, columns=None,
null=None, title='data', griddef=None, dh=None, x=None, y=None, z=None, ifrom=None,
ito=None, weights=None, cat=None, catdict=None, variables=None, notvariables=None,
delimiter=r"\s+", headeronly=False, h5path=None, h5datasets=None, nreals=-1,
tmin=None):
self.flname = flname
self.readfl = readfl
# File types include 'GSLIB' and 'CSV' and 'HDF5'
self.fltype = fltype
self.headeronly = headeronly
# Assign a default delimiter
self.delimiter = delimiter
self.data = data
if null is None:
self.null = Parameters['data.null']
else:
self.null = null
self.title = title
# Note that the default grid definition will be checked against Parameters after
# initializing DataFile.data
self.griddef = griddef
self.store = None
self.h5fl = False
self.nreals = nreals
# Read the data in if ``data`` is None but a file is passed
if (data is None) and (flname is not None) and (readfl is None):
self.readfl = True
if (data is None) and (readfl is None):
self.data = pd.DataFrame()
# Data types include 'point', 'grid' - not that grid is assigned later after
# default griddefs are considered... which may override the point assignment below
if dftype is not None:
self.dftype = dftype
else:
self.dftype = 'point'
# Read the file
if self.readfl:
_, extention = os.path.splitext(flname)
extention = extention.lower()
if extention in ['.h5', '.hdf5', '.hd5']:
self.h5fl = True
from .iotools import read_file
# Load the h5fl as a H5Store class as required
if self.h5fl:
from .h5_io import H5Store
self.store = H5Store(flname)
# read the file and import data and/or header
self.data = read_file(self.flname, fltype=fltype, headeronly=headeronly,
delimiter=delimiter, h5path=h5path, h5datasets=h5datasets,
columns=columns, griddef=self.griddef, ireal=nreals, tmin=tmin)
# Try and coerce data into a pandas dataframe if it is not already
if (data is not None) and (not isinstance(data, pd.DataFrame)):
try:
self.data = pd.DataFrame(data=data, columns=columns)
except:
raise ValueError("Please ensure the passed `data` can be coerced into a pandas"
" dataframe (i.e., data = pd.DataFrame(data=data)")
# Initialize the specialized columns
self.dh = dh
self.ifrom = ifrom
self.ito = ito
self.x = x
self.y = y
self.z = z
self.weights = weights
self.cat = cat
self.variables = variables
# Will set/validate specialized columns by iterating through this dictionary
_specialattr = self._get_specialattr()
if self.data is not None:
# Check for duplicate column names
self.check_for_duplicate_cols()
# Set specialized columns, validating their existence DataFile.data if user provided,
# or assigning based on common names if present in DataFile.data
for colattr, colval in _specialattr.items():
self.setcol(colattr, colval)
# Get variables
self.setvarcols(variables, notvariables)
else: # data is None, so `self.variables` has no meaning
if self.variables is not None:
# Simply assign the names, where the only validation is that a string is provided
for colattr, colval in _specialattr.items():
if not isinstance(colval, str) and colval is not None:
raise TypeError(colattr + ' should be a list or tuple or string!')
setattr(self, colattr, colval)
if isinstance(self.variables, str):
self.variables = [variables]
elif isinstance(self.variables, tuple):
self.variables = list(variables)
elif not isinstance(self.variables, list):
raise TypeError('variables should be a list or tuple or string!')
# If a hdf5 file was read in, check to see if a griddef can be found
if self.readfl and self.h5fl:
# Handle some input parameters
if h5path in [None, '', ' ', '/']:
h5path = '/'
if isinstance(h5datasets, str):
h5datasets = [h5datasets]
if h5datasets is None:
h5datasets = ['']
gridstrs = []
# If multiple datasets are loaded, check them all for a griddef
for dataset in h5datasets:
attrs = self.store.h5data[h5path + '/' + dataset].attrs
if 'griddef' in attrs:
gridstrs.append(attrs['griddef'])
# Set the griddef if only one or multiple griddefs were found that are all the same;
# otherwise, don't load anything
if len(gridstrs) > 0:
from .grid_definition import GridDef
from ..utility.logging import printerr
if len(np.unique(gridstrs)) > 1:
printerr("Multiple grid definitions are in the HDF5 file. Nothing has been"
" set.", errtype='error')
else:
gridstr = gridstrs[0].replace("b'", "'").replace("'", "").replace("\\n", "\n")
if self.griddef is not None:
printerr("Grid definition is being rewritten by griddef saved in h5 file",
errtype='warning')
self.griddef = GridDef(gridstr)
# If griddef is None, check if a default griddef is initialized - assign it to this
# DataFile if its length matches. Convert the fltype if the user hasn't specified
# a fltype
if self.data is not None and griddef is None and Parameters['data.griddef'] is not None:
if Parameters['data.griddef'].count() == self.data.shape[0]:
self.griddef = Parameters['data.griddef']
if dftype is None and self.griddef is not None:
if self.griddef.count() == self.data.shape[0]:
self.dftype = 'grid'
# Set the catdict if appropriate
self.catdict = None
if self.cat is not None:
if catdict is None:
catdict = Parameters['data.catdict']
if catdict is not None:
try:
self.setcatdict(catdict)
except:
# The catdict will not be set if catdict.keys() doesn't match self.cat
pass
[docs]
def __str__(self):
"""
Return the name of the data file if asked to 'print' the data file... or use the datafile
in a string!
"""
return str(self.flname)
def __len__(self):
"""
Return the length of the dataframe this DataFile object contains
"""
if self.data is not None:
return len(self.data)
else:
return 0
def __getitem__(self, col):
"""
Access the column of data corresponding to `col` in the self.data `DataFrame`
"""
from copy import deepcopy
assert(isinstance(self.data, pd.DataFrame)), "`obj.data` is not a `pd.DataFrame`"
if isinstance(col, (bool, pd.core.series.Series)):
new_datafile = deepcopy(self)
try:
new_datafile.data = self.data[col]
except Exception as exception_message:
raise TypeError(str(exception_message))
return new_datafile
assert(isinstance(col, (str, list))), \
"`DataFile` can be indexed by `str` or `list` of columns"
if isinstance(col, (list, tuple)):
for c in col:
assert(c in self.data.columns), "`{}` is not in this file!".format(col)
else:
assert(col in self.data.columns), "`{}` is not in this file!".format(col)
return self.data[col]
def __setitem__(self, col, value):
if self.data is None:
self.data = pd.DataFrame()
self.data.loc[:, col] = value
def _get_specialattr(self):
'''Return a dictionary of the current special attribute values'''
specialattr = {'dh': self.dh, 'ifrom': self.ifrom, 'ito': self.ito, 'x': self.x,
'y': self.y, 'z': self.z, 'cat': self.cat, 'weights': self.weights}
return specialattr
def _get_shape(self):
assert isinstance(self.data, pd.DataFrame), "DataFile.data must be a pandas.DataFrame!"
return self.data.shape
shape = property(_get_shape)
[docs]
def head(self, n=5):
'''Return the first n rows of the data, accessing self.data.head()'''
assert isinstance(self.data, pd.DataFrame), "DataFile.data must be a pandas.DataFrame!"
return self.data.head(n)
def tail(self, n=5):
'''Return the last n rows of the data, accessing self.data.tail()'''
assert isinstance(self.data, pd.DataFrame), "DataFile.data must be a pandas.DataFrame!"
return self.data.tail(n)
def _get_columns(self):
""" Get the columns of the pandas data frame"""
return self.data.columns
columns = property(_get_columns)
def _get_locations(self):
return self[self.xyz]
locations = property(_get_locations)
def _get_info(self):
"""
Print a summary of the special attributes found in this datafile
Parameters:
verbose (bool): if True prints the datafile.data.info() showing the types and counts
of each column in the dataframe
"""
printstr = 'DataFile: {}\n'.format(self.flname)
specialattr = self._get_specialattr()
if not all([v is None for v in specialattr.values()]):
printstr += 'Attributes:\n'
for attr, value in specialattr.items():
if value is not None:
printstr += "{}: '{}', ".format(attr, value)
printstr += '\n'
else:
printstr += 'No Special Attributes Found \n'
if self.data is not None:
if self.variables is not None:
printstr += 'Variables:\n'
if isinstance(self.variables, str):
printstr += self.variables
else:
printstr += ', '.join(["'{}'".format(var) for var in self.variables])
if self.griddef is not None:
printstr += '\nGrid Definitions:\n'
printstr += str(self.griddef)
return printstr
info = property(_get_info)
[docs]
def rename(self, columns):
'''
Applies a dictionary to alter self.DataFrame column names. This applies the DataFrame.rename
function, but updates any special attributes (dh, x, y, etc.) with the new name, if previously
set to the old name. Users should consider using the self.columns property if changing all
column names.
Parameters:
columns(dict): formatted as {oldname1: newname1, oldname2:newname2}, etc, where the old and
new names are strings. The old names must be present in data.columns.
'''
# if this column already exists... drop it before renaming to avoid dups?
for old, new in columns.items():
if new in self.data.columns:
self.data.drop(new, axis=1, inplace=True)
self.data.rename(index=str, columns=columns, inplace=True)
# Update the special attributes
specialattr = self._get_specialattr()
for old, new in columns.items():
found = False
for colattr, colval in specialattr.items():
if isinstance(colval, str):
colval = [colval]
if isinstance(colval, list):
for i in range(len(colval)):
if colval[i] == old:
colval[i] = new
found = True
break
if found:
if len(colval) < 1:
colval = None
elif len(colval) == 1:
colval = colval[0]
self.setcol(colattr, colval)
break
# Update the variables
if isinstance(self.variables, list):
variables = self.variables
elif isinstance(self.variables, str):
variables = [self.variables]
else:
variables = None
if isinstance(variables, list):
for old, new in columns.items():
if old in variables:
i = variables.index(old)
variables[i] = new
if len(variables) == 1:
variables = variables[0]
self.variables = variables
[docs]
def drop(self, columns):
'''
This applies the DataFrame.drop function, where axis=1, inplace=True and columns is
used in place of the labels. It also updates any special attributes (dh, x, y, etc.),
setting them to None if dropped. Similarly, if any variables are dropped, they are
removed from self.variables.
Parameters:
columns(str or list): column names to drop
'''
if isinstance(columns, str):
columns = [columns]
self.data.drop(axis=1, labels=columns, inplace=True)
# Set the special attributes to None if in columns
specialattr = self._get_specialattr()
for key, val in specialattr.items():
if isinstance(val, str):
val = [val]
if isinstance(val, list):
temp = []
for v in val:
if v not in columns:
temp.append(v)
if len(temp) < 1:
temp = None
elif len(temp) == 1:
temp = temp[0]
setattr(self, key, temp)
# Update the variables
if isinstance(self.variables, list):
variables = self.variables
elif isinstance(self.variables, str):
variables = [self.variables]
else:
variables = None
if isinstance(variables, list):
temp = []
for var in variables:
if var not in columns:
temp.append(var)
if len(temp) < 1:
temp = None
elif len(temp) == 1:
temp = temp[0]
self.variables = temp
def _get_xyz(self):
assert(self.x is not None), 'No x column has been assigned for the current instance'
assert(self.y is not None), 'No y column has been assigned for the current instance'
if self.z is None:
return [self.x, self.y, None]
else:
return [self.x, self.y, self.z]
xyz = property(_get_xyz)
[docs]
def check_for_duplicate_cols(self):
"""
Run a quick check on the column names to see if any of them are duplicated. If they
are duplicated then print a Warning and rename the columns
"""
cols=pd.Series(self.data.columns)
# Check for duplicates by comparing unique list to full column list
if any(cols.duplicated()):
from ..utility.logging import printerr
printerr("Duplicate column names found when reading in data!!",
errtype='warning')
for dup in cols[cols.duplicated()].unique():
cols[cols[cols == dup].index.values.tolist()] = [dup + '_' + str(i) if i != 0 else dup for i in range(sum(cols == dup))]
self.data.columns=cols
# print out what we changed the column names to.
print('New unique column names are: ', list(cols))
[docs]
def setcol(self, colattr, colname=None):
"""
Set a specialized column attribute (``dh, ifrom, ito, x, y, z, cat or weights``) for the
``DataFile``, where ``DataFile.data`` must be initialized. If colname is None, then the
attribute will be set if a common name for it is detected in ``DataFile.data``.columns
(e.g., if ``colattr='dh'`` and ``colname=None``, and ``'DHID'`` is found in
``DataFile.data``, then ``DataFile.dh='DHID'``. The attribute will be None if none of the
common names are detected. If colname is not None, then the provided string will be
assigned to the attribute, e.g. DataFile.colattr=colname. Note, however, that an error
will be thrown if colname is not None and colname is not in ``DataFile.data``.columns.
This is used on DataFile initialization, but may also be useful for calling after
specialized columns are altered.
Parameters:
colattr(str) : must match one of: ``'dh'``, ``'ifrom'``, ``'ito'``, ``'x'``, ``'y'``,
``'z'``, ``'cat'`` or ``'weights'``
colname(str or list) : if not None, must be the name(s) of a column in ``DataFile.data``.
List is only valid if ``colattr=weights``
Examples:
Set the x attribute (dat.x) based on a specified value:
>>> dat.setcol('x', 'Easting')
Set the x attribute (dat.x), where the function checks common names for x:
>>> dat.setcol('x')
"""
if self.data is None:
raise ValueError('function can only be called after DataFile.data is initialized!')
isset = False
columns = []
for s in list(self.data.columns):
try:
columns.append(s.lower())
except AttributeError:
columns.append(s)
# Note that weights is the single attribute that is a list - so it's handled in a non-general
# way for now. Should probably be cleaned up.
if colname is None:
# No attribute name is provided, so the function will check common
# naming conventions for this attribute
# First, grab the list of names to check
checks = _CHECKS[colattr]
if Parameters['data.'+colattr] is not None:
# If a default naming convention for this attribute was set for this project,
# then add it to the start of the checks list
checks = [Parameters['data.'+colattr].lower()] + checks
weights = []
for check in checks:
i = [i for i, x in enumerate(columns) if x.lower() == check.lower()]
if len(i) > 0:
i = i[0]
colname = self.data.columns[i]
# Set the attribute since the checked name is in the columns
if colattr != 'weights':
setattr(self, colattr, colname)
isset = True
break
else:
weights.append(colname)
if len(weights) > 0:
if len(weights) == 1:
weights = weights[0]
setattr(self, colattr, weights)
isset = True
else:
if isinstance(colname, str):
colnames = [colname]
elif isinstance(colname, tuple) or isinstance(colname, list):
if colattr != 'weights':
raise ValueError('colname must be a str unless weights!')
colnames = colname
else:
raise ValueError(colname+' is invalid!')
for colname in colnames:
if colname not in self.data.columns:
raise ValueError(colname + ' is not a column in the data!')
if len(colnames) == 1:
setattr(self, colattr, colnames[0])
else:
setattr(self, colattr, colnames)
isset = True
# Throw a warning if the attr is a coordinate and has nans
if isset:
if any([colattr == coord for coord in ['x', 'y', 'z']]):
if np.any(np.isnan(self.data[colname].values)):
print('WARNING: null (NaN) values in the {} coordinate column!'.format(colname))
print(' You may want to check the tmin setting (also, Parameters[data.tmin])')
[docs]
def setvarcols(self, variables=None, notvariables=None):
"""
Set the variables for the DataFile. If provided, the function checks that the
variables are present in the DataFrame. If not provided, the function assigns columns
that are not specified as the variables (``dh, x, y, z, rt, weights``), as well as a list of
user specified notvariables.
This is used on DataFile initialization, but may also be useful for calling after
variables are added or removed.
Parameters:
variables(list or str) : list of strings
notvariables(list or str) : list of strings
Examples:
Set the variables based on a specified list:
>>> dat.setvarcols(variables=['Au', 'Carbon'])
Set the variables based on the function excluding specialized columns (dh, x, y, etc.):
>>> dat.setvarcols()
Set the variables based on the function excluding specialized columns (dh, x, y, etc.),
as well as a user specified list of what is not a variable:
>>> dat.setvarcols(notvariables=['Data Spacing', Keyout'])
"""
self.variables = None
if variables is None:
# Construct a list of the specialized columns
if notvariables is None:
notvariables = []
elif isinstance(notvariables, str):
notvariables = [notvariables]
elif isinstance(notvariables, tuple):
notvariables = list(notvariables)
elif not isinstance(notvariables, list):
raise ValueError('variables should be a string or list of strings')
# Grab a dictionary of the special attributes
specialattr = self._get_specialattr()
for val in specialattr.values():
if val is not None:
if isinstance(val, str):
notvariables.append(val)
else:
for v in val:
notvariables.append(v)
# Variables are the columns not in notvariables
self.variables = []
for column in self.data.columns:
if column not in notvariables:
self.variables.append(column)
else:
if self.variables is None:
self.variables = variables
if not isinstance(self.variables, list) and not isinstance(self.variables, str):
raise ValueError('variables should be a string or list of strings')
if isinstance(self.variables, str):
self.variables = [self.variables]
for variable in self.variables:
if variable not in self.data.columns:
raise ValueError('{} is not data.data.columns!'.format(variable))
# Convert to a string if the list is length-1
if isinstance(self.variables, list):
if len(self.variables) == 1:
self.variables = self.variables[0]
elif len(self.variables) == 0:
self.variables = None
# Read only property number of variables
def _get_nvar(self):
if isinstance(self.variables, list):
nvar = len(self.variables)
elif isinstance(self.variables, str):
nvar = 1
else:
nvar = 0
return nvar
nvar = property(_get_nvar)
def __repr__(self):
return self.info
[docs]
def setcatdict(self, catdict):
'''
Set a dictionary for the categories, which should be formatted as:
>>> catdict = {catcode:catname}
Example:
>>> catdict = {0: "Mudstone", 1: "Sandstone"}
>>> self.setcatdict(catdict)
'''
if self.cat is None:
raise ValueError('self.cat must be initialized!')
# Ensure that catdict provides a key for each category in the data
cats = self[self.cat]
try:
cats = np.unique(cats[np.isfinite(cats)])
except TypeError as exc:
raise TypeError('Make sure the provided categorical column, "{}", contains numerical values'.format(self.cat)) from exc
for cat in cats:
if cat not in catdict.keys():
raise ValueError('{} in self.cat, but not in catdict.keys()!'.format(cat))
self.catdict = catdict
[docs]
def check_datafile(self, flname, variables, sep, fltype):
"""
Run some quick checks on the DataFile before writing and grab info if not provided
"""
# Default to introspection
if flname is None:
flname = self.flname
# Default separator is comma, unless the file is GSLIB-style
if sep is None:
if fltype is not None:
if fltype.lower() == 'gslib':
sep = ' '
elif fltype.lower() == 'csv':
sep = ','
else:
sep = ','
# Check for single variable only and convert to list of length 1
if isinstance(variables, str):
variables = [variables]
# Get variable names
if variables is None:
variables = self.data.columns.tolist()
return flname, variables, sep, fltype
[docs]
def write_file(self, flname, title=None, variables=None, fmt=None, sep=None, fltype=None, data=None, h5path=None, griddef=None,
null=None, tvar=None, nreals=1):
"""Writes out a GSLIB-style, VTK, CSV, Excel (XLSX), HDF5 data file.
Parameters:
flname (str): Path (or name) of file to write out.
Keyword Args:
title (str): Title for output file.
variables (List(str)): List of variables to write out if only a subset is desired.
fmt (str): Format to use for floating point numbers.
sep (str): Delimiter to use for file output, generally don't need to change.
fltype (str): Type of file to write either ``gslib``, ``vtk``, ``csv``, ``xlsx``,
or ``hdf5``.
data (str): Subset of data to write out - cannot be used with variables
option!
h5path (str): The h5 group path to write data to (H5 filetype)
griddef (obj): a gslib griddef object
tvar (str): Name of variable to use for compression when NaNs exist within it
nreals (int): number of realizations you are writing out (needed for GSB)
null (float): If a number is provided, NaN numbers are converted to this value
prior to writing. May be useful since NaN's are more easily handled
within python and pandas than null values, but are not valid
in GSLIB. Set to None to disable (but NaN's must be handled prior to
this function call if so).
Note:
pygeostat.write_file is saved for backwards compatibility or as an overloaded class method.
Current write functions can be called seperately with the functions listed below:
>>> import pygeostat as gs
>>> import pandas as pd
>>> gs.write_gslib(gs.DataFile or pd.DataFrame)
>>> gs.write_csv(gs.DataFile or pd.DataFrame)
>>> gs.write_hdf5(gs.DataFile or pd.DataFrame)
>>> gs.write_vtk(gs.DataFile or pd.DataFrame)
>>> gs.write_gsb(gs.DataFile or pd.DataFrame)
Note:
The GSB format is not specifically intended for general users of pygeostat. Some CCG programs use GSB that
is a compressed GSLIB-like binary data format that greatly reduces the computational expense.
The following calls are equivalent:
>>> data_file.write_file('testgslib.out')
>>> data_file.write_file('testgsb.gsb')
is equivalent to:
>>> gs.write_gslib(data_file, 'testgslib.out')
>>> gs.write_gsb(data_file, 'testgsb.gsb')
and similar to:
>>> gs.write_gslib(data_file.data, 'testgslib.out')
>>> gs.write_gsb(data_file.data, 'testgsb.gsb')
"""
from .grid_definition import GridDef
# Infer a filetype if none is specified
# ------------------------------------
if fltype is None:
if flname.endswith('.dat') or flname.endswith('.out'):
fltype = 'gslib'
elif flname.endswith('.csv'):
fltype = 'csv'
elif flname.endswith('.vtk'):
fltype = 'vtk'
elif flname.endswith('.hvtk'):
fltype = 'hvtk'
elif flname.endswith('.hd5') or flname.endswith('.hdf5') or flname.endswith('.h5'):
fltype = 'hdf5'
elif flname.endswith('.gsb'):
fltype = 'gsb'
else:
raise ValueError(" Output file type (fltype) cannot be inferred!")
# Check the arguments and for any info not passed thats already assigned
# ------------------------------------
variables0 = variables # Storing this for VTK due to conflicting definitions
flname, variables, sep, fltype = self.check_datafile(flname, variables, sep, fltype)
fltype = fltype.lower()
# Check data
if null is None and fltype != 'vtk':
null = self.null
if data is None:
data = self.data
# Check GridDef
if griddef is None and self.griddef is not None:
griddef = self.griddef
# For some reason, the following isinstance is returning an erroneous False. Any ideas?
if griddef is not None:
if not isinstance(griddef, GridDef):
printerr("griddef is not an instance of pygeostat.GridDef Class and will not be written to file",
errtype='error')
raise ValueError('griddef must be pygeostat.GridDef instance')
# Based on the filetype call the correct iotools function
# ------------------------------------
if fltype == 'gslib':
iotools.write_gslib(self.data, flname, title=self.title, variables=variables,
fmt=fmt, sep=sep, null=null)
elif fltype == 'csv' or fltype.lower() == 'xlsx' or fltype.lower() == 'excel':
iotools.write_csv(self.data, flname, variables=variables,
fmt=fmt, sep=sep, fltype=fltype, null=null)
elif fltype == 'hdf5' or fltype == 'h5' or fltype == 'hd5':
from .h5_io import write_h5
if isinstance(griddef, GridDef):
griddef = griddef.__str__().encode()
else:
griddef = None
# As with read and tmin, null is not yet implemented for h5
write_h5(self.data, flname, h5path=h5path, datasets=variables, gridstr=griddef)
elif fltype == 'vtk':
iotools.write_vtk(self, flname, variables=variables0, null=null)
elif fltype == 'hvtk':
if griddef is None:
printerr('ERROR: this DataFile must be a 3D gridded DataFile with a .griddef '
'attribute!', errtype='error')
return
else:
griddef = self.griddef
if flname.endswith('hvtk'):
flname = flname.replace('.hvtk', '.xdmf')
iotools.write_hvtk(self.data, flname, self.griddef, variables=variables)
elif fltype == 'gsb':
if not isinstance(fmt, list):
fmt = 0
iotools.write_gsb(self.data, flname, tvar=tvar, variables=variables,
nreals=nreals, fmt=fmt, griddef=griddef)
else:
printerr('Unsupported File type. Try with either "gslib"'
'"csv", "xlsx", "excel", "hdf5", or "vtk"', errtype='error')
[docs]
def gscol(self, variables, string=True):
"""Returns the GSLIB (1-ordered) column given a (list of) variable(s).
Parameters:
variables (str or List(str)): Path, or name, of the data file.
Keyword Args:
string (bool): If True returns the columns as a string.
Returns:
cols (int or List(int) or string): GSLIB 1-ordered column(s).
Note:
None input returns a 0, which may be necessary, for example, with 2-D
data:
>>> data.xyz
... ['East', 'North', None]
>>> data.gscol(data.xyz)
... '2 3 0'
Examples:
Some simple calls
>>> data_file.gscol('Bitumen')
... 5
>>> data_file.gscol(['Bitumen', 'Fines'])
... [5, 6]
>>> data_file.gscol(['Bitumen', 'Fines'], string=True)
... '5 6'
"""
if isinstance(variables, str) or variables is None:
variables = [variables]
cols = []
for var in variables:
if var is None:
cols.append(0)
else:
cols.append(self.data.columns.get_loc(var) + 1)
if len(cols) == 1:
cols = cols[0]
if not string:
return cols
else:
if not isinstance(cols, list):
return str(cols)
else:
return ' '.join(list(map(str, cols)))
[docs]
def describe(self, variables=None):
"""
Describe a data set using pandas describe(), but exclude special variables.
Keyword Args:
variables (List(str)): List of variables to describe.
Returns:
self.data[variables].describe(): Pandas description of variables.
Examples:
Describe all none special variables in the DataFrame (will exclued columns set as dh ID,
coordinate columns, etc.)
>>> data_file.describe()
Or describe specific variables
>>> data_file.describe(['Bitumen', 'Fines'])
"""
# Get a list of variables to set to np.nan
if variables is None:
if self.variables is None:
# Should rarely happen... but just in case
self.setvarcols()
variables = self.variables
if variables is None:
variables = list(self.columns)
return self.data[variables].describe()
def copy(self):
"""
Copy the DataFile, which provides frequent utility in workflows.
"""
from copy import deepcopy
return deepcopy(self)
[docs]
def gendict(self, var, outvar=None):
"""
Generates a dictionary with unique IDs from alphanumeric IDs.
This is particularly useful for alphanumeric drill hole IDs
which cannot be used in GSLIB software.
Parameters:
var (str): Variable to generate a dictionary for
Keyword Args:
outvar (str): Variable to generate using generated dictionary.
Returns:
newdict (dict): Dictionary of alphanumerics to numeric ids.
Examples:
A simple call
>>> data_file.gendict('Drillhole')
OR
>>> dh_dict = data_file.gendict('Drillhole')
"""
# Get the unique IDs
ids = self.unique_cats(var)
# Build up reasonable numeric IDs given traditional DH labeling
new_ids = []
for idx, str_id in enumerate(ids):
try:
new_id = int(''.join(c for c in str_id if c.isdigit()))
except:
new_id = 0
# Make it unique!
while new_id in new_ids:
new_id = int(''.join([str(new_id), str(idx)]))
new_ids.append(new_id)
# Generate the dictionary
newdict = dict(zip(ids, new_ids))
# Apply the dictionary if desired
if outvar is not None:
self.applydict(var, outvar, newdict)
# Return the new dictionary
return newdict
[docs]
def applydict(self, origvar, outvar, mydict):
"""Applies a dictionary to the original variable to get a new variable.
This is particularly useful for alphanumeric drill hole IDs
which cannot be used in GSLIB software.
Parameters:
origvar (str): Name of original variable.
outvar (str): Name of output variable.
mydict (dict): Dictionary of values to apply.
Examples:
>>> data_file.applydict('Drillhole', 'Drillhole-mod', mydict)
"""
def dictapply(value):
if value in mydict:
return mydict[value]
else:
return value
self.data[outvar] = self.data[origvar].apply(dictapply)
[docs]
def unique_cats(self, variable, truncatenans=False):
"""Returns a sorted list of the unique categories given a variable.
Parameters:
variable (str): Name of original variable.
Keyword Args:
truncatenans (bool): Truncates missing values if True.
Returns:
unique_cats (List(object)): Sorted, list of set(object).
Examples:
A simple call that
>>> data_file.unique_cats('Drillhole')
Or to save the list
>>> unique_dh_list = data_file.unique_cats('Drillhole')
"""
if truncatenans:
variable = self.truncatenans(variable)
if isinstance(variable, str):
return sorted(list(set(self.data[variable])))
else:
try:
return sorted(list(set(variable)))
except:
raise Exception('Could not get unique categories!')
[docs]
def truncatenans(self, variable):
"""Returns a truncated list with nans removed for a variable.
Parameters:
variable (str): Name of original variable.
Returns:
truncated (values): Truncated values.
Examples:
A simple call that will return the list
>>> data_file.truncatenans('Bitumen')
"""
if isinstance(variable, str):
return [x for x in self.data[variable] if not np.isnan(x)]
else:
try:
return [x for x in variable if not np.isnan(x)]
except:
raise Exception('Could not truncate NaNs!')
[docs]
def addcoord(self):
"""
Only use on DataFile classes containing GSLIB style gridded data.
If x, y, or z coordinate column(s) do not exist they are created. If the created or current
columns only have null values, they are populated based on the GridDef class pass to the
DataFile class.
Note:
A griddef must be assigned to the DataFile class either at read in like here
>>> data_file = gs.DataFile(flname='test.out', griddef=grid)
Or later such it can be manually assigned such as here
>>> data_file.griddef = gs.GridDef(gridstr=my_grid_str)
"""
# Make sure the griddef in the class is set
if self.griddef is None:
print('ERROR: GridDef is not defined within the DataFile class')
return
# Make sure the number of data makes sense with the griddef
if len(self.data) % self.griddef.count() != 0:
print('ERROR: The number of data found does not work with the number of cells')
return
# Check to make sure coordinate columns don't exist and if they do, force them to be loaded
# to the DataFile class
columns = list(self.data.columns)
if 'x' in columns and self.x is None:
print("ERROR: The column 'x' already exists and isn't saved to the DataFile class")
return
if 'y' in columns and self.y is None:
print("ERROR: The column 'y' already exists and isn't saved to the DataFile class")
return
if 'z' in columns and self.z is None:
print("ERROR: The column 'z' already exists and isn't saved to the DataFile class")
return
# Add coordinate columns as needed
if self.x is None:
self.data['x'] = float("nan")
self.x = 'x'
if self.y is None:
self.data['y'] = float("nan")
self.y = 'y'
if self.z is None:
self.data['z'] = float("nan")
self.z = 'z'
# Move the new coord columns to the start
xyzcols = [self.x, self.y, self.z]
columns = [columns[idx] for idx in range(len(columns)) if columns[idx] not in xyzcols]
xyzcols.extend(list(columns))
self.data = self.data[xyzcols]
# Figure out what columns need to be populated
reqcols = []
for column in xyzcols:
if self.data[column].isnull().values.sum() == len(self.data[column]):
reqcols.append(True)
else:
reqcols.append(False)
# Add the coordinates
nreal = len(self.data) // self.griddef.count()
x, y, z = self.griddef.get_coordinates()
gridxyz = np.stack((x, y, z), axis=1)
xyzreals = gridxyz.copy()
for ireal in range(1, nreal):
xyzreals = np.concatenate((xyzreals, gridxyz), axis=0)
x = xyzreals[:, 0]
y = xyzreals[:, 1]
z = xyzreals[:, 2]
if reqcols[0]:
self.data.loc[:, self.x] = x
if reqcols[1]:
self.data.loc[:, self.y] = y
if reqcols[2]:
self.data.loc[:, self.z] = z
[docs]
def infergriddef(self, blksize=None, databuffer=5, nblk=None):
"""
Infer a grid definition with the specified dimensions to cover the set of data values. The
function operates with two primary options:
1. Provide a block size (node spacing), the function infers the required number
of blocks (grid nodes) to cover the data
2. Provide the number of blocks, the function infers the required block size
A data buffer may be used for expanding the grid beyond the data extents. Basic integer
rounding is also used for attempting to provide a 'nice' grid in terms of the origin
alignment.
Parameters:
blksize(float or 3-tuple): provides (xsiz, ysiz, zsiz). If blksize is not None,
nblk must be None. Set zsiz None if the grid is 2-D. A float may also be provided,
where xsiz = ysiz = zsiz = float is assumed.
databuffer (float or 3-tuple): buffer between the data and the edge of the model,
optionally for each direction
nblk (int or 3-tuple): provides (nx, ny, nz). If blksize is not None,
nblk must be None. Set nz to None or 1 if the grid is 2-D. An int may also be provided,
where nx = ny = nz = int is assumed.
Returns:
griddef (GridDef): this function returns the grid definition object as well as
assigns the griddef to the current `gs.DataFile`
Note:
this function assumes things are either 3D or 2D along the xy plane. If nx == 1
or ny == 1, nonsense will result!
Usage:
First, import a datafile using gs.DataFile(), make sure to assign the correct columns
to x, y and z:
>>> datfl = gs.DataFile('test.dat',x='x',y='y',z='z')
Now create the griddef from the data contained within the dataframe:
>>> blksize = (100, 50, 1)
>>> databuffer = (10, 25, 0) # buffer in the x, y and z directions
>>> griddef = datfl.infergriddef(blksize, databuffer)
Check by printing out the resulting griddef:
>>> print(griddef)
Examples:
For 3D data, infergriddef() returns a 3D grid definition even if zsiz is given as None or 0 or 1:
.. code-block:: python
df3d = gs.ExampleData("point3d_ind_mv")
a = df3d.infergriddef(blksize = [50,60,1])
b = df3d.infergriddef(blksize = [50,60,None])
c = df3d.infergriddef(blksize = [50,60,0])
#a,b,c are returned as Pygeostat GridDef:
# 20 135.0 50.0
# 19 1230.0 60.0
# 82 310.5 1.0
For 3D data, nz given as None or 0 or 1 returns a 2D grid that covers the vertical extent of the 3D data:
.. code-block:: python
d = df3d.infergriddef(nblk = [50,60,1])
e = df3d.infergriddef(nblk = [50,60,None])
f = df3d.infergriddef(nblk = [50,60,0])
#d,e,f are returned as Pygeostat GridDef:
# 50 119.8 19.6
# 60 1209.1 18.2
# 1 350.85 81.7
Where xsiz = ysiz = zsiz, a float can also be provided, or where nx = ny = nz, an int can also be provided:
.. code-block:: python
df3d.infergriddef(blksize = 75)
df3d.infergriddef(blksize = [75,75,75])#returns the same as its above line
df3d.infergriddef(nblk = 60)
df3d.infergriddef(nblk = [60,60,60])#returns the same as its above line
If data is 2-D, zsiz or nz must be provided as None. Otherwise it raise exception:
.. code-block:: python
df2d = gs.ExampleData("point2d_ind")
df2d.infergriddef(nblk = [60, 60, None])
df2d.infergriddef(blksize = [50,60,None])
"""
from .grid_definition import GridDef
from ..datautils.utils import round_sigfig
import math
# Check the parameter inputs
twod = False
if nblk is None and blksize is None:
raise ValueError(("ERROR: both nblk and blksize are specified."
"One is specified and the other is inferred!"))
elif nblk is not None and blksize is not None:
raise ValueError(("ERROR: both nblk and blksize are not specified."
"One is specified and the other is inferred!"))
elif nblk is not None:
# nblk is the constant, so check its input
if isinstance(nblk, tuple) or isinstance(nblk, list):
if len(nblk) != 3:
raise ValueError(("ERROR: nblk should be an integer or a length 3 tuple!"))
else:
nx, ny, nz = nblk[0], nblk[1], nblk[2]
else:
nx = nblk
ny = nblk
nz = nblk
nx = int(nx); ny = int(ny)
if nz is not None: nz = int(nz)
if nz is None or nz == 0:
nz = 1
twod = True
else:
# blksize is the constant, so check its input
if isinstance(blksize, tuple) or isinstance(blksize, list):
if len(blksize) != 3:
raise ValueError(("ERROR: blksize should be a float or a length 3 tuple!"))
else:
xsiz, ysiz, zsiz = blksize[0], blksize[1], blksize[2]
else:
xsiz = blksize
ysiz = blksize
zsiz = blksize
if zsiz is None or zsiz == 0:
twod = True
zsiz = 1.0
# Make sure either 1 or 3 values passed to the buffer.
if isinstance(databuffer, tuple) or isinstance(databuffer, list):
if len(databuffer) != 3:
raise ValueError(("ERROR: Ensure a 3 buffers are passed if specifying"
"a buffer for each direction"))
else:
bx, by, bz = databuffer
else:
bx = databuffer
by = databuffer
bz = databuffer
# Very basic check on the data inputs
if self.x is None:
raise ValueError("ERROR: the x column must be saved to the DataFile class")
if self.y is None:
raise ValueError("ERROR: the y column must be saved to the DataFile class")
if not twod:
if self.z is None:
raise ValueError("ERROR: the z column must be saved to the DataFile class unless 2-D")
# get the min's and max's
xmin, xmax = (min(self.data[self.x]) - bx), (max(self.data[self.x]) + bx)
ymin, ymax = (min(self.data[self.y]) - by), (max(self.data[self.y]) + by)
if not twod or self.z is not None:
zmin, zmax = (min(self.data[self.z]) - bz), (max(self.data[self.z]) + bz)
else:
zmin, zmax = (0.0, 1.0)
# round mins down/up to the nearest integer, likely providing a nicer
# origin/division
xmin -= xmin % 1.0
ymin -= ymin % 1.0
zmin -= zmin % 1.0
xmax += xmax % 1.0
ymax += ymax % 1.0
zmax += zmax % 1.0
if nblk is not None:
# Infer the block sizes - note that this should be divided by nx, not nx-1
# Since it's the distance of the grid extents (not the min/max centroid)
xsiz = (xmax - xmin) / nx
ysiz = (ymax - ymin) / ny
if not twod or self.z is not None:
zsiz = (zmax - zmin) / nz
else:
zsiz = 1.0
sizes = [xsiz, ysiz, zsiz]
xsiz, ysiz, zsiz = [float(round_sigfig(v, 3)) for v in sizes]
else:
# Infer the number of blocks
nx = math.ceil((xmax - xmin) / xsiz)
ny = math.ceil((ymax - ymin) / ysiz)
if not twod or self.z is not None:
nz = math.ceil((zmax - zmin) / zsiz)
else:
nz = 1
griddef = GridDef(grid_arr=[nx, xmin + 0.5 * xsiz, xsiz,
ny, ymin + 0.5 * ysiz, ysiz,
nz, zmin + 0.5 * zsiz, zsiz])
self.griddef = griddef
return griddef
[docs]
def spacing(self, n_nearest, var=None, inplace=True, dh=None, x=None, y=None):
'''
Calculates data spacing in the xy plane, based on the average distance to the nearest
n_nearest neighbours. The x, y coordinates of 3-D data may be provided in combination
with a dh (drill hole or well), in which case the mean x, y of each dh is calculated
before performing the calculation. If a dh is not provided in combination with 3-D xy's,
then calculation is applied to all data and may create memory issues if greater than
~5000-10000 records are provided. A var specifier allows for the calculation to only
applied where var is not NaN.
If ``inplace==True``:
The output is concatenated as a 'Data Spacing ({Parameters['plotting.unit']})' column
if ``inplace=False`` (or 'Data Spacing' if Parameters['plotting.unit'] is None). If var is
used, then the calculation is only performed where DataFile[var] is not NaN, and
the output is concatenated as '{var} Data Spacing ({Parameters['plotting.unit']})'.
If ``inplace==False``:
The funciton returns dspace as a numpy array if dspace.shape[0] is equal to
DataFile.shape[0], meaning that dh and var functionality was not used, or did
not lead to differences in the length of dspace and DataFile (so that the x and
y in DataFile can be used for plotting dspace in map view).
The function returns a tuple of the form (dspace, dh, x, y), if dh is not None and
dspace.shape[0] is not equal to DataFile.shape[0]. The function returns a tuple of
the form (dspace, x, y) if dh is None and and var is not None and dspace.shape[0]
is not equal to DataFile.shape[0].
Parameters:
n_nearest (int): number of nearest neighbours to consider in data spacing
calculation
var (str): variable for calculating data spacing, where the calculation is only
applied to locations where var is not NaN. If None, the calculation is
to all locations.
inplace (bool): if True, the output data spacing is concatenated
dh (str): dh name, which can override self.dh
x (str): x coordinate name, which can override self.x
y (str): y coordinate name, which can override self.y
Examples:
Calculate data spacing without consideration of underlying variables, based on the
nearest 8 neighbours.
>>> dat.spacing(8)
Output as a numpy array rather than concatenating a column:
>>> dspace = dat.spacing(8, inplace=False):
Only consider values where Au is non NaN for the calculation:
>>> (dspace, x, y) = dat.spacing(8, inplace=False, var=Au)
'''
# Check the data input
if self.data is None:
raise ValueError('DataFile.data must be initialized!')
if self.data.ndim < 2:
raise ValueError(('DataFile.data.ndim must be greater than 1, given than an x and y'
'columns are required!'))
# Check the coordinate inputs
if x is None:
if self.x is None:
raise Exception('x must be provided either as DataFile.x or the kwarg!')
xc = self[self.x].values
else:
xc = self[x].values
if y is None:
if self.y is None:
raise Exception('y must be provided either as DataFile.y or the kwarg!')
yc = self[self.y].values
else:
yc = self[y].values
# Check the drill hole
if dh is None:
if self.dh is None:
dhc = np.arange(self.shape[0])
else:
dhc = self[self.dh].values
else:
dhc = self[dh].values
# Check the var
if var is not None:
if var not in self.columns:
raise KeyError('{} is not in DataFile.data!'.format(var))
vidx = np.logical_not(np.isnan(self[var].values))
xc = xc[vidx]
yc = yc[vidx]
dhc = dhc[vidx]
# Record this dht for output indexing
dhr = dhc
# Calculate the x and y as the average of each dh
dhu = np.unique(dhc)
if dhu.shape[0] == dhc.shape[0]:
xu = xc
yu = yc
dhu = dhc
else:
xu = np.zeros(dhu.shape[0])
yu = np.zeros(dhu.shape[0])
for i, dhut in enumerate(dhu):
idx = dhc == dhut
xu[i] = np.mean(xc[idx])
yu[i] = np.mean(yc[idx])
# Record the c's for future calculations
xc = xu
yc = yu
dhc = dhu
# Check the n_nearest now that we've reduced down to the calculated data
n = xc.shape[0]
if not isinstance(n_nearest, int):
raise ValueError('n_nearest must be an integer!')
elif n_nearest < 1:
raise ValueError('n_nearest must be larger than 0!')
elif n_nearest > n:
raise ValueError(('n_nearest must be less than or equal to the '
'number of values (after dh/var filtering)!'))
if n > 5000:
print(('WARNING: current implementation of function is likely too memory intensive'
'for greater than 5000 data'))
# Repeat the vector n times
xc = np.tile(xc, (n, 1))
yc = np.tile(yc, (n, 1))
# Calculate the squared distance between all points
# Distance matrix results (0s on the diagonal)
dspace = np.square(np.subtract(xc, xc.T))
dspace = np.add(dspace, np.square(np.subtract(yc, yc.T)))
dspace = np.sort(dspace, axis=0)
# Calcate the average distance to the n_nearest data
dspace = np.sqrt(dspace[1:n_nearest+1, :])
dspace = np.mean(dspace, axis=0)
if inplace:
# Build the output array
dspace1 = np.zeros(dhr.shape[0])
for i, dhut in enumerate(dhu):
idx = dhr == dhut
dspace1[idx] = dspace[i]
if var is not None:
dspace = np.zeros(self.shape[0])
dspace[np.where(vidx)] = dspace1
dspace[np.where(np.logical_not(vidx))] = np.nan
else:
dspace = dspace1
# Figure out the data spacing name and assign
if var is not None:
name = var+' '
else:
name = ''
name = name+'Data Spacing'
if Parameters['plotting.unit'] is not None:
name = name+' ({})'.format(Parameters['plotting.unit'])
self[name] = dspace
else:
# Determine the form of the output
if self.shape[0] == n:
return dspace
elif dh is None:
return (dspace, xc, yc)
else:
return (dspace, dhc, xc, yc)
[docs]
class DictFile:
'Class containing dictionary file information'
def __init__(self, flname=None, readfl=False, dictionary={}):
self.flname = flname
self.dict = dictionary
if readfl:
self.read_dict()
[docs]
def read_dict(self):
'Read dictionary information from file'
reader = csv.reader(open(self.flname, 'r'), delimiter=',')
for item in reader:
if item:
self.dict[item[0]] = item[1]
[docs]
def write_dict(self):
'Write dictionary information to csv style dictionary'
writerfile = open(self.flname, 'w')
writer = csv.writer(writerfile, delimiter=',', lineterminator='\n')
for key, value in self.dict.items():
writer.writerow([key, value])
writerfile.close()
[docs]
def ExampleData(testfile, griddef=None, **kwargs):
"""
Get an example pygeostat DataFile
Parameters:
testfile (str): one of the available pygeostat test files, listed below
Test files available in pygeostat include:
* "point2d_ind": 2d indicator dataset
* "point2d_surf": 2d point dataset sampling a surface
* "grid2d_surf": 'Thickness' from 'point2d_surf' interpolated on the grid
* "point3d_ind_mv": 3d multivariate and indicator dataset
* "oilsands": 3D Oil sands data set
* "accuracy_plot": Simulated realizations to test accuracy plot
* "location_plot": 2D data set to test location plot
* "3d_grid": 3D gridded data set
* "point2d_mv" : 2D multivariate data set
* "cluster": GSLIB datafile (data with declustering weights)
* "97data": GSLIB datafile (the first 97 rows of cluster datafile)
* "data": GSLIB datafile (2D data set of primary and secondary variable)
* "parta": GSLIB datafile (small 2D dataset part A)
* "partb": GSLIB datafile (small 2D dataset part B)
* "partc": GSLIB datafile (small 2D dataset part C)
* "true": GSLIB datafile (Primary secondary data pairs)
* "ydata": GSLIB datafile (2D spatial seondary data with some primary data)
"""
_testfiles = {
'point2d_ind': "point2d_ind.dat",
'point2d_surf': "point2d_surf.dat",
'grid2d_surf': "grid2d_surf.dat",
'point3d_ind_mv': "point3d_ind_mv.dat",
'3d_grid' : '3d_grid.out',
'accuracy_plot': 'accuracy_plot.dat',
'location_plot': 'location_plot.dat',
'oilsands': 'oilsands.dat',
'point2d_mv': 'point2d_mv.dat',
'3d_correlation': '3d_correlation.dat',
'3d_estimate': '3d_estimate.out',
'experimental_variogram': 'varcalc.out',
'variogram_model': 'varmodel.out',
'reservoir_boundary': 'reservoir_boundary.dat',
'reservoir_data': 'reservoir_data.dat',
'reservoir_surface': 'reservoir_surface.dat',
'cluster': 'cluster.dat',
'97data': '97data.dat',
'data': 'data.dat',
'parta': 'parta.dat',
'partb': 'partb.dat',
'partc': 'partc.dat',
'true': 'true.dat',
'ydata': 'ydata.dat',
}
if testfile not in _testfiles:
raise ValueError("Invalida file name. Choose one of {}".format(list(_testfiles.keys())))
data_dir = os.path.abspath(os.path.join(os.path.dirname( __file__ ), r'example_data'))
return DataFile(os.path.join(data_dir, _testfiles[testfile]), griddef=griddef, **kwargs)