---
# System prepended metadata

title: Lesson 4 | 自訂函式與資料整理

---

# 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，並能用自訂函式功能壓縮程式碼，讓我們能更簡潔有效地完成一些重複的動作。
