# DTAIDistance: dynamic time warping with a C core you can actually install

> DTAIDistance computes distances between time series, principally dynamic time warping, in a pure Python implementation and a fast C implementation that needs only Cython. The C path is where the speed is, but it also brings a build step, an OpenMP dependency and a set of tuning parameters that change the result rather than just the runtime.

**wannesm/dtaidistance** — Time series distances: Dynamic Time Warping (fast  DTW implementation in C)

- Repository: https://github.com/wannesm/dtaidistance
- Stars: 1,250 · Forks: 194
- Language: Python
- License: NOASSERTION
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/wannesm-dtaidistance

## Two implementations, one interface

DTAIDistance is a library for time series distances, used in the DTAI Research Group at KU Leuven. Its subject is dynamic time warping, the distance measure that compares two sequences while allowing one to be stretched or compressed in time, and it offers both a pure Python implementation and a fast implementation in C.

The C implementation has only Cython as a dependency, it is compatible with Numpy and Pandas, and it is written so that unnecessary data copy operations are avoided. The last point is the one that decides the performance, and it is also the one that decides how you call the library, as the section on the fast path below shows.

The two implementations are interchangeable behind the same interface, and the choice is visible in the function names. `dtw.distance` takes ordinary Python sequences, and `dtw.distance_fast` calls the C code directly.

The example in the README is the whole API in four lines:

```python
from dtaidistance import dtw
import numpy as np
s1 = np.array([0.0, 0, 1, 2, 1, 0, 1, 0, 0])
s2 = np.array([0.0, 1, 2, 0, 0, 0, 0, 0, 0])
d = dtw.distance_fast(s1, s2)
```

Numpy is an optional runtime dependency since version 2, and it is also optional for compiling the C library, where only Cython is required. The project metadata still lists numpy as a dependency, but the README is explicit that it can be removed by compiling from source, and the build configuration comments in `pyproject.toml` explain why numpy is included in the build requirements anyway, since the build isolation would otherwise require the no-build-isolation flag.

The library is licensed Apache-2.0, requires Python 3.8 or above, and has a Zenodo DOI for citing a specific version.

## Installing it, and what the compiler is doing

There are two ordinary installation routes:

```bash
pip install dtaidistance
```

```bash
conda install -c conda-forge dtaidistance
```

The pip installation requires numpy to compile the numpy-compatible C code using Cython, and the README notes that this dependency is optional and can be removed by compiling from source. If compilation problems appear, for example because the C-based implementation or OpenMP is not available, the README points at the installation page in the documentation for the other options.

That sentence tells you what the build does. The C library is compiled as a Python extension, and it optionally uses OpenMP for parallel execution. The `setup.py` shows the compiler-specific flags in detail, with a separate entry for each toolchain: the unix entry passes `-Xpreprocessor` and `-fopenmp`, with a comment explaining that the preprocessor flag is required for the built-in Clang on macOS while other LLVM installations do not need it. There is an msvc entry with the Microsoft OpenMP switch, a mingw32 entry that adds `-march=native` and a `MS_WIN64` define, and separate entries for Homebrew LLVM and for GNU GCC, because the OpenMP runtime library to link against is named differently in each case.

The `-march=native` flag on the MinGW path is worth noting, because it means the binary is tuned for the machine that built it. That is fine for a local build and wrong for a distributed wheel.

The parallelisation note from the v2 changes matters for the runtime: it is implemented directly in C and included if OpenMP is installed, so whether your copy of the library is parallel depends on the machine and the toolchain that built it, not on a flag you pass at runtime.

## The fast path needs contiguous doubles, and pruning changes the cost

The README is specific about what the fast path requires, and the requirement is about the data layout rather than the values.

The fastest version, described as 30 to 300 times faster, uses the C directly but requires an array as input with the double type, and optionally prunes computations by setting `max_dist` to the Euclidean upper bound:

```python
from dtaidistance import dtw
import array
s1 = array.array('d',[0, 0, 1, 2, 1, 0, 1, 0, 0])
s2 = array.array('d',[0, 1, 2, 0, 0, 0, 0, 0, 0])
d = dtw.distance_fast(s1, s2, use_pruning=True)
```

A numpy array with dtype double or float works as well. The distinction is between a Python list, which the pure Python path can consume, and an array of doubles in contiguous memory, which the C path can consume without copying. This is the practical meaning of the claim that unnecessary data copies are avoided.

