From 98b1b4f22450bc890a2175ab5ed65051b3ff2e16 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 30 Sep 2023 11:42:06 -0700 Subject: [PATCH 01/15] Add a `.drop_attrs` method Part of #3891 --- xarray/core/dataarray.py | 10 ++++++++++ xarray/core/dataset.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index ef4389f3c6c..0d52b25c52e 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -7097,3 +7097,13 @@ def to_dask_dataframe( # this needs to be at the end, or mypy will confuse with `str` # https://mypy.readthedocs.io/en/latest/common_issues.html#dealing-with-conflicting-names str = utils.UncachedAccessor(StringAccessor["DataArray"]) + + def drop_attrs(self) -> Self: + """ + Removes all attributes from the DataArray. + """ + self = self.copy() + + self.attrs = {} + + return self diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 459e2f3fce7..30580a95794 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10300,3 +10300,18 @@ def resample( restore_coord_dims=restore_coord_dims, **indexer_kwargs, ) + + def drop_attrs(self) -> Self: + """ + Removes all attributes from the Dataset and its variables. + """ + # Remove attributes from the dataset + self = self.copy() + + self.attrs = {} + + # Remove attributes from each variable in the dataset + for var in self.variables: + self[var].attrs = {} + + return self From 0adfd02f882ab408961f0eb0f3c28e0fb51bade9 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 8 Oct 2023 14:06:08 -0700 Subject: [PATCH 02/15] Add tests --- xarray/tests/test_dataarray.py | 5 +++++ xarray/tests/test_dataset.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/xarray/tests/test_dataarray.py b/xarray/tests/test_dataarray.py index d497cd5a54d..b75c4e4a517 100644 --- a/xarray/tests/test_dataarray.py +++ b/xarray/tests/test_dataarray.py @@ -2955,6 +2955,11 @@ def test_assign_attrs(self) -> None: assert_identical(new_actual, expected) assert actual.attrs == {"a": 1, "b": 2} + def test_drop_attrs(self) -> None: + # Mostly tested in test_dataset.py, but adding a very small test here + da = DataArray([], attrs=dict(a=1, b=2)) + assert da.drop_attrs().attrs == {} + @pytest.mark.parametrize( "func", [lambda x: x.clip(0, 1), lambda x: np.float64(1.0) * x, np.abs, abs] ) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 3841398ff75..c4c94e5f102 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4355,6 +4355,32 @@ def test_assign_attrs(self) -> None: assert_identical(new_actual, expected) assert actual.attrs == dict(a=1, b=2) + def test_drop_attrs(self) -> None: + # Simple example + ds = Dataset().assign_attrs(a=1, b=2) + original = ds.copy() + expected = Dataset() + result = ds.drop_attrs() + assert_identical(result, expected) + + # Doesn't change original + assert_identical(ds, original) + + # Example with variables and coords with attrs, check they're dropped too + var = Variable("x", [1, 2, 3], attrs=dict(x=1, y=2)) + idx = IndexVariable("y", [1, 2, 3], attrs=dict(c=1, d=2)) + ds = Dataset(dict(x=var), coords=dict(y=idx)).assign_attrs(a=1, b=2) + original = ds.copy(deep=True) + + result = ds.drop_attrs() + + assert result.attrs == {} + assert result["x"].attrs == {} + assert result["y"].attrs == {} + + # Doesn't change original + assert_identical(ds, original) + def test_assign_multiindex_level(self) -> None: data = create_test_multiindex() with pytest.raises(ValueError, match=r"cannot drop or update.*corrupt.*index "): From 40ffa5c936d7a23f956e73323e7faf723b2542a8 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 8 Oct 2023 14:10:08 -0700 Subject: [PATCH 03/15] Add explicit coords test --- xarray/tests/test_dataset.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index c4c94e5f102..708e62630ec 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4370,8 +4370,9 @@ def test_drop_attrs(self) -> None: var = Variable("x", [1, 2, 3], attrs=dict(x=1, y=2)) idx = IndexVariable("y", [1, 2, 3], attrs=dict(c=1, d=2)) ds = Dataset(dict(x=var), coords=dict(y=idx)).assign_attrs(a=1, b=2) - original = ds.copy(deep=True) + assert ds.coords["y"].attrs != {} + original = ds.copy(deep=True) result = ds.drop_attrs() assert result.attrs == {} @@ -4380,6 +4381,9 @@ def test_drop_attrs(self) -> None: # Doesn't change original assert_identical(ds, original) + # Specifically test that the attrs on the coords are still there. (The index + # can't currently contain `attrs`, so we can't test those.) + assert ds.coords["y"].attrs != {} def test_assign_multiindex_level(self) -> None: data = create_test_multiindex() From 12523644a4b289c34825ced724c8e1d17efea495 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sun, 8 Oct 2023 14:23:02 -0700 Subject: [PATCH 04/15] Use `._replace` for half the method --- xarray/core/dataset.py | 7 ++++--- xarray/tests/test_dataset.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index b8e7bf9b73f..952e10c6127 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10344,12 +10344,13 @@ def drop_attrs(self) -> Self: Removes all attributes from the Dataset and its variables. """ # Remove attributes from the dataset - self = self.copy() - - self.attrs = {} + self = self._replace(attrs={}) # Remove attributes from each variable in the dataset for var in self.variables: + # variables don't have a `._replace` method, so we copy and then remove. If + # we added a `._replace` method, we could use that instead. + self[var] = self[var].copy() self[var].attrs = {} return self diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 708e62630ec..5d18d5e12d5 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4369,7 +4369,7 @@ def test_drop_attrs(self) -> None: # Example with variables and coords with attrs, check they're dropped too var = Variable("x", [1, 2, 3], attrs=dict(x=1, y=2)) idx = IndexVariable("y", [1, 2, 3], attrs=dict(c=1, d=2)) - ds = Dataset(dict(x=var), coords=dict(y=idx)).assign_attrs(a=1, b=2) + ds = Dataset(dict(var1=var), coords=dict(y=idx)).assign_attrs(a=1, b=2) assert ds.coords["y"].attrs != {} original = ds.copy(deep=True) @@ -4378,6 +4378,8 @@ def test_drop_attrs(self) -> None: assert result.attrs == {} assert result["x"].attrs == {} assert result["y"].attrs == {} + assert list(result.data_vars) == list(ds.data_vars) + assert list(result.coords) == list(ds.coords) # Doesn't change original assert_identical(ds, original) From 10ab1237ce77ee518d56e0c8fbf65eaf43523153 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 14 Oct 2023 12:23:39 -0700 Subject: [PATCH 05/15] . --- xarray/core/dataset.py | 27 +++++++++++++++++++++++---- xarray/tests/test_dataset.py | 9 +++++++-- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index b55efadcdb2..076c9f51793 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10342,15 +10342,34 @@ def resample( def drop_attrs(self) -> Self: """ Removes all attributes from the Dataset and its variables. + + Returns + ------- + Dataset """ # Remove attributes from the dataset self = self._replace(attrs={}) # Remove attributes from each variable in the dataset for var in self.variables: - # variables don't have a `._replace` method, so we copy and then remove. If - # we added a `._replace` method, we could use that instead. - self[var] = self[var].copy() - self[var].attrs = {} + # variables don't have a `._replace` method, so we copy and then remove + # attrs. If we added a `._replace` method, we could use that instead. + if var not in self.indexes: + self[var] = self[var].copy() + self[var].attrs = {} + + new_idx_variables = {} + + # Not sure this is the most elegant way of doing this, but it works. + # (Contributions welcome for a more general "map over all variables, including + # indexes" approach.) + for idx, idx_vars in self.xindexes.group_by_index(): + # copy each coordinate variable of an index and drop their attrs + temp_idx_variables = {k: v.copy() for k, v in idx_vars.items()} + for v in temp_idx_variables.values(): + v.attrs = {} + # maybe re-wrap the index object in new coordinate variables + new_idx_variables.update(idx.create_variables(temp_idx_variables)) + self = self.assign(**new_idx_variables) return self diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index 67de33ba615..fd575a6171c 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4366,10 +4366,15 @@ def test_drop_attrs(self) -> None: # Doesn't change original assert_identical(ds, original) - # Example with variables and coords with attrs, check they're dropped too + # Example with variables and coords with attrs, and a multiindex. (arguably + # should have used a canonical dataset with all the features we're should + # support...) var = Variable("x", [1, 2, 3], attrs=dict(x=1, y=2)) idx = IndexVariable("y", [1, 2, 3], attrs=dict(c=1, d=2)) - ds = Dataset(dict(var1=var), coords=dict(y=idx)).assign_attrs(a=1, b=2) + mx = xr.Coordinates.from_pandas_multiindex( + pd.MultiIndex.from_tuples([(1, 2), (3, 4)], names=["d", "e"]), "z" + ) + ds = Dataset(dict(var1=var), coords=dict(y=idx, z=mx)).assign_attrs(a=1, b=2) assert ds.coords["y"].attrs != {} original = ds.copy(deep=True) From 8baff449fa849bfa8936fa2718583a1469b6faef Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Sat, 14 Oct 2023 23:49:58 -0700 Subject: [PATCH 06/15] --- xarray/core/dataarray.py | 4 ++++ xarray/core/dataset.py | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 3142c518c0f..035179393e0 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -7150,6 +7150,10 @@ def to_dask_dataframe( def drop_attrs(self) -> Self: """ Removes all attributes from the DataArray. + + Returns + ------- + DataArray """ self = self.copy() diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 076c9f51793..97a7c0f8de8 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10361,14 +10361,14 @@ def drop_attrs(self) -> Self: new_idx_variables = {} # Not sure this is the most elegant way of doing this, but it works. - # (Contributions welcome for a more general "map over all variables, including - # indexes" approach.) + # (Should we have a more general "map over all variables, including + # indexes" approach?) for idx, idx_vars in self.xindexes.group_by_index(): # copy each coordinate variable of an index and drop their attrs temp_idx_variables = {k: v.copy() for k, v in idx_vars.items()} for v in temp_idx_variables.values(): v.attrs = {} - # maybe re-wrap the index object in new coordinate variables + # re-wrap the index object in new coordinate variables new_idx_variables.update(idx.create_variables(temp_idx_variables)) self = self.assign(**new_idx_variables) From cf03f4b1fe4c760374b45d9ae5d8ae36baf32e77 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 17 Oct 2023 11:14:44 -0700 Subject: [PATCH 07/15] Add a `deep` kwarg (default `True`?) --- xarray/core/dataarray.py | 13 +++++++------ xarray/core/dataset.py | 11 +++++++++-- xarray/tests/test_dataset.py | 13 ++++++++++++- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 035179393e0..0dbfe3f8837 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -7147,16 +7147,17 @@ def to_dask_dataframe( # https://mypy.readthedocs.io/en/latest/common_issues.html#dealing-with-conflicting-names str = utils.UncachedAccessor(StringAccessor["DataArray"]) - def drop_attrs(self) -> Self: + def drop_attrs(self, deep: bool = True) -> Self: """ Removes all attributes from the DataArray. + Parameters + ---------- + deep : bool, default True + Removes attributes from coordinates. + Returns ------- DataArray """ - self = self.copy() - - self.attrs = {} - - return self + return self._to_temp_dataset().drop_attrs(deep=deep).to_array() diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 97a7c0f8de8..0217c414c59 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10339,10 +10339,15 @@ def resample( **indexer_kwargs, ) - def drop_attrs(self) -> Self: + def drop_attrs(self, deep: bool = True) -> Self: """ Removes all attributes from the Dataset and its variables. + Parameters + ---------- + deep : bool, default True + Removes attributes from all variables. + Returns ------- Dataset @@ -10350,6 +10355,9 @@ def drop_attrs(self) -> Self: # Remove attributes from the dataset self = self._replace(attrs={}) + if not deep: + return self + # Remove attributes from each variable in the dataset for var in self.variables: # variables don't have a `._replace` method, so we copy and then remove @@ -10359,7 +10367,6 @@ def drop_attrs(self) -> Self: self[var].attrs = {} new_idx_variables = {} - # Not sure this is the most elegant way of doing this, but it works. # (Should we have a more general "map over all variables, including # indexes" approach?) diff --git a/xarray/tests/test_dataset.py b/xarray/tests/test_dataset.py index fd575a6171c..82c9be0eece 100644 --- a/xarray/tests/test_dataset.py +++ b/xarray/tests/test_dataset.py @@ -4375,13 +4375,16 @@ def test_drop_attrs(self) -> None: pd.MultiIndex.from_tuples([(1, 2), (3, 4)], names=["d", "e"]), "z" ) ds = Dataset(dict(var1=var), coords=dict(y=idx, z=mx)).assign_attrs(a=1, b=2) + assert ds.attrs != {} + assert ds["var1"].attrs != {} + assert ds["y"].attrs != {} assert ds.coords["y"].attrs != {} original = ds.copy(deep=True) result = ds.drop_attrs() assert result.attrs == {} - assert result["x"].attrs == {} + assert result["var1"].attrs == {} assert result["y"].attrs == {} assert list(result.data_vars) == list(ds.data_vars) assert list(result.coords) == list(ds.coords) @@ -4392,6 +4395,14 @@ def test_drop_attrs(self) -> None: # can't currently contain `attrs`, so we can't test those.) assert ds.coords["y"].attrs != {} + # Test for deep=False + result_shallow = ds.drop_attrs(deep=False) + assert result_shallow.attrs == {} + assert result_shallow["var1"].attrs != {} + assert result_shallow["y"].attrs != {} + assert list(result.data_vars) == list(ds.data_vars) + assert list(result.coords) == list(ds.coords) + def test_assign_multiindex_level(self) -> None: data = create_test_multiindex() with pytest.raises(ValueError, match=r"cannot drop or update.*corrupt.*index "): From e0201072d790ec603e56de6ec7bdbc747748bff2 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Tue, 17 Oct 2023 12:22:10 -0700 Subject: [PATCH 08/15] --- xarray/core/dataarray.py | 5 ++++- xarray/core/dataset.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 7a8850f2abd..2eb3b14fb38 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -11,6 +11,7 @@ Generic, Literal, NoReturn, + cast, overload, ) @@ -7166,4 +7167,6 @@ def drop_attrs(self, deep: bool = True) -> Self: ------- DataArray """ - return self._to_temp_dataset().drop_attrs(deep=deep).to_array() + return ( + self._to_temp_dataset().drop_attrs(deep=deep).pipe(self._from_temp_dataset) + ) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index 93bef182aef..2b81f342a19 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10383,6 +10383,6 @@ def drop_attrs(self, deep: bool = True) -> Self: v.attrs = {} # re-wrap the index object in new coordinate variables new_idx_variables.update(idx.create_variables(temp_idx_variables)) - self = self.assign(**new_idx_variables) + self = self.assign(new_idx_variables) return self From e02562e6c5659645f5bf8270d36c27c2bfac8e65 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:24:04 +0000 Subject: [PATCH 09/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray/core/dataarray.py | 1 - 1 file changed, 1 deletion(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 2eb3b14fb38..aafddb668ab 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -11,7 +11,6 @@ Generic, Literal, NoReturn, - cast, overload, ) From 02a92173ddcf0cdad4ce1e5f6b3175cc95dfe61a Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Thu, 19 Oct 2023 10:38:22 -0700 Subject: [PATCH 10/15] api --- doc/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/api.rst b/doc/api.rst index 96b4864804f..4c218d261ee 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -111,6 +111,7 @@ Dataset contents Dataset.drop_duplicates Dataset.drop_dims Dataset.drop_encoding + Dataset.drop_attrs Dataset.set_coords Dataset.reset_coords Dataset.convert_calendar @@ -304,6 +305,7 @@ DataArray contents DataArray.drop_indexes DataArray.drop_duplicates DataArray.drop_encoding + DataArray.drop_attrs DataArray.reset_coords DataArray.copy DataArray.convert_calendar From 4f4b878a7efae63537e011e4581c98a3fbd78c30 Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Thu, 19 Oct 2023 10:42:20 -0700 Subject: [PATCH 11/15] --- doc/whats-new.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 120339ff90e..a23e65c765b 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -14,7 +14,18 @@ What's New np.random.seed(123456) -.. _whats-new.2023.10.0: +.. _whats-new.2023.11.0: + +v2023.11.0 (unreleased) +----------------------- + +New Features +~~~~~~~~~~~~ + +- Add :py:meth:`DataArray.drop_attrs` & :py:meth:`Dataset.drop_attrs` methods, + to return an object without ``attrs``. A ``deep`` parameter controls whether + variables' ``attrs`` are also dropped. + By `Maximilian Roos `_. (:pull:`8288`) v2023.10.0 (19 Oct, 2023) ------------------------- From 26e14272696650baa9ff9bd2bc6b6d36e7d2427e Mon Sep 17 00:00:00 2001 From: Maximilian Roos Date: Thu, 19 Oct 2023 12:08:09 -0700 Subject: [PATCH 12/15] --- doc/whats-new.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index a23e65c765b..9734b24a8f3 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -27,6 +27,8 @@ New Features variables' ``attrs`` are also dropped. By `Maximilian Roos `_. (:pull:`8288`) +.. _whats-new.2023.10.0: + v2023.10.0 (19 Oct, 2023) ------------------------- From 3733ccd2120b1b927c110b5ed76af0635b78baf6 Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:21:24 -0700 Subject: [PATCH 13/15] Update xarray/core/dataarray.py Co-authored-by: Michael Niklas --- xarray/core/dataarray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/dataarray.py b/xarray/core/dataarray.py index 955db62b198..53e9b47bfae 100644 --- a/xarray/core/dataarray.py +++ b/xarray/core/dataarray.py @@ -7411,7 +7411,7 @@ def to_dask_dataframe( # https://mypy.readthedocs.io/en/latest/common_issues.html#dealing-with-conflicting-names str = utils.UncachedAccessor(StringAccessor["DataArray"]) - def drop_attrs(self, deep: bool = True) -> Self: + def drop_attrs(self, *, deep: bool = True) -> Self: """ Removes all attributes from the DataArray. From 6e8085f852a99a519569846db0ed6dc8eb0d334b Mon Sep 17 00:00:00 2001 From: Maximilian Roos <5635139+max-sixty@users.noreply.github.com> Date: Thu, 11 Jul 2024 10:22:51 -0700 Subject: [PATCH 14/15] Update xarray/core/dataset.py Co-authored-by: Michael Niklas --- xarray/core/dataset.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray/core/dataset.py b/xarray/core/dataset.py index a4cdf3c6e1e..3930b12ef3d 100644 --- a/xarray/core/dataset.py +++ b/xarray/core/dataset.py @@ -10681,7 +10681,7 @@ def resample( **indexer_kwargs, ) - def drop_attrs(self, deep: bool = True) -> Self: + def drop_attrs(self, *, deep: bool = True) -> Self: """ Removes all attributes from the Dataset and its variables. From 878c527d681d3a14889711d686be58ada822ebd3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 17:23:07 +0000 Subject: [PATCH 15/15] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- doc/whats-new.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/whats-new.rst b/doc/whats-new.rst index 90bdb52ddc8..6a8e898c93c 100644 --- a/doc/whats-new.rst +++ b/doc/whats-new.rst @@ -30,7 +30,7 @@ New Features to return an object without ``attrs``. A ``deep`` parameter controls whether variables' ``attrs`` are also dropped. By `Maximilian Roos `_. (:pull:`8288`) - + Breaking changes ~~~~~~~~~~~~~~~~