Tutorial: Generating Tables and Exporting

Generating Tables and Exporting

This walkthrough is also available as a Jupyter ipynb Notebook - you can run yourself

Generating Tables

[todo]

Use tables to:

  • filter
  • sort
  • highlight rows / columns / style entire table
  • add columns
  • format data
  • export to different types of formats:
    • html
    • markdown
    • csv
    • array

utils = require('jupyter-ijavascript-utils');
['utils'];
[ 'utils' ]

Accessing a Sample Dataset

The Vega team has a Sample Datasets library

The jupyter-ijavascript-utils library references it, so you can get sample data quickly.

We can see the list of the datasets available: utils.datasets.list()

// [ // 'annual-precip.json', // 'anscombe.json', // 'barley.json', // 'budget.json', // 'budgets.json', // 'burtin.json', // ... // ] The DataSet we want is the facinating GapMinder Life Expectancy study

$$.async()
utils.datasets.fetch('gapminder.json')
.then(data => {
	gapMinder = data;
	$$.sendResult(`captured gap minder records: ${gapMinder.length}`);
});

// 'captured gap minder records: 693'
'captured gap minder records: 693'

As we called $$.async() - the cell knows that it should pause execution for the next cell until $$.sendResult(...) is called.

Note - the utils.ijs.await method available in the library can simplify this call, to support await.

//-- does the same thing as the cell above
utils.ijs.await(async ($$, console) => {
	gapMinder = await utils.datasets.fetch('gapminder.json');
    return `captured gap minder records: ${gapMinder.length}`;
});

// 'captured gap minder records: 693'

See the ijs.await() docs for more.

Understanding the Data

One option to understand the kinds of data is to always look at the first record:

gapMinder[0];

// gives:
// {
//   year: 1955,
//   country: 'Afghanistan',
//   cluster: 0,
//   pop: 8891209,
//   life_expect: 30.332,
//   fertility: 7.7
// }
{
  year: 1955,
  country: 'Afghanistan',
  cluster: 0,
  pop: 8891209,
  life_expect: 30.332,
  fertility: 7.7
}

Render as a Table

We can simply render this dataset through the command:

utils.table(objectCollection).render()

The TableGenerator#render will generate the HTML and then send it to the browser to render it.

utils.table(gapMinder)
    .limit(3)
    .render()
year country cluster pop life_expect fertility
1,955 Afghanistan 0 8,891,209 30.332 7.7
1,960 Afghanistan 0 9,829,450 31.997 7.7
1,965 Afghanistan 0 10,997,885 34.02 7.7

Filtering Data

You can filter results similar to the Array.filter() method

utils.table(gapMinder)
    .filter((r) => r.country === 'Afghanistan')
    .render()
year country cluster pop life_expect fertility
1,955 Afghanistan 0 8,891,209 30.332 7.7
1,960 Afghanistan 0 9,829,450 31.997 7.7
1,965 Afghanistan 0 10,997,885 34.02 7.7
1,970 Afghanistan 0 12,430,623 36.088 7.7
1,975 Afghanistan 0 14,132,019 38.438 7.7
1,980 Afghanistan 0 15,112,149 39.854 7.8
1,985 Afghanistan 0 13,796,928 40.822 7.9
1,990 Afghanistan 0 14,669,339 41.674 8
1,995 Afghanistan 0 20,881,480 41.763 8
2,000 Afghanistan 0 23,898,198 42.129 7.479
2,005 Afghanistan 0 29,928,987 43.828 7.069

Rendering groups of data

This also works with groups to find a particular list.

I think I'm quite interested to see how the data looks for Afghanistan.

Lets use the utils.table() or TableGenerator() to make this easier to see.

utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
)
  .render()
year country cluster pop life_expect fertility
1,955 Afghanistan 0 8,891,209 30.332 7.7
1,960 Afghanistan 0 9,829,450 31.997 7.7
1,965 Afghanistan 0 10,997,885 34.02 7.7
1,970 Afghanistan 0 12,430,623 36.088 7.7
1,975 Afghanistan 0 14,132,019 38.438 7.7
1,980 Afghanistan 0 15,112,149 39.854 7.8
1,985 Afghanistan 0 13,796,928 40.822 7.9
1,990 Afghanistan 0 14,669,339 41.674 8
1,995 Afghanistan 0 20,881,480 41.763 8
2,000 Afghanistan 0 23,898,198 42.129 7.479
2,005 Afghanistan 0 29,928,987 43.828 7.069

