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.
- https://doi.org/10.1371/journal.pgen.1000695
time_units: years
generation_time: 25

- name: ancestral
  description: Equilibrium/root population
  - {end_time: 220e3, start_size: 7300}
- name: AMH
  description: Anatomically modern humans
  ancestors: [ancestral]
  - {end_time: 140e3, start_size: 12300}
- name: OOA
  description: Bottleneck out-of-Africa population
  ancestors: [AMH]
  - {end_time: 21.2e3, start_size: 2100}
- name: YRI
  description: Yoruba in Ibadan, Nigeria
  ancestors: [AMH]
  - start_size: 12300
- name: CEU
  description: Utah Residents (CEPH) with Northern and Western European Ancestry
  ancestors: [OOA]
  - {start_size: 1000, end_size: 29725}
- name: CHB
  description: Han Chinese in Beijing, China
  ancestors: [OOA]
  - {start_size: 510, end_size: 54090}

- {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)

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))
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
  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.",
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()

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",

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

<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")