Metadata-Version: 2.4
Name: infostop
Version: 0.2.0
Summary: Temporospatial clustering in Python. Well suited for mobility data.
Home-page: https://github.com/ulfaslak/infostop
Author: Ulf Aslak, Laura Alessandretti
Author-email: ulfjensen@gmail.com
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Topic :: Scientific/Engineering :: Information Analysis
Classifier: Topic :: Scientific/Engineering :: Mathematics
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pybind11>=2.4
Requires-Dist: infomap>=1.0.6
Requires-Dist: folium>=0.7.0
Requires-Dist: numpy
Requires-Dist: tqdm
Requires-Dist: scikit-learn
Requires-Dist: scipy
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-dist
Dynamic: summary

# Infostop
*Python package for detecting stop locations in mobility data*

[![Build Status][build-image]][build-url]

This package implements the algorithm described in https://arxiv.org/pdf/2003.14370.pdf, for detecting stop locations in time-ordered location data.

Infostop is useful to anyone who wishes to detect stationary events in location coordinate streams. It is, thus, a framework to simplify dense and rich location time-series into sequences of events.

## Usage
Given a location trace such as:

```Python
>>> data 
array([[ 55.75259295,  12.34353885 ],
       [ 55.7525908 ,  12.34353145 ],
       [ 55.7525876 ,  12.3435386  ],
       ...,
       [ 63.40379175,  10.40477095 ],
       [ 63.4037841 ,  10.40480265 ],
       [ 63.403787  ,  10.4047871  ]])
```

Or with time information

```Python
>>> data 
array([[ 55.75259295,  12.34353885, 1581401760 ],
       [ 55.7525908 ,  12.34353145, 1581402760 ],
       [ 55.7525876 ,  12.3435386 , 1581403760 ],
       ...,
       [ 63.40379175,  10.40477095, 1583401760 ],
       [ 63.4037841 ,  10.40480265, 1583402760 ],
       [ 63.403787  ,  10.4047871 , 1583403760 ]])
```

A stop location solution can be obtained using:

```Python
>>> from infostop import Infostop
>>> model = Infostop()
>>> labels = model.fit_predict(data)
```

`labels` has one integer per row of `data`: the label of the stop location the point belongs to, or `-1` if the point is not part of a stop (for example, it was recorded while moving). With the default `distance_metric="haversine"`, the distances `r1` and `r2` are in meters. The time parameters `min_staying_time` and `max_time_between` are in the units of the time column, and their defaults assume seconds.

Alternatively, `data` can also be a list of `numpy.array`s, in which case it is assumed that list elements are separate traces in the same space. In this *multi segment* (or *multi user*) case, Infostop finds stop locations that are shared by different segments.

Solutions can be plotted using:

```Python
>>> from infostop import plot_map
>>> folmap = plot_map(model)
>>> folmap.m
```

Stop locations on a university campus, with points coloured by their label:

