Repository: arviz-devs/arviz

Issue:
Add method to delete groups from InferenceData
## Tell us about it
Add a method to delete groups from InferenceData

With many groups InferenceData can get quite large, making it hard to add to VCS or email. Things like sample_stats might not be needed for all results sharing. 


## Thoughts on implementation
1. Delete the object from `az.InferenceData` with `del` method in python
2. Remove string in `az.InferenceData._groups`
Add method to delete groups from InferenceData
## Tell us about it
Add a method to delete groups from InferenceData

With many groups InferenceData can get quite large, making it hard to add to VCS or email. Things like sample_stats might not be needed for all results sharing. 


## Thoughts on implementation
1. Delete the object from `az.InferenceData` with `del` method in python
2. Remove string in `az.InferenceData._groups`
Add method to delete groups from InferenceData
## Tell us about it
Add a method to delete groups from InferenceData

With many groups InferenceData can get quite large, making it hard to add to VCS or email. Things like sample_stats might not be needed for all results sharing. 


## Thoughts on implementation
1. Delete the object from `az.InferenceData` with `del` method in python
2. Remove string in `az.InferenceData._groups`


Discussion:
Was this fixed?
This is not fixed
Was this fixed?
This is not fixed
Was this fixed?
This is not fixed

Resolving patch:
diff --git a/arviz/data/inference_data.py b/arviz/data/inference_data.py
--- a/arviz/data/inference_data.py
+++ b/arviz/data/inference_data.py
@@ -42,6 +42,11 @@ def __repr__(self):
             options="\n\t> ".join(self._groups)
         )
 
+    def __delattr__(self, group):
+        """Delete a group from the InferenceData object."""
+        self._groups.remove(group)
+        object.__delattr__(self, group)
+
     @staticmethod
     def from_netcdf(filename):
         """Initialize object from a netcdf file.
@@ -73,16 +78,18 @@ def from_netcdf(filename):
                     groups[group] = data
         return InferenceData(**groups)
 
-    def to_netcdf(self, filename, compress=True):
+    def to_netcdf(self, filename, compress=True, groups=None):
         """Write InferenceData to file using netcdf4.
 
         Parameters
         ----------
         filename : str
             Location to write to
-        compress : bool
+        compress : bool, optional
             Whether to compress result. Note this saves disk space, but may make
             saving and loading somewhat slower (default: True).
+        groups : list, optional
+            Write only these groups to netcdf file.
 
         Returns
         -------
