ランダムフォレストにおける変数の重要性
ランダムフォレストでの変数の重要度分析
伝統的な方法と新しい展開
ランダムフォレストとその一般化(特に一般化ランダムフォレスト(GRF)および分散的ランダムフォレスト(DRF))は、どのデータサイエンティストのツールボックスにも欠かせない強力で使いやすい機械学習手法です。これらはだけでなく、チューニングの必要なしに幅広いデータセットで堅牢なパフォーマンスを示すだけでなく、欠損値に対応し、さらには信頼区間を提供することもできます。この記事では、これらが提供できる別の機能に焦点を当てます:特徴の重要性の概念。具体的には以下の点に焦点を当てます:
- 従来のランダムフォレスト(RF)、これはp個の予測変数Xを与えられた条件付き変数Yの予測を行うために使用されます。
- 分散的ランダムフォレスト、これはp個の予測変数Xを与えられたd次元変数Yの全条件付き分布を予測するために使用されます。
残念ながら、これらのフォレストも含めて多くの現代の機械学習手法は解釈性に欠けます。つまり、操作が多すぎるため、予測変数とYの実際の関連性を特定することは不可能に思えます。この問題に対処する一般的な方法は、変数の重要性尺度(VIMP)を定義することです。これにより、少なくともどの予測変数が重要かを判断するのに役立ちます。一般的に、これには2つの異なる目的があります:
(1) 最大の精度を持つ少数の変数を見つけること
(2) 追加の探索対象とするために影響を与える変数を検出およびランク付けすること
(1)と(2)の違いは、Xの要素間に依存関係(つまり、ほとんどいつも)がある場合に重要です。たとえば、2つの変数が相互に高く相関しており、かつYとも高く相関している場合、目的(1)ではどちらかの変数を削除しても精度に影響がありません。なぜなら、両方の変数が同じ情報を伝えるからです。しかし、目的(2)では両方を含めるべきです。なぜなら、これら2つの変数は、ドメインの専門家にとっては実際には異なる意味を持つかもしれないからです。
今日は、目的(1)に焦点を当て、予測精度がほぼ同じであるより少ない予測子の数を見つけようとします。たとえば、以下の賃金の例では、79個の予測子を20個ほどに減らすことができ、精度のわずかな減少しかありません。これらの最重要予測子には、賃金に影響を与えることがよく知られている年齢や教育などの変数が含まれています。また、この(2)に関するVoAGIの多くの素晴らしい記事があり、シャプリー値を使用したこちらの記事やこちらの記事などもあります。また、ランダムフォレストでシャプリー値を効率的に計算するための非常に最近のエキサイティングな学術文献もありますが、これについては別の記事の材料です。
今日私たちが見る2つの尺度は、実際には任意の手法に使用できるより一般的な変数の重要性尺度であり、以下で説明するドロップ
始まり
RFの可変重要度指標は、RF自体と同じくらい古いものである。最初の精度であるMean Decrease Accuracy(MDA)は、Breimanによって彼の画期的なランダムフォレスト論文[1]で提案された。アイデアはシンプルである:ある次元j=1,…,pごとに、全予測の精度と、X_jをランダムに置換した場合の予測の精度を比較する。これは、X_jとYの関係を断ち切り、X_jがデザイン上Yの予測に役立たない場合の精度と、役立つ可能性がある場合の精度を比較するためのものである。
MDAのさまざまなバージョンがRおよびPythonで実装されている:
残念ながら、この方法で変数X_jを置換すると、Yだけでなく他の変数との関係も断たれてしまう。これは、X_jが他の変数と独立している場合には問題ではないが、依存関係がある場合には問題となる。そのため、[3]では、Xに依存関係があるとすぐにMDAは意味のないものに収束することを示している。特に、MDAは、Yを予測するために重要ではない変数X_jに高い重要度を与えることがあり、実際にはYを予測するために重要な別の変数X_lと高い相関関係がある場合(以下の例で示される)でも、重要な変数を検出できないことがある。同時に、[3, 第2.1節]の一連の論文で示されているように、実際に関連性のある変数を検出できないことがある。直感的には、X_jが含まれていない場合のモデルのパフォーマンスを測定し、代わりに置換されたX_j変数のパフォーマンスを測定したいと考える。
2つ目の伝統的な精度指標はMean Decrease Impurity(MDI)であり、与えられた共変量で分割するすべてのノードでの純度の減少を重み付けして、森のすべてのツリーで平均します。残念ながら、MDIは最初から定義が曖昧である(何を測定すべきか明確ではない)という問題があり、このアプローチの実用上の問題をいくつかの論文が強調しています(例:[5])。そのため、MDAがしばしば選択される傾向にあるため、MDIについて詳しく説明しません。
現代の進展 I: Sobol-MDA
長い間、これらの何種類かの非公式な指標が私たちのできる限りの最善策だと思っていました。最近出版されたある論文がそれを変えました。この論文では、上述の人気のある指標が実際にはかなり欠陥があり、私たちが測定したいものを測定していないことが理論的に示されています。では、実際に測定したいものは何でしょうか?1つの可能な答えは以下です:Sobol指数(元々計算機科学の文献で提案されたもの):
これを具体化しましょう。まず、tau(X)=E[ Y | X]は、推定したい条件付き期待値関数です。これは、ランダムなXの関数であるため、確率変数です。そのため、X^{(-j)}は、j番目の共変量を除外したp-1ベクトルです。したがって、ST^{(j)}は、j番目の出力変数を除外すると、出力説明分散の減少です。
上記は、より伝統的な方法で測定する方法です。ただし、私にとっては、以下のように表記する方が直感的です:
ここで、dは2つのランダムベクトル間の距離であり、上記のST^{(j)}に対しては、この距離は通常のユークリッド距離です。したがって、ST^{(j)}の上部は、私たちが求めたいもの(tau(X))と、変数jを除いた場合の取得結果との平均二乗距離を単純に測定しています。
質問は、これを効率的に推定する方法です。直感的なドロップアンドリアンプリンシプル(記号法:X)を使用するだけで十分であることがわかりました。RFを使用してtau(X)を推定し、X_jをドロップしてRFを再フィッティングしてtau(X^{(-j)})の推定値を得ると、一貫性のある推定量が得られます:
ここでtau_n(X_i)はp個の予測子を使用したテストポイントX_iのRF推定値であり、tau_n(X_i^{(-j)})はp-1個の予測子のみを使用した更新されたフォレストです。
ただし、これには森をp回再フィットする必要があるため、pが大きい場合には非常に効率的ではありません!そのため、[3]の著者は「ソボル-MDA」と呼ばれる方法を開発しました。フォレストを各回再フィットする代わりに、フォレストを1回だけフィットします。そして、テストポイントは同じフォレストにドロップされ、結果の予測値は「投影」されて(1)の測定値を形成します。つまり、X_jによる分割は単純に無視されます(目標はX_jなしの推定値を得ることを覚えておいてください)。著者らは、この投影アプローチを使用して(1)を計算した場合、一貫性のある推定量が得られることを示すことができます!これは本当に素晴らしいアイデアであり、アルゴリズムが高次元においても適用可能になります。
この方法は、非常に速いrangerパッケージに基づくRで実装されています。
Modern Developments II: MMDベースの感度指標
距離dを使用した定式化を見ると、より困難な問題に対して変数の重要性を測定するために異なる距離を使用することができるかという自然な疑問が生じます。そのような最近の例は、距離dとしてMMD距離を使用することです:
MMD距離は、ガウスカーネルなどのカーネルkを使用して、分布間の距離を比較的簡単に構築することができる素晴らしいツールです:
詳細は別の記事に譲ります。最も重要なポイントは、I^{(j)}が条件付き期待値よりも一般的なターゲットを考慮しているということです。I^{(j)}は、X_jがYの分布にいかなる方法で影響を与える場合に重要であると認識します。X_jがYの条件付き平均を変更せずに分散または分位数のみを変更し、重要であるとは限らない場合があります(以下の例を参照)。この場合、ソボル-MDAはX_jを重要と認識しませんが、MMD法は認識します。これは必ずしもMMD法が優れているわけではありませんが、単に異なるツールです。条件付き期待値の予測に興味がある場合は、ST^{(j)}が適切な測定値です。ただし、分布の他の側面、特に分位数を予測する場合は、I^{(j)}が適しています。再び、I^{(j)}はドロップアンドリアンプリンシプル(変数の$j$を除去してj=1,…,pの各々の場合にDRFを再フィットする)を使用して一貫して推定することができます。または、ソボル-MDAと同じ投影アプローチを使用することもできます。ドロップアンドリアンプリンシプルベースの実装は、この記事の末尾に添付されています。ここでは、この方法をMMD-MDAと呼んでいます。
シミュレーションデータ
この2つの現代的な指標を簡単なシミュレーション例で説明します:まず、GitlabからSobol-MDAパッケージをダウンロードしてインストールし、次にこの例で必要なすべてのパッケージをロードします。
library(kernlab)library(drf)library(Matrix)library(DescTools)library(mice)library(sobolMDA)source("compute_drf_vimp.R") ##このファイルの内容は以下で見つけることができますsource("evaluation.R") ##このファイルの内容は以下で見つけることができます
次に、このシンプルな例からシミュレーションを行います:X_1、X_2、X_4、…、X_10を(-1,1)の範囲で独立して一様に生成し、X_1とX_3の間に依存関係を作成します(X_3=X_1 + 一様誤差)。そして、Yを以下のようにシミュレーションします。
##平均値と標準偏差のシフトを経験するデータをシミュレーションする# Xx1 <- runif(n,-1,1)x2 <- runif(n,-1,1)X0 <- matrix(runif(7*n,-1,1), nrow=n, ncol=7)x3 <- x1+ runif(n,-1,1)X <- cbind(x1,x2, x3, X0)# 依存変数をシミュレートするYY <- as.matrix(rnorm(n,mean = 0.8*(x1 > 0), sd = 1 + 1*(x2 > 0)))colnames(X)<-paste0("X", 1:10)head(cbind(Y,X))
次に、Xを考慮したYの条件付き期待値を推定するためのSobol-MDA手法を分析します。
##条件付き期待値推定のための変数の重要性を分析するXY <- as.data.frame(cbind(Xfull, Y))colnames(XY) <- c(paste('X', 1:(ncol(XY)-1), sep=''), 'Y')num.trees <- 500forest <- sobolMDA::ranger(Y ~., data = XY, num.trees = num.trees, importance = 'sobolMDA')sobolMDA <- forest$variable.importancenames(sobolMDA) <- colnames(X)sort(sobolMDA, decreasing = T) X1 X8 X7 X6 X5 X9 0.062220958 0.021946135 0.016818860 0.016777223 -0.001290326 -0.001540919 X3 X10 X4 X2 -0.001578540 -0.007400854 -0.008299478 -0.020334150
見て分かるように、X_1が最も重要な変数であり、他の変数は同じくらい重要度が低い(重要ではない)です。これは、Yの条件付き期待値はX_1によってのみ変化するためです。重要なのは、X_1とX_3の間の依存関係にもかかわらず、この指標がこれを正しく認識していることです。したがって、この例では上述の目標(1)を成功裏に達成しました。一方で、従来のMDAにも目を向けることができます。
forest <- sobolMDA::ranger(Y ~., data = XY, num.trees = num.trees, importance = 'permutation')MDA <- forest$variable.importancenames(MDA) <- colnames(X)sort(MDA, decreasing = T) X1 X3 X6 X7 X8 X2 0.464516976 0.118147061 0.063969310 0.032741521 0.029004312 -0.004494380 X4 X9 X10 X5 -0.009977733 -0.011030996 -0.014281844 -0.018062544
この場合、X_1が最も重要な変数であることは正しく認識されていますが、X_3も非常に高い値で2番目に配置されています。これに対し、X_3はX_2、X_4、…、X_10と同じくらい重要でない変数です。
しかし、より一般的にYの分布を予測したい場合、たとえば分位数の推定のためにはどうでしょうか?この場合、X_2がYの条件付き分散に与える影響を認識できる尺度が必要です。ここでMMD変数重要度尺度が登場します:
MMDVimp <- compute_drf_vimp(X=X, Y=Y)sort(MMDVimp, decreasing = T) X2 X1 X10 X6 X8 X3 0.683315006 0.318517259 0.014066410 0.009904518 0.006859128 0.005529749 X7 X9 X4 X5 0.003476256 0.003290550 0.002417677 0.002036174
また、この尺度は正しく重要な要素を特定することができます。X_1とX_2が最も重要な変数であることを再度示しています。そして、X_1とX_3の間の相関関係にもかかわらず、これを行っています。興味深いことに、X_2からの分散シフトへの重要度がX_1からの期待値シフトよりも高くなっています。
実データ
最後に、変数重要度尺度を示すための実データの応用を紹介します。DRFでは、多変量のYを扱うことができますが、ここではもっと単純にするために一変量の設定に焦点を当て、アメリカ国勢調査局の2018年のアメリカコミュニティ調査からの米国の賃金データを考慮します。最初のDRF論文では、私たちは2018年のアメリカコミュニティ調査から約100万人の正社員のデータを取得し、給与情報と給与に関連するすべての共変量を抽出しました。このような豊富なデータは、DRFのような手法で実験するのに理想的です(実際、この分析ではごくわずかなサブセットのみを使用します)。ロードするデータはここで見つけることができます。
# データの読み込み(https://github.com/lorismichel/drf/blob/master/applications/wage_data/data/datasets/wage_benchmark.Rdata)load("wage_benchmark.Rdata")##訓練データの定義n<-1000Xtrain<-X[1:n,] Ytrain<-Y[1:n,]Xtrain<-cbind(Xtrain,Ytrain[,"male"])colnames(Xtrain)[ncol(Xtrain)]<-"male"Ytrain<-Ytrain[,1, drop=F]##テストデータの定義ntest<-2000Xtest<-X[(n+1):(n+ntest),] Ytest<-Y[(n+1):(n+ntest),]Xtest<-cbind(Xtest,Ytest[,"male"])colnames(Xtest)[ncol(Xtest)]<-"male"Ytest<-Ytest[,1, drop=F]
これで両方の変数重要度尺度を計算します(DRFではドロップアンドリアーン法のみが実装されているため、少し時間がかかります):
# 両方の尺度の変数重要度を計算する# 1. Sobol-MDAXY <- as.data.frame(cbind(Xtrain, Ytrain))colnames(XY) <- c(paste('X', 1:(ncol(XY)-1), sep=''), 'Y')num.trees <- 500forest <- sobolMDA::ranger(Y ~., data = XY, num.trees = num.trees, importance = 'sobolMDA')SobolMDA <- forest$variable.importancenames(SobolMDA) <- colnames(Xtrain)# 2. MMD-MDAMMDVimp <- compute_drf_vimp(X=Xtrain,Y=Ytrain,silent=T)print("条件付き期待値の推定における上位10の重要な変数")sort(SobolMDA, decreasing = T)[1:10]print("条件付き分布の推定における上位5つの重要な変数")sort(MMDVimp, decreasing = T)[1:10]
Sobol-MDA:education_level age male 0.073506769 0.027079349 0.013722756 occupation_11 occupation_43 industry_54 0.013550320 0.010025332 0.007744589 industry_44 occupation_23 occupation_15 0.006657918 0.005772662 0.004610835 marital_never married 0.004545964
MMD-MDA:education_level age male 0.420316085 0.109212519 0.027356393 occupation_43 occupation_11 marital_never married 0.016861954 0.014122583 0.003449910 occupation_29 marital_married industry_81 0.002272629 0.002085207 0.001152210 industry_72 0.000984725
この場合、2つの変数の重要度指標は、どの変数が重要かについてかなり同意しています。これは因果関係の分析ではありませんが、賃金予測に重要な「年齢」、「教育レベル」、および「性別」として知られている変数が、2つの指標で非常に重要と見なされていることも素晴らしいことです。
予測変数の少ないセットを得るには、j=1、…、p-1 として、
(I) 最も重要でない変数を削除します
(II) テストセット上でロス(例:平均二乗誤差)を計算します
(III) 残りの変数に対して変数の重要度を再計算します
(IV) 特定の停止基準が満たされるまで繰り返します
たとえば、ロスが5%以上増加した場合に停止することができます。この記事では、私の生活を簡単にするために、「SobolMDA」と「MMDVimp」に保存された同じ変数の重要度値を使用します。つまり、ステップ(III)を無視し、(I)、(II)、(IV)のみを考慮します。推定の目標が完全条件付き分布である場合、ステップ(II)も完全に明確ではありません。私たちは、予測した分布の予測エラーを考慮したMMDロスを使用します。条件付き平均の場合、平均二乗誤差を使用します。これは以下に見つかる「evalall」関数で行われます:
# Remove variables one-by-one according to the importance values saved in SobolMDA# and MMDVimp.evallistSobol<-evalall(SobolMDA, X=Xtrain ,Y=Ytrain ,Xtest, Ytest, metrics=c("MSE"), num.trees )evallistMMD<-evalall(MMDVimp, X=Xtrain ,Y=Ytrain ,Xtest, Ytest, metrics=c("MMD"), num.trees )plot(evallistSobol$evalMSE, type="l", lwd=2, cex=0.8, col="darkgreen", main="MSE loss" , xlab="Number of Variables removed", ylab="Values")plot(evallistMMD$evalMMD, type="l", lwd=2, cex=0.8, col="darkgreen", main="MMD loss" , xlab="Number of Variables removed", ylab="Values")
これにより、次の2つの図が生成されます:
両方の図で、やや波打った線が見られることに注意してください。これは、重要性指標を再計算せずに(つまり、ステップ(III)を省略して)作成したこと、およびフォレストのランダム性に起因するものです。これに加えて、グラフは変数を削除するたびにエラーが徐々に増加する様子をうまく示しています。最も重要な変数に対しては速くなり、最も重要でない変数に対しては最初は遅く増加します。実際に、50番目に重要でない変数を削除しても、両方の場合におけるロスはほとんど変わりません!実際には、ロスが6%以上増加しないように、およそ70の変数を両方の場合に削除することができます。ただし、多くの予測子はワンホットエンコーディングされたカテゴリカル変数の一部であり、したがって予測子を削除する際には注意が必要です。ただし、実際の応用では、これは依然として望ましいかもしれません。
結論
この記事では、ランダムフォレストにおける変数の重要度の現代的なアプローチを、条件付き期待値と条件付き分布の両方に関して、少ない予測変数または共変量を得ることを目指して調査しました。賃金データの例では、これにより予測の精度がほぼ同じままで予測子が劇的に削減されることがわかりました。
上記の手法はランダムフォレストに厳密に制約されているわけではありませんが、原則としてより一般的に使用することができます。ただし、フォレストでは、毎回フォレストを再適合させる必要なく、すべての変数 j に対して重要度を計算するためのエレガントな投影手法が可能です (!) これは [3] および [4] の両方で説明されています。
文学
[1] Breiman, L. (2001). ランダムフォレスト. 機械学習、45(1):5–32.
[2] Breiman, L. (2003a). ランダムフォレストの設定、使用、理解、バージョン3.1. UCバークレー、統計学部の技術レポート
[3] Bénard, C., Da Veiga, S., and Scornet, E. (2022). ランダムフォレストの平均減少精度: 不一致性とソボル-MDAを用いた実践的な解決策。バイオメトリカ、109(4): 881–900.
[4] Clément Bénard、Jeffrey Näf、およびJulie Josse. 分布ランダムフォレストの変数の重要性に基づくMMD、2023。
[5] Strobl, C., Boulesteix, A.-L., Zeileis, A., and Hothorn, T. (2007). ランダムフォレストの変数の重要性測定におけるバイアス: イラスト、源泉、および解決策。BMCバイオインフォマティクス、8:25。
付録: コード
#### compute_drf_vimp.R の内容 #######' 分布ランダムフォレストの変数の重要性#' @param X 入力トレーニングデータの行列#' @param Y 出力トレーニングデータの行列#' @param X_test 入力テストデータの行列。NULLの場合、袋外推定が使用されます。#' @param num.trees DRFを適合させる木の数。デフォルト値は500本です。#' @param silent FALSEの場合は変数の反復番号を表示し、それ以外の場合は何も表示されません。デフォルトはFALSEです。#' @return すべての入力変数の重要性値のリスト#' @export#' @examplescompute_drf_vimp <- function(X, Y, X_test = NULL, num.trees = 500, silent = FALSE){ # 初期DRFのフィット bandwidth_Y <- drf:::medianHeuristic(Y) k_Y <- rbfdot(sigma = bandwidth_Y) K <- kernelMatrix(k_Y, Y, Y) DRF <- drf(X, Y, num.trees = num.trees) wall <- predict(DRF, X_test)$weights # 正規化定数の計算 wbar <- colMeans(wall) wall_wbar <- sweep(wall, 2, wbar, "-") I0 <- as.numeric(sum(diag(wall_wbar %*% K %*% t(wall_wbar)))) # 変数を1つずつ削除してdrf重要性を計算 I <- sapply(1:ncol(X), function(j) { if (!silent){print(paste0('変数X', j, 'の重要性を計算しています...'))} DRFj <- drf(X = X[, -j, drop=F], Y = Y, num.trees = num.trees) DRFpredj <- predict(DRFj, X_test[, -j]) wj <- DRFpredj$weights Ij <- sum(diag((wj - wall) %*% K %*% t(wj - wall)))/I0 return(Ij) }) # 再学習バイアスの計算 DRF0 <- drf(X = X, Y = Y, num.trees = num.trees) DRFpred0 = predict(DRF0, X_test) w0 <- DRFpred0$weights vimp0 <- sum(diag((w0 - wall) %*% K %*% t(w0 - wall)))/I0 # 最終的な重要性の計算(バイアスの除去と負の値の切り捨て) vimp <- sapply(I - vimp0, function(x){max(0,x)}) names(vimp)<-colnames(X) return(vimp) }#### evaluation.Rの内容 ######compute_mmd_loss <- function(Y_train, Y_test, weights){ # Y_train <- scale(Y_train) # Y_test <- scale(Y_test) bandwidth_Y <- (1/drf:::medianHeuristic(Y_train))^2 k_Y <- rbfdot(sigma = bandwidth_Y) K_train <- matrix(kernelMatrix(k_Y, Y_train, Y_train), ncol = nrow(Y_train)) K_cross <- matrix(kernelMatrix(k_Y, Y_test, Y_train), ncol = nrow(Y_train)) weights <- matrix(weights, ncol = ncol(weights)) t1 <- diag(weights%*%K_train%*%t(weights)) t2 <- diag(K_cross%*%t(weights)) mmd_loss <- mean(t1) - 2*mean(t2) mmd_loss}evalall <- function(Vimp, X ,Y ,Xtest, Ytest, metrics=c("MMD","MSE"), num.trees ){ if (ncol(Ytest) > 1 & "MSE" %in% metrics){ metrics <- metrics[!( metrics %in% "MSE") ] } # 重要性の増加順にソートし、最も重要ではない変数が最初に削除されるようにする Vimp<-sort(Vimp) if ( is.null(names(Vimp)) ){ stop("後で名前が必要です") } evalMMD<-matrix(0, nrow=ncol(X)) evalMSE<-matrix(0, nrow=ncol(X)) ###アイデア:変数の重要性を測定する関数を作り、このループを実行します!! for (j in 1:ncol(X)){ if (j==1){ if ("MMD" %in% metrics){ DRFred<- drf(X=X,Y=Y) weights<- predict(DRFred, newdata=Xtest)$weights evalMMD[j]<-compute_mmd_loss(Y_train=Y, Y_test=Ytest, weights) } if ("MSE" %in% metrics){ XY <- as.data.frame(cbind(X, Y)) colnames(XY) <- c(paste('X', 1:(ncol(XY)-1), sep=''), 'Y') RFfull <- sobolMDA::ranger(Y ~., data = XY, num.trees = num.trees) XtestRF<-Xtest colnames(XtestRF) <- paste('X', 1:ncol(XtestRF), sep='') predRF<-predict(RFfull, data=XtestRF) evalMSE[j] <- mean((Ytest - predRF$predictions)^2) } }else{ if ("MMD" %in% metrics){ DRFred<- drf(X=X[,!(colnames(X) %in% names(Vimp[1:(j-1)])), drop=F],Y=Y) weights<- predict(DRFred, newdata=Xtest[,!(colnames(Xtest) %in% names(Vimp[1:(j-1)])), drop=F])$weights evalMMD[j]<-compute_mmd_loss(Y_train=Y, Y_test=Ytest, weights) } if ("MSE" %in% metrics){ XY <- as.data.frame(cbind(X[,!(colnames(X) %in% names(Vimp[1:(j-1)])), drop=F], Y)) colnames(XY) <- c(paste('X', 1:(ncol(XY)-1), sep=''), 'Y') RFfull <- sobolMDA::ranger(Y ~., data = XY, num.trees = num.trees) XtestRF<-Xtest[,!(colnames(Xtest) %in% names(Vimp[1:(j-1)])), drop=F] colnames(XtestRF) <- paste('X', 1:ncol(XtestRF), sep='') predRF<-predict(RFfull,
We will continue to update VoAGI; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles