Clojure and The UFC: Part 2

Professional fighting and the world of Clojure // Exploring fighters and their fights with the Clojure programming language.

NOTE: This was written in a Gorilla REPL and is known to not format/indent Clojure code correctly with certain fonts

Part 1 Viewable https://cdn.rawgit.com/runexec/ufc-clojure/master/html/part1.html?cdn=1

Part 1 Raw https://github.com/runexec/ufc-clojure

Tons of charts. Tons of code.

  • The following code block can be skipped if you've already read Part 1.
  • There are a lot of charts to look at if you're not interested in the code.
 
(ns ufc
  (:require 
   [gorilla-plot.core :as plot]
   [gorilla-renderable.core :as r]
   [gorilla-repl.html :as h]
   [clojure.pprint :refer [pprint
                           print-table]]
   [clojure.data.json :as json]
    [clojure.string :as s]
   [hiccup.core :refer [html]]))
;; Define the default dataset file
(def stats-json-fp "complete-fighter-stats.json")
;; Define the default JSON key
(def entry-point
  "Default JSON key for main data"
  "fighters")
;; Function gets the JSON value of entry-point
(defn stats*
  "Loads JSON file and returns entry-point value."
  ([fp] (stats* fp entry-point))
  ([fp entry-point]
     (-> fp
         slurp
         json/read-str
         (get entry-point {}))))
;; Create a version of stats* that caches the results
(let [f (memoize stats*)]
  (defn stats
  "Loads JSON file and returns cached entry-point value."
  ([]
     (f stats-json-fp))
  ([^:String fp] 
     (f fp entry-point))
  ([^:String fp 
    ^:String entry-point]
     (f fp entry-point))))
;; Bruteforce numeric type enforcement
(defn enforce-type
  "Integer first, Float second, original object on fail"
  [x]
  (try
    (Integer/parseInt x)
    (catch Exception ex
      (try
        (Float/parseFloat x)
        (catch Exception ex x)))))
;; The function used to modify fighter-without-fights fn
(defn recursive-enforce-type
  "recursively calls enforce-type on a fighter map"
  [x]
  (reduce merge
          (for [[k v] x
                :let [f (if-not (map? v)
                          enforce-type
                          recursive-enforce-type)]]
            (hash-map k (f v)))))
;; Add recursive-enforce-type to this fn
(defn fighter-without-fights
  "Returns a fighter without fights collection"
  [fighter-map]
  (recursive-enforce-type
   (dissoc fighter-map "Fights")))
(defn ->numerics
  "Recursively filters for numeric values from a fighter map"
  [x]
  (let [data (recursive-enforce-type x)]
    (reduce merge
            (for [[k v] data]
              (if-not (map? v)
                (if-not (number? v)
                  {}
                  (hash-map k v))
                (->numerics v))))))
(defn without
  "Removes values from a fighter map"
  [x & ks]
  (apply dissoc x ks))
(defn ->numeric-map
  "Returns numeric map without IDs"
  [x]
  (-> x
      fighter-without-fights
      ->numerics
      (without "MMAID"
               "WeightClassID"
               "FighterID")))
(defn ->html [x]
  (h/html-view (html x)))
#'ufc/->html

Attribute Extraction

  • x-gen-get-it fn makes a xform that calls enforce-type on a specified collection value.
  • (def x-...) forms call x-gen-get-it to create a representative xform
xxxxxxxxxx
 
(defn x-gen-get-it [get-in-coll]
  (map #(-> %
            (get-in get-in-coll 0)
            enforce-type)))
(def x-age
  (x-gen-get-it ["Age"]))
(def x-previous
  (x-gen-get-it ["Previous"]))
(def x-current
  (x-gen-get-it ["Current"]))
(def x-striking-defense
  (x-gen-get-it ["StrikingDefense"]))
(def x-no-contests
  (x-gen-get-it ["NoContests"]))
(def x-takedown-defense
  (x-gen-get-it ["TakedownDefense"]))
(def x-kd-average
  (x-gen-get-it ["KDAverage"]))
(def x-takedown-average
  (x-gen-get-it ["TakedownAverage"]))
(def x-weight
  (x-gen-get-it ["Weight"]))
(def x-wins
  (x-gen-get-it ["Wins"]))
(def x-draws
  (x-gen-get-it ["Draws"]))
(def x-reach
  (x-gen-get-it ["Reach"]))
(def x-height
  (x-gen-get-it ["Height"]))
(def x-takedown-accuracy
  (x-gen-get-it ["TakedownAccuracy"]))
