# Notebook from mguo123/pan_omics
Path: notebooks/4B_mg_get_unique_hichip_loops.ipynb

07/28/2020

get unique hichip loops and anchors per tissue save as bedfile

also get differential atac files (sherlock and write to bed)_____no_output_____

import os, glob
import pandas as pd
import numpy as np
import pybedtools
import matplotlib.pyplot as plt
import re
import seaborn as sns_____no_output_____loop_files = glob.glob('../data/interim/merged/loops/*.loops.csv')
anchor_annon_dir = '../data/interim/annon/anchor_atac'
anchor_annon_files = glob.glob('../data/interim/annon/anchor_atac/*_annon.bed')
promoter_annon_dir = '../data/interim/annon/promoter_anchors/'
promoter_annon_files = glob.glob('../data/interim/annon/promoter_anchors/promoter_*_annon.bed')
# anchor_annon_files
anchor_dir = '../data/interim/merged/anchors/'
anchor_files = glob.glob('../data/interim/merged/anchors/*.anchors.csv')_____no_output_____normal_tissues = ['Airway','Astrocytes','Bladder','Colon','Esophageal',
'GDSD0',
'GDSD3',
'GDSD6',
'GM12878',
'HMEC',
'Melanocytes',
'Ovarian',
'Pancreas',
'Prostate',
'Renal',
'Thyroid',
'Uterine']

_____no_output_____

# 1. Unique info

for each tissue (split normal and all) get following stats
- num unique loops
- num unique anchors
- num common loops
- num common anchors
- num loops
- num anchors

summative stats (across all tissues)
    - total number of distinct anchors
    - total number of distinct loops_____no_output_____

save_dir = '../data/processed/fig2_hichip/unique'
if not os.path.exists(save_dir):
    os.makedirs(save_dir)_____no_output_____%%time
# first get set of all loops
# all_loops, all_loops_normal = set(),set()
# all_anchors, all_anchors_normal = set(),set()

tissues = []
loops_all_df = pd.DataFrame(columns=['loop_name','tissue'])
anchors_all_df = pd.DataFrame(columns=['anchors','tissue'])

# tissue_info_dict = {}
for loop_file in sorted(loop_files):
    loop_filename = os.path.basename(loop_file)
    tissue = loop_filename.split('.')[0]

    #### NORMAL only####
    if tissue not in normal_tissues:
        continue
    print(tissue)
    tissues.append(tissue)
    anchor_file = os.path.join(anchor_dir, tissue+'.anchors.csv')
    loop_df = pd.read_csv(loop_file, index_col=0)
    loop_df['loop_name'] = loop_df.apply(lambda row: '::'.join(sorted([row.source,row.target])),axis=1)
    loop_df['tissue'] = tissue

    anchor_df = pd.read_csv(anchor_file, index_col=0)
    anchor_df['tissue'] = tissue

    loops_all_df = pd.concat([loops_all_df, loop_df[['loop_name', 'tissue']]])
    anchors_all_df = pd.concat([anchors_all_df, anchor_df[['anchors', 'tissue']]])
    print(tissue, 'done')

#     tissue_info_dict[tissue] = [loop_df,anchor_df,anchor_to_count_dict]

#     all_loops  = all_loops.union(set(loop_df.loop_name))
#     all_anchors  = all_anchors.union(set(anchor_df.anchors))

#     if tissue in normal_tissues:
#         all_loops_normal = all_loops_normal.union(set(loop_df.loop_name))
#         all_anchors_normal = all_anchors_normal.union(set(anchor_df.anchors))
    Airway
Airway done
Astrocytes
Astrocytes done
Bladder
Bladder done
Colon
Colon done
Esophageal
Esophageal done
GDSD0
GDSD0 done
GDSD3
GDSD3 done
GDSD6
GDSD6 done
GM12878
GM12878 done
HMEC
HMEC done
Melanocytes
Melanocytes done
Ovarian
Ovarian done
Pancreas
Pancreas done
Prostate
Prostate done
Renal
Renal done
Thyroid
Thyroid done
Uterine
Uterine done
CPU times: user 1min 46s, sys: 1.14 s, total: 1min 47s
Wall time: 1min 19s
all_loops = loops_all_df.loop_name.unique()
all_anchors = anchors_all_df.anchors.unique()
_____no_output_____# get loop/anchor by tissue binary matrix, 1 or 0 depending on whether loop/anchor exists in tissue
loops_all_df['count']=1
loop_counts = loops_all_df.pivot_table(index='loop_name', columns='tissue',values='count', fill_value=0)
loop_counts['num_tissues'] = loop_counts.sum(axis=1)

anchors_all_df['count']=1
anchor_counts = anchors_all_df.pivot_table(index='anchors', columns='tissue',values='count', fill_value=0)
anchor_counts['num_tissues'] = anchor_counts.sum(axis=1)
_____no_output_____# unique loop counts
loop_counts[loop_counts.num_tissues==1].sum()_____no_output_____# unique anchor counts
anchor_counts[anchor_counts.num_tissues==1].sum()_____no_output_____unique_loops_df = loop_counts[loop_counts.num_tissues==1]
unique_anchors_df = anchor_counts[anchor_counts.num_tissues==1]_____no_output_____for tissue in unique_loops_df.columns:
    print(tissue)
    loop_df = pd.Series(unique_loops_df[tissue][unique_loops_df[tissue]==1].index).str.split('[:_]',expand=True)
    loop_df.drop(3,axis=1).to_csv(os.path.join(save_dir, tissue+'_loops.bed'),header=None,index=None, sep='\t')
    print(os.path.join(save_dir, tissue+'_loops.bed'))Airway
../data/processed/fig2_hichip/unique/Airway_loops.bed
Astrocytes
../data/processed/fig2_hichip/unique/Astrocytes_loops.bed
Bladder
../data/processed/fig2_hichip/unique/Bladder_loops.bed
Colon
../data/processed/fig2_hichip/unique/Colon_loops.bed
Esophageal
../data/processed/fig2_hichip/unique/Esophageal_loops.bed
GDSD0
../data/processed/fig2_hichip/unique/GDSD0_loops.bed
GDSD3
../data/processed/fig2_hichip/unique/GDSD3_loops.bed
GDSD6
../data/processed/fig2_hichip/unique/GDSD6_loops.bed
GM12878
../data/processed/fig2_hichip/unique/GM12878_loops.bed
HMEC
../data/processed/fig2_hichip/unique/HMEC_loops.bed
Melanocytes
../data/processed/fig2_hichip/unique/Melanocytes_loops.bed
Ovarian
../data/processed/fig2_hichip/unique/Ovarian_loops.bed
Pancreas
../data/processed/fig2_hichip/unique/Pancreas_loops.bed
Prostate
../data/processed/fig2_hichip/unique/Prostate_loops.bed
Renal
../data/processed/fig2_hichip/unique/Renal_loops.bed
Thyroid
../data/processed/fig2_hichip/unique/Thyroid_loops.bed
Uterine
../data/processed/fig2_hichip/unique/Uterine_loops.bed
num_tissues
../data/processed/fig2_hichip/unique/num_tissues_loops.bed
for tissue in unique_loops_df.columns:
    print(tissue)
    anchor_df = pd.Series(unique_anchors_df[tissue][unique_anchors_df[tissue]==1].index).str.split('[:_]',expand=True)
    pybedtools.BedTool.from_dataframe(anchor_df).sort().saveas(os.path.join(save_dir, tissue+'_anchors.bed'))
    print(os.path.join(save_dir, tissue+'_anchors.bed'))Airway
../data/processed/fig2_hichip/unique/Airway_anchors.bed
Astrocytes
../data/processed/fig2_hichip/unique/Astrocytes_anchors.bed
Bladder
../data/processed/fig2_hichip/unique/Bladder_anchors.bed
Colon
../data/processed/fig2_hichip/unique/Colon_anchors.bed
Esophageal
../data/processed/fig2_hichip/unique/Esophageal_anchors.bed
GDSD0
../data/processed/fig2_hichip/unique/GDSD0_anchors.bed
GDSD3
../data/processed/fig2_hichip/unique/GDSD3_anchors.bed
GDSD6
../data/processed/fig2_hichip/unique/GDSD6_anchors.bed
GM12878
../data/processed/fig2_hichip/unique/GM12878_anchors.bed
HMEC
../data/processed/fig2_hichip/unique/HMEC_anchors.bed
Melanocytes
../data/processed/fig2_hichip/unique/Melanocytes_anchors.bed
Ovarian
../data/processed/fig2_hichip/unique/Ovarian_anchors.bed
Pancreas
../data/processed/fig2_hichip/unique/Pancreas_anchors.bed
Prostate
../data/processed/fig2_hichip/unique/Prostate_anchors.bed
Renal
../data/processed/fig2_hichip/unique/Renal_anchors.bed
Thyroid
../data/processed/fig2_hichip/unique/Thyroid_anchors.bed
Uterine
../data/processed/fig2_hichip/unique/Uterine_anchors.bed
num_tissues
../data/processed/fig2_hichip/unique/num_tissues_anchors.bed
all_counts_results_df = pd.DataFrame()
all_counts_results_df['num_loops'] = loop_counts.sum(axis=0)
all_counts_results_df['num_unique_loops'] = loop_counts[loop_counts.num_tissues==1].sum()
all_counts_results_df['num_common_loops'] = loop_counts[loop_counts.num_tissues==(loop_counts.shape[1]-1)].sum()
all_counts_results_df['num_anchors'] = anchor_counts.sum(axis=0)
all_counts_results_df['num_unique_anchors'] = anchor_counts[anchor_counts.num_tissues==1].sum()
all_counts_results_df['num_common_anchors'] = anchor_counts[anchor_counts.num_tissues==(anchor_counts.shape[1]-1)].sum()
all_counts_results_df.drop('num_tissues',inplace=True)_____no_output_____# all_counts_results_df.to_csv(os.path.join(save_dir, 'all_counts_results_df.csv'))
_____no_output_____
print('Total # of loops all tissues: ',all_counts_results_df.num_loops.sum())
print('Total # unique loops all tissues: ',all_counts_results_df.num_unique_loops.sum())
print('Total # common loops all tissues: ',all_counts_results_df.num_common_loops[0])

