11 January 2009

More phylogeny-related questions that can be written as SQL

Pursuant to my last post (Using SQL to make phylogenetic queries), I've started to dream up some other questions that Names on Nodes will be able to answer:

"What are all dinosaur species that lived during the Maastrichtian Age?"


Possible SQL:

SELECT dinosaur.name
FROM node_clade(ARRAY(
SELECT *
FROM resolve_signifier_identities(ARRAY[
'urn:isbn:0853010064::Hylaeosaurus_armatus',
'urn:isbn:0853010064::Iguanodon_bernissartensis',
'urn:isbn:0853010064::Megalosaurus_bucklandii'
])
), 0) AS dinosaur_identity
JOIN signifier AS dinosaur
ON dinosaur.identity_id = dinosaur_identity
JOIN signifierannotation
ON dinosaur.id = signifierannotation.signifier_id
AND signifierannotation.role
= 'http://namesonnodes.org/quantities::years_bp'
JOIN quantity AS years_bp
ON signifierannotation.id = years_bp.id
JOIN signifier_categories
ON signifier_categories.signifier_id = dinosaur.id
WHERE years_bp.value <= 70600000
AND years_bp.value >= 65500000
AND signifier_categories.element = 'species'
GROUP BY dinosaur.name
ORDER BY dinosaur.name;

"What are the largest named subtaxa of Angiospermae?"


Possible SQL:

SELECT angiosperm.name
FROM coarsest(ARRAY(
SELECT * FROM subsets(
resolve_signifier_identity(
'urn:isbn:00800694/146::Angiospermae'),
, 0)
EXCEPT
SELECT resolve_signifier_identity(
'urn:isbn:00800694/146::Angiospermae')
), 0) AS angiosperm_identity
JOIN signifier AS angiosperm
ON angiosperm.identity_id = angiosperm_identity
GROUP BY angiosperm.name
ORDER BY angiosperm.name;

"Which catalogued specimens represent stem-mammals?"


Possible SQL:

SELECT catalogue.name, specimen.name
FROM signifier AS specimen
JOIN authority
ON specimen.authority_id = authority.id
JOIN authorityidentity AS catalogue
ON authority.identity_id = catalogue.id
JOIN signifier_categories
ON signifier_categories.signifier_id = specimen.id
AND signifier_categories.element = 'specimen'
WHERE specimen.identity_id = ANY(
SELECT total(
ARRAY(SELECT
resolve_signifier_identity(
'urn:isbn:0853010064::Mammalia')),
ARRAY(SELECT
resolve_signifier_identity(
'urn:doi:10.1051/jp4:20020447::Anthropocene')),
0)
EXCEPT
SELECT subsets(
ARRAY(SELECT resolve_signifier_identity(
'urn:isbn:0853010064::Mammalia')),
0)
)
GROUP BY catalogue.name, specimen.name
ORDER BY catalogue.name, specimen.name;

"How many anseriform species are flightless?"


Possible SQL:

SELECT COUNT(anseriform.identity_id)
AS flightless_anseriform_count
FROM signifier AS anseriform
JOIN signifier_categories
ON signifier_categories.signifier_id = anseriform.id
AND signifier_categories.element = 'species'
WHERE anseriform.identity_id = ANY(
SELECT * FROM subsets(ARRAY(
SELECT resolve_signifier_identity(
'urn:isbn:0853010064::Anseriformes')
),
0)
)
AND anseriform.identity_id = ANY(
SELECT * FROM subsets(ARRAY(
SELECT resolve_signifier_identity(
'biofile:9A75AD84C56B43AD681991EBA56D8B4E6E36B311'
|| '::data/flight/absent')
),
0)
);


Many more possibilities. Note that the "0" in the queries above means "use all available information". Other numbers can be used to restrict information to certain datasets.

08 January 2009

Using SQL to make phylogenetic queries

"Computer, can any dinosaurs fly?"

It would be great if we could ask computers questions this easily. Unfortunately, computers lack our natural linguistic skills. But this doesn't mean they are incapable of answering such questions—the questions just need to be rephrased.

SQL (pronounced either as "S.Q.L." or "sequel", and standing for "Structured Query Language") is the most commonly-used language for querying relational databases. This past week I have been adapting the phylogenetic operations in my 2007 paper into SQL (specifically the PostgreSQL version) as part of my Names on Nodes project. There have been a few difficulties to do with the fact that the database only records taxa (sets of organisms), while my published algorithms work at the organism level. But this is a minor difficulty so far, and things seem to be working.

To get an idea of how the system works, let's look at the question posited at the beginning of this post. How can the question be translated into a SQL query?

