Source code for ioos_qc.axds
#!/usr/bin/env python
# coding=utf-8
"""Tests based on the IOOS QARTOD manuals."""
import logging
from typing import Tuple, Sequence
from collections import namedtuple
import numpy as np
from ioos_qc.utils import (
isnan,
add_flag_metadata,
mapdates
)
from ioos_qc.qartod import QartodFlags
L = logging.getLogger(__name__) # noqa
FLAGS = QartodFlags # Default name for all check modules
NOTEVAL_VALUE = QartodFlags.UNKNOWN
span = namedtuple('Span', 'minv maxv')
[docs]@add_flag_metadata(standard_name='gross_range_test_quality_flag',
long_name='Gross Range Test Quality Flag')
def valid_range_test(inp : Sequence[any],
valid_span : Tuple[any, any],
dtype : np.dtype = None,
start_inclusive : bool = True,
end_inclusive : bool = False,
) -> np.ma.core.MaskedArray:
"""Checks that values are within a min/max range. This is not unlike a `qartod.gross_range_test`
with fail and suspect bounds being equal, except that here we specify the inclusive range that
should pass instead of the exclusive bounds which should fail. This also supports datetime-like
objects where as the `qartod.gross_range_test` method only supports numerics.
Given a 2-tuple of minimum/maximum values, flag data outside of the given
range as FAIL data. Missing and masked data is flagged as UNKNOWN. The first span value is
treated as inclusive and the second span valid is treated as exclusive. To change this
behavior you can use the parameters `start_inclusive` and `end_inclusive`.
Args:
inp (Sequence[any]): Data as a sequence of objects compatible with the fail_span objects
fail_span (Tuple[any, any]): 2-tuple range which to flag outside data as FAIL. Objects
should be of equal format to that of the inp parameter as they will be checked for
equality without type conversion.
dtype (np.dtype): Optional. If your data is not already numpy-typed you can specify its
dtype here.
start_inclusive (bool): Optional. If the starting span value should be inclusive (True) or
exclusive (False).
end_inclusive (bool): Optional. If the ending span value should be inclusive (True) or
exclusive (False).
Returns:
np.ma.core.MaskedArray: A masked array of flag values equal in size to that of the input.
"""
# Numpy array inputs
if dtype is None and hasattr(inp, 'dtype'):
dtype = inp.dtype
# Pandas column inputs
# This is required because we don't want to restrict a user from using a pd.Series
# directly with this function. If the data was coming from a Store, it would
# always be a numpy array.
elif dtype is None and hasattr(inp, 'values') and hasattr(inp.values, 'dtype'):
dtype = inp.values.dtype
# Save original shape
original_shape = inp.shape
if dtype is None:
L.warning("Trying to guess data input type, try specifying the dtype parameter")
# Try to figure out the dtype so masked values can be calculated
try:
# Try datetime-like objects
inp = np.ma.masked_invalid(mapdates(inp))
valid_span = np.ma.masked_invalid(mapdates(valid_span))
except BaseException:
try:
# Try floating point numbers
inp = np.ma.masked_invalid(np.array(inp).astype(np.floating))
valid_span = np.ma.masked_invalid(np.array(valid_span).astype(np.floating))
except BaseException:
# Well, we tried.
raise ValueError(
"Could not determine the type of input, try using the dtype parameter")
else:
inp = np.ma.masked_invalid(np.array(inp, dtype=dtype))
valid_span = np.ma.masked_invalid(np.array(valid_span, dtype=dtype))
inp = inp.flatten()
# Start with everything as passing (1)
flag_arr = np.ma.ones(inp.size, dtype='uint8')
# Set fail on either side of the bounds, inclusive and exclusive
if not isnan(valid_span[0]):
with np.errstate(invalid='ignore'):
if start_inclusive is True:
flag_arr[inp < valid_span[0]] = QartodFlags.FAIL
else:
flag_arr[inp <= valid_span[0]] = QartodFlags.FAIL
if not isnan(valid_span[1]):
with np.errstate(invalid='ignore'):
if end_inclusive is True:
flag_arr[inp > valid_span[1]] = QartodFlags.FAIL
else:
flag_arr[inp >= valid_span[1]] = QartodFlags.FAIL
# If the value is masked or nan set the flag to MISSING
flag_arr[inp.mask] = QartodFlags.MISSING
return flag_arr.reshape(original_shape)