The `use_pruning` flag is the more interesting half. The v2 notes say that the `max_dist` argument turned out to be similar to the work of Silva and Batista on PrunedDTW, and that the toolbox now implements a version equal to PrunedDTW. In other words, the pruning is not an approximation bolted on afterwards, it is a known algorithm with a published definition, which is what makes the result trustworthy rather than merely fast.

The tuning parameters, documented with the `__doc__` of the distance function, are: `window`, to allow only shifts up to a given amount away from the two diagonals, `max_dist`, to stop when the returned distance will be larger than that value, `max_step`, to disallow steps larger than a value, `max_length_diff`, to return infinity when the length difference exceeds a value, `penalty`, the penalty added when compression or expansion is applied, `psi`, a relaxation to ignore the beginning and end of the sequences for cyclical sequences, and `use_pruning` itself.

## Distances that explain themselves, not just numbers

A distance on its own tells you that two series are similar and not how. The library has grown a set of tools for the second question, and the v2 feature list names them.

Dynamic Subsequence Warping, added in v2.5, is used to explain similarities and dissimilarities. The API is an `ExplainPair` object:

```python
from dtaidistance.explain.dsw import ExplainPair
pair = ExplainPair(s1, s2, delta_rel=1, delta_abs=0.5)
pair.plot_warping(filename='/path/to/file.png')
```

The two delta parameters, one relative and one absolute, are what let you describe what counts as the same feature appearing at a slightly different place or a slightly different value.

There is also the warping path itself. `dtw.warping_path` returns the path that realises the distance, and the visualisation module can draw it. If you want the full matrix of all possible warping paths rather than just the best one, `dtw.warping_paths` returns the distance and the paths, and `dtw.best_path` selects the best from that set.

The `psi` parameter shows up in the example that uses warping paths, and the README explains what it does there: it relaxes the matching at the beginning and the end, and in that example it results in a perfect match even though the sine waves are slightly shifted. For cyclical data, that is the difference between a distance that measures phase and one that measures the cycle as a whole.

The visualisation lives in `dtaidistance.dtw_visualisation` and the optional `viz` extra in the project metadata pulls in matplotlib, so the plotting is an optional dependency rather than a core one.

## From pairwise to matrices, clustering and subsequence search

The library is not only a pairwise distance function, and the v2 notes list the additions: subsequence search and local concurrences in v2.3, support for multivariate time series in v2.3, and DTW Barycenter Averaging for clustering in v2.2.

The workhorse is `dtw.distance_matrix`, which computes the distances between all the sequences in a list of sequences. The README says the method expects a list of lists or arrays, and that you can set variables to use more or less C code with `use_c` and `use_nogil`, and to choose parallel or serial execution with `parallel`.

`use_nogil` deserves a note. It refers to releasing the GIL while the C code runs, which is what allows the parallelism to be real rather than interleaved. The same knob exists for other reasons elsewhere in the ecosystem, where a library that holds the GIL for a long computation blocks every other thread in the process.

The subpackages in the project metadata match the feature list. There is `dtaidistance.clustering` for the barycenter averaging and the clustering of series, `dtaidistance.subsequence` for subsequence search, `dtaidistance.connectors`, `dtaidistance.symbolization`, and `dtaidistance.explain` with its `dsw` subpackage.

The `connectors` package is worth understanding if you use it: it is how the library reaches a data store to pull series for comparison, which is the piece that turns a distance function into a tool for a large collection of measurements. The visible documentation does not list the supported stores, so that is a question for the documentation rather than for this summary.

## The test and benchmark targets show how the project is checked

The `Makefile` at the repository root is mostly a benchmarking harness, and it is more informative than a test list.

The common test target runs pytest with the benchmark suite skipped, and there is a separate target for running the tests without the C libraries, using the `pytest-nolibs.ini` configuration, which is how the pure Python path gets tested on its own. There is a Windows variant of that same no-libraries run, and a PyPy target that runs the suite under PyPy with the no-libraries configuration, since the C extension will not be present there.

The benchmark targets are split by subject: one for the C distance functions, one for the C matrix computation, one for clustering and one for subsequence search, plus a parallel C variant that runs the matrix and distance benchmarks together. The benchmark settings in the Makefile enable verbose output, autosave and a histogram of results written to a timestamped file, disable garbage collection during the run, and restrict the run to the benchmark tests only.

The effect is that the project measures its own hot paths as separate suites rather than one lump, which is the right way to catch a regression in the C code that would be invisible in a functional test.

