schedule2019-01-09

眺めていられる遺伝的浮動(genetic draft)のシミュレーション|JavaScript

私は書いたコードのログが流れてるところをぼーっと眺めるのが好きです。 シミュレーション途中のグラフや画像が変わるのも大好きです。

そんな訳で、昔学んだ遺伝学から遺伝的浮動のシミュレーションを自分で作ったら、好きなだけぼーっとできるのではないかと思い立ちます。 是非、眺めてやって下さい。

ブラウザはChromeを推奨!

遺伝的浮動 (genetic draft)

このブログを見にくる方々はエンジニアが多いと思うので、簡単にですが説明します。

遺伝子が変異しても生物の生存に有利でも不利でもないものがあります。 集団の中でその変異型の遺伝子を持つ割合を遺伝子頻度と言います。 **遺伝的浮動(genetic draft)**とは、ある集団の持つ遺伝子の頻度が世代を経て偶発的に変動する様です。

シミュレーションでは、変異型の遺伝子が生存や生殖成功率に関して何の影響もないと仮定しています。 次の世代の遺伝子頻度は、親世代から無作為に選ばれた遺伝子を引継ぐとして算出しています。

詳しくは、Introduction to population genetics simulation を参照して下さい。 以下のシミュレーションもこちらを参考に作成しました。

シミュレーション

とりあえず、眺めて見ましょう。

一番目のグラフが遺伝子頻度(frequency)をあわらします。 0世代目は50%から始めて、各世代(generations)の頻度の変化を計算しています。 同時に10個ずつ計算。

集団サイズNは300にしてあります。

下の2つは、その集団で遺伝子が完全に広がるまでの世代数(Time until fixation)と遺伝子が集団から消失するまでの世代数(Time until lost)をカウントしています。 試行回数を多く取ると二項分布になります。 ニョキニョキしているところをみながら気長に待つと、段々とそれっぽい分布があるような気がしてきます。

ソースコード

集団サイズや初期の頻度を変えるとまた違った分布になります。

グラフはamCharts 4で書いています。

設定もいじれるように拡張中です。。。

html

<div id="chartdiv" class="chart"></div>
<div id="chartdivfixation" class="chart-harf"></div>
<div id="chartdivlost" class="chart-harf"></div>

<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/material.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>
<script src="/images/posts/57/genetic_draft.js"></script>


<style>
.chart {
  width: 100%;
  height: 400px;
}
.chart-harf {
  width: 100%;
  height: 200px;
}
</style>

javascript

{
  window.onload = function () {
    ini();
  }

  var size = 300;
  var derived = 150;

  var GEN_MAX = 500;
  var count_lost_chart = null;
  var count_fixation_chart = null;
  var frequecy_chart = null;
  var draft_num = 0;
  var parallel_num = 10;
  var interval_msec = 10;

  function ini(){
    // グラフ描画
    am4core.useTheme(am4themes_animated);
    frequecy_chart = createFrequencyChart();
    count_lost_chart = createCounter("lost");
    count_fixation_chart = createCounter("fixation");
  }

  function createFrequencyChart() {
    var chart = am4core.create("chartdiv", am4charts.XYChart);

    // X軸
    var valueAxisX = chart.xAxes.push(new am4charts.ValueAxis());
    valueAxisX.title.text = 'Generations';
    valueAxisX.renderer.minGridDistance = 50;
    // スケール固定
    valueAxisX.min = 0;
    valueAxisX.max = GEN_MAX - 1;

    var valueAxisY = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxisY.title.text = 'frequecy';
    // スケール固定
    valueAxisY.min = 0;
    valueAxisY.max = 1;


    for (let n = 1; n <= parallel_num; n++) {
      let series = createSeries(chart);
      addData(series);
    }

    chart.cursor = new am4charts.XYCursor();
    chart.cursor.xAxis = valueAxisX;

    return chart;
  }

  function createCounter(id) {
    var chart = am4core.create(`chartdiv${id}`, am4charts.XYChart);
    chart.data = initCounterData(GEN_MAX);

    // X軸
    var categoryAxis = chart.xAxes.push(new am4charts.ValueAxis());
    // categoryAxis.title.text = 'Generations';
    categoryAxis.renderer.minGridDistance = 50;

    var valueAxisY = chart.yAxes.push(new am4charts.ValueAxis());
    valueAxisY.title.text = `Time until ${id}`;


    // Create series
    var series = chart.series.push(new am4charts.StepLineSeries());
    series.dataFields.valueY = "count";
    series.dataFields.valueX = "generation";
    series.tooltipText = "{valueY}";
    series.fillOpacity = 0.1;
    series.defaultState.transitionDuration = 10;
    series.hiddenState.transitionDuration = 10;

    chart.cursor = new am4charts.XYCursor();
    chart.cursor.xAxis = categoryAxis;

    
    return chart;
  }

  function initCounterData(gen_max) {
    var data = [];
    for (var i = 0; i < gen_max; i += 10) {
      data.push({ generation: i, count: 0 });
    }
    return data;
  }

  function createSeries(chart) {

    // Create series
    var series = chart.series.push(new am4charts.LineSeries());
    series.dataFields.valueY = "frequency";
    series.dataFields.valueX = "generation";
    series.tooltipText = "{valueY}";
    series.defaultState.transitionDuration = 10;
    series.hiddenState.transitionDuration = 400;

    return series;
  }

  function addData(series) {
    var gen = 0;
    var p = derived / size;
    var data = [{ generation: gen, frequency: p }];

    series.data = data;

    var calc = function () {
      p = calcNextGeneration(p, size);
      series.addData({ generation: gen++, frequency: p }, 0); // 第二引数はpush
      if (gen > GEN_MAX) {
        return true;
      }
      else if (p == 0) {
        // gene lost
        count_lost_chart.data[Math.floor(gen / 10)].count++;
        count_lost_chart.data = count_lost_chart.data;
        return true;
      }
      else if (p == 1) {
        // gene fixation
        count_fixation_chart.data[Math.floor(gen / 10)].count++;
        count_fixation_chart.data = count_fixation_chart.data;
        return true;
      }
      return false;
    }

    // add data
    var interval;
    function startInterval(calc) {
      interval = setInterval(function () {
        var res = calc();
        if (res) {
          clearInterval(interval);
          addData(series);
          console.log(draft_num);
        }
      }, interval_msec);
    }

    startInterval(calc);
  }

  function calcNextGeneration(p, size) {
    var derived = 0;
    for (let i = 0; i < size; i++) {
      if (Math.random() < p) {
        derived++;
      }
    }
    return derived / size;
  }

}

JavaScriptを初めて学ぶ方へのオススメ! 実際にプログラミングを書いて覚えられるので、オンラインでの学習と合わせて読み進めると理解が深まると思います。