☰
正交试验设计全流程:选表、表头设计、极差方差分析与脚本避坑
2026/10/26 2:07:57 网站建设 项目流程

简介:正交设计助手破解版面向需要开展正交试验的科研人员、工程技术人员与高校师生,针对混合水平试验中表头设计繁琐、人工计算易错、结果分析门槛高等问题,提供可直接使用的正交试验设计与分析工具。压缩包共10个文件,约1.22MB,内含可执行程序与动态链接库以支撑软件运行,许可、XML配置和CHM帮助文档用于功能授权、参数配置与使用查阅,TXT说明则便于快速了解安装与使用注意事项。已有2408人学习下载,说明该工具在多因素试验场景中具有一定关注度。读者可获得混合正交实验表的生成与分析支持,减少手工排表与数据整理时间,适合课程实验、工艺优化和科研预试验等场景参考使用。

1. 从 81 组试到 9 组:正交设计助手这类工具替你干了什么

某高校实验室做一次工艺参数摸底,4 个因子、每个 3 个水平,全组合 81 组,材料和时间都不允许;换成 L9(3^4) 正交表,9 组试验就能把主效应的先后排出来。这就是正交试验最朴素的诱惑,也是正交设计助手这类桌面小工具存在的理由——它把三件事打包了:按选中的表型生成试验方案、把录入的响应值算成极差和方差分析表、一键导出成别人能照着做的表。适合手里有试验台但没有统计软件授权的人,也适合要把方案交给产线执行的人。不过它只是算得快,选哪张表、因子塞进哪一列、误差从哪来,这些账还得你自己先算清;网上那些改名换壳的安装包,后面单独说。

2. 选表这一关:自由度算不平,后面全是白忙

正交设计助手打开后第一屏通常就是让你选表型,下拉框里几十张表,很多人的第一反应是「行数越少越好」。这是最容易翻车的地方。正交表不是随便挑一张能塞下因子数的,它得先满足自由度约束,再满足交互作用不混杂,最后才是行数经济性。这一章把这三件事拆开讲,顺手给一套能在纸上或者脚本里五分钟算完的口径。

2.1 自由度先算,表才选得对

自由度是这个流程里唯一不能靠感觉的量。总自由度等于试验次数减一;一个因子的自由度等于它的水平数减一;两个因子交互作用的自由度等于两者自由度之积。选表要同时满足两条:表的列数不小于「主因子数 + 需要考察的交互列数 + 至少一列留作误差」,表的行数不小于「所有需要考察项的自由度之和 + 1」。

拿 3 个 2 水平因子加 1 个交互作用举例:3×(2−1)=3,交互 1×1=1,合计 4,加 1 得 5,所以至少要 5 行,L8(2^7) 是合适的,还剩 3 列富余,可以留空白列做误差。再看 4 个 3 水平因子:4×(3−1)=8,加 1 得 9,正好卡在 L9(3^4) 上——注意,这时候 4 列全被主因子占满,一列空白都不剩,交互作用根本安排不进去,误差只能靠重复试验获得。很多人做到方差分析那一步才发现没有误差项,根子就在这里。

def need_runs(n_factors, n_levels, interactions=0, extra_error_col=True): """估算正交试验所需的最小行数与列数 n_factors : 主因子个数 n_levels : 每个因子的水平数(各因子水平数相同的场合) interactions : 需要考察的交互作用个数,2 水平时每个占 1 个自由度 extra_error_col : 是否额外留一列空白列做误差 """ df_factor = n_factors * (n_levels - 1) df_inter = interactions * (n_levels - 1) ** 2 df_total = df_factor + df_inter min_runs = df_total + 1 min_cols = n_factors + interactions + (1 if extra_error_col else 0) return min_runs, min_cols print(need_runs(4, 3)) # (9, 5) -> 行数刚好 L9,但列数要 5,L9 只有 4 列 print(need_runs(4, 3, extra_error_col=False)) # (9, 4) -> 饱和,没有误差列

