Chapter 5a - Presenting tables in toyplot

1 Chapter 5a – Presenting Tables in Python (using Toyplot)

Follow along with the code by running cells as you encounter them

Chapter Overview

  1. Packages and Data

  2. Creating a Basic Table

  3. Customising Fonts

  4. Aligning Columns

  5. Adjusting column widths and height

  6. Setting Grid Lines

  7. Setting Titles, Subtitles and Captions

  8. Styling
    8.1. Colouring Alternative Rows
    8.2. Adding additional header rows
    8.3. Highlighting Minimum and Maximum Rows
    8.4. Inserting Whitespace between Groups

  9. Grouping a column

  10. Saving Images

Previous versions of this course had tables in Matplotlib as the main content of this chapter. From March 2021 the main content of this chapter will use the package toyplot. This is not included as part of Anaconda and so must be installed separately.

Content for creating tables in Matplotlib has been moved to Chapter 5b. This is included as some users are unable to install additional packages. Creation of tables in Matplotlib is generally more involved and a fiddly process compared to toyplot.

toyplot can also be used to create a variety of other visualisations. It is described as a “kid sized” plotting tool, however as tools like Matplotlib and Seaborn have more support for non-table visualisation we only cover the tables aspect of this package.

Package documentation can be found here – toyplot documentation

Toyplot is the closest package the authors have found to encompassing all of the elements of the ONS and GSS Style Guide. However please note that there may still be elements that are not perfect matches.


2 1. Packages and Data

An alternative to tables in Matplotlib is using the package toyplot. This is not included as part of Anaconda and so must be installed seperatly.

Let’s start, as always by loading our packages and our data.

This chapter was written using:

  • pandas version 1.1.5
  • toyplot 0.19.0

Remember you can use the .__version__ attribute (e.g np.__version__ ) to check your version.

More information about the packages is given in Chapter 1.

We’re following standard convention for nicknames, and we’ll also load the gapminder data.

import pandas as pd
import toyplot

# Read in Data

gapminder = pd.read_csv("../data/gapminder.csv") # Read in the Data

We’re going to use the gapminder data and we’re going to prepare it before we start.

As with other visualisations data preparation is a really important step.

We need to have our data mostly formatted before we visualise it.

Here I’m manipulating my data so that I have the population data for 3 years (1997, 2002 and 2007) for the first five countries in each continent – apart from Oceania, which only comprises of 2 countries in our dataset.

select_countries_3y_pop = (gapminder[gapminder["year"].isin([1997, 2002, 2007])] # Filter for the years
                           .filter(["country", "continent", "year", "pop"]) # .filter *selects* columns
                           .astype({"pop": "int64"}) # Supresses the Scientific Notation of the year columns
                           .pivot_table(index = ["continent","country"], 
                                        columns = "year", values = "pop") # Make the data "wider" so each year is a col
                           .reset_index() # Reset our index; so we don't have a multi index
                           .groupby("continent").head()) # Group by the continent and return the first 5 rows for each continent.

select_countries_3y_pop.columns.name = None #Removed the "year" label on the index

