Changelog
All notable changes to this library will be documented in this file.
The format is based on Keep a Changelog and this project adheres to Semantic Versioning.
Unreleased
1.1.1 - 2023-08-01
- PR #133: Add guards to some usages of
phyloref.internalSpecifiers/phyloref.externalSpecifiers
. - PR #136: Improved GitHub test action, including changing Node versions.
- PR #127: Upgraded Newick.js to ^1.2.1. This means that we no longer support Node.js v10.x, which end-of-lifed on April 30, 2021.
1.1.0 - 2023-05-11
- PR #129: Added curator notes to phylorefs and phylogenies.
- PR #128: Added citation to README and a CITATION.CFF file.
- PR #125: Added curator information as contributors to Phyx file.
1.0.1 - 2021-10-15
- Created a tutorial for using phyx.js, including:
- PR #99: Add tutorial.
- PR #103: Improve integration of the tutorial.
- PR #109: Add resolve.js instructions to Introduction tutorial.
- Improvements to README files, including:
- PR #108: Added links to published JSON-LD contexts and JSON schemas.
- PR #110: Added note that Binder cannot be used.
- PR #111: Changes link to tutorials to directory.
- PR #106: Fixed typo in resolve.js.
- Updated timeout to 60s as 20s was timing out on GitHub Actions.
- Updated NPM packages and resorted packages in package.json.
1.0.0 - 2021-03-16
- Many changes across the entire library.
- Two new scripts:
bin/phyx2owl.js
for converting a Phyx file into OWL/JSON-LD andbin/resolve.js
to resolve a Phyx file on the Open Tree of Life. - Incorporation of JPhyloRef into testing.
- Fixed a bug in which phyloref and phylogeny
@id
values were being overwritten when generating JSON-LD. - Added scripts to generate every possible topology from n=2 to n=6 with expected resolution and to test them with JPhyloRef.
0.2.1 - 2019-08-15
- Updated all NPM packages to their latest version.
- Fixed a bug in which node's types were not being correctly set to obo:CDAO_0000140.
0.2.0 - 2019-07-18
Changed
- The single index.js has been split into multiple files, one per class, with documentation using ESdocs (#21).
- Added support for generating "model 2.0" ontologies, which can be reasoned over in an OWL 2 EL reasoner such as Elk (#4). As part of this change, specifiers have been changed so that they are taxonomic units, rather than containing taxonomic units and a new Phyx context file has been created (#19).
- Taxonomic units have been cleaned up, are clearly typed, and taxonomic names now include nomenclatural codes (#18) and support trinomial names (#22).
0.1.2 - 2019-02-08
Added
- Moved Phyx context file for JSON-LD into this repository from the Curation Tool and changed URI to point to it.
0.1.1 - 2019-02-06
Added
- A replacement for the Phylotree.js Newick parser.
0.1.0 - 2019-01-27
Added
- Transfered initial code from the Phyloreference Curation Tool. The initial release of this package was based on commit 14d2c3d5d1 in that repository.
Changed
Introduction to phyx.js
Written by Gaurav Vaidya. Last updated August 24, 2021
This tutorial provides an introduction to the phyx.js library, and shows you how it can be used to read a Phyx file, check it for validity, examine phyloreferences, phylogenies and specifiers, and describe how to convert the file into RDF.
Starting with Phyx files
Phyx files are digitized clade definitions in a JSON-LD format. While you can use an editor like Klados to create Phyx files, you can also write one by yourself as a JSON document.
var alligatoridae_brochu2003 = {
"@context": "https://www.phyloref.org/phyx.js/context/v1.0.0/phyx.json",
// Phylogeny from Brochu 2003: https://doi.org/10.1146/annurev.earth.31.100901.141308
"phylogenies": [{
"newick": "(Parasuchia,(rauisuchians,Aetosauria,(sphenosuchians,(protosuchians,(mesosuchians,(Hylaeochampsa,Aegyptosuchus,Stomatosuchus,(Allodaposuchus,('Gavialis gangeticus',(('Diplocynodon ratelii',('Alligator mississippiensis','Caiman crocodilus')Alligatoridae)Alligatoroidea,('Tomistoma schlegelii',('Osteolaemus tetraspis','Crocodylus niloticus')Crocodylinae)Crocodylidae)Brevirostres)Crocodylia))Eusuchia)Mesoeucrocodylia)Crocodyliformes)Crocodylomorpha));",
"source": {
"type": "article",
"title": "Phylogenetic approaches toward crocodylian history",
"authors": [{
"firstname": "Christopher",
"middlename": "A.",
"lastname": "Brochu"
}],
"year": 2003,
"figure": "1",
"identifier": [{
"type": "doi",
"id": "10.1146/annurev.earth.31.100901.141308"
}],
"journal": {
"name": "Annual Review of Earth and Planetary Sciences",
"volume": "31",
"pages": "357--397",
"identifier": [{ "type": "eISSN", "id": "1545-4495" }]
}
}
}],
// Clade definition from Brochu 2003: Alligatoridae
"phylorefs": [{
"label": "Alligatoridae",
"scientificNameAuthorship": { "bibliographicCitation": "(Cuvier 1807)" },
"phylorefType": "phyloref:PhyloreferenceUsingMinimumClade",
"definition": "Alligatoridae (Cuvier 1807).\n\nLast common ancestor of Alligator mississippiensis and Caiman crocodilus and all of its descendents.",
"definitionSource": {
"bibliographicCitation": "Brochu (2003) Phylogenetic approaches toward crocodylian history. Annual Review of Earth and Planetary Sciences 31:1, 357-397. doi: 10.1146/annurev.earth.31.100901.141308"
},
"internalSpecifiers": [
{
"@type": "http://rs.tdwg.org/ontology/voc/TaxonConcept#TaxonConcept",
"hasName": {
"@type": "http://rs.tdwg.org/ontology/voc/TaxonName#TaxonName",
"nomenclaturalCode": "http://rs.tdwg.org/ontology/voc/TaxonName#ICZN",
"label": "Caiman crocodilus Linnaeus, 1758",
"nameComplete": "Caiman crocodilus",
"genusPart": "Caiman",
"specificEpithet": "crocodilus"
}
}, {
"@type": "http://rs.tdwg.org/ontology/voc/TaxonConcept#TaxonConcept",
"hasName": {
"@type": "http://rs.tdwg.org/ontology/voc/TaxonName#TaxonName",
"nomenclaturalCode": "http://rs.tdwg.org/ontology/voc/TaxonName#ICZN",
"label": "Alligator mississippiensis (Daudin, 1802)",
"nameComplete": "Alligator mississippiensis",
"genusPart": "Alligator",
"specificEpithet": "mississippiensis"
}
}
]
}]
}
Validating a Phyx document using JSON Schema
We publish a JSON Schema with phyx.js, which can be used to validate that a Phyx document is correctly formed. We use Ajv, a JSON Schema validator for JavaScript. Note that we use the copy of the context file that is included in this repository, but you can also download it from our website.
var fs = require('fs');
var Ajv = require('ajv');
// Configure Ajv.
var ajv = new Ajv({
allErrors: true, // Display all error messages, not just the first.
});
// We use the JSON Schema included with this repository, but you can download the
// Phyx JSON Schema from https://www.phyloref.org/phyx.js/context/v1.0.0/schema.json
var validator = ajv.compile(JSON.parse(fs.readFileSync('../docs/context/v1.0.0/schema.json')));
// Attempt to validate the Brochu 2003 example file.
var result = validator(alligatoridae_brochu2003);
console.log(`Is alligatoridae_brochu2003 valid? ${result}`);
console.log('Errors:', validator.errors || 'none');
// Let's make an invalid copy of the Brochu 2003 example file to make sure this is working.
var alligatoridae_brochu2003copy = {...alligatoridae_brochu2003};
delete alligatoridae_brochu2003copy['@context']; // @context is required for successful validation
var result = validator(alligatoridae_brochu2003copy);
console.log(`Is alligatoridae_brochu2003copy valid? ${result}`);
console.log('Errors:', validator.errors);
Is alligatoridae_brochu2003 valid? true
Errors: none
Is alligatoridae_brochu2003copy valid? false
Errors: [
{
keyword: 'required',
dataPath: '',
schemaPath: '#/required',
params: { missingProperty: '@context' },
message: "should have required property '@context'"
}
]
Examining phyloreferences, taxonomic units and taxon concepts
The phyx.js library was built in order to simplify the process of working with individual components of Phyx documents, and to facilitate the conversion of a Phyx document into OWL. The library consists of a series of wrappers, each of which wraps part of the document. For example, we can wrap every specifier that is a taxonomic unit using the TaxonomicUnitWrapper.
This provides a number of convenience methods: for example, .internalSpecifiers
and .externalSpecifiers
will always return lists, whether or not these are defined in the underlying phyloreference (if undefined, the methods return empty lists). There is also a .specifiers
method that lists both internal and external specifiers.
Furthermore, taxonomic units that are taxon concepts can be wrapped by a TaxonConceptWrapper, which have methods for accessing the "complete name" (i.e. the monomial, binomial or trinomial name) and the nomenclatural code.
// Load the Phyx library.
var phyx = require('..');
// List all the phyloreferences along with their specifiers.
alligatoridae_brochu2003.phylorefs.forEach(phyloref => {
let wrappedPhyloref = new phyx.PhylorefWrapper(phyloref);
console.log(wrappedPhyloref.label);
// Extract the "complete name" and the nomenclatural code short name for each specifier that is a taxonomic unit.
wrappedPhyloref.internalSpecifiers.forEach(specifier => {
let wrappedSpecifier = new phyx.TaxonomicUnitWrapper(specifier);
if (wrappedSpecifier.taxonConcept) {
let wrappedTaxonConcept = new phyx.TaxonConceptWrapper(wrappedSpecifier.taxonConcept);
console.log(` - Internal: ${wrappedTaxonConcept.nameComplete} (${wrappedTaxonConcept.nomenCodeDetails.shortName})`);
} else {
console.log(` - Internal: ${wrappedSpecifier.label}`);
}
});
// Note that the phyloref doesn't have an `externalSpecifiers` key, but the wrapper provides it as an empty list
// for ease of use.
wrappedPhyloref.externalSpecifiers.forEach(specifier => {
let wrappedSpecifier = new phyx.TaxonomicUnitWrapper(specifier);
if (wrappedSpecifier.taxonConcept) {
let wrappedTaxonConcept = new phyx.TaxonConceptWrapper(wrappedSpecifier.taxonConcept);
console.log(` - External: ${wrappedTaxonConcept.nameComplete} (${wrappedTaxonConcept.nomenCodeDetails.shortName})`);
} else {
console.log(` - External: ${wrappedSpecifier.label}`);
}
});
console.log();
});
Alligatoridae
- Internal: Caiman crocodilus (ICZN)
- Internal: Alligator mississippiensis (ICZN)
Examining phylogenies
Phylogenies are stored in JSON files as Newick strings, but the PhylogenyWrapper can be used to look at internal and terminal node labels and to translate the Newick string into a JSON structure for use by downstream programs.
var phylogeny = alligatoridae_brochu2003.phylogenies[0];
console.log(`The phylogeny is represented by the Newick string: ${phylogeny.newick}`);
console.log();
// Display internal and external nodes.
var wrappedPhylogeny = new phyx.PhylogenyWrapper(phylogeny);
console.log(`This consists of the following nodes:\n - Internal nodes: ${wrappedPhylogeny.getNodeLabels('internal').join(', ')}`);
console.log(` - Terminal nodes: ${wrappedPhylogeny.getNodeLabels('terminal').join(', ')}`);
console.log();
// Convert the Newick string into a JSON structure for examination.
console.log(`Newick string as a JSON structure: ${JSON.stringify(phyx.PhylogenyWrapper.getParsedNewick(phylogeny.newick), undefined, 2)}`);
console.log();
The phylogeny is represented by the Newick string: (Parasuchia,(rauisuchians,Aetosauria,(sphenosuchians,(protosuchians,(mesosuchians,(Hylaeochampsa,Aegyptosuchus,Stomatosuchus,(Allodaposuchus,('Gavialis gangeticus',(('Diplocynodon ratelii',('Alligator mississippiensis','Caiman crocodilus')Alligatoridae)Alligatoroidea,('Tomistoma schlegelii',('Osteolaemus tetraspis','Crocodylus niloticus')Crocodylinae)Crocodylidae)Brevirostres)Crocodylia))Eusuchia)Mesoeucrocodylia)Crocodyliformes)Crocodylomorpha));
This consists of the following nodes:
- Internal nodes: Alligatoridae, Alligatoroidea, Crocodylinae, Crocodylidae, Brevirostres, Crocodylia, Eusuchia, Mesoeucrocodylia, Crocodyliformes, Crocodylomorpha
- Terminal nodes: Parasuchia, rauisuchians, Aetosauria, sphenosuchians, protosuchians, mesosuchians, Hylaeochampsa, Aegyptosuchus, Stomatosuchus, Allodaposuchus, Gavialis gangeticus, Diplocynodon ratelii, Alligator mississippiensis, Caiman crocodilus, Tomistoma schlegelii, Osteolaemus tetraspis, Crocodylus niloticus
Newick string as a JSON structure: {
"json": {
"children": [
{
"children": [
{
"label": "Crocodylomorpha",
"children": [
{
"label": "Crocodyliformes",
"children": [
{
"label": "Mesoeucrocodylia",
"children": [
{
"label": "Eusuchia",
"children": [
{
"children": [
{
"label": "Crocodylia",
"children": [
{
"label": "Brevirostres",
"children": [
{
"label": "Crocodylidae",
"children": [
{
"label": "Crocodylinae",
"children": [
{
"label": "Crocodylus niloticus",
"name": "Crocodylus niloticus"
},
{
"label": "Osteolaemus tetraspis",
"name": "Osteolaemus tetraspis"
}
],
"name": "Crocodylinae"
},
{
"label": "Tomistoma schlegelii",
"name": "Tomistoma schlegelii"
}
],
"name": "Crocodylidae"
},
{
"label": "Alligatoroidea",
"children": [
{
"label": "Alligatoridae",
"children": [
{
"label": "Caiman crocodilus",
"name": "Caiman crocodilus"
},
{
"label": "Alligator mississippiensis",
"name": "Alligator mississippiensis"
}
],
"name": "Alligatoridae"
},
{
"label": "Diplocynodon ratelii",
"name": "Diplocynodon ratelii"
}
],
"name": "Alligatoroidea"
}
],
"name": "Brevirostres"
},
{
"label": "Gavialis gangeticus",
"name": "Gavialis gangeticus"
}
],
"name": "Crocodylia"
},
{
"label": "Allodaposuchus",
"name": "Allodaposuchus"
}
]
},
{
"label": "Stomatosuchus",
"name": "Stomatosuchus"
},
{
"label": "Aegyptosuchus",
"name": "Aegyptosuchus"
},
{
"label": "Hylaeochampsa",
"name": "Hylaeochampsa"
}
],
"name": "Eusuchia"
},
{
"label": "mesosuchians",
"name": "mesosuchians"
}
],
"name": "Mesoeucrocodylia"
},
{
"label": "protosuchians",
"name": "protosuchians"
}
],
"name": "Crocodyliformes"
},
{
"label": "sphenosuchians",
"name": "sphenosuchians"
}
],
"name": "Crocodylomorpha"
},
{
"label": "Aetosauria",
"name": "Aetosauria"
},
{
"label": "rauisuchians",
"name": "rauisuchians"
}
]
},
{
"label": "Parasuchia",
"name": "Parasuchia"
}
]
}
}
Accessing citations
Another example of a wrapper that can be used for wrapping a part of a Phyx file is the CitationWrapper. This can be used to wrap citations anywhere in the Phyx file to provide a full bibliographic citation for the citation.
var wrappedPhylogenyCitation = new phyx.CitationWrapper(alligatoridae_brochu2003.phylogenies[0].source);
console.log(`The source of the phylogeny in this Phyx document is: ${wrappedPhylogenyCitation.toString()}`);
var wrappedPhylorefCitation = new phyx.CitationWrapper(alligatoridae_brochu2003.phylorefs[0].definitionSource);
console.log(`The definition source of the phyloreference in this Phyx document is: ${wrappedPhylorefCitation.toString()}`);
The source of the phylogeny in this Phyx document is: Christopher A. Brochu (2003) Phylogenetic approaches toward crocodylian history Annual Review of Earth and Planetary Sciences 31:357--397 fig 1 doi: 10.1146/annurev.earth.31.100901.141308
The definition source of the phyloreference in this Phyx document is: Brochu (2003) Phylogenetic approaches toward crocodylian history. Annual Review of Earth and Planetary Sciences 31:1, 357-397. doi: 10.1146/annurev.earth.31.100901.141308
Converting a Phyx document into OWL
A Phyx document (which is in JSON-LD format) can be converted into OWL/RDF in the form of N-Quads by using the PhyxWrapper
to wrap the entire Phyx document. A base URL can be specified.
nQuads = new phyx.PhyxWrapper(alligatoridae_brochu2003).toRDF('http://example.org/phyx-tutorial#');
nQuads.then(nq => console.log(nq.slice(0, 2500) + '...'));
<http://example.org/phyx-tutorial#phylogeny0> <http://ontology.phyloref.org/phyloref.owl#newick_expression> "(Parasuchia,(rauisuchians,Aetosauria,(sphenosuchians,(protosuchians,(mesosuchians,(Hylaeochampsa,Aegyptosuchus,Stomatosuchus,(Allodaposuchus,('Gavialis gangeticus',(('Diplocynodon ratelii',('Alligator mississippiensis','Caiman crocodilus')Alligatoridae)Alligatoroidea,('Tomistoma schlegelii',('Osteolaemus tetraspis','Crocodylus niloticus')Crocodylinae)Crocodylidae)Brevirostres)Crocodylia))Eusuchia)Mesoeucrocodylia)Crocodyliformes)Crocodylomorpha));" .
<http://example.org/phyx-tutorial#phylogeny0> <http://purl.obolibrary.org/obo/CDAO_0000148> <http://example.org/phyx-tutorial#phylogeny0_node0> .
<http://example.org/phyx-tutorial#phylogeny0> <http://purl.org/dc/terms/source> _:b165 .
<http://example.org/phyx-tutorial#phylogeny0> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://ontology.phyloref.org/phyloref.owl#ReferencePhylogenyEvidence> .
<http://example.org/phyx-tutorial#phylogeny0> <http://www.w3.org/2000/01/rdf-schema#isDefinedBy> _:b0 .
<http://example.org/phyx-tutorial#phylogeny0_node0> <http://purl.obolibrary.org/obo/CDAO_0000149> <http://example.org/phyx-tutorial#phylogeny0_node1> .
<http://example.org/phyx-tutorial#phylogeny0_node0> <http://purl.obolibrary.org/obo/CDAO_0000149> <http://example.org/phyx-tutorial#phylogeny0_node29> .
<http://example.org/phyx-tutorial#phylogeny0_node0> <http://purl.obolibrary.org/obo/CDAO_0000200> <http://example.org/phyx-tutorial#phylogeny0> .
<http://example.org/phyx-tutorial#phylogeny0_node0> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <http://purl.obolibrary.org/obo/CDAO_0000140> .
<http://example.org/phyx-tutorial#phylogeny0_node10> <http://ontology.phyloref.org/phyloref.owl#has_Sibling> <http://example.org/phyx-tutorial#phylogeny0_node13> .
<http://example.org/phyx-tutorial#phylogeny0_node10> <http://purl.obolibrary.org/obo/CDAO_0000149> <http://example.org/phyx-tutorial#phylogeny0_node11> .
<http://example.org/phyx-tutorial#phylogeny0_node10> <http://purl.obolibrary.org/obo/CDAO_0000149> <http://example.org/phyx-tutorial#phylogeny0_node12> .
<http://example.org/phyx-tutorial#phylogeny0_node10> <http://purl.obolibrary.org/obo/CDAO_0000179> <http://example.org/phyx-tutorial#phylogeny0_node9> .
<http://example.org/phyx-tutorial#phylogeny0_node10> <http://purl.obolibrary.org/obo/CDAO_0000187> _:b50 .
<http://example.org/phyx-tutorial#phylogeny0_node10> <http://purl.obolibrary.org/obo/CDAO_00002...
// We can convert these N-Quads into RDF/Turtle for easier interpretation.
// We only convert a few N-Quads as a demonstration.
var N3 = require('n3');
var { Readable } = require("stream")
nQuads.then(nq => {
someNqs = nq.slice(0, 1071)
var streamParser = new N3.StreamParser(),
inputStream = Readable.from([someNqs]),
streamWriter = new N3.StreamWriter();
inputStream.pipe(streamParser);
streamParser.pipe(streamWriter);
streamWriter.pipe(process.stdout);
});
undefined;
Navigating a Phyx document as a JSON file
Most phyx.js wrappers have been designed to help interpret the more complex parts of a Phyx file, such as the phyloreferences, specifiers, phylogenies, citations and the entire Phyx document. However, since every Phyx document is also a JSON document, much of the information in the Phyx document can be accessed sufficiently easily using standard JSON libraries. In some cases, as in the demonstration below, this requires more complex code than would be necessary by using the phyx.js wrappers.
// List all the phylorefs in a Phyx document.
alligatoridae_brochu2003copy.phylorefs.forEach((phyloref, index) => {
console.log(`- Phyloref ${index + 1}. ${phyloref.label}:`);
(phyloref.internalSpecifiers || []).forEach(specifier => {
console.log(` - Internal specifier: ${(specifier.hasName || {}).nameComplete}`);
});
(phyloref.externalSpecifiers || []).forEach(specifier => {
console.log(` - External specifier: ${(specifier.hasName || {}).nameComplete}`);
});
console.log();
});
<http://example.org/phyx-tutorial#phylogeny0> <http://ontology.phyloref.org/phyloref.owl#newick_expression> "(Parasuchia,(rauisuchians,Aetosauria,(sphenosuchians,(protosuchians,(mesosuchians,(Hylaeochampsa,Aegyptosuchus,Stomatosuchus,(Allodaposuchus,('Gavialis gangeticus',(('Diplocynodon ratelii',('Alligator mississippiensis','Caiman crocodilus')Alligatoridae)Alligatoroidea,('Tomistoma schlegelii',('Osteolaemus tetraspis','Crocodylus niloticus')Crocodylinae)Crocodylidae)Brevirostres)Crocodylia))Eusuchia)Mesoeucrocodylia)Crocodyliformes)Crocodylomorpha));";
<http://purl.obolibrary.org/obo/CDAO_0000148> <http://example.org/phyx-tutorial#phylogeny0_node0>;
<http://purl.org/dc/terms/source> _:b0_b165;
a <http://ontology.phyloref.org/phyloref.owl#ReferencePhylogenyEvidence>;
<http://www.w3.org/2000/01/rdf-schema#isDefinedBy> _:b0_b0.
- Phyloref 1. Alligatoridae:
- Internal specifier: Caiman crocodilus
- Internal specifier: Alligator mississippiensis
Looking up phyloreferences on the Open Tree of Life
An included script, resolve.js
, can be used to resolve a phyloreference on the Open Tree of Life. This can be executed from the command line by running:
$ npm run resolve test/examples/correct/brochu_2003.json
However, this script can also be invoked from within Node.js.
var child_process = require('child_process');
child = child_process.spawnSync('../bin/resolve.js', ['../test/examples/correct/brochu_2003.json'], {
encoding: 'utf-8',
stdio: 'pipe',
});
results = JSON.parse(child.output.join('\n'));
Object.keys(results).forEach(key => {
console.log(`- ${key}:`);
values = results[key];
values.forEach(value => {
if('status' in value) {
resolved = value['resolved'];
console.log(` - Resolved ${value['cladeType']} clade-type phylorefence`);
console.log(` to: ${resolved['@id']} (label: ${resolved['label']})`);
} else if ('error' in value) {
console.log(` - Could not resolve: ${value['error']}`);
} else {
console.log(` - Unable to interpret: ${JSON.stringify(value, undefined, 2)}`);
}
})
});
- Alligatoridae:
- Resolved minimum phylorefence
to: https://tree.opentreeoflife.org/opentree/argus/opentree13.4@ott195670 (label: Alligatoridae)
- Alligatorinae:
- Resolved maximum phylorefence
to: https://tree.opentreeoflife.org/opentree/argus/opentree13.4@ott151255 (label: Alligatorinae)
- Caimaninae:
- Resolved maximum phylorefence
to: https://tree.opentreeoflife.org/opentree/argus/opentree13.4@ott195671 (label: Caimaninae)
- Crocodyloidea:
- Resolved maximum phylorefence
to: https://tree.opentreeoflife.org/opentree/argus/opentree13.4@ott335582 (label: Crocodylidae)
- Crocodylidae:
- Resolved minimum phylorefence
to: https://tree.opentreeoflife.org/opentree/argus/opentree13.4@ott1092501 (label: Longirostres)
- Diplocynodontinae:
- Could not resolve: no_mrca_found:400
About this notebook
This document was created as a Jupyter Notebook, and the source file is available in our GitHub repository. We recommend installing Jupyterlab via Homebrew on Mac, but other installation options are available. Once Jupyter Notebook is set up, you should be able to open this notebook for editing by running jupyter notebook Introduction\ to\ phyx.js.ipynb
from the command line.
We use IJavascript to use Javascript as a kernel in Jupyter Notebook. If you would like to edit this notebook, you will need to install this as well.