这段代码的价值在于把「感觉够用」换成两个硬数字。第一行输出说明:4 个 3 水平因子如果还想要空白列,L9(3^4) 其实是不够的,要么降到 3 个因子,要么上 L27(3^13),要么接受没有空白列、靠重复试验造误差。参数interactions只在 2 水平或 3 水平的规则设计里好估,混合水平表里交互项的自由度是两列自由度相乘,不能直接用这个简化公式,得单独算。

提示:选表之前先在纸上写下「主因子数、水平数、要不要看交互、有没有重复试验」这四项,四项定不下来就不要打开软件。

2.2 表头设计:交互作用不是随便找一列塞

表选对了,第二关是把因子分配到具体列号。正交表的列不是等价的,某些列之间存在固定的交互关系,这种关系在标准表里由「交互作用表」给出。以 L8(2^7) 为例,标准配置是列 1 放 A、列 2 放 B、列 3 放 A×B、列 4 放 C、列 5 放 A×C、列 6 放 B×C、列 7 放 A×B×C。

这意味着什么?如果你把第 4 个因子 D 放到列 3,那么 D 的主效应就和 A×B 完全混在一起了。混杂(别名)的后果不是算不出来,而是算出来了你也没法解释:D 显著,到底是 D 在起作用,还是 A 和 B 的联合效应在起作用,数据里没有信息可以区分。这类问题在事后无法补救,只能重做试验。

实操上有两条路。第一条是查表法:拿到表型后先找它的交互作用表,把要考察的交互项对应的列标出来,剩下的列才给主因子。第二条是回避法:如果交互作用不是必须考察的对象(很多工艺摸底阶段确实不是),索性把所有因子安排在互不混杂的列上,把交互项统一推进误差项,代价是交互效应会污染误差估计,F 检验变保守。

我一般的做法是,先用 L8 或 L9 做一轮只排主效应的筛选试验,把 6 个候选因子砍到 3 个,再用 L8 或 L27 做一轮带交互的细化试验。两轮加起来 17 组,比一上来就上 L27 的 27 组更省,而且第一轮的结论能帮你判断第二轮到底该看哪些交互。

2.3 空白列、重复试验与误差项

方差分析必须有误差项,这跟极差分析不一样。极差分析只比 k 值大小,不需要误差;一旦要做 F 检验判断显著性,就必须回答「这点差异相对于随机波动算不算大」。误差来源只有三种:空白列、重复试验、把平方和最小的若干项合并进误差(pooling)。

表型列数各列水平饱和可容主因子交互安排典型用途
L4(2^3)323不可用2 至 3 因子粗筛
L8(2^7)727有标准交互表2 至 4 因子 2 水平
L12(2^11)11211不可用多因子筛,无交互
L9(3^4)434基本不可用3 至 4 因子 3 水平,饱和
L18(2^1×3^7)8混合8不可用1 个 2 水平加 7 个 3 水平
L27(3^13)13313可安排部分交互3 水平含交互
L16(4^5)545部分可用4 水平工艺优化

表格里「交互安排」这一栏是很多人忽略的。L12 和 L18 属于非饱和的特殊构造表,它们的列之间不满足标准交互关系,你没法从交互作用表里查到某一列对应哪个交互项,把交互项硬塞进去只会得到一堆无法解释的混杂。这类表的设计初衷就是「只筛主效应」,用它们做交互分析属于误用。

空白列的用法也有讲究。留 1 列空白做误差,自由度就是该列的水平数减一;留 2 列空白,误差自由度是两列之和,F 检验更稳。但如果所有空白列加起来自由度还是太小(比如只有 1),F 检验的分辨力会很差,这时候宁可用重复试验:同一组参数做 2 到 3 次,把组内变异作为纯误差,自由度等于「试验组数 ×(重复次数 − 1)」。

2.4 在工具里把方案建出来,然后核对三件事

正交设计助手的建表流程大同小异:新建方案、选表型、设定因子数与水平、进入表头设计界面把因子拖到列上、生成试验方案、导出。看似简单,但生成之后千万别直接拿着去开做,先核对三件事。

第一件,列数够不够。生成后数一下被主因子占用的列数加上空白列数,是否等于表的列数;如果工具允许你只填一半列,剩下的列会自动变成空白列,这是好事,但要在导出前确认它们确实存在。

