單元目錄
鹽為什麼會把牆吃掉
R 的第一行
向量、資料框與管線
從 ppm 到重量百分比
當量濃度 mEq/kg
電荷平衡與兩條路徑
石膏扣除與 ECOS 輸入
讀進 Runsalt 輸出檔
畫圖,然後看懂它
批次處理與綜合實作
附錄:疑難排解 ·
詞彙對照 ·
檔案下載 ·
參考文獻
UNIT 00
鹽為什麼會把牆吃掉
走進任何一座老磚造建築,你大概都看過這種景象:離地面半公尺以下的牆面,
表層剝落、磚粉簌簌掉、有時還泛著一層白霜。
那層白霜是可溶鹽 ,而剝落是它造成的。
鹽從哪裡來
地下水沿著磚石的毛細孔往上爬(稱為毛細上升 ,rising damp),
沿途溶解土壤和建材裡的鹽類帶上來。水在牆面蒸發,鹽留下。
幾十年下來,牆體下段就累積了高濃度的鹽。常見來源還有:
硝酸鹽 (NO₃⁻)—— 動物排泄、有機物分解、農地施肥,是「人為活動史」的化學指紋
氯化物 (Cl⁻)—— 海鹽氣膠、道路除冰鹽、早期不當的清潔劑
硫酸鹽 (SO₄²⁻)—— 空氣汙染 SO₂ 沉降後氧化、水泥砂漿、石膏灰泥
鈉、鉀、鈣、鎂 (Na⁺, K⁺, Ca²⁺, Mg²⁺)—— 上面那些陰離子的搭檔,來自建材本身與地下水
三種破壞機制
機制 發生時機 破壞方式
結晶壓 濕度下降、溶液過飽和
鹽晶體在孔隙裡長大,對孔壁施加壓力。過飽和度高時可達數十 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 好過 x;water_ml 好過 w。
不能以數字開頭,不能有空格。多字之間用底線,例如 chloride_ppm。
R 區分大小寫 :Dry_g 和 dry_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 之後內建的管線。你在網路上還會看到 %>%,
那是 magrittr/dplyr 套件提供的版本,功能幾乎一樣。
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 單位是怎麼消掉的
一步一步跟著單位走,你就不會再背錯公式:
萃取液裡有多少鹽?
濃度 C(mg/L)× 體積 V(L)= 質量(mg)。
但我們的 V 是毫升,所以要先除 1000:C × V/1000 mg。
換成公克
再除 1000:C × V / 1 000 000 g。
除以乾樣重,得到分率
C × V / (1 000 000 × m),單位是 g/g,也就是 kg/kg —— 一個沒有單位的分率 。
要百分比就再乘 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.4527 1 35.45
硝酸根 NO₃⁻ 62.0049 1 62.00
硫酸根 SO₄²⁻ 96.064 2 48.03
鈉離子 Na⁺ 22.9898 1 22.99
鉀離子 K⁺ 39.0983 1 39.10
鈣離子 Ca²⁺ 40.078 2 20.04
鎂離子 Mg²⁺ 24.305 2 12.15
看出來了嗎
鎂的當量重只有 12.15,是所有離子中最小的。
也就是說同樣 1 ppm 的鎂,貢獻的電荷是同樣 1 ppm 氯的 2.9 倍 。
這解釋了一個常見的困惑:明明鎂的 ppm 數字看起來很小,
為什麼在電荷平衡表上卻很有份量。
4-2 算給你看
你應該會看到:
離子 ppm wt % mEq/kg 觀察
Cl⁻ 50 0.50 141.03 陰離子中最多
NO₃⁻ 30 0.30 48.38 當量重大,換算後縮水
SO₄²⁻ 20 0.20 41.64 雙價,稍微補回來一些
Na⁺ 40 0.40 173.99 當量重最小的一價離子 → 份量最重
K⁺ 10 0.10 25.58
Ca²⁺ 15 0.15 74.85 ppm 只有鈉的 3/8,當量卻有 43 %
Mg²⁺ 5 0.05 41.14 ppm 最小,當量卻和硫酸根相當
陰離子計 100 1.00 231.06
陽離子計 70 0.70 315.56 比陰離子多 36 %!
注意這個反轉
看 ppm:陰離子 100、陽離子 70 —— 陰離子比較多。
看 mEq/kg:陰離子 231、陽離子 316 —— 陽離子反而多出 36 % 。
同一份數據,換個單位,結論完全相反。
這就是為什麼電荷平衡一定 要在當量的尺度上做,不能用 ppm 或 wt% 目測。
4-3 順便:含水率
SaltsR 還提供兩個秤重就能算的指標,常和鹽含量一起報告:
AMC(實際含水率) :樣品剛取下來時的含水量,反映當下的潮濕狀態。
HMC(吸濕含水率) :把乾樣放進 20 °C / 95 %RH 平衡後吸了多少水。
HMC 高就代表鹽多 —— 因為吸水的主要是鹽而不是材料本身,
所以 HMC 可以當成快速的鹽害篩檢指標。
👉 換你做:哪一個離子「最會膨脹」?
把七個離子各設成 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 為什麼會不平衡
一杯溶液必須電中性 —— 正電荷總數等於負電荷總數,這是物理定律,沒有例外。
所以量出來不平衡,只可能有兩個原因:
分析誤差。 每個離子的測定都有不確定度,七個誤差疊起來就會有幾個百分點的落差。
這種誤差是隨機 的,可能偏正也可能偏負,而且通常不大。
有離子沒被測到。 標準的離子層析套組測七個離子,
但真實牆體裡還有別的 —— 最重要的是碳酸根 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⁺
演算法是一個「水桶接力」:
拿 Δe 去扣 Ca²⁺。如果 Ca 夠扣 ,扣完就平衡了,結束。
如果 Ca 不夠扣 (會扣成負數),就把 Ca 歸零,
算出「還差多少」,把剩下的差額交給 Mg²⁺。
Mg 不夠就歸零,剩下的交給 Na⁺。
Na 不夠就歸零,剩下的交給 K⁺。
每一級都用 max(目前值 − 待扣量, 0) 保證不會出現負濃度,
然後重新計算陰陽差額,把餘額往下傳。
執行結果會像這樣 —— 請仔細看第一行和第二行的關係:
級 離子 扣除前 扣掉 扣除後 還剩差額
5a Ca²⁺ 74.854 74.854 0.000 9.656
5b Mg²⁺ 41.144 9.656 31.488 0.000
5c Na⁺ 173.990 0.000 173.990 0.000
5d K⁺ 25.577 0.000 25.577 0.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]
原因有兩個:
物理上 :CaSO₄·2H₂O(石膏)在水中的溶解度極低
—— 20 °C 約 2.14 g/L。它幾乎不會潮解,也不會參與濕度循環,
在鹽害模擬中是「惰性的」。
數值上 :把一個幾乎不溶的鹽留在系統裡,
會讓模型花力氣算一個不重要的相,還可能造成收斂問題。
式 6 · 石膏上限
e lim,CaSO₄ = min( e SO₄,adj , e Ca,adj )
為什麼是取最小值 ?因為 CaSO₄ 是 1:1 的化合物 ——
要形成 1 當量的石膏,需要 1 當量的 Ca 加 1 當量的 SO₄。
誰先用完,石膏就停在哪裡。這就是化學裡的限量試劑 概念。
式 7 · 扣除
e SO₄,final = e SO₄,adj − e lim
e Ca,final = e Ca,adj − e lim
兩邊各扣掉同樣的當量,所以電荷平衡不會被破壞 —— 扣完仍然是中性的。
6-2 三種典型結果
樣品 路徑 SO₄ adj Ca adj 石膏上限 結果
MH-01 Pathway 2 41.64 0.00 0.00
鈣早在式 5a 就被扣光 → 沒有石膏 ,硫酸根全部保留
MH-02 Pathway 1 111.03 419.19 111.03
硫酸根先用完 → SO₄ 歸零 ,還剩 308 mEq 的鈣
MH-08 Pathway 2 3331.11 3262.36 3262.36
鈣先用完 → Ca 歸零,硫酸根剩 68.75;石膏高達 22.2 wt%
6-3 式 8:換成莫耳分率 —— ECOS 真正要的輸入
Runsalt 不吃 mEq/kg,它要的是莫耳分率 (每個離子占總莫耳數的比例,加起來等於 1)。
式 8 · 兩步
① ci = e i,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 解讀這個格式
每個鹽相佔兩列 :鹽名_X 是相對濕度,鹽名_Y 是該鹽的莫耳量。
不同鹽的 X 範圍不一樣長 —— 因為每個鹽只在自己的穩定濕度區間裡存在。
鹽名用礦物學/化學式表示:NaCl(岩鹽 halite)、
KNO3(硝石 niter)、Na2SO4.MgSO4.4H2O(鈉鎂礬 blödite)等。
課程資料夾附了
ECOS_phases.csv 供對照。
這是「寬格式」,很難分析
每個鹽兩列、每列一長串數字 —— 這種格式人看得懂,但程式很難處理。
資料分析的第一步幾乎永遠是把它轉成長格式 :
每一列代表「一個鹽、在一個濕度、有多少莫耳」。
一旦變成長格式,畫圖、篩選、分組統計全都變得很簡單。
這個轉換叫做 tidy ,是資料科學的核心技能之一。
7-2 目標格式
Salt RH mol Temp Crystallisation
NaCl 15.00 0.3314 20 NA
NaCl 16.66 0.3314 20 NA
… … … … …
NaCl 69.78 0.0301 20 69.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 怎麼讀這張圖
X 軸 是相對濕度,從低(乾)到高(濕)。
Y 軸 是該鹽以固體形式存在 的莫耳量。
曲線水平的一段 = 這個濕度區間裡,該鹽全部是固體,量不變。
曲線往右下掉 = 濕度升高,鹽開始溶解。
曲線結束的那一點 (最右端)= 全部溶解完的濕度,
也就是結晶/潮解臨界 RH 。從高濕往低濕走時,就是在這裡開始結晶。
鹽相 礦物名 結晶 RH (%) 意義
NaCl 岩鹽 halite 69.78 最先結晶 —— 最危險
Na₂SO₄·MgSO₄·4H₂O 鈉鎂礬 blödite 68.12 緊接著
KNO₃ 硝石 niter 61.48
NaNO₃ 鈉硝石 nitratine 59.82
MgSO₄·4H₂O starkeyite 58.16
MgSO₄·H₂O kieserite 26.62 只在很乾的環境出現
Na₂SO₄ 無水芒硝 thenardite 16.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 一頁的診斷報告,必須包含:
資料品質檢查 :哪些樣品的數據可信?哪些需要重測?理由是什麼?
空間分布 :北牆三個高度(MH-01/02/03)的鹽含量趨勢說明了什麼?
這符合毛細上升的預期嗎?
路徑分布 :八個樣品各走哪條路徑?走 Pathway 2 的樣品,
被歸類為碳酸鹽的陽離子占多少?這個假設合理嗎(提示:這是石灰砂漿的老建築)?
主要威脅 :從離子組成判斷,哪些樣品的硝酸鹽比例特別高?
這暗示什麼歷史使用?
處置建議 :至少一項濕度控制建議與一項後續調查建議。
提示: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 反應中先耗盡的那個成分,決定產物上限
吸濕含水率 HMC 20 °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
參考文獻與資料來源
本課程使用的方法出處
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 全部依此實作。
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 只收六種離子、不處理碳酸鹽與石膏、不考慮動力學與孔隙結構、
不含結冰,以及「建議用體積而非莫耳判讀」等限制,均出自本文。
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 的分型判讀依據。
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
— 「混合鹽的潮解點永遠低於任一單組分」的實驗證據。
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.
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
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.txt 與 SaltsR_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。