简介:面向使用SPSS 16.0/17.0开展多变量统计分析的科研人员与数据分析学习者,这份R语言扩展包围绕偏最小二乘回归(PLS)展开,用于在R环境中补齐PLS建模能力,并借助R与SPSS的接口把相关分析功能引入熟悉的SPSS操作界面。压缩包为tgz格式,约10.55MB,上游未提供具体文件清单与类型明细,解压后可按包结构自行查阅。已有948人学习下载,说明该方向在统计建模人群中具有一定关注度。内容涉及pls、mixOmics等R包中的PLS1/PLS2、PLSR、sPLS与PLS-DA等方法,适合需要处理高维、强共线性数据的研究者对照学习;对于不熟悉R语法的SPSS用户,也可通过接口方式调用R完成回归与判别分析,并在此基础上理解权重向量、成分提取与模型验证的基本流程。整体更偏向方法入门与工具集成,而非成套案例数据。
1. 一份 R语言安装包能跑多远:从装完到 PLS分析 出第一张得分图
很多人对「R语言安装包」的期待是双击、下一步、完成,然后打开一个蓝白界面就以为万事大吉。真到要做 PLS分析 的时候才发现,装完只是起点:包库在哪、路径干不干净、R 和包的版本对不对得上,这三个问题任意一个没解决,library(pls)那一行就会给你脸色看。这份资源解决的正是这段最磨人的路——把 R 本体、IDE、离线包库和一套能直接跑的 PLS/PLS-DA 脚本凑成一个自洽的环境,让你不用在装包上耗掉一整天。适合两类人:一类是被近红外、色谱、代谢组学数据逼着学 PLS 的分析岗,一类是想把建模流程固化成脚本、以后换数据直接复用的工程岗。化学计量和组学数据里变量成百上千、样本常常只有几十个,这种数据用普通最小二乘必翻车,PLS 是绕不开的选择。
2. 装环境:把 R语言安装包铺成一个能装包的离线仓库
2.1 三个组件的安装顺序与路径约定
这套东西里的角色分得很清楚,装错顺序或装错位置,后面每一步都会连带出问题。
| 组件 | 作用 | 装错的典型表现 |
|---|---|---|
| R 本体 | 解释器与基础包(stats、Matrix 等) | RStudio 启动后提示找不到 R |
| RStudio | 编辑器与图形设备,不参与计算 | 界面能开,但 console 里一跑就报错 |
| 离线包库 | 预先下好的 CRAN / Bioconductor 二进制包 | install.packages报包不存在或依赖缺失 |
顺序必须是先 R 本体、再 RStudio。反过来的话 IDE 会自己去扫注册表找一个不存在的 R,然后在启动横幅里给你一句语焉不详的告警。安装路径只用 ASCII 字符,别带空格,也别放在会被同步的文档目录里——同步进程会在你装包的瞬间锁住文件,报出unable to move temporary installation这种看着像玄学、实际就是文件被占用的错误。
装完之后先别急着装包,把库路径钉死。
# 1) 先确认版本与位数:PLS 相关的包几乎都是 64 位二进制 R.version.string print(R.version$arch) # 期望看到 x86_64 print(.libPaths()) # 看清楚默认装到哪个目录去了 # 2) 给当前用户单独建一个包目录,避开同步盘和中文路径 user_lib <- "D:/Rlibs/4.3" dir.create(user_lib, recursive = TRUE, showWarnings = FALSE) # 3) .libPaths 的顺序就是查找顺序,第一位既是首选安装位置 .libPaths(c(user_lib, .libPaths())) print(.libPaths()).libPaths()返回的是一个字符向量,R 找包时从头往后扫,装包时默认装到第一个元素指向的目录。这行代码直接决定了你以后所有的包落在哪,写错一次,后面会反复出现「明明装过、重启就没了」的怪事。R.version$arch用来确认位数,混装 32 位包是 Windows 上最隐蔽的一类问题,报错信息完全指不到根因。
想让它每次启动都生效,把同一行写进用户级.Rprofile,而不是每次开会话手动敲一遍。
prof <- path.expand("~/.Rprofile") write('.libPaths(c("D:/Rlibs/4.3", .libPaths()))', file = prof, append = TRUE)path.expand("~")在 Windows 上一般落到用户主目录,在 Linux 上是$HOME,比硬编码路径稳。用append = TRUE是防止覆盖掉别人已经写进去的配置。
2.2 用本地仓库一次装齐 pls、ropls、mixOmics
真正做 PLS分析,光有pls一个包不够:pls管回归和交叉验证,ropls出得分图和置换检验,mixOmics管判别和多分类。这三个包的来源不在一处——pls在 CRAN,ropls和mixOmics在 Bioconductor,装的时候要把两个仓库同时挂上。
离线仓库的目录结构要和镜像保持一致,CRAN 那边是bin/windows/contrib/<版本>或src/contrib,Bioconductor 那边是packages/<版本>/bioc/src/contrib。结构对了,install.packages才认。
# 本地仓库用 file:/// 前缀,路径分隔符统一用正斜杠 local_cran <- "file:///D:/R-offline/cran" local_bioc <- "file:///D:/R-offline/bioc" install.packages( c("pls", "ropls", "mixOmics"), repos = c(CRAN = local_cran, BioCsoft = local_bioc), lib = "D:/Rlibs/4.3", dependencies = TRUE, type = "win.binary" # Linux / macOS 改成 "source" )repos是一个命名向量,名字随你取,但必须是能对上的仓库名,Bioconductor 的软件包仓库惯例写成BioCsoft。lib要显式指到刚才建好的目录,不写就装到默认位置,前面钉路径的努力白费。dependencies = TRUE会连带装Imports、Depends、LinkingTo三层依赖,mixOmics的依赖链能拖出十几个包,漏一层就跑不起来。type在 Windows 上用"win.binary"直接装编译好的二进制,省掉 Rtools;Linux 上只能"source",那就得先有编译器和对应的数学库头文件。
装完别急着往下走,先验一遍依赖闭包是不是真的齐了。
ap <- available.packages(repos = c(CRAN = local_cran, BioCsoft = local_bioc)) # 递归解析 mixOmics 的全部依赖 need <- tools::package_dependencies("mixOmics", db = ap, recursive = TRUE)[[1]] # 和已装列表做差集,返回空向量才说明齐了 setdiff(need, rownames(installed.packages(lib.loc = "D:/Rlibs/4.3")))available.packages读的是仓库索引,也就是PACKAGES那个文件;索引没生成或过期,这个函数会返回空矩阵,后面的差集就永远算不对。真遇到这种情况,先在仓库目录上跑一次tools::write_PACKAGES()重建索引。这一步是我见过最省时间的自检:报错信息经常只说某个底层包缺失,而setdiff会一次性把所有缺的列给你。
2.3 版本对齐:CRAN 与 Bioconductor 混装的自检清单
R 的二进制包在文件名里带构建时的 R 主版本号,用 4.4 去加载为 4.3 编译的二进制包,纯 R 代码的包可能只是警告,含 C++ 的包会直接加载失败并提示built under R version。ropls和mixOmics都含编译代码,属于后者。
常见的做法是把「R 版本 → 包库目录 → 仓库子目录」三者绑成一列,一个 R 版本配一个包目录,升级 R 就换目录,不混用。装完统一自检一遍:
sessionInfo() # 一次性看清 R 版本、平台、已加载包 packageVersion("pls") packageVersion("ropls") packageVersion("mixOmics") # 检查所有已装包的编译版本是否与当前 R 匹配 ip <- installed.packages(lib.loc = "D:/Rlibs/4.3") table(ip[, "Built"])installed.packages()返回的矩阵里有一列Built,记录包在哪个 R 版本下构建。这一列出现两种不同取值,就说明目录被混用过,后面迟早出问题。sessionInfo()的输出建议在每个分析脚本开头或结尾打印一次,存档比事后回忆靠谱得多。
提示:离线包里如果只有源码包,Windows 上必须先装与 R 主版本严格对应的 Rtools,而且 Rtools 的 bin 目录要进 PATH,否则
source安装会在编译阶段停住,报错指向一个和真实原因毫不相干的头文件。
3. PLS 回归:ncomp 定阶、RMSEP 曲线与 VIP 筛选
3.1 为什么这批数据必须用 PLS 而不是普通最小二乘
近红外光谱、色谱指纹、代谢组学矩阵有个共同特征:变量数远大于样本数,而且相邻变量高度相关。401 个波长点、60 个样品,设计矩阵的秩最多 60,普通最小二乘的solve(t(X) %*% X)直接奇异,就算用广义逆硬算出来,系数也会剧烈震荡,换个样本就翻符号。
PLS 的思路是把 X 和 Y 同时投影到低维潜变量空间,投影时最大化两者的协方差。它和主成分回归的差别在于有没有用上 Y:主成分回归只挑 X 方差最大的方向,方差大的方向未必和响应相关;PLS 的每个成分都带着 Y 的信息,因此更少成分就能解释更多响应变异。
选型边界也要说清楚:X 与 Y 的关系强非线性、或者离群样本没清理干净的时候,PLS 出来的前两个成分会被离群点整个拉偏,得分图上就是两三个点孤零零挂在角落。这种情况先做异常样本检测,别指望靠加成分数救回来。
3.2 训练/测试切分与标准化:一张光谱矩阵的建模骨架
用pls包自带的汽油数据走一遍完整骨架,这个数据集是 60 个样品、401 个近红外波长点、响应为辛烷值,结构和工业场景几乎一样。
library(pls) data(gasoline) # 60 样品 × 401 波长 X <- gasoline$NIR # 矩阵:行 = 样品,列 = 波长 Y <- gasoline$octane # 响应:辛烷值 # 固定种子做 7:3 切分,保证任何人重跑结果一致 set.seed(20240501) idx <- sample(nrow(X), size = round(0.7 * nrow(X))) Xtr <- X[idx, ]; Ytr <- Y[idx] Xte <- X[-idx, ]; Yte <- Y[-idx] # 建模:scale = TRUE 对 X 做标准化,Y 只做中心化 m0 <- plsr(Xtr, Ytr, ncomp = 15, scale = TRUE, validation = "none") # 在测试集上按 1..15 个成分分别算 RMSE pred <- drop(predict(m0, comps = 1:15, newdata = Xte)) rmse_te <- sqrt(colMeans((pred - Yte)^2)) round(rmse_te, 3)plsr的位置参数直接接受矩阵 X 和向量 Y,比写公式接口干净,也不会因为列名里有特殊字符而解析失败。scale = TRUE对 X 逐列标准化,这一步对光谱数据是必须的——不同波长段的吸光度量级差一个数量级,不标准化等于给量级大的波段偷偷加权。ncomp = 15只是先给个上限,后面再定阶。validation = "none"表示这次拟合不算交叉验证,因为后面要在独立测试集上评估。
predict返回的是三维数组(样本 × 响应 × 成分数),drop()把长度为 1 的响应维压掉,得到一个「样本 × 成分」的矩阵,这样colMeans才能按成分逐列算 RMSE。漏掉drop()是最常见的报错来源,colMeans会提示维度不对。
3.3 交叉验证定 ncomp:RMSEP 曲线怎么读、onesigma 怎么用
测试集只能评一次,定阶必须靠交叉验证。pls包内置的 CV 支持随机分段、连续分段和交错分段,光谱数据有采集顺序时选连续分段更保守。
set.seed(20240501) m_cv <- plsr(Xtr, Ytr, ncomp = 15, scale = TRUE, validation = "CV", segments = 10, segment.type = "random", # 有采集顺序时改 "consecutive" jackknife = TRUE) rmsep <- RMSEP(m_cv, estimate = "CV") plot(rmsep, legendpos = "topright") # onesigma:在最小值 + 一个标准误范围内,选成分数最少的模型 n_opt <- selectNcomp(m_cv, method = "onesigma", plot = TRUE) n_optsegments = 10是折数,样本量在 50 以下时建议降到 5 到 7,否则每折里的样品太少,RMSEP 的方差会大到看不出趋势。segment.type三个取值的取舍:"random"适合样本独立的情况,"consecutive"把连续样品成段留出,能暴露随采集时间漂移的问题,"interleaved"是折中方案。jackknife = TRUE会额外算系数的不确定度,后面看哪些波长真的重要时要用。
estimate有两个关键取值:"CV"是标准交叉验证误差,"adjCV"是偏差修正版,小样本时后者更稳,两个都画出来对比一下是常规操作。
曲线怎么读:RMSEP 先陡降、到一个最低点、再缓慢回升,最低点对应的成分数就是候选。如果曲线一路下降没有拐点,通常不是「成分越多越好」,而是数据里混了泄漏——比如同一批样品的不同测量被分到了不同折里。selectNcomp的 onesigma 规则是「一个标准误内取最简」,比直接取 argmin 稳健,尤其适合要交付给别人复用的模型。
单次 CV 的随机性不小,我的习惯是外层再套一层重复,换 5 个种子各跑一次 10 折,把n_opt和对应 RMSEP 记下来看分布,稳定落在同一个数上才算定下来。
3.4 载荷与 VIP:把「哪些波长重要」讲清楚
模型能预测还不够,业务方一定要问「哪个波段在起作用」。pls包给出的是回归系数和载荷,变量重要性投影需要自己算,公式本身不复杂。
vip <- function(model, ncomp) { W <- model$loading.weights[, seq_len(ncomp), drop = FALSE] # X 权重 w Q <- model$Yloadings[seq_len(ncomp), 1] # Y 载荷 q T <- model$scores[, seq_len(ncomp), drop = FALSE] # X 得分 t ss <- colSums(T^2) * Q^2 # 各成分解释的方差 w2 <- rowSums(sweep(W^2, 2, ss, "*")) # 按成分加权 sqrt(nrow(W) * w2 / sum(w2)) # 归一到均值平方为 1 } v <- vip(m_cv, ncomp = 6) summary(v) which(v > 1) # 常用阈值 plot(v, type = "h", xlab = "波长序号", ylab = "VIP")sweep(W^2, 2, ss, "*")是把每个成分的平方权重乘上该成分解释的方差,MARGIN = 2表示按列扫。最后一步除以总和再乘变量数,使所有变量 VIP 的平方均值恰好为 1——这也是「VIP > 1」这个经验阈值的由来,它表示该变量的重要性高于平均水平,不是什么统计检验的临界值。
还有个容易忽略的点:coef(m_cv, ncomp = 6, intercept = TRUE)给出的系数是在标准化尺度上的,要还原到原始波长尺度得自己按scale的center和scale属性反变换,否则报给业务方的系数没法解释。m_cv$scale$sd里存着这组值。
4. PLS-DA 判别:ropls 与 mixOmics 两条路线的取舍
4.1 哑变量编码与判别规则:PLS-DA 到底在拟合什么
把连续的 Y 换成类别指示矩阵,PLS 就变成了 PLS-DA。两类问题时,哑变量是一列 0/1,模型拟合的其实是「属于该类的程度」,判别阈值取 0.5 或者取预测值更大的那一类。多类问题用 one-hot 编码,每个类别一列,判别规则取预测值最大的那一列。
library(ropls) data(sacurine, package = "ropls") # 尿液代谢组学数据,含性别分组 X <- sacurine$dataMatrix Y <- sacurine$sampleMetadata$gender # 手动看一眼哑变量结构,ropls / mixOmics 内部会自动做 Ydum <- model.matrix(~ Y - 1) colnames(Ydum) <- levels(Y) head(Ydum) dim(X)model.matrix(~ Y - 1)里的-1去掉截距项,得到完整的 one-hot 编码;不去掉的话第一列会被当成基准类而省略,两类问题时看起来一样,多类时就会少一列,后面按列取类别名会错位。
这里必须强调一句:PLS-DA 的得分图非常容易「看起来很漂亮」。哪怕 Y 是随机打乱的,只要成分数给够,两类样本在图上照样能被一条直线分开。这是高维数据的固有性质,不是模型好。所以分类模型的结论不能看得分图,只能看置换检验和独立验证。
4.2 ropls 七行代码:得分图、Q2Y 与置换检验一起出
ropls的优势是把建模、交叉验证、置换检验、诊断图打包在一个函数里,输出的摘要信息密度很高。
mod <- opls( x = X, y = Y, predI = NA, # 预测成分数,NA 表示按 Q2 自动选 orthoI = 0, # 0 = 不做正交过滤,即标准 PLS-DA crossvalI = 7, # 7 折交叉验证 permI = 200, # 200 次置换检验 scaleC = "standard" # 逐变量标准化 ) mod # 打印 R2X / R2Y / Q2Y 及置换 p 值orthoI是这套接口最容易设错的参数。它控制正交成分的个数:设成NA会启用 OPLS 过滤,把与 Y 无关的变异单独剥离;设成0才是干净的标准 PLS-DA。做方法对比时统一用0,不然你比的是两种不同的模型。
predI = NA让函数按交叉验证的 Q2 自动定阶,也可以直接写死一个整数,方便复现。permI = 200是置换次数,少于 100 时 p 值的分辨率不够,报出来的< 0.01意义有限。
摘要里三个指标要一起看:R2X是模型解释的 X 变异比例,R2Y是解释的 Y 变异比例,Q2Y是交叉验证的预测能力。经验上Q2Y大于 0.5 算有实用价值,0.3 到 0.5 之间只能算趋势,低于 0.3 基本别下结论。置换检验给的是pR2Y和pQ2,两个都小于 0.05 才算站得住。
出图直接调plot,靠typeVc切换图型:
plot(mod, typeVc = "x-score", parAsColFcVn = Y) # 得分图,按分组着色 plot(mod, typeVc = "x-loading") # 载荷图,看变量贡献 plot(mod, typeVc = "outlier") # 样本诊断,找异常点typeVc = "outlier"这张图值得单独看一眼,横纵轴是样本到模型的距离和残差,落在右上角的样本对模型影响过大,删掉重跑一遍看结论是否还成立,是常规自查步骤。
4.3 mixOmics 的 tune.splsda 与多分类扩展
mixOmics的强项在多组学整合和多分类,单组学判别也能用,接口是另一套逻辑:先调参、再建模、再perf评估。
library(mixOmics) set.seed(20240501) tune_res <- tune.splsda( X, Y, ncomp = 5, validation = "Mfold", folds = 5, dist = "max.dist", nrepeat = 10, progressBar = FALSE ) tune_res$choice.ncompdist决定判别时用哪种距离:"max.dist"取预测值最大的类,"centroids.dist"比到各类中心的距离,"mahalanobis.dist"考虑协方差结构。三者在样本量小时结论可能不一致,"max.dist"最常用也最稳。nrepeat是重复次数,交叉验证的误差估计靠它压方差,10 次是下限。
定完成分数再建模和评估:
m <- plsda(X, Y, ncomp = 3) perf_m <- perf(m, validation = "Mfold", folds = 5, nrepeat = 20, dist = "max.dist", progressBar = FALSE) perf_m$error.rate plotIndiv(m, comp = c(1, 2), ind.group = TRUE, ellipse = TRUE)perf_m$error.rate给出的是各成分数下的错误率,注意里面有overall和各类别两档,样本不平衡时只看overall会被多数类带偏,必须逐类看召回率。plotIndiv的ellipse = TRUE画的是置信椭圆,椭圆重叠说明这两个成分分不开,别硬解释。
多分类场景下mixOmics的 one-hot 编码和判别规则是自动处理的,两类时它和ropls的结论通常一致,差别在图的样式和附加指标。选哪个更多是习惯问题:要写方法学部分、需要完整的置换检验和诊断指标,走ropls;要做多组学整合或者多分类比较,走mixOmics。同一个项目里两条路线都跑一遍、看结论是否一致,是我对模型稳定性的最低要求。
4.4 独立测试集与重复双交叉验证:别用训练集准确率交差
训练集上 95% 的准确率在高维组学数据里毫无意义。评估必须至少做一层留出,理想的是嵌套交叉验证:外层留出测试集,内层交叉验证定成分数。
set.seed(7) ho <- sample(nrow(X), round(0.3 * nrow(X))) Xtr <- X[-ho, ]; Ytr <- Y[-ho] Xte <- X[ho, ]; Yte <- Y[ho] m_ho <- plsda(Xtr, Ytr, ncomp = 2) pred_cls <- predict(m_ho, Xte)$MajorityVote$max.dist[, 2] mean(pred_cls == Yte) # 留出集准确率 table(Predicted = pred_cls, Actual = Yte) # 混淆矩阵,逐类看predict返回的MajorityVote$max.dist是一个「样本 × 成分数」的矩阵,[, 2]取ncomp = 2那一列,列索引必须和建模时的成分数对上——取错列是很隐蔽的错误,准确率会莫名其妙地低一截。
混淆矩阵比总体准确率重要得多。两类各 100 个样本,全判成多数类也能有 50% 的准确率,而混淆矩阵一眼就能看出模型根本没学到少数类。报告里至少给三个数:总体准确率、每类召回率、AUC。样本量允许的话,重复 20 次随机留出、把准确率的分布一起报出来,比单个数字有说服力。
5. 避坑排查:R语言安装包跑 PLS 最常见的五次翻车
5.1 装包报「依赖不可用」,但明明把包装进了仓库
现象:install.packages("ropls", ...)返回dependency 'XXX' is not available,去仓库目录里翻,那个包的文件确实在。
原因:仓库索引PACKAGES文件没生成或者过期。available.packages读的是索引不是文件系统,索引里没有的包,install.packages就当它不存在。离线仓库从别处拷贝过来、或者新加了包没重建索引,都会踩这一条。
解决:在仓库根目录重建索引后再装。
# CRAN 结构:对 contrib 目录重建;Bioconductor 结构同理 tools::write_PACKAGES("D:/R-offline/cran/bin/windows/contrib/4.3", type = "win.binary")重建完先用nrow(available.packages(...))确认索引里能数出包来,再执行安装。后面我养成了习惯:任何离线仓库接手,第一步先重建索引,再做依赖闭包差集。
5.2 包装完重启就消失,或者报「无法移动临时安装」
现象:当前会话library(pls)正常,重启 R 就提示没有这个包;或者安装过程中途报unable to move temporary installation。
原因有两个,往往同时出现。一是包被装进了临时目录或某个没写进.libPaths的位置,会话结束就失效;二是包目录落在同步盘或被其他进程占用的目录里,安装最后一步的「临时目录改名」被拦下。
解决:路径固定加写成永久配置。
# 固定库路径,写进用户级 .Rprofile,每次启动自动生效 prof <- path.expand("~/.Rprofile") write('.libPaths(c("D:/Rlibs/4.3", .libPaths()))', file = prof, append = TRUE) # 装完立刻验证包确实落在预期目录 find.package("pls")find.package返回的路径必须以你设的D:/Rlibs/4.3开头,不是的话说明上面某一步没生效。同步盘的问题没法靠代码绕,只能换目录。
5.3 数据里混进因子列,plsr 直接报错
现象:plsr报invalid type (list),或者底层报NA/NaN/Inf in foreign function call,数据看着干干净净。
原因:从 CSV 读进来的表里带了样本名、批次、组别这些非数值列,或者某一列因为有个"ND"字符串被整体读成了字符型。plsr只吃数值矩阵,遇到因子列的错误信息完全指不到真正的列。
解决:读入后先做数值化清洗,把元数据单独摘出去。
raw <- read.csv("data/spectra.csv", check.names = FALSE, row.names = 1) num_cols <- vapply(raw, is.numeric, logical(1)) X <- as.matrix(raw[, num_cols, drop = FALSE]) meta <- raw[, !num_cols, drop = FALSE] # 分组、批次留在这里 storage.mode(X) <- "double" sum(!is.finite(X)) # 必须是 0,非 0 就说明有 NA / Inf 混进来了check.names = FALSE保住原始列名,光谱数据的列名常是波长数值,被自动改写成X400之类以后就对不上了。storage.mode强制成双精度,比as.numeric逐列转换省事。sum(!is.finite(X))这一行是我每次读数据必跑的自检,比等到建模时报错省时间。
5.4 ncomp 一路调大,RMSEP 几乎单调下降
现象:交叉验证曲线一路往下走,看不到拐点,训练集 R² 逼近 1,换到测试集误差大得离谱。
原因:要么validation设成了"none"却拿训练残差当评估,要么交叉验证的分段方式让相邻样品泄漏了——同一批样品做了多次测量,随机分段把它们分到了训练折和验证折,模型等于提前看过答案。
解决:改分段策略,并用独立测试集做最终裁决。
# 有重复测量或采集顺序时,按样品来源分组分段更保守 m_cv2 <- plsr(Xtr, Ytr, ncomp = 15, scale = TRUE, validation = "CV", segments = 10, segment.type = "consecutive", # 或按批次手工构造 segments jackknife = TRUE) selectNcomp(m_cv2, method = "onesigma", plot = TRUE)segment.type = "consecutive"把连续样品成段留出,能挡住一部分泄漏。更严格的做法是自己构造segments参数,把同一来源的样品强制放进同一折。判据很简单:曲线必须有拐点,没有拐点就说明评估环节有问题,不是成分数的问题。
5.5 置换检验 p 值漂亮得不像真的
现象:permI = 200跑完,pR2Y和pQ2都小于 0.01,但Q2Y只有 0.2 出头,两者对不上。
原因:置换次数太少导致 p 值分辨率不够(200 次能报出的最小值就是 0.005 量级);分组严重不平衡时,置换后的零分布被多数类主导;或者置换时没有保持分层,把本来相关的结构打散了。
解决:加置换次数、做分层置换、换更能反映不平衡的指标。
mod2 <- opls(X, Y, predI = 2, orthoI = 0, crossvalI = 7, permI = 1000, # 提到 1000 次,p 值分辨率到 0.001 scaleC = "standard") mod2同时把混淆矩阵和 AUC 一起报,不要只给一个 p 值。Q2Y低但 p 值极小,通常说明模型确实抓到了一点点真实信号,但这点信号不足以做预测,结论要写「存在统计关联,预测能力有限」,不能写成「模型有效」。
6. 进阶:把一次 PLS分析 固化成可复现的脚本骨架
临时在控制台里敲出来的模型,过两周自己都复现不了。真正省事的做法是把「读取 → 切分 → 定阶 → 评估 → 落盘」封成一个函数,参数全部显式化
本文还有配套的精品资源,点击获取