第二件,水平映射对不对。软件里通常用 1、2、3 表示水平,你的实际参数是温度 80、90、100 还是 85、95、105,必须在方案表里显式写出来,而不是靠脑子记。导出的方案表应该同时有一列「水平编码」和一列「实际取值」,否则交给别人执行时必定错位。

第三件,重复次数和随机化。同一水平的重复试验要在方案里标出次序,而且实际执行顺序最好打乱——按表里的行序从第一行做到最后一行,设备漂移、环境变化会和列号产生系统性关联,这一步不做随机化,后面查不出原因的「异常显著」多半来源于此。

3. 用脚本复刻一遍:正交表生成、正交性自检与导出

工具能点出来,为什么还要写一遍?我的理由是三条:生成的表要能进版本库、能批量出多套方案、能跟后续的数据处理脚本串起来。更重要的是,自己写一遍就再也不会搞错哪列是主效应、哪列是误差。

3.1 硬编码加校验:L9(3^4) 的最短实现

L9 是 3 水平场合用得最多的一张表,行数少、结构清晰,直接把这 9 行写死在代码里比任何生成算法都可靠。

import pandas as pd # 标准 L9(3^4),0/1/2 代表三个水平 L9 = [ [0, 0, 0, 0], [0, 1, 1, 1], [0, 2, 2, 2], [1, 0, 1, 2], [1, 1, 2, 0], [1, 2, 0, 1], [2, 0, 2, 1], [2, 1, 0, 2], [2, 2, 1, 0], ] # 列号 -> 因子名,None 表示该列留作误差 COL_MAP = {0: "温度A", 1: "时间B", 2: "浓度C", 3: None} # 编码 -> 实际取值 LEVEL_MAP = { "温度A": {0: 80, 1: 90, 2: 100}, "时间B": {0: 30, 1: 45, 2: 60}, "浓度C": {0: 5, 1: 10, 2: 15}, } def build_plan(rows, col_map, level_map): data = {} for idx, name in col_map.items(): if name is None: continue data[name] = [level_map[name][r[idx]] for r in rows] plan = pd.DataFrame(data) plan.insert(0, "试验号", range(1, len(plan) + 1)) return plan plan = build_plan(L9, COL_MAP, LEVEL_MAP) print(plan)

build_plan做了两件事:把编码矩阵按列映射成因子名,再把编码翻译成实际物理量。COL_MAP里值为None的列被跳过,但它在原始矩阵里依然存在——那正是误差列,后面做方差分析时要用到它。LEVEL_MAP必须逐因子写全,不能只写一个通用映射,因为不同因子的量纲完全不同,共用一套映射是错位的高发区。输出的表里第一列是试验号,方便现场对照。

3.2 正交性自检:两两列的组合出现次数必须相等

正交表的定义性质是:任意两列的水平组合,各组合出现的次数相同。这个性质是整张表成立的基础,一旦手工改动过表(比如为了迁就设备限制删掉某一行),性质就被破坏了。加一个自检函数,改完表随手跑一下。

def check_orthogonal(design: pd.DataFrame): """校验任意两列的水平组合是否等频出现""" cols = list(design.columns) bad = [] for i in range(len(cols)): for j in range(i + 1, len(cols)): cnt = design.groupby([cols[i], cols[j]]).size() if cnt.nunique() != 1: bad.append((cols[i], cols[j], cnt.to_dict())) return (len(bad) == 0), bad code_matrix = pd.DataFrame(L9, columns=[0, 1, 2, 3]) ok, detail = check_orthogonal(code_matrix) print(ok, detail) # True []

check_orthogonal不关心水平数是否相同,只用nunique() == 1判断组合频次是否一致,所以对混合水平表同样适用。返回的detail会列出所有不满足的列对及其组合计数,方便定位是哪两列出了问题。常见的报错场景是:删了一行试验、把某个水平编码从 0/1/2 改成了 1/2/3 却没同步其他列、或者从 Excel 里粘回来时行列错位。

注意:自检通过只代表表本身正交,不代表你的表头设计合理。正交性和无混杂是两回事,前者靠这个函数,后者靠 2.2 节的人工核对。

3.3 混合水平表不要硬套:L18 的处理方式