(def x-slpm
  (x-gen-get-it ["SLpM"]))
(def x-submissions-average
  (x-gen-get-it ["SubmissionsAverage"]))
(def x-losses
  (x-gen-get-it ["Losses"]))
(def x-average-fight-time-seconds
  (x-gen-get-it ["AverageFightTime_Seconds"]))
(def x-striking-accuracy
  (x-gen-get-it ["StrikingAccuracy"]))
(def x-sapm
  (x-gen-get-it ["SApM"]))
(def x-numeric-map
  (map ->numeric-map))
;; Testing the above by using x-age and x-losses
;; on a random fighter.
(let [fighter (rand-nth (stats))
      fm (->numeric-map fighter)
      finto #(first (into [] % [fm]))]
  (println
   (format "Someone is %s years old with %s losses"
           (finto x-age)
           (finto x-losses))))
Someone is 29 years old with 3 losses
nil

Simplify Numeric Extraction

  • get-numeric-stats fn chains x-numeric-map with an arbitrary xform and returns a lazy transformer for all fighters.
xxxxxxxxxx
 
(defn get-numeric-stats [xform]
  (sequence (comp x-numeric-map
                  xform)
            (stats)))
;; Testing with x-age
(->> x-age
     get-numeric-stats
     rand-nth
     (format "%s is a random age")
     pprint)
"34 is a random age"
nil

Frequency and Charting

Scroll beyond the code if you just want the chart

  • frequent fn takes a mixed or single type collection. Returns a collection of distinct values and a count of each occurrence within the provided collection. Examplel => {:value "abc" :count 1}
  • frequent-chart fn takes a collection and some optional charting options. Returns a chart displaying the results of frequent.
xxxxxxxxxx
 
;; Freq finding fn
(defn frequent [coll]
  (let [uniq (-> coll distinct sort)
        freq (fn [x coll]
               (let [t (transient [])]
                 (doseq [v coll]
                   (if (= x v) (conj! t v)))
                 (count t)))]
    (map #(hash-map :value %
                    :count (freq % coll))
         uniq)))
;; Similar to plot/histogram
(defn frequent-chart [coll & opts]
  (let [freq (frequent coll)
        labels (map :value freq)
        plots (map :count freq)
        data [labels plots]
        call (if-not (seq opts)
               data
               (into data opts))]
    (apply plot/bar-chart call)))
;; Generate chart
(let [ages (get-numeric-stats x-age)
      sorted-ages (sort ages)            
      opts [:plot-size 600
            :aspect-ratio 2.5]
       xys (map #(let [{x :value y :count} %]
                   [x y])
                (frequent ages))]
  
  ;; Chart Header
  
  [(->html [:div
             [:h1 "Age of fighters"]
            [:h2 "x = age"] 
            [:h2 "y = number of fighters"]])
   
   ;; Bar Chart
   
   (plot/compose
    (apply frequent-chart
           ages
           :color "red"
           opts)
     
     ;; Line Graph
    (apply plot/list-plot
           xys
           :color "brown"
           :joined true
           opts))])
[

Age of fighters

x = age

y = number of fighters

20212223242526272829303132333435363738394041424447051015202530354045505560
]

The Men and Women of The UFC

Scroll beyond the code if you just want the chart

xxxxxxxxxx
 
;; Gender identifiers
(defn female?
  [fighter]
  (boolean
    (try
      (re-find (re-pattern ".*women.*")
               (-> fighter
                   (get-in ["UFCWeightClass"
                            "Description"]
                           "default-is-male")
                   s/lower-case))
      (catch NullPointerException ex
        
          ;; Fighter has a nil description
          ;; assuming male
        
          false))))
(defn male?
  [fighter]
  (not (female? fighter)))
;; Fighters grouped by gender
(def females (filter female? (stats)))
(def males (filter male? (stats)))
;; Chart found/diffs
(let [cm (count males)
      cf (count females)
      more (if (= cm cf)
             ["There are an equal amount." 0]
             (condp = (max cm cf)
               cf ["More women than men"
                   (- cf cm)]
               cm ["More men than women"
                   (- cm cf)]))]
  
  ;; Chart Header
  
  [(->html [:h1 "Gender of Fighters"])
   
   ;; Diff Chart 
   
   (plot/bar-chart ["Female" "Male"] 
                   [(count females) 
                    (count males)]
                   :color "purple")
   
   ;; Diff Footer
   
   (->html
    [:div
     [:h2 {:style "color:red;"}
      (first more)]
     [:h2 {:style "color:blue;"}
      (str "Diff Amount: " (second more))]])])