![Points on a map of a university campus, coloured by stop location label](https://raw.githubusercontent.com/ulfaslak/infostop/master/docs/img/infostop_example_map.png)

The [documentation](https://infostop.readthedocs.io) has runnable examples and explains every parameter.

## Advantages
* **Simplicity**: At its core, the method works by two steps. (1) Reducing the location trace to the medians of each stationary event and (2) embedding the resulting locations into a network that connects locations that are within a user-defined distance and clustering that network.
* **Multi-trace support:** Currently, no other libraries support clustering multiple traces at once to find global stop locations. Infostop does. The image above visualizes stop locations at a campus for a population of almost 1000 university students.
* **Flow based**: Spatial clusters correspond to collections of location points that contain large amounts of flow when represented as a network. This enables the recovery of locations where traces slightly overlap.
* **Speed**: First the point space is reduced to the median of stationary points (executed in a fast C++ module), then spatially neighboring points connected using a Ball search tree algorithm, and finally the network is clustered using the C++ based Infomap program. For example, clustering 100.000 location points takes about a second.

## Installation
`pip install infostop`

**macOS without Infomap wheels** (Intel Macs, and Apple Silicon Macs older than macOS 15, on Python 3.11+): pip has to compile Infomap, and Infomap's build needs OpenMP unless you switch it off ([#77](https://github.com/ulfaslak/infostop/issues/77)). Install Infomap without OpenMP first, then infostop:
```Bash
$ OPENMP=0 pip install infomap
$ pip install infostop
```
If you use Homebrew and your Python runs natively (not an x86_64 Python under Rosetta on Apple Silicon), `brew install libomp` before `pip install infostop` also works.

**Reproducing earlier results.** Since infostop 0.2.0, Infomap clusters the network 50 times (`num_trials=50`) and keeps the best result. This makes labels agree between Infomap versions and between operating systems for almost all inputs. Compared with infostop 0.1.9, some labels change: on some crowded stops (stops that overlap within `r2`), and, with `label_singleton=False`, at places whose stop events share one coordinate. To get the labels of 0.1.9, use the same operating system as before, and either:

- pin `infostop==0.1.9` and `infomap==1.0.6`, or
- use infostop 0.2.0 with Infomap 1.0.6 and `num_trials=1`. This keeps the `label_singleton=False` change.

The [changelog](https://github.com/ulfaslak/infostop/blob/master/CHANGELOG.md#020-2026-10-28) has the details.

## Development notes
We welcome contributions. Before you get started, you may want to read the notes below.

You should **create a virtual environment** with a Python from 3.9 to 3.14 (CI tests 3.9 and 3.14). `make env` uses the `python3` on your `PATH`. On Python 3.9 and 3.10, pip installs Infomap 2.9.2, the last Infomap for these versions. It has no wheels for them, so pip compiles it, which needs a C++ compiler. In your local `infostop` folder, do:
```Bash
$ make env
$ source env/bin/activate
```

**Install the development tools** (`pytest`, `black`, `coverage`, `build`) and the dependencies:
```Bash
(env) $ make deps
```

**Install `infostop`** into your virtual environment.
Do this by running:
```Bash
(env) $ make install
```
This command builds the package and replaces any pre-existing installation of Infostop, so you will probably want to run it after each code update.

**Run tests**:
```Bash
(env) $ make test
```
Like CI, this runs the tests from a temporary folder against the installed package (not the source folder), so run `make install` first.

**Check test coverage**:
```Bash
(env) $ make coverage
(env) $ cd htmlcov
(env) $ python -m http.server 8001
```
Then go to localhost:8001 in your browser to look at the coverage report.

**Format code with `black`**. We don't want to argue about code formatting. Please run `black` to apply standard formatting to your code before you make a pull request.

The `Makefile` implements a number of commands that are useful during development.
Go ahead and execute `make help` to see descriptions of available commands, or inspect the file so you understand what's going on. 

**Convenient: create an ipykernel for the virtual environment**
If you use Jupyter notebooks, you can install the virtual environment into Jupyter as a kernel. Run:
```Bash
(env) $ pip install ipykernel
(env) $ python -m ipykernel install --user --name=infostop_env
```
This lets you select the virtual environment as a kernel in a Jupyter notebook.

**Versioning and deployment to PyPI**
Releases are made by the maintainer. A version number on PyPI can never be reused, so a release cannot be undone.

1. On an up-to-date `master` that has everything you want to release, execute the `increment_version.py` script ONCE. It increments the last number of `__version__` in `infostop/metadata.py`. Commit the change.
2. [Tag](https://git-scm.com/book/en/v2/Git-Basics-Tagging) that commit with `v` followed by the version in `infostop/metadata.py`. For example, if it says `0.2.1`:
```Bash
(env) $ git tag -a v0.2.1 -m "Infostop version 0.2.1"
```
3. Push the commit and the tag together:
```Bash
(env) $ git push --atomic origin master v0.2.1
```

Pushing a `v*` tag starts the [Release workflow](https://github.com/ulfaslak/infostop/blob/master/.github/workflows/release.yml). It builds the sdist and the wheels (Linux x86_64 and aarch64, macOS x86_64 and arm64, Windows x86_64, for each supported Python version), runs the tests against each wheel, and then uploads all of them to PyPI with [Trusted Publishing](https://docs.pypi.org/trusted-publishers/), so no API token is stored on GitHub. It uploads nothing if the tag is not `v` + `__version__`, if the tagged commit is not on `master`, or if a build or a test fails. Every PR builds and tests the same wheels, so a broken build shows up before the release.

Do not upload the output of `make build` by hand: on Linux it is a `linux_x86_64` wheel, which PyPI rejects.


[build-image]: https://github.com/ulfaslak/infostop/actions/workflows/ci.yml/badge.svg
[build-url]: https://github.com/ulfaslak/infostop/actions/workflows/ci.yml
