func (forest *Runtime) GrowTree(samples tabula.ClasetInterface) (
	cm *classifier.CM, stat *classifier.Stat, e error,
) {
	stat = &classifier.Stat{}
	stat.ID = int64(len(forest.trees))
	stat.Start()
	bag, oob, bagIdx, oobIdx := tabula.RandomPickRows(
		samples.(tabula.DatasetInterface),
		forest.nSubsample, true)
	bagset := bag.(tabula.ClasetInterface)
	if debug.Value >= 2 {
		bagset.RecountMajorMinor()
		fmt.Println(tag, "Bagging:", bagset)
	}
	cart, e := cart.New(bagset, cart.SplitMethodGini,
		forest.NRandomFeature)
	if e != nil {
		return nil, nil, e
	}
	forest.AddCartTree(*cart)
	forest.AddBagIndex(bagIdx)
	if forest.RunOOB {
		oobset := oob.(tabula.ClasetInterface)
		_, cm, _ = forest.ClassifySet(oobset, oobIdx)
		forest.AddOOBCM(cm)
	}
	stat.End()
	if debug.Value >= 3 && forest.RunOOB {
		fmt.Println(tag, "Elapsed time (s):", stat.ElapsedTime)
	}
	forest.AddStat(stat)
	if forest.RunOOB {
		forest.ComputeStatFromCM(stat, cm)
		if debug.Value >= 2 {
			fmt.Println(tag, "OOB stat:", stat)
		}
	}
	forest.ComputeStatTotal(stat)
	e = forest.WriteOOBStat(stat)
	return cm, stat, e
}
GrowTree build a new tree in forest, return OOB error value or error if tree
can not grow.

Algorithm,

(1) Select random samples with replacement, also with OOB.
(2) Build tree using CART, without pruning.
(3) Add tree to forest.
(4) Save index of random samples for calculating error rate later.
(5) Run OOB on forest.
(6) Calculate OOB error rate and statistic values.
package rf
import (
	"errors"
	"fmt"
	"math"
	"github.com/shuLhan/share/lib/debug"
	"github.com/shuLhan/share/lib/floats64"
	"github.com/shuLhan/share/lib/ints"
	"github.com/shuLhan/share/lib/mining/classifier"
	"github.com/shuLhan/share/lib/mining/classifier/cart"
	libstrings "github.com/shuLhan/share/lib/strings"
	"github.com/shuLhan/share/lib/tabula"
)
const (
	tag = "[rf]"
	DefNumTree = 100
	DefPercentBoot = 66
	DefOOBStatsFile = "rf.oob.stat"
	DefPerfFile = "rf.perf"
	DefStatFile = "rf.stat"
)
var (
	ErrNoInput = errors.New("rf: input samples is empty")
)
type Runtime struct {
	classifier.Runtime
	NTree int `json:"NTree"`
	NRandomFeature int `json:"NRandomFeature"`
	PercentBoot int `json:"PercentBoot"`
	nSubsample int
	trees []cart.Runtime
	bagIndices [][]int
}
func (forest *Runtime) Trees() []cart.Runtime {
	return forest.trees
}
func (forest *Runtime) AddCartTree(tree cart.Runtime) {
	forest.trees = append(forest.trees, tree)
}
func (forest *Runtime) AddBagIndex(bagIndex []int) {
	forest.bagIndices = append(forest.bagIndices, bagIndex)
}
func (forest *Runtime) Initialize(samples tabula.ClasetInterface) error {
	if forest.NTree <= 0 {
		forest.NTree = DefNumTree
	}
	if forest.PercentBoot <= 0 {
		forest.PercentBoot = DefPercentBoot
	}
	if forest.NRandomFeature <= 0 {
		ncol := samples.GetNColumn() - 1
		forest.NRandomFeature = int(math.Sqrt(float64(ncol)))
	}
	if forest.OOBStatsFile == "" {
		forest.OOBStatsFile = DefOOBStatsFile
	}
	if forest.PerfFile == "" {
		forest.PerfFile = DefPerfFile
	}
	if forest.StatFile == "" {
		forest.StatFile = DefStatFile
	}
	forest.nSubsample = int(float32(samples.GetNRow()) *
		(float32(forest.PercentBoot) / 100.0))
	return forest.Runtime.Initialize()
}
func (forest *Runtime) Build(samples tabula.ClasetInterface) (e error) {
	if samples == nil {
		return ErrNoInput
	}
	e = forest.Initialize(samples)
	if e != nil {
		return
	}
	fmt.Println(tag, "Training set    :", samples)
	fmt.Println(tag, "Sample (one row):", samples.GetRow(0))
	fmt.Println(tag, "Forest config   :", forest)
	for t := 0; t < forest.NTree; t++ {
		if debug.Value >= 1 {
			fmt.Println(tag, "tree #", t)
		}
		for {
			_, _, e = forest.GrowTree(samples)
			if e == nil {
				break
			}
			fmt.Println(tag, "error:", e)
		}
	}
	return forest.Finalize()
}