[

Gender of Fighters

FemaleMale050100150200250300350400450500550600

More men than women

Diff Amount: 514

]

Comparative Charting

Scroll beyond the code if you just want the chart

  • mf-header fn generates chart/graph header information with or without optional color arguments.
  • mf-compare fn compares two fighters of the opposite gender and returns a single chart.
xxxxxxxxxx
;; Comparative colors
(def male-color "deepskyblue")
(def female-color "crimson")
(def tied-color "green")
;; Legend Header fn
(defn mf-header
  ([] (mf-header male-color
                 female-color
                 tied-color))
  ([male-color
    female-color
    tied-color]
   
   ;; Header Local HTML Header fn
   
     (let [-html (fn [s color]
                   [:h2
                    {:style (format "color:%s;" color)}
                    s])]
       
       ;; Return HTML
       
       (->html [:div                
                (-html "Female" female-color)
                (-html "Male" male-color)
                (-html "Tied (when applied)" tied-color)
                (-html "Zero/Blank" "black")]))))
;; Compare and Chart fn
(defn mf-compare
  ([label
    male
    female]
     (mf-compare label
                 male
                 male-color
                 female
                 female-color
                 tied-color))
  ([label
    male
    male-color
    female
    female-color
    tied-color]
   
   ;; Local Charting fn
   
     (let [chart (fn [color value]
                   (plot/bar-chart [label]
                                   [value]
                                   :opacity 0.60
                                   :aspect-ratio 0.25
                                   :plot-size 80
                                   :color color))
           
           ;; Default Value Display Order
           
           default [(chart female-color female)
                    (chart male-color male)]]
       
       ;; Greatest Value Set Behind Lowest (if not tied)
       
       (if (= male female)      
         (chart tied-color male)
         (apply
          plot/compose
          (if (< male female)
            default
            (reverse default)))))))
;; Age comparison between two random fighters
;; one male
;; one female
(let [k "Age"
      rand-age #(-> %
                    rand-nth
                    ->numeric-map
                    (get k))]
  
  ;; Chart Header
  
  [(->html [:h1 "Comparing Genders - An Example"])
   (mf-header)
   (->html [:h2 "Some random fighters"])
   
   ;; Comparison Chart
   
   (mf-compare k
               (rand-age males)
               (rand-age females))])
[

Comparing Genders - An Example

Female

Male

Tied (when applied)

Zero/Blank

Some random fighters

Age051015202530
]

The Youngest & Oldest Fighters

Scroll beyond the code if you just want the chart

x
;; Not the same as get-numeric-stats
(defn numeric-map-attr
  [data xform]
  (let [xform (map #(sequence (comp x-numeric-map
                                    xform)
                              [%]))]
    (sequence xform data)))
(let [age #(numeric-map-attr % x-age)
      females (age females)
      males (age males)
      xcapable (fn [f]
                 #(->> %
                       (mapcat identity)
                       (apply f)))
      xmax (xcapable max)
      xmin (xcapable min)]
  [(->html [:h1 "Youngest & Oldest Fighters"])
   (mf-header)
   (mf-compare "Oldest"
               (xmax males)
               (xmax females))
   (mf-compare "Youngest"
               (xmin males)
               (xmin females))])
[

Youngest & Oldest Fighters

Female

Male

Tied (when applied)

Zero/Blank

Oldest05101520253035404550
Youngest02468101214161820
]

Comparing Mean, Median, Mode, and Range

Scroll beyond the code if you just want the chart

xxxxxxxxxx
 
;; Mean, Median, Mode, and Range
(defn mean
  [& xs]
  (float
   (/ (reduce + xs)
      (count xs))))
(defn median
  [& xs]
  (let [sz (count xs)
        sz (if-not (odd? sz) sz (inc sz))
        half (dec (float (quot sz 2)))]
    (if (<= sz 2)
      (first xs)
      (-> xs
          sort
          (nth half)))))