print('Total # of anchors all samples: ',all_counts_results_df.num_anchors.sum())
print('Total # unique anchors all tissues: ',all_counts_results_df.num_unique_anchors.sum())
print('Total # common anchors all tissues: ',all_counts_results_df.num_common_anchors[0])

print()
Total # of loops all tissues:  13249551
Total # unique loops all tissues:  1235518
Total # common loops all tissues:  1529
Total # of anchors all samples:  4646665
Total # unique anchors all tissues:  49665
Total # common anchors all tissues:  5904

Total # of loops normal tissues :  3180706
Total # unique loops normal tissues:  602193
Total # common loops normal tissues:  3567
Total # of anchors all samples normal:  1616223
Total # unique anchors normal tissues:  74240
Total # common anchors normal tissues:  9916

# 2. differential atac

on local computer

```
cd /Users/mguo123/Google Drive/1_khavari/omics_project-LD/pan_omics/data/processed/fig2_hichip/unique_atac

scp mguo123@login.sherlock.stanford.edu:/oak/stanford/groups/khavari/users/lkhd/project/3D/ATAC/hiseq/epithelia/ggr-project/by_celltype/*differential.txt .

```
then manually rename a couple
```
mv epithelia.HMEC.00.differential.txt epithelia.HMEC.differential.txt
mv epithelia.GDS.D0.differential.txt epithelia.GDSD0.differential.txt
mv epithelia.GDS.D3.differential.txt epithelia.GDSD3.differential.txt
mv epithelia.GDS.D6.differential.txt epithelia.GDSD6.differential.txt
```
_____no_output_____

save_dir = '../data/processed/fig2_hichip/unique_atac'
if not os.path.exists(save_dir):
    os.makedirs(save_dir)_____no_output_____sorted(glob.glob(os.path.join(save_dir, '*txt')))_____no_output_____for file in sorted(glob.glob(os.path.join(save_dir, '*txt'))):
    tissue = os.path.basename(file).split('.')[1]
    save_path = os.path.join(save_dir, tissue+'_atac_diff.bed')
    df = pd.read_csv(file, header=None, sep='\t')
    print(tissue, df.shape[0])
    df.columns = ['name','score']
    df[['chr','start','stop']] = df.name.str.split('[:-]',expand=True)
    df = df[['chr','start','stop','score']]
    pybedtools.BedTool.from_dataframe(df).sort().saveas(save_path)
#     print(save_path)Airway 23032
Astrocytes 25097
Bladder 27452
Colon 15185
Esophageal 18687
GDSD0 29132
GDSD3 30743
GDSD6 32995
GM12878 41327
HMEC 35742
Melanocytes 28917
Ovarian 13274
Pancreas 15659
Prostate 9180
Renal 20003
Thyroid 15144
Uterine 24126


# Notebook from lyoussifou/EPA1324_open
Path: src/week_3/Assignment 2 model answer.ipynb

# The importance of space
Agent based models are useful when the aggregate system behavior emerges out of local interactions amongst the agents. In the model of the evolution of cooperation, we created a set of agents and let all agents play against all other agents. Basically, we pretended as if all our agents were perfectly mixed. In practice, however, it is much more common that agents only interact with some, but not all, other agents. For example, in models of epidemiology, social interactions are a key factors. Thus, interactions are dependend on your social network. In other situations, our behavior might be based on what we see around us. Phenomena like fashion are at least partly driven by seeing what others are doing and mimicking this behavior. The same is true for many animals. Flocking dynamics as exhibited by starling, or shoaling behavior in fish, can be explained by the animal looking at its neirest neighbors and staying within a given distance of them. In agent based models, anything that structures the interaction amongst agents is typically called a space. This space can be a 2d or 3d space with euclidian distances (as in models of flocking and shoaling), it can also be a grid structure (as we will show below), or it can be a network structure.

MESA comes with several spaces that we can readily use. These are

* **Grid;** an 'excel-like' space with each agent occopying a single grid cell
* **MultiGrid;** like grid, but with more than one agent per grid cell
* **HexGrid;** like grid, but on a hexagonal grid (*e.g.*, the board game Catan) thus changing who your neighbours are
* **ConinuousSpace;** a 2d continous space were agents can occupy any coordinate
* **NetworkGrid;** a network structure were one or more agents occupy a given node.

A key concern when using a none-networked space, is to think carefull about what happens at the edges of the space. In a basic implementation, agents in for example the top left corner has only 2 neighbors, while an agent in the middle has four neighbors. This can give rise to artifacts in the results. Basically, the dynamics at the edges are different from the behavior further away from the edges. It is therefore quite common to use a torus, or donut, shape for the space. In this way, there is no longer any edge and artifacts are thus removed.

# The emergence of cooperation in space
The documentation of MESA on the different spaces is quite limited. Therefore, this assignment is largely a tutorial continuing on the evolution of cooperation.

We make the following changes to the model

* Each agent gets a position, which is an x,y coordinate indicating the grid cell the agent occupies
* The model has a grid, with an agent of random class. We initialize the model with equal probabilities for each type of class
* All agents play against their neighbors. On a grid, neighborhood can be defined in various ways. Below, we use a neighborhood distance of 1, and we do include diagonal neighbors.
* The evolutionary dynamic, after all agents having played, is that each agent compares its scores to its neighbors. It will adopt whichever strategy within its neighborhood performed best.
* Next to using a Grid from MESA, we also use a DataCollector to handle collecting statistics.

Below, I discuss in more detail the code containing the most important modifications
_____no_output_____

from collections import deque, Counter, defaultdict
from enum import Enum
from itertools import combinations
from math import floor

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

from mesa import Model, Agent
from mesa.space import SingleGrid
from mesa.datacollection import DataCollector

class Move(Enum):
    COOPERATE = 1
    DEFECT = 2

class AxelrodAgent(Agent):
    """ An agent with fixed initial wealth."""
    def __init__(self, unique_id, pos, model):
        super().__init__(unique_id, model)
        self.points = 0
        self.pos = pos

    def step(self):
        '''
        the move and any logic for deciding
        on the move goes here

        Returns
        -------
        Move.COOPERATE or Move.DEFECT

        '''
        raise NotImplemetedError

    def receive_payoff(self, payoff, my_move, opponent_move):
        '''

        Parameters
        ----------
        payoff : int
        my_move : {Move.COOPERATE, Move.DEFECT}
        opponements_move : {Move.COOPERATE, Move.DEFECT}

        '''
        self.points += payoff

    def reset(self):
        '''
        called after playing N iterations agains
        another player
        '''
        raise NotImplementedError

class TitForTat(AxelrodAgent):

    def __init__(self, unique_id, pos, model):
        super().__init__(unique_id, pos, model)
        self.opponent_last_move = Move.COOPERATE

    def step(self):
        return self.opponent_last_move

    def receive_payoff(self, payoff, my_move, opponent_move):
        super().receive_payoff(payoff, my_move, opponent_move)
        self.opponent_last_move = opponent_move

    def reset(self):
        self.opponent_last_move = Move.COOPERATE

class ContriteTitForTat(AxelrodAgent):

    def __init__(self, unique_id, pos, model):
        super().__init__(unique_id, pos, model)
        self.opponent_last_two_moves = deque([Move.COOPERATE, Move.COOPERATE], maxlen=2)

    def step(self):
        if (self.opponent_last_two_moves[0] == Move.DEFECT) and\
           (self.opponent_last_two_moves[1] == Move.DEFECT):
            return Move.DEFECT
        else:
            return Move.COOPERATE

    def receive_payoff(self, payoff, my_move, opponent_move):
        super().receive_payoff(payoff, my_move, opponent_move)
        self.opponent_last_two_moves.append(opponent_move)

    def reset(self):
        self.opponent_last_two_moves = deque([Move.COOPERATE, Move.COOPERATE], maxlen=2)

