Infix mathematical notation in Incanter

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 x^3 - 5x^2 + 3x +5 , (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)

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))

The complete code for this post is available here.

2 responses to “Infix mathematical notation in Incanter

  1. Very nicely done. I like the infix-math.

  2. This is awesome! Very well done on the infix math and vectorized operators. Clojure is quickly becoming my favorite language!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s