简介:「正交表构造方法PPT课件」面向统计学、质量管理、工程优化及科研实验设计方向的初学者与实务工作者,系统梳理正交试验设计的核心概念与正交表构造逻辑。课件从析因设计实验次数过多的问题切入,说明正交试验如何借助「均匀分散、整齐可比」的原则从全面试验中挑选代表性组合,进而讲解Ln(ji)符号中行数、位级数与列数的含义,并以L9(3^3)、L18(3^7)、L8(4×2^4)等典型表为例,辨析等水平表与混合型正交表的差异。同时结合三因素三水平实例,对比全面实验法与正交表安排实验的工作量差异,帮助读者掌握根据因素与水平数选表、安排试验并分析因素影响的基本流程。资源包共1个pptx文件,约440KB,页面按概念、符号、结构、正交性与应用逐步展开,适合课堂讲授或自学浏览。目前已有79人学习。
1. 正交表构造方法PPT课件:卡人的从来不是排版
「正交表构造方法PPT课件.pptx」这个文件名,通常出现在两个场景里:测试团队要做组合用例裁剪的内部培训,质量工程岗给新人解释“7 个参数各 3 个取值为什么不用跑 2187 次”。真动手做这份课件的人会发现,难点不在母版和配色——排版半小时就能搞定,卡住的是表本身:这张表从哪来、正交性凭什么成立、混合水平的列怎么拆、现场演示时能不能一键验证。
把这件事讲透,背后至少要跑通三件事:用有限域或者贪心算法把表算出来,用脚本当场校验正交性,再用 python-pptx 批量生成表格页和公式页。读者如果只是要一份能用的课件,可以直接抄第 4 章的脚本;如果要讲清“为什么 L9 是 9 行而不是 8 行”,第 2、3 章的推导是必须补的。这份课件真正值钱的地方,是它把统计试验设计和组合测试两个领域的共同底座摊开给人看。
2. 正交表的结构与构造方法选型:L_n(m^k)、强度与正交性判据
2.1 正交表的三要素:行数、水平数、列数与 L_n(m^k) 记法
正交表的标准写法是 L_n(m^k),三个数字各管一件事:n 是行数,也就是实际要跑的试验次数;k 是列数,代表最多能安排多少个因素;m 是每一列的水平数,也就是单个因素有几个取值。L9(3^4) 读作“9 次试验、4 个因素、每因素 3 个水平”,它替代的是 3^4 = 81 次全组合。
做课件时容易讲错的一点是行数从哪来。对称表(所有列水平数相同)里,n = m² 时最多只能放 m+1 列,这不是巧合:L4(2^3)、L9(3^4)、L16(4^5)、L25(5^6) 全部满足 k = m+1,它正是有限域构造的直接产物。列数需求超过 m+1 时,就得换更大的表,比如要 7 个 2 水平因素只能上 L8(2^7) 或 L16(2^15),行数从 4 跳到 8 再到 16。因素数、列数、行数之间的这种“跳档”关系,是必须讲的一课,否则听众会以为行数可以随便定。
非对称表另算,L18(2^1×3^7) 是经典例子:18 行,1 个 2 水平列加 7 个 3 水平列。它的 18 不是任何水平数的平方,由正交拉丁方拼合而成,正交性要按最小公倍数 6 来核对——2 水平与 3 水平两列的组合共 6 种,每种在 18 行里出现 3 次。
2.2 正交性判据与强度 t:什么时候 L18 比 L16 更合适
严格正交的判据可以写成一句话:任意两列的水平组合出现次数完全相同,都等于 n/m²(混合表按两列水平数之积算)。这个条件是“强度 2”的定义。把定义推广一步就是强度 t:任意 t 列的所有水平组合出现次数相等,t=3 的表能覆盖三因素之间的交互作用,但行数会明显上涨——3 水平、4 因素的强度 3 表最少要 27 行。
工程上还有一类更宽松的表叫覆盖数组 CA(N; t, k, v),只要求任意 t 列的组合至少出现一次,不要求等频。行数能压得比严格正交表更少,代价是失去方差分析所需的均衡性。这个区别在课件里必须标清楚:
| 指标 | 严格正交表 | 覆盖数组 |
|---|---|---|
| 组合频次 | 每对组合出现次数完全相同 | 至少出现一次 |
| 行数 | 固定,多为 m² 或其倍数 | 不固定,追求最小 |
| 能否做方差分析 | 能 | 不能,只用于组合覆盖 |
| 典型工具 | 标准正交表手册 | PICT、贪心算法脚本 |
| 适用场景 | 试验设计、参数寻优 | 软件组合测试、用例裁剪 |
回到 L18 与 L16 的选择:因素里有 2 水平和 3 水平混搭,又不想为凑齐 4 水平做拟水平时,L18 是更省的选择;如果所有因素都能统一到 2 水平,L16 更整齐,后续分析也简单。
2.3 四种构造方法选型对比:查表、有限域、Hadamard、贪心覆盖
真正落地时只有四条路,课件里给一张对比表比讲十页公式有用:
| 方法 | 适用条件 | 行数 | 混合水平支持 | 实现成本 |
|---|---|---|---|---|
| 查标准表 | 因素数与水平数匹配现成表 | 固定 | 需直接选混合表 | 最低 |
| 有限域 GF(q) | q 为素数或素数幂 | q² | 要靠并列/拟水平 | 中等 |
| Hadamard 矩阵 | 全部为 2 水平,n = 2^j | 2n | 不支持 | 中等 |
| 贪心覆盖 | 任意水平组合 | 近似最小 | 天然支持 | 较高,要调参 |
不管走哪条路,交付前都要过一遍校验。这段校验脚本可以直接放进课件当现场演示:
import itertools import numpy as np def check_orthogonal(rows, strength=2, tol=1e-9): """校验强度为 strength 的正交性,返回 (是否通过, 明细列表)""" A = np.asarray(rows) # 每行是一次试验,每列是一个因素 n, k = A.shape ok, report = True, [] for cols in itertools.combinations(range(k), strength): sub = A[:, cols] # 取出 t 列 counts = {} for row in sub: counts[tuple(row)] = counts.get(tuple(row), 0) + 1 freq = set(counts.values()) # 各组合的频次应当只有一个值 expect = n / len(counts) # 混合水平下 len(counts) 自动等于水平数之积 if len(freq) != 1 or abs(next(iter(freq)) - expect) > tol: ok = False report.append((cols, sorted(freq), expect)) return ok, report ok, detail = check_orthogonal([[0,0,0],[0,1,1],[0,2,2], [1,0,1],[1,1,2],[1,2,0], [2,0,2],[2,1,0],[2,2,1]]) print(ok) # True,这就是 L9(3^4) 的其中三列逻辑上这段代码只做两件事:枚举所有 t 列组合,统计每种水平组合出现的次数,再检查这些次数是否整齐。strength改成 3 就变成三列校验;expect = n / len(counts)这个写法对混合水平表同样成立,因为笛卡尔积的长度就是水平数之积,不需要为 L18 单独写分支。
注意:n 大于 100 时这段统计会变慢,实际项目里我一般只抽样校验若干列组合,或者把组合键转成整数编码后用 numpy 的 bincount 统计。
3. 用 Python 实现正交表构造:从有限域到混合水平
3.1 有限域 GF(p) 构造 L_{p²}(p^{p+1}) 的最小实现
对任意素数 p,标准构造法只需要十几行代码。核心思路是把行号 t 写成二元组 (a, b),两者都取自 GF(p),列则取 a、b 以及全部 a + λb(λ 遍历 GF(p)):
def oa_from_prime_field(p): """构造 L_{p^2}(p^{p+1}),p 必须是素数""" assert p > 1 and all(p % d for d in range(2, int(p ** 0.5) + 1)), "p 必须为素数" table = [] for a in range(p): for b in range(p): # 前两列固定取 a、b,其余 p 列取 a + λb mod p row = [a, b] + [(a + lam * b) % p for lam in range(p)] table.append(row) return table t = oa_from_prime_field(3) print(len(t), len(t[0])) # 9 4,即 L9(3^4)为什么这样构造出来一定是正交表?任取两列,它们的取值分别是 a + λ₁b 和 a + λ₂b。当 λ₁ ≠ λ₂ 时,(λ₁ - λ₂) 在 GF(p) 上可逆,给定任意一对目标值都能唯一解出 (a, b),所以每种组合恰好出现一次。λ₁ = λ₂ 时两列相同,组合自然均匀。这就是课件里“为什么”部分唯一需要讲的代数理由,比画图直观得多。
换 p 值就能拿到不同规格:p=2 得 L4(2^3),p=3 得 L9(3^4),p=5 得 L25(5^6)。但 4 水平拿不到,因为 4 不是素数。要构造 L16(4^5) 必须上素数幂 GF(4),元素用二进制表示,加法即按位异或,乘法查对数表:
LOG = [None, 0, 1, 2] # LOG[1]=0, LOG[2]=1, LOG[3]=2 EXP = [1, 2, 3] # EXP[0]=1, EXP[1]=2, EXP[2]=3 def gf4_add(a, b): return a ^ b # GF(4) 加法等价于按位异或 def gf4_mul(a, b): if a == 0 or b == 0: return 0 return EXP[(LOG[a] + LOG[b]) % 3] # 乘法走对数表,模 3 是因为乘法群阶为 3把 3.1 里的% p换成gf4_add和gf4_mul,同一套循环就能生成 L16(4^5)。这一步在课件里值得单独一页,它把“水平数必须是素数幂”这个限制从口诀变成了可运行的事实。
3.2 贪心构造混合水平正交表:逐行扩展的完整脚本
实际项目里因素水平数很少整齐,[2,3,3,4,5] 这种组合查表查不到,有限域也套不上,只能走贪心。做法是一行一行加,每加一行都挑“新覆盖组合数最多”的那一行:
import itertools, random def greedy_covering_array(factors, strength=2, seed=42, tries=200): """factors: 每个因素的水平数列表;返回覆盖所有 t 列组合的行集合""" rng = random.Random(seed) combos = list(itertools.combinations(range(len(factors)), strength)) uncovered = set() for c in combos: for vals in itertools.product(*[range(factors[i]) for i in c]): uncovered.add((c, vals)) # 待覆盖的 (列组合, 取值) 全集 rows = [] while uncovered: best_row, best_gain = None, -1 for _ in range(tries): # 随机撒 tries 个候选行 row = [rng.randrange(m) for m in factors] gain = sum(1 for (c, v) in uncovered if tuple(row[i] for i in c) == v) if gain > best_gain: best_gain, best_row = gain, row rows.append(best_row) for (c, v) in list(uncovered): # 把这一行覆盖掉的组合移除 if tuple(best_row[i] for i in c) == v: uncovered.discard((c, v)) return rows t = greedy_covering_array([3, 3, 3, 3]) print(len(t), len(t[0])) # 大约 9~12 行,全组合是 81 行factors的顺序就是列的语义顺序,值是每列的水平数,混合水平直接写 [2,3,3,4,5] 即可。tries是每行撒的候选数,直接决定结果质量和耗时:因素少时 60 就够,因素上到十几个、水平数到 5 时建议提到 300 以上,否则很容易被局部最优困住,多出三成行数。seed固定是为了让课件现场的演示结果可复现——这一点比追求绝对最小行数更重要,听众要能自己跑出同一个数字。
3.3 构造结果的校验、截断与常见错误
生成完必须校验,三个最容易翻车的地方:
第一,行数与水平组合的整除关系。混合水平表要检查任意两列的最小公倍数 L = lcm(m_i, m_j) 是否整除 n,不整除就说明它不是严格正交表,只能当覆盖数组标注。L18 之所以成立,是因为 2 和 3 的 lcm 是 6,18 能被 6 整除。
第二,水平编码混用。有的表用 0 起始,有的用 1 起始,同一张表里混着来会直接破坏统计结果,也会让check_orthogonal报出假失败。我一般统一成 0 起始,展示给业务方的表格页再加一列序号从 1 开始。
第三,截断列的合法性。L16(2^15) 只取前 7 列做 7 因素完全没问题,但反过来,把 4 水平列强行当 2 水平列用(拟水平法)会破坏该列与其他列的正交性,必须重新跑一遍校验脚本,通不过就不能写进课件当结论。
4. 把正交表构造方法做成 PPT 课件的工程化流程
4.1 课件页面结构:公式页、表页、对比页的比例与信息密度
我一般把 24~30 页的课件按 1:1:1 分三段:前段讲问题(组合爆炸有多可怕,用具体数字,例如 7 因素 3 水平 2187 行 vs L27 的 27 行),中段讲构造(公式 + 可运行代码 + 逐步校验),后段讲落地(工具选型、团队协作、常见误用)。文字页控制在总页数的两成以内,其余全是表、公式和代码截图。
信息密度是翻车重灾区。一张完整 L27(3^13) 有 27 行 13 列,塞进 16:9 一页会把字号压到 8pt 以下,投影时最后一排根本看不清。常见做法是拆成两页,或者只展示前 4 列并在备注里说明截断。表格页的硬指标:最小字号不低于 12pt,单页表格行数不超过 20 行。
4.2 用 python-pptx 批量生成正交表页面
手画 20 页表格没人愿意干第二次,用脚本生成才是正解:
from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor prs = Presentation() prs.slide_width, prs.slide_height = Inches(13.333), Inches(7.5) # 16:9 BLANK = prs.slide_layouts[6] # 空白版式 def add_oa_slide(title, table, note=""): slide = prs.slides.add_slide(BLANK) tb = slide.shapes.add_textbox(Inches(0.6), Inches(0.35), Inches(12), Inches(0.8)) tb.text_frame.text = title tb.text_frame.paragraphs[0].font.size = Pt(28) tb.text_frame.paragraphs[0].font.bold = True rows, cols = len(table), len(table[0]) shape = slide.shapes.add_table(rows, cols, Inches(0.6), Inches(1.4), Inches(12.1), Inches(5.4)) tbl = shape.table size = Pt(14) if rows <= 20 else Pt(9) # 行数多就降字号,别让表格溢出 for r, row in enumerate(table): for c, val in enumerate(row): cell = tbl.cell(r, c) cell.text = str(val) for p in cell.text_frame.paragraphs: p.font.size = size if r == 0: # 表头反色 cell.fill.solid() cell.fill.fore_color.rgb = RGBColor(0x1F, 0x4E, 0x79) for p in cell.text_frame.paragraphs: p.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) if note: nb = slide.shapes.add_textbox(Inches(0.6), Inches(6.9), Inches(12), Inches(0.5)) nb.text_frame.text = note return slide oa = oa_from_prime_field(3) header = [["No."] + [f"F{i+1}" for i in range(len(oa[0]))]] add_oa_slide("L9(3^4):任意两列的组合各出现 1 次", header + [[i + 1] + r for i, r in enumerate(oa)], "9 行替代 81 行全组合,压缩率 88.9%") prs.save("正交表构造方法.pptx")参数上要注意三点。add_table用的是真实表格对象而不是图片,好处是讲课时可以现场改某个格子的值重算。尺寸单位统一用Inches,系数按 13.333 宽换算,直接抄不会错位。字号我用行数做条件判断,rows <= 20用 14pt,否则用 9pt——但 9pt 已经接近投影极限,更好的办法是拆页而不是缩字号。
提示:python-pptx 里设置行高(row.height)经常被内容撑开覆盖,别依赖它做垂直居中,宁可让表格整体上移、底部留白。
4.3 公式渲染与图片嵌入:matplotlib mathtext 的三个坑
正交表课件绕不开公式,python-pptx 本身不支持公式对象,通用做法是用 matplotlib 把公式渲染成透明 PNG 再插进去:
import matplotlib matplotlib.use("Agg") # 无界面环境必须指定后端,否则报错 import matplotlib.pyplot as plt def render_formula(tex, out_png, fontsize=30, color="#1F4E79"): fig = plt.figure(figsize=(0.01, 0.01)) fig.text(0, 0, f"${tex}$", fontsize=fontsize, color=color) fig.savefig(out_png, dpi=300, bbox_inches="tight", transparent=True, pad_inches=0.05) plt.close(fig) render_formula(r"n = m^2,\quad k = m + 1", "f1.png") render_formula(r"\sum_{j=1}^{m} c_{ij} = \frac{n}{m}", "f2.png")第一个坑是 mathtext 不是完整 LaTeX:\begin{array}、大部分\text{}用法都不支持,矩阵只能改用\matrix或干脆画成表格页。第二个坑是bbox_inches="tight"和透明背景要一起用,只设透明不设 tight,公式周围会留一大片空白,插进 PPT 后对不齐。第三个坑是体积:300dpi 的图片单页超过 6 张,pptx 很容易冲到几十 MB,公式改用 200dpi 在投影上肉眼看不出差别。
插入时按顺序往下排就行:
def add_formula_slide(title, png_list): slide = prs.slides.add_slide(BLANK) slide.shapes.add_textbox(Inches(0.6), Inches(0.35), Inches(12), Inches(0.8)) \ .text_frame.text = title top = Inches(1.6) for png in png_list: slide.shapes.add_picture(png, Inches(1.2), top, height=Inches(0.9)) top += Inches(1.2) # 固定步长排布,避免公式互相压叠 return slide4.4 打包前的自检清单
课件做完别急着发出去,下面这几项每一条我都踩过:
| 检查项 | 建议阈值 | 不达标会怎样 |
|---|---|---|
| 单文件体积 | 小于 20 MB | 邮件和 IM 直接传不动 |
| 中文字体 | 等线、微软雅黑等通用字体 | 换机器变宋体,公式和表格错位 |
| 表格最小字号 | 不低于 12 pt | 投影最后一排看不清 |
| 公式图片分辨率 | 200~300 dpi | 全屏放大后边缘发虚 |
| 幻灯片尺寸 | 13.333 × 7.5 英寸 | 16:9 投影两侧出现黑边 |
| 总页数 | 20~30 页 | 90 分钟根本讲不完 |
体积超标时优先压公式图片,而不是删表格——表格是这份课件的核心资产,公式可以精简到每页最多三个。
5. 课件里的正交表怎么讲透:三个必调参数与验证技巧
5.1 强度 t 与随机候选数:两个直接决定行数的参数
现场演示最有冲击力的一幕,是把强度从 2 调到 3。同样是 4 个 3 水平因素,强度 2 的贪心结果通常 9~12 行,强度 3 直接跳到 27 行左右,正好等于 m^t 的下界。改一个参数、行数翻三倍,这个对比比任何公式都更能说明“交互作用是要花钱买的”。
第二个参数是贪心的随机候选数tries。单次贪心不保证最优,常规做法是换几个随机种子跑,取行数最少的那次:
for seed in (1, 7, 42, 2024): rows = greedy_covering_array([3] * 4, strength=2, seed=seed, tries=300) print(seed, len(rows))我把这段做成课件里的一个代码页,让听众自己改tries观察行数波动。经验值:4~6 个因素取 100~200 次候选足够稳定,因素数超过 10 个、水平数超过 4 时,300~500 次起步,否则同一份模型不同次运行的行数能差 30%,讲课时前后两页数字对不上就很尴尬。
5.2 混合水平拆分的验证技巧
混合水平是课件里最容易讲糊的部分,两种拆分法必须区分清楚。拟水平法是把 3 水平列重复一个取值凑成 4 水平,好处是能塞进 L16 这类整齐的表,代价是该列与其他列的正交性被破坏,拆完必须重新跑check_orthogonal,通不过就只能当覆盖数组用。并列法是把 L16(4^5) 里的一列 4 水平按 (0,1) 与 (2,3) 拆成两列 2 水平,行数不变,但拆出来的两列完全相关,不能同时用来估计独立效应——课件里如果后面要讲方差分析,这一条必须用红字标出来。
一个稳妥的验证套路是两步走:先用check_orthogonal检查频次集合大小是否为 1,再用整除规则复核混合水平表——计算任意两列水平数的最小公倍数 L,检查 n % L 是否为 0,不为 0 就说明这张表天生做不到严格正交,只能在课件里标注为覆盖数组。把这两步封成一个validate_oa(rows)函数,第 4 章的脚本每次生成完表格页顺手调用一次,课件的每一张表都能在讲台上按一次回车证明它对不对,不用背结论。
本文还有配套的精品资源,点击获取