class NoisySpatialEvolutionaryAxelrodModel(Model):

    def __init__(self, N, noise_level=0.01, seed=None,
                 height=20, width=20,):
        super().__init__(seed=seed)
        self.noise_level = noise_level
        self.num_iterations = N
        self.agents = set()

        self.payoff_matrix = {}
        self.payoff_matrix[(Move.COOPERATE, Move.COOPERATE)] = (2, 2)
        self.payoff_matrix[(Move.COOPERATE, Move.DEFECT)] = (0, 3)
        self.payoff_matrix[(Move.DEFECT, Move.COOPERATE)] = (3, 0)
        self.payoff_matrix[(Move.DEFECT, Move.DEFECT)] = (1, 1)

        self.grid = SingleGrid(width, height, torus=True)

        strategies = AxelrodAgent.__subclasses__()
        num_strategies = len(strategies)
        self.agent_id = 0

        for cell in self.grid.coord_iter():
            _, x, y = cell
            pos = (x, y)

            self.agent_id += 1

            strategy_index = int(floor(self.random.random()*num_strategies))

            agent = strategies[strategy_index](self.agent_id, pos, self)
            self.grid.position_agent(agent, (x, y))
            self.agents.add(agent)

        self.datacollector = DataCollector(model_reporters={klass.__name__:klass.__name__
                                                            for klass in strategies})

    def count_agent_types(self):
        counter = Counter()
        for agent in self.agents:
            counter[agent.__class__.__name__] += 1

        for k,v in counter.items():
            setattr(self, k, v)

    def step(self):
        '''Advance the model by one step.'''
        self.count_agent_types()
        self.datacollector.collect(self)

        for (agent_a, x, y) in self.grid.coord_iter():
            for agent_b in self.grid.neighbor_iter((x,y), moore=True):
                for _ in range(self.num_iterations):
                    move_a = agent_a.step()
                    move_b = agent_b.step()

                    #insert noise in movement
                    if self.random.random() < self.noise_level:
                        if move_a == Move.COOPERATE:
                            move_a = Move.DEFECT
                        else:
                            move_a = Move.COOPERATE
                    if self.random.random() < self.noise_level:
                        if move_b == Move.COOPERATE:
                            move_b = Move.DEFECT
                        else:
                            move_b = Move.COOPERATE

                    payoff_a, payoff_b = self.payoff_matrix[(move_a, move_b)]

                    agent_a.receive_payoff(payoff_a, move_a, move_b)
                    agent_b.receive_payoff(payoff_b, move_b, move_a)
                agent_a.reset()
                agent_b.reset()

        # evolution
        # tricky, we need to determine for each grid cell
        # is a change needed, if so, log position, agent, and type to change to
        agents_to_change = []
        for agent_a in self.agents:
            neighborhood = self.grid.iter_neighbors(agent_a.pos, moore=True,
                                                    include_center=True)
            neighborhood = ([n for n in neighborhood])
            neighborhood.sort(key=lambda x:x.points, reverse=True)
            best_strategy = neighborhood[0].__class__
            # if best type of strategy in neighborhood is
            # different from strategy type of agent, we need
            # to change our strategy
            if not isinstance(agent_a, best_strategy):
                agents_to_change.append((agent_a, best_strategy))

        for entry in agents_to_change:
            agent, klass = entry
            self.agents.remove(agent)
            self.grid.remove_agent(agent)

            pos = agent.pos
            self.agent_id += 1

            new_agent = klass(self.agent_id, pos, self)
            self.grid.position_agent(new_agent, pos)
            self.agents.add(new_agent)
_____no_output_____

In the `__init__`, we now instantiate a SingleGrid, with a specified width and height. We set the kwarg torus to True indicating we are using a donut shape grid to avoid edge effects. Next, we fill this grid with random agents of the different types. This can be implemented in various ways. What I do here is using a list with the different classes (*i.e.*, types of strategies). By drawing a random number from a unit interval, multiplying it with the lenght of the list of classes and flooring the resulting number to an integer, I now have a random index into this list with the different classes. Next, I can get the class from the list and instantiate the agent object.

Some minor points with instantiating the agents. First, we give the agent a position, called pos, this is a default attribute assumed by MESA. We also still need a unique ID for the agent, we do this with a simple counter (`self.agent_id`). `self.grid.coord_iter` is a method on the grid. It returns an iterator over the cells in the grid. This iterator returns the agent occupying the cell and the x and y coordinate. Since the first item is `null` because we are filling the grid, we can ignore this. We do this by using the underscore variable name (`_`). This is a python convention.

Once we have instantiated the agent, we place the agent in the grid and add it to our collection of agents. If you look in more detail at the model class, you will see that I use a set for agents, rather than a list. The reason for this is that we are going to remove agents in the evolutionary phase. Removing agents from a list is memory and compute intensive, while it is computationally easy and cheap when we have a set.

```python
self.grid = SingleGrid(width, height, torus=True)

strategies = AxelrodAgent.__subclasses__()
num_strategies = len(strategies)
self.agent_id = 0

for cell in self.grid.coord_iter():
    _, x, y = cell
    pos = (x, y)

    self.agent_id += 1

    strategy_index = int(floor(self.random.random()*num_strategies))

    agent = strategies[strategy_index](self.agent_id, pos, self)
    self.grid.position_agent(agent, (x, y))
    self.agents.add(agent)
```

We also use a DataCollector. This is a default class provided by MESA that can be used for keeping track of relevant statistics. It can store both model level variables as well as agent level variables. Here we are only using model level variables (i.e. attributes of the model). Specifically, we are going to have an attribute on the model for each type of agent strategy (i.e. classes). This attribute is the current count of agents in the grid of the specific type. To implement this, we need to do several things.

1. initialize a data collector instance
2. at every step update the current count of agents of each strategy
3. collect the current counts with the data collector.

For step 1, we set a DataCollector as an attribute. This datacollector needs to know the names of the attributes on the model it needs to collect. So we pass a dict as kwarg to model_reporters. This dict has as key the name by which the variable will be known in the DataCollector. As value, I pass the name of the attribute on the model, but it can also be a function or method which returns a number. Note th at the ``klass`` misspelling is deliberate. The word ``class`` is protected in Python, so you cannot use it as a variable name. It is common practice to use ``klass`` instead in the rare cases were you are having variable refering to a specific class.

```python
self.datacollector = DataCollector(model_reporters={klass.__name__:klass.__name__
                                                    for klass in strategies})
```

For step 2, we need to count at every step the number of agents per strategy type. To help keep track of this, we define a new method, `count_agent_types`. The main magic is the use of `setattr` which is a standard python function for setting attributes to a particular value on a given object. This reason for writing our code this way is that we automatically adapt our attributes to the classes of agents we have, rather than hardcoding the agent classes as attributes on our model. If we now add new classes of agents, we don't need to change the model code itself. There is also a ``getattr`` function, which is used by for example the DataCollector to get the values for the specified attribute names.

```python
def count_agent_types(self):
    counter = Counter()
    for agent in self.agents:
        counter[agent.__class__.__name__] += 1

    for k,v in counter.items():
        setattr(self, k, v)

```

For step 3, we modify the first part of the ``step`` method. We first count the types of agents and next collect this data with the datacollector.
```python
self.count_agent_types()
self.datacollector.collect(self)
```

The remainder of the ``step`` method has also been changed quite substantially. First, We have to change against whom each agent is playing. We do this by iterating over all agents in the model. Next, we use the grid to give us the neighbors of a given agent. By setting the kwarg ``moore`` to ``True``, we indicate that we include also our diagonal neighbors. Next, we play as we did before in the noisy version of the Axelrod model.

```python
for agent_a in self.agents:
    for agent_b in self.grid.neighbor_iter(agent_a.pos, moore=True):
        for _ in range(self.num_iterations):
```