# View the new DataFrame
select_countries_3y_pop
continent country 1997 2002 2007
0 Africa Algeria 2.907202e+07 3.128714e+07 3.333322e+07
1 Africa Angola 9.875024e+06 1.086611e+07 1.242048e+07
2 Africa Benin 6.066080e+06 7.026113e+06 8.078314e+06
3 Africa Botswana 1.536536e+06 1.630347e+06 1.639131e+06
4 Africa Burkina Faso 1.035284e+07 1.225121e+07 1.432620e+07
52 Americas Argentina 3.620346e+07 3.833112e+07 4.030193e+07
53 Americas Bolivia 7.693188e+06 8.445134e+06 9.119152e+06
54 Americas Brazil 1.685467e+08 1.799142e+08 1.900106e+08
55 Americas Canada 3.030584e+07 3.190227e+07 3.339014e+07
56 Americas Chile 1.459993e+07 1.549705e+07 1.628474e+07
77 Asia Afghanistan 2.222742e+07 2.526840e+07 3.188992e+07
78 Asia Bahrain 5.985610e+05 6.563970e+05 7.085730e+05
79 Asia Bangladesh 1.233153e+08 1.356568e+08 1.504483e+08
80 Asia Cambodia 1.178296e+07 1.292671e+07 1.413186e+07
81 Asia China 1.230075e+09 1.280400e+09 1.318683e+09
110 Europe Albania 3.428038e+06 3.508512e+06 3.600523e+06
111 Europe Austria 8.069876e+06 8.148312e+06 8.199783e+06
112 Europe Belgium 1.019979e+07 1.031197e+07 1.039223e+07
113 Europe Bosnia and Herzegovina 3.607000e+06 4.165416e+06 4.552198e+06
114 Europe Bulgaria 8.066057e+06 7.661799e+06 7.322858e+06
140 Oceania Australia 1.856524e+07 1.954679e+07 2.043418e+07
141 Oceania New Zealand 3.676187e+06 3.908037e+06 4.115771e+06

3 2. Creating a Basic Table

Firstly in Toyplot we create the canvas for our table to sit on. This is similar to create the fig and axes in matplotlib.

The measurements are in pixels, and are entered as width, height. We have used parameter names here - most of the documentation for toyplot does not use them.

Here I have added the parameter style = {"background-color":"gray"} to demonstrate the area.

canvas = toyplot.Canvas(width=700, height=500, style = {"background-color":"gray" })

Adding a table to the canvas is simple. canvas.table() takes an argument for data which among other things will take a Pandas Data Frame.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06

4 3. Customising Fonts

The table is broken down into several parts. These are table.cells which acts on all cells, and table.body which excludes header rows. Within those we can access columns and rows, e.g table.cells.row and table.cell.column.

These can then be indexed to make behaviours act on certain instances.

We can set “styles” for text in rows using .lstyle (L probably standing for letters) This takes a dictionary of a select few CSS methods. Methods of dealing with text can be found in the documentation.

Here we’ve used some of the useful parameters; note that ”fill” corresponds to text colour.

I’ve set the main text separately to the header rows. Here I’ve used the slicing technique [1:] to select from index 1 through to the end rather than hard coding for the number of rows.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06

5 4. Aligning Columns

By default columns are aligned based on their data type. Numerical data is aligned right, and text based data is aligned in the centre.

I’d like Continent and Country to be aligned left. To do this within the table.cells.columns I index columns 0 and 1 and set the alignment to be left.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06

6 5. Adjusting column widths and height

We can also do things like manually adjust the width of columns and the height of them as well.

Toyplot naturally attempts to fit the plot or table to the canvas; which gives a nice effect but may need fine tuning.

This looks to have made no difference here - but if we didn’t include it our future gridlines would be in the wrong place.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Set width for Country Column
table.cells.column[[1]].width = 150

# Make the header row taller
table.cells.row[[0]].height = 20
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06

7 6. Setting Grid Lines

We can set up gridlines using table.cells.grid and then vlines for vertical lines or hlines for horizontal lines.

Within the square brackets we specify which rows we want these to apply to. This is given as a list of

[ROW, COLUMNS]

is used to represent ALL rows or columns, and a subset of rows or columns can be passed as a list.

In the visualisation I did not want a vertical line at the start or end of my chart – so I passed

[…, [1,2,3,4]]

to have the lines go down ALL rows, but only appear on columns 1:4. Slicing does not work in this instance so they are written out.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3,4]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06

8 7. Setting Titles, Subtitles and Captions

While titles can be set directly from the canvas.table there is more control if these are set as canvas.text() methods.

These take an x and y argument to specify the location.

The style = argument takes some basic CSS to define its behaviour. In addition to the ones we’ve talked about before a useful argument is:

text-anchor - by default this is the middle of the text entered, but can be changed to “start” or “end” to give more control.

