Adjective Noun

Using matplotlib in Perl 6 (part 2)

2017-03-05 23:00, Tags: raku perl python matplotlib

This is Part 2 in a series. You can start at the Intro here.

In Part 1 of this series, I managed to use matplotlib in Perl 6 to output a simple graph from the matplotlib tutorial. In this post, I will tackle the first graph in the matplotlib gallery, barh_demo.py.

import matplotlib.pyplot as plt
import numpy as np

plt.rcdefaults()
fig, ax = plt.subplots()

# Example data
people = ('Tom', 'Dick', 'Harry', 'Slim', 'Jim')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
error = np.random.rand(len(people))

ax.barh(y_pos, performance, xerr=error, align='center',
        color='green', ecolor='black')
ax.set_yticks(y_pos)
ax.set_yticklabels(people)
ax.invert_yaxis()  # labels read top-to-bottom
ax.set_xlabel('Performance')
ax.set_title('How fast do you want to go today?')

plt.show()

Again, numpy is being used, but I figured Perl 6 can probably do whatever is needed natively, so I proceeded to convert the imports to Perl parlance (perlance?) and got started.

use Matplotlib;
my $plt = Matplotlib.new;

$plt.rcdefaults();
my ( $fig, $ax ) = $plt.subplots();

As mentioned in the tail end of Part 1, Python likes scalar values, so from here on in I'm going to stick to using mostly scalar variables in my Perl code. I may also provide alternative methods of doing things if I think they are noteworthy or interesting. Now on with the show!

I need to create the example data... people is just your average list of strings. Perl has always had nice quoting construct to create a list of words, so that ones is as simple as

my $people = < Tom Dick Harry Slim Jim >;

Now what's numpy.arange doing? I knew that len(people) was 5, but I didn't know what arange was doing with that 5. It turns out it simply creates an array of values starting at 0, incrementing by one and up to 5, ie. [0, 1, 2, 3, 4]. As seen in Part 1, I can use the handy Sequence operator for that, but creating lists like this is fairly common which is why Perl 6 also provides a convenient shortcut for it:

my $list = (0 ...^ 5); # Result: (0, 1, 2, 3, 4)
my $list = ^5;         # same thing

Of course, I can use a variable instead of a literal integer. As covered in Part 1, lists used in a numerical context are coerced to their number of elements. These features combined mean that my y_pos assignment looks like this.

my $y_pos = ^$people;

For the next two lists of values, performance and error, numpy is being used to generate some random values. Both values reference len(people), and this equates to the number of random values returned: 5. Perl has a fairly standard rand function that - without arguments - returns a random value between 0 and 1. However, I can use the "list repetition" operator (xx) to create a list of random values. Again referring to Part 1, I know that mathematical operations on a numpy array are equivalent to mapping that operation across each element, so I ended up with this.

my $performance = ( rand xx $people ).map( 10 × * + 3 );
my $xerr = rand xx $people;

In those 3 lines of code, The $people list variable is used in a numerical context so it is evaluated to it's number of elements: 5. You could always be more explicit in your code and say $people.elems if you prefer.

You may have noticed I called my list of error values xerr instead of error and there's a good reason. Perl 6 provides a convenient syntax for passing named arguments (kwargs in Python) when a variable has the same name as the named argument.
Oh, and one more thing. Remember that < word quoting > syntax I used before? I can use that when passing named arguments too. Here's a quick illustration using an example function foo() that takes 1 positional parameter, and 1 named parameter.

sub foo( $positional, :$named ) { ... }

# Calling the function
foo( 'bar', :named('baz') );

# Using the word-quoting construct
foo( 'bar', :named<baz> );

# or if I had a $named variable
my $named = 'baz';
foo( 'bar', :$named );

To some, it may seem odd to have so many ways to pass a named argument (and truthfully there's more) but I hope what comes through here is that the syntax tries to consistent in how it's used... But I digress... Less plodding and more plotting!

With the above out of the way, the rest of the code is pretty straight forward. Here's the final code.

use Matplotlib;
my $plt = Matplotlib.new;

$plt.rcdefaults();
my ( $fig, $ax ) = $plt.subplots();

# Example data
my $people = < Tom Dick Harry Slim Jim >;
my $y_pos = ^$people;
my $performance = ( rand xx $people ).map( 10 × * + 3 );
my $xerr = rand xx $people;

$ax.barh(
    $y_pos, $performance,
    :$xerr, :align<center>, :color<green>, :ecolor<black>
);
$ax.set_yticks($y_pos);
$ax.set_yticklabels($people);
$ax.invert_yaxis(); # labels read top-to-bottom
$ax.set_xlabel('Performance');
$ax.set_title('How fast do you want to go today?');

$plt.show();

This graph is based on random data, so the output will vary slightly with each run, but ultimately I get something along the lines of this when I run it.

Well that was mostly painless. The wrapper module is working as expected, and I was able to emulate a basic function of numpy with relative ease. I glanced back to the the Matplotlib gallery... Next in line was the fill_demo. Let's go!