This is an automated email from the ASF dual-hosted git repository.

zhengruifeng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/master by this push:
     new 4be5711835fe Revert "[SPARK-34591][ML] Add decision tree pruning as a 
parameter"
4be5711835fe is described below

commit 4be5711835fe0aef58db4b965707d7b5a9b5c365
Author: Ruifeng Zheng <[email protected]>
AuthorDate: Fri May 8 18:21:28 2026 +0800

    Revert "[SPARK-34591][ML] Add decision tree pruning as a parameter"
    
    This reverts commit 1f4650674a663627cdf38a6100d9fb7cf1527c47.
    
    ### What changes were proposed in this pull request?
    
    ### Why are the changes needed?
    
    ### Does this PR introduce _any_ user-facing change?
    
    ### How was this patch tested?
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Closes #55759 from zhengruifeng/revert_tree.
    
    Authored-by: Ruifeng Zheng <[email protected]>
    Signed-off-by: Ruifeng Zheng <[email protected]>
---
 .../ml/classification/DecisionTreeClassifier.scala |   8 +-
 .../ml/classification/RandomForestClassifier.scala |   7 +-
 .../apache/spark/ml/tree/impl/RandomForest.scala   | 661 +++++++++------------
 .../org/apache/spark/ml/tree/treeParams.scala      |  19 +-
 .../spark/mllib/tree/configuration/Strategy.scala  |   8 +-
 .../spark/ml/tree/impl/RandomForestSuite.scala     | 411 ++++---------
 python/pyspark/ml/classification.py                |  26 +-
 python/pyspark/ml/tree.py                          |  13 -
 8 files changed, 400 insertions(+), 753 deletions(-)

diff --git 
a/mllib/src/main/scala/org/apache/spark/ml/classification/DecisionTreeClassifier.scala
 
