# Lesson 4 | 自訂函式與資料整理
## 第一節:自訂函式(1)
* 目前我們已經學會了許多的語法,但也慢慢發現往往需要好幾行程式碼才能完成我們要做的事情,我們有沒有可能簡化他呢?
* 自訂函式是一個簡化重複程式碼的好方法,舉例來說我們經常想要對學生的成績做加分,最常用的手段是「開根號乘以10」,我們可以把函數寫成這樣:
* ```def``` 所代表的就是「定義」definition。
```python=
print("定義前:", add_score)
```
```python=
def add_score(score):
modified_score = (score ** (1/2)) * 10
return modified_score
print("定義後:", add_score)
```
* 定義好函式後,我們就可以重複使用他。
```python=
modified_score = add_score(64)
print(modified_score)
modified_score = add_score(81)
print(modified_score)
# 下列程式碼做到相同的事情
score = 10
print((score ** (1/2))*10)
```
## 第一節:自訂函式(2)
* 讓我們複習一下第二課的費波納奇數列,他的做法如下:
```python=
x = [0] * 10
x[0:2] = 1, 1
for idx in range(2, 10):
x[idx] = x[idx - 1] + x[idx - 2]
print(x)
```
* 仔細思考一下,我們若想要指定產生不同長度的費波納奇數列,其中10是個可變動的數字,於是我們將其抽換為變數。
```python=
n = 10
x = [0] * n
x[0:2] = 1, 1
for idx in range(2, n):
x[idx] = x[idx - 1] + x[idx - 2]
print(x)
```
* 這樣是不是就可以將這段程式碼整合成一個函式了?
```python=
def fib(n):
x = [0] * n
x[0:2] = 1, 1
for idx in range(2, n):
x[idx] = x[idx - 1] + x[idx - 2]
return x
```
* 用起來將會非常方便,可以重複使用,要更改程式碼時也只需要修改後再次定義該函式就好。
```python=
x = fib(10)
print(x)
x = fib(20)
print(x)
```
## 第一節:自訂函式(3)
* 假定我們希望讓使用者指定起始值,函式也能允許多個輸入參數:
```python=
def fib(a, b, n):
x = [0] * n
x[0:2] = a, b
for idx in range(2, n):
x[idx] = x[idx - 1] + x[idx - 2]
return x
x = fib(1, 1, 10)
print(x)
x = fib(-5, 3, 10)
print(x)
```
## 第一節:自訂函式(4)
* 然而,在剛剛的函式```fib```中,當 n < 3 時將無法正常產生數列:
```python=
x = fib(1, 1, -2)
print(x)
```
* 所以,我們需要在函式中確認n的大小以避免錯誤產生,還記得條件判斷式嗎?
* 不要忘記函式「print()」能與使用者溝通。
```python=
def fib(a, b, n):
if n < 3:
print("n必須大於等於3。")
else:
x = [0] * n
x[0:2] = a, b
for idx in range(2, n):
x[idx] = x[idx - 1] + x[idx - 2]
return x
```
* 現在當n < 3時,函式就會提出警告。
```python=
x = fib(1, 1, -2)
print(x) # 在n < 3的情況下沒有return,所以回傳None。
x = fib(1, 1, 3)
print(x)
```
## 練習1:
* 問題1:第二節課中尋找質數的程式碼相當長,請你試著將其打包成一個自訂函式,讓使用者能指定要尋找「多少以下的質數」。
* 完整程式碼如下:
```python=
num_to_slove = 101
# 宣告變數
x = list(range(2, num_to_slove))
answer = [True] * len(x)
for i in range(len(x)):
for j in range(i):
if answer[j]:
if x[i] % x[j] == 0:
answer[i] = False
break
for idx in range(len(x)):
if answer[idx]:
print(x[idx])
```
* 這題的重點在於請同學們將```num_to_slove```提出,也請對輸入數字為2以下時進行錯誤提示。
* 現在也請同學一併將質數儲存成一個list,還記得第三課提到list的append()方法嗎?
```python=
# list的方法「append()」的範例
prime_num = []
print(prime_num)
prime_num.append(1)
print(prime_num)
```
<details>
<summary>解答</summary>
```python=
def find_prime_number(n):
if n <= 2:
print("請輸入大於2的n")
else:
x = list(range(2, n))
answer = [True] * len(x)
for i in range(len(x)):
for j in range(i):
if answer[j]:
if x[i] % x[j] == 0:
answer[i] = False
break
prime_num = []
for idx in range(len(x)):
if answer[idx]:
prime_num.append(x[idx])
return prime_num
```
</details>
## 第二節:合併資料(1)
* 現在再讓我們回到第三課所處理的資料檔,這份資料檔最討厭的地方是經常會有重複,所以我們可以使用函式將整個資料整理的過程給打包起來:
* 請按[這裡](https://linchin.ndmctsgh.edu.tw/data/monitoring_1.csv)下載上週的檔案,這是北市數個檢測站在早上5點半所測得的汙染物濃度。
* 這是原始的處理程式碼:
```python=
import csv
import numpy as np
with open('monitoring_1.csv', 'r', encoding = 'CP950') as f:
reader = csv.DictReader(f)
headers = reader.fieldnames
air_data = {}
for i in headers:
air_data[i] = []
for row in reader:
for k, v in row.items():
air_data[k].append(v)
air_data['duplicated'] = [False]
for i in range(1, len(air_data['school'])):
is_dupliated = air_data['school'][i] in air_data['school'][0:i]
air_data['duplicated'].append(is_dupliated)
duplicated_row = np.array(air_data['duplicated'])
for k in air_data.keys():
np_data = np.array(air_data[k])
air_data[k] = list(np_data[duplicated_row == False])
```
* 下列程式碼可以讓同學們觀察資料,請直接使用:
```python=
for i in range(len(air_data['time'])):
print(air_data['time'][i],
air_data['school'][i],
air_data['time2'][i], end = "\n")
```
## 第二節:合併資料(2)
* 透過剛剛我們所學,我們可以將這段程式碼包成函式:
```python=
def clean_data(file_path):
with open(file_path, 'r', encoding = 'CP950') as f:
reader = csv.DictReader(f)
headers = reader.fieldnames
air_data = {}
for i in headers:
air_data[i] = []
for row in reader:
for k, v in row.items():
air_data[k].append(v)
air_data['duplicated'] = [False]
for i in range(1, len(air_data['school'])):
is_dupliated = air_data['school'][i] in air_data['school'][0:i]
air_data['duplicated'].append(is_dupliated)
duplicated_row = np.array(air_data['duplicated'])
for k in air_data.keys():
np_data = np.array(air_data[k])
air_data[k] = list(np_data[duplicated_row == False])
return air_data
```
* 讓我們來實際使用看看。
```python=
air_data = clean_data('monitoring_1.csv')
for i in range(len(air_data['time'])):
print(air_data['time'][i],
air_data['school'][i],
air_data['time2'][i], end = "\n")
```
## 第二節:合併資料(3)
* 現在我們要做簡單的資料合併,首先請按[這裡](https://linchin.ndmctsgh.edu.tw/data/monitoring_2.csv)下載另外一個檔案。
* 這是則在早上7點的時候所測得的汙染物濃度。
* 我們利用剛剛的自訂函式```clean_data```直接處理。
```python=
air_data = clean_data('monitoring_2.csv')
for i in range(len(air_data['time'])):
print(air_data['time'][i],
air_data['school'][i],
air_data['time2'][i], end = "\n")
```
## 練習2:
* 問題1:假設我們有興趣的變數其實只有s_d0(這是PM2.5的濃度),而剩下的欄位我們暫時不感興趣,請同學們幫忙把兩個檔案合併。
* 請用下列程式碼創建一個新的dict。
```python=
merge_data = {
'time': [], 'school': [], 's_d0': []
}
```
* 接著用上面的函式```clean_data```分別處理完兩個檔案之後,再用迴圈取出我們要的資料,並放入```merge_data```中
* 先放入早上5點半的資料,再放入早上7點的資料,可以先利用```.keys()```來觀察有哪些欄位名稱。
<details>
<summary>解答</summary>
```python=
air_data_1 = clean_data('monitoring_1.csv')
air_data_2 = clean_data('monitoring_2.csv')
merge_data = {
'time': [], 'school': [], 's_d0': []
}
# 先放入五點的資料
for i in range(len(air_data_1['school'])):
merge_data['time'].append(air_data_1['time'][i])
merge_data['school'].append(air_data_1['school'][i])
merge_data['s_d0'].append(air_data_1['s_d0'][i])
# 再放入七點的資料
for i in range(len(air_data_2['school'])):
merge_data['time'].append(air_data_2['time'][i])
merge_data['school'].append(air_data_2['school'][i])
merge_data['s_d0'].append(air_data_2['s_d0'][i])
```
* 觀察資料。
```python=
for i in range(len(merge_data['time'])):
print(merge_data['time'][i],
merge_data['school'][i],
merge_data['s_d0'][i], end = "\n")
```
* 延伸挑戰:同學們也可以試著創建key:'s_d0_5'以及's_d0_7',若一個學校有五點以及七點的採集資料,將分別放到's_d0_5'以及's_d0_7',若沒有資料則填入```None```,並試著寫出檔案。
</details>
## 第三節:初級資料轉換(1)
* 請在這裡[下載](https://linchin.ndmctsgh.edu.tw/data/comorbidity_1.csv)一份範例資料。
* 這份資料是描述每個人疾病狀況的檔案,我們希望將這份直式資料轉為橫式資料。
* 原始的資料(直式)像這樣:
```python=
with open('comorbidity_1.csv', 'r', encoding = 'CP950') as f:
reader = csv.DictReader(f)
headers = reader.fieldnames
data = {}
for i in headers:
data[i] = []
for row in reader:
for k, v in row.items():
data[k].append(v)
# 先印出欄位名稱。
print(*list(data.keys()))
for i in range(len(data['ID'])):
print(data['ID'][i], data['Disease'][i], end = "\n")
```
* 我們希望講他轉換成(橫式)這樣:

## 第三節:初級資料轉換(2)
* 這是我們第一次面對到一個很實際的案例,在開始之前要先思考怎樣才能達到我們想要的結果。
* 請各位先回想在前面幾節課學過哪些函式或是程式撰寫技巧?我保證只使用已經學會的功能就足以應付這個問題了。
1. 函式「set()」。
2. 函式「list()」。
3. 函式「dict()」。
4. 透過第三方套件NumPy進行索引。
5. 迴圈與條件判斷式。
## 第三節:初級資料轉換(3)
* 在最開始的時候,我們要先對這份資料表做初步分析。這份資料有幾個人?有幾種可能的疾病?
* 我們很明顯是需要用到函式「set()」以及函式「list()」的組合
* 我們要先取得人數,才能創建能放入資料的變數。
```python=
uni_id = list(set(data['ID']))
uni_id.sort() # 這裡是list的另外一個方法sort(),可以排序資料,可有可無。
print(uni_id)
print(len(uni_id))
```
* 讓我們再觀察一下總共有幾種疾病。
```python=
uni_disease = list(set(data['Disease']))
print(uni_disease)
print(len(uni_disease))
```
## 第三節:初級資料轉換(4)
* 我們發現有24位病人,總共有4種疾病,因此先創造一個長度為24,有5個key然後value為list的字典,其中第一個Key為ID,其他key分別來描述4種疾病的狀態。
* 這裡的重點在於,我們可以先將疾病都填```False```,讓後面的程式碼更簡潔。
```python=
new_data = {
'ID': uni_id,
'CKD': [False] * len(uni_id),
'Depression': [False] * len(uni_id),
'DM': [False] * len(uni_id),
'HTN': [False] * len(uni_id)
}
print(new_data)
```
## 第三節:初級資料轉換(5)
* 接著,如果我們想要了解一個人有哪些疾病,我們需要在原始檔中使用索引來找到與他有關的資料。
* 我們可以透過第三方套件NumPy來做到更簡潔的索引。
* 先做資料型態轉換,接著就可以透過索引簡單地來拿到第一個人的資料。
```python=
data['ID'] = np.array(data['ID'])
data['Disease'] = np.array(data['Disease'])
i = 0
sub_id = new_data['ID'][i]
print(sub_id)
sub_disease = data['Disease'][data['ID'] == sub_id]
print(sub_disease)
```
* 這個人所有疾病通通都有,真是不走運,這時候我們又要用到條件判斷式,搭配對於我們對要填入的```new_data```的dict進行索引,就可以完成資料的轉換。
```python=
i = 0
print(new_data['CKD'][i])
print(new_data['Depression'][i])
print(new_data['DM'][i])
print(new_data['HTN'][i])
for j in sub_disease:
if j == "CKD":
new_data['CKD'][i] = True
if j == "Depression":
new_data['Depression'][i] = True
if j == "DM":
new_data['DM'][i] = True
if j == "HTN":
new_data['HTN'][i] = True
```
* 我們接著看一下,確實有成功的填入資料。
```python=
print(*list(new_data.keys()))
for i in range(len(new_data['ID'])):
print(new_data['ID'][i],
new_data['CKD'][i],
new_data['Depression'][i],
new_data['DM'][i],
new_data['HTN'][i], end = "\n")
```
## 練習3:
* 問題1:剛剛所有程式碼由上而下,已經能夠完成這張表格了,現在請你利用迴圈功能把所有步驟完成,讓24個人的疾病狀態都進入new_data內。

<details>
<summary>解答</summary>
- 重點其實就是把他包成迴圈!
```python=
new_data = {
'ID': uni_id,
'CKD': [False] * len(uni_id),
'Depression': [False] * len(uni_id),
'DM': [False] * len(uni_id),
'HTN': [False] * len(uni_id)
}
data['ID'] = np.array(data['ID'])
data['Disease'] = np.array(data['Disease'])
for i in range(len(new_data['ID'])):
sub_id = new_data['ID'][i]
sub_disease = data['Disease'][data['ID'] == sub_id]
for j in sub_disease:
if j == "CKD":
new_data['CKD'][i] = True
if j == "Depression":
new_data['Depression'][i] = True
if j == "DM":
new_data['DM'][i] = True
if j == "HTN":
new_data['HTN'][i] = True
```
* 觀察一下資料。
```python=
print(*list(new_data.keys()))
for i in range(len(new_data['ID'])):
print(new_data['ID'][i],
new_data['CKD'][i],
new_data['Depression'][i],
new_data['DM'][i],
new_data['HTN'][i], end = "\n")
```
</details>
## 總結
* 本次課程我們介紹了自訂函式以及一些資料處理的例子,熟悉這些能力將我們減少許多paper work的工作。
* 本節課開始並沒有給大家太多新的函式,而是利用迴圈組合作出一些比較複雜的事情,請同學試著學習如何思考處理流程,資料流、資了前處理是分析資料或是進行研究的基礎核心工作。
* 同學在上完這堂課後,應有能力將一些資料處理流程做成一個簡易的pipeline,並能用自訂函式功能壓縮程式碼,讓我們能更簡潔有效地完成一些重複的動作。