Adjective Noun

Using matplotlib in Perl 6 (part 5)

2017-03-10 23:24, Tags: raku perl python matplotlib

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

All the graphs I've done so far are using the standard Matplotlib style. In some of them, I might have specified some colour here or there, but Matplotlib also provides "style sheets" for changing the overall look and tone of a graph. That's what's happening here.

import matplotlib.pyplot as plt
plt.style.use('bmh')

It looks like style is another sub-package to matplotlib.pyplot, which then calls a method called use. In a similar vein to how I solved my issues with mlab, I'll need to create a new Matplotlib::Plot::Style class and also make it possible to instantiate that class from a pyplot object. I figured I could sort that out later... First I need to actually get the graph working.

First things first, I need to define a plot_beta_hist function that's called several times later.

from numpy.random import beta
# stuff...
def plot_beta_hist(ax, a, b):
    ax.hist(beta(a, b, size=10000), histtype="stepfilled",
            bins=25, alpha=0.8, normed=True)

We've got another numpy.random function here. This time it's beta which is... err, something to do with... um, probability distributions... parametrized by, uh... shape parameters... *blink*
Look, Wikipedia's over there, you can read all about it. I checked RosettaCode and found nothing so I tried to look up an algorithm, but the sites I found all used a lot of fancy symbols. This stuff was way over my head.

I really wanted to avoid making another wrapper for numpy, so I turned to my faithful stead, Perl 5. I found Math::Random on MetaCPAN which has a random_beta function!

For those unaware, Perl 6 is a different language to Perl 5 with a completely different runtime, so you can't natively use Perl 5 modules. However - just like Inline::Python - there is also an Inline::Perl5 module in the Perl 6 ecosystem. It's also made by Stefan Seifert (thanks Stefan!), and it's also much more developed and stable... to the point where you can import pretty much import any Perl 5 module and just use it as if it were a native module. No need for creating wrappers. It's as easy as this.

use Math::Random:from<Perl5> <random_beta>;

I don't even need to say use Inline::Perl5, I just use Math::Random and tell Perl 6 that this it's a Perl 5 module. Inline::Perl5 is automatically imported and does the rest. In this particular case, I'm also importing the random_beta function I need... And that's it!
Oh, and I'm also making use of that < word quoting > syntax covered way back in Part 2.

Perl has always been known as a great glue language, and here we see Perl 6 not simply spawning processes and parsing the output, but actually communicating directly with Python and Perl 5 and combining them to form one big delicious langwich! It really is a thing of beauty.

I converted the rest of the code (sans style sheet) and ran it. A wild graph appears! What the...

This is why you should read documentation. In the original Python, numpy.random.beta takes it's α and β values first followed by the size. However Math::Random::random_beta takes a size value first, followed by α and β. Serves me right. I swapped the arguments the right way around. Here's the final code (again, sans style sheet).

use Matplotlib;
use Math::Random:from<Perl5> <random_beta>;

my $plt = Matplotlib::Plot.new;

#$plt.style.use('bmh');

sub plot_beta_hist($ax, $a, $b) {
    my $beta = random_beta( 10000, $a, $b );
    $ax.hist(
        $beta,
        :histtype('stepfilled'),
        :bins(25), :alpha(0.8),
        :normed(True)
    );
}

my ( $fig, $ax ) = $plt.subplots();
plot_beta_hist( $ax, 10, 10 );
plot_beta_hist( $ax,  4, 12 );
plot_beta_hist( $ax, 50, 12 );
plot_beta_hist( $ax,  6, 55 );
$ax.set_title("'bmh' style sheet");

$plt.show()

The only real difference in my plot_beta_hist function is I've assigned the result (a list) to a scalar variable $beta before passing it to the hist method. On Reddit, raiph and I discussed other ways to pass scalar variables to Python.

You could wrap the call to random_beta in a scalar container

$ax.hist(
    $( random_beta( $a, $b, 10000 ) ),
    # other args...
);

or you could assign it to the anonymous scalar so it gets evaluated to a scalar before being passed to the function

$ax.hist(
    $ = random_beta( $a, $b, 10000 ),
    # other args...
);

There's more ways, and if you're playing along at home the choice is yours, but I like the readability of mine. Whichever one is used, the result is finally something that looks close to the example in the gallery.

Alright, I got a graph, but here's a quick life lesson. Just because someone presents data in a pretty graph doesn't mean it's true. This is a prime example of a misleading graph. It clearly says atop the graph it's using a 'bmh' style sheet. Lies! So, lets get back to that.

Similar to what I had done in Part 4, I created another class wrapping matplotlib.pyplot.style and also made it possible to instantiate this class when calling the style method on a Matplotlib::Plot object.

class Matplotlib::Plot::Style {
    method FALLBACK($name, |c) {
        $py.call('matplotlib.pyplot.style', $name, |c)
    }
}

class Matplotlib::Plot {
    method style {
        Matplotlib::Plot::Style.new
    }
    # stuff ...
}

Problem solved, right? Nope... I got an error back from Python saying name 'use' does not exist which is typically something Python says when you haven't imported something before trying to use it, but that didn't look like the case here. Additionally, while Inline::Python is kind of amazing for what it does, it's not really documented as it's still under development. I was really only aware of 2 methods: call and run.

The call method is literally for calling class methods. It takes 2 mandatory arguments: a package, and a method name; optional arguments can also be called. The run method is more for running arbitrary bits (ie, not class methods). It just takes one mandatory argument: some python code as a string. I managed to get it working using run instead. Again, my Python ignorance is showing here, but it seems style.use is not a method call, but probably an attribute or something.

I added a use method to my Matplotlib::Plot::Style class and called run with the relevant python code. While researching pyplot.style I found it also has an available attribute that returns a list of all the style sheets! That could come in handy, so I added it as well. For both these uses of run, I'm passing the eval option, which tells Inline:Python to return values from the evaluated python code.

class Matplotlib::Plot::Style {
    method use($name) {
        $py.run("matplotlib.pyplot.style.use('$name')", :eval)
    }
    method available {
        $py.run("matplotlib.pyplot.style.available", :eval)
    }
    method FALLBACK($name, |c) {
        say $name;
        $py.call('matplotlib.pyplot.style', $name, |c)
    }
}

The python code in is in double-quotes so that variables get interpolated, but I also had to put single-quotes around the argument for it to work... but it did work, and that's all I really care about. Look at this thing

So there you have it... One more thing! Remember I can get a list available styles? I can use this to pick a random style to display each time.

my $name = $plt.style.available.pick;
$plt.style.use($name);
# stuff...
$ax.set_title("'$name' style sheet");

So that's part 5. I didn't think I would write this much, but I'm still having fun here.

To be continued...