Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,6 @@ Data Processing
BlockMean
Trend

Composite Estimators
--------------------

.. autosummary::
:toctree: generated/

Vector

Model Selection
---------------

Expand Down
2 changes: 1 addition & 1 deletion src/verde/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from .surfer import load_surfer
from .trend import Trend
from .utils import grid_to_table, make_xarray_grid, maxabs, variance_to_weights
from .vector import Vector, VectorSpline2D
from .vector import VectorSpline2D

# Append a leading "v" to the generated version by setuptools_scm
__version__ = f"v{__version__}"
116 changes: 0 additions & 116 deletions src/verde/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,122 +16,6 @@
from .spline import warn_weighted_exact_solution


class Vector(BaseGridder):
"""
Fit an estimator to each component of multi-component vector data.

Provides a convenient way of fitting and gridding vector data using scalar
gridders and estimators.

Each data component provided to :meth:`~verde.Vector.fit` is fitted to a
separated estimator. Methods like :meth:`~verde.Vector.grid` and
:meth:`~verde.Vector.predict` will operate on the multiple components
simultaneously.

.. warning::

Never pass code like this as input to this class: ``[vd.Trend(1)]*3``.
This creates 3 references to the **same instance** of ``Trend``, which
means that they will all get the same coefficients after fitting. Use a
list comprehension instead: ``[vd.Trend(1) for i in range(3)]``.

Parameters
----------
components : tuple or list
A tuple or list of the estimator/gridder instances used for each
component. The estimators will be applied for each data component in
the same order that they are given here.

Attributes
----------
components : tuple
Tuple of the fitted estimators on each component of the data.
region_ : tuple
The boundaries (``[W, E, S, N]``) of the data used to fit the
interpolator. Used as the default region for the
:meth:`~verde.Vector.grid` and :meth:`~verde.Vector.scatter` methods.

See also
--------
verde.Chain : Chain filtering operations to fit on each subsequent output.

"""

def __init__(self, components):
super().__init__()
self.components = components

def fit(self, coordinates, data, weights=None):
"""
Fit the estimators to the given multi-component data.

The data region is captured and used as default for the
:meth:`~verde.Vector.grid` and :meth:`~verde.Vector.scatter` methods.

All input arrays must have the same shape. If weights are given, there
must be a separate array for each component of the data.

Parameters
----------
coordinates : tuple of arrays
Arrays with the coordinates of each data point. Should be in the
following order: (easting, northing, vertical, ...). Only easting
and northing will be used, all subsequent coordinates will be
ignored.
data : tuple of array
The data values of each component at each data point. Must be a
tuple.
weights : None or tuple of array
If not None, then the weights assigned to each data point of each
data component. Typically, this should be 1 over the data
uncertainty squared.

Returns
-------
self
Returns this estimator instance for chaining operations.

"""
if not isinstance(data, tuple):
raise ValueError(
"Data must be a tuple of arrays. {} given.".format(type(data))
)
if weights is not None and not isinstance(weights, tuple):
raise ValueError(
"Weights must be a tuple of arrays. {} given.".format(type(weights))
)
coordinates, data, weights = check_fit_input(coordinates, data, weights)
self.region_ = get_region(coordinates[:2])
for estimator, data_comp, weight_comp in zip(self.components, data, weights):
estimator.fit(coordinates, data_comp, weight_comp)
return self

def predict(self, coordinates):
"""
Evaluate each data component on a set of points.

Requires a fitted estimator (see :meth:`~verde.Vector.fit`).

Parameters
----------
coordinates : tuple of arrays
Arrays with the coordinates of each data point. Should be in the
following order: (easting, northing, vertical, ...). Only easting
and northing will be used, all subsequent coordinates will be
ignored.

Returns
-------
data : tuple of array
The values for each vector component evaluated on the given points.
The order of components will be the same as was provided to
:meth:`~verde.Vector.fit`.

"""
check_is_fitted(self, ["region_"])
return tuple(comp.predict(coordinates) for comp in self.components)


