├── go.mod ├── Test └── Test.go ├── Tree.go ├── README.md ├── Node.go └── go.sum /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/hammamikhairi/Decision-Tree 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/go-gota/gota v0.12.0 // indirect 7 | github.com/mauricelam/genny v0.0.0-20190320071652-0800202903e5 // indirect 8 | github.com/tobgu/qframe v0.3.6 // indirect 9 | golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect 10 | gonum.org/v1/gonum v0.9.1 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /Test/Test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | DecisionTree "github.com/hammamikhairi/Decision-Tree" 8 | 9 | "github.com/go-gota/gota/dataframe" 10 | "github.com/go-gota/gota/series" 11 | ) 12 | 13 | func ReadCSVToDF(path string) dataframe.DataFrame { 14 | csvfile, err := os.Open(path) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | return dataframe.ReadCSV(csvfile) 20 | } 21 | 22 | func test() { 23 | 24 | // Load Data 25 | // tested on the Titanic dataset from kaggle 26 | // https://www.kaggle.com/competitions/titanic/data 27 | f := ReadCSVToDF("Data/train.csv") 28 | 29 | f = f.Select([]string{"Survived", "Age", "Fare", "Pclass", "SibSp"}).Filter( 30 | dataframe.F{Colname: "Age", Comparator: series.GreaterEq, Comparando: 0}, 31 | dataframe.F{Colname: "Fare", Comparator: series.GreaterEq, Comparando: 0}, 32 | dataframe.F{Colname: "Survived", Comparator: series.GreaterEq, Comparando: 0}, 33 | dataframe.F{Colname: "Pclass", Comparator: series.GreaterEq, Comparando: 0}, 34 | dataframe.F{Colname: "SibSp", Comparator: series.GreaterEq, Comparando: 0}, 35 | ) 36 | 37 | Y, err := f.Col("Survived").Int() 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | tree := DecisionTree.TreeInit(Y, f.Select([]string{"Age", "Fare", "Pclass", "SibSp"}), 100, 4) 43 | 44 | tree.Sprout() 45 | 46 | df := ReadCSVToDF("Data/test.csv").Select([]string{"Age", "Fare", "Pclass", "SibSp"}) 47 | 48 | response := tree.Predict(df) 49 | 50 | res, err := ReadCSVToDF("Data/verif.csv").Col("Survived").Int() 51 | 52 | if err != nil { 53 | panic(err) 54 | } 55 | 56 | var total int = len(response) 57 | var passed int = 0 58 | 59 | for index, pred := range response { 60 | if pred == fmt.Sprint(res[index]) { 61 | passed++ 62 | } else { 63 | fmt.Println(pred, res[index]) 64 | } 65 | } 66 | 67 | fmt.Println(total, passed) 68 | 69 | } 70 | -------------------------------------------------------------------------------- /Tree.go: -------------------------------------------------------------------------------- 1 | package decisiontree 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "sort" 7 | 8 | "github.com/go-gota/gota/dataframe" 9 | "github.com/go-gota/gota/series" 10 | ) 11 | 12 | type Counter map[string]int 13 | 14 | // The Decision Tree 15 | type DecisionTree struct { 16 | root *node 17 | } 18 | 19 | // Initialize the Decision Tree 20 | // 21 | // Parameters 22 | // ---------- 23 | // Y : []int : The target variable to predict 24 | // X : dataframe.DataFrame : The features 25 | // maxTreeDepth : int : The maximum depth of the tree 26 | // minDfSplit : int : The minimum number of samples to split a node 27 | func TreeInit(Y []int, X dataframe.DataFrame, maxTreeDepth, minDfSplit int) *DecisionTree { 28 | tree := &DecisionTree{ 29 | root: NodeInit(Y, X, 0, maxTreeDepth, minDfSplit, "ROOT"), 30 | } 31 | return tree 32 | } 33 | 34 | func count(Y []int) Counter { 35 | counter := make(Counter) 36 | for _, cont := range Y { 37 | counter[fmt.Sprint(cont)]++ 38 | } 39 | 40 | return counter 41 | } 42 | func countErr(Y []int, err error) Counter { 43 | counter := make(Counter) 44 | for _, cont := range Y { 45 | counter[fmt.Sprint(cont)]++ 46 | } 47 | 48 | return counter 49 | } 50 | 51 | func maxCount(counter Counter) string { 52 | 53 | keys := make([]string, 0, len(counter)) 54 | for key := range counter { 55 | keys = append(keys, key) 56 | } 57 | 58 | if len(keys) <= 1 { 59 | return keys[0] 60 | } 61 | 62 | sort.SliceStable(keys, func(i, j int) bool { 63 | return counter[keys[i]] > counter[keys[j]] 64 | }) 65 | 66 | return keys[0] 67 | } 68 | 69 | func giniImputiry(s0, s1 int) float64 { 70 | 71 | if s0+s1 == 0 { 72 | return 0.0 73 | } 74 | 75 | prob0 := float64(s0) / float64(s0+s1) 76 | prob1 := float64(s1) / float64(s0+s1) 77 | 78 | return 1 - (math.Pow(prob0, 2) + math.Pow(prob1, 2)) 79 | 80 | } 81 | func setFromList(list []string) (set []string) { 82 | ks := make(map[string]bool) // map to keep track of repeats 83 | 84 | for _, e := range list { 85 | if _, v := ks[e]; !v { 86 | ks[e] = true 87 | set = append(set, e) 88 | } 89 | } 90 | return 91 | } 92 | 93 | func uniqueGotaSeries(s series.Series) series.Series { 94 | return series.New(setFromList(s.Records()), s.Type(), s.Name) 95 | } 96 | 97 | func meth(col []float64) []float64 { 98 | var methed []float64 99 | for i := 0; i < len(col)-1; i++ { 100 | methed = append(methed, (col[i]+col[i+1])/2) 101 | } 102 | 103 | return methed 104 | } 105 | 106 | // Generate tree 107 | func (tree *DecisionTree) Sprout() { 108 | tree.root.sprout() 109 | } 110 | 111 | // Predict the target variable for a given set of features 112 | // 113 | // Parameters 114 | // ---------- 115 | // X : dataframe.DataFrame : The features 116 | func (tree *DecisionTree) Predict(data dataframe.DataFrame) []string { 117 | 118 | features := tree.root.data.features 119 | var predictions []string 120 | 121 | x, _ := data.Dims() 122 | for i := 0; i < x; i++ { 123 | nmap := make(map[string]float64) 124 | for _, feature := range features { 125 | nmap[feature] = data.Col(feature).Elem(i).Float() 126 | } 127 | predictions = append(predictions, tree.root.predict(nmap)) 128 | } 129 | 130 | return predictions 131 | } 132 | 133 | // Print the tree in a human readable format 134 | func (tree *DecisionTree) Print() { 135 | tree.root.print(1) 136 | } 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Decision Tree 2 | 3 | ## What is a Decision Tree 4 | 5 | Decision Tree is the most powerful and popular tool for classification and prediction. A Decision tree is a flowchart-like tree structure, where each internal node denotes a test on an attribute, each branch represents an outcome of the test, and each leaf node (terminal node) holds a class label. ( sited from [here](https://www.geeksforgeeks.org/decision-tree/) ) 6 | 7 |