The text = parameter will also allow HTML linking. It is advised to ensure the <a target = “_blank”> is included to open in a new window rather than your existing Jupyter Notebook.

As the symbols < , > and & are used in XHTML 5 these should be replaced with the codes &lt; for < , &gt; for > and &amp; for & if you wish to use them.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3,4]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}


# Title
canvas.text(x = 45, y= 20,  
            text = "Table 1: Population of select countries over three years",
            style= {"font-size":"16px", "font-weight":"bold", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
# Subtitle
canvas.text(x = 45, y= 35,  
            text = "Population for 1997, 2002 and 2007",
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
#Caption
canvas.text(x = 510, y= 465,  
            text = ("<a target='_blank' href='https://www.gapminder.org/'>Source: Gapminder.org</a>"), # A link opens in a new window!
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"});
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06Table 1: Population of select countries over three yearsPopulation for 1997, 2002 and 2007Source: Gapminder.org

9 8. Styling

9.1 8.1 Colouring Alternative Rows

Colouring alternative rows can be done using table.body.row[].style.

Here a list comprehension is used to identify the even numbered rows and colours them. Note this is working on the “body” rows – so Africa, Algeria is row 0, which is even and why it is coloured.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3,4]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}


# Title
canvas.text(x = 45, y= 20,  
            text = "Table 1: Population of select countries over three years",
            style= {"font-size":"16px", "font-weight":"bold", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
# Subtitle
canvas.text(x = 45, y= 35,  
            text = "Population for 1997, 2002 and 2007",
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
#Caption
canvas.text(x = 510, y= 465,  
            text = ("<a target='_blank' href='https://www.gapminder.org/'>Source: Gapminder.org</a>"), # A link opens in a new window!
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})

# Colour even Rows
rows_to_colour = [(each_row % 2 == 0) for each_row in range(len(select_countries_3y_pop))] # This uses list comprehension to highlight odd numbered rows.

table.body.row[rows_to_colour].style = {"fill":"#F2F2F2"}; 
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06Table 1: Population of select countries over three yearsPopulation for 1997, 2002 and 2007Source: Gapminder.org

9.2 8.2 Adding additional header rows

Adding in additional header rows is as simple as passing trows = in to the canvas.table() method. Setting this to 2 gives us 2 top rows.

Actions like merging the cells; adding text in to them and formatting can then be done; as seen below.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_countries_3y_pop, trows = 2)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Add the text in to the top row and format
merged = table.top.cell[0, 2:5].merge()
merged.data = "Year"
merged.align = "center"

merged2 = table.top.cell[0, 0:2].merge()
merged2.data = "Location"
merged2.align = "center"


# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3,4]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}


# Title
canvas.text(x = 45, y= 20,  
            text = "Table 1: Population of select countries over three years",
            style= {"font-size":"16px", "font-weight":"bold", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
# Subtitle
canvas.text(x = 45, y= 35,  
            text = "Population for 1997, 2002 and 2007",
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
#Caption
canvas.text(x = 510, y= 465,  
            text = ("<a target='_blank' href='https://www.gapminder.org/'>Source: Gapminder.org</a>"), # A link opens in a new window!
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})

# Colour even Rows
rows_to_colour = [(each_row % 2 == 0) for each_row in range(len(select_countries_3y_pop))] # This uses list comprehension to highlight odd numbered rows.

table.body.row[rows_to_colour].style = {"fill":"#F2F2F2"}; 
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07AfricaAngola9.87502e+061.08661e+071.24205e+07AfricaBenin6.06608e+067.02611e+068.07831e+06AfricaBotswana1.53654e+061.63035e+061.63913e+06AfricaBurkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07AmericasBolivia7.69319e+068.44513e+069.11915e+06AmericasBrazil1.68547e+081.79914e+081.90011e+08AmericasCanada3.03058e+073.19023e+073.33901e+07AmericasChile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07AsiaBahrain598561656397708573AsiaBangladesh1.23315e+081.35657e+081.50448e+08AsiaCambodia1.1783e+071.29267e+071.41319e+07AsiaChina1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06EuropeAustria8.06988e+068.14831e+068.19978e+06EuropeBelgium1.01998e+071.0312e+071.03922e+07EuropeBosnia and Herzegovina3.607e+064.16542e+064.5522e+06EuropeBulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07OceaniaNew Zealand3.67619e+063.90804e+064.11577e+06YearLocationTable 1: Population of select countries over three yearsPopulation for 1997, 2002 and 2007Source: Gapminder.org

9.3 8.3 Highlighting Minimum and Maximum Rows

We can also highlight minimum and maximum rows.

I’ve created a new data set just for 1997 to show this. It’s also really important to reset the index; we’ve not needed to do this so far but toyplot creates its own index and the two need to match up.

The low and high index are found using a filter and returning the index value. There may be more efficient ways to find this value, but the row value is then passed to the row indexer, and an lstyle applied to colour the text or a style applied to colour the rows. Here one of each has been done for demonstration purposes.

select_97 = select_countries_3y_pop[["continent", "country", 1997]]
select_97.reset_index(inplace=True, drop = True)
# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_97)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}


# Title
canvas.text(x = 45, y= 20,  
            text = "Table 1: Population of select countries over three years",
            style= {"font-size":"16px", "font-weight":"bold", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
# Subtitle
canvas.text(x = 45, y= 35,  
            text = "Population for 1997, 2002 and 2007",
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
#Caption
canvas.text(x = 510, y= 465,  
            text = ("<a target='_blank' href='https://www.gapminder.org/'>Source: Gapminder.org</a>"), # A link opens in a new window!
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})

# Colour even Rows
rows_to_colour = [(each_row % 2 == 0) for each_row in range(len(select_97))] # This uses list comprehension to highlight odd numbered rows.

table.body.row[rows_to_colour].style = {"fill":"#F2F2F2"}


# Find the lowest and highest rows

low_index = select_97[select_97[1997] == select_97[1997].min()].index.values
high_index = select_97[select_97[1997] == select_97[1997].max()].index.values

# Colour high and low
table.body.row[low_index].style = { "fill":"blue", "opacity": "0.5"}
table.body.row[high_index].lstyle = {"font-weight":"bold", "fill":"red"}
continentcountry1997AfricaAlgeria2.9072e+07AfricaAngola9.87502e+06AfricaBenin6.06608e+06AfricaBotswana1.53654e+06AfricaBurkina Faso1.03528e+07AmericasArgentina3.62035e+07AmericasBolivia7.69319e+06AmericasBrazil1.68547e+08AmericasCanada3.03058e+07AmericasChile1.45999e+07AsiaAfghanistan2.22274e+07AsiaBahrain598561AsiaBangladesh1.23315e+08AsiaCambodia1.1783e+07AsiaChina1.23008e+09EuropeAlbania3.42804e+06EuropeAustria8.06988e+06EuropeBelgium1.01998e+07EuropeBosnia and Herzegovina3.607e+06EuropeBulgaria8.06606e+06OceaniaAustralia1.85652e+07OceaniaNew Zealand3.67619e+06Table 1: Population of select countries over three yearsPopulation for 1997, 2002 and 2007Source: Gapminder.org

9.4 8.4 Inserting Whitespace between Groups

Gaps can be added in to the data using table.body.gaps.rows or .columns

The indexes passed should be the ones before the gap, e.g 5 will have a blank line after index 5.

It’s important to note here that the added lines are just white space – they’re not blank lines with their own index. Note the colouring of “even” rows has had to be adjusted here to match previous aesthetics.

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = select_97)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}


# Title
canvas.text(x = 45, y= 20,  
            text = "Table 1: Population of select countries over three years",
            style= {"font-size":"16px", "font-weight":"bold", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
# Subtitle
canvas.text(x = 45, y= 35,  
            text = "Population for 1997, 2002 and 2007",
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
#Caption
canvas.text(x = 510, y= 465,  
            text = ("<a target='_blank' href='https://www.gapminder.org/'>Source: Gapminder.org</a>"), # A link opens in a new window!
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})




# Find the lowest and highest rows

low_index = select_97[select_97[1997] == select_97[1997].min()].index.values
high_index = select_97[select_97[1997] == select_97[1997].max()].index.values

# Colour high and low
table.body.row[low_index].style = { "fill":"blue", "opacity": "0.5"}
table.body.row[high_index].lstyle = {"font-weight":"bold", "fill":"red"}

# White space with gaps
a = table.body.gaps.rows[[4,9,14,19 ]] = "0.4cm"

# Colour "even" Rows
table.body.row[[0,2,4,5,7,9, 10, 12,14,15,17,19,20]].style = {"fill":"#F2F2F2"}
continentcountry1997AfricaAlgeria2.9072e+07AfricaAngola9.87502e+06AfricaBenin6.06608e+06AfricaBotswana1.53654e+06AfricaBurkina Faso1.03528e+07AmericasArgentina3.62035e+07AmericasBolivia7.69319e+06AmericasBrazil1.68547e+08AmericasCanada3.03058e+07AmericasChile1.45999e+07AsiaAfghanistan2.22274e+07AsiaBahrain598561AsiaBangladesh1.23315e+08AsiaCambodia1.1783e+07AsiaChina1.23008e+09EuropeAlbania3.42804e+06EuropeAustria8.06988e+06EuropeBelgium1.01998e+07EuropeBosnia and Herzegovina3.607e+06EuropeBulgaria8.06606e+06OceaniaAustralia1.85652e+07OceaniaNew Zealand3.67619e+06Table 1: Population of select countries over three yearsPopulation for 1997, 2002 and 2007Source: Gapminder.org

10 9. Grouping a column

Often tables will have a “grouped” column. Rather than the first 5 rows having “Africa” in the continent column; only the first would. This can make data slightly easier to read; some people find this more attractive. This can also create the illusion of merged cells – similarly to Excel. This solution is one of many ways it could be done; this method avoids multiple indexes which often don’t work well with Matplotlib.

Using the .duplicated() method on the continent column returns a Boolean series; False where it’s the first instance of the value; and True where it’s repeated.

.loc[] can then be used, passing that Boolean series as the lookup; updating the “continent” column and setting the value of the column (where True) to blank.

The data then gives the impression of “grouping” the continents together; it’s a fake impression as all that has happened is the duplicate values have become empty strings – but it works for the desired look in the final table.

grouped_continents = select_countries_3y_pop.copy()
grouped_continents.reset_index(inplace = True, drop=True) # Reset the index
duplicate_continents = grouped_continents["continent"].duplicated() # Returns "False" for first item, "True" for subsequent
grouped_continents.loc[duplicate_continents,"continent"] = ""  # Replaces "True" values with blanks
grouped_continents.head(7) # Check it out! Gives the impression of a multilevel index
continent country 1997 2002 2007
0 Africa Algeria 29072015.0 31287142.0 33333216.0
1 Angola 9875024.0 10866106.0 12420476.0
2 Benin 6066080.0 7026113.0 8078314.0
3 Botswana 1536536.0 1630347.0 1639131.0
4 Burkina Faso 10352843.0 12251209.0 14326203.0
5 Americas Argentina 36203463.0 38331121.0 40301927.0
6 Bolivia 7693188.0 8445134.0 9119152.0

Let’s check out the effect when we apply it to our previous table:

# Create the Canvas
canvas = toyplot.Canvas(width=700, height=500)

# Add the Data
table = canvas.table(data = grouped_continents, trows = 2)

# Set alignment for Continent and Country
table.cells.column[[0, 1]].align = "left"  # [[0,1]] only affects the first 2 columns.

# Add the text in to the top row and format
merged = table.top.cell[0, 2:5].merge()
merged.data = "Year"
merged.align = "center"

merged2 = table.top.cell[0, 0:2].merge()
merged2.data = "Location"
merged2.align = "center"


# Set width for Country Column
table.cells.column[[1]].width = 150

# Header Row Style
table.cells.row[0].lstyle = {"font-size":"14px", "font-family":"sans-serif", "font-weight": "bold", "fill": "black"}

# Style table text
table.cells.row[1:].lstyle = {"font-size":"12px", "font-family":"sans-serif", "fill": "black"}

# Set up the gridlines
table.cells.grid.vlines[..., [1,2,3,4]]= "single"  # ALL rows, columns 1 to 4
table.cells.grid.hlines[1,...] = "single"  # ROW 1 , ALL columns
table.cells.grid.style= { "stroke-width": "1", "stroke": "grey"}


# Title
canvas.text(x = 45, y= 20,  
            text = "Table 1: Population of select countries over three years",
            style= {"font-size":"16px", "font-weight":"bold", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
# Subtitle
canvas.text(x = 45, y= 35,  
            text = "Population for 1997, 2002 and 2007",
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})
#Caption
canvas.text(x = 510, y= 465,  
            text = ("<a target='_blank' href='https://www.gapminder.org/'>Source: Gapminder.org</a>"), # A link opens in a new window!
            style= {"font-size":"14px", "font-family":"sans-serif", "text-anchor":"start", "fill": "black"})

# Colour even Rows
rows_to_colour = [(each_row % 2 == 0) for each_row in range(len(grouped_continents))] # This uses list comprehension to highlight odd numbered rows.

table.body.row[rows_to_colour].style = {"fill":"#F2F2F2"}; 
continentcountry199720022007AfricaAlgeria2.9072e+073.12871e+073.33332e+07Angola9.87502e+061.08661e+071.24205e+07Benin6.06608e+067.02611e+068.07831e+06Botswana1.53654e+061.63035e+061.63913e+06Burkina Faso1.03528e+071.22512e+071.43262e+07AmericasArgentina3.62035e+073.83311e+074.03019e+07Bolivia7.69319e+068.44513e+069.11915e+06Brazil1.68547e+081.79914e+081.90011e+08Canada3.03058e+073.19023e+073.33901e+07Chile1.45999e+071.5497e+071.62847e+07AsiaAfghanistan2.22274e+072.52684e+073.18899e+07Bahrain598561656397708573Bangladesh1.23315e+081.35657e+081.50448e+08Cambodia1.1783e+071.29267e+071.41319e+07China1.23008e+091.2804e+091.31868e+09EuropeAlbania3.42804e+063.50851e+063.60052e+06Austria8.06988e+068.14831e+068.19978e+06Belgium1.01998e+071.0312e+071.03922e+07Bosnia and Herzegovina3.607e+064.16542e+064.5522e+06Bulgaria8.06606e+067.6618e+067.32286e+06OceaniaAustralia1.85652e+071.95468e+072.04342e+07New Zealand3.67619e+063.90804e+064.11577e+06YearLocationTable 1: Population of select countries over three yearsPopulation for 1997, 2002 and 2007Source: Gapminder.org

11 10. Saving Images

Toyplot has a few back ends to save our outputs to.

The most commonly used ones are PDF, SVG and HTML.

There is also the ability to save to a png; although I have been unable to get this to work.

There are methods of converting .svg, .html or .pdf files to .png within Python if needed. Or the open source software “InkScape” opens SVG files and can convert them to .png or .jpeg files.

# Save as HTML
import toyplot.html
toyplot.html.render(canvas, "../outputs/figure1_html.html")

# Save as PDF
import toyplot.pdf
toyplot.pdf.render(canvas, "../outputs/figure1_pdf.pdf")

# Save as SVG
import toyplot.svg
toyplot.svg.render(canvas, "../outputs/figure1_pdf.pdf")

12 End of Chapter

You have completed the main content of the Data Visualization Course.

You may wish to explore tables in Matplotlib - chapter 5b or continue to Chapter 6 - Case Studies.

Please ensure you complete the survey on the Learning Hub

return to menu