Screenshot of render table

Styling a Table

Note that we can style the table if we'd like to show when the life expectancy rises above 40 years of age

utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
  )
  .styleRow(({ record:r }) => r.life_expect > 40 ? 'background-color: #AAA' : '')
  .styleCell(({ record, columnIndex }) =>
        record.life_expect > 40 && columnIndex === 3 ? 'background-color: #AFA' : '')
  .render();
year country cluster pop life_expect fertility
1,955 Afghanistan 0 8,891,209 30.332 7.7
1,960 Afghanistan 0 9,829,450 31.997 7.7
1,965 Afghanistan 0 10,997,885 34.02 7.7
1,970 Afghanistan 0 12,430,623 36.088 7.7
1,975 Afghanistan 0 14,132,019 38.438 7.7
1,980 Afghanistan 0 15,112,149 39.854 7.8
1,985 Afghanistan 0 13,796,928 40.822 7.9
1,990 Afghanistan 0 14,669,339 41.674 8
1,995 Afghanistan 0 20,881,480 41.763 8
2,000 Afghanistan 0 23,898,198 42.129 7.479
2,005 Afghanistan 0 29,928,987 43.828 7.069

Screenshot of styling a table

Adjusting the Table

While the table is helpful, lets clean it up a bit:

  • hide the cluster column
  • add in a new column for the continent
  • make the year render as a string - ex: 1966

We'll do this through

continents = [
  { id: 0, continent: 'South Asia' },
  { id: 1, continent: 'Europe & Central Asias' },
  { id: 2, continent: 'Sub-Saharan Africa' },
  { id: 3, continent: 'Americas' },
  { id: 4, continent: 'East Asia & Pacific' },
  { id: 5, continent: 'Middle East & North Africa' }
];
clusterMap = utils.group.index(continents, 'id');
// map of contents with the id field as the key
Map(6) {
  0 => { id: 0, continent: 'South Asia' },
  1 => { id: 1, continent: 'Europe & Central Asias' },
  2 => { id: 2, continent: 'Sub-Saharan Africa' },
  3 => { id: 3, continent: 'Americas' },
  4 => { id: 4, continent: 'East Asia & Pacific' },
  5 => { id: 5, continent: 'Middle East & North Africa' }
}

First, lets augment the data with the continent

Any properties passed in the object to augment, are appended to the dataset (non-mutating)

utils.table(gapMinder)
  .filter((r) => r.country === 'Afghanistan')

  //-- add new field / column
  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  .render();
year country cluster pop life_expect fertility continent
1,955 Afghanistan 0 8,891,209 30.332 7.7 South Asia
1,960 Afghanistan 0 9,829,450 31.997 7.7 South Asia
1,965 Afghanistan 0 10,997,885 34.02 7.7 South Asia
1,970 Afghanistan 0 12,430,623 36.088 7.7 South Asia
1,975 Afghanistan 0 14,132,019 38.438 7.7 South Asia
1,980 Afghanistan 0 15,112,149 39.854 7.8 South Asia
1,985 Afghanistan 0 13,796,928 40.822 7.9 South Asia
1,990 Afghanistan 0 14,669,339 41.674 8 South Asia
1,995 Afghanistan 0 20,881,480 41.763 8 South Asia
2,000 Afghanistan 0 23,898,198 42.129 7.479 South Asia
2,005 Afghanistan 0 29,928,987 43.828 7.069 South Asia

Column Labels

The columns are the names of the properties on the objects by default.

We can change that by calling labels()

Use an object to map the property name to a string of what the Column Header should be.

utils.table(gapMinder)
  .filter((r) => r.country === 'Afghanistan')

  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})

  .render();
year country cluster population life expectancy fertility continent
1,955 Afghanistan 0 8,891,209 30.332 7.7 South Asia
1,960 Afghanistan 0 9,829,450 31.997 7.7 South Asia
1,965 Afghanistan 0 10,997,885 34.02 7.7 South Asia
1,970 Afghanistan 0 12,430,623 36.088 7.7 South Asia
1,975 Afghanistan 0 14,132,019 38.438 7.7 South Asia
1,980 Afghanistan 0 15,112,149 39.854 7.8 South Asia
1,985 Afghanistan 0 13,796,928 40.822 7.9 South Asia
1,990 Afghanistan 0 14,669,339 41.674 8 South Asia
1,995 Afghanistan 0 20,881,480 41.763 8 South Asia
2,000 Afghanistan 0 23,898,198 42.129 7.479 South Asia
2,005 Afghanistan 0 29,928,987 43.828 7.069 South Asia

