Visualizing geographical AMPL data using IPython and Google Charts

In this post I'll describe how to get geographical data from AMPL and display it in IPython using an interactive Google Chart. I'll use a simple map coloring problem implemented with AMPL constraint programming extensions.

First install the ampl extension for IPython also known as iampl if you haven't done this already. The extension is now available in the Python Package Index so you can use standard Python tools such as pip or easy_install to install it. See the installation instructions for details.

Once the extension is installed you can load it in IPython:

In [1]:
%load_ext ampl

Now it is possible to write AMPL code directly in an IPython notebook using the %%ampl magic. The followng code declares a small map coloring model, provides some data and solves it with Gecode (you can download the AMPL driver for Gecode from the AMPL repository on Google Code):

In [2]:
%%ampl
param NumColors;

set Countries;
set Neighbors within {Countries, Countries};

var color{Countries} integer >= 1 <= NumColors;

s.t. different_colors{(c1, c2) in Neighbors}:
  (color[c1] != color[c2]);

data;

param NumColors := 4;

set Countries := Belgium Denmark France Germany Luxembourg Netherlands;

set Neighbors :=
  Belgium France 
  Belgium Germany 
  Belgium Netherlands
  Belgium Luxembourg
  Denmark Germany 
  France  Germany 
  France  Luxembourg
  Germany Luxembourg
  Germany Netherlands;

option solver gecode;
solve;
gecode 4.0.0: feasible solution
6 nodes, 0 fails

After evaluating the above code, AMPL objects become available in IPython and can be used as regular Python objects. For example, a collection of variables color, which maps country names to their colors can be accessed similarly to Python's dict:

In [3]:
print(color)
{'Netherlands': 2.0, 'Denmark': 1.0, 'Luxembourg': 4.0, 'France': 2.0, 'Germany': 3.0, 'Belgium': 1.0}

Now let's use Google Charts to display a colored map. GeoChart is a simple helper function which takes two positional arguments, a list of column names for the data table and a dictionary containing the data itself. It also takes arbitrary keyword arguments which are mapped to chart options. See the GeoChart documentation for the list of available options.

In [4]:
from ampl.gchart import GeoChart
GeoChart(['Country', 'Color'], color, region=150, legend=False, height=500,
         colorAxis={'colors': ['#ff9900', '#3366cc', '#109618', '#dc3912']})
Out[4]:

Here's the full implementation of the GeoChart function from the ampl.gchart package included in the distribution:

def GeoChart(keys, data, **kwargs):
    table = "{},\n".format(keys)
    for i in data:
        table += "['{}', {}],\n".format(i, data[i])
    options = ""
    for arg, value in kwargs.iteritems():
        if isinstance(value, bool):
            value = 'true' if value else 'false'
        elif isinstance(value, dict):
            items = ""
            for k, v in value.iteritems():
                items += "{}: {},".format(k, v)
            value = "{{{}}}".format(items)
        options += "{}:{},\n".format(arg, value)
    return Javascript("""
        container.show();
        function draw() {{
          var chart = new google.visualization.GeoChart(element[0]);
          chart.draw(google.visualization.arrayToDataTable([{}]), {{{}}});
        }}
        google.load('visualization', '1.0',
          {{'callback': draw, 'packages':['geochart']}});
        """.format(table, options), lib="https://www.google.com/jsapi")

As you can see the implementation is very simple, all it does is converting the data and options passed as arguments into JavaScript and returning the result as a Javascript object that is processed by IPython. In fact, you could construct the Javascript object directly but it would be much less convenient.

With little effort it is possible to create similar wrappers for other types of charts from the Google Chart library or even different Javascript libraries.

comments powered by Disqus