4. 环氧增韧-黏度DOE的随机化与区组划分方案
为什么这一例必须先解决“顺序”问题
24 run 的响应曲面设计表只规定了“做哪些配方”,没有规定“按什么顺序做”。如果按因子水平从低到高依次操作,炉温、环境湿度、基料静置时间、操作员手感都会随试验进程单向漂移,漂移量会被误读成因子效应。本例要交付的是一份可执行的随机化顺序 + 区组划分方案,把两个已知噪声源——同一批次基料的分装/静置时间与不同操作员——从处理效应的估计中剥离出去。
噪声源识别与区组策略
先明确两个噪声源的性质,因为它们决定区组怎么划:
- 基料批次内的时间漂移:同一桶环氧基料开封后,随静置时间延长黏度缓慢上升、固化放热行为略有变化。这是连续型噪声,无法用“批”来区组,只能用随机化 + 时间分段来打散,并记录每 run 的开封后累计时间作为协变量。
- 操作员:不同人配胶搅拌速度、脱泡时间、浇注手法不同,会同时影响冲击强度与黏度。这是离散型噪声,适合做区组因子,让每位操作员承担完整或近似完整的设计点集合。
因此采用双区组结构:以操作员为主区组(2 名操作员 × 12 run),在每个操作员内部再按“试验日”切分为 3 个时间块(每块 4 run),块内完全随机,块间顺序也随机。这样操作员效应被区组吸收,日内漂移被块吸收,剩余漂移由块内随机化打散。
区组划分的硬约束:任一区组内必须尽量包含全部因子水平的均衡组合,否则区组效应会与某个因子的主效应部分混杂(partial confounding)。本例 24 run 设计含 6 个中心点,中心点应均匀分布到各操作员与各时间块,用于独立估计纯误差与漂移曲率。
随机化与区组划分方案(可视化)
随机化顺序的生成与落地
方案不能靠手工排,否则容易把同一因子水平排在一起。用一段可运行的 Python 脚本生成并校验:先按操作员分层,再在每层内按时间块分层,块内用固定种子打乱,最后检查每个块内因子水平的均衡性与中心点分布。
import random
from collections import Counter
# 24 run 设计点:因子为增韧剂含量 T、稀释剂含量 D,各取低/中/高
# 简化表示:设计表已由前一步生成,这里只做区组与随机化
design = [
("run01","低","中"),("run02","低","低"),("run03","中","中"),("run04","低","高"),
("run05","低","低"),("run06","低","中"),("run07","低","高"),("run08","C","C"),
("run09","C","C"),("run10","中","低"),("run11","中","高"),("run12","C","C"),
("run13","中","低"),("run14","中","高"),("run15","C","C"),("run16","高","低"),
("run17","高","中"),("run18","高","高"),("run19","高","低"),("run20","C","C"),
("run21","高","高"),("run22","中","中"),("run23","C","C"),("run24","高","中"),
]
random.seed(20240604) # 固定种子,保证方案可复现
operators = ["甲", "乙"]
blocks_per_op = 3
runs_per_block = 4
# 1) 中心点单独抽出,均匀分配
centers = [r for r in design if r[1] == "C"]
others = [r for r in design if r[1] != "C"]
random.shuffle(centers)
random.shuffle(others)
# 2) 非中心点按操作员分层,再按块分层
plan = []
idx = 0
for op in operators:
for b in range(blocks_per_op):
block = others[idx: idx + runs_per_block - 1] # 每块留 1 个位置给中心点
idx += runs_per_block - 1
block.append(centers.pop()) # 每块插入 1 个中心点
random.shuffle(block) # 块内随机
for order, r in enumerate(block, 1):
plan.append({"run": r[0], "T": r[1], "D": r[2],
"operator": op, "block": f"{op}{b+1}", "order": order})
# 3) 块间顺序随机(同一操作员内)
for op in operators:
blk_ids = [f"{op}{i}" for i in range(1, blocks_per_op + 1)]
random.shuffle(blk_ids)
for new_pos, bid in enumerate(blk_ids, 1):
for row in plan:
if row["block"] == bid:
row["block_seq"] = new_pos
# 4) 校验:每块 4 run、每块恰 1 个中心点、每操作员 12 run
for op in operators:
for b in range(1, blocks_per_op + 1):
bid = f"{op}{b}"
rows = [r for r in plan if r["block"] == bid]
assert len(rows) == 4, bid
assert sum(1 for r in rows if r["T"] == "C") == 1, bid
assert sum(1 for r in plan if r["operator"] == op) == 12
for row in sorted(plan, key=lambda x: (x["operator"], x["block_seq"], x["order"])):
print(row)
运行后得到 24 行执行清单,每行含 run 号、操作员、时间块、块内顺序与块间顺序。脚本中的 assert 是硬校验:任何一块不是 4 run、或某块没有中心点,都会立即报错,避免把有缺陷的方案带到实验台上。
执行时的漂移控制与记录
- 基料分装:同一批次基料一次性分装成 24 份等量小样并编号,避免“边做边取”引入开封时间差异;每份记录分装时刻。
- 累计时间协变量:每 run 记录从分装到浇注完成的累计时间 t(分钟),分析时作为协变量,若 t 的系数显著则说明漂移存在,需在模型中保留。
- 块内不中断:一个时间块内的 4 run 连续完成,中途不插入其他操作员或隔夜,否则块内漂移不再同质。
- 环境记录:每块记录室温与相对湿度,作为潜在协变量备查。
常见错误:为了“省事”把 6 个中心点全排在最后一天做。这样中心点只反映末期状态,纯误差估计会混入时间漂移,导致后续 ANOVA 的误差项偏大或偏小。本例强制每块 1 个中心点,正是为了把纯误差与漂移分开。
方案验收清单
| 检查项 | 通过条件 |
|---|---|
| 主区组划分 | 2 名操作员各 12 run,各自近似覆盖全部因子水平组合 |
| 子区组划分 | 每操作员 3 个时间块,每块 4 run,块内连续完成 |
| 中心点分布 | 6 个中心点,每块恰 1 个 |
| 随机化 | 块内顺序随机、块间顺序随机,种子已记录可复现 |
| 协变量记录 | 每 run 有基料累计时间 t、室温、湿度 |
| 异常处理 | 块内异常整块标记补做,不单点替换 |
满足以上六项,这份随机化与区组方案即可交付实验台执行;后续分析时把操作员、时间块作为区组项、t 作为协变量纳入模型,双响应(冲击强度、黏度)的因子效应才不会被时间漂移与操作员差异污染。