8 | Decision Tree 9 |

10 | 11 | ## Usage 12 | 13 | ```go 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | 21 | DecisionTree "github.com/hammamikhairi/Decision-Tree" 22 | 23 | "github.com/go-gota/gota/dataframe" 24 | "github.com/go-gota/gota/series" 25 | ) 26 | 27 | func main() { 28 | 29 | // Load data 30 | // tested on the Titanic dataset from kaggle 31 | // https://www.kaggle.com/competitions/titanic/data 32 | csvfile, err := os.Open("Data/train.csv") 33 | if err != nil { 34 | panic(err) 35 | } 36 | 37 | // convert data into a dataframe 38 | f := dataframe.ReadCSV(csvfile) 39 | 40 | // chose the column to train on 41 | Y, err := f.Col("Survived").Int() 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | // initialize Decision Tree 47 | tree := DecisionTree.TreeInit(Y, f.Select([]string{"Age", "Fare"}), 10, 20) 48 | 49 | // generate Tree 50 | tree.Sprout() 51 | 52 | // print Tree 53 | tree.Print() 54 | 55 | // Root 56 | // | GINI impurity : 0.48238903404499056 57 | // | Class distribution : {0: 424, 1: 290} 58 | // | Predicted class : 0 59 | // --- Split rule : Fare <= 52.277100000000004 60 | // | GINI impurity : 0.4448243103771814 61 | // | Class distribution : {0: 389, 1: 195} 62 | // | Predicted class : 0 63 | // ---------- Split rule : Fare <= 10.48125 64 | // | GINI impurity : 0.31596085502704785 65 | // | Class distribution : {0: 192, 1: 47} 66 | // | Predicted class : 0 67 | // ---------- Split rule : Fare > 10.48125 68 | // | GINI impurity : 0.48991388363789123 69 | // | Class distribution : {0: 197, 1: 148} 70 | // | Predicted class : 0 71 | // --- Split rule : Fare > 52.277100000000004 72 | // | GINI impurity : 0.39349112426035515 73 | // | Class distribution : {0: 35, 1: 95} 74 | // | Predicted class : 1 75 | // ---------- Split rule : Age <= 63.5 76 | // | GINI impurity : 0.37696075392150785 77 | // | Class distribution : {0: 32, 1: 95} 78 | // | Predicted class : 1 79 | // ---------- Split rule : Age > 63.5 80 | // | GINI impurity : 0 81 | // | Class distribution : {0: 3, 1: 0} 82 | // | Predicted class : 0 83 | 84 | 85 | // prepare a dataframe for prediction 86 | df := dataframe.New( 87 | series.New([]string{"20", "15"}, series.String, "Age"), 88 | series.New([]float64{53.025, 14.0}, series.Float, "Fare"), 89 | ) 90 | 91 | // predict 92 | fmt.Println(tree.Predict(df)) // -> [1, 0] 93 | 94 | } 95 | ``` 96 | 97 | ## Installation 98 | 99 | ```bash 100 | go get github.com/hammamikhairi/Decision-Tree 101 | ``` 102 | 103 | ## LFSA 88 104 | 105 | This project is a part of [LFSA 88](https://github.com/hammamikhairi/LFSA-88). 106 | -------------------------------------------------------------------------------- /Node.go: -------------------------------------------------------------------------------- 1 | package decisiontree 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/go-gota/gota/dataframe" 9 | "github.com/go-gota/gota/series" 10 | ) 11 | 12 | type Data struct { 13 | rule string 14 | features []string 15 | counts map[string]int 16 | giniImpurity float64 17 | yhat string 18 | nb int 19 | } 20 | 21 | type node struct { 22 | Y []int 23 | X dataframe.DataFrame 24 | bestFeature string 25 | bestValue string 26 | depth int 27 | maxDepth int 28 | minDfSplit int 29 | data *Data 30 | left *node 31 | right *node 32 | } 33 | 34 | func NodeInit(Y []int, X dataframe.DataFrame, depth, maxDepth, minDfSplit int, rule string) *node { 35 | 36 | if len(Y) == 0 { 37 | return nil 38 | } 39 | 40 | genCount := count(Y) 41 | var nd *node = &node{ 42 | Y: Y, 43 | X: X, 44 | depth: depth, 45 | maxDepth: maxDepth, 46 | minDfSplit: minDfSplit, 47 | data: &Data{ 48 | rule: rule, 49 | features: X.Names(), 50 | counts: genCount, 51 | giniImpurity: giniImputiry(genCount["0"], genCount["1"]), 52 | yhat: maxCount(genCount), 53 | nb: len(Y), 54 | }, 55 | left: nil, 56 | right: nil, 57 | bestFeature: "", 58 | bestValue: "", 59 | } 60 | 61 | return nd 62 | } 63 | 64 | func (nd *node) split() (string, string) { 65 | df := nd.X.Copy().Mutate( 66 | series.New(nd.Y, series.Int, "Y"), 67 | ) 68 | 69 | giniBase := nd.data.giniImpurity 70 | maxGain := 0.0 71 | bestFeature := "" 72 | bestValue := "" 73 | 74 | for _, feature := range nd.data.features { 75 | sorted := df.Arrange( 76 | dataframe.Sort(feature), 77 | ) 78 | xmeans := meth(uniqueGotaSeries(sorted.Col(feature)).Float()) 79 | 80 | for _, val := range xmeans { 81 | leftCounts := countErr( 82 | sorted.Filter( 83 | dataframe.F{Colname: feature, Comparator: series.Less, Comparando: val}, 84 | ).Col("Y").Int(), 85 | ) 86 | rightCounts := countErr( 87 | sorted.Filter( 88 | dataframe.F{Colname: feature, Comparator: series.GreaterEq, Comparando: val}, 89 | ).Col("Y").Int(), 90 | ) 91 | 92 | s0Left, s1Left, s0Right, s1Right := leftCounts["0"], leftCounts["1"], rightCounts["0"], rightCounts["1"] 93 | 94 | totalLeft := s0Left + s1Left 95 | totalRight := s0Right + s1Right 96 | 97 | weightLeft := float64(totalLeft) / float64(totalLeft+totalRight) 98 | weightRight := float64(totalRight) / float64(totalLeft+totalRight) 99 | 100 | wGINI := weightLeft*giniImputiry(s0Left, s1Left) + weightRight*giniImputiry(s0Right, s1Right) 101 | 102 | GINIgain := giniBase - wGINI 103 | 104 | if GINIgain >= maxGain { 105 | bestFeature = feature 106 | bestValue = fmt.Sprint(val) 107 | 108 | maxGain = GINIgain 109 | } 110 | } 111 | } 112 | 113 | return bestFeature, bestValue 114 | } 115 | 116 | func (nd *node) sprout() { 117 | 118 | if nd.depth < nd.maxDepth && nd.data.nb >= nd.minDfSplit { 119 | bestFeature, bestValue := nd.split() 120 | nd.bestFeature = bestFeature 121 | nd.bestValue = bestValue 122 | df := nd.X.Mutate( 123 | series.New(nd.Y, series.Int, "Y"), 124 | ) 125 | 126 | if bestFeature == "" { 127 | panic("bestFeature") 128 | } 129 | 130 | leftDf, rightDf := df.Filter( 131 | dataframe.F{Colname: bestFeature, Comparator: series.LessEq, Comparando: bestValue}, 132 | ).Copy(), df.Filter( 133 | dataframe.F{Colname: bestFeature, Comparator: series.Greater, Comparando: bestValue}, 134 | ).Copy() 135 | // NodeInit(Y []int, X dataframe.DataFrame, maxDepth, minDfSplit int) 136 | leftY, lerr := leftDf.Col("Y").Int() 137 | rightY, rerr := rightDf.Col("Y").Int() 138 | if lerr != nil || rerr != nil { 139 | panic(lerr) 140 | } 141 | nd.left = NodeInit( 142 | leftY, 143 | leftDf, 144 | nd.depth+1, 145 | nd.maxDepth, 146 | nd.minDfSplit, 147 | fmt.Sprintf("%s <= %s", bestFeature, bestValue), 148 | ) 149 | if nd.left != nil { 150 | nd.left.sprout() 151 | } 152 | 153 | nd.right = NodeInit( 154 | rightY, 155 | rightDf, 156 | nd.depth+1, 157 | nd.maxDepth, 158 | nd.minDfSplit, 159 | fmt.Sprintf("%s > %s", bestFeature, bestValue), 160 | ) 161 | if nd.right != nil { 162 | nd.right.sprout() 163 | } 164 | 165 | } 166 | } 167 | 168 | func (nd *node) print(padding int) { 169 | spaces := strings.Repeat("-", padding*nd.depth) 170 | fmt.Print(spaces) 171 | if nd.data.rule == "ROOT" { 172 | fmt.Println("Root") 173 | } else { 174 | fmt.Println(" Split rule : ", nd.data.rule) 175 | } 176 | fmt.Print(strings.Repeat(" ", padding*nd.depth)) 177 | fmt.Println(" | GINI impurity : ", nd.data.giniImpurity) 178 | fmt.Print(strings.Repeat(" ", padding*nd.depth)) 179 | fmt.Printf(" | Class distribution : {0: %d, 1: %d}\n", nd.data.counts["0"], nd.data.counts["1"]) 180 | fmt.Print(strings.Repeat(" ", padding*nd.depth)) 181 | fmt.Println(" | Predicted class : ", nd.data.yhat) 182 | 183 | if nd.left != nil { 184 | nd.left.print(padding + 2) 185 | } 186 | 187 | if nd.right != nil { 188 | nd.right.print(padding + 2) 189 | } 190 | } 191 | 192 | func (nd *node) predict(to map[string]float64) string { 193 | 194 | if nd.left == nil && nd.right == nil { 195 | return nd.data.yhat 196 | } 197 | 198 | parsed, err := strconv.ParseFloat(nd.bestValue, 64) 199 | if err != nil { 200 | panic(err) 201 | } 202 | 203 | if to[nd.bestFeature] <= parsed { 204 | return nd.left.predict(to) 205 | } else { 206 | return nd.right.predict(to) 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 2 | gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= 3 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 4 | github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= 5 | github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= 6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 8 | github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= 9 | github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= 10 | github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= 11 | github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= 12 | github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= 13 | github.com/go-gl/gl v0.0.0-20180407155706-68e253793080/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= 14 | github.com/go-gl/glfw v0.0.0-20180426074136-46a8d530c326/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 15 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 16 | github.com/go-gota/gota v0.12.0 h1:T5BDg1hTf5fZ/CO+T/N0E+DDqUhvoKBl+UVckgcAAQg= 17 | github.com/go-gota/gota v0.12.0/go.mod h1:UT+NsWpZC/FhaOyWb9Hui0jXg0Iq8e/YugZHTbyW/34= 18 | github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= 19 | github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= 20 | github.com/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 21 | github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= 22 | github.com/llgcode/draw2d v0.0.0-20180817132918-587a55234ca2/go.mod h1:mVa0dA29Db2S4LVqDYLlsePDzRJLDfdhVZiI15uY0FA= 23 | github.com/llgcode/ps v0.0.0-20150911083025-f1443b32eedb/go.mod h1:1l8ky+Ew27CMX29uG+a2hNOKpeNYEQjjtiALiBlFQbY= 24 | github.com/mauricelam/genny v0.0.0-20190320071652-0800202903e5 h1:PnFl95tWh3j7c5DebZG/TGsBJvbnHvPjK4lzltouI4Y= 25 | github.com/mauricelam/genny v0.0.0-20190320071652-0800202903e5/go.mod h1:i2AazGGunAlAR5u0zXGYVmIT7nnwE6j9lwKSMx7N6ko= 26 | github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= 27 | github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= 28 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 29 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 30 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 31 | github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= 32 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 33 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 34 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 35 | github.com/tobgu/qframe v0.3.6 h1:WAoXQA10dyKDMk8ioCz4tHOba8pDGBgfngeI7pKNWok= 36 | github.com/tobgu/qframe v0.3.6/go.mod h1:2Xb/8FU39symTNTNAwsfdf6NGBExauEc4CxgGOmoUMk= 37 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 38 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 39 | golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 40 | golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 41 | golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 42 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 43 | golang.org/x/exp v0.0.0-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= 44 | golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= 45 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 46 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 47 | golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 48 | golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 49 | golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 50 | golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 51 | golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 52 | golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 53 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 54 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 55 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 56 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 57 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 58 | golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk= 59 | golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= 60 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 61 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 62 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 63 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 64 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 65 | golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 66 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 67 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 68 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 69 | golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 70 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 71 | golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 72 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 73 | golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 74 | golang.org/x/tools v0.0.0-20190319232107-3f1ed9edd1b4/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 75 | golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 76 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 77 | gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= 78 | gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= 79 | gonum.org/v1/gonum v0.9.1 h1:HCWmqqNoELL0RAQeKBXWtkp04mGk8koafcB4He6+uhc= 80 | gonum.org/v1/gonum v0.9.1/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= 81 | gonum.org/v1/netlib v0.0.0-20180816165226-ebcc3d2662d3/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 82 | gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= 83 | gonum.org/v1/plot v0.0.0-20180905080458-5f3c436ce602/go.mod h1:VIQWjXleEHakKVLjfhAAXUy3mq0NuXvobpOBf0ZBZro= 84 | gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= 85 | gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= 86 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 87 | --------------------------------------------------------------------------------