Second, we have to add the evolutionary dynamic. This is a bit tricky. First, we loop again over all agents in the model. We check its neighbors and see which strategy performed best. If this is of a different type (``not isinstance(agent_a, best_strategy)``, we add it to a list of agents that needs to be changed and the type of agent to which it needs to be changed. Once we know all agents that need to be changed, we can make this change.

Making the change is quite straighforward. We remove the agent from the set of agents (`self.agents`) and from the grid. Next we get the position of the agent, we increment our unique ID counter, and create a new agent. This new agent is than added to the grid and to the set of agents.

```python
# evolution
agents_to_change = []
for agent_a in self.agents:
    neighborhood = self.grid.iter_neighbors(agent_a.pos, moore=True,
                                            include_center=True)
    neighborhood = ([n for n in neighborhood])
    neighborhood.sort(key=lambda x:x.points, reverse=True)
    best_strategy = neighborhood[0].__class__
    # if best type of strategy in neighborhood is
    # different from strategy type of agent, we need
    # to change our strategy
    if not isinstance(agent_a, best_strategy):
        agents_to_change.append((agent_a, best_strategy))

for entry in agents_to_change:
    agent, klass = entry
    self.agents.remove(agent)
    self.grid.remove_agent(agent)

    pos = agent.pos
    self.agent_id += 1

    new_agent = klass(self.agent_id, pos, self)
    self.grid.position_agent(new_agent, pos)
    self.agents.add(new_agent)

```

## Assignment 1
Can you explain why we need to first loop over all agents before we are changing a given agent to a different strategy?

it is only after all agents have played against all their neighbors that we know the true total score per agent._____no_output_____## Assignment 2
Add all agents classes (i.e., strategie) from the previous assignment to this model. Note that you might have to update the ``__init__`` method to reflect the new pos keyword argument and attribute. _____no_output_____

class Defector(AxelrodAgent):
    def step(self):
        return Move.DEFECT

    def reset(self):
        pass

class Cooperator(AxelrodAgent):
    def step(self):
        return Move.COOPERATE

    def reset(self):
        pass

class GrimTrigger(AxelrodAgent):
    def __init__(self, unique_id, pos, model):
        super().__init__(unique_id, pos, model)
        self.betrayed = False

    def step(self):
        if self.betrayed:
            return Move.DEFECT
        else:
            return Move.COOPERATE

    def receive_payoff(self, payoff, my_move, opponent_move):
        super().receive_payoff(payoff, my_move, opponent_move)
        if opponent_move == Move.DEFECT:
            self.betrayed = True

    def reset(self):
        self.betrayed = False

class Pavlov(AxelrodAgent):
    def __init__(self, unique_id, pos, model):
        super().__init__(unique_id, pos, model)
        self.opponent_last_move = Move.COOPERATE
        self.my_last_move = Move.COOPERATE

    def step(self):
        if self.opponent_last_move == self.my_last_move:
            my_move = Move.COOPERATE
        else:
            my_move = Move.DEFECT
        return my_move

    def receive_payoff(self, payoff, my_move, opponent_move):
        super().receive_payoff(payoff, my_move, opponent_move)
        self.opponent_last_move = opponent_move
        self.my_last_move = my_move

    def reset(self):
        self.opponent_last_move = Move.COOPERATE
        self.my_last_move = Move.COOPERATE

class Random(AxelrodAgent):

    def step(self):
        if self.random.random() < 0.5:
            return Move.COOPERATE
        else:
            return Move.DEFECT

    def reset(self):
        pass_____no_output_____

## Assignment 3
Run the model for 50 steps, and with 200 rounds of the iterated game. Use the defaults for all other keyword arguments.

Plot the results.

*hint: the DataCollector can return the statistics it has collected as a dataframe, which in turn you can plot directly.*_____no_output_____

model = NoisySpatialEvolutionaryAxelrodModel(200)
for _ in range(50):
    model.step()_____no_output_____model.datacollector.get_model_vars_dataframe().plot()_____no_output_____

This new model is quite a bit noisier than previously. We have a random initialization of the grid and depending on the initial neighborhood, different evolutionary dynamics can happen. On top, we have the noise in game play, and the random agent.

## Assignment 4
Let's explore the model for 10 replications. Run the model 10 times, with 200 rounds of the iterated prisoners dilemma. Run each model for fifty steps. Plot the results for each run.

1. Can you say anything generalizable about the behavioral dynamics of the model?
2. What do you find striking in the results and why?
3. If you compare the results for this spatially explicit version of the Emergence of Cooperation with the none spatially explicit version, what are the most important differences in dynamics. Can you explain why adding local interactions results in these changes? _____no_output_____

for _ in range(10):
    model = NoisySpatialEvolutionaryAxelrodModel(200)
    for _ in range(50):
        model.step()
    model.datacollector.get_model_vars_dataframe().plot()
    plt.show()_____no_output_____


# Notebook from ibrahimsesay/griddb
Path: Copy_of_Week1_Assignment_2_ibrahimsesay_.ipynb

_____no_output_____# [Problem.1] The minimum number of folds over Mt. Fuji

1.   List item
2.   List item

_____no_output_____

Thick0=0.00008
distance=3776

for n in range(1,44):
    thick=Thick0*2**n
    if(thick>=distance):
        break
print("Number of times of folds if exceeds Mt. Fuji is = {}".format(n))Number of times of folds if exceeds Mt. Fuji is = 26

# [Problem. 2] The function corresponding to an arbitrary thickness_____no_output_____

import math

def arbitrary_thickness(distance):
    thickness = 0.00008

    num_of_bends = math.log2(distance/thickness)

    return f'The number of times a paper {distance}m thick bends is : {round(num_of_bends)}'

arbitrary_thickness(20000)_____no_output_____#Number of times to fold a paper to reach the nearest neighboring star, Proxima centauri
thickness(40208000000000)_____no_output_____

# [Problem. 3] Required paper length_____no_output_____

def paper_length(distance):
    thickness = 0.00008

    num_of_bends = math.log2(distance/thickness)

    length = (thickness*math.pi/6)*(2**num_of_bends + 4)*(2**num_of_bends - 1)

    return f'The length of the paper necessary to reach a thickness of {distance}m is: {round(length)}m'
paper_length(100)_____no_output_____#length of paper needed to reach the Moon
paper_length(384400)_____no_output_____#length of paper needed to reach Mt. Fuji from Sierra Leone
paper_length(14235000)_____no_output_____#length of paper needed to reach Alpha Centauri, closest star to the sun
paper_length(41320000000000000000)_____no_output_____


# Notebook from lcerdeira/rapids-single-cell-examples
Path: notebooks/dsci_bmmc_60k_cpu.ipynb

# CPU analysis of single-cell ATAC-seq data from 60K cells

Copyright (c) 2020, NVIDIA CORPORATION.
Licensed under the Apache License, Version 2.0 (the "License") you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License._____no_output_____### Import requirements_____no_output_____

%%time
import numpy as np
import scanpy as sc
import anndata
import matplotlib.pyplot as plt
from collections import OrderedDict

import time
import os, wget

import utils

import warnings
warnings.filterwarnings('ignore', 'Expected ')
warnings.simplefilter('ignore')CPU times: user 2.49 s, sys: 5.82 s, total: 8.31 s
Wall time: 1.42 s

## Set parameters_____no_output_____

# filtering peaks
n_top_peaks = 25000 # Number of peaks to retain

# PCA
n_components = 50 # Number of principal components to compute

# t-SNE
tsne_n_pcs = 20 # Number of principal components to use for t-SNE
n_jobs=32

# KNN
n_neighbors = 10 # Number of nearest neighbors for KNN graph
knn_n_pcs = 50 # Number of principal components to use for finding nearest neighbors

# UMAP
umap_min_dist = 0.4
umap_spread = 1.0

# Differential peaks
n_diff_peaks = 50 # Number of differential peaks to compute for each cluster

# Marker genes
# Gene coordinates are in GRCh37, from grch37.ensembl.org
markers = ['MS4A1', 'LEF1', 'NKG7', 'TREM1', 'GATA1', 'PAX5']
marker_coords = OrderedDict()
marker_coords['MS4A1'] = ['chr11', 60223225, 60238233, 'fwd']
marker_coords['LEF1'] = ['chr4', 108968701, 109090112, 'rev']
marker_coords['NKG7'] = ['chr19', 51874860, 51875969, 'rev']
marker_coords['TREM1'] = ['chr6', 41235664, 41254457, 'rev']
marker_coords['GATA1'] = ['chrX', 48644962, 48652716, 'fwd']
marker_coords['PAX5'] = ['chr9', 36833272, 37034103, 'rev']_____no_output_____start_time = time.time()_____no_output_____

## Input data_____no_output_____

%%time
input_file = "../data/dsci_resting_nonzeropeaks.h5ad"

if not os.path.exists(input_file):
    print('Downloading import file...')
    os.makedirs('../data', exist_ok=True)
    wget.download('https://rapids-single-cell-examples.s3.us-east-2.amazonaws.com/dsci_resting_nonzeropeaks.h5ad',
                  input_file)

adata = anndata.read_h5ad(input_file)
adata_raw = adata.copy()CPU times: user 189 ms, sys: 192 ms, total: 381 ms
Wall time: 380 ms

## Preprocessing_____no_output_____

preprocessing_start = time.time()_____no_output_____

### tf-idf normalization

We use log1p(pseudocount / tf)\*idf, motivated by discussion in [this blog post](http://andrewjohnhill.com/blog/2019/05/06/dimensionality-reduction-for-scatac-data/) as well as our results on various scATAC datasets._____no_output_____

%%time
lognormalized = utils.logtf_idf(adata.X, pseudocount=10**3)
adata.X = lognormalizedCPU times: user 2.45 s, sys: 575 ms, total: 3.03 s
Wall time: 3.03 s

### Frequency-based peak selection_____no_output_____

%%time
adata = utils.filter_peaks(adata, n_top_peaks)
print(adata.X.shape)CPU times: user 388 ms, sys: 82.5 ms, total: 471 ms
Wall time: 470 ms
print("Preprocessing time: %.2fsec" % (time.time() - preprocessing_start))Preprocessing time: 3.53sec

## Cluster & Visualize_____no_output_____### PCA_____no_output_____

%%time
sc.pp.pca(adata, n_comps = n_components)CPU times: user 22min 53s, sys: 1h 24min 58s, total: 1h 47min 51s
Wall time: 2min 29s

### UMAP visualization_____no_output_____

%%time
sc.pp.neighbors(adata, n_neighbors=n_neighbors, n_pcs=knn_n_pcs)CPU times: user 37.4 s, sys: 3.74 s, total: 41.1 s
Wall time: 38.6 s
%%time
sc.tl.umap(adata, min_dist=umap_min_dist, spread=umap_spread)CPU times: user 2min 12s, sys: 1min 6s, total: 3min 19s
Wall time: 38.2 s

### Graph clustering_____no_output_____

%%time
sc.tl.louvain(adata)CPU times: user 6.76 s, sys: 58.1 ms, total: 6.82 s
Wall time: 6.81 s
%%time
sc.tl.leiden(adata)CPU times: user 18.8 s, sys: 85.1 ms, total: 18.8 s
Wall time: 18.8 s
%%time

fig, axs = plt.subplots(nrows = 1, ncols = 3, figsize = (12, 3), dpi=100)

sc.pl.umap(adata, color=['louvain'], ax=axs[0], show=False)
sc.pl.umap(adata, color=['leiden'], ax=axs[1], show=False)
sc.pl.umap(adata, color=['cell_type'], ax=axs[2], show=False)

plt.tight_layout()
plt.show()_____no_output_____

### tSNE visualization_____no_output_____

%%time
sc.tl.tsne(adata, n_pcs=tsne_n_pcs, n_jobs=n_jobs)CPU times: user 15min 17s, sys: 5.27 s, total: 15min 22s
Wall time: 4min 12s
%%time

fig, axs = plt.subplots(nrows = 1, ncols = 3, figsize = (12, 3), dpi=100)

sc.pl.tsne(adata, color=['louvain'], ax=axs[0], show=False)
sc.pl.tsne(adata, color=['leiden'], ax=axs[1], show=False)
sc.pl.tsne(adata, color=['cell_type'], ax=axs[2], show=False)

plt.tight_layout()
plt.show()_____no_output_____

### Visualize marker gene activities_____no_output_____We use the raw adata object to compute gene activity scores. This allows us to consider all peaks, not just the subset we used for clustering and UMAP._____no_output_____

%%time
# find which peaks overlap with each marker gene (+ 5kb upstream)
peaks_per_gene = {gene : [] for gene in markers}

for (i, peak) in enumerate(adata_raw.var_names):
    chrom, coords = peak.strip().split(':')
    start, end = [int(c) for c in coords.split('-')]
    for gene in markers:
        if utils.overlap(marker_coords[gene], [chrom, start, end], 5000, 0):
            peaks_per_gene[gene].append((i, peak))

for gene in peaks_per_gene:
    print(f"{gene} has {len(peaks_per_gene[gene])} nearby peaks")MS4A1 has 2 nearby peaks
LEF1 has 30 nearby peaks
NKG7 has 4 nearby peaks
TREM1 has 5 nearby peaks
GATA1 has 6 nearby peaks
PAX5 has 30 nearby peaks
CPU times: user 649 ms, sys: 581 µs, total: 650 ms
Wall time: 648 ms
%%time
# Calculate per-cell gene activity scores for each marker gene
cell_gene_scores = np.zeros((adata.X.shape[0], len(marker_coords.keys())))
for (i, gene) in enumerate(peaks_per_gene.keys()):
    indices = [x[0] for x in peaks_per_gene[gene]]
    peak_scores = adata_raw.X[:, indices].toarray()
    cell_gene_scores[:, i] = np.sum(peak_scores, axis=1) + 1

# Store per-cell gene activity scores with adata
for (i, gene) in enumerate(markers):
    adata.obs[gene] = np.log2(cell_gene_scores[:, i].tolist())
    adata.obs.loc[np.isneginf(adata.obs[gene]), gene] = 0CPU times: user 1.01 s, sys: 35.4 ms, total: 1.05 s
Wall time: 1.05 s
%%time

fig, axs = plt.subplots(nrows=2, ncols=3, figsize = (8, 4), dpi=100)
axs = axs.flatten()
for (i, gene) in enumerate(markers):
    sc.pl.umap(adata, color_map="Blues", color=gene, ax = axs[i], show=False)
plt.tight_layout()
plt.show()_____no_output_____

## Find differential peaks_____no_output_____We use a logistic regression model to find peaks that show differential activity across clusters. Here, we needed to increase the maximum number of iterations to 1200 for the model to converge._____no_output_____

%%time
sc.tl.rank_genes_groups(adata,
                        groupby="louvain",
                        n_genes=n_diff_peaks,
                        groups='all',
                        reference='rest',
                        method='logreg',
                        max_iter=1200
                       )CPU times: user 50min 42s, sys: 2h 21min 31s, total: 3h 12min 14s
Wall time: 16min 46s
%%time

fig, axs = plt.subplots(len(adata.uns['rank_genes_groups']['names'][0])//3 + 1, 3, figsize=(9,9), dpi=100)
axs = axs.flatten()

for (i, peak) in enumerate(adata.uns['rank_genes_groups']['names'][0]):
    sc.pl.umap(adata, color=peak,
           ax=axs[i], show=False,
           vmax=min(25, np.max(adata[:,peak].X)),
           cmap='viridis'
          )
plt.tight_layout()
plt.show()_____no_output_____print("Full time: %s" % (time.time() - start_time))Full time: 1530.34348654747


# Notebook from rgbrown/160319
Path: notes/Notes on dynamical systems.ipynb

import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
matplotlib.rcParams['figure.figsize'] = (10.0, 8.0)_____no_output_____

# Phase plane analysis of dynamical systems
When we have a system of two differential equations in two variables, $\mathbf{x}' = \mathbf{f}(\mathbf{x})$ we have a very useful analysis technique available to us, that of the *phase plane*. The phase plane is a two-dimensional plot with the two state variables of the ODE system on the axes. Each point on the plane corresponds to a particular choice of $\mathbf{x}$ at which we can compute a velocity vector $\mathbf{x}'$ and plot it on the graph (if we like) as an arrow originating at $\mathbf{x}$.

### Example: Fitzhugh-Nagumo equations
We're going to use the Fitzhugh-Nagumo equations as our example:
\begin{equation}
  \begin{split}
     \epsilon \frac{dv}{dt} &= f(v) - w + I_{\text{app}} \\
     \frac{dw}{dt} &= v - \gamma w
  \end{split}
\end{equation}
where
\begin{equation*}
  f(v) = v(1 - v)(v - \alpha)
\end{equation*}
and $0 < \alpha < 1$, $\epsilon \ll 1$. We'll use $\alpha = 0.1$, $\gamma = 0.5$, and $\epsilon = 0.01$ for this example.

Below we're going to define our equations in code so we can use them to illustrate various things about the system._____no_output_____

# Parameters
alpha = 0.1
gamma = 0.5
epsilon = 0.1
I_app = 0

# Define the f(v) function separately
def f(v):
    return v*(1 - v)*(v - alpha)

# Fitzhugh-Nagumo equations - these will pick up the parameter values above
def fhn(x):
    v, w = x[0], x[1]
    dv = 1/epsilon*(f(v) - w + I_app)
    dw = v - gamma*w
    return array([dv, dw])

def plot_vector_field():
    V, W = meshgrid(np.linspace(-1, 2, 20), np.linspace(-1, 2, 20))
    dV, dW = fhn([V, W])
    plt.quiver(V, W, dV, dW)
    plt.title('Vector field for Fitzhugh-Nagumo equations', fontsize=16)
    plt.xlabel('$v$', fontsize=16)
    plt.ylabel('$w$', fontsize=16)

plot_vector_field()_____no_output_____

If we simulate our system numerically for a given choice of initial conditions. e.g. $v(0) = -1$, $w(0) = -0.5$, we can plot the resulting $v$, $w$ against each other to give a *trajectory*_____no_output_____

from scipy.integrate import odeint
# initial conditions and simulation time

v0, w0 = -1, -0.5
t = np.linspace(0, 10, 2000)
def compute_trajectory(v0, w0, t):
    # scipy's odeint does the heavy lifting for us. Its ode function requires an additional
    # t input, so that's what the lambda is doing. We could have just added this to fhn at the
    # start
    X = odeint(lambda x, t: fhn(x), [v0, w0], t)

    # Solution comes out as a big array with two columns, so pull v and w out
    v, w = X[:, 0], X[:, 1]
    return [v, w]

plot_vector_field()
v, w = compute_trajectory(v0, w0, t)
plt.plot(v, w)_____no_output_____

Note that the trajectory is tangent to the vector field at all points._____no_output_____## Nullclines
The first thing we do in the phase plane method is to compute the *nullclines* of our system. These are the curves were either $\frac{dv}{dt} = 0$ or $\frac{dw}{dt} = 0$. For our example:
\begin{equation*}
  \frac{dv}{dt} = 0 \implies w = f(v) + I_{\text{app}} = 0
\end{equation*}
and
\begin{equation*}
  \frac{dw}{dt} = 0 \implies w = \frac{1}{\gamma} v.
\end{equation*}

Referring to our example, the curve $\frac{dv}{dt} = 0$ corresponds to all points where the velocity vector has no horizontal component, trajectories can only cross this nullcline vertically. Moreover, the sign of $\frac{dv}{dt}$ is fixed on either side of the nullcline, as the sign cannot change without passing back through it. For our example, $\frac{dv}{dt}$ is *positive* when we are below the nullcline, meaning trajectories move right when we are beneath it, and *negative* when we are above the nullcline, meaning trajectories will move left.

_____no_output_____

def null_v(v):
    return f(v) + I_app
def null_w(v):
    return 1/gamma*v
v_trajectory, w_trajectory = compute_trajectory(v0, w0, t)

v = np.linspace(-1, 2, 200)
plot_vector_field()
plt.plot(v, null_v(v), label='dv/dt = 0')
plt.plot(v, null_w(v), label='dw/dt=0')

plt.plot(v_trajectory, w_trajectory, label='sample trajectory')
plt.ylim([-1, 2])

text(-1, -0.2, 'dv/dt > 0 \ndw/dt < 0', fontsize=16, bbox=dict(alpha=1, facecolor='white'))
text(0.5, -0.8, 'dv/dt > 0 \ndw/dt > 0', fontsize=16, bbox=dict(alpha=1, facecolor='white'))
text(1.5, 1, 'dv/dt < 0 \ndw/dt > 0', fontsize=16, bbox=dict(alpha=1, facecolor='white'))
text(-0.1, 1.5, 'dv/dt < 0 \ndw/dt < 0', fontsize=16, bbox=dict(alpha=1, facecolor='white'))
plt.legend(fontsize=16)_____no_output_____

You can see at this point that we can already understand quite a bit about how the system behaves. For our given trajectory, it starts in a regime where it is moving right and downward. When the trajectory crosses the $w$ nullcline (orange), it begins to move upward, but continues moving right until it hits the $v$ nullcline(blue). It crosses this nullcline vertically, and then begins to move left, still moving upward. It starts to move down after crossing the orange nullcline again, and then gradually spirals into the origin.

This simple piece of analysis (figuring out the nullclines) already gives us a lot of qualitative insight into how the ODE system behaves. Now, the next thing we have to do is look at the *steady states*, the points where the nullclines intersect._____no_output_____## Steady states
The next thing that we want to do is to understand what kind of long term behaviour the system can exhibit. For our example we see that the trajectory we plotted spirals into the origin and seems to stay there. The origin is an example of what we call a *stable steady state*, i.e. is a point $\mathbf{x}^\star$ satisfying
\begin{equation}
    \mathbf{f}(\mathbf{x}^*) = \mathbf{0}.
\end{equation}
and for which trajectories starting near $\mathbf{x}^\star$ do not depart a small neighbourhood of it.

Some steady states are stable, some are unstable, and some look like a mixture. We're going to take a small digression and look at linear systems now.

## Digression: Linear autonomous systems
Forget our complicated example for a second, we're going to consider some simpler systems. Consider the system
\begin{equation}
    \mathbf{x}' = A \mathbf{x}
\end{equation}
where $A$ is a 2x2 matrix. You may recall from your earlier studies that assuming $A$ has two linearly independent eigenvectors that the solution to this system is
\begin{equation}
    \mathbf{x}(t) = c_1 \mathbf{v}_1 \exp(\lambda_1 t) + c_2 \mathbf{v}_2 \exp(\lambda_2 t)
\end{equation}
where $\lambda_1, \lambda_2$ are the eigenvalues of $A$ (may be duplicate), and $\mathbf{v}_1, \mathbf{v}_2$ are the two corresponding eigenvectors. The constants are found by considering the initial conditions:
\begin{equation*}
    \begin{bmatrix} x_1(0) \\ x_2(0) \end{bmatrix} =
    c_1 \mathbf{v_1} + c_2 \mathbf{v}_2
\end{equation*}
i.e. $c_1, c_2$ are the coefficients of $\mathbf{v}_1$ and $\mathbf{v}_2$ when expressing the initial condition using the eigenvectors as a basis.

If the eigenvectors are real, note that if $\mathbf{x}(0)$ is lined up with an eigenvector, then the other coefficient will be zero, and the trajectory will track this eigenvector into the origin (if $\lambda < 0$) or out to infinity (if $\lambda > 0$) or just stay put (if $\lambda = 0$).

There are a few typical cases that we frequently see (we'll ignore the zero real part possibilities):

### Two negative real eigenvalues: stable node
Considering the form of the solution, it is evident that if both eigenvalues have negative real part that $\mathbf{x}(t) \to \mathbf{0}$ as $t \to \infty$. In this case, the system is *stable* and we call the origin a *stable node*

### Two positive real eigenvalues: unstable node
This situation is the opposite of the previous one, no matter the initial condition the exponentials in the solution will blow up and the long term solution is infinite. We say the origin is an *unstable node*

### One positive, one negative real eigenvalue: saddle node
This one is interesting. If we start on the eigenvector corresponding to the negative eigenvalue, then the trajectory will hone in on the origin. This eigenvector is called the *stable manifold* of the steady state, the set of points for which the steady state is stable.

If we start anywhere else, the coefficient of the positive eigenvalue term will be nonzero and so eventually the positive exponential term will dominate.

### Complex (conjugate) eigenvalues, negative real parts: stable spiral
If $\lambda = a \pm ib$ with $a < 0$, then the solution takes the form
\begin{equation*}
    \mathbf{x}(t) = \exp(at)\left(\mathbf{u}_1 \cos bt + \mathbf{u}_2 \sin bt\right)
\end{equation*}
In this case the solution oscillates with decaying amplitude, and so in the phase plane it spirals into the origin. We call this a *stable spiral*.

### Complex (conjugate) eigenvalues, positive real parts: unstable spiral
Same as previously, except the exponential term corresponds to a growing exponential, and so trajectories spiral away from the origin. We call this an *unstable spiral*.

## Back to steady states: Hartman-Grobman Theorem
So why did we look at linear systems? Well, turns out that if we approximate our nonlinear system by a linear system near around a steady state, then due to a cool theorem called the *Hartman-Grobman Theorem*, that the nonlinear system is guaranteed to behave qualitatively the same as the linear system in some neighbourhood of the fixed point, so long as the real parts of the eigenvalues are nonzero.

### Approximating our nonlinear system by a linear one
So, how do we do this? Well, we can expand our system around the steady state in a 2D Taylor series. Let's assume $\mathbf{x}^\star$ is a steady state, i.e. $\mathbf{f}(\mathbf{x}^\star) = \mathbf{0}$. Then
\begin{align*}
    \mathbf{f}(\mathbf{x} - \mathbf{x}^\star) &= \mathbf{f}(\mathbf{x}^\star) + J(\mathbf{x}^\star)(\mathbf{x} - \mathbf{x}^\star) + \mathcal{O}(\lVert \mathbf{x} - \mathbf{x}^\star \rVert^2) \\
    &= J(\mathbf{x}^\star)(\mathbf{x} - \mathbf{x}^\star) + \mathcal{O}(\lVert \mathbf{x} - \mathbf{x}^\star \rVert^2)
\end{align*}
where $J(\mathbf{x})$ is the Jacobian of the system, evaluated at $\mathbf{x}$.

So, defining new coordinates $\mathbf{u} = \mathbf{x} - \mathbf{x}^\star$ so that the origin in $\mathbf{u}$-space is the steady state, we have that
\begin{align*}
    \mathbf{u}' &= J(\mathbf{x}^\star)\mathbf{u} + \text{higher order terms} \\
    &\approx J(\mathbf{x}^\star)\mathbf{u}.
\end{align*}
This approximation is a linear system, and so long as the eigenvalues of $J(\mathbf{x}^\star)$ have nonzero real parts, this approximation behaves the same way as the nonlinear system near the steady state.

### Fitzhugh-Nagumo equations
So let's look at the steady state of the Fitzhugh-Nagumo equations. We can see that the only steady state is at the origin by inspection, so we don't need to do any fancy variable translations. We need the Jacobian. If $\mathbf{x} = \begin{bmatrix} v & w \end{bmatrix}^T$, and express our system as
\begin{equation}
  \begin{split}
     \frac{dv}{dt} &= \frac{1}{\epsilon}\left(f(v) - w + I_{\text{app}}\right) = g(v, w) \\
     \frac{dw}{dt} &= v - \gamma w = h(v, w)
  \end{split}
\end{equation}

\begin{equation*}
    J(\mathbf{0}) = \begin{bmatrix}
        \frac{\partial g}{\partial v} & \frac{\partial g}{\partial w} \\
        \frac{\partial h}{\partial v} & \frac{\partial h}{\partial w}
    \end{bmatrix}(\mathbf{0}) =
    \begin{bmatrix}
        \frac{1}{\epsilon}\left(-3v^2 + 2(\alpha + 1)v - \alpha\right) & -\frac{1}{\epsilon} \\
        1 & -\gamma
    \end{bmatrix}(\mathbf{0}) = \begin{bmatrix}
    -\frac{\alpha}{\epsilon} & -\frac{1}{\epsilon} \\
    1 & -\gamma
    \end{bmatrix}
\end{equation*}

For our choice of parameters, this matrix has eigenvalues $\lambda \approx -5.25 \pm 8.8i$, which makes the steady state of the linear system, and hence locally the nonlinear system, a *stable spiral*._____no_output_____


# Notebook from evafast/scrnaseq_paper
Path: 03c_overlap_clusters_HSC_MPP.ipynb

## 03c overlap clusters HSC MPP

this should help with HSC cluster naming

1) how much overlap in enriched genes between clusters
2) differential proportion analysis - how similar are clusters between replicates for both MPPs and LTs

from this paper https://elifesciences.org/articles/43882#s4

done with this docker image:

docker run --rm -d --name test_eva -p 8883:8888 -e JUPYTER_ENABLE_LAB=YES -v /Users/efast/Documents/:/home/jovyan/work r_scanpy:vs5_____no_output_____

#load some packages that are needed for plotting + computation

import math
from matplotlib import pyplot as plt
import pandas as pd
import scipy.stats as stats
import seaborn as sns
%matplotlib inline_____no_output_____LT = pd.read_csv('./write/all_LT_clusters.csv')
MPP = pd.read_csv('./write/all_MPP_clusters.csv')_____no_output_____# read in the list with enriched genes and calculate overlap of pairwise comparison between clusters

results_df_list =[]
results_df = pd.DataFrame()

for k in range(0, 12, 2):
    results_df_list =[]
    for i in range(0, 16, 2):
        mrg = pd.merge(LT, MPP, how='inner', left_on=LT.iloc[:,k], right_on=MPP.iloc[:,i])
        len_m = len(mrg)
        results_df_list.append(len_m)

    results_df_list = pd.Series(results_df_list)
    results_df = results_df.append(results_df_list, ignore_index=True)_____no_output_____results_df_____no_output_____results_df.columns = ['Primed', 'Metabolism', 'Progenitor', 'Cell-cycle', 'Acute-Activation', 'Interferon', 'Interferon cell-cycle','Myeloid']
results_df.index = ['Metabolism', 'Quiescent', 'Activated', 'Interferon', 'Acute-Activation', 'Cell-cycle']_____no_output_____results_df_____no_output_____results_df_p = results_df/100
results_df_p_____no_output_____results_df_p.columns.tolist()_____no_output_____results_df_p.index.tolist()_____no_output_____results_df_p = results_df_p[['Progenitor', 'Primed', 'Metabolism', 'Cell-cycle', 'Interferon cell-cycle',
                 'Interferon', 'Acute-Activation','Myeloid']]
results_df_p = results_df_p.reindex(['Quiescent', 'Activated','Metabolism','Cell-cycle', 'Interferon',
                                     'Acute-Activation'])_____no_output_____sns.set(font_scale=1.4)

plt.figure(figsize=(12,5))
sns.heatmap(results_df_p.T, annot=True, annot_kws={"size": 16})
plt.yticks(rotation=0, fontsize = 12)
plt.xticks(rotation=-0, fontsize = 12)
plt.savefig("./figures/overlap_clusters_MPP_LT_all.pdf", dpi=300, bbox_inches='tight')_____no_output_____

## 2) Differential proportion analysis_____no_output_____

import scanpy as sc
import numpy as np
import scipy as sp
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams
from matplotlib import colors
import seaborn as sb
import scipy.stats as stats

import rpy2.rinterface_lib.callbacks
import logging

from rpy2.robjects import pandas2ri
import anndata2ri_____no_output_____# Ignore R warning messages
#Note: this can be commented out to get more verbose R output
rpy2.rinterface_lib.callbacks.logger.setLevel(logging.ERROR)

# Automatically convert rpy2 outputs to pandas dataframes
pandas2ri.activate()
anndata2ri.activate()
%load_ext rpy2.ipython

plt.rcParams['figure.figsize']=(8,8) #rescale figures
sc.settings.verbosity = 3
#sc.set_figure_params(dpi=200, dpi_save=300)
sc.logging.print_versions()scanpy==1.4.5.1 anndata==0.7.1 umap==0.3.10 numpy==1.17.3 scipy==1.3.0 pandas==0.25.3 scikit-learn==0.22.2.post1 statsmodels==0.10.0 python-igraph==0.7.1 louvain==0.6.1
%%R
# Load libraries from correct lib Paths for my environment - ignore this!
.libPaths(.libPaths()[c(3,2,1)])

# Load all the R libraries we will be using in the notebook
library(ggplot2)
library(plyr)

source("diffprop_functions.R");_____no_output_____

## Comparison LT clusters_____no_output_____

# read csvs, rename columns, merge and transpose
LT = pd.read_csv('./write/LT_cell_numbers.csv', index_col = 0)
LT = LT.T
LT_____no_output_____%%R -i LT
## Read in file of counts of cells in each population across conditions

obs.counts = as.matrix(LT)
print(obs.counts)       Metabolism Quiescent Activated Interferon Acute.Activation Cell.cycle
GCSF         1204       937       780         21                0         42
ct           1865      1981      1556         40                0         79
dmPGE2         83        24       108          0              261          0
indo         1757      1816      1947         58                1         47
pIC           188       184        58        316                0          2
%%R

## Run an example using error (p) of 0.05 and with 100,000 iterations
tip.exp <- generateNull(obs.counts, n=100000, p=0.1);     # Generate the null distribution based on sampling

print(obs.counts/apply(obs.counts, 1, sum))[1] 1000
[1] 2000
[1] 3000
[1] 4000
[1] 5000
[1] 6000
[1] 7000
[1] 8000
[1] 9000
[1] 10000
[1] 11000
[1] 12000
[1] 13000
[1] 14000
[1] 15000
[1] 16000
[1] 17000
[1] 18000
[1] 19000
[1] 20000
[1] 21000
[1] 22000
[1] 23000
[1] 24000
[1] 25000
[1] 26000
[1] 27000
[1] 28000
[1] 29000
[1] 30000
[1] 31000
[1] 32000
[1] 33000
[1] 34000
[1] 35000
[1] 36000
[1] 37000
[1] 38000
[1] 39000
[1] 40000
[1] 41000
[1] 42000
[1] 43000
[1] 44000
[1] 45000
[1] 46000
[1] 47000
[1] 48000
[1] 49000
[1] 50000
[1] 51000
[1] 52000
[1] 53000
[1] 54000
[1] 55000
[1] 56000
[1] 57000
[1] 58000
[1] 59000
[1] 60000
[1] 61000
[1] 62000
[1] 63000
[1] 64000
[1] 65000
[1] 66000
[1] 67000
[1] 68000
[1] 69000
[1] 70000
[1] 71000
[1] 72000
[1] 73000
[1] 74000
[1] 75000
[1] 76000
[1] 77000
[1] 78000
[1] 79000
[1] 80000
[1] 81000
[1] 82000
[1] 83000
[1] 84000
[1] 85000
[1] 86000
[1] 87000
[1] 88000
[1] 89000
[1] 90000
[1] 91000
[1] 92000
[1] 93000
[1] 94000
[1] 95000
[1] 96000
[1] 97000
[1] 98000
[1] 99000
[1] 100000
       Metabolism  Quiescent  Activated  Interferon Acute.Activation
GCSF    0.4034853 0.31400804 0.26139410 0.007037534     0.0000000000
ct      0.3378011 0.35881181 0.28183300 0.007245064     0.0000000000
dmPGE2  0.1743697 0.05042017 0.22689076 0.000000000     0.5483193277
indo    0.3123000 0.32278706 0.34607181 0.010309278     0.0001777462
pIC     0.2513369 0.24598930 0.07754011 0.422459893     0.0000000000
        Cell.cycle
GCSF   0.014075067
ct     0.014309002
dmPGE2 0.000000000
indo   0.008354070
pIC    0.002673797
%%R
### P-value tests for different treatments

print('GCSF')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="GCSF",to.plot=F))
print('dmPGE2')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="dmPGE2",to.plot=F))
print('pIC')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="pIC",to.plot=F))
print('indo')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="indo",to.plot=F))[1] "GCSF"
      Metabolism        Quiescent        Activated       Interferon
         0.08226          0.16757          0.32153          0.49787