Formatting Columns

If we want to change how certain columns are rendered (such as avoiding columns for years)

We can use the formatter() method

Similar to the Augment method, all the properties passed on the object are passed to a function to format before printing.

utils.table(gapMinder)
  .filter((r) => r.country === 'Afghanistan')

  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})

  //-- format a specific value, say a year to a String
  .formatter({
    // property: formattingFunction
    life_expect: (val) => String(val),
    
    //-- alternatively, you can convert to 'Number', 'String' or 'Boolean'
    year: 'string'
  })

  .render();
year country cluster population life expectancy fertility continent
1955 Afghanistan 0 8,891,209 30.332 7.7 South Asia
1960 Afghanistan 0 9,829,450 31.997 7.7 South Asia
1965 Afghanistan 0 10,997,885 34.02 7.7 South Asia
1970 Afghanistan 0 12,430,623 36.088 7.7 South Asia
1975 Afghanistan 0 14,132,019 38.438 7.7 South Asia
1980 Afghanistan 0 15,112,149 39.854 7.8 South Asia
1985 Afghanistan 0 13,796,928 40.822 7.9 South Asia
1990 Afghanistan 0 14,669,339 41.674 8 South Asia
1995 Afghanistan 0 20,881,480 41.763 8 South Asia
2000 Afghanistan 0 23,898,198 42.129 7.479 South Asia
2005 Afghanistan 0 29,928,987 43.828 7.069 South Asia

Specifying Columns

There are a few options to specifying which columns to show, or not, and the order.

In this example, we can simply keep all the columns - but not show one column in particular:

columnsToExclude()



utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
  )


  //-- add new field / column
  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})



  //-- explicitly exclude a specific column
  .columnsToExclude(['cluster'])

  //-- or you could explicitly set the columns and order
  // .columns(['year', 'continent', 'country', 'pop', 'life_expect', 'fertility'])

  .render();
year country population life expectancy fertility continent
1,955 Afghanistan 8,891,209 30.332 7.7 South Asia
1,960 Afghanistan 9,829,450 31.997 7.7 South Asia
1,965 Afghanistan 10,997,885 34.02 7.7 South Asia
1,970 Afghanistan 12,430,623 36.088 7.7 South Asia
1,975 Afghanistan 14,132,019 38.438 7.7 South Asia
1,980 Afghanistan 15,112,149 39.854 7.8 South Asia
1,985 Afghanistan 13,796,928 40.822 7.9 South Asia
1,990 Afghanistan 14,669,339 41.674 8 South Asia
1,995 Afghanistan 20,881,480 41.763 8 South Asia
2,000 Afghanistan 23,898,198 42.129 7.479 South Asia
2,005 Afghanistan 29,928,987 43.828 7.069 South Asia

Alternatively, you can specify only the columns to show and the order will be preserved

JSON.stringify(utils.object.keys(gapMinder));
// "year","country","cluster","pop","life_expect","fertility","continent"
'["year","country","cluster","pop","life_expect","fertility"]'
utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
  )


  //-- add new field / column
  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})



  //-- explicitly exclude a specific column
  //.columnsToExclude(['cluster'])
  //-- or you could explicitly set the columns and order
  .columns(['year', 'continent', 'country', 'pop', 'life_expect', 'fertility'])

  .render();
year continent country population life expectancy fertility
1,955 South Asia Afghanistan 8,891,209 30.332 7.7
1,960 South Asia Afghanistan 9,829,450 31.997 7.7
1,965 South Asia Afghanistan 10,997,885 34.02 7.7
1,970 South Asia Afghanistan 12,430,623 36.088 7.7
1,975 South Asia Afghanistan 14,132,019 38.438 7.7
1,980 South Asia Afghanistan 15,112,149 39.854 7.8
1,985 South Asia Afghanistan 13,796,928 40.822 7.9
1,990 South Asia Afghanistan 14,669,339 41.674 8
1,995 South Asia Afghanistan 20,881,480 41.763 8
2,000 South Asia Afghanistan 23,898,198 42.129 7.479
2,005 South Asia Afghanistan 29,928,987 43.828 7.069

Exporting to different types of data

Note that there are a few other types of output, such as:

Like the Markdown table shown here:

