func (cm *CM) String() (s string) {
	s += "Confusion Matrix:\n"
	s += "\t"
	for _, col := range cm.GetColumnsName() {
		s += col + "\t"
	}
	s += "\n"
	rows := cm.GetDataAsRows()
	for x, row := range *rows {
		s += cm.rowNames[x] + "\t"
		for _, v := range *row {
			s += v.String() + "\t"
		}
		s += "\n"
	}
	s += fmt.Sprintf("TP-FP indices %d %d\n", len(cm.tpIds), len(cm.fpIds))
	s += fmt.Sprintf("FN-TN indices %d %d\n", len(cm.fnIds), len(cm.tnIds))
	return
}
String will return the output of confusion matrix in table like format.
package classifier
import (
	"fmt"
	"strconv"
	"github.com/shuLhan/share/lib/tabula"
)
type CM struct {
	tabula.Dataset
	rowNames []string
	nSamples int64
	nTrue int64
	nFalse int64
	tpIds []int
	fpIds []int
	tnIds []int
	fnIds []int
}
func (cm *CM) initByNumeric(vs []int64) {
	colTypes := make([]int, 0, len(vs))
	colNames := make([]string, 0, len(vs))
	for _, v := range vs {
		vstr := strconv.FormatInt(v, 10)
		colTypes = append(colTypes, tabula.TInteger)
		colNames = append(colNames, vstr)
		cm.rowNames = append(cm.rowNames, vstr)
	}
	colTypes = append(colTypes, tabula.TReal)
	colNames = append(colNames, "class_error")
	cm.Dataset.Init(tabula.DatasetModeMatrix, colTypes, colNames)
}
func (cm *CM) ComputeStrings(valueSpace, targets, predictions []string) {
	cm.init(valueSpace)
	for x, target := range valueSpace {
		col := cm.GetColumn(x)
		for _, predict := range valueSpace {
			cnt := cm.countTargetPrediction(target, predict,
				targets, predictions)
			col.PushBack(tabula.NewRecordInt(cnt))
		}
		cm.PushColumnToRows(*col)
	}
	cm.computeClassError()
}
func (cm *CM) ComputeNumeric(vs, actuals, predictions []int64) {
	cm.initByNumeric(vs)
	for x, act := range vs {
		col := cm.GetColumn(x)
		for _, pred := range vs {
			cnt := cm.countNumeric(act, pred, actuals, predictions)
			rec := tabula.NewRecordInt(cnt)
			col.PushBack(rec)
		}
		cm.PushColumnToRows(*col)
	}
	cm.computeClassError()
}
func (cm *CM) init(valueSpace []string) {
	colTypes := make([]int, 0, len(valueSpace))
	colNames := make([]string, 0, len(valueSpace))
	for _, v := range valueSpace {
		colTypes = append(colTypes, tabula.TInteger)
		colNames = append(colNames, v)
		cm.rowNames = append(cm.rowNames, v)
	}
	colTypes = append(colTypes, tabula.TReal)
	colNames = append(colNames, "class_error")
	cm.Dataset.Init(tabula.DatasetModeMatrix, colTypes, colNames)
}
func (cm *CM) countTargetPrediction(target, predict string,
	targets, predictions []string,
) (
	cnt int64,
) {
	predictslen := len(predictions)
	for x, v := range targets {
		if x >= predictslen {
			break
		}
		if v != target {
			continue
		}
		if predict == predictions[x] {
			cnt++
		}
	}
	return
}
func (cm *CM) countNumeric(act, pred int64, actuals, predictions []int64) (
	cnt int64,
) {
	minlen := len(actuals)
	if len(predictions) < minlen {
		minlen = len(predictions)
	}
	for x := 0; x < minlen; x++ {
		if actuals[x] != act {
			continue
		}
		if predictions[x] != pred {
			continue
		}
		cnt++
	}
	return cnt
}
func (cm *CM) computeClassError() {
	var tp, fp int64
	cm.nSamples = 0
	cm.nFalse = 0
	classcol := cm.GetNColumn() - 1
	col := cm.GetColumnClassError()
	rows := cm.GetDataAsRows()
	for x, row := range *rows {
		for y, cell := range *row {
			if y == classcol {
				break
			}
			if x == y {
				tp = cell.Integer()
			} else {
				fp += cell.Integer()
			}
		}
		nSamplePerRow := tp + fp
		errv := float64(fp) / float64(nSamplePerRow)
		col.PushBack(tabula.NewRecordReal(errv))
		cm.nSamples += nSamplePerRow
		cm.nTrue += tp
		cm.nFalse += fp
	}
	cm.PushColumnToRows(*col)
}
func (cm *CM) GroupIndexPredictions(sampleIds []int,
	actuals, predictions []int64,
) {
	cm.tpIds = nil
	cm.fpIds = nil
	cm.tnIds = nil
	cm.fnIds = nil
	min := len(sampleIds)
	if len(actuals) < min {
		min = len(actuals)
	}
	if len(predictions) < min {
		min = len(predictions)
	}
	for x := 0; x < min; x++ {
		if actuals[x] == 1 {
			if predictions[x] == 1 {
				cm.tpIds = append(cm.tpIds, sampleIds[x])
			} else {
				cm.fnIds = append(cm.fnIds, sampleIds[x])
			}
		} else {
			if predictions[x] == 1 {
				cm.fpIds = append(cm.fpIds, sampleIds[x])
			} else {
				cm.tnIds = append(cm.tnIds, sampleIds[x])
			}
		}
	}
}
func (cm *CM) GroupIndexPredictionsStrings(sampleIds []int,
	actuals, predictions []string,
) {
	if len(sampleIds) == 0 {
		return
	}
	cm.tpIds = nil
	cm.fpIds = nil
	cm.tnIds = nil
	cm.fnIds = nil
	min := len(sampleIds)
	if len(actuals) < min {
		min = len(actuals)
	}
	if len(predictions) < min {
		min = len(predictions)
	}
	for x := 0; x < min; x++ {
		if actuals[x] == "1" {
			if predictions[x] == "1" {
				cm.tpIds = append(cm.tpIds, sampleIds[x])
			} else {
				cm.fnIds = append(cm.fnIds, sampleIds[x])
			}
		} else {
			if predictions[x] == "1" {
				cm.fpIds = append(cm.fpIds, sampleIds[x])
			} else {
				cm.tnIds = append(cm.tnIds, sampleIds[x])
			}
		}
	}
}
func (cm *CM) GetColumnClassError() *tabula.Column {
	return cm.GetColumn(cm.GetNColumn() - 1)
}
func (cm *CM) GetTrueRate() float64 {
	return float64(cm.nTrue) / float64(cm.nTrue+cm.nFalse)
}
func (cm *CM) GetFalseRate() float64 {
	return float64(cm.nFalse) / float64(cm.nTrue+cm.nFalse)
}
func (cm *CM) TP() int {
	row := cm.GetRow(0)
	if row == nil {
		return 0
	}
	v, _ := row.GetIntAt(0)
	return int(v)
}
func (cm *CM) FP() int {
	row := cm.GetRow(0)
	if row == nil {
		return 0
	}
	v, _ := row.GetIntAt(1)
	return int(v)
}
func (cm *CM) FN() int {
	row := cm.GetRow(1)
	if row == nil {
		return 0
	}
	v, _ := row.GetIntAt(0)
	return int(v)
}
func (cm *CM) TN() int {
	row := cm.GetRow(1)
	if row == nil {
		return 0
	}
	v, _ := row.GetIntAt(1)
	return int(v)
}
func (cm *CM) TPIndices() []int {
	return cm.tpIds
}
func (cm *CM) FNIndices() []int {
	return cm.fnIds
}
func (cm *CM) FPIndices() []int {
	return cm.fpIds
}
func (cm *CM) TNIndices() []int {
	return cm.tnIds
}