Acute.Activation       Cell.cycle
         0.49940          0.48052
[1] "dmPGE2"
      Metabolism        Quiescent        Activated       Interferon
         0.03561          0.00005          0.27202          0.43429
Acute.Activation       Cell.cycle
         0.00000          0.15334
[1] "pIC"
      Metabolism        Quiescent        Activated       Interferon
         0.13329          0.07182          0.00111          0.00000
Acute.Activation       Cell.cycle
         0.45117          0.19183
[1] "indo"
      Metabolism        Quiescent        Activated       Interferon
         0.25738          0.17509          0.04653          0.37871
Acute.Activation       Cell.cycle
         0.46157          0.24265

## MPPs_____no_output_____

# read csvs, rename columns, merge and transpose
MPP = pd.read_csv('./write/MPP_cell_numbers.csv', index_col = 0)
MPP = MPP.T
MPP_____no_output_____%%R -i MPP
## Read in file of counts of cells in each population across conditions

obs.counts = as.matrix(MPP)
print(obs.counts)       Primed Metabolism Stem.cell Cell.cycle Acute.Activation Interferon
ct        246        223       237        148                8          2
indo      459        385       388        236                5         13
GCSF      549        458       420        251               10          4
dmPGE2     31        334       140        373             1073          1
pIC       439        315       246        211                0        713
       Interferon.cell.cycle Myeloid
