Repository: mie-lab/trackintel

Issue:
location_identifier raises key error if all staypoints are pre filtered
If the tracking quality is bad and all staypoints are pre-filtered, then the variable `method_val` does not get the column purpose and therefore [the line 74](https://github.com/mie-lab/trackintel/blob/master/trackintel/analysis/location_identification.py#L74) raises a key error. 

I think the behavior should be that the staypoints are returned with a nan-filled or empty purpose column.

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/mie-lab/trackintel/pull/497?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) Report
Patch coverage: **`100.00%`** and no project coverage change.
> Comparison is base [(`4e76060`)](https://app.codecov.io/gh/mie-lab/trackintel/commit/4e76060bf415c477f688b930b1a9aa1a0f7da5aa?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) 92.33% compared to head [(`c8e0475`)](https://app.codecov.io/gh/mie-lab/trackintel/pull/497?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) 92.33%.

<details><summary>Additional details and impacted files</summary>


```diff
@@           Coverage Diff           @@
##           master     #497   +/-   ##
=======================================
  Coverage   92.33%   92.33%           
=======================================
  Files          39       39           
  Lines        2008     2010    +2     
  Branches      341      342    +1     
=======================================
+ Hits         1854     1856    +2     
  Misses        133      133           
  Partials       21       21           
```


| [Files Changed](https://app.codecov.io/gh/mie-lab/trackintel/pull/497?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab) | Coverage Δ | |
|---|---|---|
| [trackintel/analysis/location\_identification.py](https://app.codecov.io/gh/mie-lab/trackintel/pull/497?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab#diff-dHJhY2tpbnRlbC9hbmFseXNpcy9sb2NhdGlvbl9pZGVudGlmaWNhdGlvbi5weQ==) | `99.06% <100.00%> (+0.01%)` | :arrow_up: |


</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/mie-lab/trackintel/pull/497?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=mie-lab).


Patch:
diff --git a/trackintel/analysis/location_identification.py b/trackintel/analysis/location_identification.py
index e469491..79bafc6 100644
--- a/trackintel/analysis/location_identification.py
+++ b/trackintel/analysis/location_identification.py
@@ -197,6 +197,8 @@ def freq_method(staypoints, *labels):
             group["duration"] = group["finished_at"] - group["started_at"]
         # pandas keeps inner order of groups
         sp.loc[sp["user_id"] == name, "purpose"] = _freq_transform(group, *labels)
+    if "purpose" not in sp.columns:  # if empty sp
+        sp["purpose"] = None
     return sp
 
 
diff --git a/trackintel/io/file.py b/trackintel/io/file.py
index 4d2263d..9e9743a 100644
--- a/trackintel/io/file.py
+++ b/trackintel/io/file.py
@@ -1,3 +1,4 @@
+import ast
 import warnings
 from functools import wraps
 from inspect import signature
@@ -555,6 +556,7 @@ def read_tours_csv(*args, columns=None, index_col=None, tz=None, **kwargs):
     >>> trackintel.read_tours_csv('data.csv', columns={'uuid':'user_id'})
     """
     columns = {} if columns is None else columns
+    kwargs.setdefault("converters", {}).setdefault("trips", ast.literal_eval)
     tours = pd.read_csv(*args, index_col=index_col, **kwargs)
     tours.rename(columns=columns, inplace=True)
 


Test patch:
diff --git a/tests/analysis/test_location_identification.py b/tests/analysis/test_location_identification.py
index af0917c..dd4965b 100644
--- a/tests/analysis/test_location_identification.py
+++ b/tests/analysis/test_location_identification.py
@@ -182,6 +182,13 @@ class TestFreq_method:
         assert freq["purpose"].count() == example_freq["purpose"].count()
         assert_geodataframe_equal(example_freq, freq)
 
+    def test_empty_sp(self, example_freq):
+        """Test if empty sp also get purpose column."""
+        example_freq.drop(example_freq.index, inplace=True)
+        freq = freq_method(example_freq)
+        example_freq["purpose"] = None
+        assert_geodataframe_equal(example_freq, freq)
+
 
 class Test_Freq_Transform:
     """Test help function _freq_transform."""
diff --git a/tests/io/test_file.py b/tests/io/test_file.py
index fc78e63..01a50f0 100644
--- a/tests/io/test_file.py
+++ b/tests/io/test_file.py
@@ -2,6 +2,7 @@ import filecmp
 import os
 import pytest
 import pandas as pd
+from pandas.testing import assert_frame_equal
 
 import trackintel as ti
 
@@ -312,6 +313,25 @@ class TestTrips:
         assert gdf.index.name is None
 
 
+@pytest.fixture
+def example_tours():
+    """Tours to load into the database."""
+    t1 = pd.Timestamp("1971-01-01 00:00:00", tz="utc")
+    t2 = pd.Timestamp("1971-01-01 05:00:00", tz="utc")
+    t3 = pd.Timestamp("1971-01-02 07:00:00", tz="utc")
+    h = pd.Timedelta(hours=1)
+
+    list_dict = [
+        {"user_id": 0, "started_at": t1, "finished_at": t1 + h, "trips": [0, 1, 2]},
+        {"user_id": 0, "started_at": t2, "finished_at": t2 + h, "trips": [2, 3, 4]},
+        {"user_id": 1, "started_at": t3, "finished_at": t3 + h, "trips": [4, 5, 6]},
+    ]
+    tours = pd.DataFrame(data=list_dict)
+    tours.index.name = "id"
+    tours.as_tours
+    return tours
+
+
 class TestTours:
     """Test for 'read_tours_csv' and 'write_tours_csv' functions."""
 
@@ -325,6 +345,14 @@ class TestTours:
         assert filecmp.cmp(orig_file, tmp_file, shallow=False)
         os.remove(tmp_file)
 
+    def test_to_from_csv(self, example_tours):
+        """Test writing then reading functionality."""
+        tmp_file = os.path.join("tests", "data", "tours_test.csv")
+        example_tours.as_tours.to_csv(tmp_file)
+        read_tours = ti.read_tours_csv(tmp_file, index_col="id")
+        os.remove(tmp_file)
+        assert_frame_equal(example_tours, read_tours)
+
     def test_to_csv_accessor(self):
         """Test basic reading and writing functions."""
         orig_file = os.path.join("tests", "data", "geolife_long", "tours.csv")
@@ -334,7 +362,3 @@ class TestTours:
         tours.as_tours.to_csv(tmp_file)
         assert filecmp.cmp(orig_file, tmp_file, shallow=False)
         os.remove(tmp_file)
-
-    def test_from_to_postgis(self):
-        # TODO Implement some tests for reading and writing tours.
-        pass