Using NumPy

NumPy is a Python library for handling large amounts of array-based data and provides the technological basis for array-based NumPy trace step templates. The official NumPy manual can be accessed via the following link: https://docs.scipy.org/doc/numpy/index.html

The quickstart tutorial is especially recommended as an introduction: https://docs.scipy.org/doc/numpy/user/quickstart.html

In addition, some aspects that are particularly relevant for trace analysis are highlighted below.

Boolean Operators

Neither the expression if arr: print('hallo') nor the statement bool(arr) can be used to check, whether a NumPy array is empty or whether it contains True values. Instead, corresponding mechanisms of NumPy must be used, which are described in the following example:

import numpy as np

arr = np.array([3, 1, -2, 0], dtype=np.float64)

# Is arr valid?
if isinstance(arr, np.ndarray):
    print('arr is a valid NumPy array')

# Is arr empty?
if len(arr) > 0:
    print('arr is not empty')

# Handling of Boolean values
positive = arr >= 0                    # comparison with zero is carried out element by element and stored in new array: [True, True, False, True]
print(np.all(positive))                # False, as not all elements of positive are True
print(np.any(positive))                # True, as positive is True in at least one (three, in fact) element

Caution

The Boolean value of np.nan (“not a number”) is True! The expression np.all(np.array([0.1, 3.0, np.nan])) therefore evaluates to True.

Masking

Python’s indexing/slicing mechanism can also be used with NumPy to access single values or partial arrays:

import numpy as np

arr = np.arange(10)                    # [0 1 2 3 4 5 6 7 8 9]
print(arr[3:5])                        # [3, 4]
print(arr[:4])                         # [0, 1, 2, 3]
print(arr[8:])                         # [8, 9]

NumPy, however, offers even more powerful options for selecting a subset of values. On the one hand, certain values can be selected by specifying a Boolean mask, on the other hand, desired elements can also be specified in an array of indices.

Boolean mask: All values >= threshold

import numpy as np

arr = np.array([0, 3, 7, 13, 10, 11, 12.5, 15])
threshold = 12

mask = arr >= threshold                # Boolean mask: [False, False, False, True, False, False, True, True]
print(arr[mask])                       # [13, 12.5, 15]

Boolean mask: All signal values from time interval

import numpy as np

values = np.array([100, 200, 300, 400, 500])
timestamps = np.arange(5.0)            # [0.0, 1.0, 2.0, 3.0, 4.0]

# All values from the closed time interval between 1.0 and 2.5
mask = np.logical_and(1.0 <= timestamps, timestamps <= 2.5)
print(values[mask])                    # [200, 300]

Index array: Signal change from 20 to 30

import numpy as np

values = np.array([0, 10, 20, 30, 40])
timestamps = np.arange(5.0)            # [0.0, 1.0, 2.0, 3.0, 4.0]

# Create Boolean mask that contains True where a value of 20 is followed by 30
boolMask = np.logical_and(values[:-1] == 20, values[1:] == 30)  # [False, False, True, False]
# Create corresponding index array, Attention: boolMask contains one element less than values
indexArr = np.arange(values.shape[0] - 1)[boolMask]  # [2]
# indexArr refers to the position of 20 by now, but shall refer to the change from 20 to 30
indexArr = indexArr + 1                # [3]

# Alternative: numpy.where()
indexArr = np.where(boolMask)[0] + 1   # [3]

print(timestamps[indexArr])            # [3.0]
print(values[indexArr])                # [30]