b/mllib/src/main/scala/org/apache/spark/ml/classification/DecisionTreeClassifier.scala
index d5564f6a3fbd..887d8277d311 100644
--- 
a/mllib/src/main/scala/org/apache/spark/ml/classification/DecisionTreeClassifier.scala
+++ 
b/mllib/src/main/scala/org/apache/spark/ml/classification/DecisionTreeClassifier.scala
@@ -74,10 +74,6 @@ class DecisionTreeClassifier @Since("1.4.0") (
   @Since("1.4.0")
   def setMinInfoGain(value: Double): this.type = set(minInfoGain, value)
 
-  /** @group setParam */
-  @Since("4.3.0")
-  def setPruneTree(value: Boolean): this.type = set(pruneTree, value)
-
   /** @group expertSetParam */
   @Since("1.4.0")
   def setMaxMemoryInMB(value: Int): this.type = set(maxMemoryInMB, value)
@@ -138,11 +134,9 @@ class DecisionTreeClassifier @Since("1.4.0") (
 
     val strategy = getOldStrategy(categoricalFeatures, numClasses)
     require(!strategy.bootstrap, "DecisionTreeClassifier does not need 
bootstrap sampling")
-    strategy.pruneTree = $(pruneTree)
-
     instr.logNumClasses(numClasses)
     instr.logParams(this, labelCol, featuresCol, predictionCol, 
rawPredictionCol,
-      probabilityCol, leafCol, maxDepth, maxBins, minInstancesPerNode, 
minInfoGain, pruneTree,
+      probabilityCol, leafCol, maxDepth, maxBins, minInstancesPerNode, 
minInfoGain,
       maxMemoryInMB, cacheNodeIds, checkpointInterval, impurity, seed, 
thresholds)
 
     val trees = RandomForest.run(instances, strategy, numTrees = 1, 
featureSubsetStrategy = "all",
diff --git 
a/mllib/src/main/scala/org/apache/spark/ml/classification/RandomForestClassifier.scala
 
b/mllib/src/main/scala/org/apache/spark/ml/classification/RandomForestClassifier.scala
index 2c22ca5b4230..fb61358536d0 100644
--- 
a/mllib/src/main/scala/org/apache/spark/ml/classification/RandomForestClassifier.scala
+++ 
b/mllib/src/main/scala/org/apache/spark/ml/classification/RandomForestClassifier.scala
@@ -76,10 +76,6 @@ class RandomForestClassifier @Since("1.4.0") (
   @Since("1.4.0")
   def setMinInfoGain(value: Double): this.type = set(minInfoGain, value)
 
-  /** @group setParam */
-  @Since("4.3.0")
-  def setPruneTree(value: Boolean): this.type = set(pruneTree, value)
-
   /** @group expertSetParam */
   @Since("1.4.0")
   def setMaxMemoryInMB(value: Int): this.type = set(maxMemoryInMB, value)
@@ -163,11 +159,10 @@ class RandomForestClassifier @Since("1.4.0") (
     val strategy =
       super.getOldStrategy(categoricalFeatures, numClasses, 
OldAlgo.Classification, getOldImpurity)
     strategy.bootstrap = $(bootstrap)
-    strategy.pruneTree = $(pruneTree)
 
     instr.logParams(this, labelCol, featuresCol, weightCol, predictionCol, 
probabilityCol,
       rawPredictionCol, leafCol, impurity, numTrees, featureSubsetStrategy, 
maxDepth, maxBins,
-      maxMemoryInMB, minInfoGain, pruneTree, minInstancesPerNode, 
minWeightFractionPerNode, seed,
+      maxMemoryInMB, minInfoGain, minInstancesPerNode, 
minWeightFractionPerNode, seed,
       subsamplingRate, thresholds, cacheNodeIds, checkpointInterval, bootstrap)
 
     val trees = RandomForest
diff --git 
a/mllib/src/main/scala/org/apache/spark/ml/tree/impl/RandomForest.scala 
b/mllib/src/main/scala/org/apache/spark/ml/tree/impl/RandomForest.scala
index c3a16ab3dddd..cabbc497571b 100644
--- a/mllib/src/main/scala/org/apache/spark/ml/tree/impl/RandomForest.scala
+++ b/mllib/src/main/scala/org/apache/spark/ml/tree/impl/RandomForest.scala
@@ -41,6 +41,7 @@ import org.apache.spark.util.SizeEstimator
 import org.apache.spark.util.collection.OpenHashMap
 import org.apache.spark.util.random.{SamplingUtils, XORShiftRandom}
 
+
 /**
  * ALGORITHM
  *
@@ -96,9 +97,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       numTrees: Int,
       featureSubsetStrategy: String,
       seed: Long): Array[DecisionTreeModel] = {
-    val instances = input.map {
-      case LabeledPoint(label, features) =>
-        Instance(label, 1.0, features.asML)
+    val instances = input.map { case LabeledPoint(label, features) =>
+      Instance(label, 1.0, features.asML)
     }
     run(instances, strategy, numTrees, featureSubsetStrategy, seed, None)
   }
@@ -124,6 +124,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       featureSubsetStrategy: String,
       seed: Long,
       instr: Option[Instrumentation],
+      prune: Boolean = true, // exposed for testing only, real trees are 
always pruned
       parentUID: Option[String] = None,
       earlyStopModelSizeThresholdInBytes: Long = 0): Array[DecisionTreeModel] 
= {
     lastEarlyStoppedModelSize = 0
@@ -150,8 +151,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
 
     // depth of the decision tree
     val maxDepth = strategy.maxDepth
-    require(
-      maxDepth <= 30,
+    require(maxDepth <= 30,
       s"DecisionTree currently only supports maxDepth <= 30, but was given 
maxDepth = $maxDepth.")
 
     // Max memory usage for aggregates
@@ -203,10 +203,9 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       // Collect some nodes to split, and choose features for each node (if 
subsampling).
       // Each group of nodes may come from one or multiple trees, and at 
multiple levels.
       val (nodesForGroup, treeToNodeToIndexInfo) =
-        RandomForest.selectNodesToSplit(nodeStack, maxMemoryUsage, metadata, 
rng)
+      RandomForest.selectNodesToSplit(nodeStack, maxMemoryUsage, metadata, rng)
       // Sanity check (should never occur):
-      assert(
-        nodesForGroup.nonEmpty,
+      assert(nodesForGroup.nonEmpty,
         s"RandomForest selected empty nodesForGroup.  Error for unknown 
reason.")
 
       // Only send trees to worker if they contain nodes being split this 
iteration.
@@ -215,16 +214,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
 
       // Choose node splits, and enqueue new nodes as needed.
       timer.start("findBestSplits")
-      val bestSplit = RandomForest.findBestSplits(
-        baggedInput,
-        metadata,
-        topNodesForGroup,
-        nodesForGroup,
-        treeToNodeToIndexInfo,
-        bcSplits,
-        nodeStack,
-        timer,
-        nodeIds,
+      val bestSplit = RandomForest.findBestSplits(baggedInput, metadata, 
topNodesForGroup,
+        nodesForGroup, treeToNodeToIndexInfo, bcSplits, nodeStack, timer, 
nodeIds,
         outputBestSplits = strategy.useNodeIdCache)
       if (strategy.useNodeIdCache) {
         nodeIds = updateNodeIds(baggedInput, nodeIds, bcSplits, bestSplit)
@@ -234,7 +225,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       timer.stop("findBestSplits")
 
       if (earlyStopModelSizeThresholdInBytes > 0) {
-        val nodes = topNodes.map(_.toNode(strategy.pruneTree))
+        val nodes = topNodes.map(_.toNode(prune))
         val estimatedSize = SizeEstimator.estimate(nodes)
         if (estimatedSize > earlyStopModelSizeThresholdInBytes){
           earlyStop = true
@@ -267,28 +258,23 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       case Some(uid) =>
         if (strategy.algo == OldAlgo.Classification) {
           topNodes.map { rootNode =>
-            new DecisionTreeClassificationModel(
-              uid,
-              rootNode.toNode(strategy.pruneTree),
-              numFeatures,
-              strategy.getNumClasses)
+            new DecisionTreeClassificationModel(uid, rootNode.toNode(prune), 
numFeatures,
+              strategy.getNumClasses())
           }
         } else {
           topNodes.map { rootNode =>
-            new DecisionTreeRegressionModel(uid, 
rootNode.toNode(strategy.pruneTree), numFeatures)
+            new DecisionTreeRegressionModel(uid, rootNode.toNode(prune), 
numFeatures)
           }
         }
       case None =>
         if (strategy.algo == OldAlgo.Classification) {
           topNodes.map { rootNode =>
-            new DecisionTreeClassificationModel(
-              rootNode.toNode(strategy.pruneTree),
-              numFeatures,
-              strategy.getNumClasses)
+            new DecisionTreeClassificationModel(rootNode.toNode(prune), 
numFeatures,
+              strategy.getNumClasses())
           }
         } else {
           topNodes.map(rootNode =>
-            new 
DecisionTreeRegressionModel(rootNode.toNode(strategy.pruneTree), numFeatures))
+            new DecisionTreeRegressionModel(rootNode.toNode(prune), 
numFeatures))
         }
     }
   }
@@ -307,6 +293,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       featureSubsetStrategy: String,
       seed: Long,
       instr: Option[Instrumentation],
+      prune: Boolean = true, // exposed for testing only, real trees are 
always pruned
       parentUID: Option[String] = None): Array[DecisionTreeModel] = {
     val earlyStopModelSizeThresholdInBytes = 
TreeConfig.trainingEarlyStopModelSizeThresholdInBytes
     val timer = new TimeTracker()
@@ -324,12 +311,9 @@ private[spark] object RandomForest extends Logging with 
Serializable {
     val splits = findSplits(retaggedInput, metadata, seed)
     timer.stop("findSplits")
     logDebug("numBins: feature: number of bins")
-    logDebug(
-      Range(0, metadata.numFeatures)
-        .map { featureIndex =>
-          s"\t$featureIndex\t${metadata.numBins(featureIndex)}"
-        }
-        .mkString("\n"))
+    logDebug(Range(0, metadata.numFeatures).map { featureIndex =>
+      s"\t$featureIndex\t${metadata.numBins(featureIndex)}"
+    }.mkString("\n"))
 
     // Bin feature values (TreePoint representation).
     // Cache input RDD for speedup during multiple passes.
@@ -337,26 +321,14 @@ private[spark] object RandomForest extends Logging with 
Serializable {
 
     val bcSplits = input.sparkContext.broadcast(splits)
     val baggedInput = BaggedPoint
-      .convertToBaggedRDD(
-        treeInput,
-        strategy.subsamplingRate,
-        numTrees,
-        strategy.bootstrap,
-        (tp: TreePoint) => tp.weight,
-        seed = seed)
+      .convertToBaggedRDD(treeInput, strategy.subsamplingRate, numTrees, 
strategy.bootstrap,
+        (tp: TreePoint) => tp.weight, seed = seed)
       .persist(StorageLevel.MEMORY_AND_DISK)
       .setName("bagged tree points")
 
-    val trees = runBagged(
-      baggedInput = baggedInput,
-      metadata = metadata,
-      bcSplits = bcSplits,
-      strategy = strategy,
-      numTrees = numTrees,
-      featureSubsetStrategy = featureSubsetStrategy,
-      seed = seed,
-      instr = instr,
-      parentUID = parentUID,
+    val trees = runBagged(baggedInput = baggedInput, metadata = metadata, 
bcSplits = bcSplits,
+      strategy = strategy, numTrees = numTrees, featureSubsetStrategy = 
featureSubsetStrategy,
+      seed = seed, instr = instr, prune = prune, parentUID = parentUID,
       earlyStopModelSizeThresholdInBytes = earlyStopModelSizeThresholdInBytes)
 
     baggedInput.unpersist()
@@ -374,27 +346,26 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       bcSplits: Broadcast[Array[Array[Split]]],
       bestSplits: Array[Map[Int, Split]]): RDD[Array[Int]] = {
     require(nodeIds != null && bestSplits != null)
-    input.zip(nodeIds).map {
-      case (point, ids) =>
-        var treeId = 0
-        while (treeId < bestSplits.length) {
-          val bestSplitsInTree = bestSplits(treeId)
-          if (bestSplitsInTree != null) {
-            val nodeId = ids(treeId)
-            bestSplitsInTree.get(nodeId).foreach { bestSplit =>
-              val featureId = bestSplit.featureIndex
-              val bin = point.datum.binnedFeatures(featureId)
-              val newNodeId = if (bestSplit.shouldGoLeft(bin, 
bcSplits.value(featureId))) {
-                LearningNode.leftChildIndex(nodeId)
-              } else {
-                LearningNode.rightChildIndex(nodeId)
-              }
-              ids(treeId) = newNodeId
+    input.zip(nodeIds).map { case (point, ids) =>
+      var treeId = 0
+      while (treeId < bestSplits.length) {
+        val bestSplitsInTree = bestSplits(treeId)
+        if (bestSplitsInTree != null) {
+          val nodeId = ids(treeId)
+          bestSplitsInTree.get(nodeId).foreach { bestSplit =>
+            val featureId = bestSplit.featureIndex
+            val bin = point.datum.binnedFeatures(featureId)
+            val newNodeId = if (bestSplit.shouldGoLeft(bin, 
bcSplits.value(featureId))) {
+              LearningNode.leftChildIndex(nodeId)
+            } else {
+              LearningNode.rightChildIndex(nodeId)
             }
+            ids(treeId) = newNodeId
           }
-          treeId += 1
         }
-        ids
+        treeId += 1
+      }
+      ids
     }
   }
 
@@ -446,11 +417,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
         var splitIndex = 0
         while (splitIndex < numSplits) {
           if (featureSplits(splitIndex).shouldGoLeft(featureValue, 
featureSplits)) {
-            agg.featureUpdate(
-              leftNodeFeatureOffset,
-              splitIndex,
-              treePoint.label,
-              numSamples,
+            agg.featureUpdate(leftNodeFeatureOffset, splitIndex, 
treePoint.label, numSamples,
               sampleWeight)
           }
           splitIndex += 1
@@ -565,9 +532,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
     logDebug(s"numFeatures = ${metadata.numFeatures}")
     logDebug(s"numClasses = ${metadata.numClasses}")
     logDebug(s"isMulticlass = ${metadata.isMulticlass}")
-    logDebug(
-      s"isMulticlassWithCategoricalFeatures = " +
-        s"${metadata.isMulticlassWithCategoricalFeatures}")
+    logDebug(s"isMulticlassWithCategoricalFeatures = " +
+      s"${metadata.isMulticlassWithCategoricalFeatures}")
     logDebug(s"using nodeIdCache = $useNodeIdCache")
 
     /*
@@ -594,21 +560,11 @@ private[spark] object RandomForest extends Logging with 
Serializable {
         val numSamples = baggedPoint.subsampleCounts(treeIndex)
         val sampleWeight = baggedPoint.sampleWeight
         if (metadata.unorderedFeatures.isEmpty) {
-          orderedBinSeqOp(
-            agg(aggNodeIndex),
-            baggedPoint.datum,
-            numSamples,
-            sampleWeight,
+          orderedBinSeqOp(agg(aggNodeIndex), baggedPoint.datum, numSamples, 
sampleWeight,
             featuresForNode)
         } else {
-          mixedBinSeqOp(
-            agg(aggNodeIndex),
-            baggedPoint.datum,
-            splits,
-            metadata.unorderedFeatures,
-            numSamples,
-            sampleWeight,
-            featuresForNode)
+          mixedBinSeqOp(agg(aggNodeIndex), baggedPoint.datum, splits,
+            metadata.unorderedFeatures, numSamples, sampleWeight, 
featuresForNode)
         }
         agg(aggNodeIndex).updateParent(baggedPoint.datum.label, numSamples, 
sampleWeight)
       }
@@ -629,16 +585,11 @@ private[spark] object RandomForest extends Logging with 
Serializable {
         agg: Array[DTStatsAggregator],
         baggedPoint: BaggedPoint[TreePoint],
         splits: Array[Array[Split]]): Array[DTStatsAggregator] = {
-      treeToNodeToIndexInfo.foreach {
-        case (treeIndex, nodeIndexToInfo) =>
-          val nodeIndex =
-            
topNodesForGroup(treeIndex).predictImpl(baggedPoint.datum.binnedFeatures, 
splits)
-          nodeBinSeqOp(
-            treeIndex,
-            nodeIndexToInfo.getOrElse(nodeIndex, null),
-            agg,
-            baggedPoint,
-            splits)
+      treeToNodeToIndexInfo.foreach { case (treeIndex, nodeIndexToInfo) =>
+        val nodeIndex =
+          
topNodesForGroup(treeIndex).predictImpl(baggedPoint.datum.binnedFeatures, 
splits)
+        nodeBinSeqOp(treeIndex, nodeIndexToInfo.getOrElse(nodeIndex, null),
+          agg, baggedPoint, splits)
       }
       agg
     }
@@ -650,17 +601,12 @@ private[spark] object RandomForest extends Logging with 
Serializable {
         agg: Array[DTStatsAggregator],
         dataPoint: (BaggedPoint[TreePoint], Array[Int]),
         splits: Array[Array[Split]]): Array[DTStatsAggregator] = {
-      treeToNodeToIndexInfo.foreach {
-        case (treeIndex, nodeIndexToInfo) =>
-          val baggedPoint = dataPoint._1
-          val nodeIdCache = dataPoint._2
-          val nodeIndex = nodeIdCache(treeIndex)
-          nodeBinSeqOp(
-            treeIndex,
-            nodeIndexToInfo.getOrElse(nodeIndex, null),
-            agg,
-            baggedPoint,
-            splits)
+      treeToNodeToIndexInfo.foreach { case (treeIndex, nodeIndexToInfo) =>
+        val baggedPoint = dataPoint._1
+        val nodeIdCache = dataPoint._2
+        val nodeIndex = nodeIdCache(treeIndex)
+        nodeBinSeqOp(treeIndex, nodeIndexToInfo.getOrElse(nodeIndex, null),
+          agg, baggedPoint, splits)
       }
       agg
     }
@@ -669,8 +615,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
      * Get node index in group --> features indices map,
      * which is a short cut to find feature indices for a node given node 
index in group.
      */
-    def getNodeToFeatures(treeToNodeToIndexInfo: Map[Int, Map[Int, 
NodeIndexInfo]])
-        : Option[Map[Int, Array[Int]]] = {
+    def getNodeToFeatures(
+        treeToNodeToIndexInfo: Map[Int, Map[Int, NodeIndexInfo]]): 
Option[Map[Int, Array[Int]]] = {
       if (!metadata.subsamplingFeatures) {
         None
       } else {
@@ -678,8 +624,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
         treeToNodeToIndexInfo.values.foreach { nodeIdToNodeInfo =>
           nodeIdToNodeInfo.values.foreach { nodeIndexInfo =>
             assert(nodeIndexInfo.featureSubset.isDefined)
-            mutableNodeToFeatures(nodeIndexInfo.nodeIndexInGroup) =
-              nodeIndexInfo.featureSubset.get
+            mutableNodeToFeatures(nodeIndexInfo.nodeIndexInGroup) = 
nodeIndexInfo.featureSubset.get
           }
         }
         Some(mutableNodeToFeatures.toMap)
@@ -688,11 +633,10 @@ private[spark] object RandomForest extends Logging with 
Serializable {
 
     // array of nodes to train indexed by node index in group
     val nodes = new Array[LearningNode](numNodes)
-    nodesForGroup.foreach {
-      case (treeIndex, nodesForTree) =>
-        nodesForTree.foreach { node =>
-          nodes(treeToNodeToIndexInfo(treeIndex)(node.id).nodeIndexInGroup) = 
node
-        }
+    nodesForGroup.foreach { case (treeIndex, nodesForTree) =>
+      nodesForTree.foreach { node =>
+        nodes(treeToNodeToIndexInfo(treeIndex)(node.id).nodeIndexInGroup) = 
node
+      }
     }
 
     // Calculate best splits for all nodes in the group
@@ -746,20 +690,17 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       }
     }
 
-    val nodeToBestSplits = partitionAggregates
-      .reduceByKey((a, b) => a.merge(b))
-      .map {
-        case (nodeIndex, aggStats) =>
-          val featuresForNode = nodeToFeaturesBc.value.flatMap { 
nodeToFeatures =>
-            Some(nodeToFeatures(nodeIndex))
-          }
+    val nodeToBestSplits = partitionAggregates.reduceByKey((a, b) => 
a.merge(b)).map {
+      case (nodeIndex, aggStats) =>
+        val featuresForNode = nodeToFeaturesBc.value.flatMap { nodeToFeatures 
=>
+          Some(nodeToFeatures(nodeIndex))
+        }
 
-          // find best split for each node
-          val (split: Split, stats: ImpurityStats) =
-            binsToBestSplit(aggStats, bcSplits.value, featuresForNode, 
nodes(nodeIndex))
-          (nodeIndex, (split, stats))
-      }
-      .collectAsMap()
+        // find best split for each node
+        val (split: Split, stats: ImpurityStats) =
+          binsToBestSplit(aggStats, bcSplits.value, featuresForNode, 
nodes(nodeIndex))
+        (nodeIndex, (split, stats))
+    }.collectAsMap()
     nodeToFeaturesBc.destroy()
 
     timer.stop("chooseSplits")
@@ -771,64 +712,55 @@ private[spark] object RandomForest extends Logging with 
Serializable {
     }
 
     // Iterate over all nodes in this group.
-    nodesForGroup.foreach {
-      case (treeIndex, nodesForTree) =>
-        nodesForTree.foreach { node =>
-          val nodeIndex = node.id
-          val nodeInfo = treeToNodeToIndexInfo(treeIndex)(nodeIndex)
-          val aggNodeIndex = nodeInfo.nodeIndexInGroup
-          val (split: Split, stats: ImpurityStats) =
-            nodeToBestSplits(aggNodeIndex)
-          logDebug(s"best split = $split")
-
-          // Extract info for this node.  Create children if not leaf.
-          val isLeaf =
-            (stats.gain <= 0) || (LearningNode.indexToLevel(nodeIndex) == 
metadata.maxDepth)
-          node.isLeaf = isLeaf
-          node.stats = stats
-          logDebug(s"Node = $node")
-
-          if (!isLeaf) {
-            node.split = Some(split)
-            val childIsLeaf = (LearningNode.indexToLevel(nodeIndex) + 1) == 
metadata.maxDepth
-            val leftChildIsLeaf = childIsLeaf || (math.abs(stats.leftImpurity) 
< Utils.EPSILON)
-            val rightChildIsLeaf = childIsLeaf || 
(math.abs(stats.rightImpurity) < Utils.EPSILON)
-            node.leftChild = Some(
-              LearningNode(
-                LearningNode.leftChildIndex(nodeIndex),
-                leftChildIsLeaf,
-                
ImpurityStats.getEmptyImpurityStats(stats.leftImpurityCalculator)))
-            node.rightChild = Some(
-              LearningNode(
-                LearningNode.rightChildIndex(nodeIndex),
-                rightChildIsLeaf,
-                
ImpurityStats.getEmptyImpurityStats(stats.rightImpurityCalculator)))
-
-            if (outputBestSplits) {
-              val bestSplitsInTree = bestSplits(treeIndex)
-              if (bestSplitsInTree == null) {
-                bestSplits(treeIndex) = mutable.Map[Int, Split](nodeIndex -> 
split)
-              } else {
-                bestSplitsInTree.update(nodeIndex, split)
-              }
-            }
-
-            // enqueue left child and right child if they are not leaves
-            if (!leftChildIsLeaf) {
-              nodeStack.prepend((treeIndex, node.leftChild.get))
-            }
-            if (!rightChildIsLeaf) {
-              nodeStack.prepend((treeIndex, node.rightChild.get))
+    nodesForGroup.foreach { case (treeIndex, nodesForTree) =>
+      nodesForTree.foreach { node =>
+        val nodeIndex = node.id
+        val nodeInfo = treeToNodeToIndexInfo(treeIndex)(nodeIndex)
+        val aggNodeIndex = nodeInfo.nodeIndexInGroup
+        val (split: Split, stats: ImpurityStats) =
+          nodeToBestSplits(aggNodeIndex)
+        logDebug(s"best split = $split")
+
+        // Extract info for this node.  Create children if not leaf.
+        val isLeaf =
+          (stats.gain <= 0) || (LearningNode.indexToLevel(nodeIndex) == 
metadata.maxDepth)
+        node.isLeaf = isLeaf
+        node.stats = stats
+        logDebug(s"Node = $node")
+
+        if (!isLeaf) {
+          node.split = Some(split)
+          val childIsLeaf = (LearningNode.indexToLevel(nodeIndex) + 1) == 
metadata.maxDepth
+          val leftChildIsLeaf = childIsLeaf || (math.abs(stats.leftImpurity) < 
Utils.EPSILON)
+          val rightChildIsLeaf = childIsLeaf || (math.abs(stats.rightImpurity) 
< Utils.EPSILON)
+          node.leftChild = 
Some(LearningNode(LearningNode.leftChildIndex(nodeIndex),
+            leftChildIsLeaf, 
ImpurityStats.getEmptyImpurityStats(stats.leftImpurityCalculator)))
+          node.rightChild = 
Some(LearningNode(LearningNode.rightChildIndex(nodeIndex),
+            rightChildIsLeaf, 
ImpurityStats.getEmptyImpurityStats(stats.rightImpurityCalculator)))
+
+          if (outputBestSplits) {
+            val bestSplitsInTree = bestSplits(treeIndex)
+            if (bestSplitsInTree == null) {
+              bestSplits(treeIndex) = mutable.Map[Int, Split](nodeIndex -> 
split)
+            } else {
+              bestSplitsInTree.update(nodeIndex, split)
             }
+          }
 
-            logDebug(
-              s"leftChildIndex = ${node.leftChild.get.id}" +
-                s", impurity = ${stats.leftImpurity}")
-            logDebug(
-              s"rightChildIndex = ${node.rightChild.get.id}" +
-                s", impurity = ${stats.rightImpurity}")
+          // enqueue left child and right child if they are not leaves
+          if (!leftChildIsLeaf) {
+            nodeStack.prepend((treeIndex, node.leftChild.get))
+          }
+          if (!rightChildIsLeaf) {
+            nodeStack.prepend((treeIndex, node.rightChild.get))
           }
+
+          logDebug(s"leftChildIndex = ${node.leftChild.get.id}" +
+            s", impurity = ${stats.leftImpurity}")
+          logDebug(s"rightChildIndex = ${node.rightChild.get.id}" +
+            s", impurity = ${stats.rightImpurity}")
         }
+      }
     }
 
     if (outputBestSplits) {
@@ -898,12 +830,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       return ImpurityStats.getInvalidImpurityStats(parentImpurityCalculator)
     }
 
-    new ImpurityStats(
-      gain,
-      impurity,
-      parentImpurityCalculator,
-      leftImpurityCalculator,
-      rightImpurityCalculator)
+    new ImpurityStats(gain, impurity, parentImpurityCalculator,
+      leftImpurityCalculator, rightImpurityCalculator)
   }
 
   /**
@@ -927,156 +855,130 @@ private[spark] object RandomForest extends Logging with 
Serializable {
     }
 
     val validFeatureSplits =
-      Iterator
-        .range(0, binAggregates.metadata.numFeaturesPerNode)
-        .map { featureIndexIdx =>
-          featuresForNode
-            .map(features => (featureIndexIdx, features(featureIndexIdx)))
-            .getOrElse((featureIndexIdx, featureIndexIdx))
-        }
-        .withFilter {
-          case (_, featureIndex) =>
-            binAggregates.metadata.numSplits(featureIndex) != 0
-        }
+      Iterator.range(0, binAggregates.metadata.numFeaturesPerNode).map { 
featureIndexIdx =>
+        featuresForNode.map(features => (featureIndexIdx, 
features(featureIndexIdx)))
+          .getOrElse((featureIndexIdx, featureIndexIdx))
+      }.withFilter { case (_, featureIndex) =>
+        binAggregates.metadata.numSplits(featureIndex) != 0
+      }
 
     // For each (feature, split), calculate the gain, and select the best 
(feature, split).
     val splitsAndImpurityInfo =
-      validFeatureSplits.map {
-        case (featureIndexIdx, featureIndex) =>
-          val numSplits = binAggregates.metadata.numSplits(featureIndex)
-          if (binAggregates.metadata.isContinuous(featureIndex)) {
-            // Cumulative sum (scanLeft) of bin statistics.
-            // Afterwards, binAggregates for a bin is the sum of aggregates for
-            // that bin + all preceding bins.
-            val nodeFeatureOffset = 
binAggregates.getFeatureOffset(featureIndexIdx)
-            var splitIndex = 0
-            while (splitIndex < numSplits) {
-              binAggregates.mergeForFeature(nodeFeatureOffset, splitIndex + 1, 
splitIndex)
-              splitIndex += 1
-            }
-            // Find best split.
-            val (bestFeatureSplitIndex, bestFeatureGainStats) =
-              Range(0, numSplits)
-                .map { splitIdx =>
-                  val leftChildStats =
-                    binAggregates.getImpurityCalculator(nodeFeatureOffset, 
splitIdx)
-                  val rightChildStats =
-                    binAggregates.getImpurityCalculator(nodeFeatureOffset, 
numSplits)
-                  rightChildStats.subtract(leftChildStats)
-                  gainAndImpurityStats = calculateImpurityStats(
-                    gainAndImpurityStats,
-                    leftChildStats,
-                    rightChildStats,
-                    binAggregates.metadata)
-                  (splitIdx, gainAndImpurityStats)
-                }
-                .maxBy(_._2.gain)
-            (splits(featureIndex)(bestFeatureSplitIndex), bestFeatureGainStats)
-          } else if (binAggregates.metadata.isUnordered(featureIndex)) {
-            // Unordered categorical feature
-            val leftChildOffset = 
binAggregates.getFeatureOffset(featureIndexIdx)
-            val (bestFeatureSplitIndex, bestFeatureGainStats) =
-              Range(0, numSplits)
-                .map { splitIndex =>
-                  val leftChildStats =
-                    binAggregates.getImpurityCalculator(leftChildOffset, 
splitIndex)
-                  val rightChildStats = binAggregates
-                    .getParentImpurityCalculator()
-                    .subtract(leftChildStats)
-                  gainAndImpurityStats = calculateImpurityStats(
-                    gainAndImpurityStats,
-                    leftChildStats,
-                    rightChildStats,
-                    binAggregates.metadata)
-                  (splitIndex, gainAndImpurityStats)
-                }
-                .maxBy(_._2.gain)
-            (splits(featureIndex)(bestFeatureSplitIndex), bestFeatureGainStats)
-          } else {
-            // Ordered categorical feature
-            val nodeFeatureOffset = 
binAggregates.getFeatureOffset(featureIndexIdx)
-            val numCategories = binAggregates.metadata.numBins(featureIndex)
-
-            /* Each bin is one category (feature value).
-             * The bins are ordered based on centroidForCategories, and this 
ordering determines
-             * which splits are considered.  (With K categories, we
-             * consider K - 1 possible splits.)
-             *
+      validFeatureSplits.map { case (featureIndexIdx, featureIndex) =>
+        val numSplits = binAggregates.metadata.numSplits(featureIndex)
+        if (binAggregates.metadata.isContinuous(featureIndex)) {
+          // Cumulative sum (scanLeft) of bin statistics.
+          // Afterwards, binAggregates for a bin is the sum of aggregates for
+          // that bin + all preceding bins.
+          val nodeFeatureOffset = 
binAggregates.getFeatureOffset(featureIndexIdx)
+          var splitIndex = 0
+          while (splitIndex < numSplits) {
+            binAggregates.mergeForFeature(nodeFeatureOffset, splitIndex + 1, 
splitIndex)
+            splitIndex += 1
+          }
+          // Find best split.
+          val (bestFeatureSplitIndex, bestFeatureGainStats) =
+            Range(0, numSplits).map { splitIdx =>
+              val leftChildStats =
+                binAggregates.getImpurityCalculator(nodeFeatureOffset, 
splitIdx)
+              val rightChildStats =
+                binAggregates.getImpurityCalculator(nodeFeatureOffset, 
numSplits)
+              rightChildStats.subtract(leftChildStats)
+              gainAndImpurityStats = 
calculateImpurityStats(gainAndImpurityStats,
+                leftChildStats, rightChildStats, binAggregates.metadata)
+              (splitIdx, gainAndImpurityStats)
+            }.maxBy(_._2.gain)
+          (splits(featureIndex)(bestFeatureSplitIndex), bestFeatureGainStats)
+        } else if (binAggregates.metadata.isUnordered(featureIndex)) {
+          // Unordered categorical feature
+          val leftChildOffset = binAggregates.getFeatureOffset(featureIndexIdx)
+          val (bestFeatureSplitIndex, bestFeatureGainStats) =
+            Range(0, numSplits).map { splitIndex =>
+              val leftChildStats = 
binAggregates.getImpurityCalculator(leftChildOffset, splitIndex)
+              val rightChildStats = binAggregates.getParentImpurityCalculator()
+                .subtract(leftChildStats)
+              gainAndImpurityStats = 
calculateImpurityStats(gainAndImpurityStats,
+                leftChildStats, rightChildStats, binAggregates.metadata)
+              (splitIndex, gainAndImpurityStats)
+            }.maxBy(_._2.gain)
+          (splits(featureIndex)(bestFeatureSplitIndex), bestFeatureGainStats)
+        } else {
+          // Ordered categorical feature
+          val nodeFeatureOffset = 
binAggregates.getFeatureOffset(featureIndexIdx)
+          val numCategories = binAggregates.metadata.numBins(featureIndex)
+
+          /* Each bin is one category (feature value).
+           * The bins are ordered based on centroidForCategories, and this 
ordering determines which
+           * splits are considered.  (With K categories, we consider K - 1 
possible splits.)
+           *
            * centroidForCategories is a list: (category, centroid)
-             */
-            val centroidForCategories = Range(0, numCategories).map { 
featureValue =>
-              val categoryStats =
-                binAggregates.getImpurityCalculator(nodeFeatureOffset, 
featureValue)
-              val centroid = if (categoryStats.count != 0) {
-                if (binAggregates.metadata.isMulticlass) {
-                  // multiclass classification
-                  // For categorical variables in multiclass classification,
-                  // the bins are ordered by the impurity of their 
corresponding labels.
-                  categoryStats.calculate()
-                } else if (binAggregates.metadata.isClassification) {
-                  // binary classification
-                  // For categorical variables in binary classification,
-                  // the bins are ordered by the count of class 1.
-                  categoryStats.stats(1)
-                } else {
-                  // regression
-                  // For categorical variables in regression and binary 
classification,
-                  // the bins are ordered by the prediction.
-                  categoryStats.predict
-                }
+           */
+          val centroidForCategories = Range(0, numCategories).map { 
featureValue =>
+            val categoryStats =
+              binAggregates.getImpurityCalculator(nodeFeatureOffset, 
featureValue)
+            val centroid = if (categoryStats.count != 0) {
+              if (binAggregates.metadata.isMulticlass) {
+                // multiclass classification
+                // For categorical variables in multiclass classification,
+                // the bins are ordered by the impurity of their corresponding 
labels.
+                categoryStats.calculate()
+              } else if (binAggregates.metadata.isClassification) {
+                // binary classification
+                // For categorical variables in binary classification,
+                // the bins are ordered by the count of class 1.
+                categoryStats.stats(1)
               } else {
-                Double.MaxValue
+                // regression
+                // For categorical variables in regression and binary 
classification,
+                // the bins are ordered by the prediction.
+                categoryStats.predict
               }
-              (featureValue, centroid)
+            } else {
+              Double.MaxValue
             }
+            (featureValue, centroid)
+          }
 
-            logDebug(
-              s"Centroids for categorical variable: " +
-                s"${centroidForCategories.mkString(",")}")
-
-            // bins sorted by centroids
-            val categoriesSortedByCentroid = 
centroidForCategories.toList.sortBy(_._2)
-
-            logDebug(
-              s"Sorted centroids for categorical variable = " +
-                s"${categoriesSortedByCentroid.mkString(",")}")
-
-            // Cumulative sum (scanLeft) of bin statistics.
-            // Afterwards, binAggregates for a bin is the sum of aggregates for
-            // that bin + all preceding bins.
-            var splitIndex = 0
-            while (splitIndex < numSplits) {
-              val currentCategory = categoriesSortedByCentroid(splitIndex)._1
-              val nextCategory = categoriesSortedByCentroid(splitIndex + 1)._1
-              binAggregates.mergeForFeature(nodeFeatureOffset, nextCategory, 
currentCategory)
-              splitIndex += 1
-            }
-            // lastCategory = index of bin with total aggregates for this 
(node, feature)
-            val lastCategory = categoriesSortedByCentroid.last._1
-            // Find best split.
-            val (bestFeatureSplitIndex, bestFeatureGainStats) =
-              Range(0, numSplits)
-                .map { splitIndex =>
-                  val featureValue = categoriesSortedByCentroid(splitIndex)._1
-                  val leftChildStats =
-                    binAggregates.getImpurityCalculator(nodeFeatureOffset, 
featureValue)
-                  val rightChildStats =
-                    binAggregates.getImpurityCalculator(nodeFeatureOffset, 
lastCategory)
-                  rightChildStats.subtract(leftChildStats)
-                  gainAndImpurityStats = calculateImpurityStats(
-                    gainAndImpurityStats,
-                    leftChildStats,
-                    rightChildStats,
-                    binAggregates.metadata)
-                  (splitIndex, gainAndImpurityStats)
-                }
-                .maxBy(_._2.gain)
-            val categoriesForSplit =
-              categoriesSortedByCentroid.map(_._1.toDouble).slice(0, 
bestFeatureSplitIndex + 1)
-            val bestFeatureSplit =
-              new CategoricalSplit(featureIndex, categoriesForSplit.toArray, 
numCategories)
-            (bestFeatureSplit, bestFeatureGainStats)
+          logDebug(s"Centroids for categorical variable: " +
+            s"${centroidForCategories.mkString(",")}")
+
+          // bins sorted by centroids
+          val categoriesSortedByCentroid = 
centroidForCategories.toList.sortBy(_._2)
+
+          logDebug(s"Sorted centroids for categorical variable = " +
+            s"${categoriesSortedByCentroid.mkString(",")}")
+
+          // Cumulative sum (scanLeft) of bin statistics.
+          // Afterwards, binAggregates for a bin is the sum of aggregates for
+          // that bin + all preceding bins.
+          var splitIndex = 0
+          while (splitIndex < numSplits) {
+            val currentCategory = categoriesSortedByCentroid(splitIndex)._1
+            val nextCategory = categoriesSortedByCentroid(splitIndex + 1)._1
+            binAggregates.mergeForFeature(nodeFeatureOffset, nextCategory, 
currentCategory)
+            splitIndex += 1
           }
+          // lastCategory = index of bin with total aggregates for this (node, 
feature)
+          val lastCategory = categoriesSortedByCentroid.last._1
+          // Find best split.
+          val (bestFeatureSplitIndex, bestFeatureGainStats) =
+            Range(0, numSplits).map { splitIndex =>
+              val featureValue = categoriesSortedByCentroid(splitIndex)._1
+              val leftChildStats =
+                binAggregates.getImpurityCalculator(nodeFeatureOffset, 
featureValue)
+              val rightChildStats =
+                binAggregates.getImpurityCalculator(nodeFeatureOffset, 
lastCategory)
+              rightChildStats.subtract(leftChildStats)
+              gainAndImpurityStats = 
calculateImpurityStats(gainAndImpurityStats,
+                leftChildStats, rightChildStats, binAggregates.metadata)
+              (splitIndex, gainAndImpurityStats)
+            }.maxBy(_._2.gain)
+          val categoriesForSplit =
+            categoriesSortedByCentroid.map(_._1.toDouble).slice(0, 
bestFeatureSplitIndex + 1)
+          val bestFeatureSplit =
+            new CategoricalSplit(featureIndex, categoriesForSplit.toArray, 
numCategories)
+          (bestFeatureSplit, bestFeatureGainStats)
+        }
       }
 
     val (bestSplit, bestSplitStats) =
@@ -1087,13 +989,11 @@ private[spark] object RandomForest extends Logging with 
Serializable {
         val dummyFeatureIndex = featuresForNode.map(_.head).getOrElse(0)
         val parentImpurityCalculator = 
binAggregates.getParentImpurityCalculator()
         if (binAggregates.metadata.isContinuous(dummyFeatureIndex)) {
-          (
-            new ContinuousSplit(dummyFeatureIndex, 0),
+          (new ContinuousSplit(dummyFeatureIndex, 0),
             ImpurityStats.getInvalidImpurityStats(parentImpurityCalculator))
         } else {
           val numCategories = 
binAggregates.metadata.featureArity(dummyFeatureIndex)
-          (
-            new CategoricalSplit(dummyFeatureIndex, Array(), numCategories),
+          (new CategoricalSplit(dummyFeatureIndex, Array(), numCategories),
             ImpurityStats.getInvalidImpurityStats(parentImpurityCalculator))
         }
       } else {
@@ -1166,34 +1066,27 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       // being spun up that will definitely do no work.
       val numPartitions = math.min(continuousFeatures.length, 
input.partitions.length)
 
-      input
-        .flatMap { point =>
-          continuousFeatures.iterator
-            .map(idx => (idx, (point.features(idx), point.weight)))
-            .filter(_._2._1 != 0.0)
-        }
-        .aggregateByKey((new OpenHashMap[Double, Double], 0L), numPartitions)(
-          seqOp = {
-            case ((map, c), (v, w)) =>
-              map.changeValue(v, w, _ + w)
-              (map, c + 1L)
-          },
-          combOp = {
-            case ((map1, c1), (map2, c2)) =>
-              map2.foreach {
-                case (v, w) =>
-                  map1.changeValue(v, w, _ + w)
-              }
-              (map1, c1 + c2)
-          })
-        .map {
-          case (idx, (map, c)) =>
-            val thresholds = findSplitsForContinuousFeature(map.toMap, c, 
metadata, idx)
-            val splits: Array[Split] = thresholds.map(thresh => new 
ContinuousSplit(idx, thresh))
-            logDebug(s"featureIndex = $idx, numSplits = ${splits.length}")
-            (idx, splits)
+      input.flatMap { point =>
+        continuousFeatures.iterator
+          .map(idx => (idx, (point.features(idx), point.weight)))
+          .filter(_._2._1 != 0.0)
+      }.aggregateByKey((new OpenHashMap[Double, Double], 0L), numPartitions)(
+        seqOp = { case ((map, c), (v, w)) =>
+          map.changeValue(v, w, _ + w)
+          (map, c + 1L)
+        },
+        combOp = { case ((map1, c1), (map2, c2)) =>
+          map2.foreach { case (v, w) =>
+            map1.changeValue(v, w, _ + w)
+          }
+          (map1, c1 + c2)
         }
-        .collectAsMap()
+      ).map { case (idx, (map, c)) =>
+        val thresholds = findSplitsForContinuousFeature(map.toMap, c, 
metadata, idx)
+        val splits: Array[Split] = thresholds.map(thresh => new 
ContinuousSplit(idx, thresh))
+        logDebug(s"featureIndex = $idx, numSplits = ${splits.length}")
+        (idx, splits)
+      }.collectAsMap()
     } else Map.empty[Int, Array[Split]]
 
     val numFeatures = metadata.numFeatures
@@ -1264,10 +1157,9 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       featureIndex: Int): Array[Double] = {
     val valueWeights = new OpenHashMap[Double, Double]
     var count = 0L
-    featureSamples.foreach {
-      case (weight, value) =>
-        valueWeights.changeValue(value, weight, _ + weight)
-        count += 1L
+    featureSamples.foreach { case (weight, value) =>
+      valueWeights.changeValue(value, weight, _ + weight)
+      count += 1L
     }
     findSplitsForContinuousFeature(valueWeights.toMap, count, metadata, 
featureIndex)
   }
@@ -1290,8 +1182,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       count: Long,
       metadata: DecisionTreeMetadata,
       featureIndex: Int): Array[Double] = {
-    require(
-      metadata.isContinuous(featureIndex),
+    require(metadata.isContinuous(featureIndex),
       "findSplitsForContinuousFeature can only be used to find splits for a 
continuous feature.")
 
     val splits = if (partValueWeights.isEmpty) {
@@ -1365,8 +1256,7 @@ private[spark] object RandomForest extends Logging with 
Serializable {
 
   private[tree] class NodeIndexInfo(
       val nodeIndexInGroup: Int,
-      val featureSubset: Option[Array[Int]])
-      extends Serializable
+      val featureSubset: Option[Array[Int]]) extends Serializable
 
   /**
    * Pull nodes off of the queue, and collect a group of nodes to be split on 
this iteration.
@@ -1404,13 +1294,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       val (treeIndex, node) = nodeStack.head
       // Choose subset of features for node (if subsampling).
       val featureSubset: Option[Array[Int]] = if 
(metadata.subsamplingFeatures) {
-        Some(
-          SamplingUtils
-            .reservoirSampleAndCount(
-              Range(0, metadata.numFeatures).iterator,
-              metadata.numFeaturesPerNode,
-              rng.nextLong())
-            ._1)
+        Some(SamplingUtils.reservoirSampleAndCount(Range(0,
+          metadata.numFeatures).iterator, metadata.numFeaturesPerNode, 
rng.nextLong())._1)
       } else {
         None
       }
@@ -1418,13 +1303,11 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       val nodeMemUsage = RandomForest.aggregateSizeForNode(metadata, 
featureSubset) * 8L
       if (memUsage + nodeMemUsage <= maxMemoryUsage || memUsage == 0) {
         nodeStack.remove(0)
-        mutableNodesForGroup.getOrElseUpdate(
-          treeIndex,
-          new mutable.ArrayBuffer[LearningNode]()) +=
+        mutableNodesForGroup.getOrElseUpdate(treeIndex, new 
mutable.ArrayBuffer[LearningNode]()) +=
           node
         mutableTreeToNodeToIndexInfo
-          .getOrElseUpdate(treeIndex, new mutable.HashMap[Int, 
NodeIndexInfo]())(node.id) =
-          new NodeIndexInfo(numNodesInGroup, featureSubset)
+          .getOrElseUpdate(treeIndex, new mutable.HashMap[Int, 
NodeIndexInfo]())(node.id)
+          = new NodeIndexInfo(numNodesInGroup, featureSubset)
         numNodesInGroup += 1
         memUsage += nodeMemUsage
       } else {
@@ -1472,7 +1355,8 @@ private[spark] object RandomForest extends Logging with 
Serializable {
    * @param metadata decision tree metadata
    * @return subsample fraction
    */
-  private def samplesFractionForFindSplits(metadata: DecisionTreeMetadata): 
Double = {
+  private def samplesFractionForFindSplits(
+      metadata: DecisionTreeMetadata): Double = {
     // Calculate the number of samples for approximate quantile calculation.
     val requiredSamples = math.max(metadata.maxBins * metadata.maxBins, 10000)
     if (requiredSamples < metadata.numExamples) {
@@ -1481,5 +1365,4 @@ private[spark] object RandomForest extends Logging with 
Serializable {
       1.0
     }
   }
-
 }
diff --git a/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala 
b/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala
index e5f542366be7..768e14f4b74e 100644
--- a/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala
+++ b/mllib/src/main/scala/org/apache/spark/ml/tree/treeParams.scala
@@ -211,27 +211,10 @@ private[ml] trait TreeClassifierParams extends Params {
     (value: String) =>
       
TreeClassifierParams.supportedImpurities.contains(value.toLowerCase(Locale.ROOT)))
 
-  /**
-   * If true, the trained tree will undergo a pruning process after training, 
in which nodes
-   * with the same class predictions are merged. The resulting tree will be 
smaller and have
-   * faster predictions, but class probabilities will be lost.
-   * If false, no pruning is applied after training, and class probabilities 
are preserved.
-   * (default = true)
-   * @group param
-   */
-  final val pruneTree: BooleanParam = new BooleanParam(this, "pruneTree", "" +
-    "If true, the trained tree will undergo a pruning process after training, 
in which nodes" +
-    " with the same class predictions are merged. The resulting tree will be 
smaller and have" +
-    " faster predictions, but class probabilities will be lost." +
-    " If false, no pruning is applied after training, and class probabilities 
are preserved."
-  )
-
-  setDefault(impurity -> "gini", pruneTree -> true)
+  setDefault(impurity -> "gini")
 
   /** @group getParam */
   final def getImpurity: String = $(impurity).toLowerCase(Locale.ROOT)
-  /** @group getParam */
-  final def getPruneTree: Boolean = $(pruneTree)
 
   /** Convert new impurity to old impurity. */
   private[ml] def getOldImpurity: OldImpurity = {
diff --git 
a/mllib/src/main/scala/org/apache/spark/mllib/tree/configuration/Strategy.scala 
b/mllib/src/main/scala/org/apache/spark/mllib/tree/configuration/Strategy.scala
index 85f4bcc64267..200d10130eed 100644
--- 
a/mllib/src/main/scala/org/apache/spark/mllib/tree/configuration/Strategy.scala
+++ 
b/mllib/src/main/scala/org/apache/spark/mllib/tree/configuration/Strategy.scala
@@ -55,8 +55,6 @@ import org.apache.spark.mllib.tree.impurity.{Entropy, Gini, 
Impurity, Variance}
  * @param minInfoGain Minimum information gain a split must get. Default value 
is 0.0.
  *                    If a split has less information gain than minInfoGain,
  *                    this split will not be considered as a valid split.
- * @param pruneTree If this is true, the final training tree will undergo a 
pruning in which
- *                  nodes with the same classifications are merged.
  * @param maxMemoryInMB Maximum memory in MB allocated to histogram 
aggregation. Default value is
  *                      256 MB.  If too small, then 1 node will be split per 
iteration, and
  *                      its aggregates may exceed this size.
@@ -79,7 +77,6 @@ class Strategy @Since("1.3.0") (
     @Since("1.0.0") @BeanProperty var categoricalFeaturesInfo: Map[Int, Int] = 
Map[Int, Int](),
     @Since("1.2.0") @BeanProperty var minInstancesPerNode: Int = 1,
     @Since("1.2.0") @BeanProperty var minInfoGain: Double = 0.0,
-    @Since("4.3.0") @BeanProperty var pruneTree: Boolean = true,
     @Since("1.0.0") @BeanProperty var maxMemoryInMB: Int = 256,
     @Since("1.2.0") @BeanProperty var subsamplingRate: Double = 1,
     @Since("1.2.0") @BeanProperty var useNodeIdCache: Boolean = false,
@@ -116,13 +113,12 @@ class Strategy @Since("1.3.0") (
       categoricalFeaturesInfo: Map[Int, Int],
       minInstancesPerNode: Int,
       minInfoGain: Double,
-      pruneTree: Boolean,
       maxMemoryInMB: Int,
       subsamplingRate: Double,
       useNodeIdCache: Boolean,
       checkpointInterval: Int) = {
     this(algo, impurity, maxDepth, numClasses, maxBins, 
quantileCalculationStrategy,
-      categoricalFeaturesInfo, minInstancesPerNode, minInfoGain, pruneTree, 
maxMemoryInMB,
+      categoricalFeaturesInfo, minInstancesPerNode, minInfoGain, maxMemoryInMB,
       subsamplingRate, useNodeIdCache, checkpointInterval, 0.0)
   }
   // scalastyle:on argcount
@@ -204,7 +200,7 @@ class Strategy @Since("1.3.0") (
   def copy: Strategy = {
     new Strategy(algo, impurity, maxDepth, numClasses, maxBins,
       quantileCalculationStrategy, categoricalFeaturesInfo, 
minInstancesPerNode,
-      minInfoGain, pruneTree, maxMemoryInMB, subsamplingRate, useNodeIdCache,
+      minInfoGain, maxMemoryInMB, subsamplingRate, useNodeIdCache,
       checkpointInterval, minWeightFractionPerNode)
   }
 }
diff --git 
a/mllib/src/test/scala/org/apache/spark/ml/tree/impl/RandomForestSuite.scala 
b/mllib/src/test/scala/org/apache/spark/ml/tree/impl/RandomForestSuite.scala
index 0c6044181315..62f25474e947 100644
--- a/mllib/src/test/scala/org/apache/spark/ml/tree/impl/RandomForestSuite.scala
+++ b/mllib/src/test/scala/org/apache/spark/ml/tree/impl/RandomForestSuite.scala
@@ -72,9 +72,8 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     assert(splits(0).length === 0)
   }
 
-  test(
-    "Binary classification with 3-ary (ordered) categorical features," +
-      " with no samples for one category: split calculation") {
+  test("Binary classification with 3-ary (ordered) categorical features," +
+    " with no samples for one category: split calculation") {
     val arr = OldDTSuite.generateCategoricalDataPoints().map(_.asML.toInstance)
     assert(arr.length === 1000)
     val rdd = sc.parallelize(arr.toImmutableArraySeq)
@@ -109,29 +108,16 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
     // SPARK-16957: Use midpoints for split values.
     {
-      val fakeMetadata = new DecisionTreeMetadata(
-        1,
-        8,
-        8.0,
-        0,
-        0,
-        Map(),
-        Set(),
-        Array(3),
-        Gini,
-        QuantileStrategy.Sort,
-        0,
-        0,
-        0.0,
-        0.0,
-        0,
-        0)
+      val fakeMetadata = new DecisionTreeMetadata(1, 8, 8.0, 0, 0,
+        Map(), Set(),
+        Array(3), Gini, QuantileStrategy.Sort,
+        0, 0, 0.0, 0.0, 0, 0
+      )
 
       // possibleSplits <= numSplits
       {
         val featureSamples = Array(0, 1, 0, 0, 1, 0, 1, 1)
-          .map(x => (1.0, x.toDouble))
-          .filter(_._2 != 0.0)
+          .map(x => (1.0, x.toDouble)).filter(_._2 != 0.0)
         val splits = 
RandomForest.findSplitsForContinuousFeature(featureSamples, fakeMetadata, 0)
         val expectedSplits = Array((0.0 + 1.0) / 2)
         assert(splits === expectedSplits)
@@ -140,8 +126,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
       // possibleSplits > numSplits
       {
         val featureSamples = Array(0, 0, 1, 1, 2, 2, 3, 3)
-          .map(x => (1.0, x.toDouble))
-          .filter(_._2 != 0.0)
+          .map(x => (1.0, x.toDouble)).filter(_._2 != 0.0)
         val splits = 
RandomForest.findSplitsForContinuousFeature(featureSamples, fakeMetadata, 0)
         val expectedSplits = Array((0.0 + 1.0) / 2, (2.0 + 3.0) / 2)
         assert(splits === expectedSplits)
@@ -151,23 +136,11 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     // find splits should not return identical splits
     // when there are not enough split candidates, reduce the number of splits 
in metadata
     {
-      val fakeMetadata = new DecisionTreeMetadata(
-        1,
-        12,
-        12.0,
-        0,
-        0,
-        Map(),
-        Set(),
-        Array(5),
-        Gini,
-        QuantileStrategy.Sort,
-        0,
-        0,
-        0.0,
-        0.0,
-        0,
-        0)
+      val fakeMetadata = new DecisionTreeMetadata(1, 12, 12.0, 0, 0,
+        Map(), Set(),
+        Array(5), Gini, QuantileStrategy.Sort,
+        0, 0, 0.0, 0.0, 0, 0
+      )
       val featureSamples = Array(1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3).map(x => 
(1.0, x.toDouble))
       val splits = RandomForest.findSplitsForContinuousFeature(featureSamples, 
fakeMetadata, 0)
       val expectedSplits = Array((1.0 + 2.0) / 2, (2.0 + 3.0) / 2)
@@ -178,23 +151,11 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
     // find splits when most samples close to the minimum
     {
-      val fakeMetadata = new DecisionTreeMetadata(
-        1,
-        18,
-        18.0,
-        0,
-        0,
-        Map(),
-        Set(),
-        Array(3),
-        Gini,
-        QuantileStrategy.Sort,
-        0,
-        0,
-        0.0,
-        0.0,
-        0,
-        0)
+      val fakeMetadata = new DecisionTreeMetadata(1, 18, 18.0, 0, 0,
+        Map(), Set(),
+        Array(3), Gini, QuantileStrategy.Sort,
+        0, 0, 0.0, 0.0, 0, 0
+      )
       val featureSamples =
         Array(2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 4, 5).map(x => 
(1.0, x.toDouble))
       val splits = RandomForest.findSplitsForContinuousFeature(featureSamples, 
fakeMetadata, 0)
@@ -204,23 +165,11 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
     // find splits when most samples close to the maximum
     {
-      val fakeMetadata = new DecisionTreeMetadata(
-        1,
-        17,
-        17.0,
-        0,
-        0,
-        Map(),
-        Set(),
-        Array(2),
-        Gini,
-        QuantileStrategy.Sort,
-        0,
-        0,
-        0.0,
-        0.0,
-        0,
-        0)
+      val fakeMetadata = new DecisionTreeMetadata(1, 17, 17.0, 0, 0,
+        Map(), Set(),
+        Array(2), Gini, QuantileStrategy.Sort,
+        0, 0, 0.0, 0.0, 0, 0
+      )
       val featureSamples =
         Array(0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2).map(x => 
(1.0, x.toDouble))
       val splits = RandomForest.findSplitsForContinuousFeature(featureSamples, 
fakeMetadata, 0)
@@ -250,23 +199,11 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
     // find splits when most weight is close to the minimum
     {
-      val fakeMetadata = new DecisionTreeMetadata(
-        1,
-        0,
-        0.0,
-        0,
-        0,
-        Map(),
-        Set(),
-        Array(3),
-        Gini,
-        QuantileStrategy.Sort,
-        0,
-        0,
-        0.0,
-        0.0,
-        0,
-        0)
+      val fakeMetadata = new DecisionTreeMetadata(1, 0, 0.0, 0, 0,
+        Map(), Set(),
+        Array(3), Gini, QuantileStrategy.Sort,
+        0, 0, 0.0, 0.0, 0, 0
+      )
       val featureSamples = Array((10, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 
6)).map {
         case (w, x) => (w.toDouble, x.toDouble)
       }
@@ -280,10 +217,10 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     val data = Array.fill(5)(lp)
     val rdd = sc.parallelize(data.toImmutableArraySeq)
 
-    val strategy = new OldStrategy(OldAlgo.Regression, Gini, maxDepth = 2, 
maxBins = 5)
-    withClue(
-      "DecisionTree requires number of features > 0," +
-        " but was given an empty features vector") {
+    val strategy = new OldStrategy(OldAlgo.Regression, Gini, maxDepth = 2,
+      maxBins = 5)
+    withClue("DecisionTree requires number of features > 0," +
+      " but was given an empty features vector") {
       intercept[IllegalArgumentException] {
         RandomForest.run(rdd, strategy, 1, "all", 42L, instr = None)
       }
@@ -295,19 +232,23 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     val data = Array.fill(5)(instance)
     val rdd = sc.parallelize(data.toImmutableArraySeq)
     val strategy = new OldStrategy(
-      OldAlgo.Classification,
-      Gini,
-      maxDepth = 2,
-      numClasses = 2,
-      maxBins = 5,
-      categoricalFeaturesInfo = Map(0 -> 1, 1 -> 5))
+          OldAlgo.Classification,
+          Gini,
+          maxDepth = 2,
+          numClasses = 2,
+          maxBins = 5,
+          categoricalFeaturesInfo = Map(0 -> 1, 1 -> 5))
     val Array(tree) = RandomForest.run(rdd, strategy, 1, "all", 42L, instr = 
None)
     assert(tree.rootNode.impurity === -1.0)
     assert(tree.depth === 0)
     assert(tree.rootNode.prediction === instance.label)
 
     // Test with no categorical features
-    val strategy2 = new OldStrategy(OldAlgo.Regression, Variance, maxDepth = 
2, maxBins = 5)
+    val strategy2 = new OldStrategy(
+      OldAlgo.Regression,
+      Variance,
+      maxDepth = 2,
+      maxBins = 5)
     val Array(tree2) = RandomForest.run(rdd, strategy2, 1, "all", 42L, instr = 
None)
     assert(tree2.rootNode.impurity === -1.0)
     assert(tree2.depth === 0)
@@ -338,15 +279,12 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     assert(metadata.numBins(1) === 3)
 
     // Expecting 2^2 - 1 = 3 splits per feature
-    def checkCategoricalSplit(
-        s: Split,
-        featureIndex: Int,
-        leftCategories: Array[Double]): Unit = {
+    def checkCategoricalSplit(s: Split, featureIndex: Int, leftCategories: 
Array[Double]): Unit = {
       assert(s.featureIndex === featureIndex)
       assert(s.isInstanceOf[CategoricalSplit])
       val s0 = s.asInstanceOf[CategoricalSplit]
       assert(s0.leftCategories === leftCategories)
-      assert(s0.numCategories === 3) // for this unit test
+      assert(s0.numCategories === 3)  // for this unit test
     }
     // Feature 0
     checkCategoricalSplit(splits(0)(0), 0, Array(0.0))
@@ -359,8 +297,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
   }
 
   test("Multiclass classification with ordered categorical features: split 
calculations") {
-    val arr = OldDTSuite
-      .generateCategoricalDataPointsForMulticlassForOrderedFeatures()
+    val arr = 
OldDTSuite.generateCategoricalDataPointsForMulticlassForOrderedFeatures()
       .map(_.asML.toInstance)
     assert(arr.length === 3000)
     val rdd = sc.parallelize(arr.toImmutableArraySeq)
@@ -395,12 +332,8 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
       LabeledPoint(1.0, Vectors.dense(0.0, 2.0, 1.0)))
     val input = sc.parallelize(arr.map(_.toInstance).toImmutableArraySeq)
 
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Classification,
-      impurity = Gini,
-      maxDepth = 1,
-      numClasses = 2,
-      categoricalFeaturesInfo = Map(0 -> 3))
+    val strategy = new OldStrategy(algo = OldAlgo.Classification, impurity = 
Gini, maxDepth = 1,
+      numClasses = 2, categoricalFeaturesInfo = Map(0 -> 3))
     val metadata = DecisionTreeMetadata.buildMetadata(input, strategy)
     val splits = RandomForest.findSplits(input, metadata, seed = 42)
     val bcSplits = input.sparkContext.broadcast(splits)
@@ -413,17 +346,12 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     assert(topNode.stats === null)
 
     val nodesForGroup = Map(0 -> Array(topNode))
-    val treeToNodeToIndexInfo =
-      Map(0 -> Map(topNode.id -> new RandomForest.NodeIndexInfo(0, None)))
+    val treeToNodeToIndexInfo = Map(0 -> Map(
+      topNode.id -> new RandomForest.NodeIndexInfo(0, None)
+    ))
     val nodeStack = new mutable.ListBuffer[(Int, LearningNode)]
-    RandomForest.findBestSplits(
-      baggedInput,
-      metadata,
-      Map(0 -> topNode),
-      nodesForGroup,
-      treeToNodeToIndexInfo,
-      bcSplits,
-      nodeStack)
+    RandomForest.findBestSplits(baggedInput, metadata, Map(0 -> topNode),
+      nodesForGroup, treeToNodeToIndexInfo, bcSplits, nodeStack)
     bcSplits.destroy()
 
     // don't enqueue leaf nodes into node queue
@@ -448,12 +376,8 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
       LabeledPoint(1.0, Vectors.dense(0.0, 2.0, 1.0)))
     val input = sc.parallelize(arr.map(_.toInstance).toImmutableArraySeq)
 
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Classification,
-      impurity = Gini,
-      maxDepth = 5,
-      numClasses = 2,
-      categoricalFeaturesInfo = Map(0 -> 3))
+    val strategy = new OldStrategy(algo = OldAlgo.Classification, impurity = 
Gini, maxDepth = 5,
+      numClasses = 2, categoricalFeaturesInfo = Map(0 -> 3))
     val metadata = DecisionTreeMetadata.buildMetadata(input, strategy)
     val splits = RandomForest.findSplits(input, metadata, seed = 42)
     val bcSplits = input.sparkContext.broadcast(splits)
@@ -466,17 +390,12 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     assert(topNode.stats === null)
 
     val nodesForGroup = Map(0 -> Array(topNode))
-    val treeToNodeToIndexInfo =
-      Map(0 -> Map(topNode.id -> new RandomForest.NodeIndexInfo(0, None)))
+    val treeToNodeToIndexInfo = Map(0 -> Map(
+      topNode.id -> new RandomForest.NodeIndexInfo(0, None)
+    ))
     val nodeStack = new mutable.ListBuffer[(Int, LearningNode)]
-    RandomForest.findBestSplits(
-      baggedInput,
-      metadata,
-      Map(0 -> topNode),
-      nodesForGroup,
-      treeToNodeToIndexInfo,
-      bcSplits,
-      nodeStack)
+    RandomForest.findBestSplits(baggedInput, metadata, Map(0 -> topNode),
+      nodesForGroup, treeToNodeToIndexInfo, bcSplits, nodeStack)
     bcSplits.destroy()
 
     // don't enqueue a node into node queue if its impurity is 0.0
@@ -512,32 +431,18 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     val input = sc.parallelize(arr.map(_.toInstance).toImmutableArraySeq)
 
     // Must set maxBins s.t. the feature will be treated as an ordered 
categorical feature.
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Classification,
-      impurity = Gini,
-      maxDepth = 1,
-      numClasses = 2,
-      categoricalFeaturesInfo = Map(0 -> 3),
-      maxBins = 3)
-
-    strategy.pruneTree = false
-    val model = RandomForest
-      .run(
-        input,
-        strategy,
-        numTrees = 1,
-        featureSubsetStrategy = "all",
-        seed = 42,
-        instr = None)
-      .head
+    val strategy = new OldStrategy(algo = OldAlgo.Classification, impurity = 
Gini, maxDepth = 1,
+      numClasses = 2, categoricalFeaturesInfo = Map(0 -> 3), maxBins = 3)
+
+    val model = RandomForest.run(input, strategy, numTrees = 1, 
featureSubsetStrategy = "all",
+      seed = 42, instr = None, prune = false).head
 
     model.rootNode match {
-      case n: InternalNode =>
-        n.split match {
-          case s: CategoricalSplit =>
-            assert(s.leftCategories === Array(1.0))
-          case _ => fail("model.rootNode.split was not a CategoricalSplit")
-        }
+      case n: InternalNode => n.split match {
+        case s: CategoricalSplit =>
+          assert(s.leftCategories === Array(1.0))
+        case _ => fail("model.rootNode.split was not a CategoricalSplit")
+      }
       case _ => fail("model.rootNode was not an InternalNode")
     }
   }
@@ -553,21 +458,18 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     val strategy2 =
       new OldStrategy(OldAlgo.Classification, Entropy, 3, 2, 100, 
maxMemoryInMB = 0)
 
-    val tree1 = RandomForest
-      .run(rdd, strategy1, numTrees = 1, featureSubsetStrategy = "all", seed = 
42, instr = None)
-      .head
-    val tree2 = RandomForest
-      .run(rdd, strategy2, numTrees = 1, featureSubsetStrategy = "all", seed = 
42, instr = None)
-      .head
-
-    def getChildren(rootNode: Node): Array[InternalNode] =
-      rootNode match {
-        case n: InternalNode =>
-          assert(n.leftChild.isInstanceOf[InternalNode])
-          assert(n.rightChild.isInstanceOf[InternalNode])
-          Array(n.leftChild.asInstanceOf[InternalNode], 
n.rightChild.asInstanceOf[InternalNode])
-        case _ => fail("rootNode was not an InternalNode")
-      }
+    val tree1 = RandomForest.run(rdd, strategy1, numTrees = 1, 
featureSubsetStrategy = "all",
+      seed = 42, instr = None).head
+    val tree2 = RandomForest.run(rdd, strategy2, numTrees = 1, 
featureSubsetStrategy = "all",
+      seed = 42, instr = None).head
+
+    def getChildren(rootNode: Node): Array[InternalNode] = rootNode match {
+      case n: InternalNode =>
+        assert(n.leftChild.isInstanceOf[InternalNode])
+        assert(n.rightChild.isInstanceOf[InternalNode])
+        Array(n.leftChild.asInstanceOf[InternalNode], 
n.rightChild.asInstanceOf[InternalNode])
+      case _ => fail("rootNode was not an InternalNode")
+    }
 
     // Single group second level tree construction.
     val children1 = getChildren(tree1.rootNode)
@@ -613,9 +515,8 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
           nodeStack.prepend((treeIndex, topNodes(treeIndex)))
         }
         val rng = new scala.util.Random(seed = seed)
-        val (
-          nodesForGroup: Map[Int, Array[LearningNode]],
-          treeToNodeToIndexInfo: Map[Int, Map[Int, 
RandomForest.NodeIndexInfo]]) =
+        val (nodesForGroup: Map[Int, Array[LearningNode]],
+        treeToNodeToIndexInfo: Map[Int, Map[Int, RandomForest.NodeIndexInfo]]) 
=
           RandomForest.selectNodesToSplit(nodeStack, maxMemoryUsage, metadata, 
rng)
 
         assert(nodesForGroup.size === numTrees, failString)
@@ -623,15 +524,12 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
         if (numFeaturesPerNode == numFeatures) {
           // featureSubset values should all be None
-          assert(
-            
treeToNodeToIndexInfo.values.forall(_.values.forall(_.featureSubset.isEmpty)),
+          
assert(treeToNodeToIndexInfo.values.forall(_.values.forall(_.featureSubset.isEmpty)),
             failString)
         } else {
           // Check number of features.
-          assert(
-            treeToNodeToIndexInfo.values.forall(
-              _.values.forall(_.featureSubset.get.length === 
numFeaturesPerNode)),
-            failString)
+          assert(treeToNodeToIndexInfo.values.forall(_.values.forall(
+            _.featureSubset.get.length === numFeaturesPerNode)), failString)
         }
       }
     }
@@ -639,9 +537,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     checkFeatureSubsetStrategy(numTrees = 1, "auto", numFeatures)
     checkFeatureSubsetStrategy(numTrees = 1, "all", numFeatures)
     checkFeatureSubsetStrategy(numTrees = 1, "sqrt", 
math.sqrt(numFeatures).ceil.toInt)
-    checkFeatureSubsetStrategy(
-      numTrees = 1,
-      "log2",
+    checkFeatureSubsetStrategy(numTrees = 1, "log2",
       (math.log(numFeatures) / math.log(2)).ceil.toInt)
     checkFeatureSubsetStrategy(numTrees = 1, "onethird", (numFeatures / 
3.0).ceil.toInt)
 
@@ -659,7 +555,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
     val invalidStrategies = Array("-.1", "-.10", "-0.10", ".0", "0.0", "1.1", 
"0")
     for (invalidStrategy <- invalidStrategies) {
-      intercept[IllegalArgumentException] {
+      intercept[IllegalArgumentException]{
         val metadata =
           DecisionTreeMetadata.buildMetadata(rdd, strategy, numTrees = 1, 
invalidStrategy)
       }
@@ -668,9 +564,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     checkFeatureSubsetStrategy(numTrees = 2, "all", numFeatures)
     checkFeatureSubsetStrategy(numTrees = 2, "auto", 
math.sqrt(numFeatures).ceil.toInt)
     checkFeatureSubsetStrategy(numTrees = 2, "sqrt", 
math.sqrt(numFeatures).ceil.toInt)
-    checkFeatureSubsetStrategy(
-      numTrees = 2,
-      "log2",
+    checkFeatureSubsetStrategy(numTrees = 2, "log2",
       (math.log(numFeatures) / math.log(2)).ceil.toInt)
     checkFeatureSubsetStrategy(numTrees = 2, "onethird", (numFeatures / 
3.0).ceil.toInt)
 
@@ -684,7 +578,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
       checkFeatureSubsetStrategy(numTrees = 2, strategy, expected)
     }
     for (invalidStrategy <- invalidStrategies) {
-      intercept[IllegalArgumentException] {
+      intercept[IllegalArgumentException]{
         val metadata =
           DecisionTreeMetadata.buildMetadata(rdd, strategy, numTrees = 2, 
invalidStrategy)
       }
@@ -693,23 +587,15 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 
   test("Binary classification with continuous features: subsampling features") 
{
     val categoricalFeaturesInfo = Map.empty[Int, Int]
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Classification,
-      impurity = Gini,
-      maxDepth = 2,
-      numClasses = 2,
-      categoricalFeaturesInfo = categoricalFeaturesInfo)
+    val strategy = new OldStrategy(algo = OldAlgo.Classification, impurity = 
Gini, maxDepth = 2,
+      numClasses = 2, categoricalFeaturesInfo = categoricalFeaturesInfo)
     
binaryClassificationTestWithContinuousFeaturesAndSubsampledFeatures(strategy)
   }
 
   test("Binary classification with continuous features and node Id cache: 
subsampling features") {
     val categoricalFeaturesInfo = Map.empty[Int, Int]
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Classification,
-      impurity = Gini,
-      maxDepth = 2,
-      numClasses = 2,
-      categoricalFeaturesInfo = categoricalFeaturesInfo,
+    val strategy = new OldStrategy(algo = OldAlgo.Classification, impurity = 
Gini, maxDepth = 2,
+      numClasses = 2, categoricalFeaturesInfo = categoricalFeaturesInfo,
       useNodeIdCache = true)
     
binaryClassificationTestWithContinuousFeaturesAndSubsampledFeatures(strategy)
   }
@@ -762,8 +648,7 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     }
     val importances: Vector = TreeEnsembleModel.featureImportances(trees, 2)
     val tree2norm = feature0importance + feature1importance
-    val expected = Vectors.dense(
-      (1.0 + feature0importance / tree2norm) / 2.0,
+    val expected = Vectors.dense((1.0 + feature0importance / tree2norm) / 2.0,
       (feature1importance / tree2norm) / 2.0)
     assert(importances ~== expected relTol 0.01)
   }
@@ -797,45 +682,18 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     val rdd = sc.parallelize(arr.toImmutableArraySeq)
 
     val numClasses = 2
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Classification,
-      impurity = Gini,
-      maxDepth = 4,
-      numClasses = numClasses,
-      maxBins = 32)
-
-    strategy.pruneTree = true
-    val prunedTree = RandomForest
-      .run(
-        rdd,
-        strategy,
-        numTrees = 1,
-        featureSubsetStrategy = "auto",
-        seed = 42,
-        instr = None)
-      .head
-
-    strategy.pruneTree = false
-    val unprunedTree = RandomForest
-      .run(
-        rdd,
-        strategy,
-        numTrees = 1,
-        featureSubsetStrategy = "auto",
-        seed = 42,
-        instr = None)
-      .head
-
-    strategy.pruneTree = true
-    val defaultBehaviorTree = RandomForest
-      .run(rdd, strategy, numTrees = 1, featureSubsetStrategy = "auto", seed = 
42, instr = None)
-      .head
+    val strategy = new OldStrategy(algo = OldAlgo.Classification, impurity = 
Gini, maxDepth = 4,
+      numClasses = numClasses, maxBins = 32)
+
+    val prunedTree = RandomForest.run(rdd, strategy, numTrees = 1, 
featureSubsetStrategy = "auto",
+      seed = 42, instr = None).head
+
+    val unprunedTree = RandomForest.run(rdd, strategy, numTrees = 1, 
featureSubsetStrategy = "auto",
+      seed = 42, instr = None, prune = false).head
 
     assert(prunedTree.numNodes === 5)
     assert(unprunedTree.numNodes === 7)
 
-    assert(defaultBehaviorTree.numNodes == prunedTree.numNodes)
-
     assert(RandomForestSuite.getSumLeafCounters(List(prunedTree.rootNode)) === 
arr.length)
   }
 
@@ -854,45 +712,17 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     )
     val rdd = sc.parallelize(arr.toImmutableArraySeq)
 
-    val strategy = new OldStrategy(
-      algo = OldAlgo.Regression,
-      impurity = Variance,
-      maxDepth = 4,
-      numClasses = 0,
-      maxBins = 32)
-
-    strategy.pruneTree = true
-    val prunedTree = RandomForest
-      .run(
-        rdd,
-        strategy,
-        numTrees = 1,
-        featureSubsetStrategy = "auto",
-        seed = 42,
-        instr = None)
-      .head
-
-    strategy.pruneTree = false
-    val unprunedTree = RandomForest
-      .run(
-        rdd,
-        strategy,
-        numTrees = 1,
-        featureSubsetStrategy = "auto",
-        seed = 42,
-        instr = None)
-      .head
-
-    strategy.pruneTree = true
-    val defaultBehaviorTree = RandomForest
-      .run(rdd, strategy, numTrees = 1, featureSubsetStrategy = "auto", seed = 
42, instr = None)
-      .head
+    val strategy = new OldStrategy(algo = OldAlgo.Regression, impurity = 
Variance, maxDepth = 4,
+      numClasses = 0, maxBins = 32)
 
-    assert(prunedTree.numNodes === 3)
-    assert(unprunedTree.numNodes === 5)
+    val prunedTree = RandomForest.run(rdd, strategy, numTrees = 1, 
featureSubsetStrategy = "auto",
+      seed = 42, instr = None).head
 
-    assert(defaultBehaviorTree.numNodes == prunedTree.numNodes)
+    val unprunedTree = RandomForest.run(rdd, strategy, numTrees = 1, 
featureSubsetStrategy = "auto",
+      seed = 42, instr = None, prune = false).head
 
+    assert(prunedTree.numNodes === 3)
+    assert(unprunedTree.numNodes === 5)
     assert(RandomForestSuite.getSumLeafCounters(List(prunedTree.rootNode)) === 
arr.length)
   }
 
@@ -909,15 +739,13 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
     val unitWeightTrees = RandomForest.run(rddWithUnitWeights, strategy, 3, 
"all", 42L, None)
 
     val smallWeightTrees = RandomForest.run(rddWithSmallWeights, strategy, 3, 
"all", 42L, None)
-    unitWeightTrees.zip(smallWeightTrees).foreach {
-      case (unitTree, smallWeightTree) =>
-        TreeTests.checkEqual(unitTree, smallWeightTree)
+    unitWeightTrees.zip(smallWeightTrees).foreach { case (unitTree, 
smallWeightTree) =>
+      TreeTests.checkEqual(unitTree, smallWeightTree)
     }
 
     val bigWeightTrees = RandomForest.run(rddWithBigWeights, strategy, 3, 
"all", 42L, None)
-    unitWeightTrees.zip(bigWeightTrees).foreach {
-      case (unitTree, bigWeightTree) =>
-        TreeTests.checkEqual(unitTree, bigWeightTree)
+    unitWeightTrees.zip(bigWeightTrees).foreach { case (unitTree, 
bigWeightTree) =>
+      TreeTests.checkEqual(unitTree, bigWeightTree)
     }
   }
 
@@ -950,7 +778,6 @@ class RandomForestSuite extends SparkFunSuite with 
MLlibTestSparkContext {
 }
 
 private object RandomForestSuite {
-
   def mapToVec(map: Map[Int, Double]): Vector = {
     val size = (map.keys.toSeq :+ 0).max + 1
     val (indices, values) = map.toSeq.sortBy(_._1).unzip
@@ -961,12 +788,12 @@ private object RandomForestSuite {
   private def getSumLeafCounters(nodes: List[Node], acc: Long = 0): Long = {
     if (nodes.isEmpty) {
       acc
-    } else {
+    }
+    else {
       nodes.head match {
         case i: InternalNode => getSumLeafCounters(i.leftChild :: i.rightChild 
:: nodes.tail, acc)
         case l: LeafNode => getSumLeafCounters(nodes.tail, acc + 
l.impurityStats.rawCount)
       }
     }
   }
-
 }
diff --git a/python/pyspark/ml/classification.py 
b/python/pyspark/ml/classification.py
index 8f0646e2b24d..f69ecf115f5a 100644
--- a/python/pyspark/ml/classification.py
+++ b/python/pyspark/ml/classification.py
@@ -1678,7 +1678,6 @@ class _DecisionTreeClassifierParams(_DecisionTreeParams, 
_TreeClassifierParams):
             maxBins=32,
             minInstancesPerNode=1,
             minInfoGain=0.0,
-            pruneTree=True,
             maxMemoryInMB=256,
             cacheNodeIds=False,
             checkpointInterval=10,
@@ -1790,7 +1789,6 @@ class DecisionTreeClassifier(
         maxBins: int = 32,
         minInstancesPerNode: int = 1,
         minInfoGain: float = 0.0,
-        pruneTree: bool = True,
         maxMemoryInMB: int = 256,
         cacheNodeIds: bool = False,
         checkpointInterval: int = 10,
@@ -1803,7 +1801,7 @@ class DecisionTreeClassifier(
         """
         __init__(self, \\*, featuresCol="features", labelCol="label", 
predictionCol="prediction", \
                  probabilityCol="probability", 
rawPredictionCol="rawPrediction", \
-                 maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, pruneTree=True, \
+                 maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, \
                  maxMemoryInMB=256, cacheNodeIds=False, checkpointInterval=10, 
impurity="gini", \
                  seed=None, weightCol=None, leafCol="", 
minWeightFractionPerNode=0.0)
         """
@@ -1828,7 +1826,6 @@ class DecisionTreeClassifier(
         maxBins: int = 32,
         minInstancesPerNode: int = 1,
         minInfoGain: float = 0.0,
-        pruneTree: bool = True,
         maxMemoryInMB: int = 256,
         cacheNodeIds: bool = False,
         checkpointInterval: int = 10,
@@ -1841,7 +1838,7 @@ class DecisionTreeClassifier(
         """
         setParams(self, \\*, featuresCol="features", labelCol="label", 
predictionCol="prediction", \
                   probabilityCol="probability", 
rawPredictionCol="rawPrediction", \
-                  maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, pruneTree=True, \
+                  maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, \
                   maxMemoryInMB=256, cacheNodeIds=False, 
checkpointInterval=10, impurity="gini", \
                   seed=None, weightCol=None, leafCol="", 
minWeightFractionPerNode=0.0)
         Sets params for the DecisionTreeClassifier.
@@ -1864,12 +1861,6 @@ class DecisionTreeClassifier(
         """
         return self._set(maxBins=value)
 
-    def setPruneTree(self, value: bool) -> "DecisionTreeClassifier":
-        """
-        Sets the value of :py:attr:`pruneTree`.
-        """
-        return self._set(pruneTree=value)
-
     def setMinInstancesPerNode(self, value: int) -> "DecisionTreeClassifier":
         """
         Sets the value of :py:attr:`minInstancesPerNode`.
@@ -1981,7 +1972,6 @@ class _RandomForestClassifierParams(_RandomForestParams, 
_TreeClassifierParams):
             maxBins=32,
             minInstancesPerNode=1,
             minInfoGain=0.0,
-            pruneTree=True,
             maxMemoryInMB=256,
             cacheNodeIds=False,
             checkpointInterval=10,
@@ -2091,7 +2081,6 @@ class RandomForestClassifier(
         maxBins: int = 32,
         minInstancesPerNode: int = 1,
         minInfoGain: float = 0.0,
-        pruneTree: bool = True,
         maxMemoryInMB: int = 256,
         cacheNodeIds: bool = False,
         checkpointInterval: int = 10,
@@ -2108,7 +2097,7 @@ class RandomForestClassifier(
         """
         __init__(self, \\*, featuresCol="features", labelCol="label", 
predictionCol="prediction", \
                  probabilityCol="probability", 
rawPredictionCol="rawPrediction", \
-                 maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, pruneTree=True, \
+                 maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, \
                  maxMemoryInMB=256, cacheNodeIds=False, checkpointInterval=10, 
impurity="gini", \
                  numTrees=20, featureSubsetStrategy="auto", seed=None, 
subsamplingRate=1.0, \
                  leafCol="", minWeightFractionPerNode=0.0, weightCol=None, 
bootstrap=True)
@@ -2134,7 +2123,6 @@ class RandomForestClassifier(
         maxBins: int = 32,
         minInstancesPerNode: int = 1,
         minInfoGain: float = 0.0,
-        pruneTree: bool = True,
         maxMemoryInMB: int = 256,
         cacheNodeIds: bool = False,
         checkpointInterval: int = 10,
@@ -2151,7 +2139,7 @@ class RandomForestClassifier(
         """
         setParams(self, featuresCol="features", labelCol="label", 
predictionCol="prediction", \
                  probabilityCol="probability", 
rawPredictionCol="rawPrediction", \
-                  maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, pruneTree=True, \
+                  maxDepth=5, maxBins=32, minInstancesPerNode=1, 
minInfoGain=0.0, \
                   maxMemoryInMB=256, cacheNodeIds=False, 
checkpointInterval=10, seed=None, \
                   impurity="gini", numTrees=20, featureSubsetStrategy="auto", 
subsamplingRate=1.0, \
                   leafCol="", minWeightFractionPerNode=0.0, weightCol=None, 
bootstrap=True)
@@ -2175,12 +2163,6 @@ class RandomForestClassifier(
         """
         return self._set(maxBins=value)
 
-    def setPruneTree(self, value: bool) -> "RandomForestClassifier":
-        """
-        Sets the value of :py:attr:`pruneTree`.
-        """
-        return self._set(pruneTree=value)
-
     def setMinInstancesPerNode(self, value: int) -> "RandomForestClassifier":
         """
         Sets the value of :py:attr:`minInstancesPerNode`.
diff --git a/python/pyspark/ml/tree.py b/python/pyspark/ml/tree.py
index 41b8bdc600c5..63f58272aeef 100644
--- a/python/pyspark/ml/tree.py
+++ b/python/pyspark/ml/tree.py
@@ -415,13 +415,6 @@ class _TreeClassifierParams(Params):
         typeConverter=TypeConverters.toString,
     )
 
-    pruneTree = Param(Params._dummy(), "pruneTree", "" +
-                      "If true, the trained tree will undergo a pruning 
process after training, in which nodes" +
-                      " with the same class predictions are merged. The 
resulting tree will be smaller and have" +
-                      " faster predictions, but class probabilities will be 
lost." +
-                      " If false, no pruning is applied after training, and 
class probabilities are preserved.",
-                      typeConverter=TypeConverters.toBoolean)
-
     def __init__(self) -> None:
         super().__init__()
 
@@ -431,12 +424,6 @@ class _TreeClassifierParams(Params):
         Gets the value of impurity or its default value.
         """
         return self.getOrDefault(self.impurity)
-    @since("4.3.0")
-    def getPruneTree(self) -> bool:
-        """
-        Gets the value of pruneTree or its default value.
-        """
-        return self.getOrDefault(self.pruneTree)
 
 
 class _TreeRegressorParams(_HasVarianceImpurity):


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to