以苯甲酸 (BA) 與己二烯酸 (SA) 的 HPLC 資料為例,完整走一次 「小波波峰偵測 → 基線校正 → 差分演化對位 → 峰積分 → 加權檢量線 → 樣品定量」的流程。 每個步驟都附上實際運算出來的圖與數字。
本文使用的是食品中苯甲酸與己二烯酸的反相 HPLC 資料——這是最常見的防腐劑檢驗項目之一, 兩者可在同一次沖提中以 UV 230 nm 同時定量[15]。 本批資料共 5 個標準品(0.25 / 10 / 25 / 50 / 100 ppm)與 6 個樣品, 由 Shimadzu LCsolution 匯出為 ASCII 檔。
同一支管柱、同一套方法,不同時間跑出來的層析圖,滯留時間 (retention time, RT) 不會完全一樣。 造成漂移的原因包括管柱溫度、流動相組成微變、幫浦壓力、管柱老化等。
這批資料就是活生生的例子:sample-1 ~ sample-3 是上午跑的,整條層析圖比下午的標準品左移約 0.08 ~ 0.10 分鐘。下圖左為標準品、右為樣品,虛線是標準品的平均峰位置—— 可以清楚看到樣品的兩個峰都往左偏了。
Peak alignment 就是把每張圖的峰「推」回共同的時間軸上,讓後續可以用同一個積分視窗 處理所有樣品,也讓不同批次的資料可以直接比較。
alignDE 出自 Zhang, Chen & Liang (2011)[1, 2],核心想法是 「用小波找出峰在哪裡,再用差分演化搜尋每個峰該平移多少」。 和常見的 COW (correlation optimised warping)[3] 相比,它最大的優點是 只平移峰、只拉伸峰與峰之間的基線區段,因此峰形、峰高、峰面積都不會被扭曲—— 這對定量分析非常關鍵[4]。
[峰起點, 峰終點] 區間原封不動地複製過去,
只有峰與峰之間的區段用線性內插拉伸或壓縮來吸收位移量。
所以對位前後同一個峰的面積理論上完全相同——這一點我們稍後會用數字驗證。層析圖的峰近似高斯形。Mexican Hat 小波(高斯函數的二階導數)長得就像一個峰, 所以拿它去和訊號做連續小波轉換 (CWT),在「峰的位置、且尺度 s 接近峰寬」的地方 係數會出現極大值。
下圖上半是參考層析圖(100 ppm 標準品),下半是 CWT 係數的絕對值 |W(t,s)| (顏色越紅越大),黑線是脊線 (ridge line)——把不同尺度下的局部極大值串起來的軌跡。 一條夠長、且訊噪比夠高的脊線,就代表一個真正的波峰。
ridgeLength」加上「訊噪比 ≥ SNR.Th」兩個條件過濾,
就能穩健地只留下真正的峰。這套框架由 Du、Kibbe & Lin 提出[5],
原本用於質譜圖的峰偵測(即 MassSpecWavelet 套件),alignDE 直接沿用到層析圖上。值得注意的是,sample-3 與 sample-5 只偵測到 1 個峰——小波演算法認為它們的 SA 位置 沒有夠強的訊號。這和後面定量算出的「低於偵測極限」結論完全一致,等於是兩個獨立方法互相驗證。
| 檔案 | 偵測波峰數 | BA 峰 RT (min) | SA 峰 RT (min) |
|---|---|---|---|
| 0.25ppm.csv | 2 | 5.375 | 5.933 |
| 10ppm.csv | 2 | 5.367 | 5.942 |
| 25ppm.txt | 2 | 5.392 | 5.967 |
| 50ppm.txt | 2 | 5.358 | 5.925 |
| 100ppm.csv | 2 | 5.375 | 5.925 |
| sample-1.txt | 2 | 5.292 | 5.842 |
| sample-2.txt | 2 | 5.292 | 5.833 |
| sample-3.txt | 1 | 5.275 | — |
| sample-4.txt | 2 | 5.358 | 5.917 |
| sample-5.txt | 1 | 5.383 | — |
| sample-6.txt | 2 | 5.350 | 5.942 |
知道峰頂在哪裡還不夠,要積分就必須知道峰從哪裡開始、到哪裡結束。
alignDE 改用 Haar 小波再做一次轉換:Haar 小波是一個階梯函數,它的轉換係數在
訊號斜率變化最劇烈的地方(也就是峰的起點與終點)會出現局部極值。
函式 widthEstimationCWT() 就是在峰頂兩側搜尋這些位置。
本流程的做法是:只從參考層析圖推導一組積分視窗,然後套用到所有對位後的圖。 由於 BA 與 SA 兩峰之間留有空隙,程式再依層析慣例把共用邊界移到谷底 (valley-to-valley):
UV 偵測器的基線會隨梯度沖提、管柱溫度而漂移。若不扣掉,積分面積會多算一塊梯形, 而且不同樣品多算的量還不一樣。
alignDE 用 Whittaker 平滑器[6, 7](懲罰最小平方法)擬合基線: 在「貼近資料」與「基線要夠平滑」之間取平衡, 並且只讓非峰區的點參與擬合(峰區的權重設得很低),避免基線被峰拉高。
上述做法必須先知道峰在哪裡(步驟 1–2 的結果)才能設定權重。 同一團隊後續提出的 airPLS(自適應迭代重新加權懲罰最小平方法)[8] 則不需要任何先驗資訊:先用全部點擬合一條基線,把高於基線的點視為波峰、權重歸零, 把低於基線的點依偏離量給予指數權重,反覆迭代直到殘餘負值夠小為止。
它可以直接建構在 alignDE 內建的 WhittakerSmooth() 之上,不需額外套件:
airPLS <- function(x, lambda = 100, porder = 1, itermax = 20, tol = 0.001) {
m <- length(x); w <- rep(1, m); z <- x
for (i in seq_len(itermax)) {
z <- WhittakerSmooth(x, w, lambda, porder)
d <- x - z; neg <- d < 0
dssn <- sum(abs(d[neg]))
if (dssn < tol * sum(abs(x))) break # 收斂
w[!neg] <- 0 # 高於基線 → 視為波峰
w[neg] <- exp(i * abs(d[neg]) / dssn) # 低於基線 → 依偏離量加權
w[1] <- exp(i * max(abs(d[neg])) / dssn); w[m] <- w[1]
}
z
}
本流程實際跑了這個比較:同一組積分視窗、同一套加權檢量線,只換基線演算法, 並掃描 λ = 10 / 100 / 1000 / 10000。
8 個高於報告下限的結果中,CWT 與 airPLS (λ = 10–100) 的差異 除 sample-1 的 BA 為 4.0% 外,其餘全部在 1.8% 以內。 檢量線品質 airPLS 甚至略優(BA 的 R² 由 0.99707 提升到 0.99773, 回收率最大偏差由 8.90% 降到 8.01%)。換句話說,這批資料用不用 airPLS 都可以。
| 分析物 | 樣品 | CWT | airPLS_10 | airPLS_100 | airPLS_1000 | airPLS_10000 | 低 λ 群一致性 (%) |
|---|---|---|---|---|---|---|---|
| BA | sample-1.txt | 18.766 | 19.546 | 19.278 | 19.585 | 19.584 | 4.0 |
| BA | sample-2.txt | 0.053 | 0.054 | 0.061 | 0.072 | 0.074 | 15.3 |
| BA | sample-3.txt | 8.190 | 8.122 | 8.209 | 8.202 | 8.204 | 1.1 |
| BA | sample-4.txt | 1.389 | 1.406 | 1.387 | 1.399 | 1.420 | 1.4 |
| BA | sample-5.txt | 10.247 | 10.304 | 10.258 | 10.239 | 10.240 | 0.6 |
| BA | sample-6.txt | 30.929 | 30.701 | 30.954 | 30.953 | 31.031 | 0.8 |
| SA | sample-1.txt | 42.565 | 42.735 | 42.749 | 42.740 | 42.733 | 0.4 |
| SA | sample-2.txt | 5.886 | 5.792 | 5.898 | 5.829 | 5.830 | 1.8 |
| SA | sample-3.txt | 0.010 | 0.034 | 0.027 | 0.073 | 0.080 | 88.0 |
| SA | sample-4.txt | 2.062 | 2.072 | 2.061 | 2.067 | 2.082 | 0.5 |
| SA | sample-5.txt | 0.003 | 0.019 | 0.013 | 0.105 | 0.111 | 122.3 |
| SA | sample-6.txt | 0.596 | 0.648 | 0.609 | 0.855 | 0.907 | 8.4 |
灰色列為低於報告下限 0.25 ppm 的結果(判定皆為 N.D.,差異不影響結論)。「低 λ 群一致性」= CWT / airPLS_10 / airPLS_100 三者的全距相對中位數。
本流程維持 CWT 法為主(它能利用步驟 1–2 已經算出的峰位置,在本資料這種
「微量峰坐在大峰拖尾上」的情境有實質優勢),並把 airPLS 當作獨立的交叉驗證:
兩法一致的結果可以放心報告;兩法分歧的結果(都落在報告下限附近)就該人工覆核。
若要改用 airPLS 當主方法,在腳本頂端把 BASELINE_METHOD 改成 "airpls" 即可。
到這裡我們已經知道每張圖有哪些峰、峰的起訖在哪。最後一步是問: 每個峰要平移幾個取樣點,才能和參考圖最像?
alignDE 把這個問題交給差分演化 (Differential Evolution, DE)[9] ——一種族群式的全域最佳化演算法。 目標函數是「平移後的訊號與參考訊號的線性相關係數」,要最大化:
DE 的好處是不需要目標函數可微、也不容易卡在局部最佳解,代價是要跑很多次評估
(本例族群 60、150 世代)。alignDE 3.0.0 把最佳化交給 DEoptim 套件的 C 實作[10],
比舊版內建的純 R 版本快 1–2 個數量級,因此每張圖只需約 0.2–0.4 秒。
用與參考圖的相關係數當指標,改善最大的是 sample-3.txt(0.356 → 0.822)。 套用的位移量也和肉眼觀察到的漂移吻合:sample-1 ~ sample-3 都被往右推了約 0.08 ~ 0.10 分鐘。
| 檔案 | 套用位移 (min) | 相似度 對位前 | 相似度 對位後 | 相似度改善 | 耗時 (秒) |
|---|---|---|---|---|---|
| 0.25ppm.csv | +0.000 | 0.9671 | 0.9671 | +0.0000 | 0.32 |
| 10ppm.csv | +0.000 | 0.9981 | 0.9997 | +0.0016 | 0.26 |
| 25ppm.txt | -0.025 | 0.9425 | 0.9995 | +0.0570 | 0.28 |
| 50ppm.txt | +0.008 | 0.9959 | 0.9996 | +0.0037 | 0.25 |
| 100ppm.csv | +0.000 | 1.0000 | 1.0000 | +0.0000 | 0.00 |
| sample-1.txt | +0.083 | 0.5081 | 0.8695 | +0.3614 | 0.27 |
| sample-2.txt | +0.100 | 0.1989 | 0.5217 | +0.3228 | 0.27 |
| sample-3.txt | +0.100 | 0.3556 | 0.8222 | +0.4666 | 0.18 |
| sample-4.txt | +0.017 | 0.9627 | 0.9894 | +0.0267 | 0.25 |
| sample-5.txt | -0.008 | 0.8143 | 0.8215 | +0.0072 | 0.18 |
| sample-6.txt | +0.017 | 0.8086 | 0.8279 | +0.0193 | 0.27 |
對位完成後,用梯形法在共同視窗內對基線校正後的訊號積分:
本流程完全獨立於儀器軟體重新算了一次面積。五個標準品的十個峰, 和 LCsolution 報告的面積差異最大只有 3.02%—— 可以確認積分邏輯是正確的。
表格中的「未對位偏差」欄位,是拿沒有對位的訊號套用同一個視窗積分,再和對位後的結果比較。 要看這個欄位,必須只挑實際被平移過的層析圖才有意義(位移量為 0 的圖偏差必然是 0,不能當證據)。 真正被移動的幾張圖中,25ppm(位移 −0.025 min)偏差為 +0.02% / −0.45%, sample-1(位移 +0.083 min)為 +1.13% / −0.44%—— 都在積分誤差的量級內,證實 alignDE 確實保留了峰面積。
偏差特別大的三個(sample-2 的 BA、sample-3 與 sample-5 的 SA)面積都小於 2 萬 µV·s, 屬於雜訊等級的訊號,本來就沒有定量意義。
| 檔案 | 分析物 | 面積 (uV*s) | LCsolution 面積 (uV*s) | vs LCsolution (%) | 未對位偏差 (%) |
|---|---|---|---|---|---|
| 0.25ppm.csv | BA | 37,948 | 38,440 | -1.28 | +0.00 |
| 0.25ppm.csv | SA | 13,852 | 14,164 | -2.20 | +0.00 |
| 10ppm.csv | BA | 811,644 | 817,760 | -0.75 | +0.01 |
| 10ppm.csv | SA | 547,324 | 553,774 | -1.16 | -0.08 |
| 25ppm.txt | BA | 1,979,172 | 1,993,231 | -0.71 | +0.02 |
| 25ppm.txt | SA | 1,331,736 | 1,344,864 | -0.98 | -0.45 |
| 50ppm.txt | BA | 4,108,283 | 4,136,240 | -0.68 | +0.01 |
| 50ppm.txt | SA | 2,743,772 | 2,776,557 | -1.18 | +0.00 |
| 100ppm.csv | BA | 8,989,726 | 9,093,290 | -1.14 | +0.00 |
| 100ppm.csv | SA | 5,933,970 | 6,118,540 | -3.02 | +0.00 |
| sample-1.txt | BA | 1,546,022 | 1,629,519 | -5.12 | +1.13 |
| sample-1.txt | SA | 2,372,467 | 2,474,533 | -4.12 | -0.44 |
| sample-2.txt | BA | 14,395 | 16,086 | -10.51 | +50.79 |
| sample-2.txt | SA | 332,796 | 339,793 | -2.06 | -1.85 |
| sample-3.txt | BA | 692,487 | 706,509 | -1.98 | -0.05 |
| sample-3.txt | SA | 384 | 1,837 | -79.12 | +43.31 |
| sample-4.txt | BA | 131,780 | 134,493 | -2.02 | +0.00 |
| sample-4.txt | SA | 114,223 | 118,948 | -3.97 | +0.15 |
| sample-5.txt | BA | 861,660 | 878,260 | -1.89 | -0.00 |
| sample-5.txt | SA | 117 | 1,067 | -88.99 | +31.66 |
| sample-6.txt | BA | 2,566,041 | 2,607,795 | -1.60 | -0.01 |
| sample-6.txt | SA | 32,549 | 49,096 | -33.70 | +1.60 |
把五個標準品的面積對濃度做線性迴歸。這裡有一個容易踩的陷阱: 本組標準品濃度跨 400 倍(0.25 → 100 ppm)。 一般未加權的最小平方法會被最高濃度點主導,低濃度端嚴重失真。
為什麼是 1/x²,而不是 1/x?權重不該憑感覺挑。判準是「儀器響應的標準差
σ 隨濃度 x 怎麼變」[12]:σ 若不隨濃度改變就不必加權;σ² 與 x 成正比時用
1/x;σ 與 x 成正比(即相對標準差固定)時用 1/x²。層析峰面積多屬最後一種——
濃度愈高、絕對誤差愈大而相對誤差大致穩定,所以 1/x² 是合理起點。
實務上的做法是三種都跑一次、比較低濃度端的回收率再定案,
本流程的 results/weighting_comparison.csv 就是這個比較的輸出。
若要判斷資料是否真的具異質變異,可用殘差分析加統計檢定確認[13]。
同系列的咖啡因定量教材用的是未加權的最小平方法——那組標準品扣掉空白 只跨 8 倍(25 → 200 ppm),殘差圖看不到低濃度端擠成一團的喇叭形,未加權就夠了。 同樣是 HPLC 檢量線,濃度範圍不同、答案就不同,所以別把「要加權」當成通則背下來。
| 分析物 | alignDE 斜率 | alignDE 截距 | alignDE R2 | LOD (ppm) | LOQ (ppm) | 報告下限 (ppm) | LCsolution 斜率 |
|---|---|---|---|---|---|---|---|
| BA (苯甲酸) | 82,386.5 | 17,326.6 | 0.99706 | 0.051 | 0.155 | 0.25 | 83,065.9 |
| SA (己二烯酸) | 55,565.7 | -48.6 | 0.99829 | 0.039 | 0.118 | 0.25 | 56,482.7 |
本文的 LOD 與 LOQ 依 ICH Q2(R2)[14] 的定義計算: LOD = 3.3σ/S、LOQ = 10σ/S,其中 S 為檢量線斜率、σ 為響應的標準差 (加權迴歸下取最低標準點處的殘差標準差)。
檢查檢量線好不好,比 R² 更靈敏的指標是把標準品的面積代回檢量線、看能不能還原成原本的濃度。 R² 對高濃度點的誤差不敏感(0.99 以上看起來都很漂亮),但回收率會誠實地暴露問題。
| 分析物 | 檔案 | 標示濃度 (ppm) | alignDE 面積 (uV*s) | 回算濃度 (ppm) | 回收率 (%) | 線性判定 |
|---|---|---|---|---|---|---|
| BA | 0.25ppm.csv | 0.25 | 37,948 | 0.250 | 100.12 | OK |
| BA | 10ppm.csv | 10.00 | 811,644 | 9.641 | 96.41 | OK |
| BA | 25ppm.txt | 25.00 | 1,979,172 | 23.813 | 95.25 | OK |
| BA | 50ppm.txt | 50.00 | 4,108,283 | 49.656 | 99.31 | OK |
| BA | 100ppm.csv | 100.00 | 8,989,726 | 108.906 | 108.91 | OK |
| SA | 0.25ppm.csv | 0.25 | 13,852 | 0.250 | 100.07 | OK |
| SA | 10ppm.csv | 10.00 | 547,324 | 9.851 | 98.51 | OK |
| SA | 25ppm.txt | 25.00 | 1,331,736 | 23.968 | 95.87 | OK |
| SA | 50ppm.txt | 50.00 | 2,743,772 | 49.380 | 98.76 | OK |
| SA | 100ppm.csv | 100.00 | 5,933,970 | 106.793 | 106.79 | OK |
把樣品的積分面積代入檢量線反推濃度:
| 樣品 | 分析物 | alignDE 面積 (uV*s) | 濃度 (ppm) | 稀釋倍數 | 樣品濃度 (ppm) | 判定 |
|---|---|---|---|---|---|---|
| sample-1.txt | BA | 1,546,022 | 18.555 | 1 | 18.555 | 定量有效 |
| sample-1.txt | SA | 2,372,467 | 42.697 | 1 | 42.697 | 定量有效 |
| sample-2.txt | BA | 14,395 | -0.036 | 1 | -0.036 | N.D. (< LOD) |
| sample-2.txt | SA | 332,796 | 5.990 | 1 | 5.990 | 定量有效 |
| sample-3.txt | BA | 692,487 | 8.195 | 1 | 8.195 | 定量有效 |
| sample-3.txt | SA | 384 | 0.008 | 1 | 0.008 | N.D. (< LOD) |
| sample-4.txt | BA | 131,780 | 1.389 | 1 | 1.389 | 定量有效 |
| sample-4.txt | SA | 114,223 | 2.057 | 1 | 2.057 | 定量有效 |
| sample-5.txt | BA | 861,660 | 10.248 | 1 | 10.248 | 定量有效 |
| sample-5.txt | SA | 117 | 0.003 | 1 | 0.003 | N.D. (< LOD) |
| sample-6.txt | BA | 2,566,041 | 30.936 | 1 | 30.936 | 定量有效 |
| sample-6.txt | SA | 32,549 | 0.587 | 1 | 0.587 | 定量有效 |
| 參數 | 函式 | 本例設定 | 調整方向 |
|---|---|---|---|
| ROI | — | 4.5 – 7.0 min | 避開 1.2–1.7 min 的溶劑擾動 |
| scales | cwt() | 1 – 31 | 上限受訊號長度限制(301 點補到 512,最大 31) |
| SNR.Th | identifyMajorPeaks() | 1 | 0–3;越低偵測到越多峰 |
| ridgeLength | identifyMajorPeaks() | 5 | 5–10;越大越嚴格 |
| lambda | baselineCorrectionCWT() | 100 | 10–1000;越大基線越平滑 |
| threshold | baselineCorrectionCWT() | 0.3 | 峰形門檻,一般取 0.3 |
| n | peakClustering() | 5 | 3–5;峰間距門檻 |
| slack | alignDE() | 25 點 ≈ 0.21 min | 設成資料中看得到的最大位移 |
| NP / itermax | alignDE() | 60 / 150 | NP 約 2×slack;itermax ≥ 150 |
set.seed(20170522) 固定亂數種子,確保結果可重現。# 1. 安裝套件(僅需一次)
install.packages(c("DEoptim", "Matrix", "remotes"))
remotes::install_github("Tai-ShengYeh/alignDE")
# 2. 執行分析:產生 results_align/ 的 10 張圖與 6 個 CSV
Rscript hplc_alignDE.R
# 3. 產生這份教學網頁
python build_teaching_html.py
| 檔案 | 說明 |
|---|---|
lcsolution_io.R | LCsolution 匯出檔解析器(共用模組) |
hplc_alignDE.R | 本流程主程式:對位 → 積分 → 檢量線 → 定量 |
build_teaching_html.py | 本教學網頁產生器 |
hplc_calib.R / hplc_calib.py | 另一套流程:直接用 LCsolution 峰表面積建檢量線 |
results_align/ | 圖檔與 CSV 報表 |
依在本文中首次出現的順序編號;內文的上標數字可點選跳至對應條目。 所有 DOI 均經查證。