I obviously love Clojure’s prefix notation, but there are times when it is more concise, and familiar, to represent mathematical formulae using infix notation, so I have integrated the infix portion of Jeffrey Bester’s Clojure math library into Incanter. There is now a formula macro called $=
for evaluating infix mathematical formulas in incanter.core.
Here’s an example,
user> (use 'incanter.core) nil user> ($= 7 + 8 - 2 * 6 / 2) 9
Note that there must be spaces between values and operators.
Vectors can be used instead of scalars. For instance, to add 5 to each element of the vector [1 2 3],
user> ($= [1 2 3] + 5) (6 7 8)
or perform element-by-element arithmetic on vectors and matrices.
user> ($= [1 2 3] + [4 5 6]) (5 7 9) user> ($= [1 2 3] * [1 2 3]) (1 4 9) user> ($= [1 2 3] / [1 2 3]) (1 1 1) user> ($= (matrix [[1 2] [4 5]]) + 6) [ 7.0000 8.0000 10.0000 11.0000] user> ($= (trans [[1 2] [4 5]]) + 6) [7.0000 10.0000 8.0000 11.0000]
Examples of exponents using the ** function.
user> ($= 8 ** 3) 512.0 user> ($= 8 ** 1/2) 2.8284271247461903 user> ($= 2 ** -2) 0.25 user> ($= [1 2 3] ** 2) (1.0 4.0 9.0)
Parens can be used for grouping,
user> ($= 10 + 20 * (4 - 5) / 6) 20/3 user> ($= (10 + 20) * 4 - 5 / 6) 715/6 user> ($= 10 + 20 * (4 - 5 / 6)) 220/3
including arbitrarily nested groupings.
user> ($= ((((5 + 4) * 5)))) 45
Of course, variables can be used within the formula macro,
user> (let [x 10 y -5] ($= x + y / -10)) 21/2
and mathematical functions like sin, cos, tan, sq, sqrt, etc. can be used with standard Clojure prefix notation.
user> ($= (sqrt 5) * 5 + 3 * 3) 20.18033988749895 user> ($= sq [1 2 3] + [1 2 3]) (2 6 12) user> ($= sin 2 * Math/PI * 2) 5.713284232087328 user> ($= (cos 0) * 10) 10.0 user> ($= (tan 2) * Math/PI * 10) -68.64505182223235 user> ($= (asin 1/2) * 10) 5.23598775598299 user> ($= (acos 1/2) * 10) 10.47197551196598 user> ($= (atan 1/2) * 10) 4.636476090008061
Functions can also be applied to vectors,
user> ($= [1 2 3] / (sq [1 2 3]) + [5 6 7]) (6 13/2 22/3) user> ($= [1 2 3] + (sin [4 5 6])) (0.2431975046920718 1.0410757253368614 2.720584501801074)
Boolean tests are also supported.
user> ($= 3 > (5 * 2/7)) true user> ($= 3 <= (5 * 2/7)) false user> ($= 3 != (5 * 2/7)) true user> ($= 3 == (5 * 2/7)) false user> ($= 3 != 8 && 6 < 12) true user> ($= 3 != 8 || 6 > 12) true
Matrix multiplication uses the <*> function (equivalent to R’s %*% operator).
user> ($= [1 2 3] <*> (trans [1 2 3])) [1.0000 2.0000 3.0000 2.0000 4.0000 6.0000 3.0000 6.0000 9.0000] user> ($= (trans [[1 2] [4 5]]) <*> (matrix [[1 2] [4 5]])) [17.0000 22.0000 22.0000 29.0000] user> ($= (trans [1 2 3 4]) <*> [1 2 3 4]) 30.0 user> ($= [1 2 3 4] <*> (trans [1 2 3 4])) [1.0000 2.0000 3.0000 4.0000 2.0000 4.0000 6.0000 8.0000 3.0000 6.0000 9.0000 12.0000 4.0000 8.0000 12.0000 16.0000]
The Kronecker product uses the <x> function (equivalent to R’s %x% operator).
user> ($= [1 2 3] <x> [1 2 3]) [1.0000 2.0000 3.0000 2.0000 4.0000 6.0000 3.0000 6.0000 9.0000] user> ($= (matrix [[1 2] [3 4] [5 6]]) <x> 4) [ 4.0000 8.0000 12.0000 16.0000 20.0000 24.0000] user> ($= (matrix [[1 2] [3 4] [5 6]]) <x> (matrix [[1 2] [3 4]])) [ 1.0000 2.0000 2.0000 4.0000 3.0000 4.0000 6.0000 8.0000 3.0000 6.0000 4.0000 8.0000 9.0000 12.0000 12.0000 16.0000 5.0000 10.0000 6.0000 12.0000 15.0000 20.0000 18.0000 24.0000] user> ($= [1 2 3 4] <x> 4) [ 4.0000 8.0000 12.0000 16.0000] user> ($= [1 2 3 4] <x> (trans [1 2 3 4])) [1.0000 2.0000 3.0000 4.0000 2.0000 4.0000 6.0000 8.0000 3.0000 6.0000 9.0000 12.0000 4.0000 8.0000 12.0000 16.0000]
Here’s an example using the formula macro in a function-plot of Image may be NSFW.
Clik here to view., (I will use the incanter.latex/add-latex function to add a LaTeX annotation to the chart).
(use '(incanter core charts latex)) (doto (function-plot (fn [x] ($= x ** 3 - 5 * x ** 2 + 3 * x + 5)) -10 10) (add-latex 0 250 "x^3 - 5x^2 + 3x +5") view)
Image may be NSFW.
Clik here to view.
The next example will use the car-speed-to-breaking-distance sample data (the :cars dataset). I’ll partition the data, first selecting rows where the ratio of dist/speed is less than 2, then selecting rows where the ratio is greater than 2, and finally adding a line showing this threshold.
First, use the with-data
macro, passing it the :cars sample data. Then apply the $where
function in order to filter the data; pass it a $fn
, which is a bit of syntax sugar around Clojure’s fn
function that does map destructuring for functions that operate on dataset rows. For instance
($fn [x y] (…))
becomes
(fn [{:keys x y}] (…))
The parameter names specified in $fn must correspond to the column names of the dataset.
(use '(incanter core datasets charts)) (with-data (get-dataset :cars) (doto (scatter-plot :speed :dist :data ($where ($fn [speed dist] ($= dist / speed < 2)))) (add-points :speed :dist :data ($where ($fn [speed dist] ($= dist / speed >= 2)))) (add-lines ($ :speed) ($= 2 * ($ :speed))) view))
Image may be NSFW.
Clik here to view.
The complete code for this post is available here.
Image may be NSFW.
Clik here to view.
Clik here to view.
