# numpy_scipy_matplotlib.py

## Examples with numpy, scipy, and matplotlib

Original code is here.

#### NumPy

Imports the `numpy` library as the name `np`. This is a very common convention.

`import numpy as np`

Comparing Python lists and NumPy arrays.

a_list is a Python list

```a_list = [1, 2, 3, 4, 5]
print('a_list is type', type(a_list))```

a_array is a NumPy array

```a_array = np.array(a_list)
print('a_array is type', type(a_array))```

Can create Numpy arrays from a Python list and vice-versa

```b_array = np.array([3, 2, 4, 1, 5])
b_list = list(b_array)```

The `+` operator concatenates two lists

`print('a_list + b_list =', a_list + b_list)`

The `+` operator performs pairwise component addition for two arrays

`print('a_array + b_array =', a_array + b_array)`

The `+` operator can also add a scalar to an array

`print('a_array + 5 =', a_array + 5)`

Similarly, `*` will perform pairwise component multiplication

`print('a_array * b_array = ', a_array * b_array)`

Remember that when applied to a list and a scaler `*` will perform multiple concatenations

`print('a_list * 3 =', a_list * 3)`

When applied to two lists, however, `*` yields
`TypeError: can't multiply sequence by non-int of type 'list'` print('a_list * b_list =', a_list * b_list)

#### SciPy

Importing the `pearsonr` method from the `scipy.stats.stats` module. `scipy` builds on `numpy`.

`from scipy.stats.stats import pearsonr`

eight lists and their correlations

```a = [1, 2, 3, 4, 5] # initial list
b = [1, 2, 3, 4, 5] # identical to `a`
c = [0, 1, 2, 3, 4] # identical to `a` minus 1
d = [2, 4, 6, 8, 10] # identical to `a` * 2
e = [1, 2, 3, 5, 4] # very similar, but not identical to `a`
f = [1, 5, 4, 2, 3] # not similar to `a`
g = [5, 4, 3, 2, 1] # opposite of `a`
h = [1, 1, 1, 1, 1] # flat shape; standard deviation of `h` is 0```

Correlations between `a` and the other lists:

```comparisons = [b, c, d, e, f, g, h]
for comparison in comparisons:```

`pearsonr` returns a tuple corresponding to the r and p values

`    corr, p = pearsonr(a, comparison)`

Correlation between `a` and `b`, `c`, and `d` is 1.0 (maximal).
Correlation between `a` and `e` is 0.9 (high, but not maximal).
Correlation between `a` and `f` is 0.1, indicating almost no correlation.
Correlation between `a` and `g` is -1.0,indicating a maximal inverse inverse correlation.
Correlation between `a` and `h` is `nan` (not a number), since the calculation of correlation involves division by the standard deviations (which for `h` is 0).

`    print('Correlation between', a, 'and', comparison, ':', corr)`

## Using scipy distance measures in music

`harmonic_distance` measures the Euclidean distance between two interval vectors. This is identical to Richard Teitelbaum's "similarity index". You can substitute different distance measures from the `scipy.spatial.distance` module. For instance, using `cityblock` distance recreates Robert Morris' "SIM" measure.

`p` and `q` are lists of pitches. `harmonic_distance` depends on music21 and scipy.

Returns the harmonic distance of `p` and `q` interpreted as chords based on the Euclidean distance from the `scipy.spatial.distance` module. Prints an error message and returns `None` if the lists are of unequal length.

`def harmonic_distance(p, q):`
```    from music21 import chord
from scipy.spatial import distance

if len(p) != len(q):
print('lists of pitches are of unequal lengths')
return None```

Using the `music21` `Chord` class `.intervalVector` property

```    iv_p = chord.Chord(p).intervalVector
iv_q = chord.Chord(q).intervalVector
return distance.euclidean(iv_p, iv_q)```

Using `harmonic_distance` to compare the harmonic similarity of three `chords`

```chords = [[0, 2, 3, 7], [0, 2, 4, 6], [0, 4, 8, 11]]
print('Harmonic distances')
for i in range(2):
u = chords[i]
for j in range(i+1, 3):
v = chords[j]
print(u, '<-->', v, ':', harmonic_distance(u, v))```

#### Matplotlib

`pyplot` is a module in `matplotlib` for plotting. `matplotlib depends on`numpy`. See https://matplotlib.org/api/pyplot_api.html for more details.

`import matplotlib.pyplot as plt`

plotting two lists on the plane

```x = [1, 2, 3, 4, 5]
y = [1, 7, 4, 2, 3]
plt.plot(x, y)```

plot should automatically open

`plt.show()`

`arange` is the `numpy` array equivalent of Python's built-in `range` function.

```x = np.arange(20)
print('x is', x)```

plot f(x) = x**2

```print('x squared is', x**2)
plt.plot(x, x**2)
plt.show()```

plot f(x) = sin(2πx/20) using `numpy`'s `sin` function and `pi` constant

```y = np.sin(x * 2 * np.pi / len(x))
plt.plot(x, y)
plt.show()

```