Jekyll2018-03-12T14:06:15+00:00http://armchairecology.blog/Armchair EcologyDispatches from the mean fieldThree years of using Julia2018-03-12T00:00:00+00:002018-03-12T00:00:00+00:00http://armchairecology.blog/julia-after-three-years<p>I started using the <a href="https://julialang.org/">Julia</a> programming language in 2014, and by 2015, it
represented about 75% of my programming time/output. These have been interesting
years, because the language changed a lot, but also because I found a tool that
makes me very productive.</p>
<!--more-->
<p>My two favourite pieces of cookware are a Victorinox chef knife, and a Lodge
cast-iron skillet. I use the chef knife to do things it’s not really meant to do
(like garlic purée), or things that should probably be done with a paring knife.
But I’m efficient with it, and it makes me happy. The same goes for the
cast-iron. Some things should probably be done in non-stick, but I don’t care. I
love doing them this way, and they turn out alright in the end.</p>
<p>Programming languages are the same: they are <em>tools</em>. A good tool is one that
makes you productive, happy, and allow you to deliver a usable product. For me,
<code class="highlighter-rouge">Julia</code> allowed all of that, and this is in large part because of the things I
do often (write custom code to do various numerical things), the things I do
somewhat often (differential equations, embarrassingly parallel problems), and
the things I don’t do very often at all (statistics).</p>
<p>The one area where <code class="highlighter-rouge">Julia</code> shines is that it eliminates the need for
intermediate steps when preparing code. For one of my first large simulation
projects, I wrote initial proof of concept code in <code class="highlighter-rouge">R</code>. Then I moved it to
<code class="highlighter-rouge">python</code>, and after spending some time identifying bottlenecks, I re-wrote these
parts in <code class="highlighter-rouge">C</code>. I don’t do this anymore: I use the same language for proof of
concept, prototyping, and production. And because I spent a bit of time reading
the <a href="https://docs.julialang.org/en/stable/manual/performance-tips/">performance tips</a> section of the manual, the code for the three steps
is often very similar. This is a huge time saver, and it decreases the cognitive
load associated with switching languages.</p>
<p>One thing I have noticed over the last two years is that my use of supercomputer
time decreased. Because the <a href="https://docs.julialang.org/en/stable/manual/parallel-computing/#Parallel-Computing-1">parallel computing</a> abilities out of the
box are really strong, most of what I need can be done on my 32-cores desktop.
In a recent instance, porting code from <code class="highlighter-rouge">python</code> to <code class="highlighter-rouge">Julia</code> decreased the time
of execution by two orders of magnitudes: investing the time to make this run on
a cluster, and waiting for a spot in the queue, was not worth it anymore.</p>
<p>And now is the right time to start learning the language. The <a href="https://discourse.julialang.org/">Discourse</a>
community is very quick at answering, and very welcoming of newcomers. The
<code class="highlighter-rouge">v0.7</code> will be stable (or very close to), so there will be no more deprecation
when <code class="highlighter-rouge">v1.0</code> is released. The ecosystem of packages is starting to diversify, and
<a href="http://juliadb.org/">JuliaDB</a> is a very good solution for data crunching.</p>TimothéeI started using the Julia programming language in 2014, and by 2015, it represented about 75% of my programming time/output. These have been interesting years, because the language changed a lot, but also because I found a tool that makes me very productive.The grammar of data2018-02-19T00:00:00+00:002018-02-19T00:00:00+00:00http://armchairecology.blog/data-grammar<p>This winter, I am teaching a class on data management and analysis to biology
seniors, for the second time. Now that the data munging part of it is done,
I would like to share an observation which is as brief as it is unoriginal,
about an underlying principle of data manipulation.</p>
<!--more-->
<p>Data manipulation is a difficult thing to teach, because it requires
students to tackle several challenges at once. First, thinking about data
in a unified and systematic way, as opposed to the <em>ad hoc</em> approach that is
acquired to practice without supervision. Second, thinking about the tools to
perform the tasks, and adapt to the very different UI/UX and nomenclature of
each. Finally, and this is the part I would like to discuss, thinking about
data cleaning/manipulation/reshaping as a <em>process</em>, and therefore thinking
of tools as being many ways to work through this process.</p>
<p>We had three back-to-back classes on (i) data cleaning with <em>OpenRefine</em>,
(ii) programmatic data manipulation with <code class="highlighter-rouge">R</code>, and (iii) relational data with
<code class="highlighter-rouge">SQLite</code>; very standard walk through the fantastic <a href="http://www.datacarpentry.org/lessons/">Data Carpentry</a>
material, in the spirit of reusing <a href="/data-training-community-over-institution/">community material instead of creating
more</a>.</p>
<p>At the end of the final class, we briefly discussed the fact that all of
these tools let us do a series of <strong>four</strong> things, and if you understand
these four things, you understand 99.5% of data analysis – in short, there
is a grammar for data (and yes, this is the selling point of the tidyverse,
but this grammar existed far before the tidyverse, and the tendency of the
tidycrowd to appropriate everything and sell it as new irks me).</p>
<p><strong>Even better, these four things can be presented as questions</strong>.</p>
<p><em>What do I want</em>? Or rather, what do I want to leave out. This is the domain
of functions like <code class="highlighter-rouge">select</code>, <code class="highlighter-rouge">filter</code>, etc. Whether we apply this to columns
or row is not really important; what matter is that we start any problem by
defining the scope of the data we need.</p>
<p><em>What do I want to do</em>? This is where we <em>transform</em> (or <em>mutate</em>) our selected
rows or columns, or create some more, or merge them, or any other operation
we want. This step is usually the <em>meat</em> of the data manipulation process.</p>
<p><em>What do I want to know</em>? Is it an average? Is it some other aggregate
statistic? This is when <code class="highlighter-rouge">summarize</code>-like function come into play. This step
is essentially going from many to few rows.</p>
<p><em>What are my grouping variables</em>? The final step creates our groups in the
output. This is <code class="highlighter-rouge">GROUP BY</code> in <code class="highlighter-rouge">SQL</code>, and other things in <code class="highlighter-rouge">R</code>, and the facets
in <em>OpenRefine</em>.</p>
<p><strong>That’s it</strong>. Any additional transformation (re-order columns/rows) is
cosmetic. Next year, I will feature this approach in a more prominent way
in the class. I hope it will make it easier for students to understand
that the tools presented have, in fact, more things in common than they
have differences.</p>TimothéeThis winter, I am teaching a class on data management and analysis to biology seniors, for the second time. Now that the data munging part of it is done, I would like to share an observation which is as brief as it is unoriginal, about an underlying principle of data manipulation.Diffential Equations with Julia, updated.2018-02-12T00:00:00+00:002018-02-12T00:00:00+00:00http://armchairecology.blog/differential-equations-updated<p>The new release of the <code class="highlighter-rouge">DifferentialEquations</code> package introduces a number of
<a href="http://juliadiffeq.org/2018/01/24/Parameters.html">breaking changes</a>, which are all for the best. So I will revisit my
<a href="/solving-differential-equations-for-ecological-problems/">previous</a> post on this package, to show some of the new syntax in action.</p>
<!--more-->
<p>In a few words, the new release defines parameters of the differential equations
system as <em>part of the problem</em>, so they can be passed directly to the function.
The new way to declare a problem to solve is <code class="highlighter-rouge">f(u, p, t)</code>, where <code class="highlighter-rouge">u</code> is the
quantity to model, <code class="highlighter-rouge">p</code> are the parameters, and <code class="highlighter-rouge">t</code> is the time.</p>
<p>So let’s start:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Logistic model with r=1.1, K=10.0</span>
<span class="n">f</span><span class="x">(</span><span class="n">u</span><span class="x">,</span><span class="n">p</span><span class="x">,</span><span class="n">t</span><span class="x">)</span> <span class="o">=</span> <span class="n">u</span><span class="o">*</span><span class="mf">1.1</span><span class="o">*</span><span class="x">(</span><span class="mf">1.0</span><span class="o">-</span><span class="n">u</span><span class="o">/</span><span class="mf">10.0</span><span class="x">)</span>
<span class="c"># Now we solve the problem</span>
<span class="n">problem</span> <span class="o">=</span> <span class="n">ODEProblem</span><span class="x">(</span><span class="n">f</span><span class="x">,</span> <span class="mf">0.5</span><span class="x">,</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">10.0</span><span class="x">))</span>
<span class="n">solution</span> <span class="o">=</span> <span class="n">solve</span><span class="x">(</span><span class="n">problem</span><span class="x">)</span>
</code></pre></div></div>
<p>Now, we want to modify the values of $r$ and $K$ – this can be done using the
<code class="highlighter-rouge">p</code> parameter. First, let’s re-write <code class="highlighter-rouge">f</code> so that it calls values within <code class="highlighter-rouge">p</code>:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">f</span><span class="x">(</span><span class="n">u</span><span class="x">,</span><span class="n">p</span><span class="x">,</span><span class="n">t</span><span class="x">)</span> <span class="o">=</span> <span class="n">u</span><span class="o">*</span><span class="n">p</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span><span class="o">*</span><span class="x">(</span><span class="mf">1.0</span><span class="o">-</span><span class="n">u</span><span class="o">/</span><span class="n">p</span><span class="x">[</span><span class="mi">2</span><span class="x">])</span>
</code></pre></div></div>
<p>The next step is to provide <code class="highlighter-rouge">ODESolver</code> with an array containing the parameters:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">problem</span> <span class="o">=</span> <span class="n">ODEProblem</span><span class="x">(</span><span class="n">f</span><span class="x">,</span> <span class="mf">0.5</span><span class="x">,</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">10.0</span><span class="x">),</span> <span class="x">[</span><span class="mf">1.4</span><span class="x">,</span> <span class="mf">8.0</span><span class="x">])</span>
<span class="n">solution</span> <span class="o">=</span> <span class="n">solve</span><span class="x">(</span><span class="n">problem</span><span class="x">)</span>
</code></pre></div></div>
<p>With these ingredients, we have anything to reproduce the Allee effect model
from the <a href="/solving-differential-equations-for-ecological-problems/">previous post</a>, in a way which is much more concise:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">allee</span><span class="x">(</span><span class="n">u</span><span class="x">,</span><span class="n">p</span><span class="x">,</span><span class="n">t</span><span class="x">)</span> <span class="o">=</span> <span class="n">u</span><span class="o">*</span><span class="n">p</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span><span class="o">*</span><span class="x">(</span><span class="n">u</span><span class="o">/</span><span class="n">p</span><span class="x">[</span><span class="mi">3</span><span class="x">]</span><span class="o">-</span><span class="mf">1.0</span><span class="x">)</span><span class="o">*</span><span class="x">(</span><span class="mf">1.0</span><span class="o">-</span><span class="n">u</span><span class="o">/</span><span class="n">p</span><span class="x">[</span><span class="mi">2</span><span class="x">])</span>
</code></pre></div></div>
<p>This takes three parameters, $r$, $K$, and $A$. This allows calling the function
directly without having to re-define the problem multiple times. As a
consequence, the code is easier to read, to write, and to maintain. Of course,
we might also want to use a dictionary to store the parameters. It works out of
the box too.</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">p</span> <span class="o">=</span> <span class="n">Dict</span><span class="x">(:</span><span class="n">r</span> <span class="o">=></span> <span class="mf">1.1</span><span class="x">,</span> <span class="x">:</span><span class="n">K</span> <span class="o">=></span> <span class="mf">3.0</span><span class="x">,</span> <span class="x">:</span><span class="n">A</span> <span class="o">=></span> <span class="mf">1.0</span><span class="x">)</span>
<span class="n">allee</span><span class="x">(</span><span class="n">u</span><span class="x">,</span><span class="n">p</span><span class="x">,</span><span class="n">t</span><span class="x">)</span> <span class="o">=</span> <span class="n">u</span><span class="o">*</span><span class="n">p</span><span class="x">[:</span><span class="n">r</span><span class="x">]</span><span class="o">*</span><span class="x">(</span><span class="n">u</span><span class="o">/</span><span class="n">p</span><span class="x">[:</span><span class="n">A</span><span class="x">]</span><span class="o">-</span><span class="mf">1.0</span><span class="x">)</span><span class="o">*</span><span class="x">(</span><span class="mf">1.0</span><span class="o">-</span><span class="n">u</span><span class="o">/</span><span class="n">p</span><span class="x">[:</span><span class="n">K</span><span class="x">])</span>
<span class="n">pr</span> <span class="o">=</span> <span class="n">ODEProblem</span><span class="x">(</span><span class="n">allee</span><span class="x">,</span> <span class="mf">1.2</span><span class="x">,</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">10.0</span><span class="x">),</span> <span class="n">p</span><span class="x">)</span>
<span class="n">solve</span><span class="x">(</span><span class="n">pr</span><span class="x">)</span>
</code></pre></div></div>
<hr />
<p>Now, it’s time for a short example. Let’s write-up the logistic growth model
with competition - we’ll use the version with a pre-allocated variable for the
derivative:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">p</span> <span class="o">=</span> <span class="n">Dict</span><span class="x">(</span>
<span class="x">:</span><span class="n">r1</span> <span class="o">=></span> <span class="mf">1.1</span><span class="x">,</span> <span class="x">:</span><span class="n">r2</span> <span class="o">=></span> <span class="mf">1.4</span><span class="x">,</span>
<span class="x">:</span><span class="n">a11</span> <span class="o">=></span> <span class="mf">1.0</span><span class="x">,</span> <span class="x">:</span><span class="n">a22</span> <span class="o">=></span> <span class="mf">1.0</span><span class="x">,</span>
<span class="x">:</span><span class="n">a21</span> <span class="o">=></span> <span class="mf">1.2</span><span class="x">,</span> <span class="x">:</span><span class="n">a12</span> <span class="o">=></span> <span class="mf">1.1</span>
<span class="x">)</span>
<span class="k">function</span><span class="nf"> competition</span><span class="x">(</span><span class="n">du</span><span class="x">,</span> <span class="n">u</span><span class="x">,</span><span class="n">p</span><span class="x">,</span><span class="n">t</span><span class="x">)</span>
<span class="n">du</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span> <span class="o">=</span> <span class="n">u</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span><span class="o">*</span><span class="n">p</span><span class="x">[:</span><span class="n">r1</span><span class="x">]</span> <span class="o">-</span> <span class="n">p</span><span class="x">[:</span><span class="n">a11</span><span class="x">]</span><span class="o">*</span><span class="n">u</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span><span class="o">^</span><span class="mi">2</span> <span class="o">-</span> <span class="n">p</span><span class="x">[:</span><span class="n">a12</span><span class="x">]</span><span class="o">*</span><span class="n">u</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span><span class="o">*</span><span class="n">u</span><span class="x">[</span><span class="mi">2</span><span class="x">]</span>
<span class="n">du</span><span class="x">[</span><span class="mi">2</span><span class="x">]</span> <span class="o">=</span> <span class="n">u</span><span class="x">[</span><span class="mi">2</span><span class="x">]</span><span class="o">*</span><span class="n">p</span><span class="x">[:</span><span class="n">r2</span><span class="x">]</span> <span class="o">-</span> <span class="n">p</span><span class="x">[:</span><span class="n">a22</span><span class="x">]</span><span class="o">*</span><span class="n">u</span><span class="x">[</span><span class="mi">2</span><span class="x">]</span><span class="o">^</span><span class="mi">2</span> <span class="o">-</span> <span class="n">p</span><span class="x">[:</span><span class="n">a21</span><span class="x">]</span><span class="o">*</span><span class="n">u</span><span class="x">[</span><span class="mi">2</span><span class="x">]</span><span class="o">*</span><span class="n">u</span><span class="x">[</span><span class="mi">1</span><span class="x">]</span>
<span class="k">end</span>
<span class="n">du</span> <span class="o">=</span> <span class="n">zeros</span><span class="x">(</span><span class="mi">2</span><span class="x">)</span>
<span class="n">u0</span> <span class="o">=</span> <span class="x">[</span><span class="mf">0.1</span><span class="x">,</span> <span class="mf">0.1</span><span class="x">]</span>
<span class="n">pro</span> <span class="o">=</span> <span class="n">ODEProblem</span><span class="x">(</span><span class="n">competition</span><span class="x">,</span> <span class="n">u0</span><span class="x">,</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">10.0</span><span class="x">),</span> <span class="n">p</span><span class="x">)</span>
<span class="n">sol</span> <span class="o">=</span> <span class="n">solve</span><span class="x">(</span><span class="n">pro</span><span class="x">)</span>
</code></pre></div></div>
<p><img src="/images/posts/diffeq2.png" alt="Output of the initial run" />Now we can run this
model, and as expected given the parameters values, the first species will
gradually go extinct. The nice thing with this interface is that we don’t need
to re-declare the function describing the differential equations to use
different parameters: this can be done when declaring the <code class="highlighter-rouge">ODEProblem</code> to solve.</p>
<p>As a consequence, the real power of the new interface is that iterations over
large collections of parameters becomes trivial. As an example, we can look at
transgressive overyielding, which is defined as the coexistence equilibrium
having a higher density than the best single population equilibrium, when
changing the values of $\alpha_{12}$ and $\alpha_{21}$. In principle we don’t
need to run the model for the two species alone (because we vary parameters that
are not involved in the single species equilibrium), but computing time is
cheap.</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">N</span> <span class="o">=</span> <span class="mi">150</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">zeros</span><span class="x">(</span><span class="n">N</span><span class="x">,</span> <span class="n">N</span><span class="x">)</span>
<span class="n">alpha</span> <span class="o">=</span> <span class="n">collect</span><span class="x">(</span><span class="n">linspace</span><span class="x">(</span><span class="mf">0.2</span><span class="x">,</span> <span class="mf">2.0</span><span class="x">,</span> <span class="n">N</span><span class="x">))</span>
<span class="k">for</span> <span class="n">a1</span> <span class="k">in</span> <span class="n">eachindex</span><span class="x">(</span><span class="n">alpha</span><span class="x">),</span> <span class="n">a2</span> <span class="k">in</span> <span class="n">eachindex</span><span class="x">(</span><span class="n">alpha</span><span class="x">)</span>
<span class="n">p</span><span class="x">[:</span><span class="n">a12</span><span class="x">]</span> <span class="o">=</span> <span class="n">alpha</span><span class="x">[</span><span class="n">a1</span><span class="x">]</span>
<span class="n">p</span><span class="x">[:</span><span class="n">a21</span><span class="x">]</span> <span class="o">=</span> <span class="n">alpha</span><span class="x">[</span><span class="n">a2</span><span class="x">]</span>
<span class="n">coexist</span> <span class="o">=</span> <span class="n">last</span><span class="x">(</span><span class="n">solve</span><span class="x">(</span><span class="n">ODEProblem</span><span class="x">(</span><span class="n">competition</span><span class="x">,</span> <span class="x">[</span><span class="mf">0.1</span><span class="x">,</span> <span class="mf">0.1</span><span class="x">],</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">100.0</span><span class="x">),</span> <span class="n">p</span><span class="x">)))</span>
<span class="n">s1</span> <span class="o">=</span> <span class="n">last</span><span class="x">(</span><span class="n">solve</span><span class="x">(</span><span class="n">ODEProblem</span><span class="x">(</span><span class="n">competition</span><span class="x">,</span> <span class="x">[</span><span class="mf">0.1</span><span class="x">,</span> <span class="mf">0.0</span><span class="x">],</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">100.0</span><span class="x">),</span> <span class="n">p</span><span class="x">)))[</span><span class="mi">1</span><span class="x">]</span>
<span class="n">s2</span> <span class="o">=</span> <span class="n">last</span><span class="x">(</span><span class="n">solve</span><span class="x">(</span><span class="n">ODEProblem</span><span class="x">(</span><span class="n">competition</span><span class="x">,</span> <span class="x">[</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">0.1</span><span class="x">],</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">100.0</span><span class="x">),</span> <span class="n">p</span><span class="x">)))[</span><span class="mi">2</span><span class="x">]</span>
<span class="n">output</span><span class="x">[</span><span class="n">a1</span><span class="x">,</span><span class="n">a2</span><span class="x">]</span> <span class="o">=</span> <span class="n">sum</span><span class="x">(</span><span class="n">coexist</span><span class="x">)</span><span class="o">/</span><span class="x">(</span><span class="n">max</span><span class="x">(</span><span class="n">s1</span><span class="x">,</span><span class="n">s2</span><span class="x">))</span>
<span class="k">end</span>
</code></pre></div></div>
<p><img src="/images/posts/diffeq2-heatmap.png" alt="Heatmap" class="center-image" /></p>
<p>And that’s it! We see more transgressive overyielding when both interspecific
competition rates are low relative to intraspecific competition.</p>
<p>The <code class="highlighter-rouge">DifferentialEquations</code> package is amazing, and the code is both easy to
write and fast to run. You should definitely check out some of the other
outstanding functions it offers.</p>TimothéeThe new release of the DifferentialEquations package introduces a number of breaking changes, which are all for the best. So I will revisit my previous post on this package, to show some of the new syntax in action.But why should we model things anyways?2018-02-05T00:00:00+00:002018-02-05T00:00:00+00:00http://armchairecology.blog/why-do-we-model-things<p>The relevance of models to <em>actual</em> biology or ecology is not always obvious. In
part, this is because models are by design a gross oversimplication of actual
systems. They gloss over mechanisms. They reduce some complex behaviours to a
single parameter. They abstract a lot of processes. And in the end, they let us
say things that are general, to the point of being wrong. And this is precisely
what makes them very useful.</p>
<!--more-->
<p>For a predator to consume a prey is the outcome of a very long series of very
complex events. The predator needs to balance its own metabolic needs with the
cost of hunting, needs to detect the prey, need to figure out which preys are
suitable, then it needs to catch, kill, consume, and digest it. How should we
model this? If there are $X$ preys and $Y$ predators, I would be absolutely
happy with $\alpha X$: $\alpha$ is the rate of consumption of one unit of prey
for a unit of predator.</p>
<p>It is of course preposterous to assume that this will be enough to describe all
the mechanisms involved. I may be willing to compromise, and make my answer a
little bit more complex: $X\alpha/(1+\alpha hX)$. In this formulation, $h$ is
the average time spent by a predator to <em>process</em> a unit of prey. Is this more
realistic? In a way, yes, because any predator is either hunting ($\alpha$) or
consuming ($h$) at any give time.</p>
<p><strong>But is this <em>better</em></strong>? Well, it depends.</p>
<p>From a purely statistical point of view, the second model has more <em>parameters</em>,
so we may define “better” as “giving enough predictive power to justify spending
the extra parameter”.</p>
<p>From a biological point of view, the second model has more <em>mechanisms</em>, so it
should be better. Even though represent these mechanisms as their phenomenons
($h$ represents the fact that the consuming a prey takes time, and not the
series of actions needed to consume the prey), there seems to be more realism in
this model.</p>
<p>From a mathematical analysis point of view, the first model is linear, so its
analysis is trivial. It is better because it is easier to handle.</p>
<p>From a simulation point of view, the second model is non-linear, which should
reduce the risk of instability. Type II responses (to call the second model by
its proper name) are usually stabilizing, so this version should be better.</p>
<p><strong>So why bother?</strong></p>
<p>We could keep on dumping parameters into our model until we have reproduced
something which is very close to the real thing. But this would not be very
useful. Because a model with tons of parameters, precise though it may be, is
also unwieldy.</p>
<p>The nice thing about a model, is that we can <em>manipulate</em> it. In the second
model, we can ask questions like “What happens if the predator handles the prey
very rapidly?”. This means that $h → 0$, so $1+\alpha hX → 1$, and the second
model essentially behaves like the first one!</p>
<p>Models are <em>extremely good</em> at letting you play with the parameters. We can
arbitrarily shift entire mechanisms on and off, and observe the consequences.
This will almost never be a prediction of what happens in nature. Not even
close. But this can inform us about the relative importance of mechanisms, or
the relative interactions between them.</p>
<p>What allows us to use model is our knowledge of the underlying biology. We can
evaluate the mistakes that we are making when writing, for example, <em>growth rate
is constant unless it’s not</em> (also known as $rN$), and we can also evaluate what
can be said about the <em>output</em> of the models.</p>
<p>And when observations and models don’t match? Then models become <em>even more</em>
useful. If we put our best knowledge of the mechanisms in them, but they fail to
reproduce what we see, models become a map of our ignorance. In fact, <strong>models
that don’t match empirical observations are the most useful of all</strong>.</p>
<p>So <em>this</em> is why we bother modelling ecological processes. Because models offer
the flexibility to manipulate them, and because when they don’t work, we can
start to understand what gap in our understanding is to blame.</p>TimothéeThe relevance of models to actual biology or ecology is not always obvious. In part, this is because models are by design a gross oversimplication of actual systems. They gloss over mechanisms. They reduce some complex behaviours to a single parameter. They abstract a lot of processes. And in the end, they let us say things that are general, to the point of being wrong. And this is precisely what makes them very useful.When are networks worth it?2018-01-29T00:00:00+00:002018-01-29T00:00:00+00:00http://armchairecology.blog/when-are-networks-worth-it<p>Last week, during a seminar, my colleague <a href="http://www.shapirolab.ca/">Jesse Shapiro</a> asked a very
interesting question: are there some problems for which networks are not “worth
it”? Even though my knee-jerk answer was quite obviously “Nah”, the actual
answer is far from trivial. So here are a few ways of thinking about it, and
absolutely nothing in the way of a solution</p>
<!--more-->
<p>The question can be re-framed a little bit – when is it not worth paying the
cost that comes with the complexity of dealing with networks? As compared to
non-network based analyses, anything that involves networks has a narrower range
of measures, requires some more intensive computations to get the answer, and is
basically a different way of thinking about objects. So it makes a lot of sense
to ask if the increase in explanatory or predictive power justifies adding what
are essentially new terms or parameters in our model.</p>
<p>There are two situations, I would argue, where networks will not tell you
anything interesting. The first is when you have no interactions, or very close
to no interactions. This sounds trivial, but networks are a way of leveraging
information about the structure of interactions. Maybe there is a critical graph
density below which they don’t really deliver. The second situation is, of
course, the opposite. If the network is entirely connected, or close to, then
you may as well remove the interactions (unless the strenght of interactions is
quantified).</p>
<p>So my naive expectation is that networks should be really informative when you
have enough interaction to pick up some signal, but not so much that every node
is the same. If we play around with the idea of applying Shannon’s entropy to a
network, where our two classes are no interaction, and presence of an
interaction, it is quite easy to see that the value would peak at density $\rho =
0.5$, <em>i.e.</em> half of the matrix representing our network is filled. We
previously found that this density maximizes the variance in the degree
distribution <em>and</em> the number of possible network arrangements [@PoiGra14].</p>
<p>But this is sort of an useless answer, as most (ecological) networks are much
less connected than this (@RozSto01 report that this density
is associated with lowest stability in simulated networks, which sounds like a
reasonable point to make). The point is, there is no clear cut answer to the
question of “How do we know if a network is worth it?”. And reading some recent
papers, it is clear that we have a lot of arguments about <em>what networks can
deliver</em>, and <em>how to apply measures to extract information</em>, but virtually
nothing (that I know of) on <em>when</em> networks should or must be used. This is
obviously someting to revisit more seriously.</p>TimothéeLast week, during a seminar, my colleague Jesse Shapiro asked a very interesting question: are there some problems for which networks are not “worth it”? Even though my knee-jerk answer was quite obviously “Nah”, the actual answer is far from trivial. So here are a few ways of thinking about it, and absolutely nothing in the way of a solutionThe value of getting nothing done.2018-01-22T00:00:00+00:002018-01-22T00:00:00+00:00http://armchairecology.blog/getting-nothing-done<p>This week, I got nothing done. It was a good week. In fact, it was one of the
most productive weeks I had in a while. Let me explain. I did submit a paper,
and sent back some comments to co-authors on two manuscripts, attended a few
meetings and calls, and gave two lectures. But I did not make any of my own
projects move forward. And I did it on purpose.</p>
<!--more-->
<p>Instead on working on the things that needed work, I spent the week reading
papers, book chapters, and working on problems that were not immediately
relevant to any particular active project. I spent some time playing with
adaptive dynamics, some time working on machine learning methods I had not
explored yet, and tried to apply those I had previously explored to new
problems.</p>
<p>One of my colleague is often saying that we should take the time to just sit
back and think more often, and I decided to give this a try. So, in what way was
this productive? <strong>I learned a ton of new things</strong>.</p>
<p>In part, this was because I had no clear goal in mind. There was not this
self-imposed pressure of getting the job done, because nothing I did was tied to
a project. I was free to keep pushing when it was interesting, and free to drop
an idea when it went nowhere or started to be boring. And I was not ashamed in
getting rid of boring things, because discovery and exploration should be fun
(sometimes).</p>
<p>This was not entirely an exercise in indolence. All of the things I toyed with
were on my ever-growing list of “things I am curious about but never had the
time to think deeply about”. I started the week with a rough schedule of things
to do, and worked on one, each day.</p>
<p>At the end of the week, I had a much better idea of the value of each of these
ideas / tools / concepts for my current and future projects. This is why I think
this week in which I got nothing done was, in fact, very productive. Because it
will pay off in the future, in new ideas or in new ways to explore old ones. And
I am quite sure I will do it again.</p>
<p>Maybe not next week.</p>TimothéeThis week, I got nothing done. It was a good week. In fact, it was one of the most productive weeks I had in a while. Let me explain. I did submit a paper, and sent back some comments to co-authors on two manuscripts, attended a few meetings and calls, and gave two lectures. But I did not make any of my own projects move forward. And I did it on purpose.Naive Bayesian Classification of parasite data2018-01-15T00:00:00+00:002018-01-15T00:00:00+00:00http://armchairecology.blog/nbc-parasites<p>Classification is the task where, knowing some values or measurements of a
thing, we decide which category we should put this thing into. People that are
more serious about their nomenclature than I am would talk about labels,
instances, and features. Since I am updating my lecture notes on the technique,
I will share an explanation and an example using parasite data.</p>
<!--more-->
<p>During my Master’s, I worked on the morphometry of fish parasites from the
Monopisthocotylea subclass. The way to identify most of these species is to put
them into the microscope, look very closely as the few solid parts of the hooks
(used to attached to the gills or skin) and reproductive organs, and in some
cases, take measures of the solid parts to compare against reference
measurements.</p>
<p><img src="/images/posts/dactylogyrus.png" alt="Dactylogyrus" />
This is what we can call a “classification” task. Given information about a
sample (a parasite), we want to find the category (the species) to which it
belongs. Naive Bayesian classifiers (NBC, which I’ll use both for the
classifiers themselves and for the method) use some assumptions to simplify the
data structure to provide a prediction about the most likely class. The name of
the method gives all the information we need. It is naive (but it’s ok, because
data are unreasonably effective @HalNor09 anyways), because it makes
simplications and assumptions. It is Bayesian (but not <em>that</em> much Bayesian),
because it relies on conditional probabilities. And it does classification.
<strong>Let’s dig in</strong>.</p>
<p>In the case of assigning parasites to their species based on measurements, what
we want is the probability that the individual (our <em>problem instance</em>) belongs
to each species (the <em>labels</em> in our classification problem), given the
measurements (the <em>features</em>) we know about. Or in other words, $p(C_k|x_1,
\dots, x_n)$., where $C_k$ is a species, and $x_1, \dots, x_n$ are the
measurements for the sample. <strong>This is where the NBC becomes Bayesian</strong>: we can
rewrite this using Bayes’ theorem,</p>
<script type="math/tex; mode=display">p(C_k|\mathbf{x})=\frac{p(C_k)p(\mathbf{x}|C_k)}{p(\mathbf{x})} \,.</script>
<p>If we unpack this formula, $p(C_k)$ is the prior probability of class $k$, and
$p(\textbf{x}|C_k)$ is the likelihood. Let’s talk about $p(\textbf{x})$ for a
minute. This terms represents the probability of data. Because we know the
values of the features we have measured, this term will be a constant for all
classes. And because we are interested in the <em>most likely</em> class, we can remove
it from the expression. We end up with</p>
<script type="math/tex; mode=display">p(C_k|\mathbf{x}) \propto p(C_k)p(\mathbf{x}|C_k)\,.</script>
<p>At this point, <strong>NBC becomes Naive</strong>: we will assume the conditional
independance of all features. Of course nothing in biology is conditionally
independant, and especially not morphomological features. But we can assume that
they are, and see where this leads us. If all features are independant, then we
can rewrite $p(\textbf{x}|C_k)$ as the much easier to deal with $\prod_i
p(x_i|C_k)$. We plug back this expressing in the previous formula, and end up
with</p>
<script type="math/tex; mode=display">p(C_k|\mathbf{x}) \propto p(C_k)\prod_i p(x_i|C_k)\,.</script>
<p>The final step is to apply the <em>maximum a posteriori</em> decision rule, which is a
fancy way of saying that we take the class with the higher probability. Because
we got rid of $p(\mathbf{x})$, this is only <em>proportional to</em> the actual
probability. In any cas, we assign this sample to the class $\hat y$, where</p>
<script type="math/tex; mode=display">\hat y = \text{argmax}\left(p(C_k)\prod_i p(x_i|C_k)\right) \,.</script>
<hr />
<p>I will now use the data from <em>Lamellodiscus</em> on sparid fishes in the
Banyuls-sur-Mer bay @PoiDes10 to showcase how NBC works. These data describe the
lengths between landmarks on the hooks and copulatory organs of gill parasites.
We will attempt to retrieve host and the parasite for a smaple, knowing only its
measurements. The code is <a href="#code">available at the end of the post</a> if you are
curious.</p>
<p>In this dataset, the number of parasites retrieved from each host varies, so we
can use the frequency of the host-parasite association as a value for $p(C_k)$:
if about one third of our datapoints are <em>Lamellodiscus elegans</em> from the gills
of <em>Diplodus vulgaris</em>, then it may not be an unreasonable assumption to think
that unknown samples will have a high probability of also belonging to this
class (of course we may also be sampling my relative ability to spearfish these
species):</p>
<table>
<thead>
<tr>
<th style="text-align: left">Host</th>
<th style="text-align: left">Parasite</th>
<th style="text-align: right">$n$</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><em>D. vulgaris</em></td>
<td style="text-align: left"><em>L. elegans</em></td>
<td style="text-align: right">33</td>
</tr>
<tr>
<td style="text-align: left"><em>D. vulgaris</em></td>
<td style="text-align: left"><em>L. kechemirae</em></td>
<td style="text-align: right">16</td>
</tr>
<tr>
<td style="text-align: left"><em>D. sargus</em></td>
<td style="text-align: left"><em>L. ignoratus</em></td>
<td style="text-align: right">15</td>
</tr>
<tr>
<td style="text-align: left"><em>D. sargus</em></td>
<td style="text-align: left"><em>L. ergensis</em></td>
<td style="text-align: right">12</td>
</tr>
<tr>
<td style="text-align: left"><em>D. vulgaris</em></td>
<td style="text-align: left"><em>L. elegans</em></td>
<td style="text-align: right">10</td>
</tr>
<tr>
<td style="text-align: left"><em>L. mormyrus</em></td>
<td style="text-align: left"><em>L. ignoratus</em></td>
<td style="text-align: right">10</td>
</tr>
</tbody>
</table>
<p>When we pick an unkown sample, the next step is to calculate $p(x_1, \dots,
x_n|C_k)$, which is the products of the $p(x_i | C_k)$. For the sake of the
illustration, we will assume that all values are normally distributed within one
host-parasite association, and so the mean and standard deviation are
sufficient. Assuming a normal distribution, we get the probability that a value
$x$ is drawn from the distribution using its probability density function:</p>
<script type="math/tex; mode=display">p(x|C_k) = \frac{1}{\sqrt{2\pi\sigma_k^2}}\text{exp}\left[-\frac{(x-\mu_k)^2}{2\sigma_k^2}\right]\,.</script>
<p>Once this is done, we can multiply the values for every measurement, then
multiply by the combination relative frequency. After doing this for all
combinations, we pick the one with the highest output. <strong>How did we do</strong>?</p>
<p><img src="/images/posts/nbc-bothlabels.png" alt="Confusion matrix" /></p>
<p>This heatmap represents the predictions (rows) versus the real value (columns).
The shade of blue represents the number of predicted/observed events. In a
perfect world, that is in a world where our classifierd would never make
mistakes, the diagonal would be all blue, and the rest would be all white. In
this example, about 55% of the instances were correctly classified.</p>
<p>Believe it or not, this is actually not too bad. For one thing, these
morphometric features are a little fuzzy between species [@PoiVer11]. This can
be due to both strong adaptive pressure between hosts, as well as growth and
phenotypic plasticity. In any case, NBC is easy to implement. In datasets like
<em>Iris</em>, this gives crazy good results.</p>
<hr />
<p><span id="code"></span></p>
<p><strong>Want to try</strong>? This section is a (terse) walk through the code. The data can
be downloaded from figshare:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="n">url</span> <span class="o">=</span> <span class="s">"http://files.figshare.com/143154/lamellodiscus.txt"</span>
<span class="n">temp_file</span> <span class="o">=</span> <span class="n">download</span><span class="x">(</span><span class="n">url</span><span class="x">)</span>
<span class="n">data_array</span> <span class="o">=</span> <span class="n">readdlm</span><span class="x">(</span><span class="n">temp_file</span><span class="x">,</span> <span class="sc">'\t'</span><span class="x">,</span> <span class="sc">'\r'</span><span class="x">)</span>
<span class="n">writedlm</span><span class="x">(</span><span class="s">"lamellodiscus.csv"</span><span class="x">,</span> <span class="n">data_array</span><span class="x">)</span>
</code></pre></div></div>
<p>The next step is to load the usual group of packages:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">using</span> <span class="n">DataFrames</span><span class="x">,</span> <span class="n">CSV</span><span class="x">,</span> <span class="n">Query</span>
<span class="n">using</span> <span class="n">Distributions</span>
<span class="n">using</span> <span class="n">Plots</span><span class="x">,</span> <span class="n">StatPlots</span>
</code></pre></div></div>
<p>Then we can import the data in a <code class="highlighter-rouge">DataFrame</code>.</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Read the file as a DataFrame using missing values</span>
<span class="n">data_array</span> <span class="o">=</span> <span class="n">readdlm</span><span class="x">(</span><span class="s">"lamellodiscus.csv"</span><span class="x">)</span>
<span class="n">ctypes</span> <span class="o">=</span> <span class="kt">Any</span><span class="x">[</span><span class="n">Union</span><span class="x">{</span><span class="n">Missing</span><span class="x">,</span><span class="kt">Float64</span><span class="x">}</span> <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="x">:</span><span class="n">size</span><span class="x">(</span><span class="n">data_array</span><span class="x">,</span><span class="mi">2</span><span class="x">)]</span>
<span class="n">ctypes</span><span class="x">[</span><span class="mi">1</span><span class="x">:</span><span class="mi">3</span><span class="x">]</span> <span class="o">=</span> <span class="n">fill</span><span class="x">(</span><span class="kt">String</span><span class="x">,</span> <span class="mi">3</span><span class="x">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">CSV</span><span class="o">.</span><span class="n">read</span><span class="x">(</span><span class="s">"lamellodiscus.csv"</span><span class="x">;</span> <span class="n">header</span><span class="o">=</span><span class="mi">1</span><span class="x">,</span> <span class="n">delim</span><span class="o">=</span><span class="s">"</span><span class="se">\t</span><span class="s">"</span><span class="x">,</span> <span class="n">types</span><span class="o">=</span><span class="n">ctypes</span><span class="x">,</span> <span class="n">null</span><span class="o">=</span><span class="s">""</span><span class="x">)</span>
<span class="c"># We can remove the sample unique ID</span>
<span class="n">delete!</span><span class="x">(</span><span class="n">data</span><span class="x">,</span> <span class="x">:</span><span class="n">para</span><span class="x">)</span>
</code></pre></div></div>
<p>Before starting, we will keep only the parasites with all known features:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">compl</span> <span class="o">=</span> <span class="n">data</span><span class="x">[</span><span class="n">completecases</span><span class="x">(</span><span class="n">data</span><span class="x">),:]</span>
</code></pre></div></div>
<p>And keep only the measurements for individuals belonging to host/parasite
associations with more than 10 records.</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># This line counts the number of host/parasite combinations</span>
<span class="n">cases</span> <span class="o">=</span> <span class="n">by</span><span class="x">(</span><span class="n">compl</span><span class="x">,</span> <span class="x">[:</span><span class="n">sphote</span><span class="x">,</span> <span class="x">:</span><span class="n">sppar</span><span class="x">],</span> <span class="n">df</span> <span class="o">-></span> <span class="n">DataFrame</span><span class="x">(</span><span class="n">n</span><span class="o">=</span><span class="n">size</span><span class="x">(</span><span class="n">df</span><span class="x">,</span> <span class="mi">1</span><span class="x">)))</span>
<span class="c"># We keep the combinations with more than ten cases</span>
<span class="n">retained</span> <span class="o">=</span> <span class="nd">@from</span> <span class="n">i</span> <span class="k">in</span> <span class="n">cases</span> <span class="n">begin</span>
<span class="nd">@where</span> <span class="n">i</span><span class="o">.</span><span class="n">n</span> <span class="o">>=</span> <span class="mi">10</span>
<span class="nd">@select</span> <span class="x">{</span><span class="n">combination</span><span class="o">=</span><span class="n">i</span><span class="o">.</span><span class="n">sphote</span><span class="o">*</span><span class="n">i</span><span class="o">.</span><span class="n">sppar</span><span class="x">,</span> <span class="n">i</span><span class="o">.</span><span class="n">n</span><span class="x">}</span>
<span class="nd">@collect</span> <span class="n">DataFrame</span>
<span class="k">end</span>
<span class="c"># We select the lines from the combinations retained at the previous step</span>
<span class="n">dataset</span> <span class="o">=</span> <span class="nd">@from</span> <span class="n">i</span> <span class="k">in</span> <span class="n">compl</span> <span class="n">begin</span>
<span class="nd">@where</span> <span class="n">i</span><span class="o">.</span><span class="n">sphote</span><span class="o">*</span><span class="n">i</span><span class="o">.</span><span class="n">sppar</span> <span class="k">in</span> <span class="n">retained</span><span class="x">[:</span><span class="n">combination</span><span class="x">]</span>
<span class="nd">@select</span> <span class="n">i</span>
<span class="nd">@collect</span> <span class="n">DataFrame</span>
<span class="k">end</span>
</code></pre></div></div>
<p>At this point, what we need to apply NBC is to estimate the distribution of
every morphological feature, for every host/parasite association. We will assume
that these data are normally distributed. We could store the mean and standard
deviation, but this is not really necessary: <code class="highlighter-rouge">julia</code>’s <code class="highlighter-rouge">DataFrames</code> can store
object of any types, so we will store the distributions themselves:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span><span class="nf"> build_distribution_table</span><span class="x">(</span><span class="n">ds</span><span class="x">)</span>
<span class="n">melted</span> <span class="o">=</span> <span class="n">melt</span><span class="x">(</span><span class="n">ds</span><span class="x">)</span>
<span class="n">distribs</span> <span class="o">=</span> <span class="n">by</span><span class="x">(</span><span class="n">melted</span><span class="x">,</span> <span class="x">[:</span><span class="n">sphote</span><span class="x">,</span> <span class="x">:</span><span class="n">sppar</span><span class="x">,</span> <span class="x">:</span><span class="n">variable</span><span class="x">],</span>
<span class="n">df</span> <span class="o">-></span> <span class="n">DataFrame</span><span class="x">(</span><span class="n">d</span><span class="o">=</span><span class="n">Normal</span><span class="x">(</span><span class="n">mean</span><span class="x">(</span><span class="n">df</span><span class="x">[:</span><span class="n">value</span><span class="x">]),</span><span class="n">std</span><span class="x">(</span><span class="n">df</span><span class="x">[:</span><span class="n">value</span><span class="x">])))</span>
<span class="x">)</span>
<span class="n">out</span> <span class="o">=</span> <span class="n">unstack</span><span class="x">(</span><span class="n">distribs</span><span class="x">,</span> <span class="x">:</span><span class="n">variable</span><span class="x">,</span> <span class="x">:</span><span class="n">d</span><span class="x">)</span>
<span class="k">return</span> <span class="n">out</span>
<span class="k">end</span>
</code></pre></div></div>
<p>With this code written, we will do a simple leave one out experiment:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span><span class="nf"> leave_one_out</span><span class="x">(</span><span class="n">idx</span><span class="x">)</span>
<span class="n">train</span> <span class="o">=</span> <span class="n">dataset</span><span class="x">[</span><span class="n">find</span><span class="x">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">x</span> <span class="o">!=</span> <span class="n">idx</span><span class="x">,</span> <span class="mi">1</span><span class="x">:</span><span class="n">size</span><span class="x">(</span><span class="n">dataset</span><span class="x">,</span> <span class="mi">1</span><span class="x">)),:]</span>
<span class="n">test</span> <span class="o">=</span> <span class="n">dataset</span><span class="x">[</span><span class="n">idx</span><span class="x">,:]</span>
<span class="n">train_d</span> <span class="o">=</span> <span class="n">build_distribution_table</span><span class="x">(</span><span class="n">train</span><span class="x">);</span>
<span class="n">features</span> <span class="o">=</span> <span class="n">melt</span><span class="x">(</span><span class="n">test</span><span class="x">)</span>
<span class="n">delete!</span><span class="x">(</span><span class="n">features</span><span class="x">,</span> <span class="x">[:</span><span class="n">sphote</span><span class="x">,</span> <span class="x">:</span><span class="n">sppar</span><span class="x">])</span>
<span class="n">prediction</span> <span class="o">=</span> <span class="n">join</span><span class="x">(</span><span class="n">features</span><span class="x">,</span> <span class="n">train_d</span><span class="x">;</span> <span class="n">on</span><span class="o">=</span><span class="x">[:</span><span class="n">variable</span><span class="x">])</span>
<span class="n">prediction</span><span class="x">[:</span><span class="n">combination</span><span class="x">]</span> <span class="o">=</span> <span class="n">prediction</span><span class="x">[:</span><span class="n">sphote</span><span class="x">]</span><span class="o">.*</span><span class="n">prediction</span><span class="x">[:</span><span class="n">sppar</span><span class="x">]</span>
<span class="n">prediction</span> <span class="o">=</span> <span class="n">join</span><span class="x">(</span><span class="n">prediction</span><span class="x">,</span> <span class="n">retained</span><span class="x">;</span> <span class="n">on</span><span class="o">=</span><span class="x">[:</span><span class="n">combination</span><span class="x">])</span>
<span class="n">prediction</span><span class="x">[:</span><span class="n">p_x</span><span class="x">]</span> <span class="o">=</span> <span class="n">pdf</span><span class="o">.</span><span class="x">(</span><span class="n">prediction</span><span class="x">[:</span><span class="n">d</span><span class="x">],</span> <span class="n">prediction</span><span class="x">[:</span><span class="n">value</span><span class="x">])</span>
<span class="n">prediction</span><span class="x">[:</span><span class="n">p_c</span><span class="x">]</span> <span class="o">=</span> <span class="n">prediction</span><span class="x">[:</span><span class="n">n</span><span class="x">]</span><span class="o">./</span><span class="n">sum</span><span class="x">(</span><span class="n">prediction</span><span class="x">[:</span><span class="n">n</span><span class="x">])</span>
<span class="n">prediction</span> <span class="o">=</span> <span class="n">by</span><span class="x">(</span><span class="n">prediction</span><span class="x">,</span> <span class="x">[:</span><span class="n">sphote</span><span class="x">,</span> <span class="x">:</span><span class="n">sppar</span><span class="x">],</span> <span class="n">df</span> <span class="o">-></span> <span class="n">DataFrame</span><span class="x">(</span>
<span class="n">px</span> <span class="o">=</span> <span class="n">prod</span><span class="x">(</span><span class="n">df</span><span class="x">[:</span><span class="n">p_x</span><span class="x">]),</span>
<span class="n">pc</span> <span class="o">=</span> <span class="n">unique</span><span class="x">(</span><span class="n">df</span><span class="x">[:</span><span class="n">p_c</span><span class="x">]),</span>
<span class="n">pxpc</span> <span class="o">=</span> <span class="n">unique</span><span class="x">(</span><span class="n">df</span><span class="x">[:</span><span class="n">p_c</span><span class="x">])</span><span class="o">*</span><span class="n">prod</span><span class="x">(</span><span class="n">df</span><span class="x">[:</span><span class="n">p_x</span><span class="x">])</span>
<span class="x">))</span>
<span class="k">return</span> <span class="n">prediction</span>
<span class="k">end</span>
</code></pre></div></div>TimothéeClassification is the task where, knowing some values or measurements of a thing, we decide which category we should put this thing into. People that are more serious about their nomenclature than I am would talk about labels, instances, and features. Since I am updating my lecture notes on the technique, I will share an explanation and an example using parasite data.Notes on the Gillespie algorithm - part 22018-01-08T00:00:00+00:002018-01-08T00:00:00+00:00http://armchairecology.blog/notes-gillespie-part-2<p>In the <a href="/notes-gillespie-part-1/">previous post</a>, I collected some notes on the Gillespie
algorithm, and highlighted one difficulty: it is not always clear how to setup
the reaction rate, because some ecological models are setup in a way where the
population sizes are not expressed in units that make a lot of sense. In this
post, I will go through one solution I found, and show an example of a model
with adaptation.</p>
<!--more-->
<p>Let’s start with a simple model of logistic growth with competition:</p>
<script type="math/tex; mode=display">\frac{\dot N}{N} = r\left(1-\frac{N}{K}\right)</script>
<p>To implement mutation in this model, we may want to count the offspring, which
is not obvious since this model does not really have a component associated to
mortality. We will just simplify and assume that $rN$ offsprings are produced
(since any decrease in growth rate is due to mortality through competition).
Then the time until the next mutation is drawn from the exponential distribution
of parameter $\lambda = r\times N\times \mu$. If we call the shape of this
distribution $\beta = 1/\lambda$, the expected $\delta_t$ is $\beta$.</p>
<p>So far so good, but because $N$ is not expressed in individuals, it is unclear
(i) what the value of $\delta_t$ means, and (ii) what the value of $\mu$
relative to other parameters should be. There are a number of ways to decide on
the value of $\mu$ (one of which is to ), but here is the one I picked. First,
let’s see how many timesteps we need to reach the $N^\star = K$ equilibrium.</p>
<p>We can write this up using the <a href="/solving-differential-equations-for-ecological-problems/"><code class="highlighter-rouge">DifferentialEquations</code> package</a> for
<em>julia</em>:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">r</span><span class="x">,</span> <span class="n">K</span> <span class="o">=</span> <span class="mf">1.1</span><span class="x">,</span> <span class="mf">10.0</span>
<span class="n">f</span><span class="x">(</span><span class="n">t</span><span class="x">,</span> <span class="n">u</span><span class="x">)</span> <span class="o">=</span> <span class="n">u</span><span class="o">*</span><span class="n">r</span><span class="o">*</span><span class="x">(</span><span class="mi">1</span><span class="o">-</span><span class="n">u</span><span class="o">/</span><span class="n">K</span><span class="x">)</span>
<span class="n">t</span> <span class="o">=</span> <span class="x">(</span><span class="mf">0.0</span><span class="x">,</span> <span class="mf">20.0</span><span class="x">)</span>
<span class="n">logistic_p</span> <span class="o">=</span> <span class="n">ODEProblem</span><span class="x">(</span><span class="n">f</span><span class="x">,</span> <span class="mf">0.02</span><span class="x">,</span> <span class="n">t</span><span class="x">)</span>
<span class="n">logistic_s</span> <span class="o">=</span> <span class="n">solve</span><span class="x">(</span><span class="n">logistic_p</span><span class="x">,</span> <span class="n">callback</span><span class="o">=</span><span class="n">PositiveDomain</span><span class="x">())</span>
<span class="n">plot</span><span class="x">(</span><span class="n">logistic_s</span><span class="x">)</span>
</code></pre></div></div>
<p><img src="/images/posts/gillespie-logistic.png" alt="" /></p>
<p>The carrying capacity is reached after about 12 timesteps. So assuming we are
still dealing with bacteria, I can assume that one timestep is close to 2 hours
(we know that bacteria reach carrying capacity in the lab after exactly 24
hours, which allows the field of experimental evolution to exist).</p>
<p>One question we can ask at this point is, how long would I like to wait until
the next mutation, and work backward from here. Remember that the expected
$\delta_t = 1 / \lambda$, and $\lambda = r\times N \times \mu$. Let’s say that
at equilibrium, I would like to have a mutation within the next 20 minutes: if a
timestep is 2 hours (120 minutes), then 20 minutes is about 0.16. We can
re-organize</p>
<script type="math/tex; mode=display">\delta_t = \frac{1}{r\times N \times \mu}</script>
<p>to have</p>
<script type="math/tex; mode=display">r\times N\times \mu = \frac{1}{\delta_t} \,,</script>
<p>and finally</p>
<script type="math/tex; mode=display">\mu = \frac{1}{\delta_t\times r \times N} \,.</script>
<p>Since we want to find out what this will be <em>at equilibrium</em>, we can can
substitute $N$ by $N^\star = K$, so that we end up with</p>
<script type="math/tex; mode=display">\mu = \left(\delta_trK\right)^{-1}\,.</script>
<p>Using the values of $r$ and $\alpha$ in the code snippet above, this turns out
to $\mu \approx 0.56$. The unit of this quantity is something like mutation per
unit of population per timestep, but this does not really matter – by deciding
how long we want to wait, we can pick a value of $\mu$ that depends on the
parameters of population dynamics.</p>
<p>But there is another issue with this approach – if we assume that the
equilibrium density is higher than the starting density, then we will have to
wait a lot longer for the <em>first</em> mutation to appear. So instead of using
$N^\star$, we can use $N_0$ to calculate $\mu$. If we want to have to wait 0.16
timesteps to have the first mutation, with an initial population size of $N_0 =
0.01$, then we need to use a value of $\mu \approx 2.9 \times 10^{2}$. Of
course, this will result in a <em>very short</em> $\delta_t$ very soon, so finding a
balance between the number of mutations and the run time is going to matter a
lot.</p>
<p>This is also the time when we need to decide on the relative <em>tempo</em> of
ecological <em>vs.</em> evolutionary dynamics. Do we want to see change within a few
hours, or within a few days? Unless there are good empirical evidence to
calibrate it, $\mu$ is a parameter that is poorly constrained.</p>
<hr />
<p>Let’s wrap this up using code. But first, we will re-write a multi-strain model,
where (i) bacteria grow faster when their trait $x_i$ is optimal, and (ii)
bacteria compete with strains that have similar traits values:</p>
<script type="math/tex; mode=display">\frac{\dot N_i}{N_i} = \omega(x_i, 0, \xi_g)\times r \times \left(1-\frac{\sum_j\omega(x_i, x_j, \xi_c) N_j}{K}\right)\,.</script>
<p>As in previous work on this type of models @WeiHar05, the $\omega$ function is
defined as</p>
<script type="math/tex; mode=display">\omega(x, y, z) = \text{exp}\left(-\frac{(x-y)^2}{2z^2}\right)\,.</script>
<p>We can write both of these as (and loading the same packages as in the <a href="/notes-gillespie-part-1/">previous
post</a>):</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span><span class="nf"> fitness</span><span class="x">(</span><span class="n">x</span><span class="x">,</span><span class="n">y</span><span class="x">,</span><span class="n">breadth</span><span class="x">)</span>
<span class="n">dist</span> <span class="o">=</span> <span class="x">(</span><span class="n">x</span><span class="o">.-</span><span class="n">y</span><span class="err">'</span><span class="x">)</span><span class="o">.^</span><span class="mi">2</span>
<span class="k">return</span> <span class="n">exp</span><span class="o">.</span><span class="x">(</span><span class="o">-</span><span class="n">dist</span><span class="o">./</span><span class="x">(</span><span class="mi">2</span><span class="o">*</span><span class="n">breadth</span><span class="o">^</span><span class="mi">2</span><span class="x">))</span>
<span class="k">end</span>
<span class="k">function</span><span class="nf"> offspring</span><span class="x">(</span><span class="n">t</span><span class="x">,</span> <span class="n">u</span><span class="x">)</span>
<span class="n">gr</span> <span class="o">=</span> <span class="n">fitness</span><span class="x">(</span><span class="n">x</span><span class="x">,</span> <span class="mf">0.0</span><span class="x">,</span> <span class="n">ξ</span><span class="x">)</span><span class="o">*</span><span class="n">r</span>
<span class="k">return</span> <span class="n">gr</span>
<span class="k">end</span>
<span class="k">function</span><span class="nf"> f</span><span class="x">(</span><span class="n">t</span><span class="x">,</span><span class="n">u</span><span class="x">)</span>
<span class="n">competition</span> <span class="o">=</span> <span class="x">(</span><span class="mf">1.0</span><span class="o">-</span><span class="n">vec</span><span class="x">(</span><span class="n">sum</span><span class="x">(</span><span class="n">fitness</span><span class="x">(</span><span class="n">x</span><span class="x">,</span> <span class="n">x</span><span class="x">,</span> <span class="n">adj</span><span class="o">*</span><span class="n">ξ</span><span class="x">)</span><span class="o">.*</span><span class="n">u</span><span class="x">,</span> <span class="mi">1</span><span class="x">))</span><span class="o">/</span><span class="n">K</span><span class="x">)</span>
<span class="k">return</span> <span class="n">u</span><span class="o">.*</span><span class="n">offspring</span><span class="x">(</span><span class="n">t</span><span class="x">,</span><span class="n">u</span><span class="x">)</span><span class="o">.*</span><span class="n">competition</span>
<span class="k">end</span>
</code></pre></div></div>
<p>After a little bit of setting things up, we have a workable code. First, the
parameters:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Initial trait value</span>
<span class="n">x</span> <span class="o">=</span> <span class="x">[</span><span class="o">-</span><span class="mf">0.07</span><span class="x">]</span>
<span class="c"># Initial population size</span>
<span class="n">n</span> <span class="o">=</span> <span class="x">[</span><span class="mf">0.02</span><span class="x">]</span>
<span class="c"># Parameters</span>
<span class="n">r</span> <span class="o">=</span> <span class="mf">1.1</span>
<span class="n">K</span> <span class="o">=</span> <span class="mf">10.0</span>
<span class="n">ξ</span> <span class="o">=</span> <span class="mf">0.05</span> <span class="c"># Tolerance for being away from optimum</span>
<span class="n">adj</span><span class="o">=</span><span class="mf">0.95</span> <span class="c"># Strength of competition vs. being close to optimum</span>
<span class="n">μ</span> <span class="o">=</span> <span class="mf">0.05</span>
<span class="c"># Time</span>
<span class="n">t₀</span> <span class="o">=</span> <span class="mf">0.0</span>
<span class="n">tₙ</span> <span class="o">=</span> <span class="mf">12000.0</span>
<span class="n">δₜ</span> <span class="o">=</span> <span class="n">gillespie</span><span class="x">(</span><span class="n">n</span><span class="o">.*</span><span class="n">offspring</span><span class="x">(</span><span class="n">t₀</span><span class="x">,</span> <span class="n">n</span><span class="x">),</span> <span class="n">μ</span><span class="x">)</span>
</code></pre></div></div>
<p>And second, the main loop (minus the code to keep track of values of traits and
population sizes):</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="n">t₀</span> <span class="o">+</span> <span class="n">δₜ</span> <span class="o"><=</span> <span class="n">tₙ</span>
<span class="c"># Time for integration</span>
<span class="n">t</span> <span class="o">=</span> <span class="x">(</span><span class="n">t₀</span><span class="x">,</span> <span class="n">t₀</span> <span class="o">+</span> <span class="n">δₜ</span><span class="x">)</span>
<span class="k">if</span> <span class="n">t</span><span class="x">[</span><span class="mi">2</span><span class="x">]</span> <span class="o">></span> <span class="n">tₙ</span>
<span class="n">t</span> <span class="o">=</span> <span class="x">(</span><span class="n">t₀</span><span class="x">,</span> <span class="n">tₙ</span><span class="x">)</span>
<span class="k">end</span>
<span class="c"># We define the integration problem</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">ODEProblem</span><span class="x">(</span><span class="n">f</span><span class="x">,</span> <span class="n">n</span><span class="x">,</span> <span class="n">t</span><span class="x">)</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">solve</span><span class="x">(</span><span class="n">p</span><span class="x">,</span> <span class="n">callback</span><span class="o">=</span><span class="n">PositiveDomain</span><span class="x">())</span>
<span class="c"># Update the next starting time + population size</span>
<span class="n">t₀</span> <span class="o">=</span> <span class="n">last</span><span class="x">(</span><span class="n">s</span><span class="o">.</span><span class="n">t</span><span class="x">)</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">last</span><span class="x">(</span><span class="n">s</span><span class="x">)</span>
<span class="c"># Find next mutation</span>
<span class="n">δₜ</span> <span class="o">=</span> <span class="n">gillespie</span><span class="x">(</span><span class="n">n</span><span class="o">.*</span><span class="n">offspring</span><span class="x">(</span><span class="n">t₀</span><span class="x">,</span> <span class="n">n</span><span class="x">),</span> <span class="n">μ</span><span class="x">)</span>
<span class="c"># Pick a mutant</span>
<span class="n">to_mutate</span> <span class="o">=</span> <span class="n">sample</span><span class="x">(</span><span class="n">eachindex</span><span class="x">(</span><span class="n">x</span><span class="x">),</span> <span class="n">weights</span><span class="x">(</span><span class="n">n</span><span class="o">.*</span><span class="n">offspring</span><span class="x">(</span><span class="n">t₀</span><span class="x">,</span> <span class="n">n</span><span class="x">)))</span>
<span class="n">push!</span><span class="x">(</span><span class="n">x</span><span class="x">,</span> <span class="n">rand</span><span class="x">(</span><span class="n">Normal</span><span class="x">(</span><span class="n">x</span><span class="x">[</span><span class="n">to_mutate</span><span class="x">],</span> <span class="mf">0.005</span><span class="x">)))</span>
<span class="n">push!</span><span class="x">(</span><span class="n">n</span><span class="x">,</span> <span class="nb">eps</span><span class="x">())</span>
<span class="c"># Remove populations below a threshold</span>
<span class="n">not_extinct</span> <span class="o">=</span> <span class="n">find</span><span class="x">(</span><span class="n">n</span> <span class="o">.>=</span> <span class="nb">eps</span><span class="x">())</span>
<span class="n">n</span> <span class="o">=</span> <span class="n">n</span><span class="x">[</span><span class="n">not_extinct</span><span class="x">]</span>
<span class="n">x</span> <span class="o">=</span> <span class="n">x</span><span class="x">[</span><span class="n">not_extinct</span><span class="x">]</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Running this can take a little while (because there can be up to a thousand
strains, and because there will be many new mutations, and also of course
because this is proof of concept code). But in the end, we will see the
evolutionary trajectory of the population:</p>
<p><img src="/images/posts/gillespie-traitgram.png" alt="Traitgram" class="center-image" /></p>
<p>There is a first separation in two quasi-species after 2500 timesteps (that’s
about 200 days assuming the starting population reaches $K$ in 24 hours), then
one of these clusters further divides at 6500 timesteps. We can look at the
distribution of trait values at the final timestep - it shows a main cluster of
traits close to the optimum, and two clusters away from the optimum that
presumably offset the loss of performance by being less exposed to competition.</p>
<p><img src="/images/posts/gillespie-histogram.png" alt="Histogram" class="center-image" /></p>
<p>This is not breaking any new grounds in adaptive dynamics, but at least the code
works!</p>
<hr />
<p>To summarize, picking the right value of $\mu$ is important, because this will
regulate the relative rythm of ecological and evolutionary processes. When we
are lucky enough to be able to exactly represent birth and death, there are very
fast versions of this algorithm available [@MatHas12].</p>
<p>Some comon models for population dynamics have the strange tendency of not being
expressed in units of individuals, so some tweaks may be necessary to find a
suitable value of $\mu$. Without revisiting the old discussion about the ability
to calibrate models on empirical data (which @GetMar17
covered extremely well in a recent paper), I think this is a nice illustration
of the issues that come from of ecologists being a little carefree about the
units in which our models are expressed.</p>TimothéeIn the previous post, I collected some notes on the Gillespie algorithm, and highlighted one difficulty: it is not always clear how to setup the reaction rate, because some ecological models are setup in a way where the population sizes are not expressed in units that make a lot of sense. In this post, I will go through one solution I found, and show an example of a model with adaptation.Notes on the Gillespie algorithm - part 12018-01-01T00:00:00+00:002018-01-01T00:00:00+00:00http://armchairecology.blog/notes-gillespie-part-1<p>These past weeks, I was working on things related to mutations and whatnot. In
the course of exploratory work, I had to revisit some things I last read about
during my PhD: how do we decide when a mutation will happen, and who will
mutate? This post is an attempt at organizing some notes I took about the
Gillespie algorithm.</p>
<!--more-->
<p>The @Gil77 algorithm is a way to calculate how long in the future ($\delta_t$) a
new event will take place, given the number of possible events ($k$), and the
rate at which the event we are looking for happens ($\mu$). With the example of
mutations, this becomes relatively straightforward: $k$ is the number of
offsprings, and $\mu$ is the probability that a given offspring has a mutation
in the trait of interest. The product of $k$ and $\mu$ is the <em>rate</em> ($r$). If
there are several types of events, each with their own rate, then $r = \sum_i
k_i \mu_i$.</p>
<p>In the Gillespie algorithm, the next timestep $\delta_t$ is drawn at random from
an exponential distribution. We want the value of $\delta_t$ to become shorter
when $k$ or $\mu$ increase (<em>i.e.</em> more potential events or a higher chance of
each event happening). One property of the exponential distribution is that
$\text{E}[X] = 1/\lambda$, where $\lambda$ is the parameter of the distribution.</p>
<p>If we assume that each offspring will have a 20% chance of mutation ($k=1$, $\mu =
0.2$, $r=0.2$), then we can expect to have a mutation every five generations
($E[X]=1/r=5$). If we double the number of offsprings ($k=2$, $r=0.4$), then we
should expect to have a mutation every 2.5 generations: $E[X]=1/r=2.5$. In
short, when we know the rate $r$, we draw $\delta_t$ in an exponential
distribution of parameter $\lambda = r$. Depending the software used, the
exponential distribution function will use either the actual parameter
$\lambda$, or the <em>scale</em> ($1/\lambda$). Be careful.</p>
<p>Finding $\delta_t$ is the first step. When there are multiple populations
involved, the next question is to decide <em>which</em> will mutate. The Gillespie
algorithm assumes that all events are completely random and completely
independant, and so all we need to do is to draw an event to happen this
timestep. Assuming we have several populations with their own $k_i$ (and the
same mutation probability $\mu$), the probability of every population mutating
is</p>
<script type="math/tex; mode=display">p_i = \frac{k_i\mu}{\sum_j k_j\mu}</script>
<p>We can sample a population $i$ to mutate, by applying the weights $p_i$.</p>
<hr />
<p><strong>I guess it’s time for an illustration!</strong> Because setting up the code for a
proper example with mutations is a little bit overwhelming, I will use
something simpler: the growth of two populations of bacteria. The <em>event</em> we
want to see happen is a cellular division, and it happens at a rate $\mu$
roughly equal to the number of divisions per hour (now is a good time to
mention that the values of <em>everything</em> in the Gillespie algorithm are
dependent on the units in which the model is expressed). This example is not
the most biologically appropriate (bacterial division happens after the cell
has grown enough, and so events are not random), it will do for an overview.</p>
<p>If a bacteria doubles on average every 20 minutes, then its rate of doubling is
$3 \text{hr}^{-1}$. Starting from a single cell at $t_0=0.0$, we expect to have
a division (and therefore two cells) at $t_1 = t_0+\delta_t \approx 20
\text{min}$. The next division, at which point we will have three cells, will
take place at $t_2 = t_1 + \delta_t \approx 30 \text{min}$ – at $t_1$, we have
$k=2$, $\mu=3$, and therefore $r=6$, which means that $\delta_t$ is $1/r=1/6$
hours. The event at $t_1$ is the division of the initial cell. The event at
$t_2$ is the division of <em>either</em> daughter cells. We then expect to have a
second doubling of the population when there are four cells, which happens at
$t_2+\delta_t$, where $\delta_t$ is $1/9$ – this is approximately 7 minutes.
The population will double first after 20 minutes, then after another 17 minutes
have passed.</p>
<p>Let’s write this up (in <code class="highlighter-rouge">Julia</code>). We will need a few packages:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">using</span> <span class="n">Distributions</span>
<span class="n">using</span> <span class="n">StatsBase</span>
<span class="n">using</span> <span class="n">StatPlots</span>
</code></pre></div></div>
<p>Next, let’s write a function to calculate $\delta_t$:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span><span class="nf"> gillespie</span><span class="x">(</span><span class="n">k</span><span class="x">,</span> <span class="n">μ</span><span class="x">)</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">sum</span><span class="x">(</span><span class="n">k</span><span class="o">.*</span><span class="n">μ</span><span class="x">)</span>
<span class="k">return</span> <span class="n">rand</span><span class="x">(</span><span class="n">Exponential</span><span class="x">(</span><span class="mi">1</span><span class="o">/</span><span class="n">r</span><span class="x">))</span>
<span class="k">end</span>
</code></pre></div></div>
<p>And a function to simulate the growth of populations:</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span><span class="nf"> simulate</span><span class="x">(</span><span class="n">n</span><span class="x">,</span> <span class="n">g</span><span class="x">;</span> <span class="n">nmax</span><span class="o">=</span><span class="mi">1000</span><span class="x">)</span>
<span class="c"># Will store the time of events</span>
<span class="n">t</span> <span class="o">=</span> <span class="x">[</span><span class="mf">0.0</span><span class="x">]</span>
<span class="c"># Will store the number of individuals</span>
<span class="n">N</span> <span class="o">=</span> <span class="x">[</span><span class="n">copy</span><span class="x">(</span><span class="n">n</span><span class="x">)]</span>
<span class="k">while</span> <span class="x">(</span><span class="n">sum</span><span class="x">(</span><span class="n">last</span><span class="x">(</span><span class="n">N</span><span class="x">))</span> <span class="o"><</span> <span class="n">nmax</span><span class="x">)</span>
<span class="c"># When is the next step?</span>
<span class="n">δt</span> <span class="o">=</span> <span class="n">gillespie</span><span class="x">(</span><span class="n">n</span><span class="x">,</span> <span class="n">g</span><span class="x">)</span>
<span class="c"># Which population should have a division?</span>
<span class="n">double</span> <span class="o">=</span> <span class="n">sample</span><span class="x">(</span><span class="mi">1</span><span class="x">:</span><span class="n">length</span><span class="x">(</span><span class="n">n</span><span class="x">),</span> <span class="n">weights</span><span class="x">(</span><span class="n">n</span><span class="o">.*</span><span class="n">g</span><span class="x">))</span>
<span class="c"># We increase the size of the dividing population, and push!</span>
<span class="n">n</span><span class="x">[</span><span class="n">double</span><span class="x">]</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="n">push!</span><span class="x">(</span><span class="n">N</span><span class="x">,</span> <span class="n">copy</span><span class="x">(</span><span class="n">n</span><span class="x">))</span>
<span class="n">push!</span><span class="x">(</span><span class="n">t</span><span class="x">,</span> <span class="n">last</span><span class="x">(</span><span class="n">t</span><span class="x">)</span><span class="o">+</span><span class="n">δt</span><span class="x">)</span>
<span class="k">end</span>
<span class="k">return</span> <span class="x">(</span><span class="n">t</span><span class="x">,</span> <span class="n">N</span><span class="x">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>Let’s test our little prediction that the population will have doubled twice
($n=4$) after 37 minutes (even with the awfuly non-optimal code, this all runs
within a second):</p>
<div class="language-julia highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">time_until_4</span> <span class="o">=</span> <span class="x">[</span><span class="n">last</span><span class="x">(</span><span class="n">simulate</span><span class="x">([</span><span class="mi">1</span><span class="x">],</span> <span class="x">[</span><span class="mi">3</span><span class="x">],</span> <span class="n">nmax</span><span class="o">=</span><span class="mi">4</span><span class="x">)[</span><span class="mi">1</span><span class="x">])</span><span class="o">*</span><span class="mi">60</span> <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="x">:</span><span class="mi">200000</span><span class="x">]</span>
<span class="n">histogram</span><span class="x">(</span><span class="n">time_until_4</span><span class="x">)</span>
</code></pre></div></div>
<p><img src="/images/posts/gillespie_sampling.png" alt="Gillespie sampling" class="center-image" /></p>
<p>The vertical line is 37 minutes (the average time in this run was 36.9999…
minutes).</p>
<hr />
<p>Let’s summarize – the Gillespie algorithm works by using the number of possible
events, and the rate at which events happens, to randomly generate the time
until the next event. Events are picked with a probability relative to their
rate, and are assumed to be independant and entirely random.</p>
<p>One of the issues I ran into when using this algorithm is that the rate of
events must be expressed in a unit that is <em>right</em> with regard to both the
population sizes <em>and</em> the time. In a surprising number of ecological models,
both of these quantities are dimensionless. For example, in the case of
mutations, the $\mu$ parameter is the rate of mutation per offspring; but in
(for example), the prey-predator Lotka-Volterra model, the number of offspring
of a predator $y$ consuming a prey $x$ at rate $\delta$ is <em>not</em> simply
$\delta\times x\times y$ (or rather it is, but this is not a quantity expressed
in number of individuals). This can become problematic when we need to be
relatively confident in the fact that the tempo of evolutionary changes matches
the tempo of population dynamics. But this is a story <a href="/notes-gillespie-part-2/">for another
day</a>…</p>TimothéeThese past weeks, I was working on things related to mutations and whatnot. In the course of exploratory work, I had to revisit some things I last read about during my PhD: how do we decide when a mutation will happen, and who will mutate? This post is an attempt at organizing some notes I took about the Gillespie algorithm.Live mathematical modeling2017-12-04T08:30:33+00:002017-12-04T08:30:33+00:00http://armchairecology.blog/live-mathematical-modeling<p>One of my more vivid memories of my graduate studies is a lecture on evolutionary models. Not especially because of the content (something about mutations, maybe?), but because of <em>how</em> it was delivered. Instead of starting with a question, and then showing the model, and then solving it, the beginning of the lecture was “Here’s today’s topic, what question do you want to explore?”. After a (short) brainstorming, we settled on a topic.</p>
<!--more-->
<p>This is when the fun started. As a class, we started discussing ways to represent the relevant mechanisms as equations, and then (using a blackboard), the lecturer assembled the model. This was a transformative moment for me (as I had no previous training in modeling): models are not things you pick from a giant library of models. They are things you build to study a specific question. Modeling is <a href="http://armchairecology.blog/2017/03/31/ecological-modeling-is-world-building/" target="_blank" rel="noopener">world building</a>, and this was the first time I realized this.</p>
<p>There are (I think) two ways to introduce modeling in ecology. The first is “Here is thing called the Lotka-Volterra equations for predator prey dynamics”, and discuss them. The second is this “live modeling” approach, where the question comes first, and we build the model around it. We may end up with one of the “canonical” models, or with something different. The second approach is very close to live coding, <em>i.e.</em> writing code as you go along, and thinking out loud about it, to help learners understand the process of <em>creating</em> something.</p>
<p>This live modeling approach also explores constructing the model. While performing the actual mathematical analysis, or the simulations, can be difficult (and rewarding), these involve different skills than coming up with the right formulation of <em>everything</em> in the model. Looking back at some of my modeling experiences, I figure I spent more time building the model, than solving it. In part, this is because we have numerical recipes to solve a lot of problems. But also, how to come up with the model is never really taught.</p>
<p>This winter, I will teach a new class (somewhat awkwardly named “<em>Computational biology & modeling</em>“), where we’ll explore the space between purely mathematical models and their implementation. A lot of this class will involve building toy models to play with, and I will use this live modeling approach as well as exploring some well established models. My feeling at the moment is that being able to build a model is a skill that is transferable to scientific activities that are not mathematical in nature: it forces one to think in terms of abstraction, to break down a problem in manageable components and deeply think about their behavior, and frame all of this in the “big picture”. This is likely more important than being able to remember the Routh–Hurwitz stability criteria.</p>TimothéeOne of my more vivid memories of my graduate studies is a lecture on evolutionary models. Not especially because of the content (something about mutations, maybe?), but because of how it was delivered. Instead of starting with a question, and then showing the model, and then solving it, the beginning of the lecture was “Here’s today’s topic, what question do you want to explore?”. After a (short) brainstorming, we settled on a topic.