L18(2^1×3^7) 是最常被误用的表。它的第 1 列只有 2 个水平,后面 7 列有 3 个水平,很多人看到「18 行、能塞 8 个因子」就直接拿来用,结果做完发现有的因子只能取 2 个水平、有的取 3 个,参数空间根本不对齐。

正确用法是:把不需要精细调的三档参数压成两档放在第 1 列,其余 7 个因子保持 3 水平。压档本身有讲究,不能随便取中间值,应该取你预期的两端(比如低温/高温),让第 1 列的效应量最大化,否则这一列的极差会被人为压小,排序时被误判为不重要。

如果确实需要全 3 水平的 8 个因子,L18 帮不了你,只能上 L27(3^13) 取其中 8 列,行数从 18 涨到 27,代价明显但结构干净。我在项目里遇到这种取舍,一般先问一句「这 8 个因子里有没有两个是可以合并成一个复合因子的」,能合并就回到 L18,不能合并才接受 27 组。

3.4 导出与回读:编码、BOM 和列顺序

方案表的最终归宿通常是 Excel,交给现场的人填数据。这一步有两个隐蔽的坑:中文乱码和数字格式被改写。

# 导出:带 BOM 的 UTF-8,Excel 打开不乱码 plan.to_csv("正交试验方案.csv", index=False, encoding="utf-8-sig") # 回读:强制把试验号读成字符串,避免被识别成数字后丢失前导零 back = pd.read_csv("正交试验方案.csv", encoding="utf-8-sig", dtype={"试验号": str}) assert list(back.columns)[: len(plan.columns)] == list(plan.columns), "列顺序不一致"

encoding="utf-8-sig"里的 sig 就是 BOM,Excel 在打开 CSV 时靠它判断编码,没有它中文就是一堆问号。dtype={"试验号": str}是防「01 变成 1」这类问题;如果试验号里含字母数字混合(比如 A-01),不指定类型时 pandas 也可能自作主张。最后那句assert看着多余,但它救过我一次——某次导出时中间多插了一列备注,回读后所有列整体右移一位,方差分析跑出来的结果全是错的,而当时没有任何报错。

4. 极差与方差:把 9 行数据变成能拍板的结论

数据录进去之后,工具会给两张表:极差分析表和方差分析表。这两张表回答的是不同问题——极差回答「哪个因子影响大、取哪个水平好」,方差回答「这个影响是不是真的、能不能排除随机波动」。两张表结论一致的时候大家都开心,不一致的时候才是真正需要判断的时刻。

4.1 K、k、R 三个量的口径要对齐

极差分析的三个量各有含义:K 是某因子在某水平下所有响应值的和,k 是这个和除以该水平的出现次数,R 是该因子各水平 k 值的极差。排序看的是 R,选水平看的是 k。

这里有个容易忽略的细节:当各水平出现次数不等时,K 不能直接比大小,必须换算成 k。混合水平表里这种情况很常见——L18 的第 1 列每个水平出现 9 次,后面 7 列每个水平出现 6 次,如果你拿 K 去跨列比较,第 1 列的 K 天然比后面各列大 50%,排序结果直接失去意义。工具里如果有「按均值排序」的选项,一定要勾上。

