# Pandas

```python
import pandas as pd
```

```python
pd.DataFrame({'some_data': [1, 2, 3], 'more_data': ['x', 'y', 'z']})
```

# NetworkX

```python
import networkx as nx
import matplotlib.pyplot as plt
```

```python
nx.draw(nx.petersen_graph())
```

# Shapely

```python
from shapely.geometry import Polygon
```

```python
Polygon([(0, 0), (0, 1), (1, 0)])
```

# SymPy

```python
import sympy
```

```python
x = sympy.Symbol('x')
sympy.Integral(sympy.sin(x**2) / x)
```

# ipyleaflet

```python
from ipyleaflet import Map, basemaps, basemap_to_tiles
```

```python
m = Map(
    basemap=basemap_to_tiles(basemaps.NASAGIBS.ModisTerraTrueColorCR, "2017-04-08"),
    center=(52.204793, 360.121558),
    zoom=4
)

m
```

# Appendix

There is more:
* Iris
* Logs
* Xarray
* Snakeviz
* PyMC3
* Bokeh
* Altair
* ...

```python

```


# Realization of Recursive Filters

*This jupyter notebook is part of a [collection of notebooks](../index.ipynb) on various topics of Digital Signal Processing. Please direct questions and suggestions to [Sascha.Spors@uni-rostock.de](mailto:Sascha.Spors@uni-rostock.de).*

## Quantization of Filter Coefficients

