Metadata#
Overview#
SLiM puts SLiM-specific information into the metadata for the tree sequence,
as well as for each population, individual, node and mutation.
Here is a quick reference to what information is available:
see the SLiM manual for the more technical writeup.
A good way to get a generic metadata example is with default_slim_metadata().
Top-level:
If ts is your tree sequence, then ts.metadata is a dict,
and ts.metadata["SLiM"] contains information about the simulation:
file_version: the version of the SLiM tree sequence file formattick: the value ofcommunity.tickwithin SLiM when the file was written outcycle: the value ofsim.cyclewithin SLiM when the file was written outmodel_type: either"WF"or"nonWF"nucleotide_based: whether this is a nucleotide-based simulationseparate_sexes: whether the simulation has separate sexes or notspatial_dimensionality: for instance,""or"x"or"xy"(etcetera)spatial_periodicity: whether space wraps around in some directions (same format as dimensionality)stage: the stage of the life cycle at which the file was written out (either"first","early", or"late")name: the name of this species in SLiMthis_chromosome: contains, for the chromosome in SLiM recorded in this tree sequenceid: SLiM’s IDindex: the index of this chromosome in the list of chromosomessymbol: the user-assigned symboltype: specifies inheritance type, e.g.,"A"for autosome
chromosomes: (optional) a list of all chromosomes in the simulation, for each containing the same information as forthis_chromosometraits: a list of information for each of the traits:index: the index of the trait in SLiMname: the name in SLiM for the traittype:"additive","multiplicative", or"logistic"baselineOffsetFromUser: a value added to all traitsbaselineOffsetFromSubstitutions: the total effect of all substitutions on the traits at the time the tree sequence was savedbaselineAccumulation: whether the effect of substitutions accumulate in that valuedirectFitnessEffect: whether the trait has a direct effect on fitnessindividualOffsetMean,individualOffsetSD: parameters governing the distribution of individual-level offsets (i.e., “environment” effects)
Populations:
Information about each SLiM-produced population is written to metadata.
The format uses JSON and is extensible, so other keys may be present
and some keys may be missing (for instance, there are no spatial bounds
in a nonspatial simulation). The metadata may be None for populations
that SLiM did not use. The keys that SLiM uses are:
slim_id: the ID of this population in SLiMname: the name of the population (by default,"p0","p1", etcetera)description: a string describing the populationselfing_fraction,female_cloning_fraction,male_cloning_fraction, andsex_ratio: only present when applicable (e.g., in WF simulations)bounds_x0,bounds_x1,bounds_y0,bounds_y1,bounds_z0, andbounds_z1: the spatial bounds, when applicablemigration_records: A list of entries describing migration between populations in a WF model.
Individuals: Each individual produced by SLiM contains the following metadata:
pedigree_id: the “pedigree ID”, unique within the SLiM simulationpedigree_p1,pedigree_p2: the pedigree IDs of the individuals’ two parents (they may be equal in the case of selfing, or-1to indicate no parent, in the case of the initial generation)age: the.ageproperty within SLiM at the time the file was written outsubpopulation: the subpopulation within SLiM the individual was in at the time the file was written outsex: the sex of the individual (eitherINDIVIDUAL_TYPE_FEMALE,INDIVIDUAL_TYPE_MALE, orINDIVIDUAL_TYPE_HERMAPHRODITE)flags: additional information; currently only recording whether the individual was a “migrant” or not (see the SLiM manual)tag,tagF: the corresponding properties in SLiM: default values returned by pyslim are the special values that SLiM uses to mean that the values are unsettagL0,tagL0_set, etcetera: again, the corresponding properties in SLiM; the purpose oftagLX_setis to record whether the tag has been set in the simulationper_trait: a list of information about the trait values for this individual; these are in the same order as the traits listed in top-level metadata;phenotype: the trait value (which isNANif the phenotype has not been calculated, or was invalidated)offset: the individual’s offset (i.e., the “environmental effect”)
Nodes: Each “node” produced by SLiM (i.e., “haplosome” within SLiM) has:
slim_id: the unique ID associated with the haplosome by SLiMis_vacant: records that the node is a “vacant” node (in which case it isn’t really there, so shouldn’t have any mutations or relationships in the tree sequence!) - see Vacant nodes: sex chromosomes and haploidy for more explanation
Mutations:
Prior to SLiM 6.0, mutation metadata was associated with the tskit mutation objects.
Now, this is stored in top-level metadata, under ts.metadata["SLiM_mutation_list"].
Each entry contains:
mutation_id: the numeric ID of mutation in SLiMmutation_type: the numeric ID of theMutationTypewithin SLiMsubpopulation: the numeric ID of the subpopulation the mutation occurred inslim_time: the value ofcommunity.tickwhen the mutation occurrednucleotide: either-1if there is no associated nucleotide, or the numeric code for the nucleotide (seeNUCLEOTIDES)per_trait: a list of information in the same order as the traits in top-level metadata, recording for each:effect_size: the effect on the trait of this mutationdominance: its dominance coefficienthemizygous_dominance: its hemizygous dominance coefficient (see the SLiM manual)
padding: this is simply empty bytes, here for byte-alignment reasons, and is alwaysNone
Using top-level metadata#
If you are going to be using information from top-level metadata,
it is good practice to extract the metadata as a separate python object once
and refer to that object, since otherwise you can incur runtime penalties
for decoding and copying the metadata every time you call ts.metadata.
This can be substantial, given the amount of mutation information
in top-level metadata.
For instance, to subtract baseline offsets from individual’s trait values,
we might do:
md = ts.metadata
traits = md["SLiM"]["traits"]
values = [
[x['phenotype'] - y["baselineOffsetFromUser"] for x, y in zip(ind.metadata['per_trait'], traits)]
for ind in ts.individuals()
]
If we instead inserted ts.metadata["SLiM"]["traits"] directly into the loop,
this would become infeasibly slow.
In some more detail:
each time python evaluates ts.metadata (e.g., using ts.metadata["SLiM"])
a new copy of the metadata dict is decoded and returned. Furthermore, a number
of pyslim functions need to look up information from metadata under the hood.
For instance, previously it was acceptable to run
[pyslim.slim_time(ts, mut.time) for mut in ts.mutations()].
However, this could now easily take hours even for moderately-sized simulations.
There are several recommendations for how to mitigate this:
If you use information from top-level metadata, make a copy of it and refer to that copy instead: so,
ts_metadata = ts.metadataafterts = tskit.load(...)and then usets_metadata. However, be careful that you use the correct metadata object corresponding to the correct tree sequence object!Use a single pyslim function call rather than many calls to the same one within a loop. For instance, run:
slim_times = pyslim.slim_time(ts, ts.mutations_time)and extract slim times from this vector. Similarly, usenodes_vacant()instead ofnode_is_vacant().Some pyslim methods will accept a pre-extracted metadata dictionary as an optional argument. If this is not provided, those methods will extract the metadata again. The methods that now take a
ts_metadataargument are:individual_ages(),individual_ages_at(),individuals_alive_at(), andslim_time().
Metadata tools#
The dictionaries describing the schema for these metadata entries
are available in pyslim.slim_metadata_schemas.
Furthermore, these methods may be useful in working with metadata:
- pyslim.default_slim_metadata(name, num_chromosomes=1, num_traits=1, **kwargs)[source]#
Returns default metadata of type
name, wherenameis one of “tree_sequence”, “edge”, “site”, “mutation”, “mutation_list_entry”, “node”, “individual”, or “population”.Additional keyword arguments may be passed in to modify the returned metadata dictionary: for each such key=value pair the returned dictionary has value (re)assigned to key.
- pyslim.slim_tree_sequence_metadata_schema(num_traits=1)[source]#
The top-level metadata schema depends on the number of traits, and {data}`.slim_metadata_schemas` returns the schema for a single-trait simulation. This function returns the correct schema for a simulation with arbitrary number of traits. (The resulting schemas only differ in the “length” of the “per_trait” property of
schema["properties"]["SLiM_mutation_list"]["items"]).- Parameters:
num_traits (int) – The number of traits in the model.
- Return tskit.MetadataSchema:
The metadata schema to be used in the node table.
- pyslim.slim_individual_metadata_schema(num_traits=1)[source]#
The individual metadata schema depends on the number of traits, and {data}`.slim_metadata_schemas` returns the schema for a single-trait simulation. This function returns the correct schema for a simulation with arbitrary number of traits. (The resulting schemas only differ in the “length” of the “per_trait” property.)
- Parameters:
num_traits (int) – The number of traits in the model.
- Return tskit.MetadataSchema:
The metadata schema to be used in the node table.
- pyslim.slim_node_metadata_schema(num_chromosomes=1)[source]#
The node metadata schema depends on the number of chromosomes in a multichromosome simulation, and {data}`.slim_metadata_schemas` returns the schema for a single-chromosome simulation. This function returns the correct schema for a simulation with arbitrary number of chromosomes. (The resulting schemas only differ in the
schema["properties"]["is_vacant"]["length"]property, which is set tofloor(num_chromosomes+7)/8, as described in the SLiM manual.)- Parameters:
num_chromosomes (int) – The number of chromosomes in the model.
- Return tskit.MetadataSchema:
The metadata schema to be used in the node table.
- pyslim.set_tree_sequence_metadata(tables, model_type, tick, *, cycle=None, spatial_dimensionality='', spatial_periodicity='', separate_sexes=False, nucleotide_based=False, stage='late', name='', description='', this_chromosome=None, chromosomes=None, file_version=None, set_table_schemas=True, traits=None, SLiM_mutation_list=None)[source]#
Modifying SLiM metadata#
For more on working with metadata, see tskit’s metadata documentation.
Top-level metadata#
The entries of the top-level metadata dict are read-only.
So, although you might think that
tables.metadata["SLiM"]["model_type"] = "nonWF"
would switch the model type,
this in fact (silently) does nothing. To modify the top-level metadata,
we must (a) work with tables (as tree sequences are immutable), and (b)
extract the metadata dict, modify the dict, and copy it back in.
So, the code for modifying top-level metadata is:
md = tables.metadata
md["SLiM"]["model_type"] = "nonWF"
tables.metadata = md
Modifying the top-level metadata could be used to declare that an annotated msprime simulation is to be a spatial simulation in SLiM, by changing the “dimensionality” property in top-level metadata (and perhaps also the bounds in population metadata as well).
Modifying SLiM metadata in tables#
To modify the metadata that pyslim has introduced into
the tree sequence produced by a coalescent simulation,
or the metadata in a SLiM-produced tree sequence,
we need to edit the TableCollection that forms the editable data behind the tree sequence.
For instance, to set the ages of the individuals in the tree sequence to random numbers between 1 and 4,
we will extract a copy of the underlying tables, clear it,
and then iterate over the individuals in the tree sequence,
as we go re-inserting them into the tables
after replacing their metadata with a modified version:
tables = ts.dump_tables()
tables.individuals.clear()
for ind in ts.individuals():
md = ind.metadata
md["age"] = random.choice([1,2,3,4])
tables.individuals.append(
ind.replace(metadata=md)
)
mod_ts = tables.tree_sequence()
# check that it worked:
print("First ten ages:", [mod_ts.individual(i).metadata["age"] for i in range(10)])
for ind in mod_ts.individuals():
assert ind.metadata['age'] in [1, 2, 3, 4]
# save out the tree sequence
mod_ts.dump("modified_ts.trees")
First ten ages: [3, 1, 1, 3, 4, 4, 3, 2, 2, 3]
Technical details#
Metadata entries#
SLiM records additional information in the metadata columns of Individual and Node, and Mutation tables,
and about mutations in top-level metadata,
in a binary format using the python struct module.
However, this is transparently taken care of by tskit’s metadata module,
so the user does not have to work directly with binary information or the struct module itself.
See tskit’s metadata documentation
for details on how this works.
Nothing besides this binary information can be stored in the metadata of these tables if the tree sequence is to be used by SLiM,
and so when pyslim annotates an existing tree sequence, anything in those columns is overwritten.
Population metadata is stored as JSON, however, which is more flexible.
For more detailed documentation on the contents and format of the metadata, see the SLiM manual.
Of particular note is that nodes and populations may have empty metadata. SLiM will not use the metadata of nodes that are not associated with alive individuals, so this can safely be omitted (and makes recapitation easier). And, populations not used by SLiM will have empty metadata. All remaining metadata are required (besides edges and sites, whose metadata is not used at all).