import pandas as pd def range_analysis(df, factors, y): grand = df[y].mean() rows = [] for f in factors: g = df.groupby(f)[y] k = g.mean() rows.append({ "因子": f, "最优水平": k.idxmax(), "最优k": round(k.max(), 3), "最差水平": k.idxmin(), "最差k": round(k.min(), 3), "R": round(k.max() - k.min(), 3), "相对效应": round((k.max() - grand) / grand * 100, 2), # 百分比 }) return pd.DataFrame(rows).sort_values("R", ascending=False).reset_index(drop=True)

相对效应这一列不是标准输出,是我自己加的:(最优水平均值 − 总均值) / 总均值,用百分比表示把该因子从平均水平推到最优水平能带来多少改善。在跨项目比较时它比 R 更好用,因为 R 带着响应的量纲,不同指标之间没法比。参数y是响应列名,factors是因子列名列表,注意传进去的必须是实际取值列而不是编码列,否则 k 值没有物理意义。

4.2 平方和分解与 F 检验:误差项从哪来

方差分析的核心是把总平方和拆成各因子的平方和加误差平方和,再用均方比做 F 检验。公式本身不难,难的是误差项的自由度不够时怎么处理。

import numpy as np from scipy import stats def aov_orthogonal(df, factors, y, error_col=None, pool_below=None): yv = df[y].astype(float).values n = len(yv) grand = yv.mean() ss_total = ((yv - grand) ** 2).sum() ss, dfs = {}, {} for f in factors: g = df.groupby(f)[y] k, c = g.mean(), g.count() ss[f] = float((c * (k - grand) ** 2).sum()) dfs[f] = df[f].nunique() - 1 if error_col: g = df.groupby(error_col)[y] k, c = g.mean(), g.count() ss_e = float((c * (k - grand) ** 2).sum()) df_e = df[error_col].nunique() - 1 else: ss_e = ss_total - sum(ss.values()) df_e = n - 1 - sum(dfs.values()) # 误差自由度不足时,把 SS 最小的项并入误差(pooling) if pool_below is not None and df_e < 2: smallest = min(ss, key=ss.get) if ss[smallest] < pool_below * ss_total: ss_e += ss.pop(smallest) df_e += dfs.pop(smallest) ms_e = ss_e / df_e if df_e > 0 else np.nan out = [] for f in ss: ms = ss[f] / dfs[f] F = ms / ms_e if ms_e and ms_e > 0 else np.nan p = 1 - stats.f.cdf(F, dfs[f], df_e) if np.isfinite(F) else np.nan out.append({"来源": f, "SS": round(ss[f], 4), "df": dfs[f], "MS": round(ms, 4), "F": round(F, 3), "p": round(p, 4)}) out.append({"来源": "误差", "SS": round(ss_e, 4), "df": df_e, "MS": round(ms_e, 4), "F": None, "p": None}) return pd.DataFrame(out)

关键的三个分支要讲清楚。error_col给了就用空白列算误差;没给就用「总平方和减去所有因子平方和」倒推,这个倒推值里其实包含了交互作用和未被建模的所有效应,属于「纯误差的上界」,F 检验会偏保守。pool_below是补救手段:当误差自由度小于 2 时 F 检验基本没有分辨力,把平方和最小且占比低于阈值的项并入误差,能换回一两个自由度——这是工程上的常见妥协,但一定要在报告里注明合并了哪几项,否则别人复现不出来。

ms_e为 0 或者df_e为 0 的时候代码返回 NaN,这不是 bug 是保护。饱和设计加无重复试验,误差自由度就是 0,任何软件都算不出 F 值,这时候只能退回极差分析,或者补做重复试验。

4.3 结论怎么落地:最优组合与验证试验

极差表给出了每个因子的最优水平,把它们拼起来就是「计算最优组合」。但这个组合有个前提:所有因子之间没有显著交互。一旦某个交互项显著,主效应的最优水平就不能独立选取了——A 的最优水平取决于 B 取哪个水平,必须做二维表(A 的每个水平 × B 的每个水平)逐格比较。

最优组合的预测值按加性模型估算:总均值加上各显著因子「最优水平均值减总均值」的累加。这个预测值一定要拿去做验证试验,验证值和预测值差得远,说明有交互作用或者存在未被建模的因子,模型不成立。

我一般的做法是:验证试验做 2 到 3 次重复,取均值与预测值比。偏差在 5% 以内认为模型可用;超过 15% 就回去检查是不是漏了交互项,或者最优组合落在了试验考察范围之外的外推区——外推区的预测值再好看也不能信。

4.4 R 排序和 F 检验打架时信谁

这是实操里最高频的困惑:极差表里因子 C 排第一,方差表里 C 却不显著,因子 B 的 R 排第三但 F 检验显著。两个结论都不算错,它们回答的问题不同。

R 值不扣除误差,任何波动都会被算进去;F 检验把该因子的均方和误差均方做比,误差大的场合,R 再大也可能不显著。自由度也影响判断——3 水平因子占 2 个自由度,F 检验的分辨力天然低于 2 水平因子。还有水平设置的问题:某个因子你选的三档跨度太小,效应被压缩,R 自然小。

我的处理顺序是:先用 F 检验定「哪些因子必须留在模型里」,再在这些显著因子里用 R 排序决定优化优先级。不显著的因子不是不重要,而是「在当前试验条件下分辨不出来」,如果工艺上对它没把握,宁可保留在方案里取中间水平求稳。最忌讳的是只看 R 排序就把不显著的因子当成最重要因素,那样做出来的优化方案,换个批次就崩。

5. 避坑与排查:五条血泪记录

这一章的五条,每一条都是我在实际项目里踩过或者见别人踩过的。前四条跟工具有关,第五条跟数据格式有关。关于安装包,这里说一句实在话:来路不明的改包、绿色版、去限制版,能启动不等于能算对,省下的那点成本往往会以「结论不可复现」的形式还回来,实验室的报告一旦要对外,用正版或者用自己的脚本是唯一稳妥的路。

5.1 来路不明的安装包:界面能开,算出来的数不一定对

现象:程序能正常启动,建表、录数据、出极差表都正常,但方差分析表里的 F 值和手工复算对不上,或者同一份数据两次运行结果不同。

原因:这类小工具的方差分析模块通常依赖某个数值库,改包过程中如果替换了不同版本的运行库,浮点累加顺序变化会导致末位差异;更糟的情况是某些校验代码被摘除后,误差项的自由度计算分支走了默认值,看起来正常但口径已经变了。

解决:不要用无法核验来源的包。要么用官方渠道,要么用自己的脚本——第 3、4 章那两段代码加起来不到一百行,跑一遍和工具对一下,两边的 K、k、R 一致就说明工具可信,不一致就以手算为准。我现在凡是接手别人做过的正交分析,第一件事就是拿原始数据用脚本重算一遍极差表,五分钟的事。

5.2 改了水平数却没重排:整张表的数据全部错位

现象:方案生成后觉得某个因子的三档不合适,把 90 改成 95,导出后发现数据行和参数对不上,或者分析结果里出现「某水平只有一个样本」。

原因:编码列和取值列是两个东西。改实际取值时如果只改了一列显示文字,没有同步更新编码与水平的映射关系,或者改完之后表格没有重新排序,行序和编码矩阵就脱钩了。

解决:永远改映射表,不改方案表。第 3.1 节那种LEVEL_MAP的写法就是防这个的——实际取值全部由映射生成,改参数只改映射,重跑一遍生成函数,方案表整体重建。另外,改完参数一定要重跑 3.2 节的正交性自检,虽然参数改动不影响表的正交结构,但能强制你重新看一遍整张表。

5.3 把交互列当因子填了数据

现象:方案表里某一列明明是 A×B 的交互列,却按主因子录了实测值,做极差分析时这一列的 R 值还挺大,但物理上解释不通。

原因:工具的表头设计界面里,主因子和交互项通常都是「占一列」,视觉上没有区别。从别人那里接手方案时,只看列标题不看设计说明,就会把交互列当主因子。

解决:方案表必须在表头注明每一列的身份,主因子写「A 温度」,交互列写「A×B」,空白列写「误差」。第 3.1 节的COL_MAP就是这个作用,值为None的列在生成时被跳过,根本不会进入数据分析环节。接手别人的方案时,先问一句「这份表的交互列是哪几列」,问不出来就自己按表型重排一遍。

5.4 没有空白列也没重复试验:方差分析直接算不出来

现象:点「方差分析」按钮,软件弹出「误差自由度为 0」或者干脆给出一堆Inf、NaN。

原因:饱和设计(比如 4 个 3 水平因子用 L9)把全部列占满,误差平方和只能靠「总平方和减各因子平方和」倒推,倒推出来的是一个确定值,自由度为 0。自由度 0 的均方无定义,F 值也就不存在。这不是软件的问题,是设计阶段就注定的。

解决:三条路,按代价从低到高。第一条,把平方和最小的一列并入误差做 pooling,换 2 个自由度,代价是 F 检验偏保守;第二条,补做重复试验,同一参数做 2 到 3 次,用组内变异当纯误差,自由度等于组数乘(重复数减一);第三条,减少因子数或者换更大的表。我一般先看 pooling 后显著性结论是否稳定,稳定就用 pooling 的结果,不稳定就补重复试验。

5.5 导出的 CSV 在 Excel 里乱码、编号变科学计数法

现象:脚本导出的方案表用 Excel 打开,中文全是问号;或者试验号1-1、01变成了日期和数字,前面的零消失。

原因:不带 BOM 的 UTF-8 在中文 Windows 的 Excel 里会被按本地编码解析;CSV 没有类型信息,Excel 打开时会自动推断,01推断成数字 1,1-1推断成日期。

解决:导出时用encoding="utf-8-sig";试验号列在生成阶段就格式化成带前缀的字符串(比如R01),从源头上避免被推断成数字;如果要交给别人用 Excel 填数据,干脆导出成.xlsx并显式指定列类型。回读时显式指定dtype,不要依赖自动推断——第 3.4 节那段代码里的assert就是为这一步兜底的。

6. 进阶一步:从正交筛选走到响应面

正交表擅长的是「在多个候选因子里找出谁重要、大致往哪个方向调」,它不擅长精确找最优点——因为正交设计的水平是离散的三档,最优值大概率落在两档之间。真正要把工艺窗口定死,得在正交筛选之后接一轮响应面。

6.1 筛选与优化的分工

合理的流程是两段式。第一段用 L8 或 L9 做筛选,从 5 到 6 个候选因子里挑出 2 到 3 个显著因子,同时确认方向;第二段用中心复合设计或 Box-Behnken 设计,在这 2 到 3 个因子上做 13 到 17 组试验,拟合二次模型,找出极值点。两段加起来三十来组,比一次性上大规模全因子省得多,而且第二段的试验范围可以用第一段的结论收窄,拟合精度更高。

判断该不该进第二段的信号是:一阶模型(只有主效应)的残差有明显曲率,或者验证试验的实测值系统性偏离预测值。出现这两种情况,说明响应面在最优点附近是弯的,线性外推会把你带到沟里。

import numpy as np # 二阶响应面模型: y = b0 + sum(bi*xi) + sum(bii*xi^2) + sum(bij*xi*xj) # X 每行是一组试验,列顺序: 1, x1, x2, x3, x1^2, x2^2, x3^2, x1x2, x1x3, x2x3 def design_matrix(factors): x1, x2, x3 = factors[:, 0], factors[:, 1], factors[:, 2] return np.column_stack([ np.ones(len(factors)), x1, x2, x3, x1**2, x2**2, x3**2, x1 * x2, x1 * x3, x2 * x3, ]) def fit_quadratic(factors, y): X = design_matrix(np.asarray(factors, dtype=float)) coef, *_ = np.linalg.lstsq(X, np.asarray(y, dtype=float), rcond=None) y_hat = X @ coef ss_res = ((y - y_hat) ** 2).sum() ss_tot = ((y - np.mean(y)) ** 2).sum() return coef, 1 - ss_res / ss_tot

design_matrix里的列顺序必须和后面解释系数时一一对应,这一点比代码本身重要——十个系数依次是截距、三个线性项、三个二次项、三个交互项。fit_quadratic用最小二乘求解,返回的 R² 低于 0.85 就别急着用这个模型找最优点了,先回去看是不是漏了因子或者试验范围太窄。拿到系数之后,对二次模型求偏导置零可以解出极值点,但解出来要先检查是否落在试验范围内,超出范围的点是外推,只能当方向参考。

6.2 一个我改不掉的习惯

从那以后我每次拿到一份正交试验数据,不管是不是自己做的,都强制走一遍「重算极差表 → 核对误差自由度 → 检查最优组合是否在考察范围内」这三步,哪怕对方已经把分析报告写得很漂亮。有两次就是在这三步里发现报告的误差项被错误地算成了含交互作用的倒推值,显著性结论整体偏乐观。

正交设计这件事,工具能替你省掉的是计算,省不掉的是设计阶段的判断。选表、排表头、定误差、验证,这四步里任何一步偷懒,后面都会以「结论不可复现」的形式找回来。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询