(defn mode
  [& xs]
  (let [t (transient {:count 0 :value 0})]
    (doseq [x xs
            :let [found (filter #{x} xs)
                  c (count found)]]
      (if (< (:count t) c)             
        (assoc! t :count c :value x)))
    (:value t)))
(defn range*
  [& xs]
  (let [[l g] ((juxt first last)
               (sort xs))]
    (- g l)))
;; Generic Male & Female Comparison Chart
(defn mf-compare-*
  [f
   label
   male
   female]
  (let [f (partial apply f)]
    (mf-compare label
                (f male)
                (f female))))
;; Chart fn for Mean, Median, Mode, and Range
(defn mf-mean
  [male
   female]
  (mf-compare-* mean "Mean" male female))
(defn mf-median
  [male
   female]
  (mf-compare-* median "Median" male female))
(defn mf-mode
  [male
   female]
  (mf-compare-* mode "Mode" male female))
(defn mf-range
  [male
   female]
  (mf-compare-* range* "Range" male female))
;; A combination of calculations
(defn m3r
  [male
   female]
  (for [[t f] [["Mean" mf-mean]
               ["Median" mf-median]
               ["Mode" mf-mode]
               ["Range" mf-range]]]
    (f male female)))
;; An Example of M3R
[(->html [:h1 "Fake Numbers - A M3R Example"])
 (mf-header)
 (m3r [1 2 3] [4 5 6])]
[

Fake Numbers - A M3R Example

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.51.01.52.02.53.03.54.04.55.0
Median0.00.51.01.52.02.53.03.54.04.55.0
Mode0.00.51.01.52.02.53.03.54.0
Range0.00.20.40.60.81.01.21.41.61.82.0
)
]

Comparing All Men w/ All Women (M3R)

Scroll beyond the code if you just want the chart

xxxxxxxxxx
(defn numeric-map-attr-x*
  [data xform]
  (reduce into
          []
          (numeric-map-attr data xform)))
(defn females-numeric-x
  [xform]
  (numeric-map-attr-x* females xform))
(defn fnx
  [xform]
  (females-numeric-x xform))
(defn males-numeric-x
  [xform]
  (numeric-map-attr-x* males xform))
(defn mnx
  [xform]
  (males-numeric-x xform))
[(->html [:h1 "Fighter Age M3R"])
 (mf-header)
 (m3r (mnx x-age)
      (fnx x-age))]
[

Fighter Age M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean051015202530
Median051015202530
Mode051015202530
Range02468101214161820222426
)
]
 
(map (fn [[s x]]
       (vector
        (->html [:h1 (format "Fighter %s M3R" s)])
        (mf-header)
        (m3r (mnx x)
             (fnx x))))
     [["Age" x-age]
      ["AverageFightTime_Seconds" x-average-fight-time-seconds]
      ["Current" x-current]
      ["Draws" x-draws]
      ["Height" x-height]
      ["KDAverage" x-kd-average]
      ["Losses" x-losses]
      ["NoContests" x-no-contests]
      ["Previous" x-previous]
      ["Reach" x-reach]
      ["SApM" x-sapm]
      ["SLpM" x-slpm]
      ["StrikingAccuracy" x-striking-accuracy]
      ["StrikingDefense" x-striking-defense]
      ["SubmissionsAverage" x-submissions-average]
      ["TakedownAccuracy" x-takedown-accuracy]
      ["TakedownDefense" x-takedown-defense]
      ["Weight" x-weight]
      ["Wins" x-wins]])
([

Fighter Age M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean051015202530
Median051015202530
Mode051015202530
Range02468101214161820222426
)
]
[

Fighter AverageFightTime_Seconds M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0100200300400500600700
Median0100200300400500600700
Mode0100200300400500600700800900
Range01002003004005006007008009001,0001,100
)
]
[

Fighter Current M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.20.40.60.81.01.21.4
Median
Mode
Range012345678910
)
]
[

Fighter Draws M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.000.020.040.060.080.100.120.140.160.180.200.22
Median
Mode
Range01234567
)
]
[

Fighter Height M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean01020304050607080
Median010203040506070
Mode01020304050607080
Range01020304050607080
)
]
[

Fighter KDAverage M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.000.050.100.150.200.250.300.350.400.450.50
Median0.000.020.040.060.080.100.120.140.160.180.20
Mode
Range012345678910111213
)
]
[

Fighter Losses M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.51.01.52.02.53.03.54.04.55.0
Median0.00.51.01.52.02.53.03.54.0
Mode0.00.51.01.52.02.53.0
Range02468101214161820
)
]
[

Fighter NoContests M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.000.020.040.060.080.100.120.140.16
Median
Mode
Range0.00.20.40.60.81.01.21.41.61.82.0
)
]
[

Fighter Previous M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.20.40.60.81.01.21.4
Median
Mode
Range012345678910
)
]
[

Fighter Reach M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean010203040506070
Median01020304050607080
Mode01020304050607080
Range0102030405060708090
)
]
[

Fighter SApM M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.51.01.52.02.53.03.54.0
Median0.00.51.01.52.02.53.03.54.0
Mode
Range024681012141618202224
)
]
[

Fighter SLpM M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.51.01.52.02.53.03.54.0
Median0.00.51.01.52.02.53.0
Mode
Range024681012141618202224
)
]
[

Fighter StrikingAccuracy M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean05101520253035404550
Median05101520253035404550
Mode
Range0102030405060708090
)
]
[

Fighter StrikingDefense M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean051015202530354045505560
Median051015202530354045505560
Mode
Range0102030405060708090100
)
]
[

Fighter SubmissionsAverage M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0.00.10.20.30.40.50.60.70.80.9
Median0.000.050.100.150.200.250.300.350.400.450.50
Mode
Range0246810121416182022
)
]
[

Fighter TakedownAccuracy M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean0510152025303540
Median0510152025303540
Mode
Range0102030405060708090100
)
]
[

Fighter TakedownDefense M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean051015202530354045505560
Median010203040506070
Mode
Range0102030405060708090100
)
]
[

Fighter Weight M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean020406080100120140160
Median020406080100120140160
Mode020406080100120140160
Range020406080100120140
)
]
[

Fighter Wins M3R

Female

Male

Tied (when applied)

Zero/Blank

(
Mean02468101214
Median02468101214
Mode0123456789101112
Range05101520253035404550
)
]
)

