Skip to content

Tutorial: How to Plot in Owl?

Liang Wang edited this page Jun 21, 2017 · 48 revisions

Owl is an OCaml numerical library. Besides its extensive supports to matrix operations, it also has a flexible plotting module. Owl's Plot module is designed to help you in making fairly complicated plots with minimal coding efforts. It is built atop of Plplot but hides its complexity from users.

The module is cross-platform since Plplot calls the underlying graphics device driver to plot. However, based on my experience, the Cairo Package provides the best quality and most accurate figure so I recommend installing Cairo.

The examples in this tutorial are generated by using Cairo PNG Driver.

Create & Configure

Simply put, there are two ways of plotting: 1) with a handle; 2) without a handle. You can imagine a handle is just a "pointer" to a figure. The first option is for lazy people who do not want to fine tune their figures but want to have a look at their results quickly. Most plotting functions in Owl can be called without an explicit handle passed in. E.g., the following code creates a simple line plot.

let f x = Maths.sin x /. x in
Plot.plot_fun f 1. 15.;;

However, in most cases, you do want to have a full control over the figure you are creating and do want to configure it a bit especially if you don't like the default red on black theme. Then, here is the standard way of creating a plot using Plot.create function. Here is its type definition in Owl_plot.mli

val create : ?m:int -> ?n:int -> string -> handle

The first two parameters m and n are used for creating subplot, I will talk about subplot later. Now we only use the third parameter which defines the output file. Let's extend the previous example.

let f x = Maths.sin x /. x in
let h = Plot.create "plot_003.png" in
Plot.set_foreground_color h 0 0 0;
Plot.set_background_color h 255 255 255;
Plot.set_title h "Function: f(x) = sine x / x";
Plot.set_xlabel h "x-axis";
Plot.set_ylabel h "y-axis";
Plot.set_font_size h 8.;
Plot.set_pen_size h 3.;
Plot.plot_fun ~h f 1. 15.;
Plot.output h;;

As we can see, we first create a plot handle h and also specified the output file as plot_003.png in create function. Owl will select the suitable output device driver for you based on the file name suffix. Then we call various functions in the Plot module to configure the plot by passing in h. These functions are self-explained, and you can read Owl's documentation for details.

There are two thing worth mentioning here. First, you need to pass h to the plot function, i.e., Plot.plot_fun in this example. Second, the most important step is calling Plot.output h will actually write your figure to a file.

Simple, isn't it? Let's continue to more interesting plots.

Create Subplot

You use the same Plot.create function to start a subplot. E.g., the code below create a 2 x 2 subplot. We leave the output file name as an empty string so Plplot later will prompt a list of device drivers you can choose from, when you call Plot.output.

let h = Plot.create ~m:2 ~n:2 "" in
...

The key concept of subplot is: you need to successively call Plot.subplot to "focus" on different individual subplot to work on them. Continue the previous example, let's look at the code below.

let f p i = match i with
  | 0 -> Stats.Rnd.gaussian ~sigma:0.5 () +. p.(1)
  | _ -> Stats.Rnd.gaussian ~sigma:0.1 () *. p.(0)
in
let y = Stats.gibbs_sampling f [|0.1;0.1|] 5_000 |> Mat.of_arrays in
let h = Plot.create ~m:2 ~n:2 "" in
let _ = Plot.set_background_color h 255 255 255 in
(* focus on the subplot at 0,0 *)
let _ = Plot.subplot h 0 0 in
let _ = Plot.set_title h "Bivariate model" in
let _ = Plot.scatter ~h (Mat.col y 0) (Mat.col y 1) in
(* focus on the subplot at 0,1 *)
let _ = Plot.subplot h 0 1 in
let _ = Plot.set_title h "Distribution of y" in
let _ = Plot.set_xlabel h "y" in
let _ = Plot.set_ylabel h "Frequency" in
let _ = Plot.histogram ~h ~bin:50 (Mat.col y 1) in
(* focus on the subplot at 1,0 *)
let _ = Plot.subplot h 1 0 in
let _ = Plot.set_title h "Distribution of x" in
let _ = Plot.set_ylabel h "Frequency" in
let _ = Plot.histogram ~h ~bin:50 (Mat.col y 0) in
(* focus on the subplot at 1,1 *)
let _ = Plot.subplot h 1 1 in
let _ = Plot.set_foreground_color h 0 50 255 in
let _ = Plot.set_title h "Sine function" in
let _ = Plot.plot_fun ~h ~line_style:2 Maths.sin 0. 28. in
let _ = Plot.autocorr ~h (Mat.sequential 1 28) in
(* output your final plot *)
Plot.output h;;

