Thursday 14:00-15:15, roughly 1 h 15 min.
This morning you found out what is missing or ambiguous in records other people made. This afternoon you annotate a table yourself, and discover that the hard part is not filling fields in. It is deciding what the fields mean: the semantics.
In data/ you will find a sample metadata table taken from the Deepwater Horizon study:
collection sites, dates, coordinates, and per-sample sequencing yields.
library(dplyr); library(readr)
samples <- read_csv("data/sample-metadata.csv")
View(samples)
You should see 11 records.
Before mapping anything: what is each row one of? A site, a sample, a sequencing run, or a specimen? The answer determines every mapping decision that follows, and the original table does not state it.
Open the Darwin Core quick reference alongside your table and write the mapping down explicitly, as a table rather than in your head.
| Source column | Darwin Core term | Notes |
|---|---|---|
site |
locality |
|
lat |
decimalLatitude |
check the datum |
date |
eventDate |
ISO 8601 |
| … |
You should be able to map five columns as basically direct column-to-term matches, while the rest are more ambiguous.
Two rules worth following now and for the rest of your career:
Some columns will have no obvious home. Work out what to do with each.
The oiling status of a beach. The number of reads passing quality filters for each primer. Which of these is a property of the event, which of the sample, and which of the data product? Does Darwin Core have a place for all three?
The honest answer is that Darwin Core is a vocabulary for occurrences and specimens, not for sequencing runs, and that environmental context belongs in MIxS fields backed by ENVO. Mixed tables are normal. Knowing which standard owns which column is the skill.
Take an environmental descriptor from your table and find an ENVO term for it:
library(jsonlite)
res <- fromJSON(paste0(
"https://www.ebi.ac.uk/ols4/api/search?q=oil%20spill&ontology=envo"))
res$response$docs |> as_tibble() |> select(label, obo_id, description)
You should a result that is possibly a useful hit. But what are the considerations here?
First, “Pre-spill” and “Post-spill” describe when a sample was taken relative to an event,
not what the sample is. No ENVO class captures that, and none should. The condition
column is better kept as a project-specific field, or put in eventRemarks or
dynamicProperties in Darwin Core terms, with a reference to the Deepwater Horizon event.
Second, you could use ENVO where it does fit, as a material annotation (MIxS
env_medium). “Petroleum enriched sediment” (ENVO:00002115) would suit a post-spill
sample only if oil was actually documented at that site. Post-spill does not mean oiled:
Ryan Ct and Dauphin Island Bay have read yields close to their pre-spill values, while
Bayfront Park and Belleair Boulevard collapsed. The pre-spill samples would get a plain
sediment or beach sand term.
What you should see here is the difference between sampling-time context and a property of the sample
annotated <- samples |>
rename(locality = site,
decimalLatitude = lat,
decimalLongitude = lon,
eventDate = date) |>
mutate(basisOfRecord = "MaterialSample",
coordinateUncertaintyInMeters = NA_real_)
write_csv(annotated, "out/samples-dwc.csv")
You should see the same records again, but mapped to DarwinCore terms.
You just wrote
NAintocoordinateUncertaintyInMeters. That is the right thing to do and it is also an admission. What is the difference between a field that is absent and a field that is explicitly empty? Which is more useful to a reuser?
term <- fromJSON(paste0(
"https://www.ebi.ac.uk/QuickGO/services/ontology/go/terms/GO%3A0010228/complete"))
term$results$name
term$results$definition$text
term$results$aspect
You should see a single record (a list) for which we lookup its name, definition text,
and aspect.
Read the definition. Now imagine a gene annotated to this term. What exactly is being claimed about it, and what is not?
anc <- fromJSON(paste0(
"https://www.ebi.ac.uk/QuickGO/services/ontology/go/terms/",
"GO%3A0010228/ancestors?relations=is_a,part_of"))
anc$results$ancestors
A gene annotated to
GO:0010228is implicitly annotated to every one of those. So if you run an enrichment test and both a term and its parent come out significant, how many independent findings do you have?
Most GO annotations are electronic inference rather than direct experiment. An annotation without its evidence code is a rumour.
Give an LLM a short list of GO terms and ask it to write the biological story.
Check the GO identifiers against QuickGO. Do they exist? Do the labels match the identifiers? Then check the argument: is it treating a term and its ancestor as two pieces of evidence?
Hallucinated GO identifiers are common and take thirty seconds to falsify, which makes this the cleanest verification exercise of the week. Note what you found; you will need it for the reflection in week 4.
out/samples-dwc.csv, your annotated tableNext: TP2.4 Cross-database integration and the Q3 draft.