課程講義

十個單元,從一行程式到一份診斷報告

這一頁包含全部教材。網頁上每個深色框都是可以編輯、可以執行的真 R 環境 —— 按下「▶ 執行」就會在你的瀏覽器裡跑真正的 R,不會上傳到任何伺服器。 第一次執行需要下載約 30 MB 的 R 執行環境,請耐心等 20–40 秒。

先預熱 R 環境

UNIT 00

鹽為什麼會把牆吃掉

走進任何一座老磚造建築,你大概都看過這種景象:離地面半公尺以下的牆面, 表層剝落、磚粉簌簌掉、有時還泛著一層白霜。 那層白霜是可溶鹽,而剝落是它造成的。

鹽從哪裡來

地下水沿著磚石的毛細孔往上爬(稱為毛細上升,rising damp), 沿途溶解土壤和建材裡的鹽類帶上來。水在牆面蒸發,鹽留下。 幾十年下來,牆體下段就累積了高濃度的鹽。常見來源還有:

三種破壞機制

機制發生時機破壞方式
結晶壓濕度下降、溶液過飽和 鹽晶體在孔隙裡長大,對孔壁施加壓力。過飽和度高時可達數十 MPa,遠超過砂岩的抗拉強度。
潮解/再結晶循環濕度在臨界值上下反覆 每一次「吸水溶解 → 乾燥結晶」都在同一個孔隙裡再擠一次。循環次數比單次結晶壓更致命。
水合膨脹溫濕度變化使鹽改變含水形式 例如 Na₂SO₄(無水芒硝)吸水變成 Na₂SO₄·10H₂O(芒硝),體積膨脹約 3 倍。這是最惡名昭彰的破壞鹽。
關鍵變數:相對濕度(RH)

每一種鹽都有一個潮解相對濕度(deliquescence RH, DRH): 環境濕度高於它,鹽會自發吸水變成溶液;低於它,鹽會結晶析出。 純氯化鈉在 20–25 °C 的 DRH 約 75 %。 所以理論上,只要把室內濕度穩定控制在遠離 DRH 的一側,破壞就會停止。 問題是 —— 牆裡從來不會只有一種鹽。

混合鹽:為什麼這件事沒那麼簡單

當兩種以上的鹽共存於同一份溶液,離子之間的交互作用會降低水的活性, 使混合物的潮解點低於任何單一組分。[4] 也就是說:混合鹽會在比純鹽更低的濕度下就開始吸水, 因而在一年之中跨越臨界濕度的次數更多、循環更頻繁、破壞更快。

這正是為什麼我們不能只看「這面牆有多少鹽」,而必須算出 七個離子的完整組成,再丟進熱力學模型去算整個混合系統的行為。 那個模型叫 ECOS(Environmental Control of Salts), 它的圖形介面叫 Runsalt

SaltsR 站在哪個位置

① 現場取樣(鑽粉/刮取,記錄取樣高度與深度)
② 乾燥、秤重 → 得到乾樣重 mdry(g)
③ 加去離子水萃取 → 記錄水量 Vwater(mL)
離子層析(IC) → 得到七個離子的 ppm
⑤ SaltsR:單位換算 → 電荷平衡校正 → 扣除石膏 → 產生 ECOS 輸入檔 ←── 本課程
⑥ Runsalt/ECOS 熱力學模擬 → 得到 RH vs 各鹽相莫耳量的曲線
⑦ SaltsR:讀入 Runsalt 輸出 → 整理 → 繪圖 → 判讀     ←── 本課程
⑧ 提出濕度控制與保存處置建議
為什麼第 ⑤ 步不能跳過

離子層析量出來的七個數字,加起來的正電荷和負電荷幾乎永遠不會相等。 可能是分析誤差,也可能是有些離子(最常見的是碳酸根 CO₃²⁻)根本沒被測到。 但熱力學模型要求電中性 —— 你不能餵它一組會違反電中性的組成。 所以必須先做校正。怎麼校正、把差額算到誰頭上,會直接改變模擬結果。 這就是單元 05 的主題,也是整門課的核心。

想一想(不用寫程式)

如果一份樣品的陽離子當量總和明顯大於陰離子,最可能的解釋是什麼? 為什麼「多出來的陽離子」不太可能是儀器誤差?
提示:想想離子層析通常會漏掉哪一種常見的陰離子。答案在單元 05。

UNIT 01

R 的第一行:把它當計算機

R 是一種為資料分析而生的程式語言。你不需要先懂「程式設計」, 只要能把一句話翻成一個算式,就可以開始用它。

1-1 它就是一台計算機

下面這個深色框就是一個真正的 R 環境。按「▶ 執行」試試看, 然後把數字改掉再執行一次。改壞了按「↺ 還原」。

兩件馬上要記住的事

# 後面的文字是註解,R 完全不看它,它是寫給人看的。 養成寫註解的習慣 —— 三個月後回來看自己的程式,你會感謝當初的自己。
執行結果前面的 [1] 不是答案的一部分,它是「這是輸出的第 1 個元素」的意思, 等到單元 2 處理一長串數字時你就會明白它的用處。

1-2 把值存起來:變數與 <-

一直重複打同一個數字既累又容易錯。給它一個名字:

命名的規矩與慣例
  • <- 指派(鍵盤上是「小於號」加「減號」)。= 也能用,但 R 社群習慣用 <-
  • 名字要能看名字就知道是什麼、單位是什麼dry_g 好過 xwater_ml 好過 w
  • 不能以數字開頭,不能有空格。多字之間用底線,例如 chloride_ppm
  • R 區分大小寫Dry_gdry_g 是兩個不同的變數。這是初學者最常見的錯誤來源。

