Repository: mie-lab/trackintel

Issue:
BUG: Duplication of certain rows in `geolife_add_modes_to_triplegs`
Running `geolife_add_modes_to_triplegs` returns a dataframe with more rows out than in (duplicates).
The `tpls_id_mode` already has an index with duplicates, so it is just passed on when joined to the `tpls` on line 257. https://github.com/mie-lab/trackintel/blob/dc9c6efe28ec0b736ee8da5a243cb86b8b83eeb2/trackintel/io/dataset_reader.py#L257

Tested with user 52 from the geolife dataset.
````python
import trackintel as ti
from trackintel.io.dataset_reader import read_geolife, geolife_add_modes_to_triplegs

pfs, mode_labels = read_geolife(r".\geolife_user52", print_progress=True)

# generate triplegs
pfs, spts = pfs.as_positionfixes.generate_staypoints(time_threshold=5.0, print_progress=True)
pfs, tpls_pre = pfs.as_positionfixes.generate_triplegs(spts)
tpls_post = geolife_add_modes_to_triplegs(tpls_pre, mode_labels)

assert len(tpls_pre) == len(tpls_post)

````

Discussion:

Patch:
diff --git a/trackintel/io/dataset_reader.py b/trackintel/io/dataset_reader.py
index 862a46c..3e87d3e 100644
--- a/trackintel/io/dataset_reader.py
+++ b/trackintel/io/dataset_reader.py
@@ -201,6 +201,11 @@ def geolife_add_modes_to_triplegs(
     tpls : GeoDataFrame (as trackintel triplegs)
         triplegs with mode labels.
 
+    Notes
+    ------
+    In the case that several labels overlap with the same tripleg the label with the highest overlap (relative to the
+    tripleg) is chosen
+
     Example
     ----------
     >>> from trackintel.io.dataset_reader import read_geolife, geolife_add_modes_to_triplegs
@@ -253,7 +258,13 @@ def geolife_add_modes_to_triplegs(
     if len(tpls_id_mode_list) == 0:
         tpls["mode"] = np.nan
     else:
-        tpls_id_mode = pd.DataFrame(tpls_id_mode_list).set_index("id")
+        tpls_id_mode = pd.DataFrame(tpls_id_mode_list)
+
+        # chose label with highest overlap
+        tpls_id_mode = tpls_id_mode.sort_values(by=["id", "ratio"])
+        # keep last (df sorted ascending)
+        tpls_id_mode = tpls_id_mode.drop_duplicates(subset="id", keep="last").set_index("id")
+
         tpls = tpls.join(tpls_id_mode)
         tpls = tpls.astype({"label_id": "Int64"})
 
@@ -262,6 +273,11 @@ def geolife_add_modes_to_triplegs(
     except KeyError:
         pass
 
+    try:
+        tpls.drop(["ratio"], axis=1, inplace=True)
+    except KeyError:
+        pass
+
     return tpls
 
 
@@ -321,7 +337,12 @@ def _calc_overlap_for_candidates(candidates, tpls_this, labels_this, ratio_thres
             if ratio_this >= ratio_threshold:
                 # assign label to tripleg (by storing matching in dictionary)
                 tpls_id_mode_list.append(
-                    {"id": potential_tripleg.name, "label_id": potential_label.name, "mode": potential_label["mode"]}
+                    {
+                        "id": potential_tripleg.name,
+                        "label_id": potential_label.name,
+                        "mode": potential_label["mode"],
+                        "ratio": ratio_this,
+                    }
                 )
 
     return tpls_id_mode_list


Test patch:
diff --git a/tests/data/geolife_modes/010/labels.txt b/tests/data/geolife_modes/010/labels.txt
index e941fd3..f7fe89e 100644
--- a/tests/data/geolife_modes/010/labels.txt
+++ b/tests/data/geolife_modes/010/labels.txt
@@ -9,12 +9,12 @@ Start Time	End Time	Transportation Mode
 2008/03/31 16:00:08	2008/03/31 16:09:01	taxi
 2008/03/31 17:26:04	2008/04/01 00:35:26	train
 2008/04/01 00:48:32	2008/04/01 00:59:23	taxi
-2008/04/01 01:00:22	2008/04/01 01:08:13	walk
+2008/04/01 01:00:22	2008/04/01 05:08:13	walk
 2008/04/01 03:46:35	2008/04/01 03:54:28	taxi
 2008/04/01 04:15:38	2008/04/01 11:06:16	train
 2008/04/01 11:22:47	2008/04/01 11:28:39	taxi
 2008/04/01 11:30:37	2008/04/01 11:35:35	walk
-2008/04/02 06:09:26	2008/04/02 06:28:25	walk
+2008/04/02 06:09:26	2008/04/02 10:28:25	walk
 2008/04/02 06:30:57	2008/04/02 06:34:20	taxi
 2008/04/02 11:24:21	2008/04/02 11:50:45	bus
 2008/04/03 01:07:03	2008/04/03 11:31:55	train
@@ -24,7 +24,7 @@ Start Time	End Time	Transportation Mode
 2008/04/03 13:55:24	2008/04/03 14:01:49	taxi
 2008/04/03 15:31:51	2008/04/03 15:59:59	train
 2008/04/03 16:00:00	2008/04/04 04:13:22	train
-2008/04/04 04:27:25	2008/04/04 04:30:50	walk
+2008/04/04 04:27:25	2008/04/04 10:30:50	walk
 2008/04/04 04:31:28	2008/04/04 05:03:35	taxi
 2008/04/04 07:16:50	2008/04/04 07:53:00	taxi
 2008/04/04 08:49:13	2008/04/04 15:59:59	train
diff --git a/tests/io/test_dataset_reader.py b/tests/io/test_dataset_reader.py
index ab52372..8c08828 100644
--- a/tests/io/test_dataset_reader.py
+++ b/tests/io/test_dataset_reader.py
@@ -155,6 +155,16 @@ class TestGeolife_add_modes_to_triplegs:
 
         assert "started_at_s" not in tpls.columns
 
+    def test_no_overlap(self, read_geolife_triplegs_with_modes):
+        """Test that overlapping labels are not causing duplicate ids.
+        user 10 was modified to have several overlapping labels"""
+
+        tpls, labels = read_geolife_triplegs_with_modes
+        tpls = geolife_add_modes_to_triplegs(tpls, labels)
+
+        assert tpls.index.is_unique
+        assert not tpls.duplicated(subset=["started_at", "finished_at"]).any()
+
     def test_mode_matching(self, matching_data):
         # bring label data into right format. All labels belong to the same user
         tpls, labels_raw = matching_data