淺談統計製程管制(SPC)&小小DIY

Ivan
12 min readFeb 2, 2021

--

Statistic Process Control (SPC)

因為工作是個製程仔,所以每天最常看到的就是這個叫SPC的東西…
SPC中文叫統計製程管制,當然叫SPC比較簡潔乾脆,主要用途是將要監控的參數標準化後,依照時間順序呈現在圖表上,當參數發生異常變動時,可由SPC chart觀察出來,
當工程師發現管制圖異常,就要趕緊介入查看參數的異常原因。(不小心就說出我的工作日常QQ)

code 好讀版

管制圖?怎麼管?

SPC的歷史就不贅述了,餵狗第一條就有了~想說的是如果你要監控理應穩定的參數隨著時間的表現,就可以用SPC來查看這項參數的變動情況。就好比下面這張圖:

譬如今天我們要生產一支iphone,供應商需要製造它螢幕上的玻璃,那麼在生產的過程中,為了確保玻璃面板的厚度、長度或寬度的參數不會異常,都會對這些參數訂一個目標值(Target),想盡辦法將生產出來的貨品,其量測到的參數越接近target越好。將這些在生產中量測到的這些貨品參數對target值標準化後,以時間軸繪製出來,就能得到上圖。

而這張圖畫出來,就可以發現他之所以能「管」,便是建立在中央極限定理上。一道穩定的製程,在製造手續、製造機器都受到穩定控制的情況下,產品的參數便如同隨機變數,在經過收集多筆資料後,應當符合常態分布,上面講到的Target就是此道生產流程的目標值,理應同為數據的平均值。而SPC chart則是將這多筆的資料以時間這個維度做展開。如果我們將上圖的資料繪成組體圖表示的話,你會看到:

既然是數據組會呈現常態分佈,那麼必然會有機率出現資料點在平均值的三倍標準差之外,就像圖一的那些紅點所標示的。也因此,通常我們會以此製程的3倍標準差,當作管制圖的管制界線(Control limit)。當生產中量測到的參數超出Control limit,就會說這批貨的此項參數Out-Of-Control (OOC),而將此批貨的生產視為出現異常

動手做-產生一組模擬的SPC table

先別一次說太多SPC的東西,太無聊了(工作日常誰想知道XD)
來聊聊上面這些圖怎麼畫吧~畢竟別的沒有,用python畫圖我還算蠻精的(?!)
不過在畫圖之前,總得先有data吧?所以這篇先來講準備資料的部分。
我們根據SPC的理論,需要先產生一組以常態分配產生的亂數,最後搞到dataframe裡面,形成一組SPC 資料表。
所以先把python 資料科學的兩寶(pandas, numpy),還有其他起手式也import進來。
接著,我先把如何產生這張SPC table的一些參數,用 SPCDataConfig 這個class定義起來,之後可以讓其他控制項讀取這個config class來決定要做的事情。
這裡面,資料是由numpy的random.randn函數以 (μ,σ)=(0,1) 常態分布所產生的。而其他的一些基本input參數如下:

  • start_date_str, end_date_str: 文字格式的資料起始/最後時間,格式須遵守’YYYY/mm/dd HH:MM:SS.f’
  • ctype: SPC chart要監控的指標統計參數,本文以每筆資料均為該批貨物量測到的參數之平均值為例。
  • cnt: 資料總筆數
  • data_order: 資料的數量級。0.1, 0.01, 0.001, …依此類推
  • cl: Control limit值,user根據data_order做相應的輸入。
  • target: 指標的target,本例中為0。

同時,因為start_date_str, end_date_str的輸入格式,資料表中的時間格式也將遵循同樣格式,故在轉化為datetime型態的變數時,使用dt_fmt定義相同格式,作為格式引數。 最後,為了在開始~結束的時間內隨機佈下時間撮點,所以再將datetime格式的時間參數數值化。

import pandas as pd
import numpy as np
import os, sys, time, random
from datetime import datetime
class SPCDataConfig:
def __init__(self, start_date_str, end_date_str, ctype, cnt, data_order, cl, target):
self.start_date_str = start_date_str
self.end_date_str = end_date_str
self.ctype = ctype
self.cnt = cnt
self.data_order = data_order
self.cl = cl
self.target = target
self.dt_fmt = '%Y/%m/%d %H:%M:%S.%f'
self.start_date_dt = datetime.strptime(self.start_date_str, self.dt_fmt)
self.end_date_dt = datetime.strptime(self.end_date_str, self.dt_fmt)
self.start_date_num = int(self.start_date_dt.timestamp())
self.end_date_num = int(self.end_date_dt.timestamp())

接著,我將create一系列的class來作為SPC table從無到有的各個元件,

資料時序隨機產生器

DataTimeGenerator中,我用前一節提到的起始/最後時間數值化的結果,產生指定數量的隨機時間數值,並依遞增排序。

class DataTimeGenerator:
def __init__(self, cfg):
self.cfg = cfg
def gen(self):
time_series_num = np.random.randint(self.cfg.start_date_num, self.cfg.end_date_num, self.cfg.cnt)
time_series_num = np.sort(time_series_num)
time_series = [datetime.fromtimestamp(x) for x in time_series_num]
return time_series

指標數值隨機產生器

RawDataGenerator中,同樣以隨機值產生SPC所要監控的指標,並搭配data_order控制指標的數量級。

class RawDataGenerator:
def __init__(self, cfg):
self.cfg = cfg
def gen(self):
values = np.random.randn(self.cfg.cnt) * self.cfg.data_order
return values

Control Limit / Target 產生器

這兩個class,簡單地以np.ones()函數,產生指定control limit 或target的一維陣列。

class ControlLimitGenerator:
def __init__(self, cfg):
self.cfg = cfg
def gen(self):
CL = self.cfg.cl
UCL = np.ones(self.cfg.cnt) * CL
LCL = np.ones(self.cfg.cnt) * -CL
return UCL, LCL
class TargetGenerator:
def __init__(self, cfg):
self.cfg = cfg
def gen(self):
TARGET = np.ones(self.cfg.cnt) * self.cfg.target
return TARGET

貨品編號產生器

構想是想讓SPC chart裡每一批接受監控的貨物,各自有一個名稱,也就是貨號。一批貨中,通常含有數量不一的產品,通常基於產能考量,只會抽檢該批貨中的其中一片去監控製程穩定度。所以這邊LotIdGenerator會產生每一個時間點,受量測的貨物貨號,與產品編號。至於編號格式嘛… 而這批貨底下可能也會有為數不同的產品,所以再以貨號為基底,小數點後兩碼為產品編號:

class LotIdGenerator:
def __init__(self, cfg):
self.cfg = cfg
def gen(self):
LETTER = ['W', 'X', 'Y', 'Z']
lotid = ['A21{0:s}{0:s}{1:03d}'.format(LETTER[random.randint(0, 3)], random.randint(1, 999)) for x in range(self.cfg.cnt)]
itemid = ['{0:02d}'.format(random.randint(1, 25)) for x in range(self.cfg.cnt)]
lot_info = pd.DataFrame({
'Lot': lotid, 'Item2': itemid
}, columns=['Lot', 'Item2'])
lot_info["Lot_ID"] = lot_info['Lot']
lot_info["Item_ID"] = lot_info['Lot'].str.cat(lot_info['Item2'].values, sep='.')
return lot_info

結合所有產生器

定義完所有產生器,最後再用一個整合的產生器,去驅動各個產生器產生數列,並組合成dataframe輸出。
接著就可以定義好你想要的SPC data參數後,匯入Config物件, 再call SPCDataGenerator來產生SPC table

class SPCDataGenerator:
def __init__(self, cfg):
self.cfg = cfg
def gen(self):
TS = DataTimeGenerator(self.cfg)
RD = RawDataGenerator(self.cfg)
CL = ControlLimitGenerator(self.cfg)
TG = TargetGenerator(self.cfg)
LI = LotIdGenerator(self.cfg)
time_series = TS.gen()
values = RD.gen()
UCL, LCL = CL.gen()
target = TG.gen()
lot_info = LI.gen()
self.df = pd.DataFrame({
'Data_Time': time_series,
'Lot_ID': lot_info['Lot_ID'].values,
'Item_ID': lot_info['Item_ID'].values,
'Value': values,
'Target': target,
'UCL': UCL,
'LCL': LCL,
})
self.df = self.df[['Data_Time', 'Lot_ID', 'Item_ID', 'Value', 'Target', 'UCL', 'LCL']]
return self.df# define configurations:
data_cnt = 100
chart_type = 'MEAN'
spc_cfg = SPCDataConfig('2020/11/17 00:00:00.0', '2020/12/18 00:00:00.0', ctype=chart_type, cnt=data_cnt, data_order=0.1, cl=0.3, target=0)
SPC = SPCDataGenerator(spc_cfg)
spc_df = SPC.gen()

於是可以得到下方的輸出結果~

要把pandas 的dataframe給格式化print出來,可以使用tabulate套件

from tabulate import tabulate print(tabulate(spc_df.head(), headers='keys', tablefmt='psql'))

+----+---------+---------------------+----------+------------+-------------+----------+-------+-------+-------+-------+
| | index | Data_Time | Lot_ID | Item_ID | Value | Target | USL | UCL | LCL | LSL |
|----+---------+---------------------+----------+------------+-------------+----------+-------+-------+-------+-------|
| 0 | 0 | 2020-11-17 07:24:41 | A21W641 | A21W641.10 | 0.0935012 | 0 | 0.3 | 0.2 | -0.2 | -0.3 |
| 1 | 1 | 2020-11-17 07:52:37 | A21Z267 | A21Z267.06 | -0.140788 | 0 | 0.3 | 0.2 | -0.2 | -0.3 |
| 2 | 2 | 2020-11-17 10:33:47 | A21W999 | A21W999.06 | -0.00277936 | 0 | 0.3 | 0.2 | -0.2 | -0.3 |
| 3 | 3 | 2020-11-17 15:18:14 | A21Z412 | A21Z412.05 | -0.196211 | 0 | 0.3 | 0.2 | -0.2 | -0.3 |
| 4 | 4 | 2020-11-17 18:54:46 | A21W311 | A21W311.16 | 0.116503 | 0 | 0.3 | 0.2 | -0.2 | -0.3 |
+----+---------+---------------------+----------+------------+-------------+----------+-------+-------+-------+-------+

產生資料的部分就到這裡,畫圖的部份富堅到下次吧!

如果這篇文章對你有所幫助,就幫按個讚吧!

--

--

Ivan
Ivan

Written by Ivan

Coding | Machine Learning | 攝影 | 旅遊 工作現在是個製程仔,以後會是甚麼我還不知道,所以邊書寫文章、邊思索生涯、邊閱覽生活。https://yuweichiu.github.io/

Responses (1)