1-3 函式:別人寫好的工具

函式的長相是「名字加括號」:round(3.14159, 2)。 括號裡的東西叫引數(argument),是你交給它處理的材料。

1-4 讀懂錯誤訊息

程式出錯是常態,不是你笨。重點是看懂 R 在抱怨什麼。 下面這段刻意寫了三個錯,執行看看,每一行的訊息長什麼樣子:

錯誤訊息意思怎麼修
object '…' not found這個變數不存在檢查有沒有打錯字、大小寫、是不是忘了先執行上面那行
could not find function "…"這個函式不存在函式名打錯,或是該函式所屬的套件還沒載入
unexpected ')' / '<-'括號或符號沒配對好從左到右數括號,通常是少了一個 )
non-numeric argument你把文字丟給數學運算檢查資料型別,可能是 CSV 讀進來變成文字了
👉 換你做:用變數把 MH-01 的總鹽量算出來

把七個離子的 ppm 各存成一個變數,然後算出「總離子重量百分比」。
公式:每個離子的 wt% = ppm × 水量(mL) / (10000 × 乾重(g)),然後全部加起來。
正確答案是 1.7 wt%

UNIT 02

向量、資料框與管線

上一個單元你為七個離子各開了一個變數。七個還撐得住, 但如果有八個樣品 × 七個離子 = 56 個變數呢?這樣不行。 R 之所以好用,就是因為它讓你一次處理一整排數字

2-1 向量:一排數字當成一個東西

向量化:R 最重要的觀念

ppm * 2 不是把向量乘以 2,而是把裡面每一個數字都乘以 2。 這叫向量化運算(vectorisation)。 它讓你用一行取代七行,而且不會有「第五個忘了改」這種錯。 單元 3 之後,我們幾乎所有計算都是這樣寫的。

2-2 資料框:一張表格

向量是一排,資料框data.frame)是一張表: 每一欄可以是不同型別,每一列是一筆觀測。 離子層析報告、Excel 工作表,在 R 裡都長這樣。

2-3 管線 |>:把句子從裡到外翻成從左到右

當你要「先算 A、再對結果算 B、再對結果算 C」, 傳統寫法是 C(B(A(x))) —— 讀的時候得從最裡面往外剝。 管線運算子 |> 讓你照著做事的順序寫:

兩種管線

|> 是 R 4.1 之後內建的管線。你在網路上還會看到 %>%, 那是 magrittrdplyr 套件提供的版本,功能幾乎一樣。 SaltsR 的官方文件用的是 |>,本課程也統一用它。

👉 換你做:找出最髒的那面牆

salt_samples.csv,計算每個樣品「七個離子 ppm 的總和」, 再找出總和最高的樣品名稱。
提示:可以先把七個 ppm 欄位相加成新的一欄,再用 samples[which.max(samples$total), ]
答案應該是 MH-08(屋簷滴水線) —— 而且它的問題不只是「髒」, 單元 06 會告訴你為什麼它的數據根本不可信。

UNIT 03

從 ppm 到重量百分比(式 1)

離子層析儀給你的是萃取液的濃度(ppm,也就是 mg/L)。 但你真正想知道的是這塊磚裡有多少鹽。這兩件事不一樣。

3-1 為什麼要換算

假設兩份樣品的萃取液都測到 50 ppm 氯離子。第一份用 1 g 磚粉配 100 mL 水, 第二份用 5 g 磚粉配 100 mL 水。哪一面牆比較危險? 顯然是第一份 —— 同樣多的鹽,來自更少的材料。 所以必須把濃度正規化到乾樣重

式 1 · 離子的重量分率

wi = Cppm × Vwater 10 000 × mdry   [wt %]

3-2 單位是怎麼消掉的

一步一步跟著單位走,你就不會再背錯公式:

  1. 萃取液裡有多少鹽?
    濃度 C(mg/L)× 體積 V(L)= 質量(mg)。
    但我們的 V 是毫升,所以要先除 1000:C × V/1000 mg。
  2. 換成公克
    再除 1000:C × V / 1 000 000 g。
  3. 除以乾樣重,得到分率
    C × V / (1 000 000 × m),單位是 g/g,也就是 kg/kg —— 一個沒有單位的分率
  4. 要百分比就再乘 100
    C × V × 100 / (1 000 000 × m) = C × V / (10 000 × m)。這就是式 1。
陷阱:分率還是百分比?差 100 倍

SaltsR 套件裡有兩處寫法看起來很像,其實差 100 倍:
· fun_salt_wt()/(10000 × m),輸出百分比(例如 0.5 代表 0.5 %)
· fun_salt_balance() 內部用 C×(V/1000)/(m×1000),輸出分率(0.005 代表 0.5 %)
兩個都對,但你必須知道自己手上拿的是哪一個。 報告數字時永遠把單位寫出來,這是分析化學最基本的自我保護。

3-3 動手算

3-4 自己寫一個函式

你已經用了好幾次別人寫的函式,現在自己寫一個。 語法是 名字 <- function(引數) { 內容 }

👉 換你做

MH-05 的資料是:乾重 0.980 g、水量 100 mL、氯離子 66.824 ppm。 把上面的程式改成這組數字,算出氯離子的 wt%。
再想一想:如果實驗室助理把乾重打成 9.80 g(多了一個零), 算出來的鹽含量會變成原本的幾倍?這種錯誤在最終的判讀上會造成什麼後果?

UNIT 04

當量濃度 mEq/kg:電荷的語言(式 2)

重量百分比告訴你「有多少鹽」,但它沒辦法回答下一個問題: 正負電荷平不平衡?要談電荷,就得換一種單位。

4-1 為什麼莫耳不夠用

一個 Na⁺ 帶 +1 電荷,一個 Ca²⁺ 帶 +2。 所以 1 莫耳的 Ca²⁺ 在「電荷」這件事上,抵得過 2 莫耳的 Na⁺。 如果你只比較莫耳數,就會嚴重低估鈣和鎂的份量。

解法是用當量(equivalent, Eq):把莫耳數乘以電荷數 z。 這樣一來,1 Eq 的任何離子都帶同樣多的電荷,可以直接相加相減。 因為數值通常很小,實務上用毫當量/公斤乾樣(mEq/kg)。

式 2 · 每公斤乾樣的毫當量

ei = Cppm × Vwater mdry × (M / z)   [mEq/kg]

分母裡的 M/z當量重(equivalent weight): 分子量除以電荷數。例如 SO₄²⁻ 的 M = 96.064、z = 2,當量重就是 48.03 g/Eq。

離子符號分子量 M電荷 z當量重 M/z
氯離子Cl⁻35.4527135.45
硝酸根NO₃⁻62.0049162.00
硫酸根SO₄²⁻96.064248.03
鈉離子Na⁺22.9898122.99
鉀離子K⁺39.0983139.10
鈣離子Ca²⁺40.078220.04
鎂離子Mg²⁺24.305212.15
看出來了嗎

鎂的當量重只有 12.15,是所有離子中最小的。 也就是說同樣 1 ppm 的鎂,貢獻的電荷是同樣 1 ppm 氯的 2.9 倍。 這解釋了一個常見的困惑:明明鎂的 ppm 數字看起來很小, 為什麼在電荷平衡表上卻很有份量。

4-2 算給你看

你應該會看到:

離子ppmwt %mEq/kg觀察
Cl⁻500.50141.03陰離子中最多
NO₃⁻300.3048.38當量重大,換算後縮水
SO₄²⁻200.2041.64雙價,稍微補回來一些
Na⁺400.40173.99當量重最小的一價離子 → 份量最重
K⁺100.1025.58
Ca²⁺150.1574.85ppm 只有鈉的 3/8,當量卻有 43 %
Mg²⁺50.0541.14ppm 最小,當量卻和硫酸根相當
陰離子計1001.00231.06
陽離子計700.70315.56比陰離子多 36 %!
注意這個反轉

看 ppm:陰離子 100、陽離子 70 —— 陰離子比較多。
看 mEq/kg:陰離子 231、陽離子 316 —— 陽離子反而多出 36 %
同一份數據,換個單位,結論完全相反。 這就是為什麼電荷平衡一定要在當量的尺度上做,不能用 ppm 或 wt% 目測。

4-3 順便:含水率

SaltsR 還提供兩個秤重就能算的指標,常和鹽含量一起報告:

👉 換你做:哪一個離子「最會膨脹」?

把七個離子各設成 100 ppm(其他條件不變:乾重 1 g、水 100 mL), 算出各自的 mEq/kg,然後由大到小排序。
提示:sort(mEq, decreasing = TRUE)
你會發現順序是 Mg > Ca > Na > Cl > K > SO₄ > NO₃。 這個順序完全由當量重 M/z 決定 —— 當量重越小,同樣 ppm 換出來的當量越大。

UNIT 05

電荷平衡與兩條校正路徑(式 3–5)

這是整門課最重要的單元。上一單元我們算出 MH-01 的陽離子比陰離子多了 84.51 mEq/kg。現在的問題是:這 84.51 該怎麼處理?

5-1 為什麼會不平衡

一杯溶液必須電中性 —— 正電荷總數等於負電荷總數,這是物理定律,沒有例外。 所以量出來不平衡,只可能有兩個原因:

  1. 分析誤差。每個離子的測定都有不確定度,七個誤差疊起來就會有幾個百分點的落差。 這種誤差是隨機的,可能偏正也可能偏負,而且通常不大。
  2. 有離子沒被測到。標準的離子層析套組測七個離子, 但真實牆體裡還有別的 —— 最重要的是碳酸根 CO₃²⁻ 與碳酸氫根 HCO₃⁻。 石灰砂漿、水泥、石灰岩本身都會釋放碳酸根,但它在一般的 IC 條件下測不到。 這種缺漏是系統性的,而且只會讓陽離子看起來過剩
回答單元 0 的那道思考題

陽離子大幅過剩 → 幾乎確定是漏測了碳酸根,而不是儀器誤差。 因為儀器誤差不會有方向性,而且很少大到 20 %、30 %。 這個判斷正是接下來兩條路徑的分歧點。

5-2 式 3:算出 Δe,然後選路

式 3 · 初始電荷不平衡量

Δe = | Σe陽離子 − Σe陰離子 |

SaltsR 的判斷規則是:

條件路徑物理假設
Δe ≤ 2 % × max(Σ陰, Σ陽)
(不平衡量很小)
Pathway 1 差額歸因於分析誤差
誤差是隨機的,所以把七個離子全部按比例調到中點, 不特別怪罪任何一個離子。
或 Σ陰離子 > Σ陽離子
(陰離子過剩)
Δe > 2 % × Σ陽
 Σ陽離子 > Σ陰離子