First, a quick summary of what's in the database: taxa (sets of organisms) of all kinds are represented by a table of "signifiers". Each signifier references an "authority", which could be anything from a publication to a nomenclatural code to a systematics dataset to a personal opinion. Each signifier also has a name which is unique under that authority. For example, under the authority of the International Code of Zoological Nomenclature (ICZN for short), the name "Homo sapiens" signifies a particular taxon (a species). Under the authority of the Yale Peabody Museum's Vertebrate Paleontology collection, the name "1450" refers to a specimen, and by proxy the set of organisms (in this case, just one) which that specimen represents. Under the authority of a particular NEXUS file, "data/flight/present" may refer to a character state, and by proxy the set of all organisms exhibiting that state.

Each authority is represented by one or more Universal Resource Identifiers, or URIs. Website addresses (URLs) are one kind of URI, so, for example, the Yale Peabody Museum's Vertebrate Paleontology collection can be represented as http://www.peabody.yale.edu/collections/vp. ISBN numbers are another type of URI, so the ICZN can be represented as urn:isbn:0853010064. Systematics files are a little trickier; my current solution is to use a custom schema and a unique "hash" string of characters programmatically derived from the data in the file, e.g., something like biofile:08192A3 (actually longer, but I'll keep it short for this discussion).

Each signifier can be represented as a "qualified name" (or "QName"), which combines the authority's URI and the signifier's name. Examples:
  • http://www.peabody.yale.edu/collections/vp::1450
  • urn:isbn:0853010064::Homo_sapiens
  • biofile:08192A3::data/flight/present
(A fuller discussion of the Names on Nodes database can be found in this earlier post.)

Definitions may be stored as MathML, as indicated in my 2007 paper. Let's suppose that someone defines Dinosauria as, "the final common ancestor of Megalosaurus bucklandii von Meyer 1832, Hylaeosaurus armatus Mantell 1833, and Iguanodon bernissartensis Boulenger in Beneden 1881, plus all descendants of that ancestor." This can be rendered as:

<apply xmlns="http://www.w3.org/1998/Math/MathML">
<csymbol definitionURL="http://namesonnodes.org/phylo/math​::nodeClade"/>
<csymbol definitionURL="urn:isbn:0853010064​::Hylaeosaurus_armatus"/>
<csymbol definitionURL="urn:isbn:0853010064​::Iguanodon_bernissartensis"/>
<csymbol definitionURL="urn:isbn:0853010064​::Megalosaurus_bucklandii"/>
</apply>

Basically, this means, "Apply the nodeClade function to the the union of the signified taxa." The nodeClade function corresponds to the phrase, "the final common ancestors and all descendants thereof". It relies on three other phylogenetic functions: allDescendants, maximal, and commonAncestors. ("Maximal" in this context means "all members of a set which are not ancestral to any other members in that set"—a more precise way to say "final".)

Names on Nodes will take a MathML definition like this and convert it into a SQL query like this:
  SELECT * FROM node_clade(
ARRAY(SELECT * FROM resolve_signifier_identities(
ARRAY[
'urn:isbn:0853010064::Hylaeosaurus_armatus',
'urn:isbn:0853010064::Iguanodon_bernissartensis',
'urn:isbn:0853010064::Megalosarus_bucklandii'
])),
0);
The resolve_signifier_identities function converts an array of qualified names into a set of identity numbers corresponding to signifiers, using a query like this:
  SELECT identity_id
FROM signifier
WHERE qname = ANY(qname_list);
This is a relatively simple SQL query. The grammar is fairly intuitive (at least for a computer language). The query for the node_clade function is far more complex, though. To discuss that, I'll have to go into the database structure a bit more.

A phylogenetic definition cannot be applied without some kind of phylogenetic hypothesis. Names on Nodes is capable of reading phylogenies encoded as Newick trees, including those in NEXUS files. Let's suppose someone uploaded a NEXUS file with this Newick tree in it:

((Iguano_bern, Hylaeo_arma), (Megalo_buck, (Tyrann_rex, (Anas_plat, Passer_dome))))

Let's suppose the URI for this file is biofile:08192A3. Then this file authorizes some new signifiers:
  • biofile:08192A3::taxa/Iguano_bern
  • biofile:08192A3::taxa/Hylaeo_arma
  • biofile:08192A3::taxa/Megalo_buck
  • biofile:08192A3::taxa/Tyrann_rex
  • biofile:08192A3::taxa/Anas_plat
  • biofile:08192A3::taxa/Passer_dome
These can be objectively equated with other signifiers:
  • urn:isbn:0853010064::Iguanodon_bernissartensis
  • urn:isbn:0853010064::Hylaeosaurus_armatus
  • urn:isbn:0853010064::Megalosaurus_bucklandii
  • urn:isbn:0853010064::Tyrannosaurus_rex
  • urn:isbn:0853010064::Anas_platyrhynchos
  • urn:isbn:0853010064::Passer_domesticus