The finite numerical resolution of digital number representations has impact on the properties of filters, as already discussed for [non-recursive filters](../nonrecursive_filters/quantization_effects.ipynb#Quantization-Effects). The quantization of coefficients, state variables, algebraic operations and signals plays an important role in the design of recursive filters. Compared to non-recursive filters, the impact of quantization is often more prominent due to the feedback. Severe degradations from the desired characteristics and instability are potential consequences of a finite word length in practical implementations.

A recursive filter of order $N \geq 2$ can be [decomposed into second-order sections (SOS)](../recursive_filters/cascaded_structures.ipynb). Due to the grouping of poles/zeros to filter coefficients with a limited amplitude range, a realization by cascaded SOS is favorable in practice. We therefore limit our investigation of quantization effects to SOS. The transfer function of a SOS is given as

\begin{equation}
H(z) = \frac{b_0 + b_1 z^{-1} + b_2 z^{-2}}{1 + a_1 z^{-1} + a_2 z^{-2}}
\end{equation}

This can be [split into a non-recursive part and a recursive part](../recursive_filters/introduction.ipynb#Recursive-Filters). The quantization effects of non-recursive filters have already been discussed. We therefore focus here on the recursive part given by the transfer function

\begin{equation}
H(z) = \frac{1}{1 + a_1 z^{-1} + a_2 z^{-2}}
\end{equation}

This section investigates the consequences of quantization in recursive filters. As for non-recursive filters, we first take a look at the quantization of filter coefficients. The structure used for the realization of the filter has impact on the quantization effects. We begin with the direct form followed by the coupled form, as example for an alternative structure.

### Direct Form

Above transfer function of the recursive part of a SOS can be rewritten in terms of its complex conjugate poles $z_{\infty}$ and $z_{\infty}^*$ as

\begin{equation}
H(z) = \frac{1}{(z-z_{\infty}) (z-z_{\infty}^*)} = \frac{z^{-2}}{ 1 \underbrace{- 2 r \cos(\varphi)}_{a_1} \; z^{-1} + \underbrace{r^2}_{a_2} \; z^{-2} }
\end{equation}

where $r = |z_{\infty}|$ and $\varphi = \arg \{z_{\infty}\}$ denote the absolute value and phase of the pole $z_{\infty}$, respectively. Let's assume a [linear uniform quantization](../quantization/linear_uniform_quantization_error.ipynb#Quantization-Error-of-a-Linear-Uniform-Quantizer) of the coefficients $a_1$ and $a_2$ with quantization step $Q$. Discarding clipping, the following relations for the locations of the poles can be found

\begin{align}
r_n &= \sqrt{n \cdot Q} \\
\varphi_{nm} &= \arccos \left( \sqrt{\frac{m^2 Q}{4 n}} \right)
\end{align}
for $n \in \mathbb{N}_0$ and $m \in \mathbb{Z}$. Quantization of the filter coefficients $a_1$ and $a_2$ into a finite number of amplitude values leads to a finite number of pole locations. In the $z$-plane the possible pole locations are given by the intersections of

* circles whose radii $r_n$ are given by $r_n = \sqrt{n \cdot Q}$ with
* equidistant vertical lines which intersect the horizontal axis at $\frac{1}{2} m \cdot Q$.

The finite number of pole locations may lead to deviations from a desired filter characteristic since a desired pole location is moved to the next possible pole location. The filter may even get unstable, when poles are moved outside the unit circle. For illustration, the resulting pole locations for a SOS realized in direct form are computed and plotted.

```python
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
import scipy.signal as sig
import itertools

def compute_pole_locations(Q):
    a1 = np.arange(-2, 2+Q, Q)
    a2 = np.arange(0, 1+Q, Q)

    p = np.asarray([np.roots([1, n, m]) for (n,m) in itertools.product(a1, a2)])
    p = p[np.imag(p)!=0]

    return p

def plot_pole_locations(p, Q):
    ax = plt.gca()
    for n in np.arange(np.ceil(2/Q)+1):
        circle = Circle((0,0), radius=np.sqrt(n*Q), fill=False, color='black', ls='solid', alpha=0.05)
        ax.add_patch(circle)
        ax.axvline(.5*n*Q, color='0.95')
        ax.axvline(-.5*n*Q, color='0.95')

    unit_circle = Circle((0,0), radius=1, fill=False, color='red', ls='solid')
    ax.add_patch(unit_circle)

    plt.plot(np.real(p), np.imag(p), 'b.', ms = 4)
    plt.xlabel(r'Re{$z$}')
    plt.ylabel(r'Im{$z$}')
    plt.axis([-1.1, 1.1, -1.1, 1.1])

# compute and plot pole locations
for w in [5,6]:
    Q = 2/(2**(w-1))  # quantization stepsize
    plt.figure(figsize=(5, 5))
    p = compute_pole_locations(Q)
    plot_pole_locations(p, Q)
    plt.title(r'Direct form coefficient quantization to $w=%d$ bits'%w)
```

**Exercise**

* What consequences does the distribution of pole locations on the desired characteristics of a filter have for e.g. low/high frequencies?

Solution: Quantization of the original filter coefficients leads to a limited number of possible pole and zero locations. These locations are not uniformly distributed over the $z$-plane, as can be observed from above illustrations. The density of potential locations is especially low for low frequencies and close to the Nyquist frequency. The properties of a designed filter having poles and/or zeros at low/high frequencies will potentially deviate more when quantizing its coefficients, as a consequence.

### Coupled Form

Besides the quantization step $Q$, the pole distribution depends also on the topology of the filter. In order to gain a different distribution of pole locations after quantization, one has to derive structures where the coefficients of the multipliers are given by other values than the direct form coefficients $a_1$ and $a_2$.

One of these alternative structures is the coupled form (also known as Gold & Rader structure)

where $\Re\{z_\infty\} = r \cdot \cos \varphi$ and $\Im\{z_\infty\} = r \cdot \sin \varphi$ denote the real- and imaginary part of the complex pole $z_\infty$, respectively. Analysis of the structure reveals its difference equation as

\begin{align}
w[k] &= x[k] + \Re\{z_\infty\} \, w[k-1] - \Im\{z_\infty\} \, y[k-1] \\
y[k] &= \Im\{z_\infty\} \, w[k-1] + \Re\{z_\infty\} \, y[k-1]
\end{align}

and its transfer function as

\begin{equation}
H(z) = \frac{\Im\{z_\infty\} \; z^{-1}}{ 1 - 2 \Re\{z_\infty\} \; z^{-1} + (\Re\{z_\infty\}^2 + \Im\{z_\infty\}^2) \; z^{-2} }
\end{equation}

Note that the numerator of the transfer function differs from the recursive only SOS given above. However, this can be considered in the design of the transfer function of a general SOS.

The real- and imaginary part of the pole $z_\infty$ occur directly as coefficients for the multipliers in the coupled form. Quantization of these coefficients results therefore in a Cartesian grid of possible pole locations in the $z$-plane. This is illustrated in the following.

```python
def compute_pole_locations(w):
    Q = 1/(2**(w-1))  # quantization stepsize
    a1 = np.arange(-1, 1+Q, Q)
    a2 = np.arange(-1, 1+Q, Q)

    p = np.asarray([n+1j*m for (n,m) in itertools.product(a1, a2) if n**2+m**2 <= 1])

    return p

def plot_pole_locations(p):
    ax = plt.gca()

    unit_circle = Circle((0,0), radius=1, fill=False, color='red', ls='solid')
    ax.add_patch(unit_circle)

    plt.plot(np.real(p), np.imag(p), 'b.', ms = 4)
    plt.xlabel(r'Re{$z$}')
    plt.ylabel(r'Im{$z$}')
    plt.axis([-1.1, 1.1, -1.1, 1.1])

# compute and plot pole locations
for w in [5,6]:
    plt.figure(figsize=(5, 5))
    p = compute_pole_locations(w)
    plot_pole_locations(p)
    plt.title(r'Coupled form coefficient quantization to $w=%d$ bits'%w)
```

**Excercise**

* What is the benefit of this representation in comparison to the direct from discussed in the previous section?

Solution: A befit of the coupled form is a uniform distribution of potential pole and zero locations in the $z$-plane. This holds especially for low frequencies and close to the Nyquist frequency.

### Example - Influence of coefficient quantization

The following example illustrates the effects of coefficient quantization for a recursive [Butterworth filter](https://en.wikipedia.org/wiki/Butterworth_filter) realized in cascaded SOSs in transposed direct form II.

```python
w = 16  # wordlength of filter coefficients
N = 7  # order of filter

def uniform_midtread_quantizer(x, w, xmin=1):
    # quantization step
    Q = xmin/(2**(w-1))
    # limiter
    x = np.copy(x)
    idx = np.where(x <= -xmin)
    x[idx] = -1
    idx = np.where(x > xmin - Q)
    x[idx] = 1 - Q
    # linear uniform quantization
    xQ = Q * np.floor(x/Q + 1/2)

    return xQ

# coefficients of recursive filter
b, a = sig.butter(N, 0.2, 'low')
# decomposition into SOS
sos = sig.tf2sos(b, a, pairing='nearest')
sos = sos/np.amax(np.abs(sos))
# quantization of SOS coefficients
sosq = uniform_midtread_quantizer(sos, w, xmin=2)
# compute overall transfer function of (quantized) filter
H = np.ones(512)
Hq = np.ones(512)
for n in range(sos.shape[0]):
    Om, Hn = sig.freqz(sos[n, 0:3], sos[n, 3:6])
    H = H * Hn
    Om, Hn = sig.freqz(sosq[n, 0:3], sosq[n, 3:6])
    Hq = Hq * Hn

# plot magnitude responses
plt.figure(figsize=(10, 3))
plt.plot(Om, 20 * np.log10(abs(H)), label='continuous')
plt.plot(Om, 20 * np.log10(abs(Hq)), label='quantized')
plt.title('Magnitude response')
plt.xlabel(r'$\Omega$')
plt.ylabel(r'$|H(e^{j \Omega})|$ in dB')
plt.legend(loc=3)
plt.grid()
# plot phase responses
plt.figure(figsize=(10, 3))
plt.plot(Om, np.unwrap(np.angle(H)), label='continuous')
plt.plot(Om, np.unwrap(np.angle(Hq)), label='quantized')
plt.title('Phase')
plt.xlabel(r'$\Omega$')
plt.ylabel(r'$\varphi (\Omega)$ in rad')
plt.legend(loc=3)
plt.grid()
```

**Exercise**

* Decrease the word length `w` of the filter. What happens? At what word length does the filter become unstable?
* Increase the order `N` of the filter for a fixed word length `w`. What happens?

Solution: The deviations from the continuous (desired) realization of the filter increase with decreasing word length. The filter with order `N=5` becomes unstable for `w = 10`. Increasing the order `N` of the filter for a fixed word length results also in instabilities. Consequently, for a high order filter also a higher word length is required.

**Copyright**

This notebook is provided as [Open Educational Resource](https://en.wikipedia.org/wiki/Open_educational_resources). Feel free to use the notebook for your own purposes. The text is licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/), the code of the IPython examples under the [MIT license](https://opensource.org/licenses/MIT). Please attribute the work as follows: *Sascha Spors, Digital Signal Processing - Lecture notes featuring computational examples, 2016-2018*.


# 鍛鍊你的資料分析力 | Python 程式設計

> 模組與套件

郭耀仁 <yaojenkuo@datainpoint.com> from [DATAINPOINT](https://www.datainpoint.com/)

> One feature of Python that makes it useful for a wide range of tasks is that it comes "batteries included"—that is, the Python standard library contains useful tools for a wide range of tasks.

## 程式封裝的層級

- **套件（Libraries）**
    - **模組（Modules）**
        - 類別（Classes）
            - 函式（Functions）

## 大綱

- `import` 指令
- 自訂模組
- 自訂套件

## `import` 指令

## 使用 `import` 指令載入標準與第三方模組套件

- 標準模組套件（Standard modules/libraries）
- 外部模組套件（Third-party modules/libraries）

## 標準模組套件：伴隨 Python 直譯器而來，無需額外安裝

- `re`
- `datetime`
- `random`
- `math`
- ...etc.

Source: <https://docs.python.org/3/library/>

## 使用 `import` 指令：以 `math` 為例

```python
import math

print(math.pi)
```

    3.141592653589793

## 使用 `import` 指令搭配縮寫

```python
import math as m

print(m.pi)
```

    3.141592653589793

## 使用 `from` 搭配 `import` 指令載入部分功能

```python
from math import sin, pi

print(sin(pi/2))
```

    1.0

## 第三方套件：使用 `pip install` 指令安裝

```bash
pip install LIBRARY_NAME
```

## 使用 `pip` 安裝第三方套件 `numpy`

```bash
pip install numpy
```

## 檢視套件安裝的路徑：使用 `__file__` 屬性

```python
import numpy as np
print(np.__file__)
```

## 套件安裝的路徑（以我們上課所使用的環境為例）

- 標準模組套件：`/srv/conda/envs/notebook/lib/python3.7`
- 外部模組套件：`/srv/conda/envs/notebook/lib/python3.7/site-packages`

## 前往觀看 this

New > Terminal

```bash
cd /srv/conda/envs/notebook/lib/python3.7
ls | grep this
cat this.py
```

## 前往觀看 numpy

New > Terminal

```bash
cd /srv/conda/envs/notebook/lib/python3.7/site-packages
ls | grep numpy
```

## `pip` 是 Python 的模組套件管理工具

- 不需額外安裝
- 協助使用者從 [Python Pacakge Index](https://pypi.org/) 下載、安裝或更新

## 檢視 `pip` 版本

```bash
pip --version
```

## 更新 `pip`

- Linux / macOS

```bash
pip install --upgrade pip
```

- Windows

```bash
python -m pip install -U pip
```

## 自訂模組

## 何謂模組

- 將函式或類別封裝在一個 `.py` 檔案中
- `.py` 的檔名就是模組名稱

## 將 `celsius_to_fahrenheit()` 函式封裝在 `temperature_scale.py` 的檔案中

模組名稱：`temperature_scale`。

```python
# temperature_scale.py
def celsius_to_fahrenheit(x):
    return x * 9/5 + 32
```

## 將 `temperature_scale.py` 儲存在 `/notebooks` 目錄下（以我們上課所使用的環境為例）

```python
import temperature_scale as ts

ts.celsius_to_fahrenheit(30)
```

    86.0

```python
from temperature_scale import celsius_to_fahrenheit

celsius_to_fahrenheit(30)
```

    86.0

```python
from temperature_scale import celsius_to_fahrenheit as c2f

c2f(30)
```

    86.0

## 將 `CelsiusFahrenheit` 類別加入到 `temperature_scale.py` 的檔案中

模組名稱：`temperature_scale`。

```python
# temperature_scale.py
class CelsiusFahrenheit:
    def c2f(self, x):
        return x * 9/5 + 32
    def f2c(self, x):
        return (x - 32) * 5/9
```

## 將 `temperature_scale.py` 儲存在 `/notebooks` 目錄下（以我們上課所使用的環境為例）

```python
import temperature_scale as ts

celsius_fahrenheit = ts.CelsiusFahrenheit()
print(celsius_fahrenheit.c2f(30))
print(celsius_fahrenheit.f2c(86))
```

    86.0
    30.0

```python
from temperature_scale import CelsiusFahrenheit

celsius_fahrenheit = CelsiusFahrenheit()
print(celsius_fahrenheit.c2f(30))
print(celsius_fahrenheit.f2c(86))
```

    86.0
    30.0

```python
from temperature_scale import CelsiusFahrenheit as CF

celsius_fahrenheit = CF()
print(celsius_fahrenheit.c2f(30))
print(celsius_fahrenheit.f2c(86))
```

    86.0
    30.0

## 自訂套件

## 何謂套件

- 多個功能相關的模組（.py 檔案）可以組織成一個套件（資料夾）
- 將 `temperature_scale.py` 複製到 `demo_scripts` 資料夾
- 資料夾名稱就是套件名稱（`demo_scripts`）

```python
from demo_scripts.temperature_scale import CelsiusFahrenheit

celsius_fahrenheit = CelsiusFahrenheit()
print(celsius_fahrenheit.c2f(30))
print(celsius_fahrenheit.f2c(86))
```

    86.0
    30.0

## 隨堂練習

## 隨堂練習：在 `/notebooks` 自訂一個名為 `bmi_calculator` 的模組

\begin{equation}
BMI = \frac{weight_{kg}}{height_{m}^2}
\end{equation}

<https://en.wikipedia.org/wiki/Body_mass_index#/media/File:BMI_chart.svg>

- 在該模組中定義一個類別 `BMI`
- `BMI` 類別具備兩個方法
    - `get_bmi()`
    - `get_bmi_label()`

```python
# bmi_calculator.py
class BMI:
    """
    >>> from bmi_calculator import BMI
    >>> bmi = BMI(198, 129) # Zion Williamson
    >>> bmi.get_bmi()
    32.90480563207836
    >>> bmi.get_bmi_label()
    'Obese'
    >>> bmi = BMI(206, 113) # LeBron James
    >>> bmi.get_bmi()
    26.628334433028563
    >>> bmi.get_bmi_label()
    'Overweight'
    """
```

## 隨堂練習：在 `/notebooks` 新增一個名為 `casino` 的模組

- 在該模組中定義一個類別 `Casino`
- `Casino` 類別具備方法：
    - `toss_a_coin()`
    - `roll_a_dice()`
    - `deal_a_card()`

備註：使用標準模組套件 [random](https://docs.python.org/3/library/random.html) 實現「隨機性」需求

```python
# casino.py
class Casino:
    """
    >>> from casino import Casino
    >>> my_casino = Casino()
    >>> my_casino.toss_a_coin()
    'Head' # 或 'Tail'
    >>> my_casino.roll_a_dice()
    6 # 或 1, 2, 3, 4, 5
    >>> my_casino.deal_a_card()
    '2 of hearts' # 或其他 51 種大小花色 'king of spades', 'ace of diamonds', 'jack of clubs'...
    """
```

## 隨堂練習：在 `/notebooks` 新增一個名為 `stats` 的模組

- 在該模組中定義一個類別 `Stats`
- `Stats` 類別具備方法：
    - `mean()`
    - `median()`：<https://en.wikipedia.org/wiki/Median>
    - `modes()`：<https://en.wikipedia.org/wiki/Mode_(statistics)>

```python
# stats.py
class Stats:
    """
    >>> from stats import Stats
    >>> statistics = Stats(1, 2, 2, 3, 4, 7, 9)
    >>> statistics.mean()
    4.0
    >>> statistics.median()
    3
    >>> statistics.modes()
    [2]
    >>> statistics = Stats(1, 2, 2, 3, 4, 7, 9, 7)
    >>> statistics.mean()
    4.375
    >>> statistics.median()
    3.5
    >>> statistics.modes()
    [2, 7]
    """
```

## 執行測試

```python
%load ../test_cases/test_cases_09.py
```