Quickstart¶
Loading a Demes graph¶
Consider the well-known Gutenkunst et al. (2009) Out-of-Africa model of human history.
description: The Gutenkunst et al. (2009) OOA model.
doi:
- https://doi.org/10.1371/journal.pgen.1000695
time_units: years
generation_time: 25
demes:
- name: ancestral
description: Equilibrium/root population
epochs:
- {end_time: 220e3, start_size: 7300}
- name: AMH
description: Anatomically modern humans
ancestors: [ancestral]
epochs:
- {end_time: 140e3, start_size: 12300}
- name: OOA
description: Bottleneck out-of-Africa population
ancestors: [AMH]
epochs:
- {end_time: 21.2e3, start_size: 2100}
- name: YRI
description: Yoruba in Ibadan, Nigeria
ancestors: [AMH]
epochs:
- start_size: 12300
- name: CEU
description: Utah Residents (CEPH) with Northern and Western European Ancestry
ancestors: [OOA]
epochs:
- {start_size: 1000, end_size: 29725}
- name: CHB
description: Han Chinese in Beijing, China
ancestors: [OOA]
epochs:
- {start_size: 510, end_size: 54090}
migrations:
- {demes: [YRI, OOA], rate: 25e-5}
- {demes: [YRI, CEU], rate: 3e-5}
- {demes: [YRI, CHB], rate: 1.9e-5}
- {demes: [CEU, CHB], rate: 9.6e-5}
This YAML file can be loaded into Python with the load()
function,
to obtain a Graph
instance (modify the filename as appropriate).
import demes
ooa_graph = demes.load("../examples/gutenkunst_ooa.yaml")
isinstance(ooa_graph, demes.Graph)
True
Working with a Demes graph¶
The features of the graph can then be inspected. We may ask which demes are present in the graph.
print("Is there a deme labeled CEU in the graph?", "CEU" in ooa_graph)
print("Is there a deme labeled JPT in the graph?", "JPT" in ooa_graph)
print("Which demes are present?", [deme.name for deme in ooa_graph.demes])
Is there a deme labeled CEU in the graph? True
Is there a deme labeled JPT in the graph? False
Which demes are present? ['ancestral', 'AMH', 'OOA', 'YRI', 'CEU', 'CHB']
Or look in more detail at a single deme.
ceu = ooa_graph["CEU"]
print("How many epochs does CEU have?", len(ceu.epochs))
print(ceu.epochs[0])
How many epochs does CEU have? 1
Epoch(start_time=21200.0, end_time=0, start_size=1000, end_size=29725, size_function='exponential', selfing_rate=0, cloning_rate=0)
Similarly, we can inspect the interactions defined between demes.
Note that each symmetric migration defined in the input YAML file has been
converted into a pair of AsymmetricMigration
objects.
print("number of migrations:", len(ooa_graph.migrations))
print("migrations: ")
for migration in ooa_graph.migrations:
print(" ", migration)
print("number of pulses:", len(ooa_graph.pulses))
for pulse in ooa_graph.pulses:
print(" ", pulse)
number of migrations: 8
migrations:
AsymmetricMigration(source='YRI', dest='OOA', start_time=140000.0, end_time=21200.0, rate=0.00025)
AsymmetricMigration(source='OOA', dest='YRI', start_time=140000.0, end_time=21200.0, rate=0.00025)
AsymmetricMigration(source='YRI', dest='CEU', start_time=21200.0, end_time=0, rate=3e-05)
AsymmetricMigration(source='CEU', dest='YRI', start_time=21200.0, end_time=0, rate=3e-05)
AsymmetricMigration(source='YRI', dest='CHB', start_time=21200.0, end_time=0, rate=1.9e-05)
AsymmetricMigration(source='CHB', dest='YRI', start_time=21200.0, end_time=0, rate=1.9e-05)
AsymmetricMigration(source='CEU', dest='CHB', start_time=21200.0, end_time=0, rate=9.6e-05)
AsymmetricMigration(source='CHB', dest='CEU', start_time=21200.0, end_time=0, rate=9.6e-05)
number of pulses: 0
Building a Demes graph¶
A demographic model can also be constructed programmatically by instantiating a
Builder
object, then adding demes, migrations, and admixture
pulses via the methods available on this class.
b = demes.Builder(
description="Gutenkunst et al. (2009) three-population model.",
doi=["10.1371/journal.pgen.1000695"],
time_units="years",
generation_time=25,
)
b.add_deme("ancestral", epochs=[dict(end_time=220e3, start_size=7300)])
b.add_deme("AMH", ancestors=["ancestral"], epochs=[dict(end_time=140e3, start_size=12300)])
b.add_deme("OOA", ancestors=["AMH"], epochs=[dict(end_time=21.2e3, start_size=2100)])
b.add_deme("YRI", ancestors=["AMH"], epochs=[dict(start_size=12300)])
b.add_deme("CEU", ancestors=["OOA"], epochs=[dict(start_size=1000, end_size=29725)])
b.add_deme("CHB", ancestors=["OOA"], epochs=[dict(start_size=510, end_size=54090)])
b.add_migration(demes=["YRI", "OOA"], rate=25e-5)
b.add_migration(demes=["YRI", "CEU"], rate=3e-5)
b.add_migration(demes=["YRI", "CHB"], rate=1.9e-5)
b.add_migration(demes=["CEU", "CHB"], rate=9.6e-5)
The builder object can then be “resolved” into a Graph
using the
Builder.resolve()
method. We can check that our implementation
matches the example we loaded with the Graph.isclose()
method.
my_graph = b.resolve()
my_graph.isclose(ooa_graph)
True
For some demographic models, using the Builder
API can be far less
cumbersome than writing the equivalent YAML file. For example, we can define
a ring of demes, with migration between adjacent demes, as follows.
M = 10 # number of demes
b = demes.Builder(
description=f"a ring of {M} demes, with migration between adjacent demes",
time_units="generations",
)
for j in range(M):
b.add_deme(f"deme{j}", epochs=[dict(start_size=1000)])
if j > 0:
b.add_migration(demes=[f"deme{j - 1}", f"deme{j}"], rate=1e-5)
b.add_migration(demes=[f"deme{M - 1}", "deme0"], rate=1e-5)
ring_graph = b.resolve()
Plotting a Demes graph¶
The external demesdraw
library offers a way to visualise Graph
objects, which can be
a useful way to check that the model is what we expect.
import demesdraw
demesdraw.tubes(ring_graph)
<Axes: ylabel='time ago (generations)'>
Saving a Demes graph¶
The graph can be written out to a new YAML file using the dump()
function.
demes.dump(ring_graph, "/tmp/ring.yaml")