@@ -91,7 +98,11 @@ def to_netcdf(self, filename, compress=True):
         """
         mode = "w"  # overwrite first, then append
         if self._groups:  # check's whether a group is present or not.
-            for group in self._groups:
+            if groups is None:
+                groups = self._groups
+            else:
+                groups = [group for group in self._groups if group in groups]
+            for group in groups:
                 data = getattr(self, group)
                 kwargs = {}
                 if compress:


Test patch:
diff --git a/arviz/tests/test_data.py b/arviz/tests/test_data.py
--- a/arviz/tests/test_data.py
+++ b/arviz/tests/test_data.py
@@ -318,6 +318,45 @@ def test_sel_method(inplace):
             assert np.all(dataset.draw.values == np.arange(200, ndraws))
 
 
+@pytest.mark.parametrize("use", ("del", "delattr"))
+def test_del_method(use):
+    # create inference data object
+    data = np.random.normal(size=(4, 500, 8))
+    idata = from_dict(
+        posterior={"a": data[..., 0], "b": data},
+        sample_stats={"a": data[..., 0], "b": data},
+        observed_data={"b": data[0, 0, :]},
+        posterior_predictive={"a": data[..., 0], "b": data},
+    )
+
+    # assert inference data object has all attributes
+    test_dict = {
+        "posterior": ("a", "b"),
+        "sample_stats": ("a", "b"),
+        "observed_data": ["b"],
+        "posterior_predictive": ("a", "b"),
+    }
+    fails = check_multiple_attrs(test_dict, idata)
+    assert not fails
+    # assert _groups attribute contains all groups
+    groups = getattr(idata, "_groups")
+    assert all([group in groups for group in test_dict])
+
+    # Use del method
+    if use == "del":
+        del idata.sample_stats
+    else:
+        delattr(idata, "sample_stats")
+
+    # assert attribute has been removed
+    test_dict.pop("sample_stats")
+    fails = check_multiple_attrs(test_dict, idata)
+    assert not fails
+    assert not hasattr(idata, "sample_stats")
+    # assert _groups attribute has been updated
+    assert "sample_stats" not in getattr(idata, "_groups")
+
+
 class TestNumpyToDataArray:
     def test_1d_dataset(self):
         size = 100
@@ -628,37 +667,79 @@ def get_inference_data(self, data, eight_schools_params):
         )
 
     def test_io_function(self, data, eight_schools_params):
+        # create inference data and assert all attributes are present
         inference_data = self.get_inference_data(  # pylint: disable=W0612
             data, eight_schools_params
         )
-        assert hasattr(inference_data, "posterior")
+        test_dict = {
+            "posterior": ["eta", "theta", "mu", "tau"],
+            "posterior_predictive": ["eta", "theta", "mu", "tau"],
+            "sample_stats": ["eta", "theta", "mu", "tau"],
+            "prior": ["eta", "theta", "mu", "tau"],
+            "prior_predictive": ["eta", "theta", "mu", "tau"],
+            "sample_stats_prior": ["eta", "theta", "mu", "tau"],
+            "observed_data": ["J", "y", "sigma"],
+        }
+        fails = check_multiple_attrs(test_dict, inference_data)
+        assert not fails
+
+        # check filename does not exist and save InferenceData
         here = os.path.dirname(os.path.abspath(__file__))
         data_directory = os.path.join(here, "saved_models")
         filepath = os.path.join(data_directory, "io_function_testfile.nc")
         # az -function
         to_netcdf(inference_data, filepath)
+
+        # Assert InferenceData has been saved correctly
         assert os.path.exists(filepath)
         assert os.path.getsize(filepath) > 0
         inference_data2 = from_netcdf(filepath)
-        assert hasattr(inference_data2, "posterior")
+        fails = check_multiple_attrs(test_dict, inference_data2)
+        assert not fails
         os.remove(filepath)
         assert not os.path.exists(filepath)
 
-    def test_io_method(self, data, eight_schools_params):
+    @pytest.mark.parametrize("groups_arg", [False, True])
+    def test_io_method(self, data, eight_schools_params, groups_arg):
+        # create InferenceData and check it has been properly created
         inference_data = self.get_inference_data(  # pylint: disable=W0612
             data, eight_schools_params
         )
-        assert hasattr(inference_data, "posterior")
+        test_dict = {
+            "posterior": ["eta", "theta", "mu", "tau"],
+            "posterior_predictive": ["eta", "theta", "mu", "tau"],
+            "sample_stats": ["eta", "theta", "mu", "tau"],
+            "prior": ["eta", "theta", "mu", "tau"],
+            "prior_predictive": ["eta", "theta", "mu", "tau"],
+            "sample_stats_prior": ["eta", "theta", "mu", "tau"],
+            "observed_data": ["J", "y", "sigma"],
+        }
+        fails = check_multiple_attrs(test_dict, inference_data)
+        assert not fails
+
+        # check filename does not exist and use to_netcdf method
         here = os.path.dirname(os.path.abspath(__file__))
         data_directory = os.path.join(here, "saved_models")
         filepath = os.path.join(data_directory, "io_method_testfile.nc")
         assert not os.path.exists(filepath)
         # InferenceData method
-        inference_data.to_netcdf(filepath)
+        inference_data.to_netcdf(
+            filepath, groups=("posterior", "observed_data") if groups_arg else None
+        )
+
+        # assert file has been saved correctly
         assert os.path.exists(filepath)
         assert os.path.getsize(filepath) > 0
         inference_data2 = InferenceData.from_netcdf(filepath)
-        assert hasattr(inference_data2, "posterior")
+        if groups_arg:  # if groups arg, update test dict to contain only saved groups
+            test_dict = {
+                "posterior": ["eta", "theta", "mu", "tau"],
+                "observed_data": ["J", "y", "sigma"],
+            }
+            assert not hasattr(inference_data2, "sample_stats")
+        fails = check_multiple_attrs(test_dict, inference_data2)
+        assert not fails
+
         os.remove(filepath)
         assert not os.path.exists(filepath)