Pathway 2 差額歸因於沒測到的碳酸根
把過剩的陽離子當成碳酸鹽移除, 並依碳酸鹽溶解度由小到大逐一扣除。
為什麼「陰離子過剩」也走 Pathway 1

因為漏測碳酸根只會造成陽離子過剩。如果反而是陰離子多, 那就沒有「漏測某個陰離子」這種現成的解釋,只能歸因於分析誤差 —— 所以走等比例調整。 這條規則是不對稱的,而且它的不對稱是有物理根據的,不是隨便定的。

5-3 式 4:Pathway 1 的等比例縮放

做法很直接:把陰離子總和與陽離子總和「各自往中間拉」,讓兩邊都等於平均值。

式 4

e陰,adj = e × Σ陰 + Σ陽 2 × Σ陰     e陽,adj = e × Σ陰 + Σ陽 2 × Σ陽

調整後兩邊都會等於 (Σ陰 + Σ陽)/2,而且每個離子彼此的相對比例完全不變 —— 這正是「不怪罪任何特定離子」的數學表現。

5-4 式 5a–5d:Pathway 2 的逐級扣除

Pathway 2 的邏輯完全不同。我們相信「多出來的陽離子是碳酸鹽」, 那就必須決定:是哪一個陽離子的碳酸鹽?

答案是:從最不溶的碳酸鹽開始扣。因為最難溶的鹽最可能以固體形式 穩定存在於牆體中而不被水帶走。碳酸鹽的溶解度順序是:

CaCO₃(方解石,極難溶) →  MgCO₃  →  Na₂CO₃  →  K₂CO₃(極易溶)

所以扣除順序就是 Ca²⁺ → Mg²⁺ → Na⁺ → K⁺

演算法是一個「水桶接力」:

  1. 拿 Δe 去扣 Ca²⁺。如果 Ca 夠扣,扣完就平衡了,結束。
  2. 如果 Ca 不夠扣(會扣成負數),就把 Ca 歸零, 算出「還差多少」,把剩下的差額交給 Mg²⁺。
  3. Mg 不夠就歸零,剩下的交給 Na⁺。
  4. Na 不夠就歸零,剩下的交給 K⁺。

每一級都用 max(目前值 − 待扣量, 0) 保證不會出現負濃度, 然後重新計算陰陽差額,把餘額往下傳。

執行結果會像這樣 —— 請仔細看第一行和第二行的關係:

離子扣除前扣掉扣除後還剩差額
5aCa²⁺74.85474.8540.0009.656
5bMg²⁺41.1449.65631.4880.000
5cNa⁺173.9900.000173.9900.000
5dK⁺25.5770.00025.5770.000
請務必注意這件事

MH-01 原本測到 15 ppm 的鈣,走完 Pathway 2 之後鈣變成 0。 這不是說牆裡沒有鈣 —— 而是模型假設「這些鈣全部以 CaCO₃ 的形式存在」, 而 CaCO₃ 難溶、不參與潮解結晶循環,所以在鹽害模擬中被排除。

這是一個假設,不是測量結果。如果實際上那些鈣是以 CaCl₂ 存在 (CaCl₂ 極易潮解,是很危險的鹽),這個假設就會嚴重低估風險。 所以在報告裡一定要寫明走的是哪條路徑、扣掉了多少 —— 這是可重現性的基本要求。

5-5 一次做完:fun_salt_balance()

上面那些步驟,套件都幫你封好了:

👉 換你做:讓路徑翻轉

把上面 MH-01 的 nitrate_ppm 從 30 慢慢往上調(試試 60、90、120…), 找出讓判定從 Pathway 2 翻成 Pathway 1 的臨界值。
翻轉之後,鈣還會被歸零嗎?為什麼?
做完之後強烈建議打開 實驗室 1,用滑桿把整個過程「玩」一遍。

UNIT 06

石膏扣除與 ECOS 輸入檔(式 6–11)

電荷平衡好了,但還不能直接餵給 Runsalt。還有最後一道手續:把石膏拿掉

6-1 為什麼要拿掉石膏

ECOS 熱力學模型有一個硬性限制:它最多只處理六種離子, 而且 SO₄²⁻ 與 Ca²⁺ 不能同時完整存在。[2] 原因有兩個:

式 6 · 石膏上限

elim,CaSO₄ = min( eSO₄,adj , eCa,adj )

為什麼是取最小值?因為 CaSO₄ 是 1:1 的化合物 —— 要形成 1 當量的石膏,需要 1 當量的 Ca 加 1 當量的 SO₄。 誰先用完,石膏就停在哪裡。這就是化學裡的限量試劑概念。

式 7 · 扣除

eSO₄,final = eSO₄,adjelim eCa,final = eCa,adjelim

兩邊各扣掉同樣的當量,所以電荷平衡不會被破壞 —— 扣完仍然是中性的。

6-2 三種典型結果

樣品路徑SO₄ adjCa adj石膏上限結果
MH-01Pathway 241.640.000.00 鈣早在式 5a 就被扣光 → 沒有石膏,硫酸根全部保留
MH-02Pathway 1111.03419.19111.03 硫酸根先用完 → SO₄ 歸零,還剩 308 mEq 的鈣
MH-08Pathway 23331.113262.363262.36 鈣先用完 → Ca 歸零,硫酸根剩 68.75;石膏高達 22.2 wt%

6-3 式 8:換成莫耳分率 —— ECOS 真正要的輸入

Runsalt 不吃 mEq/kg,它要的是莫耳分率(每個離子占總莫耳數的比例,加起來等於 1)。

式 8 · 兩步

ci = ei,final / zi / 1000 [mol/kg]      ② xi = ci / Σcj [無單位]

驗證一下

把上面產生的內容,跟課程附的 SaltsR_upload_Example.txt 比對 —— 每一位小數都會一模一樣。 那個檔案就是 SaltsR 官方範例產生的,而我們剛剛用同一組輸入自己重現了它。 這是驗證自己算對了最好的方式:拿已知答案的例子回頭檢查

6-4 式 11:石膏含量與飽和度警告

最後一個檢查,是問一個很實際的實驗問題: 你加的水,夠不夠把石膏全部溶掉?

20 °C 下石膏的溶解度約 2.14 g/L。如果樣品裡的石膏超過這個量, 有一部分根本沒進到萃取液,離子層析就測不到它, 你算出來的鈣和硫酸根會偏低 —— 而且你不會知道偏低了多少。

石膏飽和度

SCaSO₄ = 石膏含量(wt 分率) (0.214 × Vwater / 10000) / mdry × 100    S > 1 就要警告

飽和度警告的正確反應

看到警告時,不要在報告上寫「石膏 22.2 wt%」然後繼續。 正確的做法是回實驗室,用更多水(或更少樣品)重新萃取一次。 演算法能告訴你「這個數字不可信」,但它沒辦法幫你把沒溶掉的石膏變出來。

注意上面練習的結果:光是把水量從 100 mL 提高到 250 mL, 飽和度就從 1.04 降到 0.42 —— 但這只是計算上的變化。 真正的重測會得到不同的(更高的)硫酸根 ppm 讀數。

6-5 誠實面對:套件的一個欄位名稱容易誤讀

給想深入的同學

SaltsR 的輸出裡有一欄叫 total_ion_content。 從名字看,你會以為它是「總離子含量」。但檢查它的算式會發現:

total_wt_adj_gypsum = total_wt - total_wt_adj      # 被移除的量
total_ion_content   = total_wt_adj_gypsum - gypsum_content

以 MH-01 為例:原始總量 1.700 wt%、校正後 1.538 wt%、石膏 0。 total_ion_content 會給你 0.162 wt%, 但這面牆實際的可溶鹽含量是 1.538 wt%。 這個欄位算的其實是「被校正掉的差額」,不是總離子含量。

本課程的函式庫因此另外提供 $total_salt_content (= 校正後總量 + 石膏),並保留 $removed_wt 讓你看到被扣掉多少。
這件事的教訓比這個欄位本身更重要: 拿到任何分析軟體的輸出,都要問「這一欄的定義到底是什麼」, 而不是照著欄位名稱猜。開源軟體的好處就是 —— 你可以打開來看。

UNIT 07

讀進 Runsalt 的輸出檔

現在假設你已經把 ECOS 輸入檔丟進 Runsalt,跑完模擬, 用選單 Plot → Export Plot Data… 匯出結果。 那個檔案長這樣:

NaCl_X  15.0 16.66 18.32 19.98 ... 68.12 69.78
NaCl_Y  0.3314 0.3314 0.3314 ... 0.1268 0.03012

Na2SO4.MgSO4.4H2O_X  18.32 19.98 ... 66.46 68.12
Na2SO4.MgSO4.4H2O_Y  0.3433 0.3433 ... 0.2035 0.1302

KNO3_X  15.0 16.66 ... 59.82 61.48
KNO3_Y  0.3915 0.3915 ... 0.3155

7-1 解讀這個格式

這是「寬格式」,很難分析

每個鹽兩列、每列一長串數字 —— 這種格式人看得懂,但程式很難處理。 資料分析的第一步幾乎永遠是把它轉成長格式: 每一列代表「一個鹽、在一個濕度、有多少莫耳」。 一旦變成長格式,畫圖、篩選、分組統計全都變得很簡單。 這個轉換叫做 tidy,是資料科學的核心技能之一。

7-2 目標格式

SaltRHmolTempCrystallisation
NaCl15.000.331420NA
NaCl16.660.331420NA
NaCl69.780.03012069.78

Crystallisation 欄只在該鹽存在的最高濕度那一列有值, 其他都是 NA(缺值)。這個 RH 就是該鹽的結晶/潮解臨界點。

7-3 實際讀進來

正規表示式速查

sub("_[XY]$", "", x) 的意思是:把「字串結尾$) 的一個底線加上 X 或 Y」換成空字串。
[[:space:]]+ 代表「一個以上的空白字元」,所以不管中間隔幾個空格都能正確切開。
正規表示式不用一次學會,用到再查就好,但知道它存在很重要。

7-4 整個資料夾一次讀

真實研究裡,你可能為同一份樣品跑了 5 °C、10 °C、20 °C、30 °C 四個溫度, 或是一整批樣品各一個檔案。tidy_runsalt_folder() 會把它們全部讀進來、 黏成一張大表,並用 filename 欄記住每列來自哪個檔案。

# 在你自己的電腦上(RStudio)
all <- tidy_runsalt_folder("runsalt_outputs/", Temp_value = 20)
table(all$filename)          # 看每個檔案讀進幾列
👉 換你做:找出每個鹽的結晶 RH

