# 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") ``` * 我們希望講他轉換成(橫式)這樣: ![L4-2](https://i.imgur.com/oHjpI6O.png) ## 第三節:初級資料轉換(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內。 ![L4-3](https://i.imgur.com/HbqMTeH.png) <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,並能用自訂函式功能壓縮程式碼,讓我們能更簡潔有效地完成一些重複的動作。