ct                         0       2
indo                       1       4
GCSF                       1       9
dmPGE2                     0      40
pIC                      196      20
%%R

## Run an example using error (p) of 0.05 and with 100,000 iterations
tip.exp <- generateNull(obs.counts, n=100000, p=0.1);     # Generate the null distribution based on sampling

print(obs.counts/apply(obs.counts, 1, sum))[1] 1000
[1] 2000
[1] 3000
[1] 4000
[1] 5000
[1] 6000
[1] 7000
[1] 8000
[1] 9000
[1] 10000
[1] 11000
[1] 12000
[1] 13000
[1] 14000
[1] 15000
[1] 16000
[1] 17000
[1] 18000
[1] 19000
[1] 20000
[1] 21000
[1] 22000
[1] 23000
[1] 24000
[1] 25000
[1] 26000
[1] 27000
[1] 28000
[1] 29000
[1] 30000
[1] 31000
[1] 32000
[1] 33000
[1] 34000
[1] 35000
[1] 36000
[1] 37000
[1] 38000
[1] 39000
[1] 40000
[1] 41000
[1] 42000
[1] 43000
[1] 44000
[1] 45000
[1] 46000
[1] 47000
[1] 48000
[1] 49000
[1] 50000
[1] 51000
[1] 52000
[1] 53000
[1] 54000
[1] 55000
[1] 56000
[1] 57000
[1] 58000
[1] 59000
[1] 60000
[1] 61000
[1] 62000
[1] 63000
[1] 64000
[1] 65000
[1] 66000
[1] 67000
[1] 68000
[1] 69000
[1] 70000
[1] 71000
[1] 72000
[1] 73000
[1] 74000
[1] 75000
[1] 76000
[1] 77000
[1] 78000
[1] 79000
[1] 80000
[1] 81000
[1] 82000
[1] 83000
[1] 84000
[1] 85000
[1] 86000
[1] 87000
[1] 88000
[1] 89000
[1] 90000
[1] 91000
[1] 92000
[1] 93000
[1] 94000
[1] 95000
[1] 96000
[1] 97000
[1] 98000
[1] 99000
[1] 100000
           Primed Metabolism  Stem.cell Cell.cycle Acute.Activation  Interferon