用讀進來的 d,對每個鹽找出 RH 的最大值。
提示一:tapply(d$RH, d$Salt, max)
提示二:課程函式庫直接提供 crystallisation_table(d)
做完之後把結果由大到小排序 —— 下一個單元就要用它。

UNIT 08

畫圖,然後看懂它

七個鹽相、一百多列數字,光看表格沒有人能得出結論。畫成圖才看得出故事。

8-1 怎麼讀這張圖

鹽相礦物名結晶 RH (%)意義
NaCl岩鹽 halite69.78最先結晶 —— 最危險
Na₂SO₄·MgSO₄·4H₂O鈉鎂礬 blödite68.12緊接著
KNO₃硝石 niter61.48
NaNO₃鈉硝石 nitratine59.82
MgSO₄·4H₂Ostarkeyite58.16
MgSO₄·H₂Okieserite26.62只在很乾的環境出現
Na₂SO₄無水芒硝 thenardite16.66極乾才出現

8-2 三個關鍵解讀

① 混合鹽把臨界濕度拉低了

純 NaCl 在 20 °C 的潮解 RH 約 75 %。 但在這個混合系統裡,岩鹽的臨界點掉到 69.78 %。 這正是單元 0 提到的「混合鹽的潮解點低於任何單一組分」。[4]
實務後果:如果你按照「純鹽表」把室內濕度控制在 72 % 以為很安全, 實際上這面牆早就在 70 % 就開始結晶了。必須用混合系統的模擬結果,不能查單鹽表。

② 最危險的不是「最多」,是「最先」

在自然的日夜/季節濕度波動中,環境濕度會反覆掃過某個區間。 臨界 RH 落在這個波動區間內的鹽,才會反覆溶解再結晶, 每一次循環都在孔隙裡再擠一次。
這個樣品裡,NaCl(69.78 %)和鈉鎂礬(68.12 %)的臨界點正好落在 台灣室內常見的 60–80 % 波動帶裡 —— 它們是主要威脅。 而無水芒硝的 16.66 % 在台灣幾乎不會被跨越,所以雖然它是最惡名昭彰的破壞鹽, 在這個環境下反而不構成問題。風險 = 鹽的性質 × 當地氣候

③ 陡峭的地方最要小心

曲線越陡,代表「濕度只要變動一點點,就有大量的鹽在溶解或結晶」。 看 NaCl 的曲線:從 RH 59.8 到 69.8 這 10 個百分點之間, 莫耳量從 0.29 直落到 0.03。這 10 % 的濕度區間就是這面牆的高風險帶

8-3 把圖翻成一句建議

分析的終點不是一張圖,是一句古蹟管理者看得懂的話。以這個樣品為例:

診斷建議(範例寫法)

本樣品主要風險鹽為岩鹽(NaCl)與鈉鎂礬, 模擬結晶臨界濕度分別為 69.8 % 與 68.1 %(20 °C)。
建議將室內相對濕度穩定控制在 55 % 以下或 78 % 以上, 並避免在 60–72 % 區間反覆進出
相較於絕對濕度值,濕度的穩定性更為關鍵 —— 長期維持在 65 % 不動,會比在 55 %–75 % 之間每天來回,破壞小得多。
(若採低濕方案,須留意木構件與彩繪層的乾裂風險, 實務上常需在不同材質的需求之間取捨。)

8-4 在 RStudio 用 ggplot2 畫

網頁上我們用 base R 畫圖(快、不用裝套件)。 在你自己的電腦上,SaltsR 提供 ggplot2 版本,出圖品質更適合投稿:

# 在 RStudio 執行(需先安裝真正的 SaltsR)
library(SaltsR)
library(ggplot2)

graph_salt_balance("SaltsRExample20C.txt",
                   Temp_value   = 20,
                   add_crystal  = TRUE,    # 標出結晶點
                   add_eqm      = FALSE)   # 標出平衡點

# 自己用 ggplot2 畫,可以完全控制樣式
d <- tidyRunsalt("SaltsRExample20C.txt")
ggplot(d, aes(RH, mol, colour = Salt)) +
  geom_line(linewidth = 1.4, alpha = .85) +
  labs(x = "相對濕度 (%)", y = "物質的量 (mol)",
       title = "ECOS 模型輸出", subtitle = "20 °C") +
  theme_classic(base_size = 14)
Godts 等人的建議:改用「體積」而不是「莫耳」

同樣 1 莫耳的不同鹽,佔的體積差很多。 要評估「孔隙會不會被塞爆」,用體積(cm³)比用莫耳更貼近物理現實。[2] Runsalt 可以切換輸出單位;做正式研究時建議兩種都看。

UNIT 09

批次處理與綜合實作

到目前為止我們一次只處理一個樣品。真實計畫裡一面牆可能取 20 個點。 這個單元教你用迴圈一次跑完,然後產出可以交出去的報告。

9-1 for 迴圈

9-2 用 lapply 寫得更簡潔

for 迴圈很好懂,但 R 更習慣的寫法是 lapply: 「把這個函式套用到清單裡的每一個元素」。

9-3 視覺化整批結果

9-4 匯出結果

9-5 兩種常見的混合型態

Godts 等人分析了歐洲 338 個場址、11 412 個樣品,發現真實牆體的鹽混合 絕大多數落在兩種型態之一。[3] 知道自己手上是哪一型,就能快速預判風險等級:

