Repository: deshima-dev/decode

Issue:
Add utility to get phase label of 1D DataArray

Discussion:

Patch:
diff --git a/CITATION.cff b/CITATION.cff
index b1d2ddd..86816f7 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -3,8 +3,8 @@ message: "If you use this software, please cite it as below."
 
 title: "de:code"
 abstract: "DESHIMA code for data analysis"
-version: 2024.8.3
-date-released: 2024-08-10
+version: 2024.8.4
+date-released: 2024-08-17
 license: "MIT"
 doi: "10.5281/zenodo.3384216"
 url: "https://github.com/deshima-dev/decode"
diff --git a/README.md b/README.md
index 089e025..8abdf64 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ DESHIMA code for data analysis
 ## Installation
 
 ```shell
-pip install decode==2024.8.3
+pip install decode==2024.8.4
 ```
 
 ## Quick look
diff --git a/decode/__init__.py b/decode/__init__.py
index 9c18d27..3378233 100644
--- a/decode/__init__.py
+++ b/decode/__init__.py
@@ -10,7 +10,7 @@ __all__ = [
     "select",
     "utils",
 ]
-__version__ = "2024.8.3"
+__version__ = "2024.8.4"
 
 
 # submodules
diff --git a/decode/utils.py b/decode/utils.py
index 11df1a6..606ddf7 100644
--- a/decode/utils.py
+++ b/decode/utils.py
@@ -1,4 +1,4 @@
-__all__ = ["mad"]
+__all__ = ["mad", "phaseof"]
 
 
 # dependencies
@@ -38,3 +38,35 @@ def mad(
         )
 
     return median(cast(xr.DataArray, np.abs(da - median(da))))
+
+
+def phaseof(
+    da: xr.DataArray,
+    /,
+    *,
+    keep_attrs: bool = False,
+    keep_coords: bool = False,
+) -> xr.DataArray:
+    """Assign a phase to each value in a 1D DataArray.
+
+    The function assigns a unique phase (int64) to consecutive
+    identical values in the DataArray. The phase increases
+    sequentially whenever the value in the DataArray changes.
+
+    Args:
+        da: Input 1D DataArray.
+        keep_attrs: Whether to keep attributes of the input.
+        keep_coords: Whether to keep coordinates of the input.
+
+    Returns:
+        1D int64 DataArray of phases.
+
+    """
+    if da.ndim != 1:
+        raise ValueError("Input DataArray must be 1D.")
+
+    is_transision = xr.zeros_like(da, bool)
+    is_transision.data[1:] = da.data[1:] != da.data[:-1]
+
+    phase = is_transision.cumsum(keep_attrs=keep_attrs)
+    return phase if keep_coords else phase.reset_coords(drop=True)
diff --git a/pyproject.toml b/pyproject.toml
index e9aae3a..c164263 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "decode"
-version = "2024.8.3"
+version = "2024.8.4"
 description = "DESHIMA code for data analysis"
 authors = [
     "Akio Taniguchi <taniguchi@a.phys.nagoya-u.ac.jp>",


Test patch:
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 177a62c..2415773 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -8,3 +8,9 @@ from dems.d2 import MS
 def test_mad() -> None:
     dems = MS.new(np.arange(25).reshape(5, 5))
     assert (utils.mad(dems, "time") == 5.0).all()
+
+
+def test_phaseof() -> None:
+    tester = xr.DataArray([0, 1, 1, 2, 2, 2, 1, 0])
+    expected = xr.DataArray([0, 1, 1, 2, 2, 2, 3, 4])
+    assert (utils.phaseof(tester) == expected).all()