ct     0.28406467  0.2575058 0.27367206 0.17090069      0.009237875 0.002309469
indo   0.30784708  0.2582160 0.26022803 0.15828303      0.003353454 0.008718981
GCSF   0.32256169  0.2690952 0.24676851 0.14747356      0.005875441 0.002350176
dmPGE2 0.01556225  0.1676707 0.07028112 0.18724900      0.538654618 0.000502008
pIC    0.20514019  0.1471963 0.11495327 0.09859813      0.000000000 0.333177570
       Interferon.cell.cycle     Myeloid
ct              0.0000000000 0.002309469
indo            0.0006706908 0.002682763
GCSF            0.0005875441 0.005287897
dmPGE2          0.0000000000 0.020080321
pIC             0.0915887850 0.009345794
%%R
### P-value tests for different treatments

print('GCSF')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="GCSF",to.plot=F))
print('dmPGE2')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="dmPGE2",to.plot=F))
print('pIC')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="pIC",to.plot=F))
print('indo')
print(two.class.test(obs.counts, tip.exp, cond.control="ct", cond.treatment="indo",to.plot=F))[1] "GCSF"
               Primed            Metabolism             Stem.cell
              0.30856               0.43321               0.36757
           Cell.cycle      Acute.Activation            Interferon
              0.36159               0.46020               0.47934