The code will generate the following plot. You can control the configuration of each individual subplot once you have "focused" on it by calling Plot.subplot. Besides teh figure handle h, Plot.subplot uses the two-dimensional index you pass in to locate the subplot.

Plot module automatically calculates the suitable page size for your subplot. If you are not happy with the calculated size, you can also specify the page size by calling Plot.set_page_size function.

Subplot is quite straightforward, right?

Multiple (Lines)

You can certainly plot multiple lines (or other types of plots) on the same page. Once you call Plot.output, the plot will be sealed and written into the final file. Here is one example with both sine and cosine lines in one plot.

let h = Plot.create "" in
let _ = Plot.plot_fun ~h ~color:(0,0,255) ~marker:"#[0x2299]" ~marker_size:8. Maths.sin 0. 9. in
let _ = Plot.plot_fun ~h ~color:(255,0,0) ~marker:"#[0x0394]" ~marker_size:8. Maths.cos 0. 9. in
let _ = Plot.legend_on h [|"Sine function"; "Cosine function"|] in
Plot.output h;;

Here is another example which has both histogram and line plot in one figure.

(* generate data *)
let f p = Stats.Pdf.gaussian p.(0) 0.5 in
let g x = Stats.Pdf.gaussian x 0.5 *. 4000. in
let y = Stats.metropolis_hastings f [|0.1|] 100_000 |>  Mat.of_arrays in
(* plot multiple data sets *)
let h = Plot.create "" in
let _ = Plot.set_background_color h 255 255 255 in
let _ = Plot.histogram ~h ~color:(255,0,50) ~bin:100 y in
let _ = Plot.plot_fun ~h ~color:(0,0,255) ~line_width:2. g (-2.) 2. in
let _ = Plot.legend_on h [|"data"; "model"|] in
Plot.output h;;

So as long as you "hold" the plot without calling Plot.output, you can plot many data sets in one figure.

Legend

Legend can be turned on and off by calling Plot.legend_on and Plot.legend_off respectively. When you call Plot.legend_on, you also need to provide an array of legend names and the position of legend. There are eight default positions in Plot.

type legend_position =
  North | South | West | East | NorthWest | NorthEast | SouthWest | SouthEast

Despite of its messy looking, the following example shows how to use legend in Owl's plot module.

(* generate data *)
let x = Mat.(uniform 1 20 *$ 10.) in
let y = Mat.(uniform 1 20) in
let z = Mat.gaussian 1 20 in
(* plot multiple data sets *)
let h = Plot.create "" in
let _ = Plot.plot_fun ~h ~line_style:1 ~color:(0,0,255) ~marker:"*" Maths.sin 1. 8. in
let _ = Plot.plot_fun ~h ~line_style:2 ~color:(0,255,0) ~marker:"+"  Maths.cos 1. 8. in
let _ = Plot.scatter ~h x y in
let _ = Plot.stem ~h x z in
let u = Mat.(abs(gaussian 1 10 *$ 0.3)) in
let _ = Plot.bar ~h ~color:(255,255,0) ~fill_pattern:3 u in
let v = Mat.(neg u *$ 0.3) in
let u = Mat.sequential 1 10 in
let _ = Plot.area ~h ~color:(0,255,0) ~fill_pattern:4 u v in
(* set up legend *)
let _ = Plot.(legend_on h ~position:NorthEast [|"test 1"; "test 2"; "scatter"; "stem"; "bar"; "area"|]) in
Plot.output h;;

Drawing

You can draw lines and rectangles in the plot. The first example actually shows different line styles in Plot by drawing multiple lines.

let h = Plot.create "" in
let _ = Plot.set_background_color h 255 255 255 in
let _ = Plot.set_pen_size h 2. in
let _ = Plot.draw_line ~h ~line_style:1 1. 1. 9. 1. in
let _ = Plot.draw_line ~h ~line_style:2 1. 2. 9. 2. in
let _ = Plot.draw_line ~h ~line_style:3 1. 3. 9. 3. in
let _ = Plot.draw_line ~h ~line_style:4 1. 4. 9. 4. in
let _ = Plot.draw_line ~h ~line_style:5 1. 5. 9. 5. in
let _ = Plot.draw_line ~h ~line_style:6 1. 6. 9. 6. in
let _ = Plot.draw_line ~h ~line_style:7 1. 7. 9. 7. in
let _ = Plot.draw_line ~h ~line_style:8 1. 8. 9. 8. in
let _ = Plot.set_xrange h 0. 10. in
let _ = Plot.set_yrange h 0. 9. in
Plot.output h;;

Similarly, the next example shows the filling patterns in Plot by drawing rectangles.

let h = Plot.create "" in
Array.init 9 (fun i ->
  let x0, y0 = 0.5, float_of_int i +. 1.0 in
  let x1, y1 = 4.5, float_of_int i +. 0.5 in
  Plot.draw_rect ~h ~fill_pattern:i x0 y0 x1 y1;
  Plot.text ~h ~color:(0,255,0) 2.3 (y0-.0.2) ("patter: " ^ (string_of_int i));
);
Plot.output h;;

Various Plots

In the following, I will use several examples to illustrate how to use the basic plotting functions in Owl.

Line Plot

Line plot is the most basic function. You can specify the colour, marker, and line style in the function.

let x = Mat.linspace 0. 2. 100 in
let y0 = Mat.sigmoid x in
let y1 = Mat.(Maths.sin @@ x) in
let h = Plot.create "" in
let _ = Plot.plot ~h ~color:(255,0,0) ~line_style:1 ~marker:"#[0x2299]" ~marker_size:8. x y0 in
let _ = Plot.plot ~h ~color:(0,255,0) ~line_style:2 ~marker:"#[0x0394]" ~marker_size:8. x y1 in
let _ = Plot.(legend_on h ~position:SouthEast [|"sigmoid"; "sine"|]) in
Plot.output h;;

Scatter Plot

For scatter plot, the most important thing is the markers. You can check all the possible marker symbols on this page, they can be passed in as a parameter to Plot.scatter function.

let x = Mat.uniform 1 30 in
let y = Mat.uniform 1 30 in
let h = Plot.create ~m:3 ~n:3 "" in
let _ = Plot.set_background_color h 255 255 255 in
let _ = Plot.subplot h 0 0 in
let _ = Plot.scatter ~h ~marker:"#[0x2295]" ~marker_size:5. x y in
let _ = Plot.subplot h 0 1 in
let _ = Plot.scatter ~h ~marker:"#[0x229a]" ~marker_size:5. x y in
let _ = Plot.subplot h 0 2 in
let _ = Plot.scatter ~h ~marker:"#[0x2206]" ~marker_size:5. x y in
let _ = Plot.subplot h 1 0 in
let _ = Plot.scatter ~h ~marker:"#[0x229e]" ~marker_size:5. x y in
let _ = Plot.subplot h 1 1 in
let _ = Plot.scatter ~h ~marker:"#[0x2217]" ~marker_size:5. x y in
let _ = Plot.subplot h 1 2 in
let _ = Plot.scatter ~h ~marker:"#[0x2296]" ~marker_size:5. x y in
let _ = Plot.subplot h 2 0 in
let _ = Plot.scatter ~h ~marker:"#[0x2666]" ~marker_size:5. x y in
let _ = Plot.subplot h 2 1 in
let _ = Plot.scatter ~h ~marker:"#[0x22a1]" ~marker_size:5. x y in
let _ = Plot.subplot h 2 2 in
let _ = Plot.scatter ~h ~marker:"#[0x22b9]" ~marker_size:5. x y in
Plot.output h;;

Stairs Plot

Instead of drawing a straight line between two points, stairs plot first moves along the x-axis then y-axis while plotting the data. The following example is self-explained.

let x = Mat.linspace 0. 6.5 20 in
let y = Mat.(Maths.sin @@ x) in
let h = Plot.create ~m:1 ~n:2 "" in
let _ = Plot.set_background_color h 255 255 255 in
let _ = Plot.subplot h 0 0 in
let _ = Plot.plot_fun ~h Maths.sin 0. 6.5 in
let _ = Plot.stairs ~h ~color:(0,128,255) x y in
let _ = Plot.subplot h 0 1 in
let _ = Plot.plot ~h ~color:(0,0,0) x y in
let _ = Plot.stairs ~h ~color:(0,128,255) x y in
Plot.output h;;

Box Plot

Box plots here generally refer to histogram, bar chart, and Whisker plot. You already learnt how to make a histogram plot. In the following, I will show how to make the other two.

Here is the example for making both bar chart and Whisker box. Just note the input to Plot.boxplot is a row-based matrix, each row represents multiple measurements of one variable, which correspond one box in the plot.

let y1 = Mat.uniform 1 10 in
let y2 = Mat.uniform 10 100 in
let h = Plot.create ~m:1 ~n:2 "" in
let _ = Plot.subplot h 0 0 in
let _ = Plot.bar ~h ~color:(0,153,51) ~fill_pattern:3 y1 in
let _ = Plot.subplot h 0 1 in
let _ = Plot.boxplot ~h ~color:(0,153,51) y2 in
Plot.output h;;

Stem Plot

Stem plot is simple, as the following code shows.

let x = Mat.linspace 0.5 2.5 25 in
let y = Mat.(Stats.Pdf.exponential 0.1 @@ x) in
let h = Plot.create ~m:1 ~n:2 "" in
let _ = Plot.set_background_color h 255 255 255 in
let _ = Plot.subplot h 0 0 in
let _ = Plot.set_foreground_color h 0 0 0 in
let _ = Plot.stem ~h x y in
let _ = Plot.subplot h 0 1 in
let _ = Plot.stem ~h ~marker:"#[0x2295]" ~marker_size:5. ~line_style:1 x y in
Plot.output h;;

Stem plot is often used to show the autocorrelation of a variable, therefore Plot module already includes autocorr for your convenience.

let x = Mat.linspace 0. 8. 30 in
let y0 = Mat.(Maths.sin @@ x) in
let y1 = Mat.uniform 1 30 in
let h = Plot.create ~m:1 ~n:2 "" in
let _ = Plot.subplot h 0 0 in
let _ = Plot.set_title h "Sine" in
let _ = Plot.autocorr ~h y0 in
let _ = Plot.subplot h 0 1 in
let _ = Plot.set_title h "Gaussian" in
let _ = Plot.autocorr ~h y1 in
Plot.output h;;

Obviously, sine function possesses stronger self-similarity than Gaussian noise.

Area Plot

Area plot is similar to line plot but also fills the space between the line and x-axis.

let h = Plot.create ~m:1 ~n:2 "" in
let x = Mat.linspace 0. 8. 100 in
let y = Mat.(Maths.atan @@ x) in
let _ = Plot.subplot h 0 0 in
let _ = Plot.area ~h ~fill_pattern:1 x y in
let x = Mat.linspace 0. (2. *. 3.1416) 100 in
let y = Mat.(Maths.sin @@ x) in
let _ = Plot.subplot h 0 1 in
let _ = Plot.area ~fill_pattern:2 ~h x y in
Plot.output h;;

Histogram & CDF Plot

Given a series of measurements, you can easily plot the histogram and empirical cumulative distribution of the data.

let x = Mat.gaussian 200 1 in
let h = Plot.create ~m:1 ~n:2 "" in
let _ = Plot.subplot h 0 0 in
let _ = Plot.set_title h "histogram" in
let _ = Plot.histogram ~h ~bin:25 x in
let _ = Plot.subplot h 0 1 in
let _ = Plot.set_title h "empirical cdf" in
let _ = Plot.ecdf ~h x in
Plot.output h;;

3D Plot

There are four functions in Plot module related to 3D plot. They are surf, mesh, heatmap, and contour functions. Again, I will illustrate them with examples.

First, let's look at mesh and surf functions.

let x, y = Mat.meshgrid (-2.5) 2.5 (-2.5) 2.5 100 100 in
let z0 = Mat.(sin ((x **$ 2.) + (y **$ 2.))) in
let z1 = Mat.(cos ((x **$ 2.) + (y **$ 2.))) in
let h = Plot.create ~m:2 ~n:2 "" in
let _ = Plot.subplot h 0 0 in
let _ = Plot.surf ~h x y z0 in
let _ = Plot.subplot h 0 1 in
let _ = Plot.mesh ~h x y z0 in
let _ = Plot.subplot h 1 0 in
let _ = Plot.surf ~h x y z1 in
let _ = Plot.subplot h 1 1 in
let _ = Plot.mesh ~h x y z1 in
Plot.output h;;

Here is another similar example with different data set.

let x, y = Mat.meshgrid (-3.) 3. (-3.) 3. 100 100 in
let z = Mat.(
  3. $* ((1. $- x) **$ 2.) * exp (neg (x **$ 2.) - ((y +$ 1.) **$ 2.)) -
  (10. $* (x /$ 5. - (x **$ 3.) - (y **$ 5.)) * (exp (neg (x **$ 2.) - (y **$ 2.)))) -
  ((1./.3.) $* exp (neg ((x +$ 1.) **$ 2.) - (y **$ 2.)))
  ) in
let h = Plot.create ~m:2 ~n:2 "" in
let _ = Plot.subplot h 0 0 in
let _ = Plot.surf ~h x y z in
let _ = Plot.subplot h 0 1 in
let _ = Plot.mesh ~h x y z in
let _ = Plot.subplot h 1 0 in
let _ = Plot.surf ~h ~contour:true x y z in
let _ = Plot.subplot h 1 1 in
let _ = Plot.mesh ~h ~contour:true x y z in
Plot.output h;;

Finally, let's look at how heatmap and contour look like.

let x, y = Mat.meshgrid (-3.) 3. (-3.) 3. 100 100 in
let z = Mat.(
  3. $* ((1. $- x) **$ 2.) * exp (neg (x **$ 2.) - ((y +$ 1.) **$ 2.)) -
  (10. $* (x /$ 5. - (x **$ 3.) - (y **$ 5.)) * (exp (neg (x **$ 2.) - (y **$ 2.)))) -
  ((1./.3.) $* exp (neg ((x +$ 1.) **$ 2.) - (y **$ 2.)))
  ) in
let h = Plot.create ~m:2 ~n:2 "" in
let _ = Plot.subplot h 0 0 in
let _ = Plot.mesh ~h ~contour:true x y z in
let _ = Plot.subplot h 0 1 in
let _ = Plot.heatmap ~h x y z in
let _ = Plot.subplot h 1 0 in
let _ = Plot.mesh ~h x y z in
let _ = Plot.subplot h 1 1 in
let _ = Plot.contour ~h x y z in
Plot.output h;;