Skip to content

Instantly share code, notes, and snippets.

@janmarek
Created June 27, 2012 11:19
Show Gist options
  • Select an option

  • Save janmarek/3003446 to your computer and use it in GitHub Desktop.

Select an option

Save janmarek/3003446 to your computer and use it in GitHub Desktop.
(ns trees.core)
(require '[clojure.contrib.math :as math])
(use 'clojure.set)
; tools
(defn
^{:doc "get column from 2 dimensional array"
:test (fn []
(assert (= [0 1 2] (get-column [[2 0] [3 1] [1 2]] 1)))
)}
get-column [arr col]
(map (fn [row] (nth row col)) arr)
)
(defn
^{:doc "returns true if predicate aplied to any of columns returns true"}
exist [fnc collection]
(if (= (count collection) 0)
false
(if (fnc (first collection))
true
(exist fnc (rest collection))
)
)
)
(defn in?
"true if seq contains elm"
[seq elm]
(some #(= elm %) seq))
; tree example:
(def test-tree #{
{:pos 0 :func > :attr 1 :value 0.5}
{:pos 1 :func < :attr 3 :value 0.2}
{:pos 2 :func < :attr 2 :value 0.3}
{:pos 5 :func < :attr 1 :value 0.4}
})
(defn
tree-positions [tree]
(map (fn [a] (a :pos)) tree)
)
(defn
^{:doc "get max node position from tree"}
tree-max-pos [tree]
(apply max (tree-positions tree))
)
(defn
^{:doc "depth of tree with n nodes, root only has depth 0"
:test (fn []
(map (fn [nodes depth]
(assert (= depth (tree-depth nodes)))
) [1 2 3 5 8 10 16 20] [0 1 1 2 3 3 4 4])
)}
tree-depth [n]
(dec (int (java.lang.Math/ceil (/ (java.lang.Math/log (+ n 1)) (java.lang.Math/log 2)))))
)
(defn
^{:doc "current row of nodes indexed by 0"}
current-row [position]
(tree-depth (+ position 1))
)
(defn
^{:doc "gen count of nodes in row indexed by 0"}
nodes-in-row [row]
(math/expt 2 row)
)
(defn
tree-row-start [row]
(dec (math/expt 2 row))
)
(defn
^{:doc "node position in row indexed by 0",
:test (fn []
(assert (= 2 (tree-position-in-row 9)))
)}
tree-position-in-row [pos]
(- pos (tree-row-start (current-row pos)))
)
(defn
^{:doc "get parent index"
:test (fn []
(assert (=
'(nil 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9)
(map tree-parent (range 0 20))
))
)}
tree-parent [node]
(if (= node 0)
nil
(+ (tree-row-start (dec (current-row node))) (int (/ (tree-position-in-row node) 2)))
)
)
(defn
^{:doc "return parent node position indexes"
:test (fn []
(map (fn [node-index res]
(assert (= res (tree-parents node-index)))
) [0 6 17] [[] [2 0] [8 3 1 0]])
)}
tree-parents [node-index]
(loop [n node-index found-parents []]
(let [parent (tree-parent n)]
(if (nil? parent)
found-parents
(recur parent (conj found-parents parent))
)
)
)
)
(defn
^{:test (fn []
(do
(assert (=
(map tree-children [0 1 5 8])
[[1 2] [3 4] [11 12] [17 18]]
))
(assert (=
[5] (tree-children 2 test-tree)
))
)
)}
tree-children
([n]
(let [
next-row-start (tree-row-start (inc (current-row n)))
first-child (+ next-row-start (* (tree-position-in-row n) 2))
]
[first-child (inc first-child)]
)
)
([n tree]
(into [] (intersection (set (tree-children n)) (set (tree-positions tree))))
)
)
(defn
^{:test (fn []
(assert (= #{3 4 6 11 12} (tree-appendable-positions test-tree)))
)}
tree-appendable-positions [tree]
(if (empty? tree)
#{0}
(let [positions (tree-positions tree)]
(difference (set (apply union (map tree-children positions))) positions)
)
)
)
(defn
^{:doc "fetch subtree"
:test (fn []
(assert (= [2 5] (tree-positions (subtree 2 test-tree))))
)}
subtree [n tree]
(set (filter (fn [item]
(let [cur-pos (item :pos) node-parents (tree-parents cur-pos)]
(or (= cur-pos n) (exist (fn [parent] (= parent n)) node-parents))
)
) tree))
)
(defn
remove-subtree [tree subtree]
(difference tree subtree)
)
(defn
^{:test (fn []
(assert (= 5 ((tree-find-by-pos test-tree 5) :pos)))
)}
tree-find-by-pos [tree pos]
(first (filter (fn [row] (= (row :pos) pos)) tree))
)
(defn
^{:test (fn []
(assert (=
#{2 5 6 13}
(set (tree-positions (move-subtree 2 test-tree)))
))
)}
move-subtree [move-to subtree-set]
(let [
subtree-root (apply min (tree-positions subtree-set))
root-children (tree-children subtree-root subtree-set)
new-left-child (first (tree-children move-to))
new-right-child (inc new-left-child)
new-root-row (merge (tree-find-by-pos subtree-set subtree-root) {:pos move-to})
]
(union #{new-root-row} (apply union (map (fn [child]
(move-subtree (if (odd? child) new-left-child new-right-child) (subtree child subtree-set)
)) root-children)))
)
)
; model
(defn
^{:test (fn []
(assert (=
#{[0 1 3] [0 1 4] [0 2 5 11] [0 2 5 12] [0 2 6]}
(model-branches test-tree)
))
)}
model-branches [tree]
(set (map (fn [endpoint]
(reverse (into [endpoint] (tree-parents endpoint)))
) (tree-appendable-positions tree)))
)
(defn
^{:test (fn []
(assert (and
(=
(list > '(nth item 1) 0.5)
(create-condition (tree-find-by-pos test-tree 0) 1)
)
(=
(list 'not (list > '(nth item 1) 0.5))
(create-condition (tree-find-by-pos test-tree 0) 2)
)
))
)}
create-condition
([row next-index]
(let [expr (list (row :func) (list 'nth 'item (row :attr)) (row :value))]
(if (odd? next-index) expr (list 'not expr))
)
)
([row]
(create-condition row 1)
)
)
(defn
^{:test (fn []
(assert (=
(list 'and (list > '(nth item 1) 0.5) (list 'not (list < '(nth item 3) 0.2)))
(create-branch-filter-condition [0 1 4] test-tree)
))
)}
create-branch-filter-condition [branch tree]
(loop [b branch conditions []]
(if (<= (count b) 1)
(apply list (into ['and] conditions))
(recur
(rest b)
(into conditions
[(create-condition (tree-find-by-pos tree (first b)) (second b))]
)
)
)
)
)
(defn
create-branch-filter-function [branch tree]
(eval (list 'fn ['item] (create-branch-filter-condition branch tree)))
)
(defn
^{:test (fn []
(assert (=
{:a 1 :b 2}
(classes [[0 :a] [1 :b] [2 :b]] 1)
))
)}
classes [data class-index]
(apply merge (map (fn [cls]
{cls (count (filter (fn [item]
(= cls (nth item class-index))
) data))}
) (distinct (get-column data class-index))))
)
(defn
^{:doc "vrátí třídu s největším poměrem počtu výskytů ve vyfiltrovaných datech vůči celému vzorku"
:test (fn []
(let [
test-dataset {
:meta {
:data [0]
:class 1
}
:data [
[0 :a]
[1 :b]
[3 :b]
[5 :b]
]
}
]
(map (fn [expected filter-cb]
(assert (= expected (evaluate-class test-dataset filter-cb)))
) [:a :a :b] [
(fn [x] (< (first x) 1))
(fn [x] (< (first x) 3))
(fn [x] (> (first x) 1))
])
)
)}
evaluate-class [dataset filter-cb]
(first (first (let [
data (dataset :data)
class-index ((dataset :meta) :class)
filtered-data (filter filter-cb data)
data-classes (classes data class-index)
filtered-data-classes (classes filtered-data class-index)
]
(sort (fn [row1 row2] (> (second row1) (second row2))) (map (fn [cls]
[cls (/ (get filtered-data-classes cls) (get data-classes cls))]
) (keys filtered-data-classes)))
)))
)
(defn
all-classes [tree test-dataset]
(into {} (map (fn [branch]
[(last branch) (evaluate-class test-dataset (create-branch-filter-function branch tree))]
) (model-branches tree)))
)
(defn
model-condition [row tree classes]
(let [
children (tree-children (row :pos))
left-child (first children)
left (if (contains? classes left-child)
(get classes left-child)
(model-condition (tree-find-by-pos tree left-child) tree classes)
)
right-child (second children)
right (if (contains? classes right-child)
(get classes right-child)
(model-condition (tree-find-by-pos tree right-child) tree classes)
)
]
(list 'if (create-condition row) left right)
)
)
(defn
model-from-tree [tree test-dataset]
(let [
classes (all-classes tree test-dataset)
condition (model-condition (tree-find-by-pos tree 0) tree classes)
]
(list 'fn ['item] condition)
)
)
; data preparation
(defn
^{:doc "shuffle dataset and take 1/3 as train data and rest as test data"}
divide-train-and-test-data [dataset]
(let [
divide-at (/ (count dataset) 3)
shuffled (shuffle dataset)
]
{:train (take divide-at shuffled), :test (nthnext shuffled divide-at)}
)
)
(defn
^{:doc "normalize numbers in dataset to 0.0 to 1.0 interval"
:test (fn []
(assert (=
'((0 1 1 1 :A) (1/2 0 0.5 1/4 :B) (1 1 0 0 :C))
(normalize-dataset [[1 2 3 8 :A] [2 0 2.5 2 :B] [3 2 2 0 :C]])
))
)}
normalize-dataset [dataset]
(let [
create-reduce-func (fn [comparator]
(fn [item1 item2]
(map (fn [a b]
(if (and (number? a) (number? b)) (comparator a b) false) ; compare for numbers or return nil
) item1 item2)
)
)
min-vec (reduce (create-reduce-func min) dataset)
max-vec (reduce (create-reduce-func max) dataset)
]
(map (fn [instance]
(map (fn [cur-val min-val max-val]
(if (number? cur-val) (/ (- cur-val min-val) (- max-val min-val)) cur-val)
) instance min-vec max-vec)
) dataset)
)
)
; model testing
(defn
^{:doc "knows where item instance is written and looks at it"
:test (fn []
(assert (= :Iris-setosa (god-classifier [5.1 3.5 1.4 0.2 :Iris-setosa]) ))
)}
god-classifier [instance]
(last instance)
)
(defn
^{:doc "returns function which returns true if classifier is correct"}
classifier-comparator-factory [classifier]
(fn [item] (= (classifier item) (god-classifier item)))
)
(defn
^{:doc "returns numbers how many classifications were correct and how many were wrong"
:test (fn []
(let [
const-classifier (fn [item] :A)
dataset [[:A] [:A] [:B] [:A] [:C]]
]
(assert (= [3 2] (test-model const-classifier dataset)))
)
)}
test-model [classifier dataset]
(reduce
(fn [results cur] (if (= cur true)
[(+ 1 (first results)) (second results)]
[(first results) (+ 1 (second results))]))
[0 0]
(map (classifier-comparator-factory classifier) dataset)
)
)
; genetics
(defn
^{:test (fn []
(do
(assert (=
#{}
(set (useless-nodes
{1 :Iris-setosa, 2 :Iris-versicolor}
[[0 1] [0 2]]
))
))
(assert (=
#{2 5 6 14}
(set (useless-nodes
{3 :Iris-setosa, 4 :Iris-virginica, 11 nil, 12 nil, 13 nil, 29 nil, 30 nil}
[[0 1 3] [0 1 4] [0 2 5 11] [0 2 5 12] [0 2 6 13] [0 2 6 14 29] [0 2 6 14 30]]
))
))
)
)}
useless-nodes [all-classes model-branches]
(let [
useless-endpoints (keys (filter (fn [[k v]]
(nil? v)
) all-classes))
parents-of-useless-endpoints (distinct (into [] (apply union (map tree-parents useless-endpoints))))
]
(filter (fn [parent] ; has all endpoints useless
(let [
parent-branches (filter (fn [branch]
(in? branch parent)
) model-branches)
parent-endpoints (map last parent-branches)
]
(empty? (difference (set parent-endpoints) (set useless-endpoints)))
)) parents-of-useless-endpoints)
)
)
(defn
; todo should remove also nodes where all test-data goes to same branch
remove-useless-nodes [tree test-dataset]
(let [
useless-nodes (useless-nodes (all-classes tree test-dataset) (model-branches tree))
]
(set (filter (fn [node]
(not (in? useless-nodes (node :pos)))
) tree))
)
)
(defn
create-random-node [at dataset-meta]
(let [
data-indexes (dataset-meta :data)
func (nth [< >] (* 2 (rand)))
attr (nth data-indexes (* (count data-indexes) (rand)))
]
{:pos at :func func :attr attr :value (rand)}
)
)
(defn
create-initial-trees [n dataset-meta]
(map (fn [n]
(let [
node-count (+ 2 (int (* 5 (rand))))
]
(set (map (fn [node-index]
(create-random-node node-index dataset-meta)
) (range 0 node-count)))
)
) (range 0 n))
)
(defn
fitness [tree test-dataset]
(let [
data (test-dataset :data)
class-index ((test-dataset :meta) :class)
model (model-from-tree tree test-dataset)
correctness (test-model (eval model) data)
correctness-num (/ (first correctness) (+ (first correctness) (second correctness)))
classes-ct (count (distinct (get-column data class-index)))
node-count (count tree)
depth (inc (inc (tree-depth (apply max (tree-positions tree)))))
magic-number (* correctness-num correctness-num classes-ct (/ 1 depth))
]
{
:magic-number magic-number
:correctness correctness-num
:classes classes-ct
:depth depth
}
)
)
(defn
mutate-add [tree pos dataset-meta]
(union
tree
#{(create-random-node pos dataset-meta)}
)
)
(defn
mutate-rm [tree pos]
(if (not= pos 0)
(remove-subtree tree (subtree pos tree))
tree
)
)
(defn
mutate-change-node [tree dataset-meta pos]
(union
(difference tree #{(tree-find-by-pos tree pos)})
#{(create-random-node pos dataset-meta)}
)
)
(defn
mutate-change-val [tree pos]
(let [random-node (tree-find-by-pos tree pos)]
(union
(difference tree #{random-node})
#{(merge
random-node {:value
(max 0.05 (min 0.95
(* (random-node :value) (inc (rand)))
))
}
)}
)
)
)
(defn
mutate [tree dataset-meta]
(let [
positions (tree-positions tree)
appendable (into [] (tree-appendable-positions tree))
random-appendable (nth appendable (* (rand) (count appendable)))
random-node-pos (nth positions (* (rand) (count positions)))
rnd (rand)
]
(cond
(< rnd 0.1) (mutate-rm tree random-node-pos)
(< rnd 0.25) (mutate-add tree random-appendable dataset-meta)
(< rnd 0.40) (mutate-change-node tree dataset-meta random-node-pos)
(< rnd 0.70) (mutate-change-val tree random-node-pos)
:else tree
)
)
)
(defn
merge-tree [t1 t2]
(let [
positions-t1 (tree-positions t1)
positions-t2 (tree-positions t2)
rnd-pos-1 (nth positions-t1 (* (rand) (count positions-t1)))
rnd-pos-2 (nth positions-t2 (* (rand) (count positions-t2)))
new-tree (if (= (count positions-t1) 1) t1 (remove-subtree t1 (subtree rnd-pos-1 t1)))
appendable (into [] (tree-appendable-positions new-tree))
random-appendable (nth appendable (* (rand) (count appendable)))
appended-subtree (move-subtree random-appendable (subtree rnd-pos-2 t2))
]
(union new-tree appended-subtree)
)
)
(defn
create-train-dataset [dataset]
{
:meta (dataset :meta)
:data ((divide-train-and-test-data (normalize-dataset (dataset :data))) :train)
}
)
(defn
run-genetics [treeset train-dataset]
(let [
dataset-meta (train-dataset :meta)
mapped (map (fn [tree]
(let [optimized (remove-useless-nodes tree train-dataset)]
[tree (fitness tree train-dataset)]
)
) treeset)
sorted (sort (fn [[tree1 fitness1] [tree2 fitness2]]
(> (fitness1 :magic-number) (fitness2 :magic-number))
) mapped)
sorted-trees (map first sorted)
ct (count sorted)
divide-at (/ ct 3)
to-merge (take divide-at sorted-trees)
to-mutate (take divide-at (nthnext sorted-trees divide-at))
to-delete (nthnext (nthnext sorted-trees divide-at) divide-at)
to-merge-ct (count to-merge)
to-delete-ct (count to-delete)
merged (set (map (fn [i]
(merge-tree
(nth to-merge (* (rand) to-merge-ct))
(nth to-merge (* (rand) to-merge-ct))
)
) (range 0 to-delete-ct)))
mutated (set (map (fn [tree]
(mutate tree dataset-meta)
) to-merge))
best-solution (first sorted)
ret (set (union (set to-merge) mutated merged))
]
(do
(println "Best solution:")
(println (model-from-tree (first best-solution) train-dataset))
(println "fitness: " (second best-solution))
(println)
(println "Average solution:")
(println "correctness: " (/ (apply + (map (fn [[tree fit]]
(fit :correctness)
) sorted)) ct))
(println "depth: " (/ (apply + (map (fn [[tree fit]]
(fit :depth)
) sorted)) ct))
ret
)
)
)
; test runner
(defn
^{:doc "run tests all functions"}
test-all []
(map
(fn [func] (println func (((meta func) :test)))) ; run tests
[
#'get-column #'god-classifier #'test-model #'normalize-dataset #'tree-depth
#'tree-position-in-row #'tree-parent #'tree-parents #'tree-children
#'tree-appendable-positions #'subtree #'tree-find-by-pos #'move-subtree
#'model-branches #'create-condition #'create-branch-filter-condition
#'evaluate-class #'classes #'useless-nodes
]
)
)
(test-all)
; dataset
(def iris-dataset {
:meta {
:data [0 1 2 3]
:class 4
}
:data [
[5.1 3.5 1.4 0.2 :Iris-setosa]
[4.9 3.0 1.4 0.2 :Iris-setosa]
[4.7 3.2 1.3 0.2 :Iris-setosa]
[4.6 3.1 1.5 0.2 :Iris-setosa]
[5.0 3.6 1.4 0.2 :Iris-setosa]
[5.4 3.9 1.7 0.4 :Iris-setosa]
[4.6 3.4 1.4 0.3 :Iris-setosa]
[5.0 3.4 1.5 0.2 :Iris-setosa]
[4.4 2.9 1.4 0.2 :Iris-setosa]
[4.9 3.1 1.5 0.1 :Iris-setosa]
[5.4 3.7 1.5 0.2 :Iris-setosa]
[4.8 3.4 1.6 0.2 :Iris-setosa]
[4.8 3.0 1.4 0.1 :Iris-setosa]
[4.3 3.0 1.1 0.1 :Iris-setosa]
[5.8 4.0 1.2 0.2 :Iris-setosa]
[5.7 4.4 1.5 0.4 :Iris-setosa]
[5.4 3.9 1.3 0.4 :Iris-setosa]
[5.1 3.5 1.4 0.3 :Iris-setosa]
[5.7 3.8 1.7 0.3 :Iris-setosa]
[5.1 3.8 1.5 0.3 :Iris-setosa]
[5.4 3.4 1.7 0.2 :Iris-setosa]
[5.1 3.7 1.5 0.4 :Iris-setosa]
[4.6 3.6 1.0 0.2 :Iris-setosa]
[5.1 3.3 1.7 0.5 :Iris-setosa]
[4.8 3.4 1.9 0.2 :Iris-setosa]
[5.0 3.0 1.6 0.2 :Iris-setosa]
[5.0 3.4 1.6 0.4 :Iris-setosa]
[5.2 3.5 1.5 0.2 :Iris-setosa]
[5.2 3.4 1.4 0.2 :Iris-setosa]
[4.7 3.2 1.6 0.2 :Iris-setosa]
[4.8 3.1 1.6 0.2 :Iris-setosa]
[5.4 3.4 1.5 0.4 :Iris-setosa]
[5.2 4.1 1.5 0.1 :Iris-setosa]
[5.5 4.2 1.4 0.2 :Iris-setosa]
[4.9 3.1 1.5 0.1 :Iris-setosa]
[5.0 3.2 1.2 0.2 :Iris-setosa]
[5.5 3.5 1.3 0.2 :Iris-setosa]
[4.9 3.1 1.5 0.1 :Iris-setosa]
[4.4 3.0 1.3 0.2 :Iris-setosa]
[5.1 3.4 1.5 0.2 :Iris-setosa]
[5.0 3.5 1.3 0.3 :Iris-setosa]
[4.5 2.3 1.3 0.3 :Iris-setosa]
[4.4 3.2 1.3 0.2 :Iris-setosa]
[5.0 3.5 1.6 0.6 :Iris-setosa]
[5.1 3.8 1.9 0.4 :Iris-setosa]
[4.8 3.0 1.4 0.3 :Iris-setosa]
[5.1 3.8 1.6 0.2 :Iris-setosa]
[4.6 3.2 1.4 0.2 :Iris-setosa]
[5.3 3.7 1.5 0.2 :Iris-setosa]
[5.0 3.3 1.4 0.2 :Iris-setosa]
[7.0 3.2 4.7 1.4 :Iris-versicolor]
[6.4 3.2 4.5 1.5 :Iris-versicolor]
[6.9 3.1 4.9 1.5 :Iris-versicolor]
[5.5 2.3 4.0 1.3 :Iris-versicolor]
[6.5 2.8 4.6 1.5 :Iris-versicolor]
[5.7 2.8 4.5 1.3 :Iris-versicolor]
[6.3 3.3 4.7 1.6 :Iris-versicolor]
[4.9 2.4 3.3 1.0 :Iris-versicolor]
[6.6 2.9 4.6 1.3 :Iris-versicolor]
[5.2 2.7 3.9 1.4 :Iris-versicolor]
[5.0 2.0 3.5 1.0 :Iris-versicolor]
[5.9 3.0 4.2 1.5 :Iris-versicolor]
[6.0 2.2 4.0 1.0 :Iris-versicolor]
[6.1 2.9 4.7 1.4 :Iris-versicolor]
[5.6 2.9 3.6 1.3 :Iris-versicolor]
[6.7 3.1 4.4 1.4 :Iris-versicolor]
[5.6 3.0 4.5 1.5 :Iris-versicolor]
[5.8 2.7 4.1 1.0 :Iris-versicolor]
[6.2 2.2 4.5 1.5 :Iris-versicolor]
[5.6 2.5 3.9 1.1 :Iris-versicolor]
[5.9 3.2 4.8 1.8 :Iris-versicolor]
[6.1 2.8 4.0 1.3 :Iris-versicolor]
[6.3 2.5 4.9 1.5 :Iris-versicolor]
[6.1 2.8 4.7 1.2 :Iris-versicolor]
[6.4 2.9 4.3 1.3 :Iris-versicolor]
[6.6 3.0 4.4 1.4 :Iris-versicolor]
[6.8 2.8 4.8 1.4 :Iris-versicolor]
[6.7 3.0 5.0 1.7 :Iris-versicolor]
[6.0 2.9 4.5 1.5 :Iris-versicolor]
[5.7 2.6 3.5 1.0 :Iris-versicolor]
[5.5 2.4 3.8 1.1 :Iris-versicolor]
[5.5 2.4 3.7 1.0 :Iris-versicolor]
[5.8 2.7 3.9 1.2 :Iris-versicolor]
[6.0 2.7 5.1 1.6 :Iris-versicolor]
[5.4 3.0 4.5 1.5 :Iris-versicolor]
[6.0 3.4 4.5 1.6 :Iris-versicolor]
[6.7 3.1 4.7 1.5 :Iris-versicolor]
[6.3 2.3 4.4 1.3 :Iris-versicolor]
[5.6 3.0 4.1 1.3 :Iris-versicolor]
[5.5 2.5 4.0 1.3 :Iris-versicolor]
[5.5 2.6 4.4 1.2 :Iris-versicolor]
[6.1 3.0 4.6 1.4 :Iris-versicolor]
[5.8 2.6 4.0 1.2 :Iris-versicolor]
[5.0 2.3 3.3 1.0 :Iris-versicolor]
[5.6 2.7 4.2 1.3 :Iris-versicolor]
[5.7 3.0 4.2 1.2 :Iris-versicolor]
[5.7 2.9 4.2 1.3 :Iris-versicolor]
[6.2 2.9 4.3 1.3 :Iris-versicolor]
[5.1 2.5 3.0 1.1 :Iris-versicolor]
[5.7 2.8 4.1 1.3 :Iris-versicolor]
[6.3 3.3 6.0 2.5 :Iris-virginica]
[5.8 2.7 5.1 1.9 :Iris-virginica]
[7.1 3.0 5.9 2.1 :Iris-virginica]
[6.3 2.9 5.6 1.8 :Iris-virginica]
[6.5 3.0 5.8 2.2 :Iris-virginica]
[7.6 3.0 6.6 2.1 :Iris-virginica]
[4.9 2.5 4.5 1.7 :Iris-virginica]
[7.3 2.9 6.3 1.8 :Iris-virginica]
[6.7 2.5 5.8 1.8 :Iris-virginica]
[7.2 3.6 6.1 2.5 :Iris-virginica]
[6.5 3.2 5.1 2.0 :Iris-virginica]
[6.4 2.7 5.3 1.9 :Iris-virginica]
[6.8 3.0 5.5 2.1 :Iris-virginica]
[5.7 2.5 5.0 2.0 :Iris-virginica]
[5.8 2.8 5.1 2.4 :Iris-virginica]
[6.4 3.2 5.3 2.3 :Iris-virginica]
[6.5 3.0 5.5 1.8 :Iris-virginica]
[7.7 3.8 6.7 2.2 :Iris-virginica]
[7.7 2.6 6.9 2.3 :Iris-virginica]
[6.0 2.2 5.0 1.5 :Iris-virginica]
[6.9 3.2 5.7 2.3 :Iris-virginica]
[5.6 2.8 4.9 2.0 :Iris-virginica]
[7.7 2.8 6.7 2.0 :Iris-virginica]
[6.3 2.7 4.9 1.8 :Iris-virginica]
[6.7 3.3 5.7 2.1 :Iris-virginica]
[7.2 3.2 6.0 1.8 :Iris-virginica]
[6.2 2.8 4.8 1.8 :Iris-virginica]
[6.1 3.0 4.9 1.8 :Iris-virginica]
[6.4 2.8 5.6 2.1 :Iris-virginica]
[7.2 3.0 5.8 1.6 :Iris-virginica]
[7.4 2.8 6.1 1.9 :Iris-virginica]
[7.9 3.8 6.4 2.0 :Iris-virginica]
[6.4 2.8 5.6 2.2 :Iris-virginica]
[6.3 2.8 5.1 1.5 :Iris-virginica]
[6.1 2.6 5.6 1.4 :Iris-virginica]
[7.7 3.0 6.1 2.3 :Iris-virginica]
[6.3 3.4 5.6 2.4 :Iris-virginica]
[6.4 3.1 5.5 1.8 :Iris-virginica]
[6.0 3.0 4.8 1.8 :Iris-virginica]
[6.9 3.1 5.4 2.1 :Iris-virginica]
[6.7 3.1 5.6 2.4 :Iris-virginica]
[6.9 3.1 5.1 2.3 :Iris-virginica]
[5.8 2.7 5.1 1.9 :Iris-virginica]
[6.8 3.2 5.9 2.3 :Iris-virginica]
[6.7 3.3 5.7 2.5 :Iris-virginica]
[6.7 3.0 5.2 2.3 :Iris-virginica]
[6.3 2.5 5.0 1.9 :Iris-virginica]
[6.5 3.0 5.2 2.0 :Iris-virginica]
[6.2 3.4 5.4 2.3 :Iris-virginica]
[5.9 3.0 5.1 1.8 :Iris-virginica]
]
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment