Repository: mmaelicke/scikit-gstat

Issue:
Output function for experimental Variogram
Implement a new return function for `Variogram`, that returns the bin edges (or centers) along with the current experimental data. This is helpful to align skgstat with the `gstools.vario_etimate` function.

Could look like:

```Python
Variogram:
    def get_empirical(self, bin_center=False) -> Tuple[np.ndarry, np.ndarray]:
        if bin_center:
            bins = ...
        else:
            bins = self.bins
        
        return (bins, self.experimental)
```

Discussion:

Patch:
diff --git a/docs/changelog.rst b/docs/changelog.rst
index 30b4c8e..faa7ce6 100644
--- a/docs/changelog.rst
+++ b/docs/changelog.rst
@@ -2,9 +2,23 @@
 Changelog
 =========
 
-Version 0.4.0
+Version 0.4.2 [WIP]
+===================
+- [Variogram] :func:`bins <skgstat.Variogram.bins>` now cases manual setted bin edges automatically
+  to a :func:`numpy.array`.
+- [Variogram] :func:`get_empirical <skgstat.Variogram.get_empirical>` returns the empirical variogram.
+  That is a tuple of the current :func:`bins <skgstat.Variogram.bins>` and 
+  :func:`experimental <skgstat.Variogram.experimental>` arrays, with the option to move the bin to the
+  lag classes centers.
+
+Version 0.4.1
 =============
+- [Variogram] moved the bin function setting into a wrapper instance method, which was an anonymous lambda before.
+  This makes the Variogram serializable again.
+- [Variogram] a list of pylint errors were solved. Still enough left.
 
+Version 0.4.0
+=============
 - [binning] added `'stable_entropy'` option that will optimize the lag class edges to be of comparable Shannon Entropy.
 
 Version 0.3.11
diff --git a/skgstat/Variogram.py b/skgstat/Variogram.py
index ff4d3e4..140e61a 100644
--- a/skgstat/Variogram.py
+++ b/skgstat/Variogram.py
@@ -608,6 +608,23 @@ class Variogram(object):
 
     @property
     def bins(self):
+        """Distance lag bins
+
+        Independent variable of the the experimental variogram sample.
+        The bins are the upper edges of all calculated distance lag
+        classes. If you need bin centers, use
+        :func:`get_empirical <skgstat.Variogram.get_empirical>`.
+
+        Returns
+        -------
+        bins : numpy.ndarray
+            1D array of the distance lag classes.
+
+        See Also
+        --------
+        Variogram.get_empirical
+
+        """
         # if bins are not calculated, do it
         if self._bins is None:
             self._bins, n = self.bin_func(self.distance, self._n_lags, self.maxlag)
@@ -621,7 +638,7 @@ class Variogram(object):
     @bins.setter
     def bins(self, bins):
         # set the new bins
-        self._bins = bins
+        self._bins = np.asarray(bins)
 
         # clean the groups as they are not valid anymore
         self._groups = None
@@ -1506,6 +1523,49 @@ class Variogram(object):
         # return the mapped result
         return np.fromiter(map(mapper, self.lag_classes()), dtype=float)
 
+    def get_empirical(self, bin_center=False):
+        """Empirical variogram
+
+        Returns a tuple of dependent and independent sample values, this
+        :class:`Variogram <skgstat.Variogram>` is estimated for.
+        This is a tuple of the current :func:`bins <skgstat.Variogram.bins>`
+        and :func:`experimental <skgstat.Variogram.experimental>`
+        semi-variance values. By default the upper bin edges are used.
+        This can be set to bin center by the `bin_center` argument.
+
+        Parameters
+        ----------
+        bin_center : bool
+            If set to `True`, the center for each distance lag bin is
+            used over the upper limit (default).
+
+        Returns
+        -------
+        bins : numpy.ndarray
+            1D array of :func:`n_lags <skgstat.Variogram.n_lags>`
+            distance lag bins.
+        experimental : numpy.ndarray
+            1D array of :func:`n_lags <skgstat.Variogram.n_lags>`
+            experimental semi-variance values.
+
+        See Also
+        --------
+        Variogram.bins
+        Variogram.experimental
+
+        """
+        # get the bins and experimental values
+        bins = self.bins
+        experimental = self.experimental
+
+        # align bin centers
+        if bin_center:
+            # get the bin centers
+            bins = np.subtract(bins, np.diff([0] + bins.tolist()) / 2)
+
+        # return
+        return bins, experimental
+
     def __get_fit_bounds(self, x, y):
         """
         Return the bounds for parameter space in fitting a variogram model.


Test patch:
diff --git a/skgstat/tests/test_variogram.py b/skgstat/tests/test_variogram.py
index f6b51b1..17569d0 100644
--- a/skgstat/tests/test_variogram.py
+++ b/skgstat/tests/test_variogram.py
@@ -817,6 +817,25 @@ class TestVariogramMethods(unittest.TestCase):
 
         self.V = Variogram(self.c, self.v, normalize=False, n_lags=10)
 
+    def test_get_empirical(self):
+        bins = self.V.bins
+        exp = self.V.experimental
+
+        emp_x, emp_y = self.V.get_empirical()
+
+        # test
+        assert_array_almost_equal(bins, emp_x)
+        assert_array_almost_equal(exp, emp_y)
+    
+    def test_get_empirical_center(self):
+        V = Variogram(self.c, self.v)
+
+        # overwrite bins
+        V.bins = [4, 8, 9, 12, 15]
+        emp_x, emp_y = V.get_empirical(bin_center=True)
+
+        assert_array_almost_equal(emp_x, [2., 6., 8.5, 10.5, 13.5])
+
     def test_clone_method(self):
         # copy variogram
         copy = self.V.clone()