Interferon.cell.cycle               Myeloid
              0.44327               0.40429
[1] "dmPGE2"
               Primed            Metabolism             Stem.cell
              0.00008               0.10881               0.00166
           Cell.cycle      Acute.Activation            Interferon
              0.39765               0.00000               0.47565
Interferon.cell.cycle               Myeloid
              0.46960               0.10900
[1] "pIC"
               Primed            Metabolism             Stem.cell
              0.14366               0.06112               0.01111
           Cell.cycle      Acute.Activation            Interferon
              0.12212               0.41777               0.00000
Interferon.cell.cycle               Myeloid
              0.00009               0.27611
[1] "indo"
               Primed            Metabolism             Stem.cell
              0.38364               0.49888               0.43097
           Cell.cycle      Acute.Activation            Interferon
              0.42664               0.44403               0.43845
Interferon.cell.cycle               Myeloid
              0.49611               0.42327
sc.logging.print_versions()
pd.show_versions()scanpy==1.4.5.1 anndata==0.7.1 umap==0.3.10 numpy==1.17.3 scipy==1.3.0 pandas==0.25.3 scikit-learn==0.22.2.post1 statsmodels==0.10.0 python-igraph==0.7.1 louvain==0.6.1

INSTALLED VERSIONS
------------------
commit           : None
python           : 3.7.3.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.19.76-linuxkit
machine          : x86_64
processor        : x86_64
byteorder        : little
LC_ALL           : en_US.UTF-8
LANG             : en_US.UTF-8
LOCALE           : en_US.UTF-8

pandas           : 0.25.3
numpy            : 1.17.3
pytz             : 2019.3
dateutil         : 2.8.1
pip              : 19.3.1
setuptools       : 41.6.0.post20191101
Cython           : None
pytest           : 5.3.5
hypothesis       : None
sphinx           : None
blosc            : None
feather          : None
xlsxwriter       : None
lxml.etree       : None
html5lib         : None
pymysql          : None
psycopg2         : None
jinja2           : 2.10.3
IPython          : 7.9.0
pandas_datareader: None
bs4              : None
bottleneck       : None
fastparquet      : None
gcsfs            : None
lxml.etree       : None
matplotlib       : 3.2.0
numexpr          : 2.7.1
odfpy            : None
openpyxl         : None
pandas_gbq       : None
pyarrow          : None
pytables         : None
s3fs             : None
scipy            : 1.3.0
sqlalchemy       : 1.3.10
tables           : 3.6.1
xarray           : None
xlrd             : None
xlwt             : None
xlsxwriter       : None
%%R

sessionInfo()R version 3.6.1 (2019-07-05)
Platform: x86_64-conda_cos6-linux-gnu (64-bit)
Running under: Ubuntu 18.04.2 LTS

Matrix products: default
BLAS/LAPACK: /opt/conda/lib/libopenblasp-r0.3.7.so

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
 [5] LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
 [9] LC_ADDRESS=C               LC_TELEPHONE=C
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C

attached base packages:
[1] tools     stats     graphics  grDevices utils     datasets  methods
[8] base

other attached packages:
[1] plyr_1.8.4    ggplot2_3.2.1

loaded via a namespace (and not attached):
 [1] Rcpp_1.0.3       withr_2.1.2      crayon_1.3.4     dplyr_0.8.3
 [5] assertthat_0.2.1 grid_3.6.1       R6_2.4.0         gtable_0.3.0
 [9] magrittr_1.5     scales_1.0.0     pillar_1.4.2     rlang_0.4.1
[13] lazyeval_0.2.2   glue_1.3.1       purrr_0.3.3      munsell_0.5.0
[17] compiler_3.6.1   pkgconfig_2.0.3  colorspace_1.4-1 tidyselect_0.2.5
[21] tibble_2.1.3
!pip list_____no_output_____