class VectorSpline2D(BaseGridder):
r"""
Elastically coupled interpolation of 2-component vector data.
Expand Down
6 changes: 3 additions & 3 deletions test/test_model_selection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import pytest
from sklearn.metrics import get_scorer

from verde import Trend, Vector, grid_coordinates, scatter_points
from verde import Trend, VectorSpline2D, grid_coordinates, scatter_points
from verde.model_selection import BlockKFold, BlockShuffleSplit, cross_val_score


Expand Down Expand Up @@ -46,9 +46,9 @@ def test_cross_val_score(trend, metric, expected):
ids=["none", "R2", "MSE"],
)
def test_cross_val_score_vector(trend, metric, expected):
"Check that CV works on Vector data types as well"
"Check that CV works on vector data types as well"
coords, data = trend[:2]
model = Vector([Trend(degree=1), Trend(degree=1)])
model = VectorSpline2D()
scores = cross_val_score(model, coords, (data, data), scoring=metric)
npt.assert_allclose(scores, expected, atol=1e-10)

Expand Down
73 changes: 1 addition & 72 deletions test/test_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
from verde.base import n_1d_arrays
from verde.coordinates import grid_coordinates
from verde.synthetic import CheckerBoard
from verde.trend import Trend
from verde.vector import Vector, VectorSpline2D
from verde.vector import VectorSpline2D


@pytest.fixture
Expand Down Expand Up @@ -63,73 +62,3 @@ def test_vector2d_fails(data2d):
spline.fit(coords, data[0])
with pytest.raises(ValueError):
spline.fit(coords, data + coords)


###############################################################################
# Test the Vector meta-gridder


@pytest.fixture()
def simple_2d_model():
"A single degree=1 2-component model"
east, north = grid_coordinates((1000, 5000, -5000, -1000), shape=(50, 50))
coefs = ([-20, 0.5, 3], [10, 2, -0.4])
data = tuple(c[0] + c[1] * east + c[2] * north for c in coefs)
return (east, north), coefs, data


@pytest.fixture()
def simple_3d_model():
"A single degree=1 3-component model"
east, north = grid_coordinates((1000, 5000, -5000, -1000), shape=(50, 50))
coefs = ([-20, 0.5, 3], [10, 2, -0.4], [30, -10, -1.3])
data = tuple(c[0] + c[1] * east + c[2] * north for c in coefs)
return (east, north), coefs, data


def test_vector_trend(simple_2d_model):
"Test the vector trend estimation on a simple problem"
coords, coefs, data = simple_2d_model
trend = Vector([Trend(degree=1), Trend(degree=1)]).fit(coords, data)
for i, coef in enumerate(coefs):
npt.assert_allclose(trend.components[i].coef_, coef)
npt.assert_allclose(trend.predict(coords)[i], data[i])
npt.assert_allclose(trend.score(coords, data), 1)


def test_vector_trend_3d(simple_3d_model):
"Test the vector trend estimation on a simple problem"
coords, coefs, data = simple_3d_model
trend = Vector([Trend(degree=1), Trend(degree=1), Trend(degree=1)])
trend.fit(coords, data)
for i, coef in enumerate(coefs):
npt.assert_allclose(trend.components[i].coef_, coef)
npt.assert_allclose(trend.predict(coords)[i], data[i])
npt.assert_allclose(trend.score(coords, data), 1)


def test_vector_trend_fails(simple_2d_model):
"Test the vector trend estimation on a simple problem"
coords, _, data = simple_2d_model
trend = Vector([Trend(degree=1), Trend(degree=1)])
with pytest.raises(ValueError):
trend.fit(coords, list(data))
with pytest.raises(ValueError):
trend.fit(coords, data, weights=[np.ones_like(data)] * 2)


def test_vector_trend_weights(simple_2d_model):
"Use weights to account for outliers"
coords, coefs, data = simple_2d_model
outlier = np.abs(data[0]).max() * 3
data_out = tuple(i.copy() for i in data)
weights = tuple(np.ones_like(i) for i in data)
for i, _ in enumerate(coefs):
data_out[i][20, 20] += outlier
weights[i][20, 20] = 1e-10
trend = Vector([Trend(degree=1), Trend(degree=1)])
trend.fit(coords, data_out, weights)
for i, coef in enumerate(coefs):
npt.assert_allclose(trend.components[i].coef_, coef)
npt.assert_allclose((data_out[i] - trend.predict(coords)[i])[20, 20], outlier)
npt.assert_allclose(trend.predict(coords)[i], data[i])
Loading