Repository: mie-lab/trackintel

Issue:
Positionfix aggregation by tripleg id fails if user_id is a string
The following minimal working example triggers an exception in `generate_triplegs` because `user_id` cannot be aggregated with the `mean` method:

```python
import pandas as pd
import geopandas as gpd
import pandas as pd
from trackintel.preprocessing.positionfixes import (
    generate_staypoints,
    generate_triplegs,
)
from trackintel.preprocessing.staypoints import generate_locations


positionfixes = gpd.GeoDataFrame(
    {
        "user_id": "test",
        "tracked_at": pd.date_range("2023-01-01T00:00:00Z", freq="60s", periods=10),
    },
    geometry=gpd.points_from_xy([0] * 5 + [1] * 5, [0] * 5 + [1] * 5),
    crs="EPSG:4326",
)

positionfixes, staypoints = generate_staypoints(positionfixes)
staypoints, _ = generate_locations(staypoints)
positionfixes, triplegs = generate_triplegs(positionfixes)
```
Traceback:
```python
Traceback (most recent call last):
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/groupby.py", line 1490, in array_func
    result = self.grouper._cython_operation(
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 959, in _cython_operation
    return cy_op.cython_operation(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 657, in cython_operation
    return self._cython_op_ndim_compat(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 482, in _cython_op_ndim_compat
    res = self._call_cython_op(
          ^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 541, in _call_cython_op
    func = self._get_cython_function(self.kind, self.how, values.dtype, is_numeric)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 173, in _get_cython_function
    raise NotImplementedError(
NotImplementedError: function is not implemented for this dtype: [how->mean,dtype->object]

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 1692, in _ensure_numeric
    x = float(x)
        ^^^^^^^^
ValueError: could not convert string to float: 'testtesttesttesttest'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 1696, in _ensure_numeric
    x = complex(x)
        ^^^^^^^^^^
ValueError: complex() arg is a malformed string

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/kpenner/dev/trackintel/test.py", line 12, in <module>
    positionfixes, triplegs = generate_triplegs(positionfixes)
                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/trackintel/preprocessing/positionfixes.py", line 364, in generate_triplegs
    tpls = posfix_grouper.agg(
           ^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/generic.py", line 1269, in aggregate
    result = op.agg()
             ^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/apply.py", line 163, in agg
    return self.agg_dict_like()
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/apply.py", line 420, in agg_dict_like
    results = {
              ^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/apply.py", line 421, in <dictcomp>
    key: obj._gotitem(key, ndim=1).agg(how) for key, how in arg.items()
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/generic.py", line 238, in aggregate
    ret = self._aggregate_multiple_funcs(func, *args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/generic.py", line 316, in _aggregate_multiple_funcs
    results[key] = self.aggregate(func, *args, **kwargs)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/generic.py", line 232, in aggregate
    return getattr(self, func)(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/groupby.py", line 1855, in mean
    result = self._cython_agg_general(
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/groupby.py", line 1507, in _cython_agg_general
    new_mgr = data.grouped_reduce(array_func)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/internals/base.py", line 197, in grouped_reduce
    res = func(arr)
          ^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/groupby.py", line 1503, in array_func
    result = self._agg_py_fallback(values, ndim=data.ndim, alt=alt)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/groupby.py", line 1457, in _agg_py_fallback
    res_values = self.grouper.agg_series(ser, alt, preserve_dtype=True)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 994, in agg_series
    result = self._aggregate_series_pure_python(obj, func)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/ops.py", line 1015, in _aggregate_series_pure_python
    res = func(group)
          ^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/groupby/groupby.py", line 1857, in <lambda>
    alt=lambda x: Series(x).mean(numeric_only=numeric_only),
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/generic.py", line 11556, in mean
    return NDFrame.mean(self, axis, skipna, numeric_only, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/generic.py", line 11201, in mean
    return self._stat_function(
           ^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/generic.py", line 11158, in _stat_function
    return self._reduce(
           ^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/series.py", line 4670, in _reduce
    return op(delegate, skipna=skipna, **kwds)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 96, in _f
    return f(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 158, in f
    result = alt(values, axis=axis, skipna=skipna, **kwds)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 421, in new_func
    result = func(values, axis=axis, skipna=skipna, mask=mask, **kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 727, in nanmean
    the_sum = _ensure_numeric(values.sum(axis, dtype=dtype_sum))
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kpenner/mambaforge/envs/trackintel/lib/python3.11/site-packages/pandas/core/nanops.py", line 1699, in _ensure_numeric
    raise TypeError(f"Could not convert {x} to numeric") from err
TypeError: Could not convert testtesttesttesttest to numeric
```


https://github.com/mie-lab/trackintel/blob/11c36f3b2567b2d1c012e284ee3f89b5723caea0/trackintel/preprocessing/positionfixes.py#L364

Discussion:

Patch:
diff --git a/README.md b/README.md
index 5c6ffdf..833c49e 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
 [![codecov.io](https://codecov.io/gh/mie-lab/trackintel/coverage.svg?branch=master)](https://codecov.io/gh/mie-lab/trackintel)
 [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 [![asv](http://img.shields.io/badge/benchmarked%20by-asv-green.svg?style=flat)](https://mie-lab.github.io/trackintel/)
+[![Downloads](https://static.pepy.tech/badge/trackintel)](https://pepy.tech/project/trackintel)
 
 *trackintel* is a library for the analysis of spatio-temporal tracking data with a focus on human mobility. The core of *trackintel* is the hierarchical data model for movement data that is used in GIS, transport planning and related fields. We provide functionalities for the full life-cycle of human mobility data analysis: import and export of tracking data of different types (e.g, trackpoints, check-ins, trajectories), preprocessing, data quality assessment, semantic enrichment, quantitative analysis and mining tasks, and visualization of data and results.
 Trackintel is based on [Pandas](https://pandas.pydata.org/) and [GeoPandas](https://geopandas.org/#). 
diff --git a/trackintel/preprocessing/positionfixes.py b/trackintel/preprocessing/positionfixes.py
index 3e57066..07af030 100644
--- a/trackintel/preprocessing/positionfixes.py
+++ b/trackintel/preprocessing/positionfixes.py
@@ -362,7 +362,7 @@ def generate_triplegs(
         posfix_grouper = pfs.groupby("tripleg_id")
 
         tpls = posfix_grouper.agg(
-            {"user_id": ["mean"], "tracked_at": [min, max], pfs.geometry.name: list}
+            {"user_id": ["first"], "tracked_at": [min, max], pfs.geometry.name: list}
         )  # could add a "number of pfs": can be any column "count"
 
         # prepare dataframe: Rename columns; read/set geometry/crs;


Test patch:
diff --git a/tests/preprocessing/test_positionfixes.py b/tests/preprocessing/test_positionfixes.py
index d0362b9..e8c6f71 100644
--- a/tests/preprocessing/test_positionfixes.py
+++ b/tests/preprocessing/test_positionfixes.py
@@ -552,5 +552,5 @@ class TestGenerate_triplegs:
         # remove isolated - not needed for this test
         pfs = pfs[~pfs.index.isin([1, 2])].copy()
         # set user ID to string
-        pfs["user_id"] = pfs["user_id"].astype(str)
-        pfs, tpls = pfs.as_positionfixes.generate_triplegs()
+        pfs["user_id"] = pfs["user_id"].astype(str) + "not_numerical_interpretable_str"
+        pfs, _ = pfs.as_positionfixes.generate_triplegs()