---
jupytext:
  formats: content_md///md:myst,content///ipynb
  text_representation:
    extension: .md
    format_name: myst
    format_version: 0.13
    jupytext_version: 1.16.4
kernelspec:
  display_name: Python 3 (ipykernel)
  language: python
  name: python3
---

(managing-data:numpy:vectorization)=
# Vectorization

NumPy is very fast if operations are applied to whole arrays instead of element-by-element. Thus, we should try to avoid iterating through array elements and processing single elements. This idea is known as *vectorization*.

```{code-cell} ipython3
import numpy as np
```

## Example: Vectorization via Indexing

Imagine we have a vector of length $n$, where $n$ is even. We would like to interchange each number at an even index with its successor. The result shall be stored in a new array.

Here is the code based on a loop:

```{code-cell} ipython3
def interchange_loop(a):
    result = np.empty_like(a)
    for k in range(0, int(a.size / 2)):
        result[2 * k] = a[2 * k + 1]
        result[2 * k + 1] = a[2 * k]
    return result

print(interchange_loop(np.array([1, 2, 3, 4, 5, 6, 7, 8])))
```

And here with vectorization:

```{code-cell} ipython3
def interchange_vectorized(a):
    result = np.empty_like(a)
    result[0::2] = a[1::2]
    result[1::2] = a[0::2]
    return result

print(interchange_vectorized(np.array([1, 2, 3, 4, 5, 6, 7, 8])))
```

Now let's look at the execution times:

```{code-cell} ipython3
%%timeit
interchange_loop(np.zeros(1000))
```

```{code-cell} ipython3
%%timeit
interchange_vectorized(np.zeros(1000))
```

The speed-up is of a factor about 60 for $n=1000$ and becomes much better if $n$ grows.

## Example: Vectorization via Broadcasting

Given two lists of numbers we want to have a two-dimensional array containing all products from these numbers.

Here is the code based on a loop:

```{code-cell} ipython3
def products_loop(a, b):
    result = np.empty((len(a), len(b)))
    for i in range(0, len(a)):
        for j in range(0, len(b)):
            result[i, j] = a[i] * b[j]
    return result

print(products_loop(range(1000), range(1000)))
```

And here with vectorization:

```{code-cell} ipython3
def products_vectorized(a, b):
    result = np.array([a]).T * np.array([b])
    return result

print(products_vectorized(range(1000), range(1000)))
```

```{hint}
The `T` member variable of a NumPy array provides the transposed array. It's not a copy (expensive) but a view (cheap). For detail see [](managing-data:numpy:linear-algebra-functions).
```

Execution times:

```{code-cell} ipython3
%%timeit
products_loop(range(1000), range(1000))
```

```{code-cell} ipython3
%%timeit
products_vectorized(range(1000), range(1000))
```

Speed-up is factor 200 for lists of lenth 1000.

## Important

Whenever you see a loop in numerical routines, spend some time to vectorize it. Almost always that's possible. Often vectorization also increases readability of the code.