型態主導離子(由多到少)吸濕性意義
Type 1
硫酸鹽型
SO₄2− > Na⁺ > K⁺ > NO₃ > Cl > Mg2+ 較低 臨界 RH 偏高,一年中被跨越的次數較少;但含硫酸鈉時要留意水合膨脹。 在完整資料集中約占三分之二,且傾向分布到較深處。
Type 2
鈣型
NO₃ > Ca2+ > Cl > Na⁺ > K⁺ > Mg2+ 明顯較高 硝酸鈣、氯化鈣極易潮解,臨界 RH 低,牆面容易長期保持潮濕。 一般認為破壞潛勢較高。
回頭看我們的資料

MH-02 的莫耳分率是 NO₃ 0.323 > Na⁺ 0.248 > Cl 0.242 > Ca2+ 0.083 —— 硝酸根主導、且鈣沒有被扣光(走 Pathway 1),整體比較接近 Type 2 的輪廓。
但請注意一個重要的方法學陷阱:樣品走 Pathway 2 時鈣常常被歸零, 這會讓它在事後看起來「不像鈣型」。 所以判斷混合型態時,要看的是校正前的離子組成, 而不是餵給 ECOS 的那組數字。

9-5 綜合實作:寫一份牆體診斷

期末練習

salt_samples.csv 的八個樣品,寫一份 A4 一頁的診斷報告,必須包含:

  1. 資料品質檢查:哪些樣品的數據可信?哪些需要重測?理由是什麼?
  2. 空間分布:北牆三個高度(MH-01/02/03)的鹽含量趨勢說明了什麼? 這符合毛細上升的預期嗎?
  3. 路徑分布:八個樣品各走哪條路徑?走 Pathway 2 的樣品, 被歸類為碳酸鹽的陽離子占多少?這個假設合理嗎(提示:這是石灰砂漿的老建築)?
  4. 主要威脅:從離子組成判斷,哪些樣品的硝酸鹽比例特別高? 這暗示什麼歷史使用?
  5. 處置建議:至少一項濕度控制建議與一項後續調查建議。

提示:MH-02 的硝酸根莫耳分率高達 0.323,是八個樣品之最。 硝酸鹽的來源通常是有機物分解或動物活動 —— 這棟建築的這個位置以前可能是什麼用途?

參考解答骨架(先自己寫過再看)
  • 資料品質:MH-08 飽和度 1.038 > 1,石膏未完全溶解, 鈣與硫酸根被低估,必須提高稀釋倍率重測。其餘七個樣品飽和度均 < 0.05,數據可用。
  • 空間分布:北牆 0.3 m(1.70 wt%)→ 1.0 m(8.38 wt%)→ 1.8 m(1.24 wt%)。 不是單調遞減,而是在 1.0 m 出現峰值。這其實正符合毛細上升的典型模式 —— 鹽會在蒸發前緣累積,也就是水分上升到達的最高處附近,而非最底部。
  • 路徑:MH-02、MH-04 走 Pathway 1(前者因 Δe 僅 0.52 % 落在 2 % 容忍內, 後者因陰離子過剩);其餘六個走 Pathway 2。石灰砂漿建築中碳酸根假設相當合理。
  • 主要威脅:MH-02 的 NO₃⁻ 莫耳分率 0.323、Cl⁻ 0.242, 是硝酸鹽主導的系統,暗示長期的有機物/動物活動(如舊時的畜舍、廁所或鴿害)。 硝酸鹽極易潮解,且 KNO₃/NaNO₃ 的臨界 RH 落在 60 % 附近,風險高。
  • 建議:(a) MH-08 重新萃取;(b) 針對 MH-02 位置追查歷史用途與現行水源, 並將該區室內 RH 穩定控制於 55 % 以下;(c) 建議加做各樣品的 Runsalt 模擬以取得個別臨界 RH。
附錄 A

疑難排解

症狀原因處理方式
網頁上按「執行」一直轉圈第一次要下載約 30 MB 的 R 環境 等 20–40 秒。網路慢時可能到 2 分鐘。之後會被瀏覽器快取,就很快了。
右下角顯示「R 環境載入失敗」網路被擋,或瀏覽器太舊 用較新版的 Chrome / Edge / Firefox / Safari;若在校園網路,可能需要開放 webr.r-wasm.org
object 'DATA' not foundR 環境還沒初始化完 等右下角出現「R 環境就緒 ✓」再執行。
could not find function "fun_salt_balance"課程函式庫沒載入成功 重新整理頁面。
算出來的 wt% 大 100 倍混用了「分率」與「百分比」兩種公式 回單元 3-2 檢查你用的是 /(10000×m) 還是 /(1000000×m)
陰陽離子看起來差很多,卻判成 Pathway 1陰離子過剩 這是正確行為。Pathway 1 的條件是「Δe ≤ 2 % 陰離子過剩」,見單元 5-2。
某個離子被算成 0,覺得不合理Pathway 2 把它當碳酸鹽扣光了 這是模型假設,不是錯誤。要在報告中明確說明,見單元 5-4 的警告。
RStudio 裡 pak::pak() 安裝失敗缺少編譯工具或相依套件 Windows 需先裝 Rtools;或改用 remotes::install_github("BhavShah01/SaltsR")
中文在圖上變成方框系統缺中文字型設定 R 裡執行 par(family = "Noto Sans CJK TC"),或改用英文標籤。
附錄 B

詞彙對照

