|
| 1 | +(ns selling.folds |
| 2 | + "Functions for combining states. |
| 3 | + |
| 4 | + Folds usually come in two variants: a friendly version like sum, and a strict |
| 5 | + version like sum*. Strict variants will throw when one of their events is |
| 6 | + nil, missing a metric, or otherwise invalid. In typical use, however, you |
| 7 | + won't *have* all the necessary information to pass on an event. Friendly |
| 8 | + variants will do their best to ignore these error conditions where sensible, |
| 9 | + returning partially complete events or nil instead of throwing. |
| 10 | + Called with an empty list, folds which would return a single event return |
| 11 | + nil." |
| 12 | + (:use [selling.common]) |
| 13 | + (:refer-clojure :exclude [count])) |
| 14 | + |
| 15 | +(defn sorted-sample-extract |
| 16 | + "Returns the events in seqable s, sorted and taken at each point p of points, |
| 17 | + where p ranges from 0 (smallest metric) to 1 (largest metric). 0.5 is the |
| 18 | + median event, 0.95 is the 95th' percentile event, and so forth. Ignores |
| 19 | + events without a metric." |
| 20 | + [s points] |
| 21 | + (let [sorted (sort-by :metric (filter :metric s))] |
| 22 | + (if (empty? sorted) |
| 23 | + '() |
| 24 | + (let [n (clojure.core/count sorted) |
| 25 | + extract (fn [point] |
| 26 | + (let [idx (min (dec n) (int (math/floor (* n point))))] |
| 27 | + (nth sorted idx)))] |
| 28 | + (map extract points))))) |
| 29 | + |
| 30 | +(defn sorted-sample |
| 31 | + "Sample a sequence of events at points. Returns events with service remapped |
| 32 | + to (str service \" \" point). For instance, (sorted-sample events [0 1]) |
| 33 | + returns a 2-element seq of the smallest event and the biggest event, by |
| 34 | + metric. The first has a service which ends in \" 0\" and the second one ends |
| 35 | + in \" 1\". If the points is a map, eg (sorted-sample events {0 \".min\" 1 |
| 36 | + \".max\"}, the the values will be appened to the service name directly. |
| 37 | + Useful for extracting histograms and percentiles. |
| 38 | + |
| 39 | + When s is empty, returns an empty list." |
| 40 | + [s points] |
| 41 | + (let [[points pnames] (if (vector? points) |
| 42 | + [points (map #(str " " %) points)] |
| 43 | + (apply map vector points))] |
| 44 | + (map (fn [pname event] |
| 45 | + (assoc event :service |
| 46 | + (str (:service event) pname))) |
| 47 | + pnames |
| 48 | + (sorted-sample-extract s points)))) |
| 49 | + |
| 50 | +(defn non-nil-metrics |
| 51 | + "Given a sequence of events, returns a compact sequence of their |
| 52 | + metrics--that is, omits any events which are nil, or have nil metrics." |
| 53 | + [events] |
| 54 | + (keep (fn [event] |
| 55 | + (when-not (nil? event) |
| 56 | + (:metric event))) |
| 57 | + events)) |
| 58 | + |
| 59 | +(defn fold* |
| 60 | + "Fold with a reduction function over metrics. Throws if any event or metric |
| 61 | + is nil." |
| 62 | + [f events] |
| 63 | + (assoc (first events) :metric (reduce f (map :metric events)))) |
| 64 | + |
| 65 | +(defn fold |
| 66 | + "Fold with a reduction function over metrics. Ignores nil events and events |
| 67 | + with nil metrics. |
| 68 | + |
| 69 | + If there are *no* non-nil events, returns nil." |
| 70 | + [f events] |
| 71 | + (when-let [e (some identity events)] |
| 72 | + (assoc e :metric (reduce f (non-nil-metrics events))))) |
| 73 | + |
| 74 | +(defn fold-all |
| 75 | + "Fold with a reduction function over metrics. |
| 76 | + |
| 77 | + If the first event has a nil :metric, or if any remaining event is nil, or |
| 78 | + has a nil metric, returns the first event, but with :metric nil and a |
| 79 | + :description of the error. |
| 80 | + |
| 81 | + If the first event is nil, returns nil." |
| 82 | + [f events] |
| 83 | + (when-let [e (first events)] |
| 84 | + (try |
| 85 | + (assoc e :metric (reduce f (map :metric events))) |
| 86 | + (catch _ ex |
| 87 | + (merge e |
| 88 | + {:metric nil |
| 89 | + :description "An event or metric was nil."}))))) |
| 90 | + |
| 91 | +(defn sum* |
| 92 | + "Adds events together. Sums metrics, merges into first of events." |
| 93 | + [events] |
| 94 | + (fold* + events)) |
| 95 | + |
| 96 | +(defn sum |
| 97 | + "Adds events together. Sums metrics, merges into first event with a metric, |
| 98 | + ignores nil events and nil metrics." |
| 99 | + [events] |
| 100 | + (fold + events)) |
| 101 | + |
| 102 | +(defn product* |
| 103 | + "Multiplies events. Returns the first event, with its metric multiplied by |
| 104 | + the metrics of all other events." |
| 105 | + [events] |
| 106 | + (fold* * events)) |
| 107 | + |
| 108 | +(defn product |
| 109 | + "Multiplies events. Returns the first event with a metric, with its metric |
| 110 | + being the product of all events with metrics." |
| 111 | + [events] |
| 112 | + (fold * events)) |
| 113 | + |
| 114 | +(defn difference* |
| 115 | + "Subtracts events. Returns the first event, with its metric reduced by the |
| 116 | + metrics of all subsequent events." |
| 117 | + [events] |
| 118 | + (fold* - events)) |
| 119 | + |
| 120 | +(defn difference |
| 121 | + "Subtracts events. Returns the first event, with its metric reduced by the |
| 122 | + metrics of all subsequent events with metrics. Returns nil if the first event |
| 123 | + is nil, or its metric is nil." |
| 124 | + [events] |
| 125 | + (fold-all - events)) |
| 126 | + |
| 127 | +(defn quotient* |
| 128 | + "Divides events. Returns the first event, with its metric divided by the |
| 129 | + product of the metrics of all subsequent events. Like quotient, but throws |
| 130 | + when any metric is nil or a divisor is zero." |
| 131 | + [events] |
| 132 | + (fold* / events)) |
| 133 | + |
| 134 | +(defn quotient |
| 135 | + "Divides events. Returns the first event, with its metric divided by the |
| 136 | + product of the metrics of all subsequent events." |
| 137 | + [events] |
| 138 | + (when-let [event (first events)] |
| 139 | + (try |
| 140 | + (fold-all / events) |
| 141 | + (catch _ e |
| 142 | + (merge event |
| 143 | + {:metric nil |
| 144 | + :description "Can't divide by zero"}))))) |
| 145 | + |
| 146 | +(defn quotient-sloppy |
| 147 | + "Like quotient, but considers 0/0 = 0. Useful for relative rates, when you |
| 148 | + want the ratio of two constant values to be zero." |
| 149 | + [events] |
| 150 | + (if (and (first events) |
| 151 | + (some zero? (map :metric events))) |
| 152 | + (assoc (first events) :metric 0) |
| 153 | + (quotient events))) |
| 154 | + |
| 155 | +(defn mean |
| 156 | + "Averages events together. Mean metric, merged into first of events. Ignores |
| 157 | + nil events and nil metrics." |
| 158 | + [events] |
| 159 | + (when-let [e (some identity events)] |
| 160 | + (let [metrics (non-nil-metrics events)] |
| 161 | + (when (seq metrics) |
| 162 | + (assoc e :metric (/ (reduce + metrics) |
| 163 | + (clojure.core/count metrics))))))) |
| 164 | + |
| 165 | +(defn adjust-occurence |
| 166 | + [state {:keys [metric] :as event}] |
| 167 | + (update-in state [metric] conj event)) |
| 168 | + |
| 169 | +(defn modes |
| 170 | + "Keep track of repeating metrics. Yields a sequence of events with the highest |
| 171 | + occuring metrics." |
| 172 | + [events] |
| 173 | + (let [occurs (reduce adjust-occurence {} events) |
| 174 | + freqs (for [o (vals occurs)] [(clojure.core/count o) (first o)])] |
| 175 | + (->> (sort-by first > freqs) |
| 176 | + (partition-by first) |
| 177 | + (first) |
| 178 | + (map second)))) |
| 179 | + |
| 180 | +(defn mode |
| 181 | + "Yield any mode returned by modes." |
| 182 | + [events] |
| 183 | + (first (modes events))) |
| 184 | + |
| 185 | +(defn median |
| 186 | + "Returns the median event from events, by metric." |
| 187 | + [events] |
| 188 | + (first (sorted-sample-extract events [0.5]))) |
| 189 | + |
| 190 | +(defn extremum |
| 191 | + "Returns an extreme event, by a comparison function over the metric." |
| 192 | + [comparison events] |
| 193 | + (reduce (fn [smallest event] |
| 194 | + (cond (nil? (:metric event)) smallest |
| 195 | + (nil? smallest) event |
| 196 | + (comparison (:metric event) (:metric smallest)) event |
| 197 | + :else smallest)) |
| 198 | + nil |
| 199 | + events)) |
| 200 | + |
| 201 | +(defn minimum |
| 202 | + "Returns the minimum event, by metric." |
| 203 | + [events] |
| 204 | + (extremum <= events)) |
| 205 | + |
| 206 | +(defn maximum |
| 207 | + "Returns the maximum event, by metric." |
| 208 | + [events] |
| 209 | + (extremum >= events)) |
| 210 | + |
| 211 | +(defn std-dev |
| 212 | + "calculates standard deviation across a seq of events" |
| 213 | + [events] |
| 214 | + (when-let [e (some identity events)] |
| 215 | + (let [ |
| 216 | + samples (non-nil-metrics events) |
| 217 | + n (clojure.core/count samples) |
| 218 | + mean (/ (reduce + samples) n) |
| 219 | + intermediate (map #(math/pow (- %1 mean) 2) samples)] |
| 220 | + (assoc e :metric (math/sqrt (/ (reduce + intermediate) n)))))) |
| 221 | + |
| 222 | +(defn count |
| 223 | + "Returns the number of events." |
| 224 | + [events] |
| 225 | + (let [events (remove nil? events)] |
| 226 | + (if-let [e (first events)] |
| 227 | + (assoc e :metric (clojure.core/count events)) |
| 228 | + (event {:metric 0})))) |
0 commit comments