year country cluster pop life_expect fertility
1,955 Afghanistan 0 8,891,209 30.332 7.7
1,960 Afghanistan 0 9,829,450 31.997 7.7
1,965 Afghanistan 0 10,997,885 34.02 7.7
1,970 Afghanistan 0 12,430,623 36.088 7.7
1,975 Afghanistan 0 14,132,019 38.438 7.7
1,980 Afghanistan 0 15,112,149 39.854 7.8
1,985 Afghanistan 0 13,796,928 40.822 7.9
1,990 Afghanistan 0 14,669,339 41.674 8
1,995 Afghanistan 0 20,881,480 41.763 8
2,000 Afghanistan 0 23,898,198 42.129 7.479
2,005 Afghanistan 0 29,928,987 43.828 7.069

Markdown

To render as Markdown, simply finish with the renderMarkdown() method

Note that this has an accompanying method: generateMarkdown()

utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
  )

  //-- add new field / column
  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  .limit(3)

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})

  //-- format a specific value, say a year to a String
  .formatter({
    // property: formattingFunction
    life_expect: (val) => String(val),
    
    //-- alternatively, you can convert to 'Number', 'String' or 'Boolean'
    year: 'string'
  })

  //-- explicitly exclude a specific column
  .columnsToExclude(['cluster'])

  //-- or you could explicitly set the columns and order
  // .columns(['year', 'continent', 'country', 'pop', 'life_expect', 'fertility'])

  .renderMarkdown();
year|country    |population|life expectancy|fertility|continent 
--  |--         |--        |--             |--       |--        
1955|Afghanistan|8,891,209 |30.332         |7.7      |South Asia
1960|Afghanistan|9,829,450 |31.997         |7.7      |South Asia
1965|Afghanistan|10,997,885|34.02          |7.7      |South Asia
year country population life expectancy fertility continent
1955 Afghanistan 8,891,209 30.332 7.7 South Asia
1960 Afghanistan 9,829,450 31.997 7.7 South Asia
1965 Afghanistan 10,997,885 34.02 7.7 South Asia

Render as CSV

To render as Markdown, simply finish with the renderCSV() method

Note that this has an accompanying method: generateCSV()

utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
  )

  //-- add new field / column
  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  .limit(3)

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})

  //-- format a specific value, say a year to a String
  .formatter({
    // property: formattingFunction
    life_expect: (val) => String(val),
    
    //-- alternatively, you can convert to 'Number', 'String' or 'Boolean'
    year: 'string'
  })

  //-- explicitly exclude a specific column
  .columnsToExclude(['cluster'])

  //-- or you could explicitly set the columns and order
  // .columns(['year', 'continent', 'country', 'pop', 'life_expect', 'fertility'])

  .renderCSV();
"year","country","population","life expectancy","fertility","continent"
"1955","Afghanistan","8,891,209","30.332","7.7","South Asia"
"1960","Afghanistan","9,829,450","31.997","7.7","South Asia"
"1965","Afghanistan","10,997,885","34.02","7.7","South Asia"

Generate Array

If you want the results, so you can do further operations, use the generateArray method.

(It is safe to ignore the legacy generateArray, as it will be removed in future versions)

utils.table(
    utils.group.by(gapMinder, 'country').get('Afghanistan')
  )

  //-- add new field / column
  .augment({
    continent: (r) => clusterMap.get(r.cluster).continent
  })

  .limit(3)

  //-- labels (property: label name)
  .labels({ pop: 'population', life_expect: 'life expectancy'})

  //-- format a specific value, say a year to a String
  .formatter({
    // property: formattingFunction
    life_expect: (val) => String(val),
    
    //-- alternatively, you can convert to 'Number', 'String' or 'Boolean'
    year: 'string'
  })

  //-- explicitly exclude a specific column
  .columnsToExclude(['cluster'])

  //-- or you could explicitly set the columns and order
  // .columns(['year', 'continent', 'country', 'pop', 'life_expect', 'fertility'])

  .generateArray2();
[
  [
    'year',
    'country',
    'population',
    'life expectancy',
    'fertility',
    'continent'
  ],
  [ '1955', 'Afghanistan', 8891209, '30.332', 7.7, 'South Asia' ],
  [ '1960', 'Afghanistan', 9829450, '31.997', 7.7, 'South Asia' ],
  [ '1965', 'Afghanistan', 10997885, '34.02', 7.7, 'South Asia' ]
]