中文英文說明
可溶鹽soluble salt能被水溶解、參與潮解結晶循環的鹽;鹽害的主角
毛細上升rising damp地下水沿毛細孔上升,把鹽帶進牆體
潮解deliquescence鹽從空氣中吸水自發溶解
潮解相對濕度DRH / RHdel發生潮解的臨界濕度
結晶相對濕度RHcry濕度下降時開始結晶的臨界值
離子層析ion chromatography (IC)定量陰陽離子的分析方法
當量equivalent (Eq)莫耳數 × 電荷數;比較電荷的通用單位
電荷平衡charge balance正負電荷總量相等的要求
莫耳分率mole fraction某離子莫耳數 ÷ 總莫耳數;ECOS 的輸入格式
石膏gypsum, CaSO₄·2H₂O難溶硫酸鈣;模擬前要扣除
限量試劑limiting reagent反應中先耗盡的那個成分,決定產物上限
吸濕含水率HMC20 °C/95 %RH 平衡後的吸水量;鹽含量的快速指標
向量化vectorisation對整排資料一次運算,R 的核心特性
長格式/寬格式long / wide format資料排列方式;分析前通常要轉成長格式
附錄 C

檔案下載

所有檔案都可以下載後在 RStudio 使用。

資料

八個樣品的離子層析數據

MH-01 到 MH-08,含取樣位置、乾重、水量、七個離子的 ppm。

資料

Runsalt 模擬輸出範例

MH-01 在 20 °C 的 ECOS 模擬結果,七個鹽相。單元 07–08 使用。

資料

ECOS 輸入檔範例

MH-01 校正後的莫耳分率,可直接匯入 Runsalt。

對照表

八個樣品的完整計算結果

每個樣品的 130 個中間欄位(式 1 到式 11 的每一步),由原始 SaltsR 套件產生。 想自己驗算時可以逐欄比對。

對照表

ECOS 鹽相與礦物名對照

75 個 ECOS 鹽相的化學式與礦物學名稱。

程式

課堂版函式庫

本課程使用的純 base R 版 SaltsR 核心函式,含完整註解。

程式

各單元練習腳本

單元 1–9 的所有練習程式碼,可在 RStudio 逐行執行。

安裝

在自己電腦裝真正的 SaltsR

課程結束後的下一步。需要先安裝 R 與 RStudio。

附錄 D

參考文獻與資料來源

本課程使用的方法出處

  1. Godts, S.; Steiger, M.; Orr, S. A.; et al. (2022). Charge balance calculations for mixed salt systems applied to a large dataset from the built environment. Scientific Data, 9(1), 324. doi:10.1038/s41597-022-01445-9
    — 式 1 到式 11 的原始出處,SaltsR 全部依此實作。
  2. Godts, S.; Steiger, M.; Orr, S. A.; et al. (2022). Modeling salt behavior with ECOS/RUNSALT: Terminology, methodology, limitations, and solutions. Heritage, 5(4), 3648–3663. doi:10.3390/heritage5040190
    — ECOS 只收六種離子、不處理碳酸鹽與石膏、不考慮動力學與孔隙結構、 不含結冰,以及「建議用體積而非莫耳判讀」等限制,均出自本文。
  3. Godts, S.; Orr, S. A.; Steiger, M. (2023). Salt mixtures in stone weathering. Scientific Reports, 13(1). doi:10.1038/s41598-023-40590-y
    — 從 11412 個真實樣品歸納出兩種常見混合型態, 以及 14 種最常出現的鹽相與其臨界 RH。單元 09 的分型判讀依據。
  4. Rörig-Dalgaard, I. (2021). Direct measurements of the deliquescence relative humidity in salt mixtures including the contribution from metastable phases. ACS Omega, 6(25), 16297–16306. doi:10.1021/acsomega.1c00538
    — 「混合鹽的潮解點永遠低於任一單組分」的實驗證據。
  5. Price, C. A. (ed.) (2000). An Expert Chemical Model for Determining the Environmental Conditions Needed to Prevent Salt Damage in Porous Materials (ECOS). European Commission Research Report No. 11, Archetype Publications, London.
  6. Bionda, D. (2005). RUNSALT — A graphical user interface to the ECOS thermodynamic model for the prediction of the behaviour of salt mixtures under changing climate conditions. science.sdf-eu.org/runsalt
  7. Shah, B.; Godts, S.; Orr, S. A. SaltsR: Tools for the analysis of salt formation data (R package, GPL-3). github.com/BhavShah01/SaltsR

資料來源說明

哪些是真實資料、哪些是教學用資料
  • SaltsRExample20C.txtSaltsR_upload_Example.txt —— 取自 SaltsR 套件的 data-raw/,是套件作者提供的官方範例,未經修改。
  • salt_samples.csv 的八個樣品 —— 為本課程設計的教學資料,不是真實古蹟的測量值。 設計原則是讓八個樣品剛好涵蓋所有教學情境:Pathway 1 的兩種觸發條件、 Pathway 2 的兩種扣除深度、無石膏/石膏受限於硫酸根/石膏受限於鈣、 以及一個會觸發飽和度警告的樣品。
    其中 MH-01 的組成刻意設為與套件官方範例完全相同, 因此它的 ECOS 輸出可以直接對照 SaltsR_upload_Example.txt 驗證。 這些數字不可引用為實驗證據。
  • 課堂版 R 函式 —— 改寫自 SaltsR 原始碼(GPL-3), 以 268 組資料(68 組指定案例 + 200 組隨機案例)逐項與原套件比對, 所有欄位的相對誤差 < 10⁻¹³。網頁互動實驗室使用的 JavaScript 版本 亦以相同方式驗證,最大相對誤差為 0。