The extras in the project metadata support that. The `tests` extra is pytest with pytest-benchmark, the `viz` extra is matplotlib, and the `dev` extra adds pytest-env, scipy, pandas, sphinx and wurlitzer for the test output, alongside the build tools. The `numpy` and `all` extras add scipy and pandas, and the library is compatible with Pandas.

The repository also has a `CITATION.cff` file and an `AUTHORS` file, and the `AUTHORS` file is included in the distribution through the `license-files` entry in the project metadata.

## Maintenance, versioning, and where the library stops being the right tool

The release history has a gap that matters for pinning. v2.5.0 was published on 2026-09-08, and before that the list shows v2.3.10 from 2022-10-07 and v2.3.5 from 2022-01-25. So the tags jump from 2022 to 2026, and the version in the project metadata is 2.5.1, which is ahead of the newest tag. The last push was on 2026-10-07.

The v2 notes also describe a set of changes that are not small. Numpy became optional, the code moved to `ssize_t` instead of `int` to allow larger data structures on 64 bit machines and to be more compatible with Numpy, the parallelisation was rewritten in C, and the pruning was changed to match PrunedDTW. A user moving from the 2.3 line to the 2.5 line is not doing a patch upgrade.

There is a licensing detail to check. The project metadata states the licence as Apache-2.0, with the licence files and the authors file listed as `license-files`, while the repository's licence classification on GitHub is recorded as a custom licence that cannot be categorised. The `LICENSE` file in the repository is the authoritative text, and the presence of `LICEN[CS]E*` patterns in the metadata suggests more than one licence file, possibly covering the C code separately. If you are shipping this in a product, read those files rather than the badge.

As for when not to use it, four cases stand out. If you need an install with no compiler and no Cython, the fast path is unavailable and you are left with pure Python, which is the same algorithm at the same distance values and a different runtime. If you need a distance whose definition is fixed by an external standard, note that `penalty` and `window` change the value being computed, so a distance measured here is a distance measured with your settings, and the parameters have to travel with the number. If your series are multivariate, the support added in v2.3 is recent relative to the rest of the library. And if you are working with very long sequences, the dynamic programming matrix grows with the product of the lengths, which is where the subsequence search and the window parameter become the relevant tools rather than the pairwise distance.

## Conclusion

Adopt DTAIDistance if you need to compare many time series and the pairwise dynamic programming cost is your bottleneck, and you can ship a C toolchain in your build. Do not adopt it if you need an install with no compiler, since the fast path requires Cython at build time, or if you need a distance measure with a published definition, because several of its parameters, including the penalty for compression and expansion, change the value being computed. Before you choose, decide whether you want the pure Python implementation or the C one, keep the input as a contiguous array of doubles rather than a list, and pin the version, because the release history jumps from v2.3.10 in 2022 to v2.5.0 in 2026 with substantial changes in between.

## FAQ

### How do I install dtaidistance?

With `pip install dtaidistance` or `conda install -c conda-forge dtaidistance`. The pip install needs numpy to compile the numpy-compatible C code with Cython, but that dependency is optional if you compile from source, and the README points to the installation documentation for cases where the C implementation or OpenMP is not available.

### What is the difference between dtw.distance and dtw.distance_fast?

The fast version calls the C implementation directly, is described as 30 to 300 times faster, and requires the input as an array of doubles, such as an array.array of type d or a numpy array with dtype double or float. The regular distance accepts ordinary Python sequences.

### Which parameters can change the DTW value computed by dtaidistance?

Several of them: `window` limits the allowed shifts, `max_step` bounds the step size, `penalty` adds a penalty when compression or expansion is applied, `psi` relaxes the matching at the start and end of the sequences, and `max_length_diff` returns infinity when the lengths differ too much. The README advises checking the `__doc__` of the distance function for the full list.

### Does dtaidistance support subsequence search and clustering?

Yes. The v2 notes list subsequence search and local concurrences, multivariate time series support, and DTW Barycenter Averaging for clustering, and the package metadata lists dtaidistance.clustering and dtaidistance.subsequence as subpackages.

## Sources

- [Issues](https://github.com/wannesm/dtaidistance/issues)
- [README](https://github.com/wannesm/dtaidistance/blob/master/README.md)
- [Releases](https://github.com/wannesm/dtaidistance/releases)
- [wannesm/dtaidistance on GitHub](https://github.com/wannesm/dtaidistance)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/wannesm-dtaidistance