Fighter Count For Each Weight Class

Scroll beyond the code if you just want the chart

xxxxxxxxxx
 
(def x-weight-class
  (map #(get-in % ["UFCWeightClass" "Description"])))
(let [call #(frequent (sequence x-weight-class %))
      all (call (stats))]
  (for [{title :value v :count} (sort-by :count all)
        :when title]
    [(->html [:div
              [:h1 title]
              [:h2 "y = # of fighters"]])
     (plot/bar-chart [title]
                     [v]
                     :plot-size 80
                     :aspect-ratio 0.30)]))
([

Women's Bantamweight

y = # of fighters

Women's Bantamweight051015202530
]
[

Flyweight

y = # of fighters

Flyweight0510152025303540
]
[

Heavyweight

y = # of fighters

Heavyweight0510152025303540
]
[

Light Heavyweight

y = # of fighters

Light Heavyweight05101520253035404550
]
[

Bantamweight

y = # of fighters

Bantamweight051015202530354045505560
]
[

Featherweight

y = # of fighters

Featherweight010203040506070
]
[

Middleweight

y = # of fighters

Middleweight01020304050607080
]
[

Welterweight

y = # of fighters

Welterweight0102030405060708090100110
]
[

Lightweight

y = # of fighters

Lightweight0102030405060708090100110120
]
)

Non-numeric Attributes

xxxxxxxxxx
 
(def x-fighting-out-of
   (map #(get % "FightingOutOf")))
(def x-fighting-out-of-city
  (comp
   x-fighting-out-of
   (map #(get % "City"))))
(def x-fighting-out-of-state
  (comp
   x-fighting-out-of
   (map #(get % "State"))))
(def x-fighting-out-of-country
  (comp
   x-fighting-out-of
   (map #(get % "Country"))))
(->> females
     (sequence x-fighting-out-of-city)
     (take 3)
     (reduce #(str %1 ", " %2))
     (str "First 3 Example: Fighting out of => ")
     pprint)
"First 3 Example: Fighting out of => Las Vegas, Victoria, San Diego"
nil

Charting Non-numeric Attributes

Scroll beyond the code if you just want the chart

xxxxxxxxxx
 
(defn chart-horizontal-percent-bars
  [xform
   limit
   data
   data-color
   bar-color]
  (as-> data x      
        (sequence xform x)
        (frequent x)
        (sort-by :count x)
        (reverse x)
        (take limit x)
        (let [cs (map :count x)
              amount (reduce + cs)
              per (memoize
                   (fn [count]
                     (Float/parseFloat
                      (format "%3.3f"
                              (* 100.0
                                 (float
                                  (/ count amount)))))))]
          (->html
           (into [:table
                  {:style "tr td {border-style:none;padding:10px;}"}]
                 
                 (for [{:keys [value count]} x
                       :let [p (per count)]]
                   [:tr 
                    [:td (or value "No Record :(")]
                    [:td p "%"]
                    [:td.container
                     (for [f (range 0.001 101)]
                       [:span {:style
                               (format
                                "background:%s;"
                                (if (<= f p)
                                  data-color
                                  bar-color))}
                        " "])]]))))))  
(chart-horizontal-percent-bars  x-fighting-out-of-city
                                20
                                females
                                female-color
                                "black")
Las Vegas12.5%
Spokane8.333%
Glendale8.333%
Victoria4.167%
Venice4.167%
Utrecht4.167%
Temecula4.167%
Sioux Falls4.167%
San Diego4.167%
Salvador4.167%
Port Colborne4.167%
Pleasant Hill4.167%
Niteroi4.167%
Moscow4.167%
Montreal4.167%
Marituba4.167%
Kelowna4.167%
Gaffney4.167%
Colorado Springs4.167%
Cleveland4.167%

Flattening Fighters

xxxxxxxxxx
 
(defn recursive-flatten-map
  [init-map m]
  (if-not (seq m)
    init-map
    (let [[k v] (first m)]
      (if-not (map? v)
        (recur (assoc init-map k v)
               (rest m))
        (recur init-map
               (into (rest m) v))))))
(defn flatten-fighter
  [fighter]
  (as-> fighter f
      (without f "Fights")
      (recursive-flatten-map {} f)
      (let [t (transient {:fighter f})]
        (doseq [x ["Abbreviation"
                   "AverageFightTime"
                   "Current"
                   "DOB"
                   "Description"
                   "FighterID"
                   "CareerStats"
                   "MMAID"
                   "Name"
                   "NickName"
                   "Previous"
                   "WeightClassID"
                   "Type"]
                :let [f (:fighter t)]]
          (assoc! t :fighter (without f x)))
        (:fighter t))))
(-> females rand-nth flatten-fighter pprint)
{"Age" "30",
 "Country" "USA",
 "StrikingDefense" "57.73",
 "NoContests" "0",
 "TakedownDefense" "60.00",
 "KDAverage" "0.0000",
 "TakedownAverage" "2.7493",
 "Weight" "135",
 "Wins" "9",
 "Draws" "0",
 "Reach" "66.0",
 "Height" "66",
 "TakedownAccuracy" "50.00",
 "SLpM" "3.6334",
 "SubmissionsAverage" "0.3235",
 "City" "Lafayette",
 "State" "Louisiana",
 "LastName" "Carmouche",
 "Losses" "5",
 "AverageFightTime_Seconds" "696",
 "Stance" "Orthodox",
 "FirstName" "Liz",
 "StrikingAccuracy" "55.61",
 "SApM" "2.9757"}
nil

Chart Heaven

Scroll beyond the code if you just want the chart

xxxxxxxxxx
(extend-type clojure.lang.LazyTransformer
  r/Renderable
  (render [self]
    {:type :list-like
     :open "<span class='clj-lazy-seq'>(</span>"
     :close "<span class='clj-lazy-seq'>)</span>"
     :separator " "
     :items (map r/render self)
     :value (pr-str self)}))
(defn chart-horizontal-fighters
  [limit
    fighters
   title-prefix
   data-color
   bar-color]
  (let [data (map flatten-fighter fighters)
        ks (-> data rand-nth keys)
        xform (fn [x] (map #(get % x)))]
    (sequence (comp
               (map #(vector (name %) (xform %)))
               (map (fn [[title xform]]
                      (lazy-seq
                       [(->html [:h1 (str title-prefix " " title)])
                        (chart-horizontal-percent-bars xform
                                                       limit
                                                       data
                                                       data-color
                                                       bar-color)]))))
               (sort ks))))
(def female-horizontal-charts
  (chart-horizontal-fighters 5 females "Female Top 5" female-color "black"))
(def male-horizontal-charts 
  (chart-horizontal-fighters 5 males "Male Top 5" male-color "black"))
(def horizontal-charts
  (chart-horizontal-fighters 5 (stats) "All Fighters Top 5" "orange" "black"))
horizontal-charts
((

All Fighters Top 5 Age

3021.509%
3220.755%
3120.377%
2718.868%
2818.491%
)
(

All Fighters Top 5 AverageFightTime_Seconds

90079.13%
No Record :(9.565%
6094.348%
7413.478%
7133.478%
)
(

All Fighters Top 5 City

Rio de Janeiro39.024%
Sao Paulo19.512%
San Diego14.634%
Salvador14.634%
Makhachkala12.195%
)
(

All Fighters Top 5 Country

USA62.222%
Brazil22.889%
Canada6.222%
United Kingdom4.667%
Japan4.0%
)
(

All Fighters Top 5 Draws

083.944%
113.264%
22.094%
70.349%
30.349%
)
(

All Fighters Top 5 FirstName

Chris24.39%
Mike21.951%
Matt19.512%
Josh17.073%
Anthony17.073%
)
(

All Fighters Top 5 Height

6921.549%
7221.212%
7119.529%
7018.855%
6818.855%
)
(

All Fighters Top 5 KDAverage

0.000091.971%
No Record :(4.015%
1.00002.19%
0.33331.095%
12.67610.73%
)
(

All Fighters Top 5 LastName

Silva36.364%
Johnson22.727%
Santos18.182%
Miller13.636%
de Lima9.091%
)
(

All Fighters Top 5 Losses

223.662%
321.972%
121.972%
416.338%
516.056%
)
(

All Fighters Top 5 NoContests

084.495%
114.286%
21.22%
)
(

All Fighters Top 5 Reach

74.021.739%
70.020.435%
71.019.565%
73.019.13%
72.019.13%
)
(

All Fighters Top 5 SApM

No Record :(42.308%
0.000019.231%
2.733315.385%
3.133311.538%
1.266711.538%
)
(

All Fighters Top 5 SLpM

No Record :(37.931%
0.000020.69%
2.733313.793%
1.800013.793%
1.400013.793%
)
(

All Fighters Top 5 Stance

Orthodox74.39%
Southpaw17.422%
No Record :(5.749%
Switch2.265%
Open Stance0.174%
)
(

All Fighters Top 5 State

No Record :(50.691%
California25.346%
England8.756%
Texas7.834%
New York7.373%
)
(

All Fighters Top 5 StrikingAccuracy

No Record :(48.148%
50.0014.815%
0.0014.815%
42.6811.111%
40.0011.111%
)
(

All Fighters Top 5 StrikingDefense

No Record :(42.857%
66.6717.857%
33.3314.286%
100.0014.286%
71.7110.714%
)
(

All Fighters Top 5 SubmissionsAverage

0.000086.957%
No Record :(4.783%
0.50004.348%
1.00002.174%
2.00001.739%
)
(

All Fighters Top 5 TakedownAccuracy

No Record :(31.863%
0.0025.0%
50.0015.196%
33.3315.196%
100.0012.745%
)
(

All Fighters Top 5 TakedownAverage

0.000070.0%
2.00009.333%
1.00008.667%
No Record :(7.333%
3.00004.667%
)
(

All Fighters Top 5 TakedownDefense

No Record :(30.286%
100.0024.571%
0.0017.714%
66.6714.286%
50.0013.143%
)
(

All Fighters Top 5 Weight

15525.734%
17023.476%
13520.316%
18515.35%
14515.124%
)
(

All Fighters Top 5 Wins

821.649%
1221.134%
1120.103%
919.588%
1417.526%
)
)
xxxxxxxxxx
 
male-horizontal-charts
((

Male Top 5 Age

3021.912%
3220.717%
3119.92%
2719.522%
2817.928%
)
(

Male Top 5 AverageFightTime_Seconds

90078.846%
No Record :(8.654%
6094.808%
7413.846%
7133.846%
)
(

Male Top 5 City

Rio de Janeiro40.0%
Sao Paulo20.0%
San Diego15.0%
Salvador12.5%
Makhachkala12.5%
)
(

Male Top 5 Country

USA62.028%
Brazil23.349%
Canada5.425%
United Kingdom4.953%
Japan4.245%
)
(

Male Top 5 Draws

083.241%
113.812%
22.21%
70.368%
30.368%
)
(

Male Top 5 FirstName

Chris24.39%
Mike21.951%
Matt19.512%
Josh17.073%
Anthony17.073%
)
(

Male Top 5 Height

7221.575%
6920.89%
7119.521%
7019.178%
6818.836%
)
(

Male Top 5 KDAverage

0.000091.968%
No Record :(3.614%
1.00002.41%
0.33331.205%
12.67610.803%
)
(

Male Top 5 LastName

Silva36.364%
Johnson22.727%
Santos18.182%
Miller13.636%
de Lima9.091%
)
(

Male Top 5 Losses

223.952%
321.557%
121.557%
416.766%
516.168%
)
(

Male Top 5 NoContests

084.375%
114.338%
21.287%
)
(

Male Top 5 Reach

74.021.681%
71.019.912%
70.019.912%
72.019.469%
73.019.027%
)
(

Male Top 5 SApM

No Record :(37.5%
0.000020.833%
2.733316.667%
3.133312.5%
1.266712.5%
)
(

Male Top 5 SLpM

No Record :(33.333%
0.000022.222%
2.733314.815%
1.800014.815%
1.400014.815%
)
(

Male Top 5 Stance

Orthodox74.449%
Southpaw18.382%
No Record :(4.596%
Switch2.39%
Open Stance0.184%
)
(

Male Top 5 State

No Record :(50.952%
California24.762%
England9.048%
Texas7.619%
New York7.619%
)
(

Male Top 5 StrikingAccuracy

No Record :(44.0%
50.0016.0%
0.0016.0%
42.6812.0%
40.0012.0%
)
(

Male Top 5 StrikingDefense

No Record :(38.462%
66.6719.231%
33.3315.385%
100.0015.385%
71.7111.538%
)
(

Male Top 5 SubmissionsAverage

0.000086.73%
0.50004.739%
No Record :(4.265%
1.00002.37%
2.00001.896%
)
(

Male Top 5 TakedownAccuracy

No Record :(31.383%
0.0024.468%
33.3316.489%
50.0014.894%
66.6712.766%
)
(

Male Top 5 TakedownAverage

0.000070.588%
2.00009.559%
1.00008.824%
No Record :(6.618%
3.00004.412%
)
(

Male Top 5 TakedownDefense

No Record :(29.814%
100.0024.845%
0.0016.149%
66.6715.528%
33.3313.665%
)
(

Male Top 5 Weight

15527.603%
17025.182%
18516.465%
14516.223%
13514.528%
)
(

Male Top 5 Wins

1221.622%
821.081%
1120.541%
918.919%
1417.838%
)
)
xxxxxxxxxx
female-horizontal-charts
((

Female Top 5 Age

3125.0%
2825.0%
3218.75%
2618.75%
3412.5%
)
(

Female Top 5 AverageFightTime_Seconds

90060.0%
29913.333%
No Record :(13.333%
8206.667%
8106.667%
)
(

Female Top 5 City

Spokane28.571%
No Record :(28.571%
Winona14.286%
Wilmington14.286%
Whitesburg14.286%
)
(

Female Top 5 Country

USA58.621%
Canada17.241%
Brazil13.793%
No Record :(6.897%
Russia3.448%
)
(

Female Top 5 Draws

096.667%
13.333%
)
(

Female Top 5 FirstName

Jessica33.333%
Sarah22.222%
Alexis22.222%
Valerie11.111%
Shayna11.111%
)
(

Female Top 5 Height

6637.5%
6729.167%
6516.667%
6912.5%
734.167%
)
(

Female Top 5 KDAverage

0.000082.143%
No Record :(7.143%
0.92693.571%
0.86213.571%
0.85773.571%
)
(

Female Top 5 LastName

de Randamie20.0%
Zingano20.0%
Tate20.0%
Smith20.0%
Rousey20.0%
)
(

Female Top 5 Losses

325.0%
125.0%
020.833%
216.667%
512.5%
)
(

Female Top 5 NoContests

086.667%
113.333%
)
(

Female Top 5 Reach

66.043.75%
No Record :(18.75%
70.012.5%
68.512.5%
68.012.5%
)
(

Female Top 5 SApM

No Record :(33.333%
8.200016.667%
6.622116.667%
6.294916.667%
5.416216.667%
)
(

Female Top 5 SLpM

No Record :(33.333%
7.099316.667%
6.933316.667%
6.919516.667%
6.622116.667%
)
(

Female Top 5 Stance

Orthodox73.333%
No Record :(26.667%
)
(

Female Top 5 State

Washington23.077%
California23.077%
No Record :(23.077%
Ontario15.385%
British Columbia15.385%
)
(

Female Top 5 StrikingAccuracy

No Record :(33.333%
83.3316.667%
80.0016.667%
63.9716.667%
57.8316.667%
)
(

Female Top 5 StrikingDefense

No Record :(33.333%
75.1916.667%
69.5316.667%
69.3216.667%
66.4716.667%
)
(

Female Top 5 SubmissionsAverage

0.000077.273%
No Record :(9.091%
5.72844.545%
2.05134.545%
1.50004.545%
)
(

Female Top 5 TakedownAccuracy

No Record :(28.571%
0.0023.81%
100.0019.048%
50.0014.286%
40.0014.286%
)
(

Female Top 5 TakedownAverage

0.000064.286%
No Record :(14.286%
8.27447.143%
6.33947.143%
6.02017.143%
)
(

Female Top 5 TakedownDefense

0.0026.316%
No Record :(26.316%
60.0015.789%
50.0015.789%
100.0015.789%
)
(

Female Top 5 Weight

135100.0%
)
(

Female Top 5 Wins

426.667%
920.0%
820.0%
1020.0%
713.333%
)
)
xxxxxxxxxx