R空间机器学习工作流:数据-模型-制图无缝整合

R空间机器学习工作流:数据-模型-制图无缝整合
1. 为什么用 R 做空间机器学习这不是“选语言”而是“选工作流”你手头有一份县级土地利用矢量图层叠加了近十年的遥感影像栅格数据、气象站点观测记录、土壤采样点位和农户调查问卷——这些数据天然带着坐标、投影、时间戳和空间依赖性。这时候打开 Python 写sklearn你会发现读一个.shp文件得先调geopandas再处理 CRS 不一致得查pyproj文档做空间自相关检验得额外装pysal最后画一张带底图的分类结果图又得切回contextily或cartopy……整个过程像在不同工具箱之间来回翻找螺丝刀、扳手和游标卡尺。而 R 的空间机器学习工作流是把“空间”这件事从底层就焊死在语法里。不是“加个空间插件”而是“空间就是默认状态”。sf包读.shp只需一行st_read(landuse.shp)它自动识别几何列、坐标系、拓扑关系raster加载.tif影像后所有像元值天然绑定地理坐标做空间裁剪、重采样、掩膜提取命令直白得像说人话crop(raster_obj, extent_obj)、resample(raster_obj, target_raster)、mask(raster_obj, sf_polygon)。更关键的是当你调用randomForest::randomForest()或e1071::svm()时输入数据框里可以同时包含数值型变量如坡度、NDVI、因子型变量如土地类型、甚至空间对象本身如st_centroid()得到的点坐标——模型训练阶段不排斥空间结构预测阶段又能直接把结果写回原始空间框架。这种“数据—模型—空间可视化”三者无缝咬合的工作流在 GIS 场景下省掉的不是几行代码而是反复校验坐标系、重投影、属性表对齐、图层叠加验证的数小时调试时间。我去年帮一个农业遥感团队重构作物病害预警模型他们原用 Python 处理 Sentinel-2 时间序列再导出 CSV 给 R 做随机森林分类。光是确保每个像素点的时间序列 CSV 行号与原始栅格行列号严格对应就花了三天排查索引错位问题。改用 R 全流程后用stars包直接加载多维 NetCDF 数据st_apply()沿时间维度计算植被指数变化率结果仍是stars对象直接喂给caret::train()预测结果用st_as_sf()转成面要素ggplot2 geom_sf()一键出图。整个 pipeline 从 17 个脚本文件压缩成 1 个 R Markdown 文档复现时间从 45 分钟缩短到 6 分钟。这不是语言优劣之争而是工作流是否尊重空间数据的物理本质——R 的生态从第一天起就把“地理坐标”当作和“数值”“字符”同等基础的数据类型来对待。2. 空间机器学习的核心设计逻辑三重解耦与一次整合很多人初学 R 空间分析会陷入一个典型误区把 GIS 软件的操作习惯直接平移过来比如“先在 QGIS 里做缓冲区分析再导出新图层最后在 R 里建模”。这完全背离了 R 空间机器学习的设计哲学。真正的高效路径是遵循“数据解耦—模型解耦—空间解耦最终在预测环节强制整合”的四步逻辑。下面拆解这个思路为何成立以及每一步具体怎么操作。2.1 数据解耦把“空间位置”和“属性特征”暂时分开但绝不丢弃空间数据的特殊性在于每个观测点点、线、面既携带属性信息如土壤 pH 值、人口密度又携带空间信息经纬度、邻接关系、距离矩阵。传统统计建模常把空间坐标当普通变量输入导致模型误判空间自相关为噪声。R 的解法是用sf或sp对象承载空间结构用标准data.frame承载属性特征二者通过行名或唯一 ID 严格绑定。举个实际例子你要预测某市社区犯罪率。原始数据是communities.sf含社区多边形几何和communities_df含失业率、教育水平、警力配置等字段。正确做法不是cbind(communities.sf, communities_df)而是# 确保两个对象行顺序和 ID 完全一致 communities_sf - st_set_geometry(communities_sf, geometry) communities_df$id - rownames(communities_df) # 或用真实 ID 字段 communities_sf$id - communities_sf$community_id # 假设原始字段叫 community_id # 创建空间-属性联合对象非简单合并而是建立引用关系 communities_full - communities_sf %% dplyr::left_join(communities_df, by c(id id)) %% st_drop_geometry() # 此时得到纯属性数据框但行序与 sf 对象严格对应这样做的好处是后续建模时你可以用communities_full喂给caret::train()模型只看到数值/因子变量而预测完成后用st_cast()或st_as_sf()把预测结果按行序“贴回”原始communities_sf的几何上空间结构毫发无损。我试过直接cbind()几何列进数据框结果randomForest报错“cannot handle objects of type ‘sfc_MULTIPOLYGON’”因为模型根本不懂如何解析 WKT 字符串。解耦不是增加步骤而是让每个环节各司其职——空间对象管空间数据框管统计R 的管道操作符%%就是它们之间的胶水。2.2 模型解耦不追求“一个包打天下”而要“按任务选最锋利的刀”R 生态里没有“万能空间机器学习包”只有针对不同任务高度优化的专用工具。强行用mlr3包裹所有算法反而掩盖了各算法的空间适配特性。我的经验是把模型选择锚定在三个核心问题上——目标变量类型连续/离散/空间计数、数据规模万级/百万级/超大栅格、空间效应强度弱自相关/强聚集性/长距离依赖。决策树与随机森林首选ranger包而非randomForest。ranger默认支持sf对象输入且用 C 实现处理百万级点数据如手机信令轨迹时速度提升 5-8 倍。关键参数num.trees和mtry需结合空间尺度调整若预测单元是 1km×1km 网格mtry设为sqrt(ncol(predictors))即可但若预测单元是行政村大小差异极大则需用tuneRF()在ranger中交叉验证因为小村样本少过深的树易过拟合。支持向量机SVMe1071::svm()适合中小规模10 万样本的二分类问题如耕地/非耕地识别。但必须注意空间数据的坐标X,Y不能直接作为特征输入 SVM否则模型会把“靠近市中心”错误学习为“更可能是商业用地”而忽略真实的光谱特征。正确做法是用sf计算每个点到最近道路、河流、建成区的距离生成dist_to_road,dist_to_river等衍生特征再把这些距离变量喂给 SVM。我实测过加入 3 个空间距离特征后耕地分类 Kappa 系数从 0.62 提升到 0.79。梯度提升机GBMgbm包对空间异常值鲁棒性强特别适合处理遥感影像中的云污染像元或 GPS 漂移点。但gbm默认不支持空间权重需手动集成spdep包计算空间滞后变量。例如先用spdep::poly2nb(communities.sf)构建邻接矩阵再用spdep::lag.listw()计算每个社区的邻居平均失业率把这个“空间滞后失业率”作为新特征加入 GBM 模型。这比单纯用xgboost更能捕捉“高失业社区扎堆出现”的空间聚集模式。提示永远不要在未检查空间自相关前就跑模型。用spdep::moran.test()对残差做莫兰指数检验若 p0.05说明模型没捕获空间依赖必须引入空间滞后项或改用spatstat的点过程模型。2.3 空间解耦把“空间预测”和“空间制图”分离避免模型被地图绑架新手常犯的错误是一拿到预测结果立刻用ggplot2::geom_sf()画热力图然后盯着颜色深浅调参。这会导致严重的“视觉引导偏差”——你优化的不是模型精度而是地图美观度。R 的正解是预测阶段输出纯数值结果向量或栅格制图阶段用独立的sf/raster工具链渲染二者完全解耦。例如用raster::predict()对整幅 Landsat 影像做土地覆盖分类# 模型已训练好名为 rf_model # 影像堆栈为 raster_stack含 B2-B7 波段 pred_raster - predict(raster_stack, rf_model, type classes) # 此时 pred_raster 是 rasterLayer 对象值为 1,2,3... 类别码 # 它不含任何颜色、图例、标题信息——纯粹的地理空间预测结果接下来制图用另一套逻辑# 定义颜色和图例 landuse_colors - c(#FF0000, #00FF00, #0000FF, #FFFF00) # 红绿蓝黄 landuse_labels - c(Urban, Forest, Water, Agriculture) # 渲染地图此时才引入视觉元素 ggplot() geom_raster(pred_raster, mapping aes(x x, y y, fill layer)) scale_fill_manual(values landuse_colors, labels landuse_labels) coord_sf(crs st_crs(pred_raster)) theme_minimal()这种分离带来的好处是你可以用同一份pred_raster快速生成三种不同用途的地图——给领导看的简洁版只留图例和比例尺、给技术部门看的误差分析版叠加验证点位和混淆矩阵、给公众看的交互版用mapview::mapview(pred_raster)。模型本身不为任何一种呈现方式妥协这才是科学分析该有的样子。3. 实操全流程从原始数据到可发布地图的 7 个硬核步骤下面以一个真实项目为例基于 2023 年 Sentinel-2 影像和地形数据预测云南省某县滑坡易发性等级低/中/高。我会带你走完从数据准备到成果交付的完整链条每一步都标注关键参数选择依据和避坑点。这不是理论推演而是我在云南山区实地踩坑后总结的“血泪清单”。3.1 步骤一空间数据标准化——解决“坐标系混乱”这个万恶之源原始数据来源五花八门Sentinel-2 L2A 产品用 WGS84 / UTM 48N县界矢量图是 CGCS2000 / Albers 等积圆锥投影DEM 数据是 WGS84 / Geographic经纬度而气象站坐标是纸质地图转绘的精度存疑。第一步不是建模而是让所有数据在同一个空间参考系下“站队”。# 1. 读取所有数据 sentinel_raster - raster(S2_20230615_B04_B08.tif) # 红外波段组合 county_boundary - st_read(yunnan_county.shp) dem_raster - raster(dem_30m.tif) weather_stations - st_read(stations.shp) # 2. 统一到 WGS84 / UTM 48N该区域最适投影 # 注意raster::projectRaster() 会重采样必须指定 methodbilinear连续变量或 ngb类别变量 sentinel_utm - projectRaster(sentinel_raster, crs st_crs(county_boundary)) dem_utm - projectRaster(dem_raster, crs st_crs(county_boundary), method bilinear) # 对于点数据气象站用 st_transform() 更精准 weather_utm - st_transform(weather_stations, crs st_crs(county_boundary)) # 3. 关键避坑检查投影后几何是否变形 # 用 st_area() 计算县界面积若投影前后相差 5%说明投影参数错误 original_area - st_area(county_boundary) projected_area - st_area(st_transform(county_boundary, crs st_crs(county_boundary))) if (abs(as.numeric(original_area - projected_area) / as.numeric(original_area)) 0.05) { stop(投影参数错误请检查 st_crs() 输入的 EPSG 代码) }实操心得我曾因 DEM 重采样用错methodngb最近邻法导致坡度计算出现阶梯状伪影模型把“伪影边缘”误判为滑坡陡坎Kappa 系数虚高 0.15。记住连续变量高程、温度、NDVI必须用bilinear或cubic离散变量土地类型、行政区划必须用ngb。3.2 步骤二空间特征工程——从坐标中榨取地理智慧滑坡易发性不仅取决于坡度、坡向更取决于“空间上下文”。R 的优势在于用几行代码就能生成人类专家需要数小时手工勾绘的空间特征。# 1. 基础地形特征从 DEM 直接计算 slope_raster - terrain(dem_utm, opt slope, unit degrees) aspect_raster - terrain(dem_utm, opt aspect) curvature_raster - terrain(dem_utm, opt curvature) # 2. 空间上下文特征R 的杀手锏 # a) 到主要河流的距离用 raster::distance() rivers_sf - st_read(rivers.shp) %% st_transform(st_crs(dem_utm)) rivers_raster - rasterize(rivers_sf, dem_utm, field 1) dist_to_river - distance(rivers_raster) # b) 到断层线的距离同上但需先缓冲 faults_sf - st_read(faults.shp) %% st_transform(st_crs(dem_utm)) faults_buffer - st_buffer(faults_sf, dist 1000) # 缓冲 1km faults_raster - rasterize(faults_buffer, dem_utm, field 1) dist_to_fault - distance(faults_raster) # c) 局部地形湿度指数TWI——滑坡核心指标 # TWI ln(As / tanβ)As 为上游汇水面积β 为坡度 # 用 whiteboxtools::wbt_flow_accumulation_full_workflow() 最准但需安装 WhiteboxTools # 替代方案用 terra::terrain() 计算坡度后用 focal() 估算汇水 twi_raster - terrain(dem_utm, opt TPI) # TPI地形位置指数可替代 TWI 简化计算 # 3. 合并所有特征为预测堆栈 predictors_stack - stack(slope_raster, aspect_raster, curvature_raster, dist_to_river, dist_to_fault, twi_raster)实操心得raster::distance()计算的是欧氏距离但在山区直线距离意义不大。我后来改用gdistance::transition()构建成本距离面把坡度、岩性作为通行成本再算“最小成本距离”模型 AUC 从 0.78 提升到 0.86。但计算耗时增加 3 倍需权衡精度与效率。3.3 步骤三样本采集与空间平衡——破解“训练样本偏倚”魔咒滑坡点位数据极度不平衡已发生的滑坡点仅 200 个而全县无滑坡区域超百万像素。若直接随机采样负样本模型会把“所有地方都不是滑坡”作为最优解。R 的解法是用空间分层抽样spatialsample 距离约束distance buffer强制平衡。# 1. 获取已知滑坡点shp 格式 landslides_sf - st_read(landslides.shp) %% st_transform(st_crs(dem_utm)) # 2. 用 spatialsample::stratified_sample() 按坡度、岩性分层 # 先将 DEM 和岩性图转为分类栅格 slope_class - reclassify(slope_raster, rcl matrix(c(0,15,1, 15,30,2, 30,90,3), ncol3, byrowTRUE)) litho_class - raster(lithology.tif) # 岩性图 # 合并分层变量 strata_raster - slope_class * 10 litho_class # 生成唯一分层码 # 分层抽样每层抽 50 个负样本且距滑坡点 500m negative_samples - spatialsample::stratified_sample( strata strata_raster, n 50, buffer 500, # 距离滑坡点最小缓冲 avoid landslides_sf # 避开滑坡点 ) # 3. 合并正负样本提取特征值 all_samples - rbind( st_coordinates(landslides_sf) %% as.data.frame() %% mutate(label High), st_coordinates(negative_samples) %% as.data.frame() %% mutate(label Low) ) # 提取所有预测变量在样本点的值 sample_values - extract(predictors_stack, all_samples[,1:2]) sample_df - cbind(all_samples, sample_values) sample_df$label - factor(sample_df$label, levels c(Low, High))实操心得spatialsample包的buffer参数极易误解。它不是“在缓冲区内抽样”而是“抽样点必须在缓冲区外”。我第一次用buffer 100结果抽到的负样本全在滑坡点 100 米内模型学到了“滑坡点周围 100 米必是滑坡”F1 分数虚高到 0.92但外推到新区域完全失效。务必用plot(negative_samples)叠加滑坡点验证空间分布。3.4 步骤四模型训练与空间交叉验证——告别“随机 K 折”的地理灾难传统caret::trainControl(method cv)的随机划分会把相邻像素分到不同折导致模型在训练时“偷看”了测试集的空间邻近信息严重高估性能。R 的正解是用spatialsample::spatial_block_cv()进行空间区块交叉验证。# 1. 创建空间区块划分类似棋盘格 cv_folds - spatialsample::spatial_block_cv( data sample_df, v 5, # 5 折 block_size 2000 # 区块边长 2km确保训练块与测试块不相邻 ) # 2. 定义训练控制参数 ctrl - trainControl( method cv, index cv_folds, # 用空间划分替代随机划分 classProbs TRUE, summaryFunction twoClassSummary ) # 3. 训练随机森林模型 rf_model - train( label ~ . - X - Y, # 排除坐标列X,Y 是样本点坐标非特征 data sample_df, method ranger, trControl ctrl, tuneGrid expand.grid(mtry c(3,5,7), splitrule gini, min.node.size 1), metric ROC ) # 4. 查看空间交叉验证结果 print(rf_model) # 输出中重点关注 Resampling results across tuning parameters: # 若 ROC 值在各 fold 间波动 0.03说明模型稳定若某 fold ROC 低 0.1 以上说明该区域地质条件特殊需单独建模实操心得spatial_block_cv()的block_size参数必须大于空间自相关尺度。我用spdep::moran.plot()计算滑坡点的莫兰指数发现空间自相关范围约 1.2km所以block_size设为 2000。若设为 500测试块与训练块仍有空间重叠CV 结果仍会乐观。3.5 步骤五全区域预测与不确定性量化——不止输出“一张图”更要输出“可信度”很多教程停在predict()输出分类图但这对决策者毫无价值。真正有用的是每个像素的预测概率 模型不确定性 空间一致性检验。# 1. 全区域预测输出概率 pred_prob - predict(rf_model, newdata as.data.frame(predictors_stack), type prob) # 2. 计算预测不确定性用 ranger 的 out-of-bag 误差 # ranger 模型自带 oob.error但需提取 rf_model$finalModel$oob.error # 查看整体 OOB 误差 # 3. 空间一致性检验用 focal() 计算每个像素周围 3x3 窗口的预测类别一致性 # 若周围 8 个像素中 6 个以上与中心像素同类别则标记为“高一致性” pred_class - predict(rf_model, newdata as.data.frame(predictors_stack), type raw) pred_majority - focal(pred_class, w matrix(1,3,3), fun function(x) names(which.max(table(x)))) # 4. 合并结果为多波段栅格 result_stack - stack( pred_class, # 概率栅格多层 pred_majority, # 主类别栅格 focal(pred_class, w matrix(1,3,3), fun function(x) sd(x)) # 不确定性标准差 )实操心得focal()计算标准差时若窗口内有 NA如影像边缘默认返回 NA。必须加na.rm TRUE参数否则整条边缘失效。我因此丢失了 15% 的有效预测区重跑耗时 2 小时。教训所有focal()操作前先用is.na()检查 NA 分布。3.6 步骤六空间后处理与制图——用 ggplot2 实现出版级地图R 的制图能力常被低估。ggplot2 geom_sf()不仅能出图更能实现 GIS 软件难以完成的“统计-空间融合可视化”。# 1. 将预测结果转为 sf 对象用于叠加行政边界 pred_sf - rasterToPolygons(pred_majority, dissolve TRUE) %% st_as_sf() %% st_transform(st_crs(county_boundary)) # 2. 制作三联图预测结果 验证点 不确定性热力图 p1 - ggplot() geom_sf(data county_boundary, fill transparent, color black, size 0.3) geom_sf(data pred_sf, aes(fill layer), alpha 0.7) scale_fill_viridis_d(option plasma, begin 0.2, end 0.8) labs(title 滑坡易发性预测结果, fill 易发等级) theme_minimal() p2 - ggplot() geom_sf(data county_boundary, fill transparent, color black, size 0.3) geom_sf(data landslides_sf, color red, size 2, shape 16) geom_sf(data negative_samples, color blue, size 1, shape 1) labs(title 验证样本分布, color 样本类型) theme_minimal() p3 - ggplot() geom_raster(result_stack[[3]], mapping aes(x x, y y, fill layer)) scale_fill_viridis_c(option magma, na.value white) labs(title 预测不确定性标准差, fill 不确定性) theme_minimal() # 3. 用 patchwork 组合三图 library(patchwork) (p1 | p2) / p3 plot_layout(heights c(1, 0.5))实操心得rasterToPolygons()的dissolve TRUE参数是双刃剑。溶解后图斑数量锐减制图快但会丢失细小滑坡体的轮廓。我最终采用dissolve FALSE再用sf::st_simplify(pred_sf, dTolerance 50)简化几何既保持细节又控制文件大小。简化容差 50 米是根据 Sentinel-2 10 米分辨率设定的合理值。3.7 步骤七成果交付与复现保障——让同事 5 分钟跑通你的全部流程最后一步常被忽略却是专业性的分水岭。一份可交付的 R 空间机器学习成果必须包含可一键运行的 R Markdown 文档用knitr::opts_chunk$set(echo TRUE, eval TRUE)确保所有代码块可见可执行数据字典data dictionary用rio::export()导出sample_df为 Excel每列注明来源、单位、取值范围环境锁定文件用renv::snapshot()生成renv.lock确保同事用renv::restore()即可复现完全相同的包版本最小数据集提供 1% 的抽样数据sample_df[1:100, ]和 100×100 像素的测试影像供快速验证流程。我曾把一个完整的滑坡预测项目打包同事在新电脑上git clone后执行renv::restore()rmarkdown::render(report.Rmd)5 分钟内生成 PDF 报告和交互地图。而用 Python 实现同样流程他花了两天配置 GDAL、PROJ、rasterio 版本冲突。4. 常见问题与排查技巧实录那些文档里不会写的“脏活”以下是我过去三年在 12 个空间机器学习项目中遇到频率最高、最让人抓狂的 7 类问题。每个问题都附带真实报错、定位方法和一招毙命的解决方案。这些不是教科书答案而是深夜调试时记在烟盒背面的救命笔记。4.1 问题一“Error in .local(x, y, ...) : cannot mix matrices and Spatial* objects”场景想用spatstat::ppp()创建点过程模型把sf对象的坐标传进去报此错。原因spatstat是老派空间包只认sp包的SpatialPointsDataFrame不认sf的sfc_POINT。sf对象的坐标是sfc列spatstat当成矩阵处理。排查class(landslides_sf) # 看到 sf data.frame class(landslides_sf$geometry) # 看到 sfc_POINT sfc # 而 spatstat 需要的是 coordinates(landslides_sp) # 返回 numeric matrix解决用as_Spatial()强制转换而非as(sf)library(sp) landslides_sp - as_Spatial(landslides_sf) # 正确 # 或更稳妥提取坐标再构建 coords - st_coordinates(landslides_sf) landslides_ppp - ppp(x coords[,1], y coords[,2], window owin(poly st_coordinates(county_boundary)))实操心得as_Spatial()会丢失sf的 CRS 信息务必在转换后手动设置proj4string(landslides_sp) - proj4string(county_sp)。我因此在点过程模型中用了 WGS84 坐标算欧氏距离结果所有距离值都是经纬度差单位是“度”而非“米”整整 debug 了 6 小时。4.2 问题二“Error in checkForRemoteErrors(val) : 7 nodes produced errors”场景用ranger::ranger()训练模型时开启num.threads 1集群报错。原因ranger的多线程依赖OpenMP而某些 Linux 服务器尤其 HPC 集群的libgomp版本与 R 编译时链接的不一致。7 个节点报错恰好对应num.threads 7。排查# 在服务器终端执行 ldd /path/to/R/lib/libR.so | grep gomp # 查看 R 链接的 libgomp 路径 ls /usr/lib64/libgomp* # 查看系统实际 libgomp # 若路径不同版本不匹配解决禁用多线程用future包做显式并行library(future) plan(multisession, workers 4) # 显式指定 worker 数 rf_model - future.apply::future_lapply( list_of_folds, # 手动分 fold function(fold_data) ranger::ranger(..., num.threads 1) )实操心得ranger的num.threads在 Windows 上常失效因为 Windows 的 OpenMP 实现不一致。统一用future方案跨平台稳定。另外num.threads 0并非“自动选择”而是“单线程”文档没写清楚害我浪费半天。4.3 问题三“Warning: CRS is not set. Using default CRS: projlonglat datumWGS84”场景用raster::raster()读 GeoTIFF后续crop()报错 “different extent”。原因GeoTIFF 的 CRS 信息存储在 GDAL 元数据中raster包有时读取失败尤其当 TIFF 是用 ENVI 或 ERDAS 生成时。raster对象的crs()返回空但extent()有值导致空间运算崩溃。排查r - raster(data.tif) crs(r) # 返回 character(0) projection(r) # 返回 # 但 gdalinfo data.tif 在终端显示正确 CRS解决强制赋值 CRS并用raster::projection()验证# 从 gdalinfo 输出复制 PROJ.4 字符串 crs_string - projutm zone48 datumWGS84 unitsm no_defs crs(r) - crs_string # 验证 projection(r) # 应返回 projutm zone48 datumWGS84 unitsm no_defs # 若仍为空用 rgdal::readGDAL() 重读 library(rgdal) r2 - readGDAL(data.tif) r - raster(r2)实操心得crs(r) - crs_string后必须用projection(r)检查是否生效。有些旧版raster包赋值后crs(r)显示正确但projection(r)仍为空此时crop()必然失败。这是raster包的著名 bug升级到terra包可根治。4.4 问题四“Error in predict.randomForest() : object has no forest component”场景用caret::train()训练method rf保存模型后load()predict()报此错。原因caret默认使用randomForest包但train()的savePredictions TRUE选项会删掉模型的forest组件以节省内存。predict.randomForest()需要forest组件做新样本预测。排查str(rf_model$finalModel) # 查看是否有 $forest 列表 # 若无则被删了解决训练时禁用savePredictions或改用ranger# 方案一禁用 rf_model - train( label ~ ., data sample_df, method rf, trControl ctrl, tuneGrid grid, savePredictions FALSE # 关键 ) # 方案二用 ranger推荐内存友好且保留所有组件 rf_model - train( label ~ ., data sample_df, method ranger, trControl ctrl, tuneGrid expand