Repository: ul-mds/gecko

Issue:
Add mutator that repeats values inside a series
E.g. for repeated names.

Discussion:

Solution patch:
diff --git a/docs/data-mutation.md b/docs/data-mutation.md
index 612b05c..3a5224b 100644
--- a/docs/data-mutation.md
+++ b/docs/data-mutation.md
@@ -567,6 +567,30 @@ print(datetime_mutator([srs]))
 # => ["2020-01-01", "2020-01-03", ..., "2020-01-25", "2020-01-31"]
 ```
 
+### Repeated values
+
+Erroneous copy-paste operations may yield an unwanted duplication of values.
+This is implemented in Gecko's `with_repeat` mutator.
+By default, it appends values with a space, but a custom joining character can be defined as well.
+
+```python
+import numpy as np
+import pandas as pd
+
+from gecko import mutator
+
+srs = pd.Series(["foo", "bar", "baz"])
+
+repeat_mutator = mutator.with_repeat()
+repeat_mutator_no_space = mutator.with_repeat(join_with="")
+
+print(repeat_mutator([srs]))
+# => ["foo foo", "bar bar", "baz baz"]
+
+print(repeat_mutator_no_space([srs]))
+# => ["foofoo", "barbar", "bazbaz"]
+```
+
 ### Using generators
 
 `with_generator` can leverage Gecko's mutators to prepend, append or replace data.
diff --git a/gecko/mutator.py b/gecko/mutator.py
index 6404097..64fb2f7 100644
--- a/gecko/mutator.py
+++ b/gecko/mutator.py
@@ -23,6 +23,7 @@
     "with_datetime_offset",
     "with_generator",
     "with_regex_replacement_table",
+    "with_repeat",
     "mutate_data_frame",
 ]
 
@@ -1509,6 +1510,29 @@ def _mutate(srs_lst: list[pd.Series]) -> list[pd.Series]:
     return _mutate
 
 
+def with_repeat(join_with: str = " ") -> Mutator:
+    """
+    Mutate data in a series by repeating it.
+    By default, it is appended with a whitespace.
+
+    Args:
+        join_with: joining character to use, space by default
+
+    Returns:
+        function returning list with repeated series values
+    """
+
+    def _mutate_series(srs: pd.Series) -> pd.Series:
+        srs_out = srs.copy()
+
+        return srs_out + join_with + srs_out
+
+    def _mutate(srs_lst: list[pd.Series]) -> list[pd.Series]:
+        return [_mutate_series(srs) for srs in srs_lst]
+
+    return _mutate
+
+
 def mutate_data_frame(
     df_in: pd.DataFrame,
     column_to_mutator_list: list[


Test patch:
diff --git a/tests/test_mutator.py b/tests/test_mutator.py
index b8713aa..bee84e4 100644
--- a/tests/test_mutator.py
+++ b/tests/test_mutator.py
@@ -26,6 +26,7 @@
     with_datetime_offset,
     with_generator,
     with_regex_replacement_table,
+    with_repeat,
 )
 from tests.helpers import get_asset_path
 
@@ -563,6 +564,22 @@ def test_with_regex_replacement_table_flags(rng):
     assert (srs_mutated == pd.Series(["foobaz", "Foobaz", "fOoBaz"])).all()
 
 
+def test_with_repeat():
+    srs = pd.Series(["foo"] * 100)
+    mutate_repeat = with_repeat()
+
+    (srs_mutated,) = mutate_repeat([srs])
+    assert (srs_mutated == "foo foo").all()
+
+
+def test_with_repeat_custom_join():
+    srs = pd.Series(["foo"] * 100)
+    mutate_repeat = with_repeat(join_with="")
+
+    (srs_mutated,) = mutate_repeat([srs])
+    assert (srs_mutated == "foofoo").all()
+
+
 def test_mutate_data_frame_single(rng):
     df = pd.DataFrame({"foo": list(string.ascii_letters)})
     df_mut = mutate_data_frame(
@@ -760,6 +777,7 @@ def test_mutate_data_frame_multicolumn_noop():
         (1, with_generator(_generate_static("foo"), "prepend")),
         (1, with_generator(_generate_static("foo"), "append")),
         (1, with_generator(_generate_static("foo"), "replace")),
+        (1, with_repeat()),
     ],
 )
 def test_mutator_no_modify(num_srs: int, func: Mutator, rng):