iT邦幫忙

2026 iThome 鐵人賽

DAY 8
0
Software Development

用 Python 打造最順手的良率統計工具:半導體工程師的模組化開發之道系列 第 8 篇

Day 8: 從數據讀取到報表生成 - 建構多用途且可重複使用的分析腳本

  • 分享至 

  • xImage
  •  

本篇目的:了解如何設計與組合 function,以模組化的方式建立可重複使用、易修改的腳本

前言

在 Day 3~ Day 7,我們說明了很多分析 wafer 時常使用的情境與對應 code。而今天要來說明如何將這些功能以模組化程式設計的方式建構function,並且將這些 function 連接起來,串出我們所需要的功能。

資料處理的流程簡介

若我們在處理 wafer 產出資料時,會先開啟好幾個 excel 檔案、刪除不使用的欄位、接著使用各種方式處理 data (例如公式、pivot table、圖表等),最後將整理完成的 data 存檔與放入報告中。我們這樣的每日任務其實就是資料科學中的 ETL(Extract, Transform, Load)。

ETL 的流程如下:

  1. 擷取 (Extract):擷取原始資料,資料一定要從來源中擷取出來才能分析。我們從 MES 系統上撈資料到 excel 的 ”開啟舊檔”,都是擷取資料的範疇。

  2. 轉換 (Transform):將原始資料轉化成有意義的分析資料,這會是花最多功夫的地方。我們手動處理 excel 表格,到使用腳本自動化處理資料清洗、篩選、分類,都屬於這個部分。

  3. 載入 (Load):將處理後的資料儲存起來。例如存下已處理好的 excel 資料等。

我們在 Day 3~ Day 7 討論的大量功能,不會脫離這 3 個分類,而在這裡我們要用模組化的方式將所需的功能串接起來。

什麼是模組化程式設計(Modular Design) 與優點

好的資料處理腳本應該要像生產線的站點一樣,生產線的特點如下

  • 職責分明:依功能區分成各種不同的站點

  • 單一職責:一個站點只做該站點負責的事

  • 低耦合度:新增或修改站點時,不會影響其他站點的運作

  • 極大彈性:可以依照產品需求,自由設計需要的流程

  • 可重複性:無論是 OP 或是 PE ,依設定操作機台時,都會得到相同的結果

而當我們在 Python 中定義函式、類別 (class) 時,就好像在 fab 中配置曝光機、蒸鍍機等各式功能不同的機器。這些函式、類別配置完成後,我們就可以在需要時使用或呼叫它。

機器是不會自己動的,需要先設計製程流程 (run card) 才能讓產線做出樣品。而要讓我們的 code 像貴司的生產線一樣正常運作 (理想上) 的話,我們也必須將各種不同物件像 run card 一樣依需求組合起來,這個過程又稱為工作流編排 (workflow orchestration)。

我們若將功能封裝起來並像串 run card 一樣設計工作流,會有以下優點

  • 可在不同地方重複使用 (呼喚 Python 中最強大的咒語:import )

  • 可以為函數取易讀性高的名字,讓整體工作流容易理解

  • 容易增加功能,就像在 run card 中多加一站

  • 好修改與除錯,可以只針對特定 function 做修改,且發生異常時也容易追蹤是哪個函式出問題

https://ithelp.ithome.com.tw/upload/images/20260921/20182319mXydGupm4n.png

模組化的簡單範例

假設你要設計一組點單 function,客人可以選餐點編號,然後程式將編號轉換成餐點名稱並輸出。非常初學的人可能會這樣寫:

customer_order = (1,1,3,2,4) # 點餐編號
real_product = []  # 裝餐點名稱的 list 

for sn in customer_order:

    if sn == 1:
        real_product.append('apple')

    elif sn == 2:
        real_product.append('banana')

    elif sn == 3:
        real_product.append('grape')

    elif sn == 4:
        real_product.append('pineapple')

上面的寫法是標準的腳本式寫法 (scripting)。雖然大家學生時寫 MATLAB 一定對這寫法不陌生,但以上面的腳本式寫法會有幾個問題

  • 難以閱讀:無法第一眼就看出編號與餐點的對應

  • 重複 code:當需要一次新增 10 個商品時,難道要多出 10 個 elif?

  • 引用困難:我們很難在同一個 py 檔內重複使用此功能。通常這樣的寫法只能用複製貼上來重新使用,還要記得改 real_product 的名稱 (當然,if 迴圈裡的也要改喔)

  • 無法對接 UI:這段 code 因為沒有封裝,因此也無法被其他指令呼叫。當未來想用排程工具或 UI 控制時,根本無法控制此段 code

若要將以上 code 改寫成模組化寫法,實踐方式如下。大致上可以將這套腳本分成以下3部分

  1. 常數的區塊:可以將一些不會讓 function 改動的值放在這,例如讀寫路徑、轉換常數等。獨立一個區塊的好處是容易閱讀與修改常數

  2. Function 的區塊:建構各式功能的區塊,一個 function 最好只負責一個主功能,例如讀取檔案與清理檔案的 function 要分開。

  3. 執行的區塊:將 fucction 依照所需呼叫與串在一起的區塊,如同產品的 run card。這裡是定義整個腳本如何運行的地方。

# 常數的區塊
MENU = {1:'apple', 2:'banana', 3:'grape', 4:'pineapple',}

# Function 的區塊
def map_sn_to_product_name(order: Sequence[int], menu:dict[int,str]) -> list[str]:

    product_list = []

    for sn in order:
        product = menu.get(sn)

        if product is not None: 
            product_list.append(product)
        else:
            print(f'無此產品編碼: {sn}')

    return product_list

# 執行的區塊
real_product = map_sn_to_product_name(customer_order, MENU)

並且可以觀察到以下的 code 比起原始資料有以下優點:

  • 單一職責:這個 function 就只做"將編號與產品名稱對應”這件事,不會去管理產品名稱

  • 極大彈性:新增產品只要在 MENU 內新增項目即可

  • 容易閱讀:編號與產品的關係非常明確

  • 可重複性:這個 function 可以重複被呼叫

Function 的設定方式請參考 Day 5

真實處理 wafer 資料的程式碼實作

架構介紹

這段code 會演示以下功能

  1. 讀取資料並建立 dict[dataframe]

  2. 建立一 xlsx,內含各片的二維產出 (分頁)、以及各片的 Vf2 產出

  3. 輸出各片的 Vf2 mapping (透明背景 + bin 3 ~ 3.1),並且檔名為"片號_vf2.png"

並且這段 code 也可以按照上面分成三個區塊

程式碼連結:

常數的區塊

在 Python 中,常數命名慣例為全大寫。我們可以將不會讓 function 改動的值放在這。

# 常數
OUTPUT = Path('E:/py_practice/sandbox/ithome_2026ironman')
READ_PATH = Path.cwd()/'test_data'/'normal'
HEADER_ROW_NUM = 7
READ_COL = ['SN', 'Vf1', 'Vf2', 'Vr1', 'Ir1', 'Po', 'Wd2', 'X', 'Y']

PO_BIN = [0,1000,1500,1700,1750,1800,1850,2000]
WD2_BIN = [0,442.5,445,447.5,450,452.5,455,457.5,500]

Function 的區塊

這裡就是建構功能的區塊。以下圖為例,我將讀取 data、衍生欄位計算、整理報表、繪圖等指令寫在這,並且讓一個 function 只對應到一個主要功能。

https://ithelp.ithome.com.tw/upload/images/20260921/20182319GzQ151NdR9.png

執行的區塊

以下區塊便是由上到下,依序作業讀檔、清理 data、運算與輸出的地方。另外下面的 if __name__ == "__main__": 的用途是讓這個 py 檔只有在直接執行下 (不是被 import) 才會運行。

if __name__ == "__main__":
    
        
        result_dict:dict[str, list[pd.DataFrame] | pd.DataFrame] = {}

        # 讀檔與轉檔
        dict_all_wafer = read_multi_wafer_data(READ_PATH, READ_COL, HEADER_ROW_NUM)
        df_all_data = turn_mapdict_to_a_dataframe(dict_all_wafer)

        # 將分 bin 結果放入 dataframe 中
        df_all_data = yield_binning(df_all_data, 'Po',PO_BIN)
        df_all_data = yield_binning(df_all_data, 'Wd2',WD2_BIN)
        df_all_data['Count'] = 1

        # 計算每片的各項產出
        df_po_yield = cal_yield_by_wafer(df_all_data, col_name='Po_bin', data_type='percent')
        df_wd_yield = cal_yield_by_wafer(df_all_data, col_name='Wd2_bin', data_type='percent')

        result_dict['All_yield'] = [df_po_yield, df_wd_yield] # 放入輸出項目中

        # 拆出逐片的產出並繪圖
        for wafer_id, df_wafer in dict_all_wafer.items():
            
            # 計算每片的 2D 產出
            df_wafer['Count'] = 1
            df_wafer = yield_binning(df_wafer, 'Po',PO_BIN)
            df_wafer = yield_binning(df_wafer, 'Wd2',WD2_BIN)

            df_a_wafer_2d = cal_2d_yield(df_wafer, col_name='Wd2_bin', row_name='Po_bin')

            result_dict[wafer_id] = df_a_wafer_2d

            # 繪製 mapping 圖
            plot_wafer_mapping(wafer_id, df_wafer, 'Vf2',OUTPUT, display_mode='transparent', scale_mode='manual',manual_range=(3,3.1))

            

        save_path = OUTPUT / 'result.xlsx'
        to_excel_flexible(result_dict, save_path)

To be continued →

我們能自己建立所需的腳本了,但若要給其他人使用似乎還缺一個 UI。本專欄即將進入第二部分 “建構 UI 並與 Python 腳本搭配,我們會在 day 9 ~ day 13 學習如何用 Qt designer 做出 UI 介面。


上一篇
Day 7: 為你的圖片加上醬汁:如何用 Matplotlib 畫出格式正確的 wafer mapping
下一篇
Day 9: 不要再對 UI code 通靈了 - 使用 QT designer 做出你的第一個 UI 介面
系列文
用 Python 打造最順手的良率統計工具:半導體工程師的模組化開發之道 共 13 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言