(The last two could conceivably be equated with vernacular names from a field guide as well, e.g., urn:isbn:0679428518::Mallard and urn:isbn:0679428518::House_Sparrow, respectively) Equated signifiers share the same identity. This file also authorizes some less visible signifiers: the hypothetical ancestors, such as the ancestor of Iguano_bern and Hylaeo_arma, the ancestor of Anas_plat and Passer_dome, etc. Names on Nodes assigns these ancestors arbitrary, numerical names.

The phylogeny in the Newick tree can be represented as a set of arcs, each pointing from a parent signifier (or "head") to a child signifier (or "tail"):
  ~1 → ~2
~1 → ~3
~2 → Iguano_bern
~2 → Hylaeo_arma
~3 → Megalo_buck
~3 → ~4
~4 → Tyrann_rex
~4 → ~5
~5 → Anas_plat
~5 → Passer_dome
These get stored in the database as rows representing parentage relations. Similar relations are used for supersets and subsets (e.g., to indicate that Tyrannosaurus includes Tyrannosaurus rex). Phylogenetic functions, such as maximal, all_ancestors, and common_ancestors, utilize the data in these relations.

Some of these functions use another function called find_parentage_arcs to get a list of all parent-child relations which involve any of a given set of signifiers. For example, maximal uses a query like this:
  SELECT id
FROM signifieridentity
WHERE id = ANY(identity_list)
EXCEPT
SELECT arc.head
FROM find_parentage_arcs(identity_list, context_id)
AS arc;
In other words, select all signifier identities from a given list, except those which are the parent (head) in any parent-child arc whose parent and child are in that list. (The context_id variable specifies which datasets to use and which to ignore, but that's an essay unto itself.)

The node_clade function uses a query like this:
  SELECT * FROM all_descendants(ARRAY(
SELECT * FROM maximal(ARRAY(
SELECT * FROM common_ancestors(identity_list,
context_id)),
context_id)),
context_id);
In other words, select all descendants/members of all maximal members of the common ancestors of the specified signifiers. (Again, context_id specifies which datasets to use; a value of zero means that all datasets should be used.)

Running the Dinosauria SQL query mentioned earlier will now yield a set including all of the signifiers mentioned so far. In other words, according to the provided definition and the provided phylogenetic hypothesis, I. bernissartensis, H. armatus, M. bucklandii, T. rex, A. platyrhynchos, and P. domesticus are all members of Dinosauria (as are the hypothetical ancestors in the phylogenetic tree).

Suppose the aforementioned NEXUS file also had a character matrix including the character "flight". This would be scored as "present" in Anas_plat and Passer_dome. Thus, the table of inclusions would include these arcs:
  flight/present → Anas_plat
flight/present → Passer_dome
(Names on Nodes also extrapolates the arc flight/present → ~5, but, again, that's another essay.)

A function called subsets retrieves all signifiers indicated as subsets of a given signifier. Thus, the question that began this essay can be written in SQL as:
  SELECT COUNT(dinosaur) > 0 AS verdict
FROM node_clade(
ARRAY(SELECT * FROM resolve_signifier_identities(
ARRAY[
'urn:isbn:0853010064::Hylaeosaurus_armatus',
'urn:isbn:0853010064::Iguanodon_bernissartensis',
'urn:isbn:0853010064::Megalosarus_bucklandii'
])),
0) AS dinosaur
JOIN subsets(
resolve_signifier_identity(
'biofile:08192A3::data/flight/present'
),
0) AS flier ON dinosaur = flier;
The verdict: true.

16 December 2008

My Yuletide Gift: Open Source ActionScript 3.0 Libraries!

I finally got around to setting up a Google Code site:

A Three-Pound Monkey Brain: ActionScript 3.0 Libraries

As you can see, there is still much to be done. But it's a start, and it's available to anyone.

Merry Solsticetime, everyone!

10 December 2008

Uncle Sam wants YOU!

In case you all were curious about what I actually do for a living, this just launched:


This is a project I worked on with several other developers. (Well, I still am working on it—there are a couple of revisions we'll be rolling out before too long.) I also did some work on the main Army Experience Center website.

I believe this is the first project I've worked on involving Papervision3D that has made its way online. Hoo-ah!

22 November 2008

One Mo'

Here's all 10 MPTs (most parsimonious trees) from Yates & Kitching (2003) combined into one network:


Hmm, just noticed how reminiscent these are of Charley Harper's work....

20 November 2008

Names on Nodes: Viewing Phylogenies

One of the primary uses for Names on Nodes will be as a viewer for phylogenetic hypotheses. The first incarnation will be able to digest hypotheses from NEXUS files; later versions will allow other file formats and ad hoc creation. I've been working on this aspect of the application lately and thought I'd post a few screen shots. (Warning: This is not the final look. I am definitely going to use a more legible font!)

Here's a very simple tree from a study on carnivoran phylogeny using supertrees (Bininda-Emonds et al. 1999):


(Note: the feliform side is a little outdated by now.)

This is a directed acyclic graph (and specifically a tree) rendered using the Flare ActionScript library, an extremely powerful (if sometimes frustrating) data visualization tool. It's based on the only tree in that particular NEXUS file. But what if a NEXUS file has more than one tree? This is where it gets kind of neat (I think)—Names on Nodes can show multiple hypotheses at once!

Here's a combination of two hypotheses from Novacek's (1989) study of mammalian phylogeny:


(Note: Much of this was changed by the subsequent molecular revolution in eutherian phylogeny.)

The NEXUS file has one tree based on extant organisms and another with some extinct taxonomic units. This network represents the phylogeny if both trees were true. Obviously, it shouldn't be taken too literally—there's no way carnivorans are descended from a hybridization of two disparate lineages, for example. But it shows a sort of consensus, and can be used for the application of phylogenetic definitions.

For the more botanically-inclined, here's a similar network for Rodman et al.'s (1984) analysis of Centrospermae:


(I have no idea how outdated this one is.)

Another thing that can be done in Names on Nodes is linking phylogenies together. For the aforementioned study by Bininda-Emonds et al. there is another NEXUS file focusing on Canidae. A user could open both files, drag the root node of the Canidae phylogeny to the terminal Canidae node in the Carnivora phylogeny, and equate the units with each other. This would produce something like this (cropped):


The root node in this phylogeny, then, could be equated with the Carnivora node in Novacek's phylogeny, and so on. In this way, gigantic networks can be compiled, representing the complete Tree of Life. Outdated hypotheses can be filtered to taste.

Next step: automated taxonomy.

Next next step: relating anything and everything to the Tree of Life.

10 November 2008

Names on Nodes: Entities

Here's a UML diagram of the latest class schema for the core Names on Nodes entities:

The white arrows indicate inheritance, i.e., "is-a" relationships. For example, a PhyloDefinition is a type of Definition. The black diamonds indicate composition, i.e., "has" relationships. For example, a Definition has any number of Anchor entities, each of which has exactly one Signifier entity.

Some comments on the major classes of entity:

Signifier.—This is what everything revolves around. A Signifier signifies a set of organisms, that is, a taxon. Signifier entities may be scientific names, specimens, character states, or taxonomic units in systematic studies.

Several Signifier entities may share the same SignifierIdentity, indicating that they are different ways of referring to the exact same thing. For example, Felis leo (ICZN) and Panthera leo (ICZN) are objective synonyms. (Subjective synonyms do not share the same identity.)

Authority.—Every Signifier is unique within an Authority. Authority entities may be publications, nomenclatural codes, personal opinions, specimen repositories, or bioinformatics files. Every Authority is associated with a unique URI (e.g., a web address, a DOI, an ISBN number, etc.).

Like Signifier entities, different Authority entities may share an identity (AuthorityIdentity). These Identity entities are hidden from other entities, so that Authority and Signifier entities can be equated or differentiated without affecting other entities in the database.

Relator.—A Relator is a set of Relation entities, each of which represent a statement about two Signifier entities, either Inclusion (i.e., a is a superset of b) or Parentage (i.e., a is immediately ancestral to b).

Definition.—A Definition defines a Signifier according to an Authority, and may have any number of Anchor entities, each of which tells whether a given Signifier is objectively a subset of the defined Signifier.

RankDefinition.—A RankDefinition consists of a rank and some number of internal Anchor entities. For example, under the ICZN (an Authority), Hominidae (a Signifier) is defined as the family (a rank) typified by Homo (a Signifier referenced by an internal Anchor).

PhyloDefinition.—A PhyloDefinition consists of a formula, expressed prosaically and mathematically. For example, according to Gauthier & de Queiroz 2001 (an Authority), Aves ( a Signifier) is defined as, "the crown clade stemming from the most recent common ancestor of Ratitae (Struthio camelus Linnaeus 1758), Tinamidae (Tetrao [Tinamus] major Gmelin 1789), and Neognathae (Vultur gryphus Linnaeus 1758)." This Definition is specified by three internal Anchor entities, respectively referencing the species Struthio camelus, Tetrao major, and Vultur gryphus (all of which are Signifier entities).

Dataset.—The relations in a Dataset are based on observation or hypothesis. A Dataset entity's Authority may be a bioinformatics file, a publication, or a personal opinion. As with Signifier entities, every Dataset can be uniquely identified by a qualified name, combining the URI of its Authority and a local name.

Context.—Applying phylogenetic definitions requires a Context, which is essentially a set of Dataset entities. All Definition and DefinitionApplication entities are implicitly included under every Context.

DefinitionApplication.—This is sort of the crux of the whole idea behind this project: that a PhyloDefinition can be automatically applied under a given Context.