Table of Contents

Here, I am just going to redo the same 2D model fitting as I did before for the original 2D spectrum, but with the spectrum to which I applied Ludovic's correction.


In [1]:
from ipyparallel import Client

cl = Client()

cl.ids


Out[1]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [2]:
%%px --local

# run whole cell on all engines a well as in the local IPython session

import numpy as np

import sys

sys.path.insert(0, '/home/claudius/Downloads/dadi')

import dadi


error in importing Two Locus modules
[stdout:0] error in importing Two Locus modules
[stdout:1] error in importing Two Locus modules
[stdout:2] error in importing Two Locus modules
[stdout:3] error in importing Two Locus modules
[stdout:4] error in importing Two Locus modules
[stdout:5] error in importing Two Locus modules
[stdout:6] error in importing Two Locus modules
[stdout:7] error in importing Two Locus modules
[stdout:8] error in importing Two Locus modules
[stdout:9] error in importing Two Locus modules
[stdout:10] error in importing Two Locus modules
[stdout:11] error in importing Two Locus modules
[stdout:12] error in importing Two Locus modules
[stdout:13] error in importing Two Locus modules
[stdout:14] error in importing Two Locus modules
[stdout:15] error in importing Two Locus modules
[stdout:16] error in importing Two Locus modules
[stdout:17] error in importing Two Locus modules
[stdout:18] error in importing Two Locus modules
[stdout:19] error in importing Two Locus modules

In [3]:
def run_dadi(p_init): # for the function to be called with map, it needs to have one input variable
    """
    p_init: initial parameter values to run optimisation from
    """
    if perturb == True:
        p_init = dadi.Misc.perturb_params(p_init, fold=fold, 
                                      upper_bound=upper_bound, lower_bound=lower_bound)
        # note upper_bound and lower_bound variables are expected to be in the namespace of each engine
    # run optimisation of paramters
    popt = dadi_opt_func(p0=p_init, data=sfs, model_func=func_ex, pts=pts_l, \
                                   lower_bound=lower_bound, upper_bound=upper_bound, \
                                   verbose=verbose, maxiter=maxiter, full_output=full_output, \
                                    fixed_params=fixed_params)
    # pickle to file
    import dill
    name = outname[:] # make copy of file name stub!
    for p in p_init:
        name += "_%.4f" % (p)
    with open(name + ".dill", "w") as fh:
        dill.dump((p_init, popt), fh)
    
    return p_init, popt

In [4]:
from glob import glob
import dill
from utility_functions import *
import pandas as pd
# turn on floating point division by default, old behaviour via '//'
from __future__ import division

In [5]:
lbview = cl.load_balanced_view()

In [6]:
from itertools import repeat

In [7]:
%matplotlib inline

import pylab

pylab.rcParams['figure.figsize'] = [12, 10]
pylab.rcParams['font.size'] = 14

In [8]:
%%px --local

# load spectrum modified with Ludovic's correction, p=35

sfs2d = dadi.Spectrum.from_file("EryPar_modified.2dsfs")

In [9]:
dadi.Plotting.plot_single_2d_sfs(sfs2d, vmin=1, cmap=pylab.cm.jet)


Out[9]:
<matplotlib.colorbar.Colorbar at 0x7fbef122be10>

In [10]:
# number of SNP's in the spectrum
sfs2d.S()


Out[10]:
60573.584426000001

simple divergence model


In [11]:
def split_nomig(params, ns, pts):
    """
    params = (nu1,nu2,T)
    ns = (n1,n2)

    Split into two populations of specifed size, no migration.

    nu1: Size ratio of population 1 after split (with respect to ancestral population size Na)
    nu2: Size ratio of population 2 after split (with respect to ancestral population size Na)
    T: Time in the past of split (in units of 2*Na generations) 
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,T = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi)

    phi = dadi.Integration.two_pops(phi, xx, T, nu1, nu2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [14]:
cl[:].push(dict(split_nomig=split_nomig))


Out[14]:
<AsyncResult: _push>

In [15]:
%%px --local

# create extrapolating version of the model function
func_ex = dadi.Numerics.make_extrap_log_func(split_nomig)

In [16]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 10 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_nomig" # set file name stub for opt. result files
fixed_params = None

In [17]:
%%px --local

# set lower and upper bounds to nu1, nu2 and T
upper_bound = [1e4, 1e4, 5]
lower_bound = [1e-4, 1e-4, 0]

In [ ]:
# perturb parameter neutral values
p0 = [0.5, 0.5, 0.1] # split into equal proportions at >200,000 generations ago

#ar_split_nomig = lbview.map(run_dadi, repeat(p0, 10))

In [28]:
% ll OUT_2D_models/split_nomig_[!p]*dill


-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.1304_0.6851_0.0601.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.1535_0.2150_0.1258.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.1606_0.5516_0.1831.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.2260_0.4902_0.0619.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.2454_0.1483_0.3527.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.5718_1.2029_0.0711.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_0.7649_1.1414_0.1141.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_1.2273_0.7343_0.0393.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_1.3523_0.2709_0.0362.dill
-rw-rw-r-- 1 claudius 298 Jun  1 20:15 OUT_2D_models/split_nomig_1.4073_0.2024_0.0270.dill

In [47]:
ar_split_nomig = []

for filename in glob("OUT_2D_models/split_nomig_[!p]*dill"):
    ar_split_nomig.append(dill.load(open(filename)))

In [48]:
get_flag_count(ar_split_nomig, NM=True)


success 5
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 5
unknown flag 0

In [93]:
returned = [flatten(out)[:7] for out in ar_split_nomig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'nu1_opt', 'nu2_opt', 'T_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[93]:
nu1_0 nu2_0 T_0 nu1_opt nu2_opt T_opt -logL
2 0.130396 0.685058 0.060110 0.762412 1.526605 0.301939 15791.588420
0 0.153522 0.215024 0.125826 0.760217 1.528322 0.302361 15791.696999
6 0.225985 0.490219 0.061947 0.760194 1.528215 0.302337 15791.697000
4 0.571756 1.202900 0.071081 0.760168 1.528309 0.302349 15791.697001
3 0.245443 0.148331 0.352726 0.760211 1.528331 0.302349 15791.697001
9 0.160553 0.551626 0.183081 0.760241 1.528333 0.302361 15791.697002
7 1.407268 0.202361 0.026992 0.760152 1.528101 0.302327 15791.697029
1 0.764874 1.141367 0.114088 0.760070 1.528050 0.302322 15791.697101
8 1.352339 0.270908 0.036199 0.760387 1.528244 0.302390 15791.697213
5 1.227281 0.734266 0.039261 0.767487 1.537413 0.303948 15791.771200

Beautiful convergence.

Interpretation


In [94]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 3:6])
popt


Out[94]:
array([ 0.76241191,  1.52660495,  0.30193872])

In [95]:
# get unscaled, best-fit model spectrum
model = func_ex(popt, ns, pts_l)

# get logL of best-fit model
ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[95]:
-15791.588419713637

In [96]:
# get theta
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
# get total sequence length in spectrum
L = sfs2d.data.sum()
# get theta per site
print "The optimal value of theta per site for the ancestral population is {0:.4f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.0076.

In [58]:
mu = 3e-9 # assumed per generation per site mutation rate
print "The total sequence length for the 2D spectrum is {0:,}.".format(int(L))
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The total sequence length for the 2D spectrum is 1,130,775.
The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:
 631,166.

I am assuming that $\nu_1$ refers to ery and $\nu_2$ refers to par. The split_nomig model with its optimal parameter values suggests the following:


In [62]:
print "The ancestral population of ery and par split apart {0:,} generations ago.".format(int(popt[2]*2*N_ref)),
print "Since then until present the ERY population had a size of {0:,} and the PAR population of {1:,}.".format(int(popt[0]*N_ref), int(popt[1]*N_ref))


The ancestral population of ery and par split apart 381,147 generations ago. Since then until present the ERY population had a size of 481,209 and the PAR population of 963,542.

With the uncorrected spectrum, I inferred a split time of 400,188 generations and effective population sizes for ery and par of 438,776 and 847,045, respectively.

The following table compares the best-fit parameter values from the uncorrected and corrected spectrum:

parameter uncorrected corrected
$N_a$ 688,875 631,166
$\nu_1$ 438,776 481,209
$\nu_2$ 847,045 963,542
T 400,188 381,147
-logL 20387 15791

The values are given translated to absolute units, i. e. $\nu_x$ in individuals and T in generations.

Residuals


In [63]:
dadi.Plotting.plot_2d_comp_multinom(model, sfs2d, vmin=1)


The best-fit model spectrum predicts far fewer SNP's with frequency [1,1] than the observed (corrected) spectrum.

influence of the degree of correction

The model fitting above was done with a 2D SFS corrected with a p of 0.35. I have created two more spectra corrected with different values of p (0.3 and 0.39). They were derived by using a different null model for optimising p (for details see Ludovics_correction.ipynb).


In [9]:
%%px --local

# load spectrum modified with Ludovic's correction

sfs2d_a = dadi.Spectrum.from_file("EryPar_modified_a.2dsfs") # p=0.30
sfs2d_b = dadi.Spectrum.from_file("EryPar_modified_b.2dsfs") # p=0.39

p=0.3


In [ ]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d_a.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d_a # use sfs corrected with p = 0.3
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 10 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_nomig_p0.30" # set file name stub for opt. result files
fixed_params = None

In [ ]:
%%px --local

# set lower and upper bounds to nu1, nu2 and T
upper_bound = [1e4, 1e4, 5]
lower_bound = [1e-4, 1e-4, 0]

In [ ]:
# perturb parameter neutral values
p0 = [0.5, 0.5, 0.1] # split into equal proportions at >200,000 generations ago

#ar_split_nomig = lbview.map(run_dadi, repeat(p0, 10))

In [34]:
% ll OUT_2D_models/split_nomig_p0.30_*dill


-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.1448_0.1260_0.0605.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.1559_0.1869_0.1554.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.2026_1.6668_0.0663.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.3427_0.1526_0.0784.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.3500_1.9156_0.0648.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.3522_0.6554_0.0499.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.5814_1.2750_0.1621.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_0.8447_0.2095_0.0921.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_1.0114_1.7203_0.1228.dill
-rw-rw-r-- 1 claudius 298 Jun  2 09:12 OUT_2D_models/split_nomig_p0.30_1.9554_0.4638_0.1178.dill

In [35]:
ar_split_nomig = []

for filename in glob("OUT_2D_models/split_nomig_p0.30_*dill"):
    ar_split_nomig.append(dill.load(open(filename)))

In [36]:
get_flag_count(ar_split_nomig, NM=True)


success 8
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 2
unknown flag 0

In [37]:
returned = [flatten(out)[:7] for out in ar_split_nomig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'nu1_opt', 'nu2_opt', 'T_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[37]:
nu1_0 nu2_0 T_0 nu1_opt nu2_opt T_opt -logL
8 1.955442 0.463817 0.117820 0.744089 1.478716 0.300635 16046.344388
1 0.144818 0.126026 0.060458 0.742039 1.481590 0.301003 16046.485431
5 0.155943 0.186864 0.155439 0.742059 1.481633 0.301014 16046.485432
7 0.844686 0.209492 0.092093 0.742075 1.481671 0.301014 16046.485432
3 0.352177 0.655446 0.049885 0.742054 1.481620 0.301013 16046.485432
0 0.202589 1.666824 0.066334 0.742069 1.481588 0.301001 16046.485432
9 0.581361 1.274999 0.162114 0.742078 1.481632 0.301005 16046.485433
6 0.350016 1.915626 0.064756 0.742087 1.481586 0.301011 16046.485433
2 0.342666 0.152639 0.078395 0.742061 1.481637 0.301017 16046.485434
4 1.011441 1.720296 0.122779 1.012591 1.781549 0.387773 16331.055686

The same $T$ is inferred as with the spectrum corrected with a $p$ of 0.35.

p=0.39


In [ ]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d_a.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d_b # use sfs corrected with p=0.39
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 10 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_nomig_p0.39" # set file name stub for opt. result files
fixed_params = None

In [ ]:
%%px --local

# set lower and upper bounds to nu1, nu2 and T
upper_bound = [1e4, 1e4, 5]
lower_bound = [1e-4, 1e-4, 0]

In [ ]:
# perturb parameter neutral values
p0 = [0.5, 0.5, 0.1] # split into equal proportions at >200,000 generations ago

#ar_split_nomig = lbview.map(run_dadi, repeat(p0, 10))

In [38]:
ar_split_nomig = []

for filename in glob("OUT_2D_models/split_nomig_p0.39_*dill"):
    ar_split_nomig.append(dill.load(open(filename)))

In [39]:
get_flag_count(ar_split_nomig, NM=True)


success 2
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 8
unknown flag 0

In [40]:
returned = [flatten(out)[:7] for out in ar_split_nomig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'nu1_opt', 'nu2_opt', 'T_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[40]:
nu1_0 nu2_0 T_0 nu1_opt nu2_opt T_opt -logL
2 0.522980 1.638493 0.345880 0.774440 1.569363 0.303603 15661.757259
0 0.480717 0.160349 0.204501 0.774392 1.569340 0.303584 15661.757264
4 0.387119 0.288683 0.275343 0.774481 1.569447 0.303619 15661.757270
8 0.127899 1.657851 0.089208 0.774360 1.569272 0.303573 15661.757467
1 1.278363 0.455339 0.184210 0.774346 1.568981 0.303566 15661.757471
7 1.390095 0.222213 0.047402 0.774301 1.569529 0.303547 15661.757681
3 1.692983 1.090530 0.251938 0.774631 1.570032 0.303679 15661.757815
9 1.476032 0.207546 0.176224 0.774172 1.568577 0.303500 15661.758359
5 0.450284 0.641202 0.029197 0.774878 1.569450 0.303776 15661.759269
6 0.954773 0.770031 0.027065 0.885433 1.692365 0.336491 15713.332985

The inferred $T$ is the same as before. So the degree of correction has no influence on the inferred divergence time. The divergence time was the only parameter that was varied in the null models used for optimising p.

divergence with migration


In [18]:
%%px --local

func_ex = dadi.Numerics.make_extrap_log_func(dadi.Demographics2D.split_mig)

In [19]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 10 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_mig" # set file name stub for opt. result files
fixed_params = None

In [20]:
%%px --local

# set lower and upper bounds to nu1, nu2, T, m
upper_bound = [1e4, 1e4, 2, 10]
lower_bound = [1e-4, 1e-4, 0, 0]

In [21]:
# nu1, nu2, T, m:
p0 = [0.5, 0.5, 0.1, 0.1]

#ar_split_mig = lbview.map(run_dadi, repeat(p0, 10))

In [22]:
% ll OUT_2D_models/split_mig_[!p]*dill


-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.1291_0.6481_0.1084_0.2316.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.1372_0.3579_0.2789_0.1646.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.2979_1.1990_0.0600_0.2557.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.3232_1.4743_0.2145_0.0439.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_0.3813_7.7662_1.9800_0.6584.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.3817_0.4182_0.2343_0.0251.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_0.4101_0.9763_1.9800_0.1383.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:32 OUT_2D_models/split_mig_0.4121_5.6440_1.9800_0.0801.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.6054_0.6593_0.1584_0.3502.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.6142_1.2180_0.2208_0.1924.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.6938_0.6460_0.0337_0.1346.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:32 OUT_2D_models/split_mig_0.7306_6.8095_1.3013_0.4164.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:32 OUT_2D_models/split_mig_0.7926_1.5026_0.4571_0.2052.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.9250_0.2040_0.0504_0.0583.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_1.0380_3.7516_1.9800_0.0573.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_1.1614_1.7857_0.1006_0.2695.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:32 OUT_2D_models/split_mig_1.8833_2.5857_0.4769_0.0706.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:32 OUT_2D_models/split_mig_2.1977_10.1219_1.9800_0.1162.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:32 OUT_2D_models/split_mig_3.1205_2.2168_0.8782_0.3363.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_5.3425_1.2061_1.6096_0.1260.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_11.0126_22.7709_5.9400_0.0437_0.3119_0.1956_0.1092.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_1.2054_5.0738_5.9400_0.0214_0.3336_0.1019_0.0275.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_1.7164_3.8718_7.9200_0.0218_0.2159_0.0908_0.0176.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_2.2342_3.2395_5.9400_0.0919_0.2080_0.1652_0.0786.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:50 OUT_2D_models/split_mig_iso_mig_2.3335_15.2576_1.8862_0.1763_0.0680_0.3384_0.1045.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.6152_14.5322_6.1771_0.0384_0.1613_0.1163_0.0199.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:00 OUT_2D_models/split_mig_iso_mig_2.6470_3.9437_3.9926_0.0634_0.3447_0.2211_0.0100.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.8052_5.0529_4.0141_0.0299_0.2311_0.2026_0.0141.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.8618_6.1742_6.2063_0.0231_0.1944_0.1075_0.0196.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_3.3672_5.8737_5.1818_0.0212_0.1317_0.1343_0.0126.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_3.5815_4.3860_7.9200_0.0402_0.1995_0.0909_0.0063.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_4.4204_17.3910_3.3502_0.1423_0.3064_0.2099_0.0165.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_4.6896_4.9306_7.9200_0.0368_0.2436_0.1309_0.0196.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:02 OUT_2D_models/split_mig_iso_mig_4.8485_7.8443_6.5328_0.0656_0.2918_0.1493_0.0061.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_5.1750_4.8629_4.8988_0.0283_0.4760_0.0538_0.0625.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:02 OUT_2D_models/split_mig_iso_mig_5.9582_10.0711_7.9200_0.0709_0.1852_0.1687_0.0182.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_6.7436_2.1538_5.9400_0.0161_0.1946_0.0559_0.1092.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_6.9803_23.0350_5.9400_0.0180_0.4691_0.0579_0.0198.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_8.0423_3.9836_4.7871_0.0895_0.1220_0.1626_0.0149.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_8.3624_7.1749_4.3187_0.0305_0.3143_0.0298_0.0465.dill

In [25]:
% ll OUT_2D_models/split_mig_[0-9]*dill


-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.1291_0.6481_0.1084_0.2316.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.1372_0.3579_0.2789_0.1646.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.2979_1.1990_0.0600_0.2557.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.3232_1.4743_0.2145_0.0439.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_0.3813_7.7662_1.9800_0.6584.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.3817_0.4182_0.2343_0.0251.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_0.4101_0.9763_1.9800_0.1383.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:32 OUT_2D_models/split_mig_0.4121_5.6440_1.9800_0.0801.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.6054_0.6593_0.1584_0.3502.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.6142_1.2180_0.2208_0.1924.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.6938_0.6460_0.0337_0.1346.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:32 OUT_2D_models/split_mig_0.7306_6.8095_1.3013_0.4164.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:32 OUT_2D_models/split_mig_0.7926_1.5026_0.4571_0.2052.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_0.9250_0.2040_0.0504_0.0583.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_1.0380_3.7516_1.9800_0.0573.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:26 OUT_2D_models/split_mig_1.1614_1.7857_0.1006_0.2695.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:32 OUT_2D_models/split_mig_1.8833_2.5857_0.4769_0.0706.dill
-rw-rw-r-- 1 claudius 314 Jun  1 20:32 OUT_2D_models/split_mig_2.1977_10.1219_1.9800_0.1162.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:32 OUT_2D_models/split_mig_3.1205_2.2168_0.8782_0.3363.dill
-rw-rw-r-- 1 claudius 315 Jun  1 20:33 OUT_2D_models/split_mig_5.3425_1.2061_1.6096_0.1260.dill

In [26]:
ar_split_mig = []

for filename in glob("OUT_2D_models/split_mig_[0-9]*dill"):
    ar_split_mig.append(dill.load(open(filename)))

In [27]:
get_flag_count(ar_split_mig, NM=True)


success 10
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 10
unknown flag 0

In [28]:
# get  optimisation results
returned = [flatten(out)[:9] for out in ar_split_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'm_0', 'nu1_opt', 'nu2_opt', 'T_opt', 'm_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[28]:
nu1_0 nu2_0 T_0 m_0 nu1_opt nu2_opt T_opt m_opt -logL
12 0.410126 0.976289 1.980000 0.138307 1.584756 2.862212 1.726329 0.210858 12809.710148
6 1.037985 3.751629 1.980000 0.057275 1.587910 2.867287 1.730633 0.210333 12809.711143
9 0.792582 1.502558 0.457088 0.205240 1.589701 2.870222 1.735177 0.210359 12809.715071
8 1.883348 2.585737 0.476910 0.070592 1.589471 2.871227 1.734395 0.210454 12809.719004
7 3.120532 2.216834 0.878223 0.336300 1.588236 2.871408 1.734567 0.210433 12809.720773
3 5.342499 1.206116 1.609641 0.126021 1.574618 2.844696 1.705099 0.211724 12809.729668
16 0.381319 7.766225 1.980000 0.658405 1.589798 2.869340 1.732618 0.210669 12809.734974
13 0.730603 6.809501 1.301306 0.416379 1.571368 2.838305 1.701136 0.212217 12809.738733
15 0.614214 1.217983 0.220814 0.192430 1.572018 2.838641 1.703611 0.211194 12809.910434
14 0.412119 5.644039 1.980000 0.080137 1.525121 2.763772 1.619169 0.216784 12810.362825

In [ ]:
%%px

maxiter = 300

In [ ]:
# nu1, nu2, T, m:
p0 = [1.5, 2.8, 1.7, 0.2]

#ar_split_mig = lbview.map(run_dadi, repeat(p0, 10))

In [74]:
get_flag_count(ar_split_mig, NM=True)


success 10
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 10
unknown flag 0

In [75]:
ar_split_mig = []

for filename in glob("OUT_2D_models/split_mig_[!p]*dill"):
    ar_split_mig.append(dill.load(open(filename)))

In [29]:
# get "unsuccessfull" optimisations
returned = [flatten(out)[:9] for out in ar_split_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'm_0', 'nu1_opt', 'nu2_opt', 'T_opt', 'm_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[29]:
nu1_0 nu2_0 T_0 m_0 nu1_opt nu2_opt T_opt m_opt -logL
12 0.410126 0.976289 1.980000 0.138307 1.584756 2.862212 1.726329 0.210858 12809.710148
6 1.037985 3.751629 1.980000 0.057275 1.587910 2.867287 1.730633 0.210333 12809.711143
9 0.792582 1.502558 0.457088 0.205240 1.589701 2.870222 1.735177 0.210359 12809.715071
8 1.883348 2.585737 0.476910 0.070592 1.589471 2.871227 1.734395 0.210454 12809.719004
7 3.120532 2.216834 0.878223 0.336300 1.588236 2.871408 1.734567 0.210433 12809.720773
3 5.342499 1.206116 1.609641 0.126021 1.574618 2.844696 1.705099 0.211724 12809.729668
16 0.381319 7.766225 1.980000 0.658405 1.589798 2.869340 1.732618 0.210669 12809.734974
13 0.730603 6.809501 1.301306 0.416379 1.571368 2.838305 1.701136 0.212217 12809.738733
15 0.614214 1.217983 0.220814 0.192430 1.572018 2.838641 1.703611 0.211194 12809.910434
14 0.412119 5.644039 1.980000 0.080137 1.525121 2.763772 1.619169 0.216784 12810.362825

The divergence time $T$ inferred with the uncorrected spectrum was only 0.92 ($2N_a$ generations).

parameter uncorrected corrected
$\nu_1$ 0.994185 1.584756
$\nu_2$ 1.766127 2.862212
T 0.922632 1.726329
m 0.250688 0.210858
-logL 18574 12809

The fact that with the corrected spectrum higher population sizes are inferred makes sense, since the correction shifts counts from higher frequency to lower frequency variants and a proportional increase in low frequency variants is the effect of population increase on the SFS. The almost doubling of the divergence time and reduction in migration is less obvious to me, but note that these parameters are relative to the $N_a$ and if inferred $N_a$ changed proportionally, then parameter values in absolute units may not be that divergent (see below).

Interpretation


In [30]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 4:8])
popt


Out[30]:
array([ 1.58475598,  2.86221207,  1.72632914,  0.2108576 ])

In [31]:
# get unscaled, best-fit model spectrum
model = func_ex(popt, ns, pts_l)

# get logL of best-fit model
ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[31]:
-12809.710148444021

In [32]:
# get theta
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
# get total sequence length in spectrum
L = sfs2d.data.sum()
# get theta per site
print "The optimal value of theta per site for the ancestral population is {0:.4f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.0037.

In [33]:
mu = 3e-9 # assumed per generation per site mutation rate
print "The total sequence length for the 2D spectrum is {0:,}.".format(int(L))
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The total sequence length for the 2D spectrum is 1,130,775.
The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:
 308,029.

The uncorrected spectrum inferred an ancestral population size of 468,295.


In [34]:
print "The ancestral population split apart {0:,} generation ago.".format(int(popt[2]*2*N_ref)), 
print "Since then, ERY and PAR had a constant population size of {0:,} and {1:,}, respectively.".format(int(popt[0]*N_ref), int(popt[1]*N_ref)), 
print "Since the split, a fraction of {0:.2e} of the population size of ERY were made up each generation of new immigrant individuals from PAR".format(popt[3]/2/N_ref/popt[0]),
print "and a fraction of {0:.2e} of the population size of PAR were made up each generation of new immigtant individuals of ERY.".format(popt[3]/2/N_ref/popt[1])
print "Put another way:",
print "Since the split ERY received a constant number of {0:.2f} new immigrant alleles per generation, while PAR received a constant number of {1:.2f} per generation.".format(popt[3]*popt[0], popt[3]*popt[1])


The ancestral population split apart 1,063,520 generation ago. Since then, ERY and PAR had a constant population size of 488,151 and 881,645, respectively. Since the split, a fraction of 2.16e-07 of the population size of ERY were made up each generation of new immigrant individuals from PAR and a fraction of 1.20e-07 of the population size of PAR were made up each generation of new immigtant individuals of ERY.
Put another way: Since the split ERY received a constant number of 0.33 new immigrant alleles per generation, while PAR received a constant number of 0.60 per generation.

The time of split inferred with the uncorrected spectrum was: 864,128 generation ago. So an almost doubling of the parameters in relative units translates to an increase by about 1/4 in absolute units (generations).

The following table compares the parameter estimates for the split-migration model derived with the uncorrected and the corrected spectrum in their absolute units:

parameter uncorrected corrected
$N_a$ 468,295 308,029
$N_{ERY}$ 465,572 488,151
$N_{PAR}$ 827,069 881,645
T (gen.) 864,128 1,063,520
$p_{par->ery}$ 2.69e-07 2.16e-07
$p_{ery->par}$ 1.52e-07 1.20e-07
-logL 18574 12809

$N_x$ have unit individuals, T has unit generations, $p_x$ are proportions of new immigrant alleles per generation.

There is a marked difference in the inferred ancestral population size ($N_a$ eq. to $N_{ref}$), which affects all other parameters. The estimated contemporary population sizes for ERY and PAR are slightly higher for the corrected spectrum, but not much so. The inferred divergence time is 1/4 higher with the corrected spectrum. The inferred migration rates (as proportion of new immigrant individuals per generation) are both slightly smaller with the corrected spectrum, but not dramatically.

Residuals


In [109]:
dadi.Plotting.plot_2d_comp_multinom(data=sfs2d, model=model, vmin=1)


This model with gene flow has greatly reduced residuals. It still predicts too few SNP's with frequency [1,1], but this residual is much reduced when compared with the divergence in isolation model above.

influence of correction

p=0.30


In [110]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d_a.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d_a # use the sfs corrected with p=0.3
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 10 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_mig_p0.30" # set file name stub for opt. result files
fixed_params = None

In [111]:
%%px --local

# set lower and upper bounds to nu1, nu2, T, m
upper_bound = [1e4, 1e4, 2, 10]
lower_bound = [1e-4, 1e-4, 0, 0]

In [ ]:
# nu1, nu2, T, m:
p0 = [1.5, 2.8, 1.7, 0.2]

#ar_split_mig = lbview.map(run_dadi, repeat(p0, 10))

In [113]:
% ll OUT_2D_models/split_mig_p0.30*dill


-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_0.4113_8.3124_1.9800_0.2789.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_0.4250_1.9062_1.0372_0.2232.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_0.5540_1.5858_1.9800_0.6120.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_0.6191_5.3350_0.4262_0.3364.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_0.9891_1.5522_1.9800_0.7920.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_1.0289_1.2984_1.9800_0.2881.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_1.7792_2.6982_1.9800_0.3689.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_2.0058_3.0709_0.6500_0.1460.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_3.0502_5.6608_1.9800_0.1392.dill
-rw-rw-r-- 1 claudius 314 Jun  2 09:28 OUT_2D_models/split_mig_p0.30_3.5905_10.5666_1.6402_0.0739.dill

In [114]:
ar_split_mig = []

for filename in glob("OUT_2D_models/split_mig_p0.30*dill"):
    ar_split_mig.append(dill.load(open(filename)))

In [115]:
# get optimisations
returned = [flatten(out)[:9] for out in ar_split_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'm_0', 'nu1_opt', 'nu2_opt', 'T_opt', 'm_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[115]:
nu1_0 nu2_0 T_0 m_0 nu1_opt nu2_opt T_opt m_opt -logL
9 0.619073 5.335041 0.426247 0.336444 1.471322 2.653140 1.572579 0.219394 13226.653783
7 0.554006 1.585756 1.980000 0.612026 1.472450 2.652522 1.575672 0.218834 13226.735871
0 2.005768 3.070855 0.649955 0.145964 1.480580 2.666666 1.587519 0.218269 13226.774751
2 1.779153 2.698225 1.980000 0.368870 1.523274 2.743886 1.669660 0.213822 13227.250769
3 0.411339 8.312366 1.980000 0.278883 1.618440 2.910058 1.853607 0.203391 13230.547008
4 3.050153 5.660791 1.980000 0.139247 1.645675 2.953670 1.914882 0.201584 13231.954367
6 3.590533 10.566626 1.640166 0.073947 1.685281 3.013192 1.993573 0.198100 13234.108940
8 0.425013 1.906170 1.037215 0.223236 1.179353 2.163423 1.056767 0.251451 13268.788639
5 1.028887 1.298367 1.980000 0.288102 1.021744 1.946962 0.922114 0.280760 13342.747461
1 0.989122 1.552190 1.980000 0.791966 0.986462 1.900748 0.877320 0.285676 13371.870805

The inferred $T$ and $m$ are not very different. The values inferred with the spectrum corrected with a p of 0.35 (see above) are quite similar ($T=1.726$, $m=0.211$).

p=0.39


In [ ]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d_a.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d_b # use the sfs corrected with p=0.39
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 10 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_mig_p0.39" # set file name stub for opt. result files
fixed_params = None

In [ ]:
# nu1, nu2, T, m:
p0 = [1.5, 2.8, 1.7, 0.2]

#ar_split_mig = lbview.map(run_dadi, repeat(p0, 10))

In [116]:
ar_split_mig = []

for filename in glob("OUT_2D_models/split_mig_p0.39*dill"):
    ar_split_mig.append(dill.load(open(filename)))

In [117]:
# get optimisations
returned = [flatten(out)[:9] for out in ar_split_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'm_0', 'nu1_opt', 'nu2_opt', 'T_opt', 'm_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[117]:
nu1_0 nu2_0 T_0 m_0 nu1_opt nu2_opt T_opt m_opt -logL
5 1.186737 1.751522 1.980000 0.461137 1.692853 3.062311 1.873604 0.203172 12546.940350
0 3.127995 9.121443 1.980000 0.462147 1.758203 3.174306 1.991875 0.197089 12547.463540
1 2.537579 9.878637 0.727451 0.089020 1.604128 2.879648 1.725914 0.215744 12549.572946
8 2.252962 2.467163 1.980000 0.623962 1.595294 2.905932 1.646046 0.206065 12553.716384
6 4.770883 5.445931 0.546950 0.148639 1.572295 2.859923 1.636817 0.206012 12556.796990
2 1.123416 5.509509 0.513859 0.432212 1.576281 3.051595 1.832997 0.210671 12570.353207
3 4.817874 10.176975 1.054042 0.091783 1.304134 2.396536 1.184463 0.244845 12591.343569
4 1.258454 0.941615 1.976222 0.242231 0.691047 1.060202 0.494421 0.415939 14026.620769
9 1.812563 0.973302 1.980000 0.186338 0.647770 0.969539 0.519351 0.469760 14384.672088
7 0.522920 9.706416 1.838284 0.061066 0.425470 1.064318 1.999990 0.804069 18339.419679

Again, the inferred $T$ is not very different, although it varies quite a bit. This model fitting requires a few more runs to achieve convergence (and maybe also a finer grid).

Since the degree of correction does not seem to have a strong influence on parameter estimation, I am going to use only the spectrum corrected with $p=0.35$ in the following.

asymmetric migration rates


In [17]:
def split_asym_mig(params, ns, pts):
    """
    params = (nu1,nu2,T,m1,m2)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na) 
    T: Time in the past of split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,T,m1,m2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, T, nu1, nu2, m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [22]:
cl[:].push(dict(split_asym_mig=split_asym_mig))


Out[22]:
<AsyncResult: _push>

In [23]:
%%px --local

func = split_asym_mig

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [24]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig" # set file name stub for opt. result files
fixed_params = None

In [122]:
%%px --local

# set lower and upper bounds to nu1, nu2, T, m1, m2
upper_bound = [1e4, 1e4, 4, 10, 10] # note, I have increased the upper bound for T
lower_bound = [1e-4, 1e-4, 0, 0, 0]

In [17]:
# using optimal parameters from simple divergence with migration model (split_mig)
p0 = [1.572018, 2.838641, 1.703611, 0.211194, 0.211194]

In [ ]:
#ar_split_asym_mig = lbview.map(run_dadi, repeat(p0, 10))

In [13]:
% ll OUT_2D_models/split_asym_mig_[0-9]*dill


-rw-rw-r-- 1 claudius 332 Jun  2 10:56 OUT_2D_models/split_asym_mig_0.4241_1.1390_0.7188_0.2226_0.1538.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:57 OUT_2D_models/split_asym_mig_0.8165_1.9939_3.9600_0.2674_0.0672.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:57 OUT_2D_models/split_asym_mig_0.8288_1.2529_3.9600_0.1038_0.5631.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:57 OUT_2D_models/split_asym_mig_0.8589_1.6675_0.8569_0.0668_0.0683.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:57 OUT_2D_models/split_asym_mig_0.9537_4.4646_1.7313_0.3606_0.1876.dill
-rw-rw-r-- 1 claudius 331 Jun  2 10:56 OUT_2D_models/split_asym_mig_1.5149_0.8486_1.6356_0.5165_0.2828.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:56 OUT_2D_models/split_asym_mig_2.5504_1.0248_0.6622_0.0589_0.0727.dill
-rw-rw-r-- 1 claudius 331 Jun  2 10:57 OUT_2D_models/split_asym_mig_3.1276_4.2771_2.7252_0.2635_0.1617.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:56 OUT_2D_models/split_asym_mig_3.9939_1.0661_2.0397_0.0975_0.4820.dill
-rw-rw-r-- 1 claudius 332 Jun  2 10:56 OUT_2D_models/split_asym_mig_5.9099_2.2131_0.6241_0.1402_0.0794.dill

In [14]:
ar_split_asym_mig = []

for filename in glob("OUT_2D_models/split_asym_mig_[0-9]*dill"):
    ar_split_asym_mig.append(dill.load(open(filename)))

In [15]:
get_flag_count(ar_split_asym_mig, NM=True)


success 5
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 5
unknown flag 0

In [18]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'T_0', 'm1_0', 'm2_0', 'nu1_opt', 'nu2_opt', 'T_opt', 'm1_opt', 'm2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[18]:
nu1_0 nu2_0 T_0 m1_0 m2_0 nu1_opt nu2_opt T_opt m1_opt m2_opt -logL
2 0.828753 1.252881 3.960000 0.103805 0.563144 1.555405 3.812884 2.343805 0.076152 0.347306 12359.631637
5 2.550436 1.024807 0.662181 0.058917 0.072694 1.534739 3.751096 2.292298 0.077597 0.350747 12359.664337
8 0.424053 1.138974 0.718766 0.222591 0.153777 1.529845 3.758155 2.285943 0.076150 0.352598 12359.684317
7 1.514872 0.848603 1.635625 0.516468 0.282811 1.575057 3.857334 2.388837 0.076225 0.342434 12359.735720
6 3.127599 4.277118 2.725190 0.263502 0.161697 1.693936 4.157804 2.677514 0.070230 0.320827 12360.799448
3 0.816507 1.993877 3.960000 0.267442 0.067155 1.605256 4.027444 2.526903 0.067682 0.341928 12361.932695
9 0.858891 1.667468 0.856949 0.066816 0.068287 1.526397 3.759222 2.351502 0.076778 0.344442 12364.139460
1 3.993936 1.066110 2.039748 0.097478 0.482028 1.614288 3.967013 2.585679 0.059440 0.337259 12382.911919
4 0.953722 4.464633 1.731286 0.360577 0.187640 0.945295 2.572276 1.192388 0.082591 0.561914 12473.567122
0 5.909949 2.213097 0.624062 0.140236 0.079440 0.970340 2.713070 1.103459 0.023247 0.596518 12633.931484

This looks like convergence. Allowing for asymmetric migration rates improves the model fit by 450 logL units and is therefore highly significant.

Interpretation


In [19]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 5:10])
popt


Out[19]:
array([ 1.5554055 ,  3.81288413,  2.34380465,  0.07615248,  0.34730636])

In [25]:
# calculate best-fit model spectrum
model = func_ex(popt, ns, pts_l)

ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[25]:
-12359.631636502287

In [26]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00307.

In [27]:
mu = 3e-9
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:
 255,715.

In [32]:
print "The ancestral population split apart {0:,} generations ago.".format(int(popt[2]*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(popt[0]*N_ref), int(popt[1]*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(popt[3]/2*popt[1])),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(popt[4]/2*popt[0])),
print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(popt[3]/2/N_ref/popt[1]),
print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(popt[4]/2/N_ref/popt[0])


The ancestral population split apart 1,198,692 generations ago. Immediately after the split the ERY population changed to a size of 397,740 and the PAR population to 975,012. Since the split of the ancestral population, PAR received 1 individual from ERY every 6.89 generations, while ERY received 1 PAR individual every 3.70 generations. Put another way: The PAR population contained a constant proportion of 3.91e-08 of new immigrant alleles each generation and the ERY population contained a constant proportion of 4.37e-07 of new immigrant alleles each generation.

The following table compares the inferred parameters for the asymmetric migration model from the uncorrected and corrected spectrum in their absolute units:

parameter uncorrected corrected
$N_{a}$ 410,678 255,715
$N_{ery}$ 378,198 397,740
$N_{par}$ 919,352 975,012
T 1,022,329 1,198,692
$p_{ery->par}$ 4.58e-08 3.91e-08
$p_{par->ery}$ 6.54e-07 4.37e-07
-logL 18104 12359

$N_x$ have unit individuals, T has unit generations, $p_x$ are proportions of new immigrant alleles per generation.

There is marked difference in the inferred size of the ancestral population between corrected and uncorrected spectra. The inferred time of the split in number of generations for the unmodified spectrum was 1,022,329. Not a dramatic difference from the one inferred with this corrected spectrum, but still 176,000 years difference.

residuals


In [136]:
dadi.Plotting.plot_2d_comp_multinom(data=sfs2d, model=model, vmin=1)


Note the asymmetry in the model spectrum for frequency classes [1, x] and [x, 1].

recent bottleneck

At the end of the last Ice Age, about 10 Ky ago, both ERY and PAR expanded from refugial areas across Iberia and across northern, central and western Europe, respectively. This expansion likely happened by long-distance migration, where only very few individuals colonised a new habitat. This should lead to a series of founder events that successively reduced genetic diversity the further away from the refugium.


In [13]:
def split_asym_mig_2epoch(params, ns, pts):
    """
    params = (nu1_1,nu2_1,T1,nu1_2,nu2_2,T2,m1,m2)
    ns = (n1,n2)

    Split into two populations of specified size, with potentially asymmetric migration.
    The split coincides with a stepwise size change in the daughter populations. Then,
    have a second stepwise size change at some point in time after the split. This is
    enforced to happen at the same time for both populations. Migration is assumed to
    be the same during both epochs.

    nu1_1: pop size ratio of pop 1 after split (with respect to Na)
    nu2_1: pop size ratio of pop 2 after split (with respect to Na)
    T1: Time from split to second size change (in units of 2*Na generations)
    nu1_2: pop size ratio of pop 1 after second size change (with respect to Na)
    nu2_2: pop size ratio of pop 2 after second size change (with respect to Na)
    T2: time in past of second size change (in units of 2*Na generations)
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1_1,nu2_1,T1,nu1_2,nu2_2,T2,m1,m2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration for time T1
    phi = dadi.Integration.two_pops(phi, xx, T1, nu1_1, nu2_1, m12=m2, m21=m1)
    
    # divergence with potentially asymmetric migration and different pop size for time T2
    phi = dadi.Integration.two_pops(phi, xx, T2, nu1_2, nu2_2, m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [14]:
cl[:].push(dict(split_asym_mig_2epoch=split_asym_mig_2epoch))


Out[14]:
<AsyncResult: _push>

In [15]:
%%px --local

func = split_asym_mig_2epoch

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [13]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_2epoch" # set file name stub for opt. result files
fixed_params = None

In [16]:
%%px --local

# set lower and upper bounds to nu1, nu2, T, m1, m2
upper_bound = [1e4, 1e4, 6, 1e4, 1e4, 6, 10, 10]
lower_bound = [1e-4, 1e-4, 0, 1e-4, 1e-4, 0, 0, 0]

In [10]:
p0 = [1.0, 3.0, 2.0, 1.5, 3.8, 0.343805, 0.076152, 0.347306]

In [23]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [24]:
ar_split_asym_mig_2epoch.done()


Out[24]:
True

In [25]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [26]:
get_flag_count(ar_split_asym_mig_2epoch, NM=True)


success 0
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 10
unknown flag 0

In [28]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True)


Out[28]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
1 1.517139 3.839154 3.104718 2.384595 2.078017 0.887224 0.119138 0.612784 1.349822 2.680508 1.479428 1.484671 3.946997 0.925030 0.068176 0.385140 12360.313916
0 2.034622 2.100669 1.856093 0.723760 10.510843 0.606915 0.021523 0.362588 1.348205 4.764060 1.643641 0.969748 3.080136 0.305469 0.022376 0.553867 12732.512844
7 0.446626 2.971482 1.571470 3.289626 5.782529 0.673063 0.050825 0.188980 0.695430 3.668885 2.042350 1.508029 4.481278 0.563182 0.036488 0.465102 12739.358352
5 0.754838 1.831002 0.617284 4.105197 10.444836 0.307109 0.027904 0.416920 0.838816 2.343427 0.584088 0.975194 2.071267 0.144070 0.050782 0.411421 12867.011430
9 2.520502 1.701997 5.940000 0.507919 1.814445 0.089194 0.244827 0.187190 1.516443 2.250294 0.931729 0.473556 1.362241 0.026806 0.204474 0.227566 12916.104189
2 1.204710 1.001633 1.705836 4.396476 2.811004 0.808576 0.024495 0.330293 1.303288 1.001036 1.897499 1.452741 3.400614 0.693919 0.176271 0.349019 13141.387630
8 0.266655 3.014680 5.667917 1.961955 4.365611 0.258243 0.044187 1.341105 0.710003 1.954529 0.799596 0.477610 4.100168 0.019434 0.000006 1.018072 13331.274221
3 2.881926 1.081841 2.553740 0.752463 12.690251 0.179814 0.134371 0.994828 0.631769 1.198003 3.161659 0.825795 3.075348 1.030023 0.045686 0.991286 13432.807795
6 0.710008 2.750384 1.058997 0.964009 6.926120 0.651244 0.092537 1.098276 0.776478 4.565232 1.076637 0.936148 3.489957 1.229462 0.001600 0.910505 13657.664327
4 3.945613 8.510530 1.427737 1.636734 6.595499 0.242741 0.106830 1.154538 1.473252 234.768682 2.356055 0.820420 1.652515 0.344892 0.011763 0.956486 15304.495518

This clearly needs refinement.


In [29]:
%%px --local

# reduce perturbation of starting values
fold = 1 # perturb randomly up to `fold` times 2-fold

# increase maximum number of iterations
maxiter = 300 # run a maximum of 300 iterations

In [30]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
popt


Out[30]:
array([ 1.3498224 ,  2.68050753,  1.47942766,  1.48467066,  3.94699747,
        0.92503044,  0.06817574,  0.38513996])

In [ ]:
p0 = popt

In [31]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [32]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [33]:
get_flag_count(ar_split_asym_mig_2epoch, NM=True)


success 0
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 20
unknown flag 0

In [34]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True)


Out[34]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
19 1.839318 3.616469 1.467276 1.274594 1.945696 0.222728 0.124267 0.246549 1.486800 4.583876 2.136617 0.723402 0.327622 0.006737 0.073639 0.384725 12051.103031
13 1.319301 3.236384 1.531698 1.036132 2.279347 0.265553 0.078984 0.560670 1.139871 3.891940 1.499383 0.995821 0.000104 0.000002 0.079275 0.482730 12073.415485
10 1.817135 3.186771 3.542872 2.145577 3.117135 0.250730 0.096144 0.258921 3.161785 8.931487 5.962177 2.552851 2.595051 0.055175 0.039346 0.184596 12153.187341
1 0.982644 4.830369 1.241273 2.541197 2.692586 0.221458 0.100007 0.303752 0.983085 3.404644 1.118720 0.970660 0.618418 0.015989 0.081159 0.561618 12160.411407
4 1.206159 4.837195 1.046501 1.009036 4.724410 0.201027 0.082181 0.690294 0.936367 3.229953 1.018924 1.048898 1.251973 0.036882 0.072748 0.546392 12252.547051
0 1.203048 2.603729 1.309654 1.585668 2.723611 0.584899 0.069167 0.474929 0.807509 2.782213 2.517267 2.987038 7.449213 3.485259 0.040410 0.194276 12308.949582
7 0.552887 3.950197 1.333653 1.466377 6.903053 0.244697 0.062283 0.282985 0.803051 1.983243 1.707723 2.183203 5.478541 2.472911 0.055400 0.264877 12310.744130
6 0.589343 1.581182 3.002917 2.284980 6.982477 0.428508 0.038679 0.178164 0.095361 1.903956 0.987682 1.939750 4.824890 2.438685 0.056907 0.303042 12316.881789
9 1.718134 1.630797 1.548844 1.985774 2.443296 0.218398 0.074314 0.586001 0.948794 2.929665 1.093835 1.640624 4.138537 1.519197 0.066364 0.348871 12328.643301
15 0.541734 3.378483 3.086310 1.509551 3.907376 0.488203 0.073146 0.255963 0.372273 4.357064 2.215619 2.518570 6.451507 2.662493 0.040392 0.242901 12337.072850
3 1.517139 3.839154 3.104718 2.384595 2.078017 0.887224 0.119138 0.612784 1.349822 2.680508 1.479428 1.484671 3.946997 0.925030 0.068176 0.385140 12360.313916
2 2.034622 2.100669 1.856093 0.723760 10.510843 0.606915 0.021523 0.362588 1.348205 4.764060 1.643641 0.969748 3.080136 0.305469 0.022376 0.553867 12732.512844
16 0.446626 2.971482 1.571470 3.289626 5.782529 0.673063 0.050825 0.188980 0.695430 3.668885 2.042350 1.508029 4.481278 0.563182 0.036488 0.465102 12739.358352
12 0.754838 1.831002 0.617284 4.105197 10.444836 0.307109 0.027904 0.416920 0.838816 2.343427 0.584088 0.975194 2.071267 0.144070 0.050782 0.411421 12867.011430
18 2.520502 1.701997 5.940000 0.507919 1.814445 0.089194 0.244827 0.187190 1.516443 2.250294 0.931729 0.473556 1.362241 0.026806 0.204474 0.227566 12916.104189
5 1.204710 1.001633 1.705836 4.396476 2.811004 0.808576 0.024495 0.330293 1.303288 1.001036 1.897499 1.452741 3.400614 0.693919 0.176271 0.349019 13141.387630
17 0.266655 3.014680 5.667917 1.961955 4.365611 0.258243 0.044187 1.341105 0.710003 1.954529 0.799596 0.477610 4.100168 0.019434 0.000006 1.018072 13331.274221
8 2.881926 1.081841 2.553740 0.752463 12.690251 0.179814 0.134371 0.994828 0.631769 1.198003 3.161659 0.825795 3.075348 1.030023 0.045686 0.991286 13432.807795
14 0.710008 2.750384 1.058997 0.964009 6.926120 0.651244 0.092537 1.098276 0.776478 4.565232 1.076637 0.936148 3.489957 1.229462 0.001600 0.910505 13657.664327
11 3.945613 8.510530 1.427737 1.636734 6.595499 0.242741 0.106830 1.154538 1.473252 234.768682 2.356055 0.820420 1.652515 0.344892 0.011763 0.956486 15304.495518

This is interesting and warrants further refinement.


In [35]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
popt


Out[35]:
array([ 1.48679958,  4.58387634,  2.1366173 ,  0.72340225,  0.32762151,
        0.00673683,  0.07363919,  0.38472506])

In [17]:
%%px --local

pts_l = [50, 60, 70]
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [36]:
dadi.Inference.optimize_log?

In [38]:
p0 = popt

In [39]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [11]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [12]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[12]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
39 2.036417 3.478564 3.368522 1.114412 0.417060 0.012055 0.105996 0.636673 1.389305 4.235224 1.889727 0.003110 0.001079 0.000020 0.077585 0.416367 12034.041371
37 2.954759 8.334390 1.179688 0.664450 0.546188 0.010706 0.076452 0.414376 1.398282 4.264537 1.907924 0.000643 0.000221 0.000004 0.077128 0.414165 12034.041492
19 1.367363 2.657288 2.922591 0.461778 0.549573 0.011291 0.117875 0.411837 1.380608 4.204235 1.863648 0.000651 0.000227 0.000004 0.078328 0.418285 12034.050226
3 1.121294 3.587683 2.897749 0.840382 0.334321 0.006196 0.109623 0.222999 1.389217 4.215985 1.877527 0.004926 0.001794 0.000033 0.078193 0.416167 12034.053012
38 1.259361 7.014333 1.412617 0.663463 0.553551 0.007157 0.095175 0.474669 1.383565 4.208778 1.868250 0.002516 0.000877 0.000016 0.078268 0.417087 12034.069858
4 1.386135 6.444557 1.670957 1.034004 0.168414 0.007450 0.055902 0.479453 1.386575 4.215731 1.880704 0.003947 0.001372 0.000025 0.077854 0.417713 12034.138110
21 1.482407 4.420358 3.966858 0.642127 0.218776 0.006825 0.088042 0.456064 1.384805 4.207439 1.875958 0.001396 0.000503 0.000009 0.077537 0.418453 12034.191202
25 1.615985 4.062677 2.226373 0.405551 0.400672 0.007007 0.114637 0.510077 1.400365 4.220466 1.884939 0.015270 0.005881 0.000109 0.078116 0.413200 12034.216669
7 1.641715 2.487105 1.228693 0.948439 0.623843 0.010742 0.067619 0.235514 1.382917 4.207073 1.853326 0.000464 0.000157 0.000003 0.078765 0.415626 12034.218928
49 1.839318 3.616469 1.467276 1.274594 1.945696 0.222728 0.124267 0.246549 1.486800 4.583876 2.136617 0.723402 0.327622 0.006737 0.073639 0.384725 12051.103031

This looks like convergence. Allowing for a second stepwise size change by adding three more parameters ($\nu_{ery_2}$, $\nu_{par_2}$ and $T2$) improves the likelihood by 325 logL units as compared to the asymmetric migration model (best logL -12359).

LRT

This recent bottleneck model can be reduced to the simpler asymmetric migration model by setting T2 to 0. So, in order to compare these two models, I just have to ask whether T2 is significantly different from 0.

I am going to fit the recent bottleneck model to the data with T2 fixed at 0. I expect that it will yield similar best-fit parameters as the asymmetric migration model from above.

I can then do a LRT of the full model vs. the nested model where T2 is set to 0.


In [43]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [50, 60, 70]
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
sfs = sfs2d
perturb = True
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_2epoch_fixedT2_at0" # set file name stub for opt. result files
fixed_params = [None, None, None, None, None, 0.0, None, None] # fix T2

In [42]:
popt = df.sort_values(by='-logL', ascending=True).iloc[0,8:16]
popt


Out[42]:
ery_1_opt    1.389305
par_1_opt    4.235224
T1_opt       1.889727
ery_2_opt    0.003110
par_2_opt    0.001079
T2_opt       0.000020
m1_opt       0.077585
m2_opt       0.416367
Name: 31, dtype: float64

In [49]:
popt = np.array(popt)

In [39]:
asym_mig_popt = [1.5554055 ,  3.81288413,  2.34380465,  0.07615248,  0.34730636]

p_names = ['ery_opt', 'par_opt', 'T_opt', 'm1_opt', 'm2_opt']

for i in range(len(p_names)):
    print p_names[i].rjust(10) + str(asym_mig_popt[i])[:5].rjust(10)


   ery_opt     1.555
   par_opt     3.812
     T_opt     2.343
    m1_opt     0.076
    m2_opt     0.347

The best-fit parameter values from the asymmetric migration model are fairly similar to the best-fit parameters of the recent bottleneck model.


In [50]:
# set p0, T2_0 will be ignored and change of ery_2 or par_2 should not change the likelihood

p0 = popt

In [51]:
#ar_split_asym_mig_2epoch_fixedT2_at0 = lbview.map(run_dadi, repeat(p0, 10))

In [52]:
ar_split_asym_mig_2epoch_fixedT2_at0.done()


Out[52]:
True

In [53]:
ar_split_asym_mig_2epoch_fixedT2_at0 = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_fixedT2_at0*dill"):
    ar_split_asym_mig_2epoch_fixedT2_at0.append(dill.load(open(filename)))

In [54]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch_fixedT2_at0]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[54]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
1 2.063706 2.214529 2.855993 0.005186 0.002023 0.000020 0.096816 0.305681 1.536858 3.765791 2.301985 0.005186 0.002023 0.0 0.077128 0.350888 12359.290971
6 0.941860 7.082065 1.786133 0.003408 0.000740 0.000020 0.093685 0.297781 1.556879 3.811306 2.354597 0.003408 0.000740 0.0 0.075194 0.347374 12359.304204
4 2.373279 2.314574 2.989517 0.002520 0.001035 0.000036 0.092528 0.285019 1.528094 3.758995 2.305390 0.002520 0.001035 0.0 0.076020 0.354800 12359.378449
2 1.513358 6.411139 2.826236 0.004138 0.000738 0.000036 0.054642 0.566541 1.508233 3.701488 2.246019 0.004138 0.000738 0.0 0.077478 0.357999 12359.408070
7 0.822767 2.686000 1.495240 0.003495 0.001867 0.000025 0.039688 0.231194 1.559184 3.803609 2.341995 0.003495 0.001867 0.0 0.076034 0.344389 12359.455559
8 2.033380 3.548028 2.399604 0.002288 0.001547 0.000012 0.108617 0.517564 1.487808 3.643049 2.186184 0.002288 0.001547 0.0 0.078856 0.361348 12359.564938
3 1.084780 2.992688 2.535944 0.006151 0.000730 0.000025 0.094724 0.258144 1.470856 3.592672 2.136808 0.006151 0.000730 0.0 0.080231 0.364053 12359.882096
5 0.734676 3.149199 1.734424 0.005878 0.001262 0.000022 0.057393 0.231822 1.495601 3.659927 2.182066 0.005878 0.001262 0.0 0.079100 0.357031 12359.903276
9 1.662370 3.373400 1.850755 0.001717 0.000659 0.000010 0.083452 0.546353 1.464893 3.577424 2.120236 0.001717 0.000659 0.0 0.080262 0.365519 12359.996631
0 1.873229 5.660204 1.333341 0.002029 0.001043 0.000034 0.093606 0.310017 1.441266 3.619893 2.081778 0.002029 0.001043 0.0 0.078463 0.366427 12363.789915

This seems to be well converged and recovers the best-fit model spectrum from the previous asymmetric migration model fitting very well. I am therefore confident that I can do a simple LRT with weights (0.5, 0.5) for $\chi^2_0$ and $\chi^2_1$.


In [58]:
ll_c = -12034
ll_s = -12359

In [61]:
dadi.Godambe.sum_chi2_ppf?

In [59]:
D = 2*(ll_c - ll_s)
D


Out[59]:
650

In [60]:
dadi.Godambe.sum_chi2_ppf(D, weights=(0.5, 0.5))


Out[60]:
0.0

Allowing for a second epoch with different population sizes after the split significantly improves the fit to the observed data. I very much doubt that there could be enough linkage in the data to compromise this result.

Interpretation


In [13]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
popt


Out[13]:
array([  1.38930513e+00,   4.23522440e+00,   1.88972686e+00,
         3.10953710e-03,   1.07862318e-03,   2.01022845e-05,
         7.75847834e-02,   4.16367492e-01])

In [14]:
# calculate best-fit model spectrum
model = func_ex(popt, ns, pts_l)

ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[14]:
-12034.122592354608

In [45]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00344.

In [47]:
mu = 3e-9
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:
 286,936.

In [49]:
ery_1, par_1, T1, ery_2, par_2, T2, m1, m2 = popt

In [58]:
print "The ancestral population split apart {0:,} generations ago.".format(int((T1+T2)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(ery_1*N_ref), int(par_1*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1*par_1/2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2*ery_1/2)),
#print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(popt[3]/2/N_ref/popt[1]),
#print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(popt[4]/2/N_ref/popt[0])
print "ERY and PAR underwent a second stepwise simultaneous population size change {0:,} generations in the past.".format(int(T2*2*N_ref)),
print "ERY changed to a size of {0:,} individuals and PAR to a size of {1:,} individuals.".format(int(ery_2*N_ref), int(par_2*N_ref))


The ancestral population split apart 1,084,475 generations ago. Immediately after the split the ERY population changed to a size of 398,642 and the PAR population to 1,215,241. Since the split of the ancestral population, PAR received 1 individual from ERY every 6.09 generations, while ERY received 1 PAR individual every 3.46 generations. ERY and PAR underwent a second stepwise simultaneous population size change 11 generations in the past. ERY changed to a size of 892 individuals and PAR to a size of 309 individuals.

The best fit time and strength of the second population size change seem unreasonably recent and severe. Fitting this model to the uncorrected spectrum yields very similar absolute parameter estimates.

Could there be more biologically reasonable parameter combinations with almost equally high likelihood, i. e. could there be a ridge in the parameter space as is common for different combinations of time and strength of population size changes?


In [70]:
6000/2/N_ref


Out[70]:
0.010455269432660168

Let's fix $T2$ at 0.0104, which should correspond to roughly 6,000 generations, i. e. roughly the time after which the expansion of PAR and ERY out of their refugia should have completed, following the major reversal in climate (cooling) of the Younger Dryas (Hewitt1996).


In [75]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [50, 60, 70]
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
sfs = sfs2d
perturb = True
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_2epoch_fixedT2" # set file name stub for opt. result files
fixed_params = [None, None, None, None, None, 0.0104, None, None] # fix T2

In [76]:
dadi.Inference.optimize_log?

In [77]:
# set p0, T2_0 will be ignored

p0 = popt * [1, 1, 1, 1e2, 1e2, 1, 1, 1]

In [79]:
#ar_split_asym_mig_2epoch_fixedT2 = lbview.map(run_dadi, repeat(p0, 10))

In [80]:
ar_split_asym_mig_2epoch_fixedT2 = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_fixedT2*dill"):
    ar_split_asym_mig_2epoch_fixedT2.append(dill.load(open(filename)))

In [81]:
get_flag_count(ar_split_asym_mig_2epoch_fixedT2, NM=False)


success 0
Maximum number of iterations exceeded. 0
Gradient and/or function calls not changing. 10
unknown flag 0

In [82]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch_fixedT2]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True)


Out[82]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
7 0.741314 3.967265 0.980762 0.296385 0.057886 0.000011 0.071243 0.620643 1.416940 4.295209 1.916030 0.701867 0.480964 0.0104 0.077413 0.408132 12052.250800
5 1.180436 7.531309 2.777329 0.245769 0.101922 0.000020 0.104953 0.814964 1.416120 4.293006 1.917056 0.699835 0.481066 0.0104 0.077438 0.408623 12052.253613
4 0.790515 5.096639 2.447776 0.224538 0.119564 0.000026 0.046892 0.394035 1.416030 4.293372 1.917056 0.699521 0.480714 0.0104 0.077456 0.408626 12052.253896
8 0.912329 5.757093 1.788985 0.176138 0.069633 0.000036 0.066547 0.239467 1.415543 4.291364 1.914853 0.699195 0.480706 0.0104 0.077510 0.408604 12052.258897
2 1.303708 3.711466 2.472007 0.435785 0.183292 0.000030 0.056073 0.585713 1.415226 4.288965 1.913321 0.700002 0.480521 0.0104 0.077605 0.408628 12052.261326
6 1.300554 2.456270 1.873246 0.236725 0.092783 0.000040 0.040439 0.279816 1.415161 4.290896 1.914941 0.702141 0.480359 0.0104 0.077546 0.408634 12052.262646
3 1.114886 3.215874 2.548389 0.471836 0.058632 0.000013 0.135669 0.260991 1.414380 4.287184 1.911801 0.699188 0.480569 0.0104 0.077615 0.408823 12052.268699
0 1.353027 4.014201 2.575338 0.436350 0.153644 0.000011 0.050223 0.504350 1.414225 4.285934 1.911377 0.700826 0.481127 0.0104 0.077562 0.408753 12052.271481
1 1.986118 3.010522 2.078727 0.203512 0.133732 0.000029 0.049976 0.330297 1.412950 4.288728 1.911995 0.702392 0.479572 0.0104 0.077546 0.409458 12052.273393
9 2.751682 6.437476 2.215723 0.157496 0.081158 0.000012 0.041833 0.416370 1.414724 4.283311 1.911142 0.699176 0.480491 0.0104 0.077807 0.408501 12052.275292

This seems to be very well converged, but the log likelihood of these parameters (-12052) is 18 logL units below the logL of the previous model, where I allowed T2 to vary freely (-12034).

Are these parameter combinations significantly worse than the previous ones?


In [84]:
ll_s = -12052
ll_c = -12034

In [87]:
D = 2*(ll_c - ll_s)

# using Chi^2 dist. with 1 d.f., since there is one parameter fewer to vary in the
# simple model (T2 is fixed) and the fixed parameter is not fixed to the boundary of
# the parameter space
p = dadi.Godambe.sum_chi2_ppf(D, weights = (0, 1))
p


Out[87]:
1.9731752898266564e-09

Yes, the 2 orders of magnitude lower T2 (corresponding to less than 100 generations) inferred above allows for a significantly better fit than a T2 fixed at 0.0104 (corresponding to about 6,000 generations). This should hold even though potential linkage between SNP's has not been taken into account here.

residuals


In [24]:
dadi.Plotting.plot_2d_comp_multinom(data=sfs2d, model=model, vmin=1)


There is a marked decrease in residuals for low frequency SNP's in PAR that are absent from ERY as compared to the asymmetric migration model. If they are absent from ERY they are likely to be either of recent origin or selected against in ERY. Bottlenecks lead to the extinction of low frequency variants and the fixation of high frequency variants. This can be seen in the SNP category ery:0, par:1, for which the recent bottleneck model predicts far fewer SNP's than the asymmetric migration model.

compare model spectra

I would like to compare the best-fit model spectra of the asymmetric migration and the recent bottleneck model.


In [15]:
recent_bottleneck = dadi.Inference.optimally_scaled_sfs(model, sfs2d)

In [28]:
dadi.Inference.optimally_scaled_sfs?

In [31]:
func_ex = dadi.Numerics.make_extrap_log_func(split_asym_mig)

popt_asym_mig = [1.5554055 ,  3.81288413,  2.34380465,  0.07615248,  0.34730636]

model_asym_mig = func_ex(popt_asym_mig, ns, pts_l)

model_asym_mig = dadi.Inference.optimally_scaled_sfs(model_asym_mig, sfs2d)

In [29]:
#model_asym_mig[10,10] = 300

In [32]:
dadi.Plotting.plot_2d_comp_multinom(data=recent_bottleneck.fold() , model=model_asym_mig.fold(), \
                                    vmin=1, title=['recent bottleneck', 'asym mig'], pop_ids=['ery', 'par'])


In the residual plot, red cells indidcate that the asymmetric migration model predicts more SNP's, blue cells indicate that the recent bottleneck model predicts more SNP's. As can be seen in the plot of the residuals between the recent bottleneck and asymmetric migration model, the asymmetric migration model predicts far more SNP's in frequency categories [0, 1] and [1, 0] than the recent bottleneck model, which is due to the predicted loss of low frequency variants in the latter model. So, the better fit of the recent bottleneck model is mainly due to the better fit to these two SNP frequency categories. I find strange that the recent bottleneck model actually predicts more SNP's in frequency classes ery:0, par: 2-6... . Shouldn't these SNP's also be reduced in frequency after a bottleneck?


In [20]:
resid_rec_bot = sfs2d - recent_bottleneck.fold()

resid_asym_mig = sfs2d - model_asym_mig.fold()

In [24]:
dadi.Plotting.plot_2d_resid?

In [25]:
# plot absolute (non-normalised) residuals for recent bottleneck model

dadi.Plotting.plot_2d_resid(resid_rec_bot)


Blue cells indicate that the recent bottleneck model predicts too many SNP's, red cells indicate that the recent bottleneck predicts too few SNP's as compared to the data. Note that these residuals are not normalised (by the expected Poisson SD of each cell).


In [26]:
# plot absolute (non-normalised) residuals for asymmetric migration model

dadi.Plotting.plot_2d_resid(resid_asym_mig)


The asymmetric migration model predicts far too many SNP's in frequency class [0, 1] and too little in class [0, 2].

Sensitivity to correction

I have already fit the recent bottleneck model to the uncorrected spectrum with essentially identical result as above: an extremely recent and severe bottleneck (see 05_2D_models.ipynb). Ludovic's correction should reduce the effect of false homozygote "calling" due to PCR duplicates and X-chromosomal loci on the SFS. In the following I will fit the recent bottleneck model to the corrected spectra with $p=0.3$ and $p=0.39$. I want to look for a correlation of parameter estimates with degree of correction ($p$).


In [9]:
%%px --local

# load spectrum modified with Ludovic's correction

sfs2d_a = dadi.Spectrum.from_file("EryPar_modified_a.2dsfs") # p=0.30
sfs2d_b = dadi.Spectrum.from_file("EryPar_modified_b.2dsfs") # p=0.39

p = 0.30


In [38]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d_a # use SFS corrected with p=0.30
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_2epoch_p0.30" # set file name stub for opt. result files
fixed_params = None

In [39]:
p0 = [1.0, 3.0, 2.0, 1.5, 3.8, 0.343805, 0.076152, 0.347306]

In [40]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [41]:
ar_split_asym_mig_2epoch.done()


Out[41]:
True

In [42]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [43]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[43]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
3 0.966194 2.825514 0.651041 1.882783 2.759400 0.134432 0.288650 0.362432 1.007763 2.567700 0.913999 1.288886 1.434773 0.003663 0.048293 0.517431 13000.941932
9 0.390691 1.352054 1.835073 1.099399 7.007643 0.373063 0.199019 0.337012 0.450503 1.691831 1.578536 1.146181 3.666307 1.082299 0.051233 0.576593 13040.205000
4 0.432299 3.921815 2.239017 1.505410 5.507222 0.112878 0.038159 1.001663 0.775432 5.686706 1.658855 1.362823 0.204092 0.011080 0.001480 1.001002 13216.516319
1 0.784594 9.027384 5.940000 4.068477 2.178841 0.339527 0.119764 0.128580 0.688162 4.261439 1.882975 1.265844 3.580409 0.207846 0.034386 0.597587 13474.133745
7 0.926273 0.771367 0.784384 1.431524 3.955060 0.366251 0.272184 1.012277 0.897215 0.848304 0.461013 0.655734 2.081114 0.388074 0.050478 1.006183 13537.932437
6 3.194185 2.501796 5.940000 2.248800 1.193532 1.186935 0.048461 0.375804 3.053458 5.656632 0.042852 1.213427 1.723814 1.130862 0.297239 0.228456 13712.959999
8 0.779324 0.943288 2.131753 0.436492 2.731591 0.806821 0.159798 0.260671 0.735152 0.905390 2.366422 1.119712 2.873831 0.726362 0.166671 0.506912 13714.518140
2 1.878973 5.944858 0.594914 2.482757 7.718691 0.295584 0.151847 0.211856 2.444577 11.839728 0.516611 0.997227 1.845752 0.343045 0.095686 0.178965 14116.962846
0 0.372201 1.686994 3.611964 0.809121 1.027351 1.237292 0.198610 0.431748 0.075166 1.164601 4.980324 0.799985 1.030425 0.874000 1.042031 0.311406 15907.139841
5 0.292009 0.853288 5.436893 2.251767 5.052516 0.503178 0.041733 0.115896 0.300227 0.870822 4.926015 2.048674 2.977308 0.808934 0.047730 0.459275 16736.940654

In [44]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[44]:
array([ 1.00776328,  2.56769993,  0.91399855,  1.28888607,  1.43477289,
        0.00366311,  0.04829268,  0.51743105])

In [45]:
p0 = popt

In [46]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [47]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [48]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[48]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
17 2.987957 2.172490 2.369832 4.050110 1.948193 0.003477 0.015474 0.364319 1.550311 4.144047 2.582564 3.704903 2.288352 0.007318 0.047334 0.366703 12796.825080
8 0.586143 2.410319 0.275880 2.045832 1.285935 0.009298 0.087348 0.543292 0.968526 2.469874 1.169219 3.772197 1.428455 0.000287 0.095362 0.528743 12841.352689
2 0.353219 1.725185 1.529709 1.078381 2.305904 0.000961 0.175237 0.197721 1.044363 2.381703 1.467844 1.096069 2.411350 0.000047 0.159023 0.477891 12966.529878
6 0.966194 2.825514 0.651041 1.882783 2.759400 0.134432 0.288650 0.362432 1.007763 2.567700 0.913999 1.288886 1.434773 0.003663 0.048293 0.517431 13000.941932
18 0.390691 1.352054 1.835073 1.099399 7.007643 0.373063 0.199019 0.337012 0.450503 1.691831 1.578536 1.146181 3.666307 1.082299 0.051233 0.576593 13040.205000
1 2.917876 4.435350 0.331745 0.471467 0.635501 0.004015 0.058344 0.189334 1.417580 4.513881 2.328244 0.252327 0.441739 0.001156 0.005056 0.447113 13103.331288
5 0.623423 1.755509 2.582501 1.140842 0.559667 0.006044 0.081382 0.179273 0.917822 3.265770 1.684543 1.115720 0.559070 0.000251 0.066900 0.590947 13119.031545
19 0.333286 1.360442 1.065971 0.476962 5.088490 0.001778 0.017283 0.181684 0.846581 2.191944 1.071256 0.422121 9.965306 0.004491 0.027556 0.718107 13149.615995
7 0.432299 3.921815 2.239017 1.505410 5.507222 0.112878 0.038159 1.001663 0.775432 5.686706 1.658855 1.362823 0.204092 0.011080 0.001480 1.001002 13216.516319
3 0.784594 9.027384 5.940000 4.068477 2.178841 0.339527 0.119764 0.128580 0.688162 4.261439 1.882975 1.265844 3.580409 0.207846 0.034386 0.597587 13474.133745

In [49]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [50]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [51]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [52]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[52]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
14 1.608348 1.773511 3.301692 1.164405 0.836761 0.026187 0.091369 0.586945 1.124680 2.743221 1.165216 1.096592 0.788703 0.012831 0.075651 0.444365 12640.882288
26 2.488574 11.242133 2.086050 2.278667 1.440457 0.014083 0.093528 0.157228 2.182165 5.941925 3.139617 2.480893 1.650124 0.054311 0.094915 0.206518 12703.877157
6 3.324769 3.955670 3.131655 12.535820 1.694982 0.002699 0.038352 0.307711 2.022371 4.972744 3.619055 10.361867 1.762237 0.001581 0.057907 0.263907 12767.700764
28 1.762846 1.931944 1.353063 2.146054 1.707917 0.002725 0.011853 0.309612 1.140257 2.759124 1.465216 1.082101 1.974424 0.000005 0.095580 0.451117 12790.615666
23 2.987957 2.172490 2.369832 4.050110 1.948193 0.003477 0.015474 0.364319 1.550311 4.144047 2.582564 3.704903 2.288352 0.007318 0.047334 0.366703 12796.825080
22 4.180508 7.450161 3.376265 1.401023 4.172412 0.008845 0.039741 0.550652 1.613374 4.329015 2.817906 1.617037 5.295754 0.004604 0.051409 0.351278 12809.028011
7 0.569360 3.571137 2.708391 3.613933 5.612210 0.008435 0.085776 0.569341 1.348514 3.524750 2.185580 4.628675 4.865026 0.000235 0.050655 0.409238 12825.269195
11 0.586143 2.410319 0.275880 2.045832 1.285935 0.009298 0.087348 0.543292 0.968526 2.469874 1.169219 3.772197 1.428455 0.000287 0.095362 0.528743 12841.352689
29 1.090873 3.020919 1.088074 1.719319 2.498412 0.005276 0.054704 0.180408 1.050907 2.527176 1.090908 2.492574 2.156938 0.000266 0.112002 0.451371 12864.927309
3 0.353219 1.725185 1.529709 1.078381 2.305904 0.000961 0.175237 0.197721 1.044363 2.381703 1.467844 1.096069 2.411350 0.000047 0.159023 0.477891 12966.529878

In [53]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [54]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [55]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [56]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[56]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
35 1.375546 6.107024 1.306094 0.322106 0.793372 0.018152 0.123517 0.138904 1.617768 3.161451 1.390068 0.377407 0.750210 0.012906 0.110569 0.346075 12617.161991
17 1.608348 1.773511 3.301692 1.164405 0.836761 0.026187 0.091369 0.586945 1.124680 2.743221 1.165216 1.096592 0.788703 0.012831 0.075651 0.444365 12640.882288
36 2.488574 11.242133 2.086050 2.278667 1.440457 0.014083 0.093528 0.157228 2.182165 5.941925 3.139617 2.480893 1.650124 0.054311 0.094915 0.206518 12703.877157
30 0.510711 8.325801 0.916098 1.273915 0.592655 0.028383 0.098544 0.792519 0.761995 3.149805 0.921135 1.355319 0.590542 0.014946 0.040615 0.724231 12704.108416
9 3.324769 3.955670 3.131655 12.535820 1.694982 0.002699 0.038352 0.307711 2.022371 4.972744 3.619055 10.361867 1.762237 0.001581 0.057907 0.263907 12767.700764
38 1.762846 1.931944 1.353063 2.146054 1.707917 0.002725 0.011853 0.309612 1.140257 2.759124 1.465216 1.082101 1.974424 0.000005 0.095580 0.451117 12790.615666
31 2.987957 2.172490 2.369832 4.050110 1.948193 0.003477 0.015474 0.364319 1.550311 4.144047 2.582564 3.704903 2.288352 0.007318 0.047334 0.366703 12796.825080
29 4.180508 7.450161 3.376265 1.401023 4.172412 0.008845 0.039741 0.550652 1.613374 4.329015 2.817906 1.617037 5.295754 0.004604 0.051409 0.351278 12809.028011
10 0.569360 3.571137 2.708391 3.613933 5.612210 0.008435 0.085776 0.569341 1.348514 3.524750 2.185580 4.628675 4.865026 0.000235 0.050655 0.409238 12825.269195
14 0.586143 2.410319 0.275880 2.045832 1.285935 0.009298 0.087348 0.543292 0.968526 2.469874 1.169219 3.772197 1.428455 0.000287 0.095362 0.528743 12841.352689

In [58]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [57]:
%%px

fold = 1

In [59]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [60]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [61]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[61]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
37 1.480511 6.251794 1.354758 0.320458 0.529562 0.022423 0.125343 0.393065 1.267057 4.010840 1.632934 0.521499 0.406962 0.009664 0.073666 0.471527 12358.822947
5 1.696588 4.274439 2.726914 0.264404 0.401341 0.009285 0.066938 0.359005 1.747805 4.970116 2.621539 0.512439 0.368584 0.007838 0.070899 0.334409 12363.091190
38 1.021383 2.211358 1.228595 0.317582 0.473706 0.010237 0.192197 0.367034 1.031340 3.706615 1.239804 0.582090 0.353347 0.009726 0.062491 0.548701 12401.136878
20 1.986912 5.546716 1.475171 0.349516 0.754059 0.022851 0.182514 0.228409 1.447713 3.721026 1.844210 0.396082 0.763225 0.010411 0.073565 0.423500 12442.784404
1 1.535277 2.359646 0.886409 0.536564 0.537947 0.011953 0.138133 0.206497 0.961692 2.784046 0.862821 0.391307 0.447618 0.009662 0.088601 0.550517 12583.328310
44 1.375546 6.107024 1.306094 0.322106 0.793372 0.018152 0.123517 0.138904 1.617768 3.161451 1.390068 0.377407 0.750210 0.012906 0.110569 0.346075 12617.161991
19 1.608348 1.773511 3.301692 1.164405 0.836761 0.026187 0.091369 0.586945 1.124680 2.743221 1.165216 1.096592 0.788703 0.012831 0.075651 0.444365 12640.882288
43 2.571808 2.751864 0.985362 0.365865 0.669805 0.024114 0.074988 0.497357 1.184529 2.598581 0.983617 0.257991 0.682128 0.007326 0.108871 0.432622 12683.308101
45 2.488574 11.242133 2.086050 2.278667 1.440457 0.014083 0.093528 0.157228 2.182165 5.941925 3.139617 2.480893 1.650124 0.054311 0.094915 0.206518 12703.877157
36 0.510711 8.325801 0.916098 1.273915 0.592655 0.028383 0.098544 0.792519 0.761995 3.149805 0.921135 1.355319 0.590542 0.014946 0.040615 0.724231 12704.108416

In [62]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [63]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [64]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [65]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[65]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
21 1.517697 5.674127 1.625900 0.392829 0.297417 0.007560 0.070800 0.271635 1.385522 4.233354 1.885857 0.277163 0.159022 0.003324 0.078687 0.406521 12337.265456
43 1.480511 6.251794 1.354758 0.320458 0.529562 0.022423 0.125343 0.393065 1.267057 4.010840 1.632934 0.521499 0.406962 0.009664 0.073666 0.471527 12358.822947
7 1.696588 4.274439 2.726914 0.264404 0.401341 0.009285 0.066938 0.359005 1.747805 4.970116 2.621539 0.512439 0.368584 0.007838 0.070899 0.334409 12363.091190
31 2.019503 5.301462 1.472102 0.427931 0.505253 0.012585 0.048007 0.309053 1.413569 3.723008 1.669678 0.374478 0.412765 0.008282 0.101752 0.373483 12397.501775
44 1.021383 2.211358 1.228595 0.317582 0.473706 0.010237 0.192197 0.367034 1.031340 3.706615 1.239804 0.582090 0.353347 0.009726 0.062491 0.548701 12401.136878
38 1.029929 2.068227 1.660360 0.309809 0.535675 0.013417 0.124782 0.433174 1.041592 3.395554 1.259912 0.696779 0.365715 0.009190 0.105346 0.508959 12410.836499
59 0.933334 7.599631 3.036324 0.499832 0.258399 0.013719 0.146140 0.681952 0.939075 3.689442 1.235148 0.480757 0.160543 0.004341 0.058258 0.642776 12426.868154
46 0.802907 3.178778 1.205825 0.276292 0.678517 0.013427 0.057123 0.700260 1.082255 3.598149 1.208740 0.532775 0.467491 0.013312 0.075746 0.569911 12427.009361
6 2.405684 2.567382 1.280826 0.871637 0.317440 0.012551 0.133682 0.410779 1.088985 3.224078 1.335351 0.859522 0.308176 0.006066 0.118193 0.456681 12439.873982
24 1.986912 5.546716 1.475171 0.349516 0.754059 0.022851 0.182514 0.228409 1.447713 3.721026 1.844210 0.396082 0.763225 0.010411 0.073565 0.423500 12442.784404

In [66]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [67]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [68]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [69]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[69]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
24 1.517697 5.674127 1.625900 0.392829 0.297417 0.007560 0.070800 0.271635 1.385522 4.233354 1.885857 0.277163 0.159022 0.003324 0.078687 0.406521 12337.265456
65 1.402683 6.497831 2.943011 0.218670 0.086664 0.003651 0.074061 0.255923 1.605529 5.105338 2.533696 0.252807 0.087400 0.001839 0.063464 0.364367 12353.162118
48 1.480511 6.251794 1.354758 0.320458 0.529562 0.022423 0.125343 0.393065 1.267057 4.010840 1.632934 0.521499 0.406962 0.009664 0.073666 0.471527 12358.822947
7 1.696588 4.274439 2.726914 0.264404 0.401341 0.009285 0.066938 0.359005 1.747805 4.970116 2.621539 0.512439 0.368584 0.007838 0.070899 0.334409 12363.091190
42 1.792997 2.619998 2.648912 0.152481 0.116793 0.003078 0.047093 0.586192 1.519683 4.672882 2.148195 0.174559 0.119933 0.002625 0.075555 0.418068 12365.051811
12 1.016486 2.378703 1.561656 0.284251 0.148082 0.002224 0.153670 0.304922 1.016515 3.737313 1.329082 0.312398 0.083193 0.002116 0.073641 0.551300 12374.241201
52 2.375415 3.712498 3.463597 0.335771 0.155110 0.001898 0.047658 0.367319 1.725757 5.583099 2.947465 0.427399 0.086110 0.001813 0.054908 0.333069 12382.707475
34 2.019503 5.301462 1.472102 0.427931 0.505253 0.012585 0.048007 0.309053 1.413569 3.723008 1.669678 0.374478 0.412765 0.008282 0.101752 0.373483 12397.501775
49 1.021383 2.211358 1.228595 0.317582 0.473706 0.010237 0.192197 0.367034 1.031340 3.706615 1.239804 0.582090 0.353347 0.009726 0.062491 0.548701 12401.136878
41 1.029929 2.068227 1.660360 0.309809 0.535675 0.013417 0.124782 0.433174 1.041592 3.395554 1.259912 0.696779 0.365715 0.009190 0.105346 0.508959 12410.836499

In [70]:
%%px --local

pts_l = [50, 60, 70]
fold = 1
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm

In [71]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [72]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [73]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.30*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [74]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[74]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
58 2.007840 4.159910 2.969165 0.318455 0.118268 0.001924 0.045617 0.467373 1.310031 4.128779 1.751464 0.002138 0.000819 0.000018 0.076835 0.439844 12324.240635
9 0.742256 5.031016 1.932264 0.465791 0.115350 0.002422 0.094853 0.379204 1.314572 4.110578 1.744965 0.000517 0.000202 0.000004 0.078136 0.438302 12324.262151
54 0.977883 7.194290 1.567574 0.365298 0.162493 0.002255 0.130861 0.600420 1.318780 4.154874 1.765112 0.003999 0.001504 0.000033 0.077080 0.434468 12324.299251
59 1.859267 5.150733 1.139473 0.220353 0.194725 0.003389 0.148861 0.570439 1.316351 4.140517 1.767503 0.000801 0.000297 0.000006 0.076759 0.437212 12324.359340
36 2.215081 3.182985 1.099600 0.140081 0.133782 0.001919 0.039491 0.315296 1.337182 4.183216 1.794713 0.003214 0.001284 0.000028 0.076298 0.426995 12324.484095
13 2.038111 8.340012 1.098592 0.171182 0.105611 0.004930 0.050572 0.345695 1.302665 4.059444 1.714147 0.001834 0.000715 0.000016 0.079089 0.437417 12324.520627
61 2.632363 7.839219 1.807742 0.411897 0.140645 0.002168 0.045979 0.571401 1.310214 4.126647 1.764003 0.002097 0.000744 0.000016 0.077325 0.436360 12324.575126
29 0.909794 3.079245 2.991001 0.249020 0.247446 0.002240 0.103598 0.292768 1.332719 4.095973 1.774081 0.000323 0.000142 0.000003 0.077564 0.436192 12324.649656
16 0.882672 2.611032 1.523211 0.317334 0.085485 0.004826 0.105415 0.717399 1.293758 4.057100 1.708134 0.001564 0.000581 0.000013 0.078951 0.441949 12324.702657
51 0.774864 2.878773 2.144576 0.220096 0.105197 0.003259 0.050549 0.573330 1.304526 4.094587 1.758416 0.012266 0.004575 0.000100 0.078211 0.437068 12324.756382

This seems to have converged.

Interpretation


In [75]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[75]:
array([  1.31003064e+00,   4.12877938e+00,   1.75146446e+00,
         2.13762718e-03,   8.18759275e-04,   1.80957038e-05,
         7.68350472e-02,   4.39844234e-01])

In [77]:
# calculate best-fit model spectrum
model = func_ex(popt, ns, pts_l)

ll_model = dadi.Inference.ll_multinom(model, sfs2d_a)
ll_model


Out[77]:
-12324.240634749853

In [78]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d_a)
L = sfs2d_a.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00364.

In [79]:
mu = 3e-9
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:
 303,683.

In [80]:
ery_1, par_1, T1, ery_2, par_2, T2, m1, m2 = popt

In [81]:
print "The ancestral population split apart {0:,} generations ago.".format(int((T1+T2)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(ery_1*N_ref), int(par_1*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1*par_1/2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2*ery_1/2)),
#print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(popt[3]/2/N_ref/popt[1]),
#print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(popt[4]/2/N_ref/popt[0])
print "ERY and PAR underwent a second stepwise simultaneous population size change {0:,} generations in the past.".format(int(T2*2*N_ref)),
print "ERY changed to a size of {0:,} individuals and PAR to a size of {1:,} individuals.".format(int(ery_2*N_ref), int(par_2*N_ref))


The ancestral population split apart 1,063,793 generations ago. Immediately after the split the ERY population changed to a size of 397,834 and the PAR population to 1,253,842. Since the split of the ancestral population, PAR received 1 individual from ERY every 6.30 generations, while ERY received 1 PAR individual every 3.47 generations. ERY and PAR underwent a second stepwise simultaneous population size change 10 generations in the past. ERY changed to a size of 649 individuals and PAR to a size of 248 individuals.

There is not significant change in the inferred timing and size of the recent bottleneck as compared to the fit with the model corrected with p=0.35.

p = 0.39


In [82]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d_b # use SFS corrected with p=0.39
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_2epoch_p0.39" # set file name stub for opt. result files
fixed_params = None

In [83]:
p0 = [1.0, 3.0, 2.0, 1.5, 3.8, 0.343805, 0.076152, 0.347306]

In [84]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [85]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.39*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [86]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[86]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
4 2.765913 10.134643 5.940000 5.085983 10.284720 0.152104 0.099886 0.098605 3.442884 8.744519 5.984803 2.563487 5.300279 0.118924 0.032116 0.175836 12022.585023
1 2.697887 8.333268 1.806789 4.434326 15.067478 0.384399 0.060384 0.306813 2.229980 5.370015 2.696111 1.830515 4.105821 0.262425 0.060354 0.280544 12106.025464
7 0.499548 1.827568 0.970366 3.397407 3.890489 0.841505 0.042460 0.218220 0.551009 2.787484 0.951466 1.318087 3.276022 0.794800 0.078754 0.482699 12159.853479
5 1.239234 4.901691 0.805687 4.088930 1.747918 0.120454 0.151939 0.423579 1.259762 4.547803 0.785661 1.065452 2.292806 0.458290 0.117438 0.431052 12318.479584
6 1.414943 3.759383 0.947202 4.818335 1.421989 0.255525 0.264139 0.217838 1.415238 2.849440 0.948107 0.741425 1.433763 0.037751 0.119368 0.404000 12354.829515
0 0.737892 4.975800 1.034146 1.580240 2.270834 0.095819 0.041362 0.208514 0.736724 4.600674 1.058984 1.152799 2.899745 0.358232 0.009400 0.615308 12416.102796
8 0.530424 1.982397 3.161472 1.367938 13.790973 0.946809 0.050071 0.362302 0.463569 4.531572 1.433726 1.400080 3.994749 0.935761 0.031383 0.574812 12420.721185
2 2.145328 7.928721 4.978674 4.422204 6.947209 0.365465 0.021977 0.595029 2.045451 6.510247 3.517630 1.734874 5.372873 0.284375 0.005748 0.370022 12556.326013
3 1.978690 3.450354 5.940000 0.602525 4.418087 0.141291 0.280366 0.241589 2.763808 3.626301 2.028128 0.677686 3.337183 0.080481 0.130877 0.334726 12674.025173
9 1.299003 11.525040 4.288264 0.844256 7.153146 0.850110 0.026464 0.582140 1.367050 1.786997 0.557076 0.911692 2.856761 0.722430 0.000399 0.709120 12709.998820

In [87]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [88]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [89]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.39*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [90]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[90]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
10 2.765913 10.134643 5.940000 5.085983 10.284720 0.152104 0.099886 0.098605 3.442884 8.744519 5.984803 2.563487 5.300279 0.118924 0.032116 0.175836 12022.585023
3 2.125129 13.528776 3.660096 1.466747 12.563928 0.207612 0.015449 0.051671 3.259937 9.160189 5.996937 1.507127 2.223112 0.052859 0.034812 0.210715 12028.833990
15 1.642061 2.963827 5.940000 1.639599 5.442101 0.059109 0.013930 0.130691 2.119885 6.228304 3.351872 1.587379 2.340430 0.055425 0.029700 0.296195 12049.889726
12 3.162393 7.715058 3.808489 0.653984 17.099581 0.042810 0.107040 0.136657 3.715979 8.022099 5.954637 0.404646 1.972985 0.007669 0.045756 0.163035 12058.595408
4 2.697887 8.333268 1.806789 4.434326 15.067478 0.384399 0.060384 0.306813 2.229980 5.370015 2.696111 1.830515 4.105821 0.262425 0.060354 0.280544 12106.025464
2 1.873441 3.450852 5.940000 4.258908 6.937992 0.401601 0.020729 0.142976 2.440016 7.519212 4.587228 2.556442 6.026293 0.370295 0.037912 0.226155 12115.726577
14 0.499548 1.827568 0.970366 3.397407 3.890489 0.841505 0.042460 0.218220 0.551009 2.787484 0.951466 1.318087 3.276022 0.794800 0.078754 0.482699 12159.853479
6 2.333172 3.553557 2.313086 0.970822 13.909292 0.091205 0.087312 0.044425 1.875624 3.982088 2.756316 0.954472 7.948830 0.005790 0.107252 0.266688 12173.073293
18 5.798642 2.272844 3.060784 1.220567 18.717691 0.049218 0.047174 0.047794 2.115332 4.754819 3.303536 1.096040 36.767181 0.019306 0.057851 0.276555 12198.043142
8 10.324663 4.297054 3.917898 5.124804 1.936735 0.095407 0.010372 0.240685 2.878011 10.542316 5.981536 5.490333 1.375548 0.029949 0.012508 0.198190 12286.747155

In [91]:
%%px --local

pts_l = [50, 60, 70]
fold = 1
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm

In [92]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [93]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [94]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.39*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [95]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[95]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
18 3.519106 4.538282 5.940000 2.309954 7.807893 0.063149 0.024122 0.336024 1.435816 4.311749 1.950320 0.001341 0.000387 0.000006 0.078182 0.403334 11864.673726
10 3.219046 5.983414 5.940000 1.546541 6.282671 0.083897 0.063038 0.091805 1.467599 4.372281 2.003230 0.007414 0.002361 0.000038 0.078167 0.394474 11864.803908
9 5.371376 6.392444 5.940000 1.306625 7.161038 0.113821 0.052170 0.177243 1.440040 4.304808 1.952946 0.020816 0.006203 0.000102 0.078083 0.402119 11864.828459
22 4.348879 10.845096 5.940000 3.345460 3.272365 0.137483 0.050249 0.324739 1.370084 4.325317 1.924085 0.551434 0.021895 0.000374 0.077083 0.409970 11879.272675
15 2.765913 10.134643 5.940000 5.085983 10.284720 0.152104 0.099886 0.098605 3.442884 8.744519 5.984803 2.563487 5.300279 0.118924 0.032116 0.175836 12022.585023
4 2.125129 13.528776 3.660096 1.466747 12.563928 0.207612 0.015449 0.051671 3.259937 9.160189 5.996937 1.507127 2.223112 0.052859 0.034812 0.210715 12028.833990
23 1.642061 2.963827 5.940000 1.639599 5.442101 0.059109 0.013930 0.130691 2.119885 6.228304 3.351872 1.587379 2.340430 0.055425 0.029700 0.296195 12049.889726
17 3.162393 7.715058 3.808489 0.653984 17.099581 0.042810 0.107040 0.136657 3.715979 8.022099 5.954637 0.404646 1.972985 0.007669 0.045756 0.163035 12058.595408
6 2.697887 8.333268 1.806789 4.434326 15.067478 0.384399 0.060384 0.306813 2.229980 5.370015 2.696111 1.830515 4.105821 0.262425 0.060354 0.280544 12106.025464
3 1.873441 3.450852 5.940000 4.258908 6.937992 0.401601 0.020729 0.142976 2.440016 7.519212 4.587228 2.556442 6.026293 0.370295 0.037912 0.226155 12115.726577

In [96]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [97]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10))

In [98]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_p0.39*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [99]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[99]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
19 0.753493 3.177695 2.266137 0.001311 0.000300 0.000008 0.155613 0.298144 1.450217 4.343478 1.976594 0.000355 0.000109 0.000002 0.078303 0.400368 11864.546101
29 1.547520 7.328486 1.664992 0.001709 0.000510 0.000009 0.077046 0.490893 1.447343 4.335819 1.971739 0.000450 0.000133 0.000002 0.078132 0.400524 11864.597890
30 1.791940 7.068825 1.119770 0.001971 0.000550 0.000003 0.075599 0.762534 1.438280 4.315425 1.953058 0.000449 0.000133 0.000002 0.078408 0.402342 11864.643253
21 3.519106 4.538282 5.940000 2.309954 7.807893 0.063149 0.024122 0.336024 1.435816 4.311749 1.950320 0.001341 0.000387 0.000006 0.078182 0.403334 11864.673726
11 3.219046 5.983414 5.940000 1.546541 6.282671 0.083897 0.063038 0.091805 1.467599 4.372281 2.003230 0.007414 0.002361 0.000038 0.078167 0.394474 11864.803908
10 5.371376 6.392444 5.940000 1.306625 7.161038 0.113821 0.052170 0.177243 1.440040 4.304808 1.952946 0.020816 0.006203 0.000102 0.078083 0.402119 11864.828459
28 0.964835 2.775654 2.965511 0.001563 0.000760 0.000003 0.046644 0.262169 1.435314 4.338598 1.965184 0.000388 0.000105 0.000002 0.076635 0.405301 11864.841101
31 1.268142 5.665663 1.587825 0.000881 0.000280 0.000004 0.110326 0.357539 1.424783 4.219118 1.912917 0.000335 0.000103 0.000002 0.081783 0.406963 11865.645103
25 0.848046 7.202555 2.219392 0.001151 0.000722 0.000012 0.067402 0.528426 1.448013 4.221249 1.937815 0.000272 0.000106 0.000002 0.081210 0.398585 11866.264628
24 1.216115 7.508575 1.010461 0.000769 0.000292 0.000006 0.091080 0.470078 1.404937 4.284692 1.902138 0.000371 0.000101 0.000002 0.078912 0.411827 11866.481736

This looks converged.

Interpretation


In [100]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[100]:
array([  1.45021670e+00,   4.34347842e+00,   1.97659428e+00,
         3.54836897e-04,   1.08654406e-04,   1.81290056e-06,
         7.83029568e-02,   4.00368268e-01])

In [101]:
# calculate best-fit model spectrum
model = func_ex(popt, ns, pts_l)

ll_model = dadi.Inference.ll_multinom(model, sfs2d_b)
ll_model


Out[101]:
-11864.54610124437

In [102]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d_b)
L = sfs2d_b.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00331.

In [103]:
mu = 3e-9
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:
 275,939.

In [104]:
ery_1, par_1, T1, ery_2, par_2, T2, m1, m2 = popt

In [105]:
print "The ancestral population split apart {0:,} generations ago.".format(int((T1+T2)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(ery_1*N_ref), int(par_1*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1*par_1/2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2*ery_1/2)),
#print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(popt[3]/2/N_ref/popt[1]),
#print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(popt[4]/2/N_ref/popt[0])
print "ERY and PAR underwent a second stepwise simultaneous population size change {0:,} generations in the past.".format(int(T2*2*N_ref)),
print "ERY changed to a size of {0:,} individuals and PAR to a size of {1:,} individuals.".format(int(ery_2*N_ref), int(par_2*N_ref))


The ancestral population split apart 1,090,841 generations ago. Immediately after the split the ERY population changed to a size of 400,172 and the PAR population to 1,198,537. Since the split of the ancestral population, PAR received 1 individual from ERY every 5.88 generations, while ERY received 1 PAR individual every 3.44 generations. ERY and PAR underwent a second stepwise simultaneous population size change 1 generations in the past. ERY changed to a size of 97 individuals and PAR to a size of 29 individuals.

This corrected spectrum infers the most recent and strongest bottleneck of all versions of the observed SFS's.

Conclusion

The degree of correction has no consistent effect on the parameter estimates of the recent bottleneck model.

p T2 ery_2 par_2
0.0 6 142 74
0.30 10 649 248
0.35 11 892 309
0.39 1 97 29

In [107]:
# get the difference between the two corrected spectra
# sfs2d_a: p=0.30
# sfs2d_b: p=0.39

resid = sfs2d_b - sfs2d_a

In [108]:
dadi.Plotting.plot_2d_resid(resid)


Red cells indicate p=0.39 has higher count, blue indicates p=0.30 has higher count. This shows that the two corrected spectra mainly differ in the cells [0, 1] and [0, 2]. I had expected that a greater degree of correction would lead to estimation of a less severe bottleneck as that SFS has a higher count in [0, 1] which I had expected to be reduced by a bottleneck.

refit recent bottleneck model with moments


In [1]:
from ipyparallel import Client

cl = Client()

cl.ids


Out[1]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [14]:
lbview = cl.load_balanced_view()

In [3]:
from glob import glob
import dill
from utility_functions import *
import pandas as pd
# turn on floating point division by default, old behaviour via '//'
from __future__ import division
from itertools import repeat

In [4]:
%matplotlib inline

import pylab

pylab.rcParams['figure.figsize'] = [12, 10]
pylab.rcParams['font.size'] = 14

In [17]:
%%px --local

# run whole cell on all engines a well as in the local IPython session

import numpy as np

import sys

sys.path.insert(0, '/usr/local/lib/python2.7/dist-packages/moments-1.0.0-py2.7.egg')

import moments

In [6]:
%%px --local

# load spectrum modified with Ludovic's correction, p=35

sfs2d = moments.Spectrum.from_file("EryPar_modified.2dsfs")

In [7]:
def split_asym_mig_2epoch(params, ns):
    """
    params = (nu1_1,nu2_1,T1,nu1_2,nu2_2,T2,m1,m2)
    ns = (n1,n2)

    Split into two populations of specified size, with potentially asymmetric migration.
    The split coincides with a stepwise size change in the daughter populations. Then,
    have a second stepwise size change at some point in time after the split. This is
    enforced to happen at the same time for both populations. Migration is assumed to
    be the same during both epochs.

    nu1: pop size ratio of pop 1 after split (with respect to Na)
    nu2: pop size ratio of pop 2 after split (with respect to Na)
    T1: Time from split to second size change (in units of 2*Na generations)
    nu1_2: pop size ratio of pop 1 after second size change (with respect to Na)
    nu2_2: pop size ratio of pop 2 after second size change (with respect to Na)
    T2: time in past of second size change (in units of 2*Na generations)
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    n1,n2: Sample sizes of resulting Spectrum
    """
    nu1,nu2,T1,nu1_2,nu2_2,T2,m1,m2 = params

    # fs for equilibrium ancestral population
    sts = moments.LinearSystem_1D.steady_state_1D(ns[0] + ns[1])
    fs = moments.Spectrum(sts)
    
    # split
    fs = moments.Manips.split_1D_to_2D(fs, ns[0], ns[1]) 
    
    # divergence with potentially asymmetric migration for time T1
    fs.integrate([nu1, nu2], T1,  m=np.array([[0, m2], [m1, 0]]))
    
    # divergence with potentially asymmetric migration and different pop size for time T2
    fs.integrate([nu1_2, nu2_2], T2, m=np.array([[0, m2], [m1, 0]]))

    return fs

In [8]:
cl[:].push(dict(split_asym_mig_2epoch=split_asym_mig_2epoch))


Out[8]:
<AsyncResult: _push>

In [9]:
def run_moments(p_init): # for the function to be called with map, it needs to have one input variable
    """
    p_init: initial parameter values to run optimisation from
    """
    if perturb == True:
        p_init = moments.Misc.perturb_params(p_init, fold=fold, 
                                      upper_bound=upper_bound, lower_bound=lower_bound)
        # note upper_bound and lower_bound variables are expected to be in the namespace of each engine
    # run optimisation of paramters
    popt = moments_opt_func(p0=p_init, data=sfs, model_func=func, \
                                   lower_bound=lower_bound, upper_bound=upper_bound, \
                                   verbose=verbose, maxiter=maxiter, full_output=full_output, \
                                    fixed_params=fixed_params)
    # pickle to file
    import dill
    name = outname[:] # make copy of file name stub!
    for p in p_init:
        name += "_%.4f" % (p)
    with open(name + ".dill", "w") as fh:
        dill.dump((p_init, popt), fh)
    
    return p_init, popt

In [10]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
func = split_asym_mig_2epoch
moments_opt_func = moments.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/moments/split_asym_mig_2epoch" # set file name stub for opt. result files
fixed_params = None

In [11]:
%%px --local

# set lower and upper bounds to nu1, nu2, T, m1, m2
upper_bound = [1e4, 1e4, 6, 1e4, 1e4, 6, 10, 10]
lower_bound = [1e-4, 1e-4, 0, 1e-4, 1e-4, 0, 0, 0]

In [12]:
p0 = [1.0, 3.0, 2.0, 1.5, 3.8, 0.343805, 0.076152, 0.347306]

In [18]:
#ar_split_asym_mig_2epoch = lbview.map(run_moments, repeat(p0, 10), block=False)

In [19]:
ar_split_asym_mig_2epoch.done()


Out[19]:
True

In [20]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/moments/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [21]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[21]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
1 1.166319 6.091018 1.903189 0.718073 6.789526 0.186542 0.023627 0.424518 1.155670 3.858552 3.521522 0.800730 0.872468 0.031812 0.079731 0.506932 12143.773454
9 0.801744 2.458384 1.259793 2.651817 15.014489 0.293287 0.049725 0.406725 1.069802 2.724609 1.671932 0.623023 0.428276 0.010177 0.095005 0.436272 12354.505497
2 0.720310 5.666346 1.176632 0.913297 1.149935 1.252569 0.283201 0.792531 1.786922 0.643009 0.706861 0.799720 2.169728 1.514715 0.149630 0.662263 12509.596994
7 0.874859 11.235513 2.793599 5.683155 1.484682 0.172428 0.042714 0.540337 0.864228 2.490974 1.927315 2.474652 1.688239 0.017064 0.091155 0.561033 12520.920232
3 2.026250 2.725070 1.376205 0.728722 1.464292 0.384750 0.026406 0.189530 1.000082 3.858751 1.512211 0.713120 1.860884 0.224391 0.068139 0.597680 12647.633729
5 1.160197 4.446896 5.240948 1.818096 3.917496 0.086486 0.059993 0.946479 0.753504 2.388221 1.883087 0.989754 0.028429 0.000003 0.001413 0.854375 12901.283120
0 0.986799 3.290110 0.639857 4.523124 9.424985 0.557618 0.236673 0.099859 0.983232 3.176785 0.887905 0.810746 1.776262 0.285893 0.010127 0.553136 12930.754282
6 0.363544 1.729249 4.670430 0.588304 3.212875 0.508478 0.208189 1.160446 0.507291 1.844103 1.102256 0.682090 1.933360 0.291109 0.045213 1.066360 13138.350884
4 1.921779 4.519132 5.940000 1.508477 2.829546 0.164641 0.301646 1.090703 0.841322 12.642782 0.098960 0.829790 3.315885 2.755248 0.000918 0.973874 13382.352829
8 2.021418 10.453109 1.330325 4.018033 2.577741 0.446530 0.023543 1.153083 2.040576 14.587621 1.420533 0.643306 2.451781 1.026895 0.000458 1.160611 13711.657953

In [22]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[22]:
array([ 1.15566958,  3.85855227,  3.52152155,  0.80072976,  0.87246825,
        0.03181178,  0.07973101,  0.50693213])

In [23]:
p0 = popt

In [24]:
#ar_split_asym_mig_2epoch = lbview.map(run_moments, repeat(p0, 10), block=False)

In [25]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/moments/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [26]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[26]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
6 3.954600 7.093978 2.181538 0.804843 0.866416 0.055028 0.031254 0.153988 1.511678 4.907849 4.543101 0.835133 0.717152 0.020051 0.049563 0.408404 12098.632714
1 1.166319 6.091018 1.903189 0.718073 6.789526 0.186542 0.023627 0.424518 1.155670 3.858552 3.521522 0.800730 0.872468 0.031812 0.079731 0.506932 12143.773454
2 2.805691 2.919456 5.940000 0.331953 1.024543 0.031484 0.079330 0.301905 2.153523 4.318613 5.093349 0.314559 1.019993 0.009935 0.096794 0.232167 12290.750899
17 0.801744 2.458384 1.259793 2.651817 15.014489 0.293287 0.049725 0.406725 1.069802 2.724609 1.671932 0.623023 0.428276 0.010177 0.095005 0.436272 12354.505497
3 0.720310 5.666346 1.176632 0.913297 1.149935 1.252569 0.283201 0.792531 1.786922 0.643009 0.706861 0.799720 2.169728 1.514715 0.149630 0.662263 12509.596994
14 0.874859 11.235513 2.793599 5.683155 1.484682 0.172428 0.042714 0.540337 0.864228 2.490974 1.927315 2.474652 1.688239 0.017064 0.091155 0.561033 12520.920232
7 2.026250 2.725070 1.376205 0.728722 1.464292 0.384750 0.026406 0.189530 1.000082 3.858751 1.512211 0.713120 1.860884 0.224391 0.068139 0.597680 12647.633729
19 0.908265 15.354357 1.947531 2.659964 2.358056 0.026313 0.272578 0.171256 0.930185 2.168652 1.379935 3.954874 2.529788 0.006234 0.109830 0.476803 12724.997998
5 0.737743 12.151369 3.153650 1.166852 0.624965 0.010514 0.222093 1.066703 0.641025 3.413343 1.859214 1.090370 0.524057 0.023801 0.056496 1.053854 12772.522461
12 1.160197 4.446896 5.240948 1.818096 3.917496 0.086486 0.059993 0.946479 0.753504 2.388221 1.883087 0.989754 0.028429 0.000003 0.001413 0.854375 12901.283120

In [27]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [30]:
%%px

fold = 1
moments_opt_func = moments.Inference.optimize_log # uses BFGS algorithm

In [29]:
moments.Inference.optimize_log?

In [31]:
#ar_split_asym_mig_2epoch = lbview.map(run_moments, repeat(p0, 10), block=False)

In [32]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/moments/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [33]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[33]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
11 2.210337 2.985213 3.705641 0.497056 1.103570 0.013065 0.049566 0.521434 1.373427 4.180751 3.526226 0.001421 0.000508 0.000015 0.077543 0.420813 12023.106298
18 2.179338 3.567431 5.940000 0.426539 1.115303 0.028243 0.091346 0.355062 1.507946 4.631555 4.474103 0.003893 0.001486 0.000043 0.068057 0.397553 12031.111343
2 2.039087 3.568643 5.774326 0.501551 0.580407 0.015703 0.028140 0.691854 1.499282 4.625325 4.367827 0.021451 0.007667 0.000224 0.067954 0.398568 12031.381273
22 1.059134 5.178381 2.724846 0.875437 0.582060 0.016742 0.025718 0.476754 1.316858 3.948084 3.087998 0.001832 0.000595 0.000018 0.085183 0.420928 12034.175186
0 1.865848 4.268062 4.263036 0.581722 0.585069 0.035405 0.081745 0.238608 1.314737 3.982931 3.092664 0.053678 0.016351 0.000501 0.085803 0.420339 12035.191078
28 1.947091 6.813402 4.212244 0.618612 0.736624 0.015425 0.049829 0.430970 1.670425 5.157176 5.217630 0.000858 0.000325 0.000009 0.059891 0.367036 12047.317739
5 1.851755 3.683058 4.605440 0.513461 0.378822 0.010029 0.051413 0.503354 1.671723 5.171693 5.187103 0.002635 0.000965 0.000027 0.059800 0.366297 12047.368156
23 2.076986 5.842034 5.884704 1.322662 0.380078 0.018812 0.028602 0.439418 1.808943 5.722572 5.978196 0.867536 0.496347 0.016848 0.051727 0.354272 12090.051183
9 3.954600 7.093978 2.181538 0.804843 0.866416 0.055028 0.031254 0.153988 1.511678 4.907849 4.543101 0.835133 0.717152 0.020051 0.049563 0.408404 12098.632714
3 1.166319 6.091018 1.903189 0.718073 6.789526 0.186542 0.023627 0.424518 1.155670 3.858552 3.521522 0.800730 0.872468 0.031812 0.079731 0.506932 12143.773454

In [34]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
p0 = popt

In [35]:
#ar_split_asym_mig_2epoch = lbview.map(run_moments, repeat(p0, 10), block=False)

In [36]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/moments/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [37]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[37]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
13 2.210337 2.985213 3.705641 0.497056 1.103570 0.013065 0.049566 0.521434 1.373427 4.180751 3.526226 0.001421 0.000508 0.000015 0.077543 0.420813 12023.106298
31 1.546412 3.684345 4.010788 0.000946 0.000300 0.000023 0.061630 0.263759 1.365578 4.158308 3.497482 0.000298 0.000107 0.000003 0.077438 0.423530 12023.209360
2 1.146240 4.660025 2.837994 0.000836 0.000323 0.000019 0.070034 0.275765 1.372330 4.214945 3.571869 0.000364 0.000104 0.000003 0.076596 0.418547 12024.127124
8 1.005195 6.473316 2.118756 0.001332 0.000523 0.000018 0.132251 0.391543 1.352550 4.260343 3.735974 0.000338 0.000100 0.000003 0.076429 0.426938 12024.817831
27 1.084511 6.383912 2.267103 0.002776 0.000547 0.000010 0.123661 0.763796 1.342638 4.016390 3.438558 0.000367 0.000120 0.000004 0.087312 0.412787 12029.665111
35 1.031424 4.163382 5.219708 0.002025 0.000755 0.000021 0.081201 0.309753 1.505651 4.646954 4.491630 0.000277 0.000100 0.000003 0.067371 0.397936 12031.042268
21 2.061519 2.805499 4.346073 0.002782 0.000325 0.000011 0.043943 0.373756 1.509959 4.654292 4.448408 0.000656 0.000246 0.000007 0.066882 0.397543 12031.075471
20 2.179338 3.567431 5.940000 0.426539 1.115303 0.028243 0.091346 0.355062 1.507946 4.631555 4.474103 0.003893 0.001486 0.000043 0.068057 0.397553 12031.111343
3 2.039087 3.568643 5.774326 0.501551 0.580407 0.015703 0.028140 0.691854 1.499282 4.625325 4.367827 0.021451 0.007667 0.000224 0.067954 0.398568 12031.381273
32 1.011119 4.814007 2.222076 0.001843 0.000445 0.000010 0.105426 0.729558 1.472713 4.641932 4.395641 0.000312 0.000106 0.000003 0.067439 0.405406 12031.700519

This seems to have converged. The best-fit parameter combination inferred with dadi had a 0logL of 12,034. So the moments model spectrum fits slightly better.

Interpretation


In [38]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
popt


Out[38]:
array([  1.37342738e+00,   4.18075145e+00,   3.52622600e+00,
         1.42068769e-03,   5.07549604e-04,   1.49128830e-05,
         7.75426428e-02,   4.20813371e-01])

In [39]:
# calculate best-fit model spectrum
model = split_asym_mig_2epoch(popt, ns)

ll_model = moments.Inference.ll_multinom(model, sfs2d)
ll_model


Out[39]:
-12023.106298258697

In [40]:
theta = moments.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00347.

In [41]:
mu = 3e-9
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The effective size of the ancestral population of ERY and PAR (in number of diploid individuals) implied by this theta is:
 289,051.

In [42]:
ery_1, par_1, T1, ery_2, par_2, T2, m1, m2 = popt

In [43]:
print "The ancestral population split apart {0:,} generations ago.".format(int((T1+T2)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(ery_1*N_ref), int(par_1*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1*par_1/2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2*ery_1/2)),
#print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(popt[3]/2/N_ref/popt[1]),
#print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(popt[4]/2/N_ref/popt[0])
print "ERY and PAR underwent a second stepwise simultaneous population size change {0:,} generations in the past.".format(int(T2*2*N_ref)),
print "ERY changed to a size of {0:,} individuals and PAR to a size of {1:,} individuals.".format(int(ery_2*N_ref), int(par_2*N_ref))


The ancestral population split apart 2,038,531 generations ago. Immediately after the split the ERY population changed to a size of 396,991 and the PAR population to 1,208,452. Since the split of the ancestral population, PAR received 1 individual from ERY every 6.17 generations, while ERY received 1 PAR individual every 3.46 generations. ERY and PAR underwent a second stepwise simultaneous population size change 8 generations in the past. ERY changed to a size of 410 individuals and PAR to a size of 146 individuals.

Note the remarkable difference in the inferred timing of the split between ERY and PAR between moments and dadi. dadi had inferred a time of split of 1,084,475 generations. So moments infers a split time twice as old! The bottleneck inferred with moments is even slightly more extreme in time and size than the one inferred with dadi.

Residual


In [46]:
moments.Plotting.plot_2d_comp_multinom(model, sfs2d, vmin=1)


compare moments and dadi spectrum


In [48]:
def split_asym_mig_2epoch(params, ns, pts):
    """
    params = (nu1_1,nu2_1,T1,nu1_2,nu2_2,T2,m1,m2)
    ns = (n1,n2)

    Split into two populations of specified size, with potentially asymmetric migration.
    The split coincides with a stepwise size change in the daughter populations. Then,
    have a second stepwise size change at some point in time after the split. This is
    enforced to happen at the same time for both populations. Migration is assumed to
    be the same during both epochs.

    nu1_1: pop size ratio of pop 1 after split (with respect to Na)
    nu2_1: pop size ratio of pop 2 after split (with respect to Na)
    T1: Time from split to second size change (in units of 2*Na generations)
    nu1_2: pop size ratio of pop 1 after second size change (with respect to Na)
    nu2_2: pop size ratio of pop 2 after second size change (with respect to Na)
    T2: time in past of second size change (in units of 2*Na generations)
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1_1,nu2_1,T1,nu1_2,nu2_2,T2,m1,m2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration for time T1
    phi = dadi.Integration.two_pops(phi, xx, T1, nu1_1, nu2_1, m12=m2, m21=m1)
    
    # divergence with potentially asymmetric migration and different pop size for time T2
    phi = dadi.Integration.two_pops(phi, xx, T2, nu1_2, nu2_2, m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [52]:
func_ex = dadi.Numerics.make_extrap_log_func(split_asym_mig_2epoch)

popt_dadi = [1.38930513e+00, 4.23522440e+00, 1.88972686e+00, 3.10953710e-03, 1.07862318e-03, 2.01022845e-05, 7.75847834e-02, 4.16367492e-01]

pts_l = [50, 60, 70]

model_dadi = func_ex(popt_dadi, ns, pts_l)

model_dadi = dadi.Inference.optimally_scaled_sfs(model_dadi, sfs2d)

model_moments = dadi.Inference.optimally_scaled_sfs(model, sfs2d)

In [53]:
dadi.Plotting.plot_2d_comp_multinom(data=model_moments.fold() , model=model_dadi.fold(), \
                                    vmin=1, title=['moments', 'dadi'], pop_ids=['ery', 'par'])


The model spectra from dadi and moments are very similar. I don't understand how the inferred times of split could be such divergent between the two programmes.

mask and refit recent bottleneck

Due to extremely low effective coverage, i. e. when ignoring PCR duplicates, it is very likely that rare alleles are underrepresented in the 2D SFS. Although Liu2015 (supplementary notes) caution against the usage and interpretation of SFSs with certain frequency classes excluded, in the following I am going to fit the recent bottleneck model to a 2D spectrum with the singleton classes excluded. This is expected to greatly reduce power to detect recent demographic history. I would like to see whether a recent bottleneck can still be inferred, which would greatly corroborate its existence, or not, which would indicate that the inference of the bottleneck is totally dependent on SNP frequency classes that are probably underrepresented due to failed detection of singletons.


In [11]:
sfs2d.mask[1, :] = True
sfs2d.mask[:, 1] = True

In [12]:
dadi.Plotting.plot_single_2d_sfs(sfs2d, vmin=1, cmap=pylab.cm.jet)


Out[12]:
<matplotlib.colorbar.Colorbar at 0x7fbee96a4410>

In [16]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_2epoch_maskSingletons" # set file name stub for opt. result files
fixed_params = None

In [19]:
%%px --local

# set lower and upper bounds to nu1, nu2, T, m1, m2
upper_bound = [1e4, 1e4, 6, 1e4, 1e4, 6, 10, 10]
lower_bound = [1e-4, 1e-4, 0, 1e-4, 1e-4, 0, 0, 0]

In [20]:
p0 = [1.0, 3.0, 2.0, 1.5, 3.8, 0.343805, 0.076152, 0.347306]

In [21]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [22]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_maskSingletons*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [23]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True)


Out[23]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
4 3.472558 8.877600 5.940000 1.428461 4.279664 0.737375 0.043860 0.151860 2.689666 5.161546 3.013242 1.959265 5.205570 0.704929 0.060358 0.249054 12414.052873
9 1.372045 1.945228 2.310813 0.419535 2.735563 0.707072 0.218654 0.195930 1.641239 2.519337 1.796483 1.608264 4.111332 0.614238 0.101852 0.327397 12467.086584
3 3.894324 1.610432 0.742450 2.192734 6.536958 0.553018 0.072530 0.162753 4.593103 2.528287 0.645438 1.101989 2.630765 0.631260 0.147307 0.348404 12579.493319
7 0.395718 5.866022 3.942392 2.834233 3.050274 0.191730 0.028473 0.335745 1.575972 4.712807 2.907966 1.662868 4.015840 0.021008 0.010623 0.404350 12712.581995
8 2.191479 0.810289 0.505648 1.649174 12.701902 0.566618 0.074911 0.451647 2.921493 0.822067 0.545079 1.269401 2.610621 0.588188 0.238729 0.210254 12894.633455
1 1.066295 10.275762 2.482497 3.163844 2.981569 0.203166 0.104001 0.111601 1.038528 4.610523 2.894502 2.961418 3.823159 0.079082 0.050662 0.444970 13219.509277
5 1.831279 8.047667 4.200743 2.832945 10.836246 0.728540 0.260295 1.000670 3.193449 1.068050 0.917993 0.663008 2.608679 0.607744 0.023729 1.000429 13232.343002
0 2.095947 2.711442 0.511448 5.468776 4.312891 0.407056 0.094835 0.087649 2.737536 3.930381 0.445944 0.762454 2.257901 0.291361 0.074195 0.407260 13376.383268
2 0.273207 2.072550 2.357865 0.669779 2.076132 0.661241 0.027912 1.356301 0.130333 1.628830 0.978070 0.890628 2.662790 0.502117 0.004508 1.001156 13479.523659
6 0.763396 5.730645 5.303157 2.464220 1.368763 0.381493 0.026181 1.331167 0.972628 3.258206 1.146730 0.590677 1.675998 0.093702 0.001509 1.256463 13714.013448

In [24]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
p0 = popt

In [25]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [26]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_maskSingletons*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [28]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[28]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
15 0.729195 2.347042 5.554511 6.972959 15.699136 2.380180 0.181868 0.541870 1.109178 2.445168 1.888700 2.340818 6.014737 2.538201 0.064304 0.244373 12357.408662
0 1.509882 14.965229 2.508661 4.872687 3.738354 0.583032 0.051372 0.077091 1.865330 5.770621 3.438538 2.204560 4.860245 0.476004 0.049829 0.273144 12392.971225
4 4.129192 10.644087 5.940000 2.064253 2.935639 1.068479 0.059677 0.081940 3.207155 4.280207 3.110356 2.319725 5.591939 1.086097 0.069298 0.207377 12409.846879
10 3.472558 8.877600 5.940000 1.428461 4.279664 0.737375 0.043860 0.151860 2.689666 5.161546 3.013242 1.959265 5.205570 0.704929 0.060358 0.249054 12414.052873
18 1.372045 1.945228 2.310813 0.419535 2.735563 0.707072 0.218654 0.195930 1.641239 2.519337 1.796483 1.608264 4.111332 0.614238 0.101852 0.327397 12467.086584
3 0.825031 3.615534 1.249947 1.114932 6.217735 0.854882 0.122393 0.211120 0.817329 3.058850 1.231432 1.186813 3.421493 0.850733 0.052639 0.509384 12478.780759
8 3.894324 1.610432 0.742450 2.192734 6.536958 0.553018 0.072530 0.162753 4.593103 2.528287 0.645438 1.101989 2.630765 0.631260 0.147307 0.348404 12579.493319
16 4.780134 15.234170 5.940000 6.898310 6.249471 0.487688 0.027915 0.263790 3.932177 10.632621 5.990055 4.071340 7.920485 0.505481 0.030459 0.127212 12587.121980
13 0.395718 5.866022 3.942392 2.834233 3.050274 0.191730 0.028473 0.335745 1.575972 4.712807 2.907966 1.662868 4.015840 0.021008 0.010623 0.404350 12712.581995
19 8.679761 3.748923 5.940000 0.744593 1.323998 0.238646 0.028351 0.274427 1.149156 3.187177 0.935546 0.561997 1.711240 0.101379 0.075004 0.735026 12803.756786

In [30]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
p0 = popt

In [31]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [34]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_maskSingletons*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [35]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[35]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
20 0.729195 2.347042 5.554511 6.972959 15.699136 2.380180 0.181868 0.541870 1.109178 2.445168 1.888700 2.340818 6.014737 2.538201 0.064304 0.244373 12357.408662
0 1.509882 14.965229 2.508661 4.872687 3.738354 0.583032 0.051372 0.077091 1.865330 5.770621 3.438538 2.204560 4.860245 0.476004 0.049829 0.273144 12392.971225
4 4.129192 10.644087 5.940000 2.064253 2.935639 1.068479 0.059677 0.081940 3.207155 4.280207 3.110356 2.319725 5.591939 1.086097 0.069298 0.207377 12409.846879
14 3.472558 8.877600 5.940000 1.428461 4.279664 0.737375 0.043860 0.151860 2.689666 5.161546 3.013242 1.959265 5.205570 0.704929 0.060358 0.249054 12414.052873
27 1.372045 1.945228 2.310813 0.419535 2.735563 0.707072 0.218654 0.195930 1.641239 2.519337 1.796483 1.608264 4.111332 0.614238 0.101852 0.327397 12467.086584
3 0.825031 3.615534 1.249947 1.114932 6.217735 0.854882 0.122393 0.211120 0.817329 3.058850 1.231432 1.186813 3.421493 0.850733 0.052639 0.509384 12478.780759
28 1.710807 3.152804 2.211508 1.716814 2.750739 5.940000 0.156722 0.075499 2.570714 4.833159 1.432887 2.248220 4.642659 1.992711 0.103931 0.207147 12507.840604
11 3.894324 1.610432 0.742450 2.192734 6.536958 0.553018 0.072530 0.162753 4.593103 2.528287 0.645438 1.101989 2.630765 0.631260 0.147307 0.348404 12579.493319
21 4.780134 15.234170 5.940000 6.898310 6.249471 0.487688 0.027915 0.263790 3.932177 10.632621 5.990055 4.071340 7.920485 0.505481 0.030459 0.127212 12587.121980
5 4.164887 1.871440 4.852616 4.018254 21.620513 3.783619 0.145681 0.544109 13.601300 2.496703 0.458706 1.730954 5.566865 3.725683 0.026599 0.380457 12645.453252

In [36]:
%%px

fold = 1

In [37]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
p0 = popt

In [38]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [39]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_maskSingletons*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [40]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[40]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
20 0.949575 2.106357 0.977719 1.677069 8.709270 1.567930 0.050415 0.253812 0.945973 2.629613 0.973867 1.683536 4.118233 1.648796 0.071195 0.330800 12324.763137
26 0.832915 4.362980 1.139907 3.250099 5.000692 3.695117 0.070264 0.140428 0.766444 2.133639 1.169087 2.413221 6.006461 3.379745 0.052192 0.229285 12326.868253
19 1.936073 3.510723 3.334566 2.786431 3.614048 1.792786 0.123292 0.378280 2.130910 2.939098 2.081683 2.085030 5.229504 1.681492 0.060323 0.268264 12331.690204
34 0.584718 1.530716 1.629279 4.016264 8.237152 2.209564 0.055727 0.419046 0.589365 1.592996 1.839736 1.982528 5.091314 2.206916 0.056946 0.303995 12338.921742
37 0.934962 3.570916 3.001679 1.380805 4.915385 3.183364 0.074086 0.229743 0.955023 3.538556 1.530046 1.934382 5.130936 1.944838 0.056152 0.298313 12339.455380
28 0.578150 2.564622 1.286595 3.845025 3.251069 1.317880 0.106134 0.271288 0.577826 2.948391 1.358487 1.698035 4.293388 1.367464 0.061230 0.355902 12349.544349
23 0.729195 2.347042 5.554511 6.972959 15.699136 2.380180 0.181868 0.541870 1.109178 2.445168 1.888700 2.340818 6.014737 2.538201 0.064304 0.244373 12357.408662
0 1.509882 14.965229 2.508661 4.872687 3.738354 0.583032 0.051372 0.077091 1.865330 5.770621 3.438538 2.204560 4.860245 0.476004 0.049829 0.273144 12392.971225
14 0.812080 2.444083 2.165155 1.342153 7.937262 1.926519 0.049994 0.467293 0.680093 1.886699 0.836347 1.411734 3.672747 1.703944 0.069056 0.442691 12394.955963
4 4.129192 10.644087 5.940000 2.064253 2.935639 1.068479 0.059677 0.081940 3.207155 4.280207 3.110356 2.319725 5.591939 1.086097 0.069298 0.207377 12409.846879

In [41]:
%%px --local

pts_l = [50, 60, 70]
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [42]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 8:16])
p0 = popt

In [43]:
#ar_split_asym_mig_2epoch = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [44]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch_maskSingletons*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [45]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[45]:
ery_1_0 par_1_0 T1_0 ery_2_0 par_2_0 T2_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt par_2_opt T2_opt m1_opt m2_opt -logL
44 0.934625 1.934697 1.289141 3.272382 4.862205 2.332039 0.038355 0.266159 0.002271 0.001924 0.000959 1.120317 2.777336 1.540432 0.111132 0.495746 12300.737736
38 1.833410 1.372508 0.706879 3.130597 6.154468 1.340163 0.058373 0.198539 0.147461 0.071471 0.033712 1.113464 2.737672 1.437755 0.115875 0.486900 12301.502547
8 0.762557 1.342619 1.087270 1.390003 2.497697 2.858986 0.060662 0.514820 0.645119 0.590475 0.303323 1.257823 3.096010 1.493595 0.102609 0.435517 12304.909347
32 1.565595 2.404518 0.509753 1.963560 7.279779 2.358053 0.047261 0.171897 0.709689 0.621737 0.312596 1.241413 3.051965 1.430832 0.105286 0.435173 12306.543393
21 1.432574 1.900151 1.712980 1.639231 3.097637 3.032008 0.086645 0.387162 0.739915 0.780218 0.403615 1.298006 3.198030 1.470168 0.099432 0.419681 12306.823723
15 0.604755 2.116578 0.556061 1.485304 2.304177 1.543635 0.042509 0.432952 0.743833 0.794612 0.398507 1.286465 3.165932 1.438291 0.100339 0.422457 12307.590593
39 0.643238 2.312922 0.914457 1.085237 4.070812 2.464913 0.116870 0.201337 0.013656 0.386713 0.268833 1.358513 3.360978 1.936132 0.089581 0.426072 12307.624553
3 0.662102 3.262028 0.680291 2.005276 5.585575 1.701654 0.069135 0.243952 0.835750 0.887093 0.467490 1.316097 3.239391 1.443951 0.098060 0.413291 12308.469771
22 0.828773 4.201020 1.738495 2.491986 2.737689 2.123364 0.057228 0.595340 1.152641 1.835764 1.049498 1.612570 3.984668 1.529454 0.079446 0.341633 12314.391972
26 0.949575 2.106357 0.977719 1.677069 8.709270 1.567930 0.050415 0.253812 0.945973 2.629613 0.973867 1.683536 4.118233 1.648796 0.071195 0.330800 12324.763137

It is apparently difficult to achieve convergence. However, the best 10 parameter combinations all do not indicate a recent and strong bottleneck. The inference of a recent and strong bottleneck therefore depends on information in the singleton size class of the 2D spectrum.

asynchonous bottleneck


In [9]:
def asynchronous_bottleneck(params, ns, pts):
    """
    params = (nu1_1,nu2_1,T1,nu1_2,nu2_2,T2,m1,m2)
    ns = (n1,n2)

    Split into two populations of specified size, with potentially asymmetric migration.
    The split coincides with the first stepwise size change in the daughter populations. 
    Then, ERY has a second stepwise size change at time [T2+T3] after the split followed
    by PAR having the 2nd size change at time T3 in the past. Migration is assumed to
    be the same during all epochs.

    nu1: pop size ratio of pop 1 after split (with respect to Na)
    nu2: pop size ratio of pop 2 after split (with respect to Na)
    T1: Time from split to 2nd size change of ERY (in units of 2*Na generations)
    nu1_2: pop size ratio of ERY after second size change (with respect to Na)
    nu2_2: pop size ratio of PAR after second size change (with respect to Na)
    T2: time in past of 2nd size change of ERY (in units of 2*Na generations)
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    T3: time in past of 2nd size change of PAR 
    The split happened T1+T2+T3 2*Na generations in the past. 
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,T1,nu1_2,T2,nu2_2,T3,m1,m2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration for time T1
    phi = dadi.Integration.two_pops(phi, xx, T1, nu1, nu2, m12=m2, m21=m1)
    
    # divergence with potentially different pop size of ERY for time T2 (+T3)
    phi = dadi.Integration.two_pops(phi, xx, T2, nu1_2, nu2, m12=m2, m21=m1)
        
    # divergence with potentially different pop size of PAR for time T3
    phi = dadi.Integration.two_pops(phi, xx, T3, nu1_2, nu2_2, m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [10]:
cl[:].push(dict(asynchronous_bottleneck=asynchronous_bottleneck))


Out[10]:
<AsyncResult: _push>

In [11]:
%%px --local

func = asynchronous_bottleneck

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [12]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/asynchronous_bottleneck" # set file name stub for opt. result files
fixed_params = None

In [13]:
%%px --local

# set lower and upper bounds to nu1,nu2,T1,nu1_2,T2,nu2_2,T3,m1,m2
upper_bound = [1e4, 1e4, 6, 1e4, 6, 1e4, 6, 10, 10]
lower_bound = [1e-4, 1e-4, 0, 1e-4, 0, 1e-4, 0, 0, 0]

Get optimal parameter values from recent bottleneck model.


In [139]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [140]:
l = 2*8+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

popt = df.sort_values(by='-logL', ascending=True).head(10).iloc[0,8:16]
popt


Out[140]:
ery_1_opt    1.389305
par_1_opt    4.235224
T1_opt       1.889727
ery_2_opt    0.003110
par_2_opt    0.001079
T2_opt       0.000020
m1_opt       0.077585
m2_opt       0.416367
Name: 39, dtype: float64

In [17]:
p0 = [1.389, 4.235, 1.889, 0.03, 1e-3, 0.01, 1e-3, 0.077, 0.416]

In [142]:
#ar_asynchronous_bottleneck = lbview.map(run_dadi, repeat(p0, 10))

In [143]:
ar_asynchronous_bottleneck = []

for filename in glob("OUT_2D_models/asynchronous_bottleneck*dill"):
    ar_asynchronous_bottleneck.append(dill.load(open(filename)))

In [145]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_asynchronous_bottleneck]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','T2_0','par_2_0','T3_0','m1_0','m2_0','ery_1_opt','par_1_opt','T1_opt','ery_2_opt','T2_opt','par_2_opt','T3_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[145]:
ery_1_0 par_1_0 T1_0 ery_2_0 T2_0 par_2_0 T3_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt T2_opt par_2_opt T3_opt m1_opt m2_opt -logL
1 4.250917 11.245171 1.048844 0.093413 0.001547 0.015170 0.000963 0.071623 0.478407 1.033933 3.744148 1.061648 0.182102 3.305795e-05 0.006420 1.472301e-04 0.079466 0.403334 12309.355617
3 0.412933 7.339823 2.234402 0.078519 0.000333 0.018938 0.000739 0.223951 0.252279 1.210454 4.761272 1.526788 0.051611 4.281211e-05 0.002359 8.844802e-05 0.080434 0.368804 12338.116791
5 3.773255 8.542468 2.448736 0.008819 0.002469 0.003494 0.001366 0.039560 0.109319 2.149637 7.468283 3.356641 0.016086 1.672746e-04 0.007783 1.908340e-04 0.015628 0.325643 12475.328699
6 0.627952 16.648341 0.773841 0.094427 0.000818 0.012033 0.000771 0.068769 0.215906 0.848004 3.583953 0.729393 0.069612 5.064026e-05 0.001464 5.197669e-05 0.045077 0.618218 12482.287511
9 0.774900 1.775400 0.886334 0.031973 0.000931 0.015973 0.001299 0.063484 0.106720 0.919637 2.251610 0.822751 0.116635 1.971352e-05 0.040121 5.909300e-04 0.071547 0.625657 12506.374457
2 1.576603 1.887878 0.510303 0.028529 0.003095 0.008340 0.002260 0.049701 0.344009 0.816681 2.595260 0.709436 0.031522 6.587840e-06 0.005449 1.063432e-04 0.010789 0.722408 12551.395072
4 0.933796 3.883626 1.912687 0.028646 0.001375 0.005524 0.001464 0.151919 1.426536 0.908124 5.299163 0.962503 0.184652 3.078450e-05 0.005572 2.933698e-04 0.001102 0.848213 12730.395446
8 0.727671 4.872012 4.779396 0.008594 0.000871 0.003089 0.000287 0.119827 0.396569 0.682979 2.434332 1.019796 5.735034 3.004936e-08 0.002110 2.605987e-08 0.046902 0.887854 12921.060590
0 1.893683 7.081884 0.522395 0.098265 0.000422 0.006122 0.001213 0.034654 0.147184 1.154191 2.579956 0.760780 0.026909 5.464428e-05 0.010075 1.856502e-04 0.003852 0.298846 13470.502752
7 0.507363 1.075815 3.120998 0.016113 0.000280 0.019652 0.003191 0.239265 0.138755 0.565652 1.126012 0.460813 1.450788 2.066223e-04 1.006317 1.081467e-08 0.163051 0.712590 13965.600700

In [146]:
popt = df.sort_values(by='-logL', ascending=True).head(10).iloc[0,9:18]
popt


Out[146]:
ery_1_opt    1.033933
par_1_opt    3.744148
T1_opt       1.061648
ery_2_opt    0.182102
T2_opt       0.000033
par_2_opt    0.006420
T3_opt       0.000147
m1_opt       0.079466
m2_opt       0.403334
Name: 1, dtype: float64

In [147]:
p0 = np.array(popt)

In [148]:
#ar_asynchronous_bottleneck = lbview.map(run_dadi, repeat(p0, 10))

In [149]:
ar_asynchronous_bottleneck = []

for filename in glob("OUT_2D_models/asynchronous_bottleneck*dill"):
    ar_asynchronous_bottleneck.append(dill.load(open(filename)))

In [150]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_asynchronous_bottleneck]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','T2_0','par_2_0','T3_0','m1_0','m2_0','ery_1_opt','par_1_opt','T1_opt','ery_2_opt','T2_opt','par_2_opt','T3_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[150]:
ery_1_0 par_1_0 T1_0 ery_2_0 T2_0 par_2_0 T3_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt T2_opt par_2_opt T3_opt m1_opt m2_opt -logL
18 3.574476 10.584429 3.766586 0.624439 0.000106 0.005959 0.000040 0.185170 0.299087 2.449812 6.937847 4.357320 0.758348 1.188723e-04 0.004145 7.822460e-05 0.057765 0.221816 12143.027400
4 4.250917 11.245171 1.048844 0.093413 0.001547 0.015170 0.000963 0.071623 0.478407 1.033933 3.744148 1.061648 0.182102 3.305795e-05 0.006420 1.472301e-04 0.079466 0.403334 12309.355617
11 0.997008 2.928811 0.577483 0.117862 0.000009 0.001793 0.000274 0.070248 0.277645 0.998765 2.659164 0.965020 0.045405 5.252602e-08 0.000270 4.169400e-06 0.174711 0.448065 12320.818813
8 0.412933 7.339823 2.234402 0.078519 0.000333 0.018938 0.000739 0.223951 0.252279 1.210454 4.761272 1.526788 0.051611 4.281211e-05 0.002359 8.844802e-05 0.080434 0.368804 12338.116791
7 0.375990 1.796777 2.003722 0.056743 0.000010 0.004645 0.000094 0.031873 0.345103 1.383847 3.402930 2.011878 0.044520 4.857758e-08 0.010622 1.978797e-08 0.056802 0.421429 12411.399752
0 3.263745 1.449081 1.258973 0.124504 0.000009 0.004832 0.000207 0.027676 0.223083 1.086535 2.873848 1.167959 0.185860 4.088867e-04 6.751401 2.436927e-09 0.066061 0.495984 12468.709151
12 3.773255 8.542468 2.448736 0.008819 0.002469 0.003494 0.001366 0.039560 0.109319 2.149637 7.468283 3.356641 0.016086 1.672746e-04 0.007783 1.908340e-04 0.015628 0.325643 12475.328699
13 0.627952 16.648341 0.773841 0.094427 0.000818 0.012033 0.000771 0.068769 0.215906 0.848004 3.583953 0.729393 0.069612 5.064026e-05 0.001464 5.197669e-05 0.045077 0.618218 12482.287511
17 0.774900 1.775400 0.886334 0.031973 0.000931 0.015973 0.001299 0.063484 0.106720 0.919637 2.251610 0.822751 0.116635 1.971352e-05 0.040121 5.909300e-04 0.071547 0.625657 12506.374457
6 1.576603 1.887878 0.510303 0.028529 0.003095 0.008340 0.002260 0.049701 0.344009 0.816681 2.595260 0.709436 0.031522 6.587840e-06 0.005449 1.063432e-04 0.010789 0.722408 12551.395072

In [151]:
p0 = np.array(df.sort_values(by='-logL', ascending=True).head(10).iloc[0,9:18])

In [152]:
#ar_asynchronous_bottleneck = lbview.map(run_dadi, repeat(p0, 10))

In [153]:
ar_asynchronous_bottleneck = []

for filename in glob("OUT_2D_models/asynchronous_bottleneck*dill"):
    ar_asynchronous_bottleneck.append(dill.load(open(filename)))

In [154]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_asynchronous_bottleneck]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','T2_0','par_2_0','T3_0','m1_0','m2_0','ery_1_opt','par_1_opt','T1_opt','ery_2_opt','T2_opt','par_2_opt','T3_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[154]:
ery_1_0 par_1_0 T1_0 ery_2_0 T2_0 par_2_0 T3_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt T2_opt par_2_opt T3_opt m1_opt m2_opt -logL
18 1.584141 4.151904 4.957267 1.857565 0.000205 0.008366 0.000114 0.092407 0.059250 1.800457 5.372171 2.915091 1.672489 2.150510e-05 0.004209 7.188958e-05 0.063375 0.310819 12093.618893
28 3.574476 10.584429 3.766586 0.624439 0.000106 0.005959 0.000040 0.185170 0.299087 2.449812 6.937847 4.357320 0.758348 1.188723e-04 0.004145 7.822460e-05 0.057765 0.221816 12143.027400
21 1.726154 3.136018 5.940000 0.281023 0.000045 0.002680 0.000054 0.095284 0.139246 1.868110 4.977217 3.207994 0.243995 1.803859e-05 0.015760 6.582062e-05 0.071598 0.292261 12253.041901
27 1.535880 12.428916 1.421438 0.756649 0.000066 0.001361 0.000129 0.101636 0.131123 1.460560 4.027202 1.463404 0.685232 2.949351e-06 0.002783 5.357164e-05 0.068233 0.374908 12298.214751
4 4.250917 11.245171 1.048844 0.093413 0.001547 0.015170 0.000963 0.071623 0.478407 1.033933 3.744148 1.061648 0.182102 3.305795e-05 0.006420 1.472301e-04 0.079466 0.403334 12309.355617
17 0.997008 2.928811 0.577483 0.117862 0.000009 0.001793 0.000274 0.070248 0.277645 0.998765 2.659164 0.965020 0.045405 5.252602e-08 0.000270 4.169400e-06 0.174711 0.448065 12320.818813
10 0.412933 7.339823 2.234402 0.078519 0.000333 0.018938 0.000739 0.223951 0.252279 1.210454 4.761272 1.526788 0.051611 4.281211e-05 0.002359 8.844802e-05 0.080434 0.368804 12338.116791
12 1.227752 16.134061 5.940000 0.663797 0.000124 0.005730 0.000171 0.093615 0.113807 1.318850 5.003568 2.901300 0.539616 1.561365e-04 0.001021 2.574348e-05 0.075497 0.456287 12379.596897
9 0.375990 1.796777 2.003722 0.056743 0.000010 0.004645 0.000094 0.031873 0.345103 1.383847 3.402930 2.011878 0.044520 4.857758e-08 0.010622 1.978797e-08 0.056802 0.421429 12411.399752
0 3.263745 1.449081 1.258973 0.124504 0.000009 0.004832 0.000207 0.027676 0.223083 1.086535 2.873848 1.167959 0.185860 4.088867e-04 6.751401 2.436927e-09 0.066061 0.495984 12468.709151

In [155]:
p0 = np.array(df.sort_values(by='-logL', ascending=True).head(10).iloc[0,9:18])

In [14]:
%%px --local

pts_l = [50, 60, 70]
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [157]:
#ar_asynchronous_bottleneck = lbview.map(run_dadi, repeat(p0, 10))

In [15]:
ar_asynchronous_bottleneck = []

for filename in glob("OUT_2D_models/asynchronous_bottleneck*dill"):
    ar_asynchronous_bottleneck.append(dill.load(open(filename)))

In [18]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_asynchronous_bottleneck]

df = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','T2_0','par_2_0','T3_0','m1_0','m2_0','ery_1_opt','par_1_opt','T1_opt','ery_2_opt','T2_opt','par_2_opt','T3_opt','m1_opt','m2_opt','-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[18]:
ery_1_0 par_1_0 T1_0 ery_2_0 T2_0 par_2_0 T3_0 m1_0 m2_0 ery_1_opt par_1_opt T1_opt ery_2_opt T2_opt par_2_opt T3_opt m1_opt m2_opt -logL
1 2.062072 6.904858 3.700748 1.047203 0.000042 0.004860 0.000036 0.092682 0.367013 1.384623 4.220191 1.854568 0.005099 3.533777e-05 0.000129 0.000002 0.077798 0.418371 12033.350315
35 2.051185 5.776144 3.396844 1.583355 0.000022 0.007065 0.000066 0.083559 0.183875 1.394468 4.278132 1.892546 0.001739 8.832482e-06 0.000129 0.000002 0.076870 0.413272 12033.593445
36 2.122395 4.319564 5.570387 1.635960 0.000037 0.003078 0.000070 0.056823 0.205432 1.356686 4.197752 1.822860 0.004022 2.294777e-05 0.000101 0.000002 0.074558 0.420536 12035.185078
13 2.297607 2.712784 4.513503 1.303427 0.000029 0.006181 0.000071 0.073110 0.176406 1.391325 4.107555 1.840515 0.000436 8.099987e-07 0.000127 0.000002 0.080539 0.413309 12035.266136
25 2.056803 3.560734 1.471369 1.442615 0.000024 0.004370 0.000060 0.043133 0.371568 1.365302 4.300915 1.891787 0.004447 2.481209e-05 0.000104 0.000002 0.078251 0.417334 12035.423785
28 2.284165 4.063335 3.283235 1.631468 0.000031 0.005585 0.000048 0.067232 0.341716 1.335497 4.252487 1.793192 0.004295 2.144011e-05 0.000102 0.000002 0.077755 0.425218 12036.653745
22 3.337459 7.601858 2.982267 3.182403 0.000040 0.003764 0.000105 0.059928 0.157525 1.221606 4.018195 1.664527 0.016289 6.698421e-05 0.000105 0.000002 0.075018 0.455682 12047.562591
17 1.353744 6.501975 5.107016 1.619759 0.000019 0.004947 0.000093 0.031952 0.169829 1.299777 4.226165 1.853527 0.487600 2.990029e-05 0.000160 0.000003 0.077483 0.426205 12064.230097
6 2.808235 4.897895 1.597431 1.409751 0.000012 0.004356 0.000095 0.126001 0.427055 1.270769 4.194766 1.777316 6899.657932 1.247087e-06 0.000493 0.000010 0.075593 0.436355 12064.838465
24 1.584141 4.151904 4.957267 1.857565 0.000205 0.008366 0.000114 0.092407 0.059250 1.800457 5.372171 2.915091 1.672489 2.150510e-05 0.004209 0.000072 0.063375 0.310819 12093.618893

This looks reasonably well converged. The inferred model is qualitatively very similar to the recent bottleneck model. The estimate for the time between the 2nd size change of ERY and the 2nd size change of PAR (T2) is very small and both bittlenecks are inferred to be very recent and very severe.


In [19]:
popt = df.sort_values(by='-logL', ascending=True).iloc[0,9:18]
popt


Out[19]:
ery_1_opt    1.384623
par_1_opt    4.220191
T1_opt       1.854568
ery_2_opt    0.005099
T2_opt       0.000035
par_2_opt    0.000129
T3_opt       0.000002
m1_opt       0.077798
m2_opt       0.418371
Name: 1, dtype: float64
parameter recent bottleneck async bottleneck
ery_1_opt 1.389305 1.384623
par_1_opt 4.235224 4.220191
T1_opt 1.889727 1.854568
ery_2_opt 0.003110 0.005099
par_2_opt 0.001079 0.000129
T2_opt 0.000020 0.000035
T3_opt 0.000002
m1_opt 0.077585 0.077798
m2_opt 0.416367 0.418371
-logL 12,034 12,033

Allowing for different times of the 2nd population size change for ERY and PAR does not improve the fit to the data significantly. It also does not lead to more sensible parameter values for the inferred time and new population size.

exponential size change

So far, the split coincided with a stepwise population size change.


In [11]:
def IM(params, ns, pts):
    """
    ns = (n1,n2)
    params = (s,nu1,nu2,T,m1,m2)

    Isolation-with-migration model with exponential pop growth or decline.

    s: Size of pop 1 after split. (Pop 2 has size 1-s.)
    nu1: Final population size ratio of pop 1 (with respect to Na)
    nu2: Final population size ratio of pop 2 (with respect to Na)
    T: Time in the past of split (in units of 2*Na generations) 
    m1: Migration from pop 1 to pop 2
    m2: Migration from pop 2 to pop 1 (2*Na*m12)
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    s,nu1,nu2,T,m1,m2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi)

    nu1_func = lambda t: s * (nu1/s)**(t/T)
    nu2_func = lambda t: (1-s) * (nu2/(1-s))**(t/T)
    phi = dadi.Integration.two_pops(phi, xx, T, nu1_func, nu2_func, m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [12]:
cl[:].push(dict(IM=IM))


Out[12]:
<AsyncResult: _push>

In [13]:
%%px --local

func = IM

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [14]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/IM" # set file name stub for opt. result files
fixed_params = None # do not fix any parameters, optimise all

In [142]:
%%px --local

# set lower and upper bounds to s, nu1, nu2, T, m1, m2
upper_bound = [1, 1e4, 1e4, 4, 10, 10] 
lower_bound = [0, 1e-4, 1e-4, 0, 0, 0]

In [15]:
# using optimal parameter values from `split_asym_mig` model as starting values to perturb
p0 = [0.5, 1.5554055, 3.81288413, 2.34380465, 0.07615248, 0.34730636]

In [ ]:
#ar_IM = lbview.map(run_dadi, repeat(p0, 10))

In [ ]:
# using optimal parameter values from `split_asym_mig` model as starting values to perturb
p0 = [0.5, 15.554055, 38.1288413, 2.34380465, 0.07615248, 0.34730636] 
# note I have add 10 to the previous nu1_0 and nu2_0

In [52]:
%%px --local

# set lower and upper bounds to s, nu1, nu2, T, m1, m2
upper_bound = [1, 1e4, 1e4, 6, 10, 10] # note, I have increased the upper bound for T
lower_bound = [0, 1e-4, 1e-4, 0, 0, 0]

In [ ]:
#ar_IM = lbview.map(run_dadi, repeat(p0, 10))

In [37]:
% ll OUT_2D_models/IM_[!l]*dill


-rw-rw-r-- 1 claudius 348 Jun  2 16:56 OUT_2D_models/IM_0.1371_6.1287_140.0705_4.0801_0.0451_0.6111.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:41 OUT_2D_models/IM_0.1375_0.9516_7.4654_1.5032_0.0584_1.2615.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:43 OUT_2D_models/IM_0.1520_0.9009_1.1476_2.5875_0.0212_0.3926.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:02 OUT_2D_models/IM_0.2143_0.4472_4.9823_3.9600_0.1064_0.1189.dill
-rw-rw-r-- 1 claudius 347 Jun  2 15:38 OUT_2D_models/IM_0.2303_1.6016_5.5039_1.9556_0.1791_0.6871.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:46 OUT_2D_models/IM_0.2793_32.9774_14.8143_1.2084_0.0794_0.4885.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:39 OUT_2D_models/IM_0.3329_50.9972_23.1707_0.9459_0.1881_1.1367.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:49 OUT_2D_models/IM_0.3843_6.5018_30.8340_3.9628_0.1540_0.1553.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:37 OUT_2D_models/IM_0.4212_1.0946_9.0692_0.6614_0.1940_0.3517.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:37 OUT_2D_models/IM_0.4632_6.4870_42.7337_1.2644_0.0209_0.5055.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:44 OUT_2D_models/IM_0.4642_1.7672_8.2948_2.6906_0.0494_0.2300.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:43 OUT_2D_models/IM_0.5487_2.4046_3.1755_3.9600_0.0421_0.1209.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:47 OUT_2D_models/IM_0.5494_14.2652_25.9158_1.8833_0.1187_0.2437.dill
-rw-rw-r-- 1 claudius 347 Jun  2 15:43 OUT_2D_models/IM_0.7727_4.9725_9.7755_2.4780_0.1754_0.3743.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:48 OUT_2D_models/IM_0.8061_40.8211_9.8303_3.9022_0.1013_0.1684.dill
-rw-rw-r-- 1 claudius 348 Jun  2 16:40 OUT_2D_models/IM_0.8847_24.4270_92.9791_0.8051_0.0516_0.9492.dill
-rw-rw-r-- 1 claudius 348 Jun  2 17:04 OUT_2D_models/IM_0.9900_12.4415_61.3255_1.0064_0.0724_0.2231.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:50 OUT_2D_models/IM_0.9900_1.3725_2.8361_0.8031_0.0197_0.1785.dill
-rw-rw-r-- 1 claudius 348 Jun  2 15:56 OUT_2D_models/IM_0.9900_3.1846_7.6041_0.6276_0.0324_0.5021.dill
-rw-rw-r-- 1 claudius 347 Jun  2 17:39 OUT_2D_models/IM_0.9900_7.3819_15.7711_5.9400_0.1224_1.3443.dill

In [40]:
ar_IM = []

for filename in glob("OUT_2D_models/IM_[!l]*dill"):
    ar_IM.append(dill.load((open(filename))))

In [147]:
get_flag_count(ar_IM, NM=True)


success 3
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 17
unknown flag 0

In [41]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_IM]

df = pd.DataFrame(data=returned, columns=['s_0' ,'nu1_0','nu2_0', 'T_0', 'm1_0', 'm2_0', 's_opt' ,'nu1_opt', 'nu2_opt', 'T_opt', 'm1_opt', 'm2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[41]:
s_0 nu1_0 nu2_0 T_0 m1_0 m2_0 s_opt nu1_opt nu2_opt T_opt m1_opt m2_opt -logL
19 0.548698 2.404634 3.175488 3.960000 0.042110 0.120852 0.391828 1.643519 4.954319 3.977713 0.066796 0.428613 12888.961573
12 0.464217 1.767160 8.294837 2.690563 0.049364 0.229995 0.449867 1.568291 4.812866 3.859359 0.071616 0.440530 12890.795363
14 0.214285 0.447155 4.982255 3.960000 0.106390 0.118936 0.357218 1.692737 5.087944 3.995020 0.062778 0.420359 12891.932937
4 0.137108 6.128675 140.070469 4.080069 0.045070 0.611109 0.397690 1.926901 5.834288 5.092989 0.056684 0.366609 12895.833048
18 0.230268 1.601646 5.503930 1.955604 0.179101 0.687077 0.396088 1.275681 3.877785 2.646682 0.085734 0.545238 12896.886524
5 0.421229 1.094606 9.069247 0.661431 0.193997 0.351657 0.451861 1.097640 3.449551 2.196653 0.100002 0.612755 12920.063167
2 0.549371 14.265177 25.915771 1.883288 0.118666 0.243711 0.655488 1.589347 5.223257 4.999083 0.065978 0.421852 12929.268439
3 0.806134 40.821082 9.830328 3.902201 0.101308 0.168433 0.671316 1.640283 5.464712 5.558106 0.061087 0.415430 12933.185111
6 0.463201 6.487004 42.733659 1.264410 0.020869 0.505456 0.402903 1.049086 3.125870 1.626309 0.112483 0.643805 12943.956230
0 0.772714 4.972494 9.775530 2.477999 0.175447 0.374279 0.699395 1.261615 4.247072 3.995846 0.082868 0.527929 12948.183752
17 0.384332 6.501850 30.833978 3.962770 0.154036 0.155305 0.180641 1.910011 5.283292 4.250161 0.056813 0.400921 12950.902590
7 0.884726 24.427049 92.979147 0.805149 0.051588 0.949227 0.653794 1.050288 3.674061 3.291988 0.084214 0.653174 12967.415678
13 0.137513 0.951613 7.465367 1.503162 0.058409 1.261464 0.463084 0.886433 2.996136 1.714164 0.102616 0.750816 12985.048016
16 0.151982 0.900923 1.147613 2.587475 0.021186 0.392602 0.973893 0.514229 1.998297 3.269200 0.162335 1.221883 13083.626517
10 0.990000 7.381876 15.771145 5.940000 0.122375 1.344277 0.983149 0.353030 1.411418 2.547121 0.180619 1.898117 13083.650197
8 0.990000 3.184593 7.604077 0.627579 0.032446 0.502074 0.940065 1.046855 3.722720 3.999991 0.123057 0.598355 13150.045015
1 0.990000 1.372460 2.836121 0.803127 0.019740 0.178463 0.878081 0.366145 1.577484 1.564362 0.156751 1.815647 13279.198944
9 0.990000 12.441481 61.325470 1.006385 0.072427 0.223145 0.968944 0.167424 0.746694 1.015299 0.275605 3.998577 13302.764040
11 0.279341 32.977415 14.814321 1.208360 0.079428 0.488465 0.043173 4.703007 2.908342 1.001791 0.012623 1.512211 25547.604998
15 0.332941 50.997190 23.170726 0.945938 0.188053 1.136728 0.354048 70.730748 20.470953 0.951958 0.094280 1.078322 55663.818148

None of the parameter combinations has higher likelihood than the best-fit parameter combinations of the previous model (lowest neg. logL 12,359). Also the three best-fit parameter combinations are very close to the upper limit of parameter bound that I set for the time parameter $T$ and the population size ratio for PAR implies a current effective population size of several millions (something I think is unlikely given the dispersal rate and the patchiness of the habitat of Chorthippus parallelus).


In [50]:
%%px

fold = 1

In [51]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0, 6:12])
popt


Out[51]:
array([ 0.39182808,  1.64351921,  4.95431884,  3.97771286,  0.06679638,
        0.42861309])

In [53]:
p0 = popt

#ar_IM = lbview.map(run_dadi, repeat(p0, 10))

In [16]:
ar_IM = []

for filename in glob("OUT_2D_models/IM_[!l]*dill"):
    ar_IM.append(dill.load((open(filename))))

In [18]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_IM]

df = pd.DataFrame(data=returned, columns=['s_0' ,'nu1_0','nu2_0', 'T_0', 'm1_0', 'm2_0', 's_opt' ,'nu1_opt', 'nu2_opt', 'T_opt', 'm1_opt', 'm2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[18]:
s_0 nu1_0 nu2_0 T_0 m1_0 m2_0 s_opt nu1_opt nu2_opt T_opt m1_opt m2_opt -logL
29 0.548698 2.404634 3.175488 3.960000 0.042110 0.120852 0.391828 1.643519 4.954319 3.977713 0.066796 0.428613 12888.961573
21 0.244774 1.367522 3.166825 5.940000 0.038599 0.490162 0.395215 1.627846 4.897217 3.950463 0.068449 0.432792 12889.051636
16 0.464217 1.767160 8.294837 2.690563 0.049364 0.229995 0.449867 1.568291 4.812866 3.859359 0.071616 0.440530 12890.795363
18 0.214285 0.447155 4.982255 3.960000 0.106390 0.118936 0.357218 1.692737 5.087944 3.995020 0.062778 0.420359 12891.932937
13 0.215944 0.830677 9.845911 3.115494 0.039453 0.677829 0.399164 1.361566 4.116342 2.926734 0.081159 0.511955 12892.046681
19 0.472006 1.362619 2.672940 4.580331 0.091601 0.303977 0.477995 1.500508 4.657926 3.750540 0.073776 0.457208 12893.946059
15 0.585261 3.009183 3.455292 2.228116 0.088300 0.320002 0.468930 1.386466 4.269351 3.278763 0.082093 0.492299 12895.522460
4 0.137108 6.128675 140.070469 4.080069 0.045070 0.611109 0.397690 1.926901 5.834288 5.092989 0.056684 0.366609 12895.833048
25 0.458086 1.410253 8.704412 2.057916 0.133268 0.341514 0.353952 1.334471 3.945490 2.648923 0.083286 0.527991 12896.764908
26 0.230268 1.601646 5.503930 1.955604 0.179101 0.687077 0.396088 1.275681 3.877785 2.646682 0.085734 0.545238 12896.886524

No better parameter combination could be found. I think it is not worth extending the search.

linear size change

This model changes the population size growth or decline from exponential to linear.


In [19]:
def IM_l(params, ns, pts):
    """
    ns = (n1,n2)
    params = (s,nu1,nu2,T,m1,m2)

    Isolation-with-migration model with LINEAR pop growth or decline.

    s: Size of pop 1 after split. (Pop 2 has size 1-s.)
    nu1: Final size of pop 1.
    nu2: Final size of pop 2.
    T: Time in the past of split (in units of 2*Na generations) 
    m1: Migration from pop 1 to pop 2
    m2: Migration from pop 2 to pop 1 (2*Na*m12)
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    s,nu1,nu2,T,m1,m2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi)

    # specify linear growth from s to nu
    nu1_func = lambda t: s + (nu1-s) * (t/T)
    nu2_func = lambda t: (1-s) + (nu2-s) * (t/T)
    
    # divergence for time T with linear growth and asymmetric gene flow
    phi = dadi.Integration.two_pops(phi, xx, T, nu1_func, nu2_func,
                               m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [150]:
# test specification of linear growth

T = 2
t = np.linspace(0, 2, 100)
nu = 4
N_t = map(lambda t: 1 + (nu-1) * t/T, t)

pylab.plot(t, N_t)


Out[150]:
[<matplotlib.lines.Line2D at 0x7f5c74de2a90>]

In [20]:
cl[:].push(dict(IM_l=IM_l))


Out[20]:
<AsyncResult: _push>

In [21]:
%%px --local

func = IM_l

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [22]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/IM_l" # set file name stub for opt. result files
fixed_params = None # do not fix any parameters, optimise all

In [23]:
%%px --local

# set lower and upper bounds to s, nu1, nu2, T, m1, m2
upper_bound = [1, 1e4, 1e4, 4, 10, 10] 
lower_bound = [0, 1e-4, 1e-4, 0, 0, 0]

In [24]:
# using optimal parameter values from `split_asym_mig` model as starting values to perturb
p0 = [0.5, 1.5554055, 3.81288413, 2.34380465, 0.07615248, 0.34730636]

In [ ]:
#ar_IM_l = lbview.map(run_dadi, repeat(p0, 10))

In [25]:
ar_IM_l = []

for filename in glob("OUT_2D_models/IM_l*dill"):
    ar_IM_l.append(dill.load((open(filename))))

In [26]:
get_flag_count(ar_IM_l, NM=True)


success 1
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 9
unknown flag 0

In [27]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_IM_l]

df = pd.DataFrame(data=returned, columns=['s_0' ,'nu1_0','nu2_0', 'T_0', 'm1_0', 'm2_0', 's_opt' ,'nu1_opt', 'nu2_opt', 'T_opt', 'm1_opt', 'm2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[27]:
s_0 nu1_0 nu2_0 T_0 m1_0 m2_0 s_opt nu1_opt nu2_opt T_opt m1_opt m2_opt -logL
1 0.203003 3.875706 4.118667 2.221169 0.273816 0.481572 9.996682e-01 1.817966 6.086413 3.993857 0.076573 0.327474 12535.032592
5 0.704564 4.513968 7.027870 3.960000 0.150884 0.495193 7.049459e-01 1.978755 5.801726 3.991335 0.064690 0.316536 12535.941358
3 0.601689 2.023649 13.809500 2.213514 0.033629 0.337455 6.172267e-01 1.911002 5.460823 3.736669 0.067011 0.327251 12540.967718
6 0.303974 2.668145 8.801616 2.822563 0.082534 0.221968 2.053428e-01 2.197845 5.055365 3.999998 0.058144 0.298362 12550.285772
4 0.653993 0.496132 2.801174 0.597627 0.061820 0.497076 5.590188e-02 2.180087 4.808371 3.999022 0.053734 0.307218 12557.175923
8 0.145981 5.096160 1.381127 3.960000 0.133269 0.263291 3.713438e-01 1.042277 2.499105 1.332073 0.109007 0.627433 12708.138553
2 0.415317 0.591004 1.657978 1.127243 0.025174 0.896826 7.922259e-01 0.768670 3.187071 1.221881 0.121274 0.814546 12797.435647
7 0.425806 1.469947 0.971380 0.880802 0.046352 0.138925 8.619370e-09 0.962189 1.178476 1.259259 0.119180 0.794375 13090.937814
9 0.258108 1.478199 1.020453 1.428272 0.169881 1.347643 1.392273e-02 0.945358 1.022459 1.126733 0.126779 0.760517 13184.211563
0 0.990000 4.574175 1.016484 3.960000 0.101989 0.104105 9.997106e-01 0.000282 1.289413 0.038803 7.389231 0.000011 29395.861430

This improves the fit significantly as compared to the same model with exponential growth or decline (best logL 12,888.96). However, the split_asym_mig model from section 3 above still provided a better fit (best logL 12,359.63). This model incorporated a stepwise size change (followed by constant population size) that coincided with the population split. Also, note that the best fit parameter combination here contains an exceedingly small proportion of the ancestral population size ($1-s_{opt}$) allocated to PAR of 0.0004.


In [31]:
%%px --local

# set lower and upper bounds to s, nu1, nu2, T, m1, m2
upper_bound = [1, 1e4, 1e4, 6, 10, 10] 
lower_bound = [0, 1e-4, 1e-4, 0, 0, 0]

In [32]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[1, 6:12])
popt


Out[32]:
array([ 0.70494593,  1.97875488,  5.80172641,  3.9913347 ,  0.06468988,
        0.31653588])

In [33]:
p0 = popt

#ar_IM_l = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [36]:
ar_IM_l = []

for filename in glob("OUT_2D_models/IM_l*dill"):
    ar_IM_l.append(dill.load((open(filename))))

In [37]:
l = 2*len(p0)+1

returned = [flatten(out)[:l] for out in ar_IM_l]

df = pd.DataFrame(data=returned, columns=['s_0' ,'nu1_0','nu2_0', 'T_0', 'm1_0', 'm2_0', 's_opt' ,'nu1_opt', 'nu2_opt', 'T_opt', 'm1_opt', 'm2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[37]:
s_0 nu1_0 nu2_0 T_0 m1_0 m2_0 s_opt nu1_opt nu2_opt T_opt m1_opt m2_opt -logL
7 0.231736 3.381135 1.661646 4.487948 0.053821 0.080453 0.962549 2.622424 8.249789 5.996610 0.048111 0.232114 12516.956746
12 0.481842 2.339626 2.884235 5.940000 0.044144 0.356524 0.999901 2.315539 7.464471 5.394943 0.057795 0.260678 12519.643353
8 0.178174 0.931910 6.936553 5.940000 0.022410 0.689138 0.000066 2.995041 6.778374 5.988330 0.040038 0.219953 12534.203960
2 0.203003 3.875706 4.118667 2.221169 0.273816 0.481572 0.999668 1.817966 6.086413 3.993857 0.076573 0.327474 12535.032592
11 0.704564 4.513968 7.027870 3.960000 0.150884 0.495193 0.704946 1.978755 5.801726 3.991335 0.064690 0.316536 12535.941358
5 0.601689 2.023649 13.809500 2.213514 0.033629 0.337455 0.617227 1.911002 5.460823 3.736669 0.067011 0.327251 12540.967718
14 0.990000 0.777637 17.102773 3.537846 0.108252 0.612727 0.984982 2.241542 6.722230 4.558736 0.062624 0.270636 12549.189993
13 0.303974 2.668145 8.801616 2.822563 0.082534 0.221968 0.205343 2.197845 5.055365 3.999998 0.058144 0.298362 12550.285772
6 0.653993 0.496132 2.801174 0.597627 0.061820 0.497076 0.055902 2.180087 4.808371 3.999022 0.053734 0.307218 12557.175923
3 0.860240 7.174919 1.774645 2.229344 0.066773 0.177180 0.999981 1.282928 4.643031 2.840185 0.108370 0.464606 12573.702783

There is no good convergence, the inferred $s$ is very high and the inferred T is close to the upper boundary that I set. Furthermore, the best -logL is still higher than the best -logL achieved with the asymmetric migration model (12,359). I don't think it is worth continuing the search.

Ancient migration


In [77]:
def split_asym_mig_iso(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by complete isolation until present.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,Ti = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
    
    # divergence without gene flow
    phi = dadi.Integration.two_pops(phi, xx, Ti, nu1, nu2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [78]:
cl[:].push(dict(split_asym_mig_iso=split_asym_mig_iso))


Out[78]:
<AsyncResult: _push>

In [15]:
%%px --local

func = split_asym_mig_iso

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [16]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_asym_mig_iso" # set file name stub for opt. result files
fixed_params = None

In [17]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2 and Ti
upper_bound = [1e4, 1e4, 6, 10, 10, 6] # note, I have increased the upper bound for T
lower_bound = [1e-4, 1e-4, 0, 0, 0, 0]

In [20]:
# using the optimal parameters from the `split_asym_mig` model
p0 = [1.555405, 3.812884, 2.343805/2, 0.076152, 0.347306, 2.343805/2]

In [168]:
#ar_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10))

In [17]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [18]:
get_flag_count(ar_split_asym_mig_iso, NM=True)


success 0
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 9
unknown flag 0

In [19]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[19]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
8 0.990000 8.359944 2.967286 0.247505 0.216418 0.295627 0.999770 2.809523 1.426900 7.429267e-02 6.816504e-01 2.820061e-02 12315.949071
4 0.990000 7.012555 1.098683 0.147341 0.395542 2.172501 0.968944 2.455098 1.104248 1.119865e-01 6.094089e-01 1.916132e-02 12356.736701
6 0.955717 12.340014 0.529553 0.139149 0.195895 0.438003 0.904117 2.476454 1.156964 9.282603e-02 7.261105e-01 2.537161e-02 12377.541453
1 0.990000 9.743392 0.717364 0.104640 0.923004 1.701956 0.915695 2.587967 1.336239 8.415303e-02 8.649855e-01 4.876505e-02 12452.463230
0 0.396562 1.011215 4.687011 0.063182 0.306567 3.954367 0.892823 2.830180 1.074032 1.952844e-232 6.809258e-01 2.400412e-286 12834.457770
7 0.790844 2.507872 0.366900 0.198516 1.385579 2.441703 0.999793 3.202772 1.908154 2.470231e-03 1.117805e+00 7.600760e-02 12888.288301
3 0.515017 3.576919 0.442112 0.019754 0.131618 0.512714 0.989586 1.563574 0.497919 3.306783e-01 7.172812e-09 2.548533e-03 14763.672024
5 0.790903 0.980743 1.183645 0.245645 0.418042 1.897280 0.659343 0.961191 1.180327 3.828303e+00 6.992257e-01 1.240319e-01 16020.739766
2 0.990000 3.478991 1.977104 0.046105 1.206637 0.931285 0.999967 3.202098 1.299714 3.994489e+00 1.508210e+00 8.130838e-01 23042.642491

In [20]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,6:12])
popt


Out[20]:
array([ 0.99977013,  2.80952272,  1.42690025,  0.07429267,  0.68165038,
        0.02820061])

In [22]:
%%px --local

pts_l = [50, 60, 70] # make finer grid

In [23]:
p0 = popt

#ar_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10))

In [24]:
get_flag_count(ar_split_asym_mig_iso, NM=True)


success 0
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 10
unknown flag 0

In [25]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [26]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True)


Out[26]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
4 1.114068 2.045370 0.628484 0.054210 0.524956 0.061246 2.877591 7.051026 5.515295 4.619322e-02 2.413644e-01 9.522990e-02 12157.097777
15 0.553390 3.423582 0.669935 0.178126 0.566459 0.009321 1.425900 3.521453 2.151054 7.305745e-02 3.906106e-01 6.877453e-03 12310.358394
18 0.990000 8.359944 2.967286 0.247505 0.216418 0.295627 0.999770 2.809523 1.426900 7.429267e-02 6.816504e-01 2.820061e-02 12315.949071
8 0.284546 1.780534 0.542622 0.157445 2.687989 0.067133 1.480242 3.818764 2.273526 6.555660e-02 3.841305e-01 4.231302e-03 12330.193803
7 0.650546 5.641617 0.512223 0.080364 0.874478 0.093402 1.124442 3.051764 1.656526 7.516770e-02 7.468150e-01 5.387482e-02 12333.745210
11 0.990000 7.012555 1.098683 0.147341 0.395542 2.172501 0.968944 2.455098 1.104248 1.119865e-01 6.094089e-01 1.916132e-02 12356.736701
13 0.955717 12.340014 0.529553 0.139149 0.195895 0.438003 0.904117 2.476454 1.156964 9.282603e-02 7.261105e-01 2.537161e-02 12377.541453
0 2.610421 1.305616 1.429607 0.188757 2.714221 0.009457 0.921184 2.174184 1.034288 1.042159e-01 6.835330e-01 2.149244e-02 12433.587948
10 0.319417 1.476172 0.997998 0.025943 0.666049 0.008386 1.031350 2.254960 0.998242 1.378614e-01 4.902879e-01 1.468785e-02 12447.888476
3 0.990000 9.743392 0.717364 0.104640 0.923004 1.701956 0.915695 2.587967 1.336239 8.415303e-02 8.649855e-01 4.876505e-02 12452.463230
14 2.391520 8.970536 2.759658 0.061570 2.159332 0.046643 3.413165 8.520401 5.998978 4.348156e-02 3.426220e-01 3.155935e-01 12598.572386
2 2.076349 5.484228 0.803127 0.059377 2.524920 0.024224 1.043791 2.937971 1.341271 1.739436e-09 7.605266e-01 3.425035e-02 12622.195411
9 0.460975 0.909325 4.590305 0.094300 2.083465 0.010531 0.870938 2.540832 0.939239 7.351060e-08 6.988656e-01 5.620391e-03 12767.140194
1 0.396562 1.011215 4.687011 0.063182 0.306567 3.954367 0.892823 2.830180 1.074032 1.952844e-232 6.809258e-01 2.400412e-286 12834.457770
17 0.790844 2.507872 0.366900 0.198516 1.385579 2.441703 0.999793 3.202772 1.908154 2.470231e-03 1.117805e+00 7.600760e-02 12888.288301
16 0.961159 1.059072 0.531496 0.037172 0.366103 0.021467 0.740368 1.136677 0.739773 3.615810e-01 5.538398e-01 4.963908e-03 13951.588975
6 0.515017 3.576919 0.442112 0.019754 0.131618 0.512714 0.989586 1.563574 0.497919 3.306783e-01 7.172812e-09 2.548533e-03 14763.672024
12 0.790903 0.980743 1.183645 0.245645 0.418042 1.897280 0.659343 0.961191 1.180327 3.828303e+00 6.992257e-01 1.240319e-01 16020.739766
5 0.990000 3.478991 1.977104 0.046105 1.206637 0.931285 0.999967 3.202098 1.299714 3.994489e+00 1.508210e+00 8.130838e-01 23042.642491

The best-fit parameter combination has a much higher likelihood than the second best. It infers a very large population size ratio for PAR and a very large $T_c$, i. e. of the time of divergence with gene flow before the beginning of the time of divergence without gene flow. I think I need to further explore this region of the parameter space.


In [27]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,6:12])
popt


Out[27]:
array([ 2.87759068,  7.05102643,  5.51529529,  0.04619322,  0.24136439,
        0.0952299 ])

In [28]:
%%px --local

pts_l = [60, 70, 80] # make finer grid
fold = 1
maxiter = 100

In [29]:
p0 = popt

#ar_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10))

In [30]:
get_flag_count(ar_split_asym_mig_iso, NM=True)


success 0
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 10
unknown flag 0

In [70]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [73]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[73]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
13 3.495075 4.606451 5.940000 0.055664 0.246368 0.105244 2.769359 6.770118 5.196347 0.049832 0.249131 0.094203 12156.467656
6 1.114068 2.045370 0.628484 0.054210 0.524956 0.061246 2.877591 7.051026 5.515295 0.046193 0.241364 0.095230 12157.097777
1 2.023358 10.767325 2.789793 0.078193 0.156185 0.061440 2.375215 5.808945 4.280676 0.057356 0.288362 0.078355 12157.700922
12 2.034905 10.369993 5.940000 0.057272 0.258997 0.157822 2.444901 5.949439 4.427677 0.057561 0.282947 0.088003 12158.247891
15 3.883738 7.047838 5.940000 0.090944 0.336280 0.158998 2.735529 6.733220 5.273479 0.047798 0.250582 0.087689 12158.898894
14 4.070522 4.065824 5.685736 0.024582 0.164961 0.098403 3.129112 7.593721 5.954911 0.045584 0.212582 0.093799 12159.427653
7 3.336565 8.323858 2.954783 0.068649 0.352139 0.060697 2.231585 5.399258 3.869496 0.063004 0.303320 0.075968 12160.418659
27 2.713572 4.394801 5.940000 0.023283 0.307180 0.140782 2.292742 5.556836 4.013403 0.066660 0.309960 0.093562 12167.807106
19 1.507658 9.941004 3.169746 0.027688 0.199411 0.160162 1.725725 4.236857 2.839430 0.078120 0.395506 0.058068 12169.570121
0 1.745966 9.620872 5.940000 0.033999 0.438397 0.159049 1.956295 4.781913 3.276959 0.078476 0.366819 0.080253 12176.583502

This looks like decent convergence. Let's try to refine it even more.


In [68]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2 and Ti
upper_bound = [1e4, 1e4, 8, 10, 10, 6] # note, I have increased the upper bound for T
lower_bound = [1e-4, 1e-4, 0, 0, 0, 0]

In [25]:
%%px --local

pts_l = [60, 70, 80] # make finer grid
fold = 1
maxiter = 300

In [74]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,6:12])
popt


Out[74]:
array([ 2.76935944,  6.77011784,  5.19634719,  0.04983223,  0.24913079,
        0.09420312])

In [75]:
p0 = popt

In [77]:
#ar_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10))

In [54]:
% ll OUT_2D_models/split_asym_mig_iso_[0-9]*dill


-rw-rw-r-- 1 claudius 348 Jun  3 13:28 OUT_2D_models/split_asym_mig_iso_0.2845_1.7805_0.5426_0.1574_2.6880_0.0671.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:26 OUT_2D_models/split_asym_mig_iso_0.3194_1.4762_0.9980_0.0259_0.6660_0.0084.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:19 OUT_2D_models/split_asym_mig_iso_0.3966_1.0112_4.6870_0.0632_0.3066_3.9544.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:44 OUT_2D_models/split_asym_mig_iso_0.4610_0.9093_4.5903_0.0943_2.0835_0.0105.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:18 OUT_2D_models/split_asym_mig_iso_0.5150_3.5769_0.4421_0.0198_0.1316_0.5127.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:26 OUT_2D_models/split_asym_mig_iso_0.5534_3.4236_0.6699_0.1781_0.5665_0.0093.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:28 OUT_2D_models/split_asym_mig_iso_0.6505_5.6416_0.5122_0.0804_0.8745_0.0934.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:23 OUT_2D_models/split_asym_mig_iso_0.7908_2.5079_0.3669_0.1985_1.3856_2.4417.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:30 OUT_2D_models/split_asym_mig_iso_0.7909_0.9807_1.1836_0.2456_0.4180_1.8973.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:20 OUT_2D_models/split_asym_mig_iso_0.9557_12.3400_0.5296_0.1391_0.1959_0.4380.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:25 OUT_2D_models/split_asym_mig_iso_0.9612_1.0591_0.5315_0.0372_0.3661_0.0215.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:31 OUT_2D_models/split_asym_mig_iso_0.9900_3.4790_1.9771_0.0461_1.2066_0.9313.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:20 OUT_2D_models/split_asym_mig_iso_0.9900_7.0126_1.0987_0.1473_0.3955_2.1725.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:21 OUT_2D_models/split_asym_mig_iso_0.9900_8.3599_2.9673_0.2475_0.2164_0.2956.dill
-rw-rw-r-- 1 claudius 348 Jun  3 12:20 OUT_2D_models/split_asym_mig_iso_0.9900_9.7434_0.7174_0.1046_0.9230_1.7020.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:27 OUT_2D_models/split_asym_mig_iso_1.1141_2.0454_0.6285_0.0542_0.5250_0.0612.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:12 OUT_2D_models/split_asym_mig_iso_1.5077_9.9410_3.1697_0.0277_0.1994_0.1602.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:34 OUT_2D_models/split_asym_mig_iso_1.6384_7.0271_3.3284_0.0934_0.2796_0.0847.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:12 OUT_2D_models/split_asym_mig_iso_1.7460_9.6209_5.9400_0.0340_0.4384_0.1590.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:35 OUT_2D_models/split_asym_mig_iso_1.8997_10.6726_4.2094_0.0350_0.1586_0.1290.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:11 OUT_2D_models/split_asym_mig_iso_2.0234_10.7673_2.7898_0.0782_0.1562_0.0614.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:11 OUT_2D_models/split_asym_mig_iso_2.0349_10.3700_5.9400_0.0573_0.2590_0.1578.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:35 OUT_2D_models/split_asym_mig_iso_2.0435_3.7747_3.3910_0.0410_0.1388_0.0624.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:27 OUT_2D_models/split_asym_mig_iso_2.0763_5.4842_0.8031_0.0594_2.5249_0.0242.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:35 OUT_2D_models/split_asym_mig_iso_2.1507_3.7523_7.3395_0.0534_0.1830_0.0545.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:36 OUT_2D_models/split_asym_mig_iso_2.2081_8.3252_5.5948_0.0456_0.4947_0.0585.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:43 OUT_2D_models/split_asym_mig_iso_2.3915_8.9705_2.7597_0.0616_2.1593_0.0466.dill
-rw-rw-r-- 1 claudius 348 Jun  3 13:27 OUT_2D_models/split_asym_mig_iso_2.6104_1.3056_1.4296_0.1888_2.7142_0.0095.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:12 OUT_2D_models/split_asym_mig_iso_2.7136_4.3948_5.9400_0.0233_0.3072_0.1408.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:36 OUT_2D_models/split_asym_mig_iso_3.1579_11.8947_6.8328_0.0892_0.3437_0.1251.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:34 OUT_2D_models/split_asym_mig_iso_3.1934_3.3982_7.4935_0.0543_0.1844_0.1000.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:11 OUT_2D_models/split_asym_mig_iso_3.3366_8.3239_2.9548_0.0686_0.3521_0.0607.dill
-rw-rw-r-- 1 claudius 348 Jun  3 19:34 OUT_2D_models/split_asym_mig_iso_3.3456_9.1989_7.9200_0.0369_0.1459_0.0741.dill
-rw-rw-r-- 1 claudius 347 Jun  3 19:33 OUT_2D_models/split_asym_mig_iso_3.4219_12.8829_7.9200_0.0909_0.1375_0.1042.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:12 OUT_2D_models/split_asym_mig_iso_3.4951_4.6065_5.9400_0.0557_0.2464_0.1052.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:12 OUT_2D_models/split_asym_mig_iso_3.8837_7.0478_5.9400_0.0909_0.3363_0.1590.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:11 OUT_2D_models/split_asym_mig_iso_4.0705_4.0658_5.6857_0.0246_0.1650_0.0984.dill
-rw-rw-r-- 1 claudius 347 Jun  3 19:33 OUT_2D_models/split_asym_mig_iso_4.5080_4.2112_4.5305_0.0386_0.3568_0.0498.dill
-rw-rw-r-- 1 claudius 346 Jun  3 14:10 OUT_2D_models/split_asym_mig_iso_4.5436_11.2858_4.1280_0.0831_0.1672_0.0722.dill

In [55]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso_[0-9]*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [56]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[56]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
31 3.421903 12.882920 7.920000 0.090910 0.137533 0.104192 3.428735 8.372007 6.719565 0.040627 0.202323 0.118790 12156.061309
29 2.208113 8.325249 5.594802 0.045599 0.494681 0.058458 3.434782 8.387801 6.732856 0.040569 0.202054 0.119088 12156.061960
17 1.638405 7.027064 3.328377 0.093406 0.279576 0.084667 3.365763 8.218499 6.573684 0.041489 0.206208 0.116898 12156.066451
4 3.157867 11.894738 6.832827 0.089239 0.343652 0.125056 3.151832 7.672115 6.074031 0.044790 0.217833 0.107115 12156.250139
2 2.150693 3.752285 7.339520 0.053352 0.182956 0.054508 2.905080 7.099776 5.511164 0.047722 0.237869 0.100633 12156.336374
8 4.507973 4.211151 4.530473 0.038619 0.356819 0.049796 2.838367 6.936326 5.355545 0.048636 0.243390 0.096733 12156.361180
19 3.495075 4.606451 5.940000 0.055664 0.246368 0.105244 2.769359 6.770118 5.196347 0.049832 0.249131 0.094203 12156.467656
16 1.899722 10.672562 4.209415 0.035018 0.158627 0.129019 2.549936 6.237224 4.683557 0.053493 0.269833 0.085552 12156.997320
15 3.193392 3.398205 7.493464 0.054278 0.184446 0.099955 2.625334 6.426852 4.896773 0.051447 0.259054 0.084092 12157.030051
9 1.114068 2.045370 0.628484 0.054210 0.524956 0.061246 2.877591 7.051026 5.515295 0.046193 0.241364 0.095230 12157.097777

This looks like good convergence.

LRT

I would like to compare this model with the asymmetric migration model.


In [42]:
ll_s = 12359
ll_c = 12156
D = 2 * (ll_s - ll_c)
D


Out[42]:
406

With the uncorrected spectrum D was greater than 1000, so the improvement in fit when introducing recent cessation of gene flow was stronger.


In [43]:
# calculate p-value for Chi-square dist.
# the weights specify a weighted sum of chi^2 distributions with 0 and 1 d.o.f
# this is because Ti is 0 in the split_asym_mig model and at the boundary of the parameter space
p = dadi.Godambe.sum_chi2_ppf(D, weights=(0.5, 0.5))
p


Out[43]:
0.0

In [65]:
dadi.Godambe.sum_chi2_ppf?

Allowing for a recent period without gene flow significantly improves the fit as compared to the asymmetric migration model. I doubt that there is enough linkage in the data to compromise this result.

Interpretation


In [44]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,6:12])
popt


Out[44]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969])

In [45]:
model = func_ex(popt, ns, pts_l)

In [46]:
ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[46]:
-12156.061308906654

In [47]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00136.

In [48]:
mu = 3e-9
L = sfs2d.data.sum() # this sums over all entries in the spectrum, including masked ones, i. e. also contains invariable sites
print "The total sequence length for the 2D spectrum is {0:,}.".format(int(L))
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The total sequence length for the 2D spectrum is 1,130,775.
The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:
 113,522.

This quite a small effective population size.


In [30]:
nu1, nu2, Tc, m1, m2, Ti = popt

In [49]:
print "The ancestral population split apart {0:,} generations ago.".format(int((Tc+Ti)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(nu1*N_ref), int(nu2*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1/2*nu2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2/2*nu1)),
print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(m1/2/N_ref/nu2),
print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(m2/2/N_ref/nu1),
print "ERY and PAR remained in contact for {0:,} generations.".format(int(Tc*2*N_ref)),
print "{0:,} generations ago gene flow between ERY and PAR had ceased.".format(int(Ti*2*N_ref))


The ancestral population split apart 1,552,611 generations ago. Immediately after the split the ERY population changed to a size of 389,237 and the PAR population to 950,409. Since the split of the ancestral population, PAR received 1 individual from ERY every 5.88 generations, while ERY received 1 PAR individual every 2.88 generations. Put another way: The PAR population contained a constant proportion of 2.14e-08 of new immigrant alleles each generation and the ERY population contained a constant proportion of 2.60e-07 of new immigrant alleles each generation. ERY and PAR remained in contact for 1,525,640 generations. 26,970 generations ago gene flow between ERY and PAR had ceased.

The following table compares the inferred parameters for the ancient migration model from the uncorrected and corrected spectrum in their absolute units:

parameter uncorrected corrected
$N_{a}$ 125,977 113,522
$N_{ery}$ 363,349 389,237
$N_{par}$ 886,821 950,409
$T_c$ 1,748,505 1,525,640
$p_{ery->par}$ 2.21e-08 2.14e-08
$p_{par->ery}$ 3.27e-07 2.60e-07
$T_i$ 43,628 26,970
D 1044 406

$N_x$ have unit individuals, $T_x$ has unit generations, $p_x$ are proportions of new immigrant alleles per generation and D is two times the likelihood ratio between the best fit parameters of this model with the hitherto best performing asymmetric migration model.

Note that with the uncorrected spectrum, I could not achieve satisfactory convergence of parameter estimations. The parameters inferred with the corrected spectrum are therefore more reliable. The inferred population sizes for ERY and PAR aren't very different between corrected and uncorrected spectra and so is the inferred time of contact ($T_c$). The migration rates are slightly reduced with the corrected spectrum and the time since cessation of gene flow ($T_i$) is inferred to be more recent than with the uncorrected spectrum.

The addition of $T_i$ improves the model by 203 log likelihood units as compared to the model without a period of complete isolation (split_asym_mig above). If the better fit is not just due to fitting noise and bias in the data, then this means that the final period of complete isolation is significantly greater than 0. I think the fact that both the raw and the corrected spectrum lead to very similar parameter estimates provides some support for the interpretation that $T_i$ is actually greater than zero and that this is not just due to fitting noise or bias in the data.

Residuals


In [38]:
dadi.Plotting.plot_2d_comp_multinom(model=model, data=sfs2d, vmin=1)


compare model spectra


In [45]:
func_ex = dadi.Numerics.make_extrap_log_func(split_asym_mig)

popt_asym_mig = [1.5554055 ,  3.81288413,  2.34380465,  0.07615248,  0.34730636]

model_asym_mig = func_ex(popt_asym_mig, ns, pts_l)

model_asym_mig = dadi.Inference.optimally_scaled_sfs(model_asym_mig, sfs2d)

model = dadi.Inference.optimally_scaled_sfs(model, sfs2d)

In [46]:
dadi.Plotting.plot_2d_comp_multinom(data=model.fold() , model=model_asym_mig.fold(), \
                                    vmin=1, title=['ancient mig', 'asym mig'], pop_ids=['ery', 'par'])


two epoch migration

I am going to try and allow different rates of gene flow in the two epochs.m


In [79]:
def two_epoch_migration(params, ns, pts):
    """
    params = (nu1,nu2,T1,m1,m2,T2,m1_2,m2_2)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time T1 followed by a second epoch T2 of potentially changed rates of gene flow
    between the two populations.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    T1: Time of first epoch after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    T2: Time of second epoch
    m1_2: Migration rate from ery into par in 2nd epoch
    m2_2: Migration rate from par into ery in 2nd epoch
    The split lies T1+T2 * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,T1,m1,m2,T2,m1_2,m2_2 = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, T1, nu1, nu2, m12=m2, m21=m1)
    
    # divergence with potentially different rate of gene flow
    phi = dadi.Integration.two_pops(phi, xx, T2, nu1, nu2, m12=m2_2, m21=m1_2)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [82]:
cl[:].push(dict(two_epoch_migration=two_epoch_migration))


Out[82]:
<AsyncResult: _push>

In [83]:
%%px --local

func = two_epoch_migration

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [84]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/two_epoch_migration" # set file name stub for opt. result files
fixed_params = None

In [85]:
%%px --local

# set lower and upper bounds to nu1,nu2,T1,m1,m2,T2,m1_2,m2_2
upper_bound = [1e4, 1e4, 6, 10, 10, 6, 10, 10]
lower_bound = [1e-4, 1e-4, 0, 0, 0, 0, 0, 0]

Get the best fit parameter values from the ancient migration model.


In [90]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso_[0-9]*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [92]:
l = 2*6+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df_ami = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

popt_ami = df_ami.sort_values(by='-logL', ascending=True).head(10).iloc[0,6:12]
popt_ami


Out[92]:
nu1_opt    3.428735
nu2_opt    8.372007
Tc_opt     6.719565
m1_opt     0.040627
m2_opt     0.202323
Ti_opt     0.118790
Name: 31, dtype: float64

For starting values, I am going to set migrations rates equal in both epochs.


In [93]:
popt_ami[-3:-1]


Out[93]:
m1_opt    0.040627
m2_opt    0.202323
Name: 31, dtype: float64

In [102]:
p0 = np.array(popt_ami)
p0


Out[102]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969])

In [103]:
# extend array size
p0 = np.resize(p0, 8)
p0


Out[103]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969,  3.42873459,  8.372007  ])

In [105]:
# set starting values of migration rates in 2nd epoch equal to 1st epoch
p0[-2:] = popt_ami[-3:-1]
p0


Out[105]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969,  0.04062737,  0.20232337])

In [106]:
#ar_two_epoch_migration = lbview.map(run_dadi, repeat(p0, 10))

In [113]:
ar_two_epoch_migration = []

for filename in glob("OUT_2D_models/two_epoch_migration_[0-9]*dill"):
    ar_two_epoch_migration.append(dill.load(open(filename)))

In [116]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_two_epoch_migration]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'T1_0', 'm1_0', 'm2_0', 'T2_0', 'm1_2_0', 'm2_2_0', 'nu1_opt', 'nu2_opt', 'T1_opt', 'm1_opt', 'm2_opt', 'T2_opt', 'm1_2_opt', 'm2_2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[116]:
nu1_0 nu2_0 T1_0 m1_0 m2_0 T2_0 m1_2_0 m2_2_0 nu1_opt nu2_opt T1_opt m1_opt m2_opt T2_opt m1_2_opt m2_2_opt -logL
5 5.644696 6.748929 3.002125 0.018630 0.247479 0.234217 0.049727 0.156318 2.538702 6.130622 4.548313 0.064246 0.263586 0.138036 0.001976 0.086557 12157.847750
7 2.948981 5.329506 5.940000 0.035441 0.196438 0.107361 0.025001 0.408056 1.767142 4.336508 2.837843 0.086956 0.376720 0.101575 0.004129 0.145098 12171.148679
0 13.066052 3.184498 5.940000 0.086044 0.180646 0.159243 0.049154 0.256511 2.189496 5.417741 3.746563 0.071557 0.322365 0.184415 0.013051 0.139515 12176.774161
4 10.440152 2.993109 2.775218 0.086204 0.248548 0.247265 0.097011 0.145460 1.631513 3.986120 2.462645 0.109470 0.422335 0.154743 0.005913 0.211822 12200.543287
9 1.304524 2.965804 5.940000 0.105781 0.061753 0.047698 0.017768 0.403388 1.371437 3.503080 2.002005 0.092343 0.486076 0.079588 0.005007 0.196700 12201.872465
1 1.396719 3.307737 5.345660 0.012358 0.103650 0.230379 0.016208 0.273416 1.386123 3.501128 1.958705 0.121167 0.489743 0.155605 0.008597 0.275667 12236.506649
6 1.048380 19.130862 4.150689 0.044466 0.160189 0.120627 0.113389 0.446234 1.101608 2.893284 1.452418 0.095354 0.593367 0.069671 0.026064 0.301572 12279.030582
2 9.766402 5.858023 5.940000 0.052243 0.665967 0.059187 0.033158 0.195318 1.966739 5.438335 3.484164 0.046854 0.426463 0.120282 0.008939 0.128167 12282.645148
3 9.340080 13.672005 5.940000 0.105902 0.070981 0.134060 0.047350 0.729642 1.641616 4.039555 2.551174 0.072351 0.327008 0.000086 0.010523 0.285561 12360.470345
8 0.971030 4.978357 5.940000 0.141666 0.149956 0.220303 0.118267 0.471358 0.985080 2.586597 1.240356 0.098172 0.575925 0.023813 0.065264 0.287212 12370.050739

In [118]:
popt = df.sort_values(by='-logL', ascending=True).iloc[0,8:16]
popt


Out[118]:
nu1_opt     2.538702
nu2_opt     6.130622
T1_opt      4.548313
m1_opt      0.064246
m2_opt      0.263586
T2_opt      0.138036
m1_2_opt    0.001976
m2_2_opt    0.086557
Name: 5, dtype: float64

In [119]:
p0 = np.array(popt)

In [120]:
#ar_two_epoch_migration = lbview.map(run_dadi, repeat(p0, 10))

In [121]:
ar_two_epoch_migration = []

for filename in glob("OUT_2D_models/two_epoch_migration_[0-9]*dill"):
    ar_two_epoch_migration.append(dill.load(open(filename)))

In [122]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_two_epoch_migration]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'T1_0', 'm1_0', 'm2_0', 'T2_0', 'm1_2_0', 'm2_2_0', 'nu1_opt', 'nu2_opt', 'T1_opt', 'm1_opt', 'm2_opt', 'T2_opt', 'm1_2_opt', 'm2_2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[122]:
nu1_0 nu2_0 T1_0 m1_0 m2_0 T2_0 m1_2_0 m2_2_0 nu1_opt nu2_opt T1_opt m1_opt m2_opt T2_opt m1_2_opt m2_2_opt -logL
9 5.644696 6.748929 3.002125 0.018630 0.247479 0.234217 0.049727 0.156318 2.538702 6.130622 4.548313 0.064246 0.263586 0.138036 0.001976 0.086557 12157.847750
7 4.678110 4.188630 5.940000 0.028242 0.539588 0.037414 0.001736 0.179484 2.083647 5.107308 3.576527 0.069250 0.329878 0.096731 0.001353 0.069254 12157.954261
12 2.948981 5.329506 5.940000 0.035441 0.196438 0.107361 0.025001 0.408056 1.767142 4.336508 2.837843 0.086956 0.376720 0.101575 0.004129 0.145098 12171.148679
0 13.066052 3.184498 5.940000 0.086044 0.180646 0.159243 0.049154 0.256511 2.189496 5.417741 3.746563 0.071557 0.322365 0.184415 0.013051 0.139515 12176.774161
14 0.748472 20.277653 5.940000 0.056911 0.364987 0.043068 0.000921 0.047623 1.392438 3.391094 1.992288 0.091041 0.464573 0.038353 0.000164 0.000059 12194.563864
6 2.975986 3.917147 1.395438 0.210341 0.108599 0.132947 0.002226 0.059428 1.526270 3.677935 2.169396 0.102268 0.450583 0.100748 0.017170 0.165797 12194.926867
8 10.440152 2.993109 2.775218 0.086204 0.248548 0.247265 0.097011 0.145460 1.631513 3.986120 2.462645 0.109470 0.422335 0.154743 0.005913 0.211822 12200.543287
17 1.304524 2.965804 5.940000 0.105781 0.061753 0.047698 0.017768 0.403388 1.371437 3.503080 2.002005 0.092343 0.486076 0.079588 0.005007 0.196700 12201.872465
1 1.354384 9.818386 2.766551 0.138175 0.726047 0.270668 0.001349 0.157645 1.333588 3.385362 1.926586 0.094938 0.530317 0.096417 0.022601 0.215701 12217.508936
3 1.396719 3.307737 5.345660 0.012358 0.103650 0.230379 0.016208 0.273416 1.386123 3.501128 1.958705 0.121167 0.489743 0.155605 0.008597 0.275667 12236.506649

In [123]:
%%px --local

pts_l = [50, 60, 70] # make finer grid
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [124]:
popt = df.sort_values(by='-logL', ascending=True).iloc[0,8:16]
popt


Out[124]:
nu1_opt     2.538702
nu2_opt     6.130622
T1_opt      4.548313
m1_opt      0.064246
m2_opt      0.263586
T2_opt      0.138036
m1_2_opt    0.001976
m2_2_opt    0.086557
Name: 9, dtype: float64

In [125]:
p0 = np.array(popt)

In [126]:
#ar_two_epoch_migration = lbview.map(run_dadi, repeat(p0, 10))

In [127]:
ar_two_epoch_migration = []

for filename in glob("OUT_2D_models/two_epoch_migration_[0-9]*dill"):
    ar_two_epoch_migration.append(dill.load(open(filename)))

In [128]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_two_epoch_migration]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'T1_0', 'm1_0', 'm2_0', 'T2_0', 'm1_2_0', 'm2_2_0', 'nu1_opt', 'nu2_opt', 'T1_opt', 'm1_opt', 'm2_opt', 'T2_opt', 'm1_2_opt', 'm2_2_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[128]:
nu1_0 nu2_0 T1_0 m1_0 m2_0 T2_0 m1_2_0 m2_2_0 nu1_opt nu2_opt T1_opt m1_opt m2_opt T2_opt m1_2_opt m2_2_opt -logL
3 4.271986 8.641312 4.794548 0.049127 0.418844 0.126553 0.001728 0.044824 2.643431 6.435740 4.821750 0.058484 0.261065 0.138893 2.747002e-09 0.072061 12154.100915
18 1.279282 5.686232 5.940000 0.044004 0.391062 0.099443 0.001037 0.080965 2.716132 6.611594 5.007243 0.056135 0.255727 0.141354 1.241622e-03 0.062925 12154.208223
12 2.490742 4.667791 5.940000 0.045071 0.203011 0.257834 0.001003 0.104594 2.402622 5.867840 4.300637 0.061562 0.285851 0.113998 1.135624e-05 0.063406 12154.489780
9 1.874343 3.290007 4.158250 0.076058 0.251006 0.245463 0.001823 0.111961 2.396407 5.856597 4.294042 0.061744 0.287009 0.113985 1.041954e-06 0.063553 12154.503482
5 1.943964 4.073098 2.381334 0.060125 0.369100 0.182855 0.001632 0.070730 2.402888 5.872039 4.307882 0.061245 0.285900 0.112139 5.608747e-05 0.061718 12154.520679
13 1.684699 6.104552 2.280193 0.040078 0.137648 0.080494 0.002983 0.164685 2.396241 5.857478 4.297961 0.060923 0.286318 0.108142 1.128269e-04 0.056165 12154.615936
15 2.965996 6.209866 4.247201 0.035964 0.318504 0.081125 0.002072 0.153527 2.385315 5.829375 4.268894 0.061789 0.288113 0.112019 5.665586e-05 0.061866 12154.616848
22 1.524336 10.087533 5.650031 0.048135 0.314430 0.092982 0.001043 0.096022 2.620108 6.361264 4.758290 0.059082 0.262611 0.140994 7.253983e-04 0.076023 12154.858977
11 1.970501 8.478834 5.796177 0.112221 0.207459 0.132190 0.002152 0.062450 2.421988 5.924217 4.383946 0.057870 0.282987 0.093348 1.845574e-04 0.029072 12155.492029
17 5.644696 6.748929 3.002125 0.018630 0.247479 0.234217 0.049727 0.156318 2.538702 6.130622 4.548313 0.064246 0.263586 0.138036 1.976358e-03 0.086557 12157.847750

This looks reasonably converged. There is no good convergence on $m1_2$, the migration rate from ERY into PAR in the 2nd epoch, but all values indicate a drastic reduction. The best-fit parameter combination for the ancient migration model had -logL of 12,156, so only 2 log likelihood units worse.


In [129]:
ll_s = -12156
ll_c = -12154
D = 2*(ll_c - ll_s)
D


Out[129]:
4

In [130]:
# calculate p-value for Chi-square dist. with 2 d.o.f
# two parameters are at the boundary of the parameter space
# therefore, D should be distributed by a mixture of Chi-square dist.'s
# with different degrees of freedom, but I cannot calculate the mixing probabilities (see Self1987)
# The following test should be conservative.
p = dadi.Godambe.sum_chi2_ppf(D, weights=(0, 0, 1))
p


Out[130]:
0.1353352832366127

I don't think that this model provides a significant improvement over the ancient migration model. All best-fit parameter combinations indicate a drastic reduction in migration from ERY into PAR in the 2nd epoch. The reduction in gene flow from PAR to ERY is inferred to be around 75-80%. The time for the second epoch (T2), is very similar to the time Ti inferred in the ancient migration model. So we cannot rule out that gene flow has continued in the 2nd epoch, especially in the direction from PAR into ERY, but there doesn't seem to be enough power to show this.

ancient migration + recent bottleneck

I would like to combine the ancient migration and recent bottleneck model. To keep the model simple, I will first run a model that enforces the coincidence of the second size change with the cessation of gene flow. Then I will run a model that allows the cessation of gene flow to occur earlier than the bottleneck (i. e. in the 1st epoch after the split) and a model that allows the cessation of gene flow to occur after the bottleneck (i. e. in the second epoch after the split).

coincident bottleneck and isolation


In [36]:
def ancMig_recBotIso(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by cessation of gene flow and coincident potential
    second size change. The split coincides with the first potential pop size change.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Tbi: Time of isolation after cessation of gene flow and second size change
    nu1_2: pop size ratio of pop 1 after second size change
    nu2_2: pop size ratio of pop 2 after second size change
    The split lies Tc+Tbi * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Tbi = params
    
    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
        
    # divergence without gene flow and potential second pop size change
    phi = dadi.Integration.two_pops(phi, xx, Tbi, nu1_2, nu2_2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [38]:
cl[:].push(dict(ancMig_recBotIso=ancMig_recBotIso))


Out[38]:
<AsyncResult: _push>

In [41]:
%%px --local

func_ex = dadi.Numerics.make_extrap_log_func(ancMig_recBotIso)

In [40]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/ancMig_recBotIso" # set file name stub for opt. result files
fixed_params = None

In [47]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2, nu1_2, nu2_2, Tbi
upper_bound = [1e4, 1e4, 6, 10, 10, 1e4, 1e4, 6] 
lower_bound = [1e-4, 1e-4, 0, 0, 0, 1e-4, 1e-4, 0]

ancient migration best fit parameter values

nu1,nu2,Tc,m1,m2,Ti [ 3.42873459, 8.372007 , 6.71956526, 0.04062737, 0.20232337, 0.11878969]

recent bottleneck best fit parameter values

nu1,nu2,T1,nu1_2,nu2_2,T2,m1,m2 [ 1.38930513e+00, 4.23522440e+00, 1.88972686e+00, 3.10953710e-03, 1.07862318e-03, 2.01022845e-05, 7.75847834e-02, 4.16367492e-01]

param recent bottleneck ancient mig
nu1 1.389 3.428
nu2 4.235 8.372
Tc 1.889 6.719
m1 0.077 0.040
m2 0.416 0.202
nu1_2 3.109e-03 -
nu2_2 1.078e-03 -
Tb 2.010e-05 -
Ti - 0.118

In [44]:
# nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Tbi

p0 = [1.5, 5, 2, 0.06, 0.3, 3e-1, 1e-1, 0.01]

In [48]:
#ar_ancMig_recBotIso = lbview.map(run_dadi, repeat(p0, 10))

In [49]:
ar_ancMig_recBotIso.done()


Out[49]:
True

In [54]:
% ll OUT_2D_models/ancMig_recBotIso_[0-9]*dill


-rw-rw-r-- 1 claudius 378 Jul 22 13:34 OUT_2D_models/ancMig_recBotIso_0.4194_14.4423_5.9400_0.0388_0.2040_0.3249_0.1138_0.0288.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:35 OUT_2D_models/ancMig_recBotIso_0.4296_18.2138_3.2194_0.2022_0.3386_1.0146_0.0850_0.0364.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:35 OUT_2D_models/ancMig_recBotIso_0.4662_9.4859_2.4039_0.0282_0.1192_0.8763_0.0589_0.0172.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:34 OUT_2D_models/ancMig_recBotIso_0.5788_3.9118_1.4565_0.0308_0.4549_0.4346_0.0285_0.0221.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:36 OUT_2D_models/ancMig_recBotIso_1.6078_1.7547_5.9400_0.0513_0.9786_0.4442_0.0589_0.0304.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:33 OUT_2D_models/ancMig_recBotIso_1.9702_2.2163_1.2388_0.1347_0.1396_0.4062_0.0472_0.0082.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:33 OUT_2D_models/ancMig_recBotIso_2.1466_1.7507_0.8202_0.2116_0.1650_0.1516_0.0858_0.0055.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:34 OUT_2D_models/ancMig_recBotIso_3.4964_1.6700_2.6433_0.0394_0.1246_0.3876_0.2672_0.0043.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:35 OUT_2D_models/ancMig_recBotIso_5.3520_9.8053_5.9400_0.0258_0.4720_1.1685_0.0311_0.0355.dill
-rw-rw-r-- 1 claudius 378 Jul 22 13:34 OUT_2D_models/ancMig_recBotIso_5.5162_4.8912_2.4442_0.2045_0.5971_0.3131_0.0499_0.0211.dill

In [55]:
ar_ancMig_recBotIso = []

for filename in glob("OUT_2D_models/ancMig_recBotIso_[0-9]*dill"):
    ar_ancMig_recBotIso.append(dill.load(open(filename)))

In [57]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_recBotIso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tbi_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tbi_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[57]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tbi_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tbi_opt -logL
3 1.970182 2.216278 1.238836 0.134656 0.139593 0.406225 0.047195 0.008243 1.321781 3.919044 1.218134 0.115785 0.387281 0.288313 0.182305 0.004374 12233.050453
8 5.352016 9.805293 5.940000 0.025818 0.471954 1.168495 0.031058 0.035542 1.852950 7.378045 3.516342 0.004864 0.374873 1.305246 0.373699 0.009473 12440.090197
7 2.146619 1.750690 0.820213 0.211585 0.164996 0.151599 0.085818 0.005540 1.028246 2.376309 0.926256 0.077146 0.539169 0.051176 0.198130 0.000524 12442.106356
6 3.496409 1.670023 2.643266 0.039408 0.124603 0.387642 0.267203 0.004322 1.259196 2.986590 1.210872 0.118903 0.316550 0.267660 0.148737 0.000147 12553.388569
0 0.578844 3.911799 1.456550 0.030847 0.454866 0.434572 0.028489 0.022093 0.987882 2.948026 2.057026 0.074250 0.551988 0.354218 0.108490 0.001563 12710.216444
5 0.419447 14.442344 5.940000 0.038754 0.204041 0.324865 0.113809 0.028834 0.793055 2.344703 0.867996 0.002460 0.736106 1.885020 0.047926 0.000025 12828.401068
1 1.607775 1.754698 5.940000 0.051318 0.978553 0.444158 0.058939 0.030432 1.210868 4.507541 2.239579 0.000597 0.994647 0.695732 1.412206 0.036674 12990.989779
4 5.516234 4.891248 2.444207 0.204498 0.597136 0.313093 0.049949 0.021093 1.584745 7.534500 3.172014 0.181045 0.432639 0.502547 0.253445 0.009542 13445.350548
2 0.429638 18.213759 3.219393 0.202249 0.338572 1.014562 0.084978 0.036427 0.763590 48.706525 2.339927 0.122136 0.893823 1.017042 0.060279 0.007778 14598.633798
9 0.466158 9.485917 2.403921 0.028217 0.119158 0.876339 0.058858 0.017176 0.736955 65.268403 2.498470 0.044034 1.063951 0.826665 0.040417 0.004615 15340.337130

In [58]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[58]:
array([ 1.32178072,  3.91904382,  1.21813362,  0.11578507,  0.38728082,
        0.28831345,  0.18230489,  0.00437431])

In [59]:
p0 = popt

In [60]:
#ar_ancMig_recBotIso = lbview.map(run_dadi, repeat(p0, 10))

In [61]:
ar_ancMig_recBotIso.done()


Out[61]:
True

In [62]:
ar_ancMig_recBotIso = []

for filename in glob("OUT_2D_models/ancMig_recBotIso_[0-9]*dill"):
    ar_ancMig_recBotIso.append(dill.load(open(filename)))

In [63]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_recBotIso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tbi_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tbi_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[63]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tbi_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tbi_opt -logL
12 1.533464 2.073390 2.461184 0.202032 0.658742 0.326864 0.246980 0.011278 1.126253 3.126719 1.264843 0.117780 0.529314 0.338364 0.361522 0.006588 12131.697648
3 1.970182 2.216278 1.238836 0.134656 0.139593 0.406225 0.047195 0.008243 1.321781 3.919044 1.218134 0.115785 0.387281 0.288313 0.182305 0.004374 12233.050453
17 0.351647 2.551956 2.531003 0.390662 0.132161 1.032089 0.505827 0.001220 1.589900 4.418820 1.939845 0.161533 0.295053 1.045949 0.301025 0.008472 12266.244449
16 5.352016 9.805293 5.940000 0.025818 0.471954 1.168495 0.031058 0.035542 1.852950 7.378045 3.516342 0.004864 0.374873 1.305246 0.373699 0.009473 12440.090197
13 2.146619 1.750690 0.820213 0.211585 0.164996 0.151599 0.085818 0.005540 1.028246 2.376309 0.926256 0.077146 0.539169 0.051176 0.198130 0.000524 12442.106356
14 1.499250 2.493017 0.597019 0.152326 0.902897 0.591330 0.678769 0.012841 0.836088 2.327021 0.861108 0.078819 0.823135 0.348555 0.785546 0.006648 12516.468737
8 4.591379 6.783844 1.504796 0.121204 1.031696 1.102251 0.173615 0.006737 0.762582 5.861636 1.573971 0.022656 1.034132 1.137506 0.222307 0.011517 12544.605763
10 3.496409 1.670023 2.643266 0.039408 0.124603 0.387642 0.267203 0.004322 1.259196 2.986590 1.210872 0.118903 0.316550 0.267660 0.148737 0.000147 12553.388569
19 0.398427 1.630699 0.752482 0.263743 0.840420 0.078805 0.173608 0.012282 0.861343 2.270195 0.840446 0.103595 0.802031 0.194661 0.425320 0.003534 12569.246945
0 0.578844 3.911799 1.456550 0.030847 0.454866 0.434572 0.028489 0.022093 0.987882 2.948026 2.057026 0.074250 0.551988 0.354218 0.108490 0.001563 12710.216444

In [64]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[64]:
array([ 1.12625285,  3.12671942,  1.26484299,  0.11778017,  0.52931385,
        0.33836369,  0.36152237,  0.00658795])

In [66]:
p0 = popt

In [67]:
#ar_ancMig_recBotIso = lbview.map(run_dadi, repeat(p0, 10))

In [68]:
ar_ancMig_recBotIso = []

for filename in glob("OUT_2D_models/ancMig_recBotIso_[0-9]*dill"):
    ar_ancMig_recBotIso.append(dill.load(open(filename)))

In [69]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_recBotIso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tbi_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tbi_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[69]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tbi_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tbi_opt -logL
10 3.553295 2.209545 2.279461 0.030644 0.222056 0.129620 0.216963 0.003138 1.748631 4.517862 2.615850 0.074940 0.344502 0.192301 0.180837 0.002480 12068.085807
17 1.533464 2.073390 2.461184 0.202032 0.658742 0.326864 0.246980 0.011278 1.126253 3.126719 1.264843 0.117780 0.529314 0.338364 0.361522 0.006588 12131.697648
14 0.461320 6.037068 3.518469 0.245968 0.464257 0.284900 0.201762 0.010540 1.499236 3.901052 2.132614 0.114302 0.342027 0.603372 0.032787 0.000577 12158.171779
4 1.970182 2.216278 1.238836 0.134656 0.139593 0.406225 0.047195 0.008243 1.321781 3.919044 1.218134 0.115785 0.387281 0.288313 0.182305 0.004374 12233.050453
23 0.351647 2.551956 2.531003 0.390662 0.132161 1.032089 0.505827 0.001220 1.589900 4.418820 1.939845 0.161533 0.295053 1.045949 0.301025 0.008472 12266.244449
8 2.746493 1.589075 1.778353 0.047996 0.528296 0.237798 0.255576 0.013757 1.322540 2.658501 1.423564 0.124004 0.398965 0.170432 1.549534 0.002720 12345.924089
11 1.589025 1.816885 3.135782 0.103391 0.139492 0.752003 0.583836 0.003716 1.135923 2.844101 1.421267 0.102757 0.447762 0.718297 0.511819 0.000197 12380.861832
21 5.352016 9.805293 5.940000 0.025818 0.471954 1.168495 0.031058 0.035542 1.852950 7.378045 3.516342 0.004864 0.374873 1.305246 0.373699 0.009473 12440.090197
18 2.146619 1.750690 0.820213 0.211585 0.164996 0.151599 0.085818 0.005540 1.028246 2.376309 0.926256 0.077146 0.539169 0.051176 0.198130 0.000524 12442.106356
27 0.609080 5.552115 0.731587 0.032296 0.252146 0.807127 0.411905 0.013364 0.666736 3.058520 0.670420 0.012584 0.883941 0.774565 0.281766 0.008159 12505.644758

In [70]:
%%px --local

pts_l = [50, 60, 70] # make finer grid
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [71]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[71]:
array([  1.74863135e+00,   4.51786243e+00,   2.61584964e+00,
         7.49398998e-02,   3.44501588e-01,   1.92301120e-01,
         1.80837187e-01,   2.48015389e-03])

In [72]:
p0 = popt

In [73]:
#ar_ancMig_recBotIso = lbview.map(run_dadi, repeat(p0, 10))

In [75]:
ar_ancMig_recBotIso = []

for filename in glob("OUT_2D_models/ancMig_recBotIso_[0-9]*dill"):
    ar_ancMig_recBotIso.append(dill.load(open(filename)))

In [76]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_recBotIso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tbi_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tbi_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[76]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tbi_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tbi_opt -logL
31 1.121698 3.500501 1.563964 0.146394 0.648317 0.328163 0.154052 0.001241 1.442382 4.861981 2.376640 0.066266 0.460058 1.782318 1.450026 0.039467 11975.211990
13 1.082886 6.064228 4.183760 0.090448 0.268058 0.291451 0.225315 0.002488 1.443690 4.858502 2.378692 0.066206 0.459660 1.774742 1.448861 0.039378 11975.215463
33 1.341194 3.818697 4.200301 0.066710 0.179784 0.358850 0.144003 0.003637 1.438315 4.845024 2.367030 0.066524 0.461106 1.776987 1.446030 0.039368 11975.241202
8 2.011230 8.245136 2.460305 0.056018 0.188406 0.110701 0.175523 0.002141 1.438192 4.844827 2.366380 0.066465 0.461198 1.776134 1.445335 0.039336 11975.242822
37 2.298075 5.431327 1.682355 0.048781 0.207764 0.248858 0.215018 0.004225 1.438246 4.844200 2.366209 0.066491 0.461118 1.776060 1.445153 0.039326 11975.245031
3 2.045100 3.132215 1.432219 0.113772 0.429482 0.114878 0.091948 0.001505 1.437449 4.841584 2.363658 0.066520 0.461323 1.774124 1.442957 0.039259 11975.252232
27 2.775518 8.310596 2.882674 0.085537 0.225897 0.203106 0.313331 0.002002 1.437100 4.839760 2.362103 0.066545 0.461340 1.772851 1.441481 0.039205 11975.258556
36 2.554342 3.817408 2.231544 0.063343 0.282127 0.117985 0.184750 0.001886 1.436913 4.839032 2.361580 0.066563 0.461400 1.772527 1.441062 0.039194 11975.260008
12 1.787702 4.394833 3.980456 0.047847 0.495627 0.109910 0.177272 0.002053 1.308932 4.147763 1.762732 0.076942 0.431950 0.000876 0.000106 0.000002 12047.393034
9 2.887293 2.495374 1.496113 0.066319 0.230643 0.148088 0.092806 0.001994 1.574127 4.825482 2.440403 0.067642 0.360572 0.001275 0.000104 0.000002 12064.275040

This looks like convergence! The best-fit ancient migration model (6 parameters) had -logL of 12,156 and the best-fit recent bottleneck model (8 parameters) had -logL of 12,034. Note that this model infers a bottleneck in the second epoch only for PAR, not for ERY (compare $\nu_{1_{opt}}$ with $\nu_{1_{2opt}}$ and $\nu_{2_{opt}}$ with $\nu_{2_{2opt}}$).

LRT

This ancMig recBotIso model can be turned into the ancient migration model by setting $\nu_{1_2} = \nu_1$ and $\nu_{2_2} = \nu_2$, i. e. no population size change at time $T_{bi}$, just cessation of gene flow. The ancient migration model is therefore nested within this ancMig recBotIso model.


In [77]:
ll_c = -11975
ll_s = -12156

D = 2*(ll_c - ll_s)
D


Out[77]:
362

In [78]:
# calculate p-value for Chi-square dist. with 2 d.o.f.
# the weights specify a weighted sum of chi^2 distributions with 0, 1 and 2 d.o.f

p = dadi.Godambe.sum_chi2_ppf(D, weights=(0, 0, 1))
p


Out[78]:
0.0

This confirms that the ancMig recBotIso model provides a significantly better fit to the data than the ancient migration model despite requiring two more parameters.

The difference between this ancMig recBotIso model and the recent bottleneck model is that the recent bottleneck model enforces continued and constant migration through the second epoch until present while this ancMig recBotIso model enforces complete isolation during the second epoch. Both models have equal number of parameters and one is not nested within the other. A likelihood ratio test can therefore not be used to compare both models. The ancMig recBotIso model provides a better fit to the data by 59 logL units and is therefore clearly the better model. Thus, there seems to be evidence in the data for both a recent "bottleneck" in PAR as well as a recent cessation (or at least reduction) of gene flow.

Interpretation


In [79]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,8:16])
popt


Out[79]:
array([ 1.44238205,  4.86198073,  2.3766401 ,  0.06626632,  0.46005763,
        1.78231791,  1.4500263 ,  0.03946682])

In [80]:
model = func_ex(popt, ns, pts_l)

In [81]:
ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[81]:
-11975.21199025464

In [82]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00296.

In [83]:
mu = 3e-9
L = sfs2d.data.sum() # this sums over all entries in the spectrum, including masked ones, i. e. also contains invariable sites
print "The total sequence length for the 2D spectrum is {0:,}.".format(int(L))
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The total sequence length for the 2D spectrum is 1,130,775.
The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:
 247,073.

In [84]:
nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Tbi = popt

In [91]:
print "The ancestral population split apart {0:,} generations ago.".format(int((Tc+Tbi)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(nu1*N_ref), int(nu2*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1/2*nu2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2/2*nu1)),
print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(m1/2/N_ref/nu2),
print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(m2/2/N_ref/nu1),
print "ERY and PAR remained in contact for {0:,} generations.".format(int(Tc*2*N_ref)),
print "{0:,} generations ago gene flow between ERY and PAR had ceased.".format(int(Tbi*2*N_ref)),
print "At the same time, ERY and PAR underwent a second stepwise simultaneous population size change.",
print "ERY changed to a size of {0:,} individuals and PAR to a size of {1:,} individuals.".format(int(nu1_2*N_ref), int(nu2_2*N_ref))


The ancestral population split apart 1,193,913 generations ago. Immediately after the split the ERY population changed to a size of 356,374 and the PAR population to 1,201,268. Since the split of the ancestral population, PAR received 1 individual from ERY every 6.21 generations, while ERY received 1 PAR individual every 3.01 generations. Put another way: The PAR population contained a constant proportion of 2.76e-08 of new immigrant alleles each generation and the ERY population contained a constant proportion of 6.45e-07 of new immigrant alleles each generation. ERY and PAR remained in contact for 1,174,411 generations. 19,502 generations ago gene flow between ERY and PAR had ceased. At the same time, ERY and PAR underwent a second stepwise simultaneous population size change. ERY changed to a size of 440,364 individuals and PAR to a size of 358,263 individuals.

Note that the bottleneck population size of PAR is still greater than the inferred ancestral population size of ERY and PAR.

Residuals


In [92]:
dadi.Plotting.plot_2d_comp_multinom(data=sfs2d, model=model, vmin=1)


isolation then bottleneck


In [10]:
def ancMig_iso_recBot(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,Ti,nu1_2,nu2_2,Tb)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by cessation of gene flow at time [Ti+Tb]. The split coincides 
    with the first potential pop size change. At time Tb there is a potential second size change.
    Note, that this model does not allow the cessation of gene flow to occur after the second
    pop size change.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of complete isolation before second size change
    nu1_2: pop size ratio of pop 1 after second size change
    nu2_2: pop size ratio of pop 2 after second size change
    Tb: Time since second pop size change (and continued complete isolation)
    The split lies Tc+Ti+Tb * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,Ti,nu1_2,nu2_2,Tb = params
    
    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
    
    # divergence without gene flow
    phi = dadi.Integration.two_pops(phi, xx, Ti, nu1, nu2, m12=0, m21=0)
        
    # potential second pop size change
    phi = dadi.Integration.two_pops(phi, xx, Tb, nu1_2, nu2_2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [11]:
cl[:].push(dict(ancMig_iso_recBot=ancMig_iso_recBot))


Out[11]:
<AsyncResult: _push>

In [12]:
%%px --local

func_ex = dadi.Numerics.make_extrap_log_func(ancMig_iso_recBot)

In [13]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/ancMig_iso_recBot" # set file name stub for opt. result files
fixed_params = None

In [14]:
%%px --local

# set lower and upper bounds to nu1,nu2,Tc,m1,m2,Ti,nu1_2,nu2_2,Tb
upper_bound = [1e4, 1e4, 6, 10, 10, 6, 1e4, 1e4, 6] 
lower_bound = [1e-4, 1e-4, 0, 0, 0, 0, 1e-4, 1e-4, 0]

In [98]:
popt = df.sort_values(by='-logL', ascending=True).iloc[0,8:16]
popt


Out[98]:
nu1_opt      1.442382
nu2_opt      4.861981
Tc_opt       2.376640
m1_opt       0.066266
m2_opt       0.460058
nu1_2_opt    1.782318
nu2_2_opt    1.450026
Tbi_opt      0.039467
Name: 31, dtype: float64

In [18]:
p0 = [1.442, 4.862, 2.376, 0.066266, 0.460058, 1e-2, 1.782, 1.45, 0.039]

In [101]:
#ar_ancMig_iso_recBot = lbview.map(run_dadi, repeat(p0, 10))

In [102]:
ar_ancMig_iso_recBot.done()


Out[102]:
True

In [103]:
ar_ancMig_iso_recBot = []

for filename in glob("OUT_2D_models/ancMig_iso_recBot_[0-9]*dill"):
    ar_ancMig_iso_recBot.append(dill.load(open(filename)))

In [104]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_iso_recBot]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[104]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_2_0 nu2_2_0 Tb_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt nu1_2_opt nu2_2_opt Tb_opt -logL
1 4.947901 3.053726 2.648023 0.058388 0.298823 0.003052 1.834875 2.856667 0.053035 1.715399 5.259198 2.975754 0.063786 0.380574 0.000908 1.817386 2.185919 0.038353 12022.983564
8 1.464668 2.389757 4.511960 0.073835 0.496096 0.007468 1.924427 1.537951 0.041622 1.384505 4.216699 2.127197 0.062749 0.473504 0.016836 1.708314 1.750755 0.027143 12036.024150
9 2.542165 10.200404 5.040071 0.073970 0.205648 0.007417 3.826292 4.150181 0.038382 2.919884 7.960134 5.408758 0.040614 0.231245 0.005406 2.645805 4.025629 0.061391 12052.706067
3 0.788326 2.475461 2.211789 0.023617 0.303627 0.006348 1.012355 1.449413 0.137728 0.867223 3.353449 1.387281 0.043243 0.802984 0.005019 1.016471 1.783774 0.032107 12313.999766
0 0.685538 3.220511 1.524861 0.027552 0.366989 0.004786 0.459903 1.268776 0.105893 0.936598 2.970726 1.482223 0.037040 0.839371 0.000059 0.453877 1.400251 0.012380 12543.174543
4 1.227351 7.218828 3.024549 0.029834 0.206476 0.011862 0.562922 0.426702 0.147730 1.546844 7.782619 2.253528 0.100742 0.503737 0.002279 0.818260 0.408465 0.018419 12607.997894
7 0.531727 1.278744 0.807373 0.128090 0.279302 0.018793 1.912226 5.118966 0.073459 0.792443 1.511921 0.791688 0.225900 0.627861 0.006357 2.169983 4.197410 0.007674 13061.388483
5 0.857942 11.465301 0.696698 0.103434 1.760972 0.013963 6.528095 1.396620 0.009987 0.712728 2.473513 0.669560 0.085460 1.323344 0.060791 7.621004 1.421056 0.021988 13093.935296
6 1.356425 1.394511 1.202871 0.108702 1.230259 0.003114 1.237832 2.643304 0.081505 1.131769 2.057256 1.288685 0.260012 1.140054 0.000682 1.120070 3.035839 0.128041 13211.859877
2 1.364280 7.347522 3.617528 0.230317 1.235810 0.017256 3.201019 0.839749 0.020300 1.350497 13.521215 2.883431 0.068054 1.236077 0.108387 1.750847 0.749806 0.061471 13833.302212

In [105]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,9:18])
popt


Out[105]:
array([  1.71539920e+00,   5.25919756e+00,   2.97575391e+00,
         6.37860070e-02,   3.80574167e-01,   9.07502414e-04,
         1.81738601e+00,   2.18591869e+00,   3.83528519e-02])

In [106]:
p0 = popt

In [107]:
#ar_ancMig_iso_recBot = lbview.map(run_dadi, repeat(p0, 10))

In [108]:
ar_ancMig_iso_recBot.done()


Out[108]:
True

In [109]:
ar_ancMig_iso_recBot = []

for filename in glob("OUT_2D_models/ancMig_iso_recBot_[0-9]*dill"):
    ar_ancMig_iso_recBot.append(dill.load(open(filename)))

In [114]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_iso_recBot]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[114]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_2_0 nu2_2_0 Tb_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt nu1_2_opt nu2_2_opt Tb_opt -logL
7 1.655419 3.196442 1.630544 0.053554 0.323001 0.001045 2.329881 3.002477 0.032739 1.572959 5.056784 2.587948 0.058160 0.428943 0.004386 1.667034 1.034916 0.025092 11988.488316
3 1.201591 6.812789 4.035213 0.084952 0.650764 0.000749 2.679875 1.476983 0.028952 1.227765 4.956760 2.156991 0.047459 0.562733 0.000220 2.125696 1.470790 0.045590 12002.874087
9 2.077680 4.666549 1.821272 0.036062 0.210657 0.000474 2.280124 2.475475 0.067582 1.716465 5.225673 2.746924 0.074520 0.394446 0.000088 2.219282 1.880742 0.053348 12005.406227
1 3.415144 4.606708 5.098854 0.033302 0.318760 0.001289 2.339791 2.334652 0.020461 2.519343 7.099974 4.691554 0.050343 0.252424 0.000550 2.418981 2.683199 0.056444 12010.070125
6 2.390272 10.239465 2.374186 0.060084 0.550052 0.000950 1.820784 2.683769 0.022156 1.621866 5.483624 2.956209 0.049018 0.422281 0.000128 2.288979 2.761693 0.066224 12021.400460
0 1.215885 8.045862 3.114207 0.037061 0.194076 0.000459 1.410804 2.464567 0.026052 1.410414 3.962268 2.193265 0.081820 0.494509 0.000261 1.780370 2.463354 0.058479 12052.891995
4 2.818469 4.055593 3.878851 0.041858 0.412616 0.000744 1.396255 2.256636 0.030139 1.884999 5.318365 3.146870 0.050546 0.357534 0.000361 1.508552 2.447149 0.031568 12074.960488
5 3.307197 8.665150 5.322863 0.043591 0.652502 0.000958 1.539678 2.695268 0.062814 1.313337 4.962304 2.645159 0.046498 0.558233 0.000411 2.169030 2.915770 0.072774 12075.358310
2 3.288000 6.389983 2.650705 0.040861 0.225837 0.001662 1.007060 2.075608 0.021423 2.385878 6.082105 3.958854 0.050947 0.275438 0.001394 1.007175 2.043794 0.024339 12100.447066
8 1.106983 8.749831 3.422520 0.086540 0.242067 0.000583 2.752432 3.250251 0.029550 1.198662 5.706835 2.589927 0.019001 0.610979 0.000076 3.259799 2.339564 0.074324 12151.082458

In [15]:
%%px --local

pts_l = [50, 60, 70] # make finer grid
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [113]:
#ar_ancMig_iso_recBot = lbview.map(run_dadi, repeat(p0, 10))

In [16]:
ar_ancMig_iso_recBot = []

for filename in glob("OUT_2D_models/ancMig_iso_recBot_[0-9]*dill"):
    ar_ancMig_iso_recBot.append(dill.load(open(filename)))

In [19]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_iso_recBot]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[19]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_2_0 nu2_2_0 Tb_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt nu1_2_opt nu2_2_opt Tb_opt -logL
33 1.010181 6.871487 1.451149 0.050146 0.238884 0.004436 2.148663 0.716418 0.015629 1.538862 4.860571 2.444424 0.067884 0.439022 0.040598 2.830209 0.011514 0.000195 11947.654312
29 1.174790 3.077788 1.314447 0.090998 0.469526 0.006879 1.555901 1.326200 0.034945 1.500366 4.741847 2.350060 0.069168 0.450128 0.040048 7.244377 0.000418 0.000007 11947.842682
19 1.652793 2.712218 3.134971 0.048063 0.645563 0.003879 1.117427 0.998939 0.017888 1.507908 4.761684 2.367556 0.068989 0.443854 0.038947 8.242256 0.001608 0.000027 11947.854569
32 3.025050 3.947268 1.808350 0.086289 0.360479 0.008389 3.088444 1.292577 0.016588 1.492642 4.750965 2.345632 0.069062 0.451831 0.039004 3.473315 0.032490 0.000560 11947.900448
0 2.848358 9.970694 3.086090 0.034983 0.590775 0.008276 1.385586 0.579818 0.039132 1.480960 4.710487 2.316689 0.069236 0.451888 0.037361 6.356166 0.029659 0.000509 11947.909203
6 1.197045 3.658880 3.155129 0.101344 0.382683 0.002398 1.067422 0.650483 0.021091 1.480023 4.691910 2.299299 0.069615 0.451695 0.037516 6.634051 0.004518 0.000077 11947.973480
1 1.380803 2.604036 4.659415 0.031791 0.587693 0.002786 1.647847 0.899368 0.028232 1.488937 4.726817 2.330127 0.069267 0.450635 0.038994 3.355878 0.008195 0.000140 11947.987724
28 1.330573 4.125379 1.876886 0.048303 0.845604 0.006949 1.361370 1.026490 0.016763 1.477900 4.680249 2.295962 0.069788 0.452330 0.037483 7.280196 0.004184 0.000071 11948.027161
18 0.991429 6.766906 2.718314 0.054957 0.358637 0.002692 2.070727 0.599475 0.031622 1.467280 4.681543 2.278421 0.069681 0.455036 0.037456 2.341921 0.006619 0.000114 11948.217748
13 1.757454 8.821764 4.258312 0.051786 0.358177 0.004093 1.411883 1.573098 0.025065 1.437682 4.844707 2.364543 0.066416 0.461319 0.000149 1.776081 1.437114 0.039076 11975.171317

In [119]:
%%px --local

dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm

In [120]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,9:18])
popt


Out[120]:
array([  1.57295944e+00,   5.05678367e+00,   2.58794797e+00,
         5.81598264e-02,   4.28942616e-01,   4.38571001e-03,
         1.66703363e+00,   1.03491644e+00,   2.50916622e-02])

In [121]:
p0 = popt

In [122]:
#ar_ancMig_iso_recBot = lbview.map(run_dadi, repeat(p0, 10))

In [123]:
ar_ancMig_iso_recBot = []

for filename in glob("OUT_2D_models/ancMig_iso_recBot_[0-9]*dill"):
    ar_ancMig_iso_recBot.append(dill.load(open(filename)))

In [124]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_iso_recBot]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[124]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_2_0 nu2_2_0 Tb_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt nu1_2_opt nu2_2_opt Tb_opt -logL
33 1.010181 6.871487 1.451149 0.050146 0.238884 0.004436 2.148663 0.716418 0.015629 1.538862 4.860571 2.444424 0.067884 0.439022 0.040598 2.830209 0.011514 0.000195 11947.654312
29 1.174790 3.077788 1.314447 0.090998 0.469526 0.006879 1.555901 1.326200 0.034945 1.500366 4.741847 2.350060 0.069168 0.450128 0.040048 7.244377 0.000418 0.000007 11947.842682
19 1.652793 2.712218 3.134971 0.048063 0.645563 0.003879 1.117427 0.998939 0.017888 1.507908 4.761684 2.367556 0.068989 0.443854 0.038947 8.242256 0.001608 0.000027 11947.854569
32 3.025050 3.947268 1.808350 0.086289 0.360479 0.008389 3.088444 1.292577 0.016588 1.492642 4.750965 2.345632 0.069062 0.451831 0.039004 3.473315 0.032490 0.000560 11947.900448
0 2.848358 9.970694 3.086090 0.034983 0.590775 0.008276 1.385586 0.579818 0.039132 1.480960 4.710487 2.316689 0.069236 0.451888 0.037361 6.356166 0.029659 0.000509 11947.909203
6 1.197045 3.658880 3.155129 0.101344 0.382683 0.002398 1.067422 0.650483 0.021091 1.480023 4.691910 2.299299 0.069615 0.451695 0.037516 6.634051 0.004518 0.000077 11947.973480
1 1.380803 2.604036 4.659415 0.031791 0.587693 0.002786 1.647847 0.899368 0.028232 1.488937 4.726817 2.330127 0.069267 0.450635 0.038994 3.355878 0.008195 0.000140 11947.987724
28 1.330573 4.125379 1.876886 0.048303 0.845604 0.006949 1.361370 1.026490 0.016763 1.477900 4.680249 2.295962 0.069788 0.452330 0.037483 7.280196 0.004184 0.000071 11948.027161
18 0.991429 6.766906 2.718314 0.054957 0.358637 0.002692 2.070727 0.599475 0.031622 1.467280 4.681543 2.278421 0.069681 0.455036 0.037456 2.341921 0.006619 0.000114 11948.217748
13 1.757454 8.821764 4.258312 0.051786 0.358177 0.004093 1.411883 1.573098 0.025065 1.437682 4.844707 2.364543 0.066416 0.461319 0.000149 1.776081 1.437114 0.039076 11975.171317

This looks like good convergence, but note that the second population size change has quite divergent parameter value combinations with almost equal likelihood. So its time and magnitude cannot be inferred accurately.

LRT

This isolation then bottleneck model can be turned into the the previous ancMig recBotIso model by setting $T_i$ to zero.


In [21]:
ll_s = -11975
ll_c = -11947

D = 2*(ll_c - ll_s)
D


Out[21]:
56

In [22]:
# calculate p-value for Chi-square dist. with 0 and 1 d.o.f.
# the weights specify a weighted sum of chi^2 distributions with 0 and 1 d.o.f
# this is because in the simple model, Ti is fixed at the boundary of the parameter space
p = dadi.Godambe.sum_chi2_ppf(D, weights=(0.5, 0.5))
p


Out[22]:
3.6193270602780103e-14

A $T_i$ greater than zero seems to be highly significant. I doubt that linkage in the data can compromise this result. This means that allowing for the cessation of gene flow to happen earlier than the 2nd population size change significantly improves the fit to the data.

Interpretation


In [23]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[3,9:18])
popt


Out[23]:
array([  1.49264201e+00,   4.75096505e+00,   2.34563160e+00,
         6.90619033e-02,   4.51831318e-01,   3.90044906e-02,
         3.47331469e+00,   3.24903426e-02,   5.59537375e-04])

Note, above I have not chosen the most likely parameter combination as popt! Instead, I have chosen the 4th best combination which also has the highest (and therefore most plausible) value for $T_b$ (the time of the recent pop size change). This parameter combination has practically identical likelihood to the most likely parameter combination in the above table.


In [24]:
model = func_ex(popt, ns, pts_l)

ll = dadi.Inference.ll_multinom(model, sfs2d)
ll


Out[24]:
-11947.900447574564

In [25]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
print "The optimal value of theta per site for the ancestral population is {0:.5f}.".format(theta/L)


The optimal value of theta per site for the ancestral population is 0.00299.

In [26]:
mu = 3e-9
L = sfs2d.data.sum() # this sums over all entries in the spectrum, including masked ones, i. e. also contains invariable sites
print "The total sequence length for the 2D spectrum is {0:,}.".format(int(L))
N_ref = theta/L/mu/4
print "The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:\n {0:,}.".format(int(N_ref))


The total sequence length for the 2D spectrum is 1,130,775.
The effective size of the ancestral population of ery and par (in number of diploid individuals) implied by this theta is:
 249,407.

In [27]:
nu1,nu2,Tc,m1,m2,Ti,nu1_2,nu2_2,Tb = popt

In [34]:
print "The ancestral population split apart {0:,} generations ago.".format(int((Tc+Ti+Tb)*2*N_ref)),
print "Immediately after the split the ERY population changed to a size of {0:,} and the PAR population to {1:,}.".format(int(nu1*N_ref), int(nu2*N_ref)),
print "Since the split of the ancestral population, PAR received 1 individual from ERY every {0:.2f} generations,".format(1.0/(m1/2*nu2)),
print "while ERY received 1 PAR individual every {0:.2f} generations.".format(1.0/(m2/2*nu1)),
print "Put another way: The PAR population contained a constant proportion of {0:.2e} of new immigrant alleles each generation".format(m1/2/N_ref/nu2),
print "and the ERY population contained a constant proportion of {0:.2e} of new immigrant alleles each generation.".format(m2/2/N_ref/nu1),
print "ERY and PAR remained in contact for {0:,} generations.".format(int(Tc*2*N_ref)),
print "{0:,} generations ago gene flow between ERY and PAR had ceased.".format(int(Ti*2*N_ref)),
print "{0:,} generations ago, ERY and PAR underwent a second stepwise simultaneous population size change.".format(int(Tb*2*N_ref)),
print "ERY changed to a size of {0:,} individuals and PAR to a size of {1:,} individuals.".format(int(nu1_2*N_ref), int(nu2_2*N_ref))


The ancestral population split apart 1,189,769 generations ago. Immediately after the split the ERY population changed to a size of 372,275 and the PAR population to 1,184,924. Since the split of the ancestral population, PAR received 1 individual from ERY every 6.10 generations, while ERY received 1 PAR individual every 2.97 generations. Put another way: The PAR population contained a constant proportion of 2.91e-08 of new immigrant alleles each generation and the ERY population contained a constant proportion of 6.07e-07 of new immigrant alleles each generation. ERY and PAR remained in contact for 1,170,034 generations. 19,456 generations ago gene flow between ERY and PAR had ceased. 279 generations ago, ERY and PAR underwent a second stepwise simultaneous population size change. ERY changed to a size of 866,269 individuals and PAR to a size of 8,103 individuals.

It is remarkable that this model gives a time estimate for the cessation of gene flow almost identical to the previous model, ancMig recBotIso that enforced a coincident pop size change, and is also a very similar estimate to the one given by the ancient migration model (26,970). In contrast to the previous model, this model allows the second pop size change to happen at any time (Tb) after the cessation of gene flow (Ti+Tb). Similar to the recent bottleneck model, this model estimates a very recent and very severe population size change for PAR, but in contrast to the recent bottleneck model it infers a substantial population size increase for ERY. I wonder whether this is just an artifact in the data (allele-drop-out?, filtering?) or indicating a real event. For instance, if the environment on the north side of the Pyrenees experienced a drastic change during the last few hundred years. Maybe a regrowth of forest has led to increasingly small and isolated populations of PAR? The question would then be why this is not mirrored on the Spanish side.

Residuals


In [35]:
dadi.Plotting.plot_2d_comp_multinom(data=sfs2d, model=model, vmin=1)


compare model spectra

I would like to compare the best-fit spectrum of the ancMig recBotIso model with the one of this isolation then bottleneck model.


In [37]:
func_ex = dadi.Numerics.make_extrap_log_func(ancMig_recBotIso)

popt_ancMig_recBotIso = [1.44238205, 4.86198073, 2.3766401, 0.06626632, 0.46005763, 1.78231791, 1.4500263, 0.03946682]

model_ancMig_recBotIso = func_ex(popt_ancMig_recBotIso, ns, pts_l)

model_ancMig_recBotIso = dadi.Inference.optimally_scaled_sfs(model_ancMig_recBotIso, sfs2d)

model = dadi.Inference.optimally_scaled_sfs(model, sfs2d)

In [72]:
dadi.Plotting.plot_2d_comp_multinom(data=model.fold() , model=model_ancMig_recBotIso.fold(), \
                                    vmin=1, title=['Iso then Bot', 'Bot+Iso'], pop_ids=['ery', 'par'])


As can be seen in the residual plot, the main difference between these two models is in how their predicted counts in the [0, 1] and [1, 0] SNP classes. Enforcing the pop size change to be as old as the cessation of gene flow leads to higher predictions for those count classes than allowing the pop size changes to be very recent.

bottleneck then isolation


In [40]:
def ancMig_bot_iso(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Tb,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by a potential second size change at time [Tb+Ti]. The split coincides 
    with the first potential pop size change. At time Ti there is a cessation of gene flow.
    Note, that this model does not allow the cessation of gene flow to occur earlier than the second
    pop size change.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split till 2nd pop size change (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    nu1_2: pop size ratio of pop 1 after second size change
    nu2_2: pop size ratio of pop 2 after second size change
    Tb: Time from second pop size change till cessation of gene flow
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Tb+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Tb,Ti = params
    
    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
    
    # potential second pop size change
    phi = dadi.Integration.two_pops(phi, xx, Tb, nu1_2, nu2_2, m12=m2, m21=m1)
    
    # divergence without gene flow
    phi = dadi.Integration.two_pops(phi, xx, Ti, nu1_2, nu2_2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [41]:
cl[:].push(dict(ancMig_bot_iso=ancMig_bot_iso))


Out[41]:
<AsyncResult: _push>

In [42]:
%%px --local

func_ex = dadi.Numerics.make_extrap_log_func(ancMig_bot_iso)

In [43]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/ancMig_bot_iso" # set file name stub for opt. result files
fixed_params = None

In [44]:
%%px --local

# set lower and upper bounds to nu1,nu2,Tc,m1,m2,nu1_2,nu2_2,Tb,Ti
upper_bound = [1e4, 1e4, 6, 10, 10, 1e4, 1e4, 6, 6] 
lower_bound = [1e-4, 1e-4, 0, 0, 0, 1e-4, 1e-4, 0, 0]

In [45]:
popt = df.sort_values(by='-logL', ascending=True).iloc[3,9:18]
popt


Out[45]:
nu1_opt      1.492642
nu2_opt      4.750965
Tc_opt       2.345632
m1_opt       0.069062
m2_opt       0.451831
Ti_opt       0.039004
nu1_2_opt    3.473315
nu2_2_opt    0.032490
Tb_opt       0.000560
Name: 32, dtype: float64

In [46]:
p0 = [1.492, 4.75, 2.34, 0.069, 0.451, 3.47, 0.032, 0.00056, 0.039]

In [47]:
#ar_ancMig_bot_iso = lbview.map(run_dadi, repeat(p0, 10))

In [48]:
ar_ancMig_bot_iso.done()


Out[48]:
True

In [49]:
ar_ancMig_bot_iso = []

for filename in glob("OUT_2D_models/ancMig_bot_iso_[0-9]*dill"):
    ar_ancMig_bot_iso.append(dill.load(open(filename)))

In [50]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_bot_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[50]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tb_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tb_opt Ti_opt -logL
6 3.992638 16.580132 1.968371 0.034767 0.293177 8.229353 0.009383 0.001171 0.010291 1.739552 6.138673 3.499437 0.033616 0.333892 13.097829 0.067352 8.060764e-04 0.000667 12208.620090
1 4.490100 17.548368 1.434258 0.136278 0.730367 9.269520 0.053584 0.002044 0.104997 0.998185 3.988296 1.818443 0.052589 0.588258 5.170566 1.195144 4.946357e-03 0.023328 12216.796226
0 3.117908 4.291532 1.012956 0.058029 0.296455 1.300014 0.011950 0.001898 0.018318 1.003485 3.156207 1.012827 0.035497 0.528170 1.364627 0.135799 4.785430e-04 0.001538 12293.836082
4 0.565286 12.832265 3.803873 0.038274 0.618585 3.254570 0.025830 0.000954 0.016067 1.478947 9.169628 3.129882 0.013627 0.548139 5.064150 0.209123 1.146958e-05 0.009801 12684.377792
7 0.592390 13.108151 3.650535 0.021228 0.248184 10.787936 0.096733 0.001403 0.150427 0.906839 3.519206 1.148215 0.000528 0.494759 8.627656 2.480603 1.054229e-07 0.021606 13244.347187
5 3.633837 2.611521 3.016315 0.138830 1.659438 2.764280 0.016503 0.000292 0.035549 0.659164 4.938164 2.142841 0.034088 1.223119 3.893752 0.259714 2.045955e-04 0.012191 13339.595162
8 1.553918 15.161563 5.940000 0.043071 1.234312 5.313955 0.075166 0.000630 0.011950 0.756369 15.327582 2.381379 0.028035 1.014797 15.982735 0.023950 5.900675e-06 0.001830 13411.344098
9 2.900588 6.741468 1.055446 0.031880 1.447720 6.631487 0.012548 0.000305 0.076685 0.910007 19.125533 1.122281 0.001311 0.980845 1.518194 0.815448 5.223957e-06 0.079815 14038.341206
3 3.027145 15.524680 1.006779 0.024211 0.365169 3.357592 0.011978 0.000178 0.148593 1.130338 41.329236 1.006460 0.004179 0.538059 5.310883 0.360080 2.662554e-06 0.039990 15741.673022
2 2.799652 16.548903 0.945624 0.096742 0.428007 5.385884 0.089241 0.000252 0.132612 1.453143 31.552844 0.921352 0.095140 0.328245 5.999401 0.846284 6.347370e-02 0.031886 16666.809369

In [51]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,9:18])
popt


Out[51]:
array([  1.73955223e+00,   6.13867301e+00,   3.49943693e+00,
         3.36155580e-02,   3.33891722e-01,   1.30978285e+01,
         6.73522783e-02,   8.06076425e-04,   6.67002729e-04])

In [52]:
p0 = popt

In [53]:
#ar_ancMig_bot_iso = lbview.map(run_dadi, repeat(p0, 10))

In [54]:
ar_ancMig_bot_iso = []

for filename in glob("OUT_2D_models/ancMig_bot_iso_[0-9]*dill"):
    ar_ancMig_bot_iso.append(dill.load(open(filename)))

In [55]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_bot_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[55]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tb_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tb_opt Ti_opt -logL
18 1.011137 7.421301 4.844279 0.076171 0.491335 3.786246 0.037808 0.002860 0.000283 1.013686 3.962025 1.343121 0.060258 0.559644 5.218072 0.028027 0.000619 0.000063 12114.998820
2 4.318705 6.233759 5.940000 0.036754 0.136401 48.883685 0.044956 0.000249 0.000639 2.645746 8.524641 5.276135 0.043467 0.218450 39.121254 0.023183 0.000169 0.000252 12137.587685
11 3.389609 11.583837 5.940000 0.027300 0.113231 3.278210 0.155272 0.000763 0.000185 2.922675 9.253594 5.948061 0.030848 0.208555 4.353991 0.048560 0.000686 0.000017 12163.692128
10 1.525264 4.275988 5.932601 0.014497 0.395024 23.318107 0.101196 0.000358 0.000261 1.655848 5.719625 2.849226 0.046861 0.401078 25.230058 0.059324 0.001131 0.000161 12183.148659
14 3.992638 16.580132 1.968371 0.034767 0.293177 8.229353 0.009383 0.001171 0.010291 1.739552 6.138673 3.499437 0.033616 0.333892 13.097829 0.067352 0.000806 0.000667 12208.620090
1 4.490100 17.548368 1.434258 0.136278 0.730367 9.269520 0.053584 0.002044 0.104997 0.998185 3.988296 1.818443 0.052589 0.588258 5.170566 1.195144 0.004946 0.023328 12216.796226
6 4.588051 15.782673 1.992290 0.029523 0.143655 31.108497 0.034747 0.001193 0.000299 1.553961 3.813726 2.188847 0.112592 0.332265 126.300845 0.046802 0.000301 0.000007 12235.520662
0 3.117908 4.291532 1.012956 0.058029 0.296455 1.300014 0.011950 0.001898 0.018318 1.003485 3.156207 1.012827 0.035497 0.528170 1.364627 0.135799 0.000479 0.001538 12293.836082
7 6.923506 22.734209 1.457735 0.026721 0.214288 10.953724 0.017444 0.000431 0.000252 1.013322 5.677485 1.580507 0.020669 0.648621 93.578851 0.006438 0.000234 0.000001 12316.156093
8 3.722155 2.611846 1.453132 0.008488 0.564568 4.286726 0.057458 0.000990 0.000483 1.310508 3.503410 1.523946 0.037619 0.453653 3.112888 0.091394 0.000010 0.000167 12490.620627

In [58]:
%%px --local

pts_l = [50, 60, 70] # make finer grid
dadi_opt_func = dadi.Inference.optimize_log # uses BFGS algorithm
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 100 iterations

In [59]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,9:18])
popt


Out[59]:
array([  1.01368580e+00,   3.96202485e+00,   1.34312096e+00,
         6.02583647e-02,   5.59643999e-01,   5.21807153e+00,
         2.80274915e-02,   6.18758817e-04,   6.26938512e-05])

In [60]:
p0 = popt

In [61]:
#ar_ancMig_bot_iso = lbview.map(run_dadi, repeat(p0, 10))

In [62]:
ar_ancMig_bot_iso.done()


Out[62]:
True

In [63]:
ar_ancMig_bot_iso = []

for filename in glob("OUT_2D_models/ancMig_bot_iso_[0-9]*dill"):
    ar_ancMig_bot_iso.append(dill.load(open(filename)))

In [64]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_ancMig_bot_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0', 'nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'nu1_2_0', 'nu2_2_0', 'Tb_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'nu1_2_opt', 'nu2_2_opt', 'Tb_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[64]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 nu1_2_0 nu2_2_0 Tb_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt nu1_2_opt nu2_2_opt Tb_opt Ti_opt -logL
4 0.981378 4.235281 1.872058 0.037741 0.378408 2.810737 0.028693 0.000602 0.000036 1.380070 4.216221 1.863204 0.078357 0.417768 0.044256 0.015857 0.000211 9.028683e-05 12033.497939
18 0.863875 4.364906 0.684780 0.031314 0.588067 9.216581 0.014087 0.000657 0.000120 1.398151 4.244103 1.884517 0.077923 0.415363 0.001958 0.000721 0.000012 1.390495e-06 12033.646463
7 0.539943 3.366886 1.029171 0.041014 0.858001 4.053974 0.038166 0.000670 0.000073 1.373566 4.214554 1.855727 0.076995 0.421275 0.005820 0.001953 0.000024 1.251890e-05 12033.967782
21 1.403142 5.807346 0.875361 0.111423 0.599521 4.010069 0.025524 0.000656 0.000034 1.383629 4.209151 1.866081 0.078332 0.417426 0.021735 0.007937 0.000145 3.427138e-06 12033.984551
10 1.011051 3.633752 0.866824 0.087460 0.670587 4.667394 0.027388 0.000320 0.000114 1.466718 4.413130 2.031751 0.075751 0.397210 0.000282 0.000102 0.000001 5.416864e-07 12034.476394
3 0.871144 2.535980 1.189073 0.043073 0.376671 8.515185 0.028001 0.000380 0.000044 1.285330 4.125343 1.776555 0.081232 0.422485 0.000663 0.000134 0.000002 2.100228e-07 12046.137495
20 0.858346 6.426436 1.409656 0.108942 0.594930 9.990062 0.037532 0.000340 0.000034 1.271852 4.183253 1.770747 0.077530 0.434347 1.160892 0.000196 0.000003 3.860484e-07 12063.848397
28 1.011137 7.421301 4.844279 0.076171 0.491335 3.786246 0.037808 0.002860 0.000283 1.013686 3.962025 1.343121 0.060258 0.559644 5.218072 0.028027 0.000619 6.269385e-05 12114.998820
2 4.318705 6.233759 5.940000 0.036754 0.136401 48.883685 0.044956 0.000249 0.000639 2.645746 8.524641 5.276135 0.043467 0.218450 39.121254 0.023183 0.000169 2.522495e-04 12137.587685
16 3.389609 11.583837 5.940000 0.027300 0.113231 3.278210 0.155272 0.000763 0.000185 2.922675 9.253594 5.948061 0.030848 0.208555 4.353991 0.048560 0.000686 1.700745e-05 12163.692128

This seems to have converged on essentially the recent bottleneck best-fit model found previously, which had a best -logL of 12,034. Note that this model can be turned into the recent bottleneck model by setting $T_i$ to zero, i. e. no period without gene flow. This bottleneck then isolation model is therefore not an improvement towards the recent bottleneck model and this indicates that gene flow must have ceased before the recent change in population size.


In [66]:
popt = df.sort_values(by='-logL', ascending=True).iloc[0,9:18]
popt


Out[66]:
nu1_opt      1.380070
nu2_opt      4.216221
Tc_opt       1.863204
m1_opt       0.078357
m2_opt       0.417768
nu1_2_opt    0.044256
nu2_2_opt    0.015857
Tb_opt       0.000211
Ti_opt       0.000090
Name: 4, dtype: float64

Let's compare with the best-fit parameter values of the recent bottleneck model.


In [68]:
ar_split_asym_mig_2epoch = []

for filename in glob("OUT_2D_models/split_asym_mig_2epoch*dill"):
    ar_split_asym_mig_2epoch.append(dill.load(open(filename)))

In [70]:
l = 2*8+1

returned = [flatten(out)[:l] for out in ar_split_asym_mig_2epoch]

df_rb = pd.DataFrame(data=returned, \
                  columns=['ery_1_0','par_1_0','T1_0','ery_2_0','par_2_0','T2_0','m1_0','m2_0', 'ery_1_opt','par_1_opt','T1_opt','ery_2_opt','par_2_opt','T2_opt','m1_opt','m2_opt','-logL'])

In [71]:
popt_rb = df_rb.sort_values(by='-logL', ascending=True).head(10).iloc[0,8:16]
popt_rb


Out[71]:
ery_1_opt    1.389305
par_1_opt    4.235224
T1_opt       1.889727
ery_2_opt    0.003110
par_2_opt    0.001079
T2_opt       0.000020
m1_opt       0.077585
m2_opt       0.416367
Name: 39, dtype: float64

The main difference is that the recent bottleneck model estimated a 10 times more severe bottleneck for both populations at a time 10 times more recent.


In [ ]:


In [ ]:


In [ ]:

recent restart of gene flow

The two populations are relatively close to the hybrid zone centre at the Col de la Quillane in the Pyrenees. The clines for some characters have been shown to be many kilometers wide. It can therefore be assumed that there is some recent gene flow between the two populations. Can this gene flow be detected from this spectrum?


In [44]:
def split_mig_iso_mig(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,Ti,Tsc)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by a period of complete isolation Ti which is followed by a restart of
    migration until present. Migration rates in the two epochs are assumed to be equal (and constant).

    nu1: Size of population 1 after split.
    nu2: Size of population 2 after split.
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    Tsc: Time of gene flow after period of isolation until present
    
    The split lies Tc+Ti+Tsc * 2Na generations in the past.
    
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,Ti,Tsc = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
    
    # divergence without gene flow
    phi = dadi.Integration.two_pops(phi, xx, Ti, nu1, nu2, m12=0, m21=0)
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tsc, nu1, nu2, m12=m2, m21=m1)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [46]:
cl[:].push(dict(split_mig_iso_mig=split_mig_iso_mig))


Out[46]:
<AsyncResult: _push>

In [47]:
%%px --local

func = split_mig_iso_mig

func_ex = dadi.Numerics.make_extrap_log_func(func)

In [48]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
# setting the smallest grid size slightly larger than the largest population sample size (36)
pts_l = [40, 50, 60]
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/split_mig_iso_mig" # set file name stub for opt. result files
fixed_params = None

In [59]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2, Ti, Tsc
upper_bound = [1e4, 1e4, 6, 10, 10, 6, 6] 
lower_bound = [1e-4, 1e-4, 0, 0, 0, 0, 0]

In [60]:
# using the best fit parameters from the ancient migration model and adding an initial Tsc of 0.05
p0 = [2.76935944,  6.77011784,  5.19634719,  0.04983223,  0.24913079, 0.09420312, 0.05]

In [53]:
#ar_mig_iso_mig = lbview.map(run_dadi, repeat(p0, 10))

In [61]:
% ll OUT_2D_models/split_mig_iso_mig*dill


-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_11.0126_22.7709_5.9400_0.0437_0.3119_0.1956_0.1092.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_1.2054_5.0738_5.9400_0.0214_0.3336_0.1019_0.0275.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_1.7164_3.8718_7.9200_0.0218_0.2159_0.0908_0.0176.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_2.2342_3.2395_5.9400_0.0919_0.2080_0.1652_0.0786.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:50 OUT_2D_models/split_mig_iso_mig_2.3335_15.2576_1.8862_0.1763_0.0680_0.3384_0.1045.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.6152_14.5322_6.1771_0.0384_0.1613_0.1163_0.0199.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:00 OUT_2D_models/split_mig_iso_mig_2.6470_3.9437_3.9926_0.0634_0.3447_0.2211_0.0100.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.8052_5.0529_4.0141_0.0299_0.2311_0.2026_0.0141.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.8618_6.1742_6.2063_0.0231_0.1944_0.1075_0.0196.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_3.3672_5.8737_5.1818_0.0212_0.1317_0.1343_0.0126.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_3.5815_4.3860_7.9200_0.0402_0.1995_0.0909_0.0063.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_4.4204_17.3910_3.3502_0.1423_0.3064_0.2099_0.0165.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_4.6896_4.9306_7.9200_0.0368_0.2436_0.1309_0.0196.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:02 OUT_2D_models/split_mig_iso_mig_4.8485_7.8443_6.5328_0.0656_0.2918_0.1493_0.0061.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_5.1750_4.8629_4.8988_0.0283_0.4760_0.0538_0.0625.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:02 OUT_2D_models/split_mig_iso_mig_5.9582_10.0711_7.9200_0.0709_0.1852_0.1687_0.0182.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_6.7436_2.1538_5.9400_0.0161_0.1946_0.0559_0.1092.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_6.9803_23.0350_5.9400_0.0180_0.4691_0.0579_0.0198.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_8.0423_3.9836_4.7871_0.0895_0.1220_0.1626_0.0149.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_8.3624_7.1749_4.3187_0.0305_0.3143_0.0298_0.0465.dill

In [62]:
ar_mig_iso_mig = []

for filename in glob("OUT_2D_models/split_mig_iso_mig*dill"):
    ar_mig_iso_mig.append(dill.load(open(filename)))

In [63]:
import pandas as pd

l = 2*len(p0)+1

# show all parameter combinations
returned = [flatten(out)[:l] for out in ar_mig_iso_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'Tsc_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', 'Tsc_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[63]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 Tsc_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt Tsc_opt -logL
0 3.367161 5.873662 5.181757 0.021243 0.131704 0.134310 0.012568 3.188610 7.794114 6.164844 0.043640 0.218696 0.114964 0.004645 12155.538032
11 4.848455 7.844252 6.532817 0.065627 0.291770 0.149297 0.006102 3.120969 7.612606 6.000069 0.045012 0.222445 0.112705 0.004608 12155.590169
4 3.581468 4.386019 7.920000 0.040211 0.199524 0.090897 0.006340 3.105754 7.584235 5.979666 0.044949 0.224270 0.111817 0.004517 12155.591385
10 2.861802 6.174207 6.206258 0.023059 0.194373 0.107483 0.019635 3.037227 7.420328 5.807496 0.045940 0.228788 0.109332 0.004451 12155.596732
17 2.615168 14.532166 6.177148 0.038351 0.161283 0.116289 0.019920 3.021104 7.379894 5.769061 0.046165 0.229732 0.108768 0.005524 12155.718311
19 4.689580 4.930597 7.920000 0.036797 0.243583 0.130929 0.019558 3.746330 9.107319 7.386824 0.039007 0.187509 0.149092 0.012786 12156.381833
9 2.646968 3.943732 3.992614 0.063371 0.344669 0.221130 0.010017 2.698943 6.615402 5.040117 0.050977 0.258592 0.094888 0.002855 12156.558507
15 5.958216 10.071090 7.920000 0.070913 0.185235 0.168712 0.018208 3.880071 9.474192 7.726884 0.036690 0.181476 0.155206 0.016580 12156.884063
1 2.805249 5.052889 4.014091 0.029912 0.231140 0.202572 0.014063 2.486040 6.086709 4.536669 0.055292 0.278498 0.089498 0.005871 12156.937086
12 8.362353 7.174874 4.318747 0.030505 0.314294 0.029781 0.046488 3.054782 7.596574 5.940901 0.042105 0.238595 0.125940 0.010319 12159.837573

There is no good convergence yet.


In [59]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,7:14])
popt


Out[59]:
array([ 3.05478165,  7.59657376,  5.9409006 ,  0.04210524,  0.23859495,
        0.12593952,  0.01031921])

In [56]:
%%px --local

pts_l = [60, 70, 80]
fold = 1

In [60]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2, Ti, Tsc
upper_bound = [1e4, 1e4, 8, 10, 10, 6, 6] 
lower_bound = [1e-4, 1e-4, 0, 0, 0, 0, 0]

In [61]:
p0 = popt

#ar_mig_iso_mig = lbview.map(run_dadi, repeat(p0, 10))

In [51]:
ar_mig_iso_mig = []

for filename in glob("OUT_2D_models/split_mig_iso_mig_[0-9]*dill"):
    ar_mig_iso_mig.append(dill.load(open(filename)))

In [53]:
% ll OUT_2D_models/split_mig_iso_mig_[0-9]*dill


-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_11.0126_22.7709_5.9400_0.0437_0.3119_0.1956_0.1092.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_1.2054_5.0738_5.9400_0.0214_0.3336_0.1019_0.0275.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_1.7164_3.8718_7.9200_0.0218_0.2159_0.0908_0.0176.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_2.2342_3.2395_5.9400_0.0919_0.2080_0.1652_0.0786.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:50 OUT_2D_models/split_mig_iso_mig_2.3335_15.2576_1.8862_0.1763_0.0680_0.3384_0.1045.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.6152_14.5322_6.1771_0.0384_0.1613_0.1163_0.0199.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:00 OUT_2D_models/split_mig_iso_mig_2.6470_3.9437_3.9926_0.0634_0.3447_0.2211_0.0100.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.8052_5.0529_4.0141_0.0299_0.2311_0.2026_0.0141.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_2.8618_6.1742_6.2063_0.0231_0.1944_0.1075_0.0196.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_3.3672_5.8737_5.1818_0.0212_0.1317_0.1343_0.0126.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_3.5815_4.3860_7.9200_0.0402_0.1995_0.0909_0.0063.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_4.4204_17.3910_3.3502_0.1423_0.3064_0.2099_0.0165.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:01 OUT_2D_models/split_mig_iso_mig_4.6896_4.9306_7.9200_0.0368_0.2436_0.1309_0.0196.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:02 OUT_2D_models/split_mig_iso_mig_4.8485_7.8443_6.5328_0.0656_0.2918_0.1493_0.0061.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_5.1750_4.8629_4.8988_0.0283_0.4760_0.0538_0.0625.dill
-rw-rw-r-- 1 claudius 364 Jun  3 19:02 OUT_2D_models/split_mig_iso_mig_5.9582_10.0711_7.9200_0.0709_0.1852_0.1687_0.0182.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_6.7436_2.1538_5.9400_0.0161_0.1946_0.0559_0.1092.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_6.9803_23.0350_5.9400_0.0180_0.4691_0.0579_0.0198.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:51 OUT_2D_models/split_mig_iso_mig_8.0423_3.9836_4.7871_0.0895_0.1220_0.1626_0.0149.dill
-rw-rw-r-- 1 claudius 364 Jun  3 15:52 OUT_2D_models/split_mig_iso_mig_8.3624_7.1749_4.3187_0.0305_0.3143_0.0298_0.0465.dill

In [54]:
import pandas as pd

l = 2*7+1

# show all parameter combinations
returned = [flatten(out)[:l] for out in ar_mig_iso_mig]

df = pd.DataFrame(data=returned, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'Tsc_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', 'Tsc_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[54]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 Tsc_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt Tsc_opt -logL
0 3.367161 5.873662 5.181757 0.021243 0.131704 0.134310 0.012568 3.188610 7.794114 6.164844 0.043640 0.218696 0.114964 0.004645 12155.538032
11 4.848455 7.844252 6.532817 0.065627 0.291770 0.149297 0.006102 3.120969 7.612606 6.000069 0.045012 0.222445 0.112705 0.004608 12155.590169
4 3.581468 4.386019 7.920000 0.040211 0.199524 0.090897 0.006340 3.105754 7.584235 5.979666 0.044949 0.224270 0.111817 0.004517 12155.591385
10 2.861802 6.174207 6.206258 0.023059 0.194373 0.107483 0.019635 3.037227 7.420328 5.807496 0.045940 0.228788 0.109332 0.004451 12155.596732
17 2.615168 14.532166 6.177148 0.038351 0.161283 0.116289 0.019920 3.021104 7.379894 5.769061 0.046165 0.229732 0.108768 0.005524 12155.718311
19 4.689580 4.930597 7.920000 0.036797 0.243583 0.130929 0.019558 3.746330 9.107319 7.386824 0.039007 0.187509 0.149092 0.012786 12156.381833
9 2.646968 3.943732 3.992614 0.063371 0.344669 0.221130 0.010017 2.698943 6.615402 5.040117 0.050977 0.258592 0.094888 0.002855 12156.558507
15 5.958216 10.071090 7.920000 0.070913 0.185235 0.168712 0.018208 3.880071 9.474192 7.726884 0.036690 0.181476 0.155206 0.016580 12156.884063
1 2.805249 5.052889 4.014091 0.029912 0.231140 0.202572 0.014063 2.486040 6.086709 4.536669 0.055292 0.278498 0.089498 0.005871 12156.937086
12 8.362353 7.174874 4.318747 0.030505 0.314294 0.029781 0.046488 3.054782 7.596574 5.940901 0.042105 0.238595 0.125940 0.010319 12159.837573

This looks like good convergence.

The best-fit ancient migration model had a log likelihood of -12156. So only one log likelihood unit worse.


In [64]:
ll_s = 12156.061309
ll_c = 12155.538032
D = 2 * (ll_s - ll_c)
D


Out[64]:
1.0465540000004694

In [65]:
# calculate p-value for Chi-square dist.
# the weights specify a weighted sum of chi^2 distributions with 1 and 2 d.o.f
# this is because Tsc is 0 in the split_asym_mig_iso model and at the boundary of the parameter space
p = dadi.Godambe.sum_chi2_ppf(D, weights=(0.5, 0.5))
p


Out[65]:
0.15315103430195065

A recent restart of gene cannot be detected from this corrected spectrum.

I arrived at the same result with the uncorrected spectrum.


In [55]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,7:14])
popt


Out[55]:
array([  3.18860973e+00,   7.79411365e+00,   6.16484431e+00,
         4.36404020e-02,   2.18696464e-01,   1.14964123e-01,
         4.64467788e-03])

In [56]:
model = func_ex(popt, ns, pts_l)

In [57]:
ll_model = dadi.Inference.ll_multinom(model, sfs2d)
ll_model


Out[57]:
-12155.59866770125

In [63]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
L = sfs2d.data.sum()
mu = 3e-9
N_ref = theta/L/mu/4
print "The ancestral population size implied by the theta is is {0:,}.".format(int(N_ref))
print "The optimal time of secondary contact inferred is {0:,} generations.".format(int(popt[-1]*2*N_ref))


The ancestral population size implied by the theta is is 122,262.
The optimal time of secondary contact inferred is 1,135 generations.

model plotting

I would like to use a module of the programme moments to plot the ancient migration model.


In [1]:
% ll /home/claudius/Downloads/moments/moments


total 52
drwxrwxr-x 2 claudius 4096 Jun 28 14:26 bench/
drwxrwxr-x 3 claudius 4096 Jun 28 14:26 doc/
-rw-rw-r-- 1 claudius 4542 Jun 28 14:26 epydoc.config
drwxrwxr-x 4 claudius 4096 Jun 28 14:26 examples/
-rw-rw-r-- 1 claudius 1503 Jun 28 14:26 LICENSE.txt
-rw-rw-r-- 1 claudius  266 Jun 28 14:26 MANIFEST.in
drwxrwxr-x 2 claudius 4096 Jun 28 14:26 moments/
-rw-rw-r-- 1 claudius 1359 Jun 28 14:26 README.md
-rw-rw-r-- 1 claudius 2316 Jun 28 14:26 setup.py
-rwxrwxr-x 1 claudius  527 Jun 28 14:26 test_demo_1D.py*
-rwxrwxr-x 1 claudius 1078 Jun 28 14:26 test_demo_2D.py*
drwxrwxr-x 3 claudius 4096 Jun 28 14:26 tests/

Installed in /usr/local/lib/python2.7/dist-packages/moments-1.0.0-py2.7.egg


In [1]:
import sys

sys.path


Out[1]:
['',
 '/usr/local/anaconda2/lib/python27.zip',
 '/usr/local/anaconda2/lib/python2.7',
 '/usr/local/anaconda2/lib/python2.7/plat-linux2',
 '/usr/local/anaconda2/lib/python2.7/lib-tk',
 '/usr/local/anaconda2/lib/python2.7/lib-old',
 '/usr/local/anaconda2/lib/python2.7/lib-dynload',
 '/usr/local/anaconda2/lib/python2.7/site-packages/Sphinx-1.3.5-py2.7.egg',
 '/usr/local/anaconda2/lib/python2.7/site-packages/setuptools-20.3-py2.7.egg',
 '/usr/local/anaconda2/lib/python2.7/site-packages',
 '/usr/local/anaconda2/lib/python2.7/site-packages/IPython/extensions',
 '/home/claudius/.ipython']

In [2]:
sys.path.insert(0, '/usr/local/lib/python2.7/dist-packages/moments-1.0.0-py2.7.egg')

In [3]:
sys.path


Out[3]:
['/usr/local/lib/python2.7/dist-packages/moments-1.0.0-py2.7.egg',
 '',
 '/usr/local/anaconda2/lib/python27.zip',
 '/usr/local/anaconda2/lib/python2.7',
 '/usr/local/anaconda2/lib/python2.7/plat-linux2',
 '/usr/local/anaconda2/lib/python2.7/lib-tk',
 '/usr/local/anaconda2/lib/python2.7/lib-old',
 '/usr/local/anaconda2/lib/python2.7/lib-dynload',
 '/usr/local/anaconda2/lib/python2.7/site-packages/Sphinx-1.3.5-py2.7.egg',
 '/usr/local/anaconda2/lib/python2.7/site-packages/setuptools-20.3-py2.7.egg',
 '/usr/local/anaconda2/lib/python2.7/site-packages',
 '/usr/local/anaconda2/lib/python2.7/site-packages/IPython/extensions',
 '/home/claudius/.ipython']

In [4]:
import moments

In [5]:
from glob import glob
import dill
from utility_functions import *
import pandas as pd
# turn on floating point division by default, old behaviour via '//'
from __future__ import division
import numpy as np

In [6]:
%matplotlib inline

import pylab

pylab.rcParams['figure.figsize'] = [12, 10]
pylab.rcParams['font.size'] = 14

In [7]:
# load spectrum modified with Ludovic's correction, p=35

sfs2d = moments.Spectrum.from_file("EryPar_modified.2dsfs")

In [8]:
ns = sfs2d.sample_sizes

In [17]:
def split_asym_mig_iso(params, ns): # note, I have removed the pts argument from the argument list
    """
    params = (nu1,nu2,Tc,m1,m2,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by complete isolation until present.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,Ti = params
    
    # fs for equilibrium ancestral population
    sts = moments.LinearSystem_1D.steady_state_1D(ns[0] + ns[1])
    fs = moments.Spectrum(sts)
    
    # split
    fs = moments.Manips.split_1D_to_2D(fs, ns[0], ns[1]) 
    
    # divergence with potentially asymmetric migration
    fs.integrate([nu1, nu2], Tc, m=np.array([[0, m2], [m1, 0]]))
    
    # divergence without gene flow
    fs.integrate([nu1, nu2], Ti, m=np.array([[0,0], [0,0]]))

    return fs

In [13]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso_[0-9]*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [14]:
l = 2*6+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[14]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
31 3.421903 12.882920 7.920000 0.090910 0.137533 0.104192 3.428735 8.372007 6.719565 0.040627 0.202323 0.118790 12156.061309
29 2.208113 8.325249 5.594802 0.045599 0.494681 0.058458 3.434782 8.387801 6.732856 0.040569 0.202054 0.119088 12156.061960
17 1.638405 7.027064 3.328377 0.093406 0.279576 0.084667 3.365763 8.218499 6.573684 0.041489 0.206208 0.116898 12156.066451
4 3.157867 11.894738 6.832827 0.089239 0.343652 0.125056 3.151832 7.672115 6.074031 0.044790 0.217833 0.107115 12156.250139
2 2.150693 3.752285 7.339520 0.053352 0.182956 0.054508 2.905080 7.099776 5.511164 0.047722 0.237869 0.100633 12156.336374
8 4.507973 4.211151 4.530473 0.038619 0.356819 0.049796 2.838367 6.936326 5.355545 0.048636 0.243390 0.096733 12156.361180
19 3.495075 4.606451 5.940000 0.055664 0.246368 0.105244 2.769359 6.770118 5.196347 0.049832 0.249131 0.094203 12156.467656
16 1.899722 10.672562 4.209415 0.035018 0.158627 0.129019 2.549936 6.237224 4.683557 0.053493 0.269833 0.085552 12156.997320
15 3.193392 3.398205 7.493464 0.054278 0.184446 0.099955 2.625334 6.426852 4.896773 0.051447 0.259054 0.084092 12157.030051
9 1.114068 2.045370 0.628484 0.054210 0.524956 0.061246 2.877591 7.051026 5.515295 0.046193 0.241364 0.095230 12157.097777

In [15]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,6:12])
popt


Out[15]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969])

I am going to use the optimal values from dadi.


In [19]:
# generate ModelPlot object
plot_mod = moments.ModelPlot.generate_model(split_asym_mig_iso, popt, ns)

In [26]:
moments.ModelPlot.plot_model?

In [30]:
# generate the plot for the ancient migration model
# the figure is writen to file, which I am reading in in the following cell using Markdown syntax
moments.ModelPlot.plot_model(plot_mod, save_file='model.png', pop_labels=['ery', 'par'], gen_time=1e-3, \
                             gen_time_units="KY", fig_title='ancient migration', nref=113522, reverse_timeline=True)

The numbers on the right in the upper plot are a bit misleading. It's a 27 (ky ago) and a zero.

refit ancient migration model with moments

I would like to fit the ancient migration with the programme moments.


In [16]:
from ipyparallel import Client

cl = Client()

cl.ids


Out[16]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [31]:
from glob import glob
import dill
from utility_functions import *
import pandas as pd
# turn on floating point division by default, old behaviour via '//'
from __future__ import division
import numpy as np
from itertools import repeat

In [18]:
%matplotlib inline

import pylab

pylab.rcParams['figure.figsize'] = [12, 10]
pylab.rcParams['font.size'] = 14

In [19]:
%%px --local

# run whole cell on all engines a well as in the local IPython session

import sys

sys.path.insert(0, '/usr/local/lib/python2.7/dist-packages/moments-1.0.0-py2.7.egg')

import moments

In [27]:
%%px --local

# load spectrum modified with Ludovic's correction, p=35

sfs2d = moments.Spectrum.from_file("EryPar_modified.2dsfs")

In [20]:
def split_asym_mig_iso(params, ns): # note, I have removed the pts argument from the argument list
    """
    params = (nu1,nu2,Tc,m1,m2,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by complete isolation until present.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,Ti = params
    
    # fs for equilibrium ancestral population
    sts = moments.LinearSystem_1D.steady_state_1D(ns[0] + ns[1])
    fs = moments.Spectrum(sts)
    
    # split
    fs = moments.Manips.split_1D_to_2D(fs, ns[0], ns[1]) 
    
    # divergence with potentially asymmetric migration
    fs.integrate([nu1, nu2], Tc, m=np.array([[0, m2], [m1, 0]]))
    
    # divergence without gene flow
    fs.integrate([nu1, nu2], Ti, m=np.array([[0,0], [0,0]]))

    return fs

In [21]:
cl[:].push(dict(split_asym_mig_iso=split_asym_mig_iso))


Out[21]:
<AsyncResult: _push>

In [24]:
def run_moments(p_init): # for the function to be called with map, it needs to have one input variable
    """
    p_init: initial parameter values to run optimisation from
    """
    if perturb == True:
        p_init = moments.Misc.perturb_params(p_init, fold=fold, 
                                      upper_bound=upper_bound, lower_bound=lower_bound)
        # note upper_bound and lower_bound variables are expected to be in the namespace of each engine
    # run optimisation of paramters
    popt = moments_opt_func(p0=p_init, data=sfs, model_func=func, \
                                   lower_bound=lower_bound, upper_bound=upper_bound, \
                                   verbose=verbose, maxiter=maxiter, full_output=full_output, \
                                    fixed_params=fixed_params)
    # pickle to file
    import dill
    name = outname[:] # make copy of file name stub!
    for p in p_init:
        name += "_%.4f" % (p)
    with open(name + ".dill", "w") as fh:
        dill.dump((p_init, popt), fh)
    
    return p_init, popt

In [12]:
moments.Inference.optimize_log_fmin?

In [28]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
func = split_asym_mig_iso
moments_opt_func = moments.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/moments/split_asym_mig_iso" # set file name stub for opt. result files
fixed_params = None

In [29]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2 and Ti
upper_bound = [1e3, 1e3, 10, 10, 10, 10] # note, I have increased the upper bound for T
lower_bound = [1e-3, 1e-3, 0, 0, 0, 0]

In [30]:
lbview = cl.load_balanced_view()

In [32]:
p0 = [ 3.42873459,  8.372007,  3.71956526,  0.04062737,  0.20232337, 0.11878969]

#ar_split_asym_mig_iso = lbview.map(run_moments, repeat(p0, 10), block=False)

In [33]:
cl.ids


Out[33]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [34]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/moments/split_asym_mig_iso_[0-9]*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [35]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[35]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
1 5.143965 19.143104 9.520667 0.053715 0.419568 0.034413 2.742542 6.642431 9.948508 0.051360 0.247458 0.086862 12160.811964
5 7.165026 20.177452 5.548444 0.121312 0.302577 0.104364 2.441767 5.860119 8.515082 0.060650 0.277564 0.084648 12164.409752
2 1.600990 18.459340 4.469988 0.041030 0.476803 0.118564 1.650751 4.070706 5.182964 0.079643 0.416536 0.056650 12176.686660
9 6.894957 2.294137 3.860232 0.010791 0.062208 0.165299 1.895717 4.372552 5.980478 0.092485 0.373507 0.083472 12202.388673
8 4.457275 16.250931 5.071847 0.021869 0.244206 0.062714 2.860671 7.115273 9.951471 0.044083 0.222968 0.043856 12245.384736
4 2.702576 11.844023 2.245923 0.029843 0.116743 0.047804 1.905676 4.691869 6.709252 0.068754 0.318092 0.021070 12257.268120
7 1.415053 2.681344 9.048971 0.023082 0.132454 0.327612 1.346556 2.591833 3.270891 0.155539 0.494983 0.053517 12405.783130
6 0.886934 11.104852 4.630034 0.160259 0.220424 0.055596 0.911642 2.340216 1.887470 0.113168 0.575141 0.005542 12438.629078
3 1.160285 19.748759 1.426853 0.028805 0.261826 0.088687 1.137496 2.577718 1.761556 0.047878 0.428064 0.007415 12727.907751
0 0.922563 13.292098 1.069339 0.040533 0.522199 0.412974 0.932552 1.991058 1.142346 0.108391 1.136093 0.068680 13390.372541

There is no good convergence yet.


In [36]:
%%px 

maxiter = 300
fold = 1

In [37]:
p0 = [2.742542, 6.642431, 4.948508, 0.051360, 0.247458, 0.086862]

#ar_split_asym_mig_iso = lbview.map(run_moments, repeat(p0, 10), block=False)

In [38]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/moments/split_asym_mig_iso_[0-9]*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [39]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[39]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
9 3.092067 6.981313 9.215293 0.027729 0.185647 0.151634 2.754277 6.723380 9.999903 0.049779 0.248170 0.089072 12160.519471
18 3.789949 7.963842 9.787925 0.036227 0.173589 0.123088 2.681576 6.544014 9.692997 0.050832 0.255735 0.088105 12160.699538
3 5.143965 19.143104 9.520667 0.053715 0.419568 0.034413 2.742542 6.642431 9.948508 0.051360 0.247458 0.086862 12160.811964
17 1.833925 4.884664 7.256460 0.036467 0.304366 0.051318 2.368024 5.749088 8.131598 0.058515 0.283867 0.073685 12161.931585
8 2.003960 5.181130 4.685140 0.082261 0.322327 0.114894 2.255845 5.518813 7.749810 0.058723 0.298415 0.068428 12162.486107
4 4.292721 9.083951 5.104426 0.044112 0.131344 0.065736 2.735293 6.486432 9.513613 0.054604 0.242058 0.084320 12163.942422
11 7.165026 20.177452 5.548444 0.121312 0.302577 0.104364 2.441767 5.860119 8.515082 0.060650 0.277564 0.084648 12164.409752
15 4.658530 8.303453 2.912470 0.062290 0.176824 0.157340 1.935958 4.700660 6.140829 0.070505 0.342182 0.057494 12166.932390
5 4.390565 3.904768 2.946865 0.051872 0.444079 0.080103 1.732538 4.255600 5.352376 0.075458 0.390929 0.054668 12171.192110
2 1.471636 11.896446 3.918631 0.037367 0.151692 0.168031 1.688905 4.068905 4.958221 0.082142 0.383211 0.048005 12175.901246

The best three parameter combinations are all hitting the upper bound on the Tc parameter. Also note, that that these three optimisation runs already started with an extremely high Tc. Dadi's best parameter combination had a much lower Tc and slightly better likelihood (12156).

ancient population growth

I wonder whether I can improve the fit, if I already let the ancestral population increase in size before the split.


In [41]:
def growth_split_asym_mig_iso(params, ns): # note, I have removed the pts argument from the argument list
    """
    params = (nu0,T0,nu1,nu2,Tc,m1,m2,Ti)
    ns = (n1,n2)

    Let ancestral population grow or shrink for time T0, then split into two populations of specifed size, 
    with potentially asymmetric migration for a time Tc followed by complete isolation until present.

    nu0: population size of ancestral population before split (with respect to Na)
    T0: time of increased/reduced pop size before split (in units of 2*Na generations)
    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu0,T0,nu1,nu2,Tc,m1,m2,Ti = params
    
    # fs for equilibrium ancestral population
    sts = moments.LinearSystem_1D.steady_state_1D(ns[0] + ns[1])
    fs = moments.Spectrum(sts)
    
    # ancient population size change
    fs.integrate([nu0], T0)
    
    # split
    fs = moments.Manips.split_1D_to_2D(fs, ns[0], ns[1]) 
    
    # divergence with potentially asymmetric migration
    fs.integrate([nu1, nu2], Tc, m=np.array([[0, m2], [m1, 0]]))
    
    # divergence without gene flow
    fs.integrate([nu1, nu2], Ti, m=np.array([[0,0], [0,0]]))

    return fs

In [42]:
cl[:].push(dict(growth_split_asym_mig_iso=growth_split_asym_mig_iso))


Out[42]:
<AsyncResult: _push>

In [43]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
func = growth_split_asym_mig_iso
moments_opt_func = moments.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/moments/growth_split_asym_mig_iso" # set file name stub for opt. result files
fixed_params = None

In [44]:
%%px --local

# set lower and upper bounds to nu1, nu2, Tc, m1, m2 and Ti
upper_bound = [1e3, 10, 1e3, 1e3, 10, 10, 10, 10] # note, I have increased the upper bound for T
lower_bound = [1e-3, 0, 1e-3, 1e-3, 0, 0, 0, 0]

In [45]:
p0 = [3, 4, 1.754277, 2.723380, 3.999903, 0.049779, 0.248170, 0.089072]

ar_growth_split_asym_mig_iso = lbview.map(run_moments, repeat(p0, 10), block=False)

In [46]:
ar_growth_split_asym_mig_iso.elapsed/60


Out[46]:
1.9724667166666667

In [52]:
ar_growth_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/moments/growth_split_asym_mig_iso_[0-9]*dill"):
    ar_growth_split_asym_mig_iso.append(dill.load(open(filename)))

In [53]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_growth_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu0_0', 'T0_0', 'nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu0_opt', 'T0_opt', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[53]:
nu0_0 T0_0 nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu0_opt T0_opt nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
4 2.058251 9.900000 0.680224 3.903516 1.364045 0.021534 0.742006 0.050622 0.710313 1.297593 0.837850 2.122538 1.844602 0.132113 0.585772 0.000919 12391.459255
1 3.806853 2.843187 5.936808 10.148203 6.806246 0.048295 0.512902 0.049886 4.131340 2.726686 2.293107 6.717482 5.557994 0.013568 0.351055 0.087501 12448.838413
9 4.178405 7.741978 0.901976 1.346842 1.735698 0.069421 0.473350 0.060771 0.757058 5.958705 0.871646 2.091521 2.102980 0.170703 0.477082 0.006363 12469.538783
2 2.366991 3.904858 0.612712 2.317982 9.900000 0.047345 0.752303 0.047076 1.320150 5.272991 1.355765 3.961273 3.550033 0.025692 0.661663 0.074177 12508.334798
7 2.747160 5.491656 1.243995 2.513159 9.900000 0.034762 0.953796 0.085883 1.046172 9.805279 1.061024 3.167141 3.007551 0.018761 0.935849 0.043172 12694.678601
3 4.961362 3.290013 3.625545 8.428179 1.654022 0.042555 0.189154 0.106773 14.190480 2.651157 2.170001 5.642370 2.658661 0.037741 0.412218 0.150906 12774.427367
5 0.910155 3.627511 2.580570 1.726519 2.246907 0.068526 0.099540 0.348798 0.831383 2.258705 1.527766 3.110576 3.388933 0.258809 0.621923 0.145640 12787.985872
0 4.457382 2.421219 0.929911 2.995967 6.115844 0.098779 0.419003 0.060477 1.903615 1.721518 0.972299 2.863530 1.865641 0.016131 0.608191 0.000370 12977.088513
8 2.603524 1.986657 0.475540 1.397244 2.286149 0.037856 0.871550 0.188551 3.430878 1.662641 0.984440 2.344918 1.046344 0.027515 0.852180 0.069202 13800.181858
6 10.226809 6.249580 0.721829 1.579874 5.926033 0.184737 0.135971 0.045658 10.171214 4.810212 0.785546 1.635687 9.970091 0.109330 1.111476 0.008975 17987.619439

Let's refine the search.


In [57]:
popt = np.array(df.sort_values(by='-logL', ascending=True).head(10).iloc[0, 8:16])
popt


Out[57]:
array([  7.10313391e-01,   1.29759325e+00,   8.37849951e-01,
         2.12253763e+00,   1.84460226e+00,   1.32113335e-01,
         5.85771976e-01,   9.19108072e-04])

In [58]:
%%px

fold = 1
maxiter = 300

In [59]:
p0 = popt

#ar_growth_split_asym_mig_iso = lbview.map(run_moments, repeat(p0, 10), block=False)

In [60]:
ar_growth_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/moments/growth_split_asym_mig_iso_[0-9]*dill"):
    ar_growth_split_asym_mig_iso.append(dill.load(open(filename)))

In [61]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_growth_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu0_0', 'T0_0', 'nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu0_opt', 'T0_opt', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[61]:
nu0_0 T0_0 nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu0_opt T0_opt nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
2 0.912404 0.768215 1.034053 3.304700 1.991203 0.227311 1.093951 0.001347 0.571868 0.880991 0.960281 2.478044 3.357635 0.122582 0.788582 0.034436 12260.371675
16 0.677313 0.773004 1.615632 2.980715 1.487722 0.154741 0.624293 0.000852 0.617975 0.720590 0.974239 2.184895 2.123879 0.164107 0.529632 0.015528 12297.432464
8 0.748772 1.418206 0.677974 3.116269 1.564618 0.229804 0.310743 0.000646 0.698819 1.535417 0.916038 2.146073 2.206215 0.137859 0.545602 0.000051 12368.927705
10 2.058251 9.900000 0.680224 3.903516 1.364045 0.021534 0.742006 0.050622 0.710313 1.297593 0.837850 2.122538 1.844602 0.132113 0.585772 0.000919 12391.459255
1 3.806853 2.843187 5.936808 10.148203 6.806246 0.048295 0.512902 0.049886 4.131340 2.726686 2.293107 6.717482 5.557994 0.013568 0.351055 0.087501 12448.838413
18 4.178405 7.741978 0.901976 1.346842 1.735698 0.069421 0.473350 0.060771 0.757058 5.958705 0.871646 2.091521 2.102980 0.170703 0.477082 0.006363 12469.538783
6 2.366991 3.904858 0.612712 2.317982 9.900000 0.047345 0.752303 0.047076 1.320150 5.272991 1.355765 3.961273 3.550033 0.025692 0.661663 0.074177 12508.334798
3 0.963385 0.927244 1.219697 1.422109 2.849989 0.130555 1.102563 0.001600 0.987655 0.941681 0.854273 2.362190 2.278653 0.083182 0.977964 0.040634 12522.357595
4 1.220828 1.188415 0.612607 1.698626 2.518470 0.076944 0.963905 0.001802 1.032747 0.867177 0.884389 2.242914 2.126266 0.097017 0.912513 0.041763 12526.197236
15 0.925614 1.265996 0.702947 2.467898 1.079890 0.171920 0.545543 0.000552 0.760902 0.647022 0.996826 2.058098 1.390676 0.144805 0.503444 0.013275 12566.925857

Still, no good convergence. Also the optimised parameters values seem to be correlated with the starting values.

Let's try to refine the search one more time.


In [62]:
popt = np.array(df.sort_values(by='-logL', ascending=True).head(10).iloc[0, 8:16])
popt


Out[62]:
array([ 0.57186809,  0.88099127,  0.96028147,  2.47804429,  3.35763481,
        0.12258205,  0.78858169,  0.03443572])

In [63]:
p0 = popt

#ar_growth_split_asym_mig_iso = lbview.map(run_moments, repeat(p0, 10), block=False)

In [ ]:
ar_growth_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/moments/growth_split_asym_mig_iso_[0-9]*dill"):
    ar_growth_split_asym_mig_iso.append(dill.load(open(filename)))

In [64]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_growth_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu0_0', 'T0_0', 'nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu0_opt', 'T0_opt', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[64]:
nu0_0 T0_0 nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu0_opt T0_opt nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
8 0.478361 0.600371 0.915652 2.394444 5.237654 0.107703 0.788940 0.054349 0.322405 0.721422 0.939739 2.281812 3.222201 0.147156 0.714855 0.029154 12161.725129
9 0.541779 0.753700 1.292661 2.234091 6.529202 0.197980 0.597210 0.044125 0.390099 0.805081 1.093583 2.666808 3.825588 0.124310 0.614952 0.034201 12161.833170
6 0.756310 0.610113 1.637206 3.863623 5.586361 0.100969 0.565471 0.034967 1.072690 0.863285 2.325932 5.683236 7.995964 0.056162 0.292024 0.069902 12163.115801
0 0.657092 1.042348 1.752530 1.671289 3.800436 0.128064 0.483207 0.035890 0.537379 1.034912 1.114684 2.740146 3.668139 0.117017 0.606216 0.034223 12167.809120
4 0.289245 1.032207 0.757127 2.035369 6.580868 0.106359 0.663344 0.053421 0.016516 1.053880 0.925626 2.172022 4.091610 0.171846 0.691154 0.026805 12177.172315
1 0.411010 1.195370 1.112308 1.994802 5.297989 0.127001 0.920352 0.024967 0.164366 1.139513 0.837759 2.128655 3.317673 0.132980 0.934745 0.042878 12227.058300
7 0.627848 0.532866 1.859941 2.357627 2.642389 0.103762 0.998211 0.029438 0.462481 0.842833 0.682708 1.667421 1.836028 0.185476 0.994732 0.022165 12232.084966
3 0.427168 0.637715 1.014916 1.413397 4.881187 0.129571 0.944485 0.046034 0.254441 0.230100 0.924312 2.433100 3.236970 0.098847 0.878437 0.035143 12251.736384
5 0.733821 0.772047 1.702140 2.663568 1.724104 0.100074 1.485616 0.020126 0.535466 0.658994 0.700799 1.885862 1.768271 0.113202 0.949344 0.016242 12305.888824
2 1.096355 0.964239 1.324215 3.570309 4.244330 0.135503 1.332969 0.033429 1.916098 0.870125 1.148375 2.780225 2.448991 0.114420 0.606875 0.045226 12455.919847

There is still no good convergence and the best parameter combination is still worse than the ancient migration model above.

with dadi


In [1]:
from ipyparallel import Client

cl = Client()

cl.ids


Out[1]:
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]

In [18]:
lbview = cl.load_balanced_view()

In [2]:
%%px --local

# run whole cell on all engines a well as in the local IPython session

import numpy as np

import sys

sys.path.insert(0, '/home/claudius/Downloads/dadi')

import dadi


error in importing Two Locus modules
[stdout:0] error in importing Two Locus modules
[stdout:1] error in importing Two Locus modules
[stdout:2] error in importing Two Locus modules
[stdout:3] error in importing Two Locus modules
[stdout:4] error in importing Two Locus modules
[stdout:5] error in importing Two Locus modules
[stdout:6] error in importing Two Locus modules
[stdout:7] error in importing Two Locus modules
[stdout:8] error in importing Two Locus modules
[stdout:9] error in importing Two Locus modules
[stdout:10] error in importing Two Locus modules
[stdout:11] error in importing Two Locus modules
[stdout:12] error in importing Two Locus modules
[stdout:13] error in importing Two Locus modules
[stdout:14] error in importing Two Locus modules
[stdout:15] error in importing Two Locus modules
[stdout:16] error in importing Two Locus modules
[stdout:17] error in importing Two Locus modules
[stdout:18] error in importing Two Locus modules
[stdout:19] error in importing Two Locus modules

In [74]:
def growth_split_asym_mig_iso(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by complete isolation until present.

    nu0: relative population size of ancient population (with respect to Na)
    T0: length of time of ancient population size change (in 2*Na generations)
    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu0,T0,nu1,nu2,Tc,m1,m2,Ti = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # ancient population growth
    phi = dadi.Integration.one_pop(phi, xx, T0, nu0)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
    
    # divergence without gene flow
    phi = dadi.Integration.two_pops(phi, xx, Ti, nu1, nu2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    
    return fs

In [65]:
cl[:].push(dict(growth_split_asym_mig_iso=growth_split_asym_mig_iso))


Out[65]:
<AsyncResult: _push>

In [75]:
%%px --local

func_ex = dadi.Numerics.make_extrap_log_func(growth_split_asym_mig_iso)

In [67]:
from glob import glob
import dill
from utility_functions import *
import pandas as pd
# turn on floating point division by default, old behaviour via '//'
from __future__ import division
import numpy as np
from itertools import repeat

In [13]:
def run_dadi(p_init): # for the function to be called with map, it needs to have one input variable
    """
    p_init: initial parameter values to run optimisation from
    """
    if perturb == True:
        p_init = dadi.Misc.perturb_params(p_init, fold=fold, 
                                      upper_bound=upper_bound, lower_bound=lower_bound)
        # note upper_bound and lower_bound variables are expected to be in the namespace of each engine
    # run optimisation of paramters
    popt = dadi_opt_func(p0=p_init, data=sfs, model_func=func_ex, pts=pts_l, \
                                   lower_bound=lower_bound, upper_bound=upper_bound, \
                                   verbose=verbose, maxiter=maxiter, full_output=full_output, \
                                    fixed_params=fixed_params)
    # pickle to file
    import dill
    name = outname[:] # make copy of file name stub!
    for p in p_init:
        name += "_%.4f" % (p)
    with open(name + ".dill", "w") as fh:
        dill.dump((p_init, popt), fh)
    
    return p_init, popt

In [14]:
%%px --local

# load spectrum modified with Ludovic's correction, p=35

sfs2d = dadi.Spectrum.from_file("EryPar_modified.2dsfs")

In [21]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
pts_l = [50, 60, 70]
func_ex = func_ex
dadi_opt_func = dadi.Inference.optimize_log_fmin # uses Nelder-Mead algorithm
sfs = sfs2d
perturb = True
fold = 2 # perturb randomly up to `fold` times 2-fold
maxiter = 100 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/dadi/growth_split_asym_mig_iso" # set file name stub for opt. result files
fixed_params = None

In [16]:
%matplotlib inline

import pylab

pylab.rcParams['figure.figsize'] = [12, 10]
pylab.rcParams['font.size'] = 14

In [17]:
%%px --local

# set lower and upper bounds to nu0, T0, nu1, nu2, Tc, m1, m2 and Ti
upper_bound = [1e3, 10, 1e3, 1e3, 10, 10, 10, 10] # note, I have increased the upper bound for T
lower_bound = [1e-3, 0, 1e-3, 1e-3, 0, 0, 0, 0]

In [22]:
# using best parameter combination from optimisations with moments
p0 = [0.322405, 0.721422, 0.939739, 2.281812, 3.222201, 0.147156, 0.714855, 0.029154]

#ar_growth_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [23]:
ar_growth_split_asym_mig_iso.done()


Out[23]:
True

In [24]:
ar_growth_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/dadi/growth_split_asym_mig_iso_[0-9]*dill"):
    ar_growth_split_asym_mig_iso.append(dill.load(open(filename)))

In [25]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_growth_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu0_0', 'T0_0', 'nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu0_opt', 'T0_opt', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[25]:
nu0_0 T0_0 nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu0_opt T0_opt nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
6 0.134223 0.825106 0.888839 4.373342 6.541907 0.150534 0.449628 0.034774 0.046183 0.764156 0.905767 2.039924 1.949478 0.209447 0.631441 0.014052 12262.628478
5 0.252917 0.209928 0.445185 3.539609 8.108074 0.433757 0.433367 0.099074 0.098221 0.260055 1.151509 2.693133 2.252404 0.128966 0.503502 0.016284 12263.195783
0 0.328864 1.623764 0.877336 4.338617 6.752281 0.083146 0.286545 0.079677 0.408743 2.281100 0.863797 2.334117 1.669998 0.107036 0.700905 0.003421 12343.265231
9 0.830144 2.019298 0.314303 2.205413 1.292294 0.050359 2.231025 0.039981 0.668315 1.473583 0.866867 2.306444 1.240350 0.050568 0.771685 0.021697 12354.001785
8 0.274450 1.218914 0.776534 0.570648 0.888096 0.270014 0.285456 0.113272 0.409702 1.229840 0.685611 1.176478 0.718191 0.541107 0.665066 0.014454 12626.021537
7 0.200997 0.533873 0.957372 0.698372 1.880397 0.067473 0.364927 0.052887 0.010735 0.307043 0.933960 1.974723 1.954844 0.283072 0.349346 0.001456 12947.071447
3 0.188569 0.537946 0.589364 0.750909 1.042721 0.381536 0.395934 0.094304 0.080349 1.089399 0.493190 0.791194 1.074422 0.762254 0.938198 0.012432 13041.895694
1 1.073996 0.235010 0.507480 1.198939 2.171307 0.099362 1.194594 0.041174 1.141121 0.669787 0.606379 1.482118 0.667162 0.104198 1.275175 0.017045 13447.939995
4 0.129020 2.118282 0.768717 0.824888 4.575784 0.461290 0.512629 0.023749 0.119361 1.610603 0.622141 0.834945 0.902434 0.664483 0.446430 0.001732 13467.715191
2 1.053817 1.936775 0.366830 0.978639 5.618175 0.355607 0.272139 0.045223 1.038290 3.294536 0.676551 0.964515 0.502158 0.607416 0.521986 0.001644 14834.973582

No good convergence yet. Let's refine the grid and increase maxiter and reduce fold.


In [68]:
%%px

fold = 1
maxiter = 300
pts_l = [50, 60, 70]

In [27]:
popt = np.array(df.sort_values(by='-logL', ascending=True).head(10).iloc[0, 8:16])
popt


Out[27]:
array([ 0.04618334,  0.76415574,  0.90576652,  2.03992401,  1.94947774,
        0.20944677,  0.63144129,  0.01405198])

In [28]:
p0 = popt

#ar_growth_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [29]:
ar_growth_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/dadi/growth_split_asym_mig_iso_[0-9]*dill"):
    ar_growth_split_asym_mig_iso.append(dill.load(open(filename)))

In [31]:
get_flag_count(ar_growth_split_asym_mig_iso, NM=True)


success 0
Maximum number of function evaluations made. 0
Maximum number of iterations reached. 20
unknown flag 0

None of the optimisation runs achieved convergence within the limit of 300 iterations.


In [30]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_growth_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu0_0', 'T0_0', 'nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu0_opt', 'T0_opt', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[30]:
nu0_0 T0_0 nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu0_opt T0_opt nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
17 0.025156 0.966251 0.467084 1.685644 2.655476 0.198602 0.369774 0.011566 0.158982 0.972331 1.323932 3.231264 2.857907 0.106863 0.530510 0.047712 12157.652488
10 0.092130 1.367449 1.219959 3.667953 2.275247 0.318282 0.564823 0.015947 0.110088 1.368700 1.139888 2.779680 2.491826 0.124632 0.614518 0.041061 12158.084806
2 0.082218 0.567914 0.552175 2.418403 1.058503 0.211223 0.428586 0.025459 0.044286 0.419266 0.512293 1.249072 1.125668 0.278188 1.368325 0.018453 12158.273880
1 0.086703 1.106330 1.746223 1.656487 1.373758 0.105862 0.435626 0.008731 0.057118 1.191871 0.783084 1.908620 1.732691 0.181039 0.895866 0.028209 12158.549028
4 0.056448 0.642539 1.427693 3.636780 2.419741 0.165201 0.349771 0.014935 0.096488 0.709415 1.612796 3.929907 3.588950 0.088293 0.435097 0.058097 12158.794649
19 0.088842 0.823319 1.664314 3.894656 3.271317 0.106120 0.467887 0.011972 0.091078 0.841412 1.629691 3.970805 3.634436 0.087356 0.430441 0.058706 12158.882607
14 0.080962 0.507299 0.787016 2.240025 2.266528 0.266524 0.339397 0.009640 0.029831 0.292834 0.821060 2.001163 1.849495 0.173709 0.853970 0.029577 12159.290069
5 0.042260 1.197768 0.613177 2.426155 1.693186 0.354824 0.336918 0.021070 0.012718 1.134690 0.806787 1.965247 1.836110 0.175934 0.869384 0.029059 12159.761122
0 0.053697 0.890830 0.925349 3.567025 1.282859 0.123114 0.648212 0.010959 0.392110 0.831958 0.918907 2.227507 1.514920 0.148429 0.736956 0.029431 12163.299530
12 0.134223 0.825106 0.888839 4.373342 6.541907 0.150534 0.449628 0.034774 0.046183 0.764156 0.905767 2.039924 1.949478 0.209447 0.631441 0.014052 12262.628478

It is difficult to achieve convergence. It may already be too complex. It does not achieve a higher likelihood than the ancient migration model (-logL: 12156).

I conclude that the addition of a population size change of the ancestral population of ERY and PAR does not improve the fit. However, it does achieve a similarly good fit with a time since split between ERY and PAR much shorter than inferred with the ancient migration model.

Maybe I can achieve convergence with another optimsation algorithm.


In [32]:
dadi.Inference.optimize?

In [33]:
%%px --local

# set up global variables on engines required for run_dadi function call

ns = sfs2d.sample_sizes # both populations have the same sample size
pts_l = [50, 60, 70]
func_ex = func_ex
dadi_opt_func = dadi.Inference.optimize # uses BFGS algorithm
sfs = sfs2d
perturb = True
fold = 1 # perturb randomly up to `fold` times 2-fold
maxiter = 300 # run a maximum of 300 iterations
verbose = 0
full_output = True # need to have full output to get the warnflags (see below)
outname = "MODIFIED_SPECTRA/OUT_2D_models/dadi/growth_split_asym_mig_iso" # set file name stub for opt. result files
fixed_params = None

In [34]:
popt = np.array(df.sort_values(by='-logL', ascending=True).head(10).iloc[0, 8:16])
popt


Out[34]:
array([ 0.15898231,  0.97233138,  1.32393179,  3.23126378,  2.85790723,
        0.10686261,  0.53050979,  0.04771157])

In [35]:
p0 = popt

#ar_growth_split_asym_mig_iso = lbview.map(run_dadi, repeat(p0, 10), block=False)

In [37]:
ar_growth_split_asym_mig_iso.elapsed/60


Out[37]:
34.59303466666667

In [69]:
ar_growth_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/dadi/growth_split_asym_mig_iso_[0-9]*dill"):
    ar_growth_split_asym_mig_iso.append(dill.load(open(filename)))

In [70]:
l = 2*len(p0)+1

success = [flatten(out)[:l] for out in ar_growth_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu0_0', 'T0_0', 'nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu0_opt', 'T0_opt', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[70]:
nu0_0 T0_0 nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu0_opt T0_opt nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
13 0.243485 0.749393 0.790999 2.478842 3.857886 0.171512 0.422471 0.035618 0.184724 0.299258 1.120924 2.742162 2.183116 0.122150 0.611739 0.036528 12155.826631
8 0.190827 0.595480 2.579873 4.153851 4.742194 0.125715 0.306428 0.041922 0.668403 1.550333 2.322523 5.688872 4.517284 0.058989 0.297679 0.079468 12156.020904
17 0.242454 0.611050 1.929829 4.070851 5.552329 0.061143 0.539000 0.032159 0.643044 0.944617 2.095336 5.131735 3.972959 0.065006 0.328071 0.070383 12156.138565
6 0.192498 0.494757 2.046877 2.469721 1.738255 0.153193 0.623615 0.024804 0.442032 1.954224 1.432426 3.508154 2.774798 0.095432 0.479957 0.047463 12156.147740
27 0.216496 1.773033 2.139193 2.300216 2.077733 0.090291 0.370286 0.036041 0.327046 2.396208 1.024295 2.513421 1.981020 0.133355 0.669639 0.033676 12156.257545
25 0.025156 0.966251 0.467084 1.685644 2.655476 0.198602 0.369774 0.011566 0.158982 0.972331 1.323932 3.231264 2.857907 0.106863 0.530510 0.047712 12157.652488
14 0.092130 1.367449 1.219959 3.667953 2.275247 0.318282 0.564823 0.015947 0.110088 1.368700 1.139888 2.779680 2.491826 0.124632 0.614518 0.041061 12158.084806
3 0.082218 0.567914 0.552175 2.418403 1.058503 0.211223 0.428586 0.025459 0.044286 0.419266 0.512293 1.249072 1.125668 0.278188 1.368325 0.018453 12158.273880
2 0.086703 1.106330 1.746223 1.656487 1.373758 0.105862 0.435626 0.008731 0.057118 1.191871 0.783084 1.908620 1.732691 0.181039 0.895866 0.028209 12158.549028
5 0.056448 0.642539 1.427693 3.636780 2.419741 0.165201 0.349771 0.014935 0.096488 0.709415 1.612796 3.929907 3.588950 0.088293 0.435097 0.058097 12158.794649

There is still no good convergence. Quite different demographic scenarios have almost identical likelihood. One of which is the ancient migration model from above, which had -logL of 12,156 with it's best fit parameter values. A population size change in the ancestral population to ERY and PAR cannot be inferred from this spectrum.

This illustrates the uncertainty in the inference of the demographic scenario. The most parsimonious model found is the ancient migration model.


In [80]:
def translate_time(i, j):
    """
    i: row index in parameter table with best-fit combinations at the top
    j: index for parameter in parameter list
    """

    popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[i,8:16])

    model = func_ex(popt, ns, pts_l)

    theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
    
    L = sfs2d.data.sum()
    mu = 3e-9
    N_ref = theta/L/mu/4
    
    return int(popt[j]*2*N_ref)

In [85]:
# get translation of time of split parameter Tc for the top 5 parameter combinations
map(translate_time, range(5), repeat(4, 5))


Out[85]:
[1517296, 1515905, 1479343, 1509180, 1504830]

Note, that despite the much smaller values for Tc (the time of split between ERY and PAR) in genetic units (2Nref) as compared to the ancient migration model, this ancestral size change model infers very similar values for Tc in absolute units (generations) (compare eith ancient).


In [86]:
# get translation of time of isolation for the top 5 parameter combinations
map(translate_time, range(5), repeat(7, 5))


Out[86]:
[25387, 26667, 26207, 25814, 25580]

The inferred time of complete isolation is also very similar to the ancient migration model best-fit parameter values.


In [87]:
# get translation of time of isolation for the top 5 parameter combinations
map(translate_param, range(5), repeat(5, 5))


Out[87]:
[84896, 19795, 24205, 51904, 101299]

In [91]:
def translate_migration(i, j, k):
    """
    i: row index in parameter table with best-fit combinations at the top
    j: index for migration parameter in parameter list
    k: index of pop size parameter in param list
    """

    popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[i,8:16])

    model = func_ex(popt, ns, pts_l)

    theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
    
    L = sfs2d.data.sum()
    mu = 3e-9
    N_ref = theta/L/mu/4
    
    return popt[j]/2/N_ref/popt[k]

In [92]:
# get translation of migration parameter ery->par for top 5 param comb
map(translate_migration, range(5), repeat(5, 5), repeat(3, 5))


Out[92]:
[6.4092487031069124e-08,
 3.0899247177476557e-08,
 3.4020148712630468e-08,
 5.0015613755533254e-08,
 6.98464733121575e-08]

In [93]:
# get translation of migration parameter par->ery for top 5 param comb
map(translate_migration, range(5), repeat(6, 5), repeat(2, 5))


Out[93]:
[7.8522919076288438e-07,
 3.819387333369181e-07,
 4.2049345899439553e-07,
 6.1605583359294688e-07,
 8.6063038581282348e-07]

The migration rate in proportion of new immigrant alleles per generation is in the same order of magnitude as for the ancient migration model. Migration from ery into par is again about 10 times weaker than in the other direction. The migration rates with this ancestral population size change model are generally slightly higher than those from the ancient migration model.

In conclusion it can be said that this ancestral population size change model predicts a very similar demographic history as the ancient migration model.

Uncertainty analysis

It is currently not possible to get bootstrap replicates of the SFS with ANGSD/realSFS for 2D SFS. This is due to realSFS either ignoring replicated sites (i. e. reading them only once) - #81 - or throwing an error when the SAF file contains replicated sites - #86.


In [1]:
import numpy as np

import sys

sys.path.insert(0, '/home/claudius/Downloads/dadi')

import dadi


error in importing Two Locus modules

In [2]:
from glob import glob
import dill
from utility_functions import *
import pandas as pd
# turn on floating point division by default, old behaviour via '//'
from __future__ import division

In [3]:
# load spectrum modified with Ludovic's correction, p=35

sfs2d = dadi.Spectrum.from_file("EryPar_modified.2dsfs")

In [4]:
def split_asym_mig_iso(params, ns, pts):
    """
    params = (nu1,nu2,Tc,m1,m2,Ti)
    ns = (n1,n2)

    Split into two populations of specifed size, with potentially asymmetric migration
    for a time Tc followed by complete isolation until present.

    nu1: population size ratio of population 1 after split (with respect to Na)
    nu2: population size ratio of population 2 after split (with respect to Na)
    Tc: Time of gene flow after split (in units of 2*Na generations) 
    m1: Migration rate from ery into par (in units of 2*Na ind per generation)
    m2: Migration rate from par into ery (in units of 2*Na ind per generation)
    Ti: Time of isolation after cessation of gene flow
    The split lies Tc+Ti * 2Na generations in the past.
    n1,n2: Sample sizes of resulting Spectrum
    pts: Number of grid points to use in integration.
    """
    nu1,nu2,Tc,m1,m2,Ti = params

    xx = dadi.Numerics.default_grid(pts)

    phi = dadi.PhiManip.phi_1D(xx)
    
    # split
    phi = dadi.PhiManip.phi_1D_to_2D(xx, phi) 
    
    # divergence with potentially asymmetric migration
    phi = dadi.Integration.two_pops(phi, xx, Tc, nu1, nu2, m12=m2, m21=m1)
    
    # divergence without gene flow
    phi = dadi.Integration.two_pops(phi, xx, Ti, nu1, nu2, m12=0, m21=0)

    fs = dadi.Spectrum.from_phi(phi, ns, (xx,xx))
    return fs

In [5]:
ns = sfs2d.sample_sizes

pts_l = [50, 60, 70]

func_ex = dadi.Numerics.make_extrap_log_func(split_asym_mig_iso)

In [6]:
ar_split_asym_mig_iso = []

for filename in glob("OUT_2D_models/split_asym_mig_iso_[0-9]*dill"):
    ar_split_asym_mig_iso.append(dill.load(open(filename)))

In [7]:
l = 2*6+1

success = [flatten(out)[:l] for out in ar_split_asym_mig_iso]

df = pd.DataFrame(data=success, \
                  columns=['nu1_0','nu2_0', 'Tc_0', 'm1_0', 'm2_0', 'Ti_0', 'nu1_opt', 'nu2_opt', 'Tc_opt', 'm1_opt', 'm2_opt', 'Ti_opt', '-logL'])

df.sort_values(by='-logL', ascending=True).head(10)


Out[7]:
nu1_0 nu2_0 Tc_0 m1_0 m2_0 Ti_0 nu1_opt nu2_opt Tc_opt m1_opt m2_opt Ti_opt -logL
31 3.421903 12.882920 7.920000 0.090910 0.137533 0.104192 3.428735 8.372007 6.719565 0.040627 0.202323 0.118790 12156.061309
29 2.208113 8.325249 5.594802 0.045599 0.494681 0.058458 3.434782 8.387801 6.732856 0.040569 0.202054 0.119088 12156.061960
17 1.638405 7.027064 3.328377 0.093406 0.279576 0.084667 3.365763 8.218499 6.573684 0.041489 0.206208 0.116898 12156.066451
4 3.157867 11.894738 6.832827 0.089239 0.343652 0.125056 3.151832 7.672115 6.074031 0.044790 0.217833 0.107115 12156.250139
2 2.150693 3.752285 7.339520 0.053352 0.182956 0.054508 2.905080 7.099776 5.511164 0.047722 0.237869 0.100633 12156.336374
8 4.507973 4.211151 4.530473 0.038619 0.356819 0.049796 2.838367 6.936326 5.355545 0.048636 0.243390 0.096733 12156.361180
19 3.495075 4.606451 5.940000 0.055664 0.246368 0.105244 2.769359 6.770118 5.196347 0.049832 0.249131 0.094203 12156.467656
16 1.899722 10.672562 4.209415 0.035018 0.158627 0.129019 2.549936 6.237224 4.683557 0.053493 0.269833 0.085552 12156.997320
15 3.193392 3.398205 7.493464 0.054278 0.184446 0.099955 2.625334 6.426852 4.896773 0.051447 0.259054 0.084092 12157.030051
9 1.114068 2.045370 0.628484 0.054210 0.524956 0.061246 2.877591 7.051026 5.515295 0.046193 0.241364 0.095230 12157.097777

In [8]:
popt = np.array(df.sort_values(by='-logL', ascending=True).iloc[0,6:12])
popt


Out[8]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969])

This is the definition of FIM_uncert in Godambe.py after I changed it:

def FIM_uncert(func_ex, grid_pts, p0, data, log=False, multinom=True, eps=0.01, return_FIM=False): """ Parameter uncertainties from Fisher Information Matrix

Returns standard deviations of parameter values.

func_ex: Model function
all_boot: List of bootstrap frequency spectra
p0: Best-fit parameters for func_ex
data: Original data frequency spectrum
eps: Fractional stepsize to use when taking finite-difference derivatives.
     Note that if eps*param is < 1e-6, then the step size for that parameter
     will simply be eps, to avoid numerical issues with small parameter
     perturbations.
log: If True, assume log-normal distribution of parameters. Returned values 
     are then the standard deviations of the *logs* of the parameter values,
     which can be interpreted as relative parameter uncertainties.
multinom: If True, assume model is defined without an explicit parameter for
          theta. Because uncertainty in theta must be accounted for to get
          correct uncertainties for other parameters, this function will
          automatically consider theta if multinom=True. In that case, the
          final entry of the returned uncertainties will correspond to
          theta.
return_FIM: if true, return the Hessian matrix, else return standard devia-
            tions of parameters
"""
if multinom:
    func_multi = func_ex
    model = func_multi(p0, data.sample_sizes, grid_pts)
    theta_opt = Inference.optimal_sfs_scaling(model, data)
    p0 = list(p0) + [theta_opt]
    func_ex = lambda p, ns, pts: p[-1]*func_multi(p[:-1], ns, pts)
H = get_godambe(func_ex, grid_pts, [], p0, data, eps, log, just_hess=True)
#return numpy.sqrt(numpy.diag(numpy.linalg.inv(H)))
uncerts = numpy.sqrt(numpy.diag(numpy.linalg.inv(H)))
if return_FIM:
    return H
else:
    return uncerts

I have changed the FIM_uncert function to return the inverse of the full Hessian, not just it's diagonal.


In [9]:
H = dadi.Godambe.FIM_uncert(func_ex=func_ex, grid_pts=pts_l, p0=popt, data=sfs2d, return_FIM=True)


If you use the Godambe methods in your published research, please cite Coffman et al. (2016) in addition to the main dadi paper Gutenkunst et al. (2009).
AJ Coffman, P Hsieh, S Gravel, RN Gutenkunst "Computationally efficient composite likelihood statistics for demographic inference" Molecular Biology and Evolution 33:591-593 (2016)

The returned Hessian matrix is the Fisher Information Matrix CrossValidated.

Thus, the Fisher information may be seen as the curvature of the support curve (the graph of the log-likelihood). Near the maximum likelihood estimate, low Fisher information therefore indicates that the maximum appears "blunt", that is, the maximum is shallow and there are many nearby values with a similar log-likelihood. Conversely, high Fisher information indicates that the maximum is sharp.

from the Wikipedia article on Fisher Information

If your data are all unlinked, you can get estimates of your parameter uncertainties using the Hessian (a.k.a. Fisher Information Matrix), which is the second derivate matrix of the log-likelihood with respect to your parameters. (Essentially you're making a quadratic approximation of the likelihood surface.)

Ryan Gutenkunst on the dadi forum

The inverse (reciprocal) of the FIM is the approximated variance-covariance matrix.


In [10]:
import numpy as np
import pandas as pd

In [11]:
var_covar = np.linalg.inv(H)

In [12]:
df_var_covar = pd.DataFrame(data=var_covar, \
             columns=['nu1', 'nu2', 'Tc', 'm1', 'm2', 'Ti', 'theta'], \
             index=['nu1', 'nu2', 'Tc', 'm1', 'm2', 'Ti', 'theta'])
df_var_covar


Out[12]:
nu1 nu2 Tc m1 m2 Ti theta
nu1 0.177630 0.429300 0.406873 -0.001978 -0.010153 0.006905 -80.105181
nu2 0.429300 1.052449 0.992028 -0.004944 -0.024457 0.016702 -195.327301
Tc 0.406873 0.992028 0.942937 -0.004644 -0.023221 0.015787 -184.902394
m1 -0.001978 -0.004944 -0.004644 0.000026 0.000112 -0.000073 0.911957
m2 -0.010153 -0.024457 -0.023221 0.000112 0.000597 -0.000369 4.569607
Ti 0.006905 0.016702 0.015787 -0.000073 -0.000369 0.000345 -3.115900
theta -80.105181 -195.327301 -184.902394 0.911957 4.569607 -3.115900 36392.681492

In [13]:
# standard deviations for the parameters (genetic units)

popt_sd = np.sqrt(np.diag(np.linalg.inv(H)))
popt_sd


Out[13]:
array([  4.21461341e-01,   1.02588932e+00,   9.71049314e-01,
         5.09750878e-03,   2.44295440e-02,   1.85792582e-02,
         1.90768660e+02])

In [14]:
model = func_ex(popt, ns, pts_l)

In [15]:
theta = dadi.Inference.optimal_sfs_scaling(model, sfs2d)
theta


Out[15]:
1541.3020921056773

In [32]:
np.resize?

In [16]:
popt = np.resize(popt, len(popt)+1)
popt


Out[16]:
array([ 3.42873459,  8.372007  ,  6.71956526,  0.04062737,  0.20232337,
        0.11878969,  3.42873459])

In [17]:
# add theta to popt

popt[-1] = theta
popt


Out[17]:
array([  3.42873459e+00,   8.37200700e+00,   6.71956526e+00,
         4.06273660e-02,   2.02323365e-01,   1.18789691e-01,
         1.54130209e+03])

In [61]:
round?

In [18]:
print "p_opt    SD        pSE"
for p, sd, psd in zip(popt, popt_sd, popt_sd/popt*100):
    print "{0:.3f} (+-{1:.3f}) (+-{2:2d}%)".format(p, sd, int(round(psd)))


p_opt    SD        pSE
3.429 (+-0.421) (+-12%)
8.372 (+-1.026) (+-12%)
6.720 (+-0.971) (+-14%)
0.041 (+-0.005) (+-13%)
0.202 (+-0.024) (+-12%)
0.119 (+-0.019) (+-16%)
1541.302 (+-190.769) (+-12%)

What is the uncertainty in the total time of split between ERY and PAR?


In [19]:
nu1, nu2, Tc, m1, m2, Ti, theta = popt

In [20]:
T_total = Tc + Ti
T_total


Out[20]:
6.8383549500585286
$$ \sigma_{T_{total}} = \sqrt{\sigma^2_{T_c} + \sigma^2_{T_i} + 2\sigma_{T_c T_i}} $$

In [35]:
from math import sqrt

In [22]:
sigma_T_total = sqrt( df_var_covar["Tc"]["Tc"]**2 + df_var_covar["Ti"]["Ti"]**2 + 2*df_var_covar["Tc"]["Ti"]**2 )
sigma_T_total


Out[22]:
0.9432011067486601

That is the estimated standard deviation of the split time $T_{total}$ in genetic units (2Nref).


In [23]:
# 95% CI

print T_total - 1.96*sigma_T_total
print T_total + 1.96*sigma_T_total


4.98968078083
8.68702911929

Now, it would be nice to get the uncertainty for the split time translated to absolute units (generations).

To make things more simple, let's get the standard deviation for $T_c$ first. This should be very close to $T_{total}$.

$$ \begin{align} N_{ref} &= \frac{\theta}{4L\mu} \\[5pt] T_c^{abs} &= T_c \times 2N_{ref} \\[5pt] T_c^{abs} &= \left(\frac{1}{2L\mu} \right) T_c \times \theta \end{align} $$
$$ V(aX) = a^2 \, V(X) $$

equation B.12 in Gillespie: Populations genetics - A concise guide. $V()$ is the variance.

$$ \hat{V}(xy) = E^2(x) V(y) + E^2(y) V(x) + 2 E(x)E(y) C(x, y) - V(x)V(y) - C^2(x, y) $$

equation (4) in:

Gray, G. Covariances in Multiplicative Estimates. Transactions of the American Fisheries Society, 1999, 128, 475-482

Note the sign change for the last two terms as compared to equation (6) in:

Bohrnstedt, G. W. & Goldberger, A. S.: On the Exact Covariance of Products of Random Variables. Journal of the American Statistical Association, 1969, 64, 1439-1442

The sign change makes $\hat{V}(xy)$ an unbiased estimator of $V(xy)$.

$x$ and $y$ are assumed to be bivariate normally distributed. $E(x)$ and $E(y)$ are the maximum likelihood estimates returned by dadi. $C()$ is the covariance.

$$ V\left(T_c^{abs}\right) = \left(\frac{1}{2L\mu}\right)^2 \left( T_c^2 \times V(\theta) + \theta^2 \times V(T_c) + 2 \theta T_c C(T_c, \theta) - V(T_c)V(\theta) - C^2(T_c, \theta) \right) $$

In [24]:
mu = 3e-9
L = sfs2d.data.sum()

In [25]:
var_theta = df_var_covar['theta']['theta']
var_theta


Out[25]:
36392.681492222313

In [26]:
theta


Out[26]:
1541.3020921056773

In [27]:
var_Tc = df_var_covar['Tc']['Tc']
var_Tc


Out[27]:
0.94293677063309866

In [28]:
cov_Tc_theta = df_var_covar['Tc']['theta']
cov_Tc_theta


Out[28]:
-184.90239394077324

In [64]:
var_Tc_theta = Tc**2 * var_theta + theta**2 * var_Tc + 2*theta*Tc*cov_Tc_theta - var_Tc*var_theta - cov_Tc_theta**2
var_Tc_theta


Out[64]:
-15254.027287502169

The highly negative $C(T_c, \theta)$ makes this variance negative. Maybe the formula of Gray1999 fails because $T_c$ and $\theta$ are not bivariate normal or the approximation of the covariance via the Hessian is not very good? The assumption of normality is necessary to drop the third moments (e. g. $C(x^2, y^2)$), which are unknown, from equation (5) in Bohrnstedt1969. If I want to calculate confidence intervals, I also have to assume that the parameters are normally distributed.


In [31]:
1/(2*L*mu)**2


Out[31]:
21724.265548113301

In [65]:
var_Tc_abs = 1/(2*L*mu)**2 * var_Tc_theta
var_Tc_abs


Out[65]:
-331382539.47186357

Instead of the unbiased estimator of the variance of the product of two variables, I am going to use "a conventional asymptotic approximation" (Bohrnstedt1969, equation (14)) and simply ignore the last two terms in $\hat{V}(yx)$ of Gray1999: $V(T_c)V(\theta)$ and $C^2(T_c, \theta)$. This leaves:

$$ \hat{V}(xy) \approx E^2(x) V(y) + E^2(y) V(x) + 2 E(x)E(y) C(x, y) $$

Note, that the approximation is conservative, i. e. the approximation will always overestimate the true variance.


In [81]:
var_Tc_theta = Tc**2 * var_theta + theta**2 * var_Tc + 2*theta*Tc*cov_Tc_theta
var_Tc_theta


Out[81]:
53250.865558481775

In [82]:
var_Tc_abs = 1/(2*L*mu)**2 * var_Tc_theta
var_Tc_abs


Out[82]:
1156835944.0593388

In [83]:
# standard deviation of Tc in generations

int( sqrt(var_Tc_abs) )


Out[83]:
34012

If this is actually close to the true standard deviation of $T_c$, then this suggests that the estimate of $T_c$ in generations is remarkably precise! The optimal $T_c$ in generations was:


In [45]:
Nref = theta/(4*L*mu)
int(Tc * 2 * Nref)


Out[45]:
1526516

The estimated correlation coefficient between $T_c$ and $\theta$ is:


In [46]:
corr_Tc_theta = cov_Tc_theta / sqrt(var_theta * var_Tc)
corr_Tc_theta


Out[46]:
-0.99814634286816439

$T_c$ and $\theta$ are highly negatively correlated. This explains why the realtively large estimated standard deviation of $T_c$ in genetic units (0.971) translates to such a small standard deviation in absolute units (generations).

Now, what is the estimate for the standard deviation for $T_i$, the time since complete isolation between ERY and PAR?


In [47]:
var_Ti = df_var_covar['Ti']['Ti']
var_Ti


Out[47]:
0.00034518883576171154

In [70]:
cov_Ti_theta = df_var_covar['Ti']['theta']
cov_Ti_theta


Out[70]:
-3.1158998533941071

In [84]:
var_Ti_abs = 1/(2*L*mu)**2 * (Ti**2 * var_theta + theta**2 * var_Ti + 2*theta*Ti*cov_Ti_theta)
var_Ti_abs


Out[84]:
4183797.8589262804

In [85]:
# standard deviation for Ti in generations

int( sqrt(var_Ti_abs) )


Out[85]:
2045

This again suggests a remarkably precise estimate!


In [51]:
# correlation coefficient between Ti and theta

cov_Ti_theta / sqrt(var_theta * var_Ti)


Out[51]:
-0.87911986147539334

$T_i$ and $\theta$ are also highly negatively correlated.

Finally, it would be nice to have an approximate standard deviation for the total time since the split between ERY and PAR.

$$ \begin{align} T_{total}^{abs} &= T_{total} \times 2N_{ref} \\[5pt] &= \left[T_c + T_i\right] \times 2N_{ref} \\[5pt] &= \frac{1}{2L\mu} \times \left[T_c + T_i\right] \times \theta \\[5pt] N_{ref} &= \frac{\theta}{4L\mu} \end{align} $$

$$ V\left(aX\right) = a^2 \times V(X) $$

equation B.12 in Gillespie: Populations genetics - A concise guide

$$ V(xy) \approx E^2(x) V(y) + E^2(y) V(x) + 2 E(x)E(y) C(x, y) $$
$$ V(x + y) = V(x) + V(y) + 2C(x, y) $$

equation B.14 in Gillespie: Populations genetics - A concise guide

$$ C\left(x_1 + x_2, y_1 \right) = C\left(x_1, y_1\right) + C\left(x_2, y_1\right) $$

equation B.16 in Gillespie: Populations genetics - A concise guide


$$ \begin{align} Var\left(T_{total}^{abs}\right) = \left(\frac{1}{2L\mu}\right)^2 \times Var\left(\left[T_c + T_i\right] \times \theta \right) \end{align} $$
$$ \begin{align} V\big([T_c + T_i] \times \theta \big) &\approx [T_c + T_i]^2 V(\theta) + \theta^2 V\left(T_c + T_i\right) \\ & \quad + 2\theta [T_c + T_i] C\left([T_c + T_i], \theta \right) \end{align} $$
$$ V\left(T_c + T_i\right) = V(T_c) + V(T_i) + 2C(T_c, T_i) $$
$$ C\left([T_c + T_i], \theta \right) = C(T_c, \theta) + C(T_i, \theta) $$

In [86]:
cov_Tc_Ti = df_var_covar['Tc']['Ti']
cov_Tc_Ti


Out[86]:
0.015786944045444427

In [87]:
var_Tc_plus_Ti = var_Tc + var_Ti + 2 * cov_Tc_Ti

var_T_total_abs = 1/(2*L*mu)**2 * ((Tc+Ti)**2 * var_theta + theta**2 * var_Tc_plus_Ti + 2*theta*(Tc+Ti)*(cov_Tc_theta+cov_Ti_theta))
var_T_total_abs


Out[87]:
1179612027.7706144

In [89]:
# standard deviation of T_total in generations

int(sqrt(var_T_total_abs))


Out[89]:
34345

In [56]:
# correlation coefficient between Tc and Ti

cov_Tc_Ti / sqrt(var_Tc * var_Ti)


Out[56]:
0.87504102192902289

$T_c$ and $T_i$ are highly correlated. That's why the $SD\left(T_{total}^{abs}\right) < SD\left(T_c^{abs}\right) + SD\left(T_i^{abs}\right)$: 34345 < 34012 + 2045.

What's the SD for $N_{a}$ in unit individuals?

$$ N_a = N_{ref} = \frac{\theta}{4L\mu} $$
$$ V\left(N_{ref}\right) = \left( \frac{1}{4L\mu} \right)^2 \times V(\theta) $$

In [57]:
var_Nref = (1/(4*L*mu))**2 * var_theta
int(sqrt(var_Nref))


Out[57]:
14058

In [58]:
# optimal estimate of Nref

int(theta/(4*L*mu))


Out[58]:
113587

What's the SD for $N_{ery}$ in unit individuals?

$$ \begin{align} N_{ery} &= \nu_1 \times N_{ref} \\[5pt] V\left(N_{ery}\right) &= \left( \frac{1}{4L\mu} \right)^2 \times V(\nu_1\theta) \\[5pt] V(\nu_1\theta) &\approx \nu_1^2 V(\theta) + \theta^2 V(\nu_1) + 2\nu_1\theta C(\nu_1, \theta) \end{align} $$

In [93]:
var_nu1 = df_var_covar['nu1']['nu1']
var_nu1


Out[93]:
0.1776296623743224

In [94]:
cov_nu1_theta = df_var_covar['nu1']['theta']
cov_nu1_theta


Out[94]:
-80.105181209165821

In [95]:
var_Nery = (1/(4*L*mu))**2 * (nu1**2 * var_theta + theta**2 * var_nu1 + 2*nu1*theta*cov_nu1_theta)
int(sqrt(var_Nery))


Out[95]:
4138

In [96]:
# correlation coefficient between nu1 and theta

cov_nu1_theta / sqrt(var_nu1 * var_theta)


Out[96]:
-0.99631308416427167

The lower variance of $N_{ery}$ as compared to $N_{ref}$ is due to the highly negative correlation between $\nu_1$ and $\theta$.

What's the SD for $N_{par}$?

$$ N_{par} = \nu_2 \times N_{ref} \\[5pt] $$

In [97]:
var_nu2 = df_var_covar['nu2']['nu2']
var_nu2


Out[97]:
1.0524488911343766

In [98]:
cov_nu2_theta = df_var_covar['nu2']['theta']
cov_nu2_theta


Out[98]:
-195.32730067517488

In [99]:
var_Npar = (1/(4*L*mu))**2 * (nu2**2 * var_theta + theta**2 * var_nu2 + 2*nu2*theta*cov_nu2_theta)
int(sqrt(var_Npar))


Out[99]:
7393

In [100]:
# correlation coefficient between nu2 and theta

cov_nu2_theta / sqrt(var_nu2 * var_theta)


Out[100]:
-0.99805715558325925

What's the SD for $p_{ery\rightarrow par}$?

$$ p_{ery->par} = \frac{m_{1}}{2N_{ref}\nu_2} \\[5pt] $$
$$ V(p_{ery->par}) = \left(2L\mu \right)^2 \times V\left(\frac{m_1}{\theta\nu_2} \right) $$
$$ V\left(\frac{x}{y}\right) \approx \left(\frac{E[x]}{E[y]}\right)^2 \times \left(\frac{V(x)}{E[x]^2} + \frac{V(y)}{E[y]^2} - 2 \frac{C(x, y)}{E[x]E[y]} \right) $$

taken from Wikipedia article Propagation of uncertainty and citations therein and this CrossValidated post. I couldn't find a better source so far.

$$ C(xy, v) = E(x)C(y, v) + E(y)C(x, v) $$

equation (12) in:

Bohrnstedt, G. W. & Goldberger, A. S.: On the Exact Covariance of Products of Random Variables. Journal of the American Statistical Association, 1969, 64, 1439-1442

With the assumption of multivariate normality, the third moment from the original equation could be dropped.

$$ V\left(\frac{m_1}{\theta\nu_2}\right) \approx \left(\frac{m_1}{\theta\nu_2}\right)^2 \times \left(\frac{V(m_1)}{m_1^2} + \frac{V(\theta\nu_2)}{(\theta\nu_2)^2} - \frac{2}{m_1\theta\nu_2} \, C(m_1, \theta\nu_2) \right) $$
$$ \begin{align} V(\nu_2\theta) &\approx \nu_2^2 V(\theta) + \theta^2 V(\nu_2) + 2\nu_2\theta C(\nu_2, \theta) \end{align} $$
$$ C(\theta\nu_2, m_1) = \theta C(\nu_2, m_1) + \nu_2 C(\theta, m_1) $$

In [101]:
var_nu2 = df_var_covar['nu2']['nu2']
var_nu2


Out[101]:
1.0524488911343766

In [102]:
var_m1 = df_var_covar['m1']['m1']
var_m1


Out[102]:
2.5984595791736249e-05

In [103]:
cov_nu2_theta = df_var_covar['nu2']['theta']
cov_nu2_theta


Out[103]:
-195.32730067517488

In [104]:
cov_nu2_m1 = df_var_covar['nu2']['m1']
cov_nu2_m1


Out[104]:
-0.0049443681301208327

In [105]:
cov_theta_m1 = df_var_covar['theta']['m1']
cov_theta_m1


Out[105]:
0.91195735252172427

In [107]:
var_nu2_theta = nu2**2 * var_theta + theta**2 * var_nu2 + 2*nu2*theta*cov_nu2_theta
var_nu2_theta


Out[107]:
10065.965439219028

In [108]:
cov_theta_nu2_m1 = theta*cov_nu2_m1 + nu2*cov_theta_m1
cov_theta_nu2_m1


Out[108]:
0.014148396507438044

In [122]:
var_m1_over_nu2theta = (m1/(nu2*theta))**2 * (var_m1/m1**2 + var_nu2_theta/(theta*nu2)**2 - 2/(m1*nu2*theta) * cov_theta_nu2_m1)
var_m1_over_nu2theta


Out[122]:
1.5612049444893069e-13

In [123]:
var_p_ery_par = (2*L*mu)**2 * var_m1_over_nu2theta

In [124]:
# standard deviation for the proportion of new gametes from ERY
# in the PAR population each generation

sqrt(var_p_ery_par)


Out[124]:
2.6807567465991263e-09

In [115]:
# MLE of p_ery->par was:

m1/(2*Nref*nu2)


Out[115]:
2.1361353349704674e-08

What's the SD for $p_{par\rightarrow ery}$?

$$ p_{par->ery} = \frac{m_{2}}{2N_{ref}\nu_1} \\[5pt] $$
$$ V\left(\frac{m_2}{\theta\nu_1}\right) \approx \left(\frac{m_2}{\theta\nu_1}\right)^2 \times \left(\frac{V(m_2)}{m_2^2} + \frac{V(\theta\nu_1)}{(\theta\nu_1)^2} - \frac{2}{m_2\theta\nu_1} \, C(m_2, \theta\nu_1) \right) $$
$$ V(\nu_1\theta) \approx \nu_1^2 V(\theta) + \theta^2 V(\nu_1) + 2\nu_1\theta C(\nu_1, \theta) $$
$$ C(\theta\nu_1, m_2) = \theta C(\nu_1, m_2) + \nu_1 C(\theta, m_2) $$

In [116]:
var_m2 = df_var_covar['m2']['m2']
var_m2


Out[116]:
0.00059680262227011815

In [118]:
var_nu1_theta = nu1**2 * var_theta + theta**2 * var_nu1 + 2*nu1*theta*cov_nu1_theta
var_nu1_theta


Out[118]:
3153.3514708146686

In [119]:
cov_nu1_m2 = df_var_covar['nu1']['m2']
cov_nu1_m2


Out[119]:
-0.01015320017630488

In [120]:
cov_theta_m2 = df_var_covar['theta']['m2']
cov_theta_m2


Out[120]:
4.5696067895571071

In [121]:
cov_m2_nu1_theta = theta*cov_nu1_m2 + nu1*cov_theta_m2
cov_m2_nu1_theta


Out[121]:
0.018820191492944005

In [125]:
var_m2_over_thetanu1 = (m2/(theta*nu1))**2 * ( var_m2/m2**2 + var_nu1_theta/(nu1*theta)**2 - 2/(m2*theta*nu1) * cov_m2_nu1_theta )   
var_m2_over_thetanu1


Out[125]:
2.1483053996492995e-11

In [126]:
var_p_par_ery = (2*L*mu)**2 * var_m2_over_thetanu1
var_p_par_ery


Out[126]:
9.888966763416656e-16

In [128]:
sqrt(var_p_par_ery)


Out[128]:
3.1446727593529756e-08

In [129]:
m2/(2*Nref*nu1)


Out[129]:
2.5974778433936024e-07

Summary

The following table shows the inferred parameters for the ancient migration model from the corrected spectrum in their absolute units together with the estimated standard errors (SD) and relative standard errors SD/MLE in percent:

parameter MLE SD %SD/MLE
$N_{a}$ 113,522 14,058 12%
$N_{ery}$ 389,237 4,138 1%
$N_{par}$ 950,409 7,393 0.78%
$T_c$ 1,525,640 34,012 2.2%
$p_{ery->par}$ 2.14e-08 2.68e-09 12%
$p_{par->ery}$ 2.60e-07 3.14e-08 12%
$T_i$ 26,970 2,045 7.6%
$T_c+T_i$ 1,552,610 34,345 2.2%

$N_x$ have unit individuals, $T_x$ has unit generations, $p_x$ are proportions of new immigrant individuals per generation.

The effective population sizes of ERY and PAR seem to be estimated with extremely high precision. The precision for the estimated time parameters also seems to be extremely good. Note that these standard errors are only valid if all SNP's in the spectrum are unlinked. If there is linkage between SNP's then these standard errors will underestimate the true standard errors. Better estimates of variance can only be achieved with bootstraps over contigs and the Godambe Information Matrix. However, acquiring bootstraps over contigs is currently not possible with ANGSD/realSFS.