# Notebook from arischwartz/test
Path: ipynb/bac_genome/n1147_pt2/.ipynb_checkpoints/atomIncorp_fracSizeR-checkpoint.ipynb

# Goal

* Analyze results from `atomIncorp_fracSize` simulation_____no_output_____# Var_____no_output_____

workDir = '/ebio/abt3_projects/methanogen_host_evo/SIPSim_pt2/data/bac_genome1147/atomIncorp_fracSize/'_____no_output_____

# Init_____no_output_____

library(dplyr)
library(tidyr)
library(ggplot2)

as.Num = function(x) x %>% as.character %>% as.numeric_____no_output_____

# Plotting fraction sizes_____no_output_____## Load_____no_output_____

# files on simulation accuracy
files = list.files(path=workDir, pattern='fracs.txt', full.names=TRUE, recursive=TRUE)
files %>% length_____no_output_____df_fracs = list()
for (f in files){
    ff = strsplit(f, '/') %>% unlist
    df = read.delim(f, sep='\t')
    df$rep = ff[length(ff)-1]
    df$frac_mu = ff[length(ff)-2]
    df$atomIncorp = ff[length(ff)-3]
    df_fracs[[f]] = df
}

df_fracs = do.call(rbind, df_fracs)
rownames(df_fracs) = 1:nrow(df_fracs)

# status
df_fracs %>% dim %>% print
df_fracs %>% head(n=3)[1] 31831     8

## Fraction sizes_____no_output_____

## min G+C cutoff
min_GC = 13.5
## max G+C cutoff
max_GC = 80
## max G+C shift
max_13C_shift_in_BD = 0.036

min_BD = min_GC/100.0 * 0.098 + 1.66
max_BD = max_GC/100.0 * 0.098 + 1.66

max_BD = max_BD + max_13C_shift_in_BD

cat('Min BD:', min_BD, '\n')
cat('Max BD:', max_BD, '\n')Min BD: 1.67323
Max BD: 1.7744
# filtering to just within min-max BD
df_fracs %>% nrow %>% print
df_fracs = df_fracs %>%
    filter(BD_min >= min_BD,
           BD_max <= max_BD)
df_fracs %>% nrow %>% print[1] 31831
[1] 30391
# plotting
options(repr.plot.width=4, repr.plot.height=3)
p_size = ggplot(df_fracs, aes(frac_mu, fraction_size)) +
    geom_boxplot() +
    labs(x='Mean fraction size', y='Fraction size') +
    theme_bw() +
    theme(
        axis.text.x = element_text(angle=45, hjust=1)
    )
plot(p_size)_____no_output_____

## Number of fractions per gradient_____no_output_____

df_fracs_s = df_fracs %>%
    mutate(frac_mu = frac_mu %>% as.character) %>%
    group_by(library, rep, frac_mu, atomIncorp) %>%
    summarize(n_fracs = fraction %>% unique %>% length) %>%
    ungroup() %>%
    mutate(frac_mu = frac_mu %>% as.character)

# plotting
options(repr.plot.width=4, repr.plot.height=3)
p_fracs = ggplot(df_fracs_s, aes(frac_mu, n_fracs)) +
    geom_boxplot() +
    labs(x='Mean fraction size', y='Number of fractions\nper gradient') +
    theme_bw()
plot(p_fracs)_____no_output_____

# Load_____no_output_____

# files on simulation accuracy
files = list.files(path=workDir, pattern='*-cMtx_byClass.txt', full.names=TRUE)
files_____no_output_____# combining files
df_byClass = list()
for (f in files){
    ff = strsplit(f, '/') %>% unlist
    fff = ff[length(ff)]
    df_byClass[[fff]] = read.delim(f, sep='\t')
}

df_byClass = do.call(rbind, df_byClass)
df_byClass$file = gsub('\\.[0-9]+$', '', rownames(df_byClass))
df_byClass$method = gsub('-.+', '', df_byClass$file)
rownames(df_byClass) = 1:nrow(df_byClass)

df_byClass %>% head(n=3)_____no_output_____# renaming methods
rename = data.frame(method = c('DESeq2', 'DESeq2_multi', 'heavy', 'qSIP'),
                   method_new = c('HR-SIP', 'MW-HR-SIP', 'Heavy-SIP', 'q-SIP'))

df_byClass = inner_join(df_byClass, rename, c('method'='method')) %>%
    select(-method) %>%
    rename('method' = method_new)

df_byClass %>% dim %>% print
df_byClass %>% head(n=3)Warning message in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y):
“joining character vector and factor, coercing into character vector”# adding number of fractions per gradient
## mean n-fracs
df_fracs_s = df_fracs %>%
    mutate(frac_mu = frac_mu %>% as.character) %>%
    group_by(library, rep, frac_mu, atomIncorp) %>%
    summarize(n_fracs = fraction %>% unique %>% length) %>%
    group_by(frac_mu) %>%
    summarize(n_fracs = mean(n_fracs)) %>%
    ungroup() %>%
    mutate(frac_mu = frac_mu %>% as.character)

## joining
df_byClass = df_byClass %>%
    inner_join(df_fracs_s %>%
                   dplyr::select(frac_mu, n_fracs) %>%
                   mutate(frac_mu = frac_mu %>% as.Num),
               c('frac_mu' = 'frac_mu'))

df_byClass %>% dim %>% print
df_byClass %>% head(n=3)[1] 23040     9
# summarize by SIPSim rep & library rep
df_byClass.s = df_byClass %>%
    group_by(method, percIncorp, frac_mu, n_fracs, variables) %>%
    summarize(mean_value = mean(values, na.rm=TRUE),
              sd_value = sd(values, na.rm=TRUE))

# plotting
options(repr.plot.width=8, repr.plot.height=5)
p = ggplot(df_byClass.s, aes(variables, mean_value, color=method,
                         ymin=mean_value-sd_value,
                         ymax=mean_value+sd_value)) +
    geom_pointrange(alpha=0.8, size=0.2) +
    labs(y='Value') +
    facet_grid(frac_mu ~ percIncorp) +
    theme_bw() +
    theme(
        axis.title.x = element_blank(),
        axis.text.x = element_text(angle=65, hjust=1)
    )
plot(p)Warning message:
“Removed 64 rows containing missing values (geom_pointrange).”# summarize by SIPSim rep & library rep
vars = c('Balanced Accuracy', 'Sensitivity', 'Specificity')
df_byClass.s.f = df_byClass.s %>%
    filter(variables %in% vars) %>%
    ungroup() %>%
    mutate(frac_mu = frac_mu %>% as.character,
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.Num),
           n_fracs = round(n_fracs, 0) %>% as.character,
           n_fracs = n_fracs %>% reorder(n_fracs %>% as.Num))

# plotting
options(repr.plot.width=9, repr.plot.height=5)
p.pnt = ggplot(df_byClass.s.f, aes(percIncorp, mean_value,
                           color=n_fracs,
                           group=n_fracs,
                           ymin=mean_value-sd_value,
                           ymax=mean_value+sd_value)) +
    geom_point(alpha=0.8) +
    geom_linerange(alpha=0.8, size=0.5) +
    geom_line() +
    scale_color_discrete('Number of\nfractions\nper gradient') +
    labs(x='atom % excess 13C') +
    facet_grid(variables ~ method) +
    theme_bw() +
    theme(
        axis.title.y = element_blank()
    )
plot(p.pnt)Warning message:
“Removed 32 rows containing missing values (geom_point).”Warning message:
“Removed 32 rows containing missing values (geom_linerange).”Warning message:
“Removed 4 rows containing missing values (geom_path).”outF = file.path(workDir, 'atomIncorp_fracSize_acc.pdf')
ggsave(outF, p.pnt, width=10, height=6)
cat('File written:', outF, '\n')Warning message:
“Removed 32 rows containing missing values (geom_point).”Warning message:
“Removed 32 rows containing missing values (geom_linerange).”Warning message:
“Removed 4 rows containing missing values (geom_path).”outF = file.path(workDir, 'atomIncorp_fracSize_acc.jpeg')
ggsave(outF, p.pnt, width=10, height=6)
cat('File written:', outF, '\n')Warning message:
“Removed 32 rows containing missing values (geom_point).”Warning message:
“Removed 32 rows containing missing values (geom_linerange).”Warning message:
“Removed 4 rows containing missing values (geom_path).”

# BD shift quantify_____no_output_____## True BD shift_____no_output_____

BDshift_files = list.files(path=workDir, pattern='BD-shift_stats.txt', full.names=TRUE, recursive=TRUE)
BDshift_files %>% length %>% print[1] 240
df_shift = list()
for(F in BDshift_files){
    tmp = read.delim(F, sep='\t')
    FF = strsplit(F, '/') %>% unlist
    FFl = length(FF)
    tmp$percIncorp = FF[FFl-3]
    tmp$frac_mu = FF[FFl-2]
    tmp$rep = FF[FFl-1]
    tmp$file = FF[FFl]
    df_shift[[F]] = tmp
}

df_shift = do.call(rbind, df_shift)
rownames(df_shift) = 1:nrow(df_shift)

df_shift = df_shift %>%
    filter(library %in% c(2,4,6)) %>%
    group_by(taxon, percIncorp, frac_mu, rep) %>%
    summarize(median = median(median)) %>%
    ungroup() %>%
    rename('median_true_BD_shift' = median)

# status
df_shift %>% nrow %>% print
df_shift %>% head(n=3)[1] 264480

## MW-HR-SIP incorp calls

* filtering BD shift estimates to just incorporators identified by MW-HR-SIP_____no_output_____

incorp_files = list.files(path=workDir, pattern='OTU_abs1e9_PCR_sub_filt_MW_DESeq2_incorp.txt', full.names=TRUE, recursive=TRUE)
incorp_files %>% length %>% print[1] 240
df_incorp = list()
for(F in incorp_files){
    tmp = read.delim(F, sep='\t')
    FF = strsplit(F, '/') %>% unlist
    FFl = length(FF)
    tmp$percIncorp = FF[FFl-3]
    tmp$frac_mu = FF[FFl-2]
    tmp$rep = FF[FFl-1]
    tmp$file = FF[FFl]
    df_incorp[[F]] = tmp
}

df_incorp = do.call(rbind, df_incorp)
rownames(df_incorp) = 1:nrow(df_incorp)
df_incorp %>% head(n=3) %>% print     baseMean log2FoldChange      lfcSE      stat    pvalue      padj         p
1  1.01763669     0.03324132 0.18511494 0.1795712 0.8574892 0.9999985 0.8791888
2  0.03647256     0.01116430 0.05991726 0.1863286 0.8521871 0.9999985 0.9999664
3 11.69057411     0.13086554 0.19979375 0.6550031 0.5124657 0.9999985 0.7245083
  occur_all occur_heavy heavy_BD_min heavy_BD_max
1         0           0          1.7         1.73
2         0           0          1.7         1.73
3         0           0          1.7         1.73
                           taxon incorp percIncorp frac_mu rep
1 Acaryochloris_marina_MBIC11017  FALSE          0   0.003   1
2       Acholeplasma_palmae_J233  FALSE          0   0.003   1
3      Acidiphilium_cryptum_JF-5  FALSE          0   0.003   1
                                          file
1 OTU_abs1e9_PCR_sub_filt_MW_DESeq2_incorp.txt
2 OTU_abs1e9_PCR_sub_filt_MW_DESeq2_incorp.txt
3 OTU_abs1e9_PCR_sub_filt_MW_DESeq2_incorp.txt
# just incorporators
df_incorp = df_incorp %>%
    filter(incorp == TRUE) %>%
    dplyr::distinct(taxon, incorp, percIncorp, frac_mu, rep) %>%
    rename('HWHRSIP_incorp' = incorp)

df_incorp %>% nrow %>% print
df_incorp %>% head(n=3) %>% print[1] 15724
                                               taxon HWHRSIP_incorp percIncorp
1                    Bacteroides_helcogenes_P_36-108           TRUE          0
2 Bifidobacterium_kashiwanohense_JCM_15439_DSM_21854           TRUE          0
3                  Emticicia_oligotrophica_DSM_17448           TRUE          0
  frac_mu rep
1   0.003   1
2   0.003  10
3   0.003  10

## q-SIP_____no_output_____

atomX_files = list.files(path=workDir, pattern='*_qSIP_atom.txt', full.names=TRUE, recursive=TRUE)
atomX_files %>% length %>% print[1] 240
df_atomX = list()
for(F in atomX_files){
    tmp = read.delim(F, sep='\t')
    FF = strsplit(F, '/') %>% unlist
    FFl = length(FF)
    tmp$percIncorp = FF[FFl-3]
    tmp$frac_mu = FF[FFl-2]
    tmp$rep = FF[FFl-1]
    tmp$file = FF[FFl]
    df_atomX[[F]] = tmp
}

df_atomX = do.call(rbind, df_atomX)
rownames(df_atomX) = 1:nrow(df_atomX)
df_atomX %>% head(n=3) %>% print                                 taxon  control treatment      BD_diff
1       Acaryochloris_marina_MBIC11017 1.708171  1.709842 0.0016714789
2 Acetobacter_pasteurianus_IFO_3283-03 1.711827  1.712034 0.0002076271
3       Acetobacterium_woodii_DSM_1030 1.702658  1.703639 0.0009809804
  control_GC control_MW treatment_max_MW treatment_MW atom_fraction_excess
1  0.7438214   308.0599         317.6635     308.3614          0.031039722
2  0.7876043   308.0817         317.6634     308.1190          0.003856485
3  0.6778097   308.0272         317.6637     308.2047          0.018211621
   atom_CI_low atom_CI_high percIncorp frac_mu rep
1 -0.005267942   0.06404530          0   0.003   1
2 -0.017872885   0.02176435          0   0.003   1
3 -0.011474687   0.04532687          0   0.003   1
                              file
1 OTU_abs1e9_PCR_sub_qSIP_atom.txt
2 OTU_abs1e9_PCR_sub_qSIP_atom.txt
3 OTU_abs1e9_PCR_sub_qSIP_atom.txt

### Joining estimate with true values_____no_output_____

# table join
df_atomX %>% nrow %>% print

df.j = left_join(df_atomX, df_shift, c('taxon' = 'taxon',
                                       'percIncorp'='percIncorp',
                                       'frac_mu'='frac_mu',
                                       'rep'='rep')) %>%
   filter(!is.na(BD_diff)) %>%
   mutate(true_incorporator = ifelse(median_true_BD_shift > 0.002, TRUE, FALSE),
          true_atom_fraction_excess = median_true_BD_shift / 0.036,
          atom_fraction_excess = ifelse(is.na(atom_CI_low), 0, atom_fraction_excess))

df.j %>% nrow %>% print
df.j %>% head(n=3) %>% print[1] 275280
df.j$true_incorporator %>% summary_____no_output_____

### Joining with MW-HR-SIP_____no_output_____

df.j = left_join(df.j, df_incorp, c('taxon' = 'taxon',
                                    'percIncorp'='percIncorp',
                                    'frac_mu'='frac_mu',
                                    'rep'='rep')) %>%
    mutate(HWHRSIP_incorp = ifelse(is.na(HWHRSIP_incorp), FALSE, TRUE))

df.j %>% nrow %>% print
df.j %>% head(n=3) %>% printWarning message in left_join_impl(x, y, by$x, by$y, suffix$x, suffix$y):
“joining factor and character vector, coercing into character vector”

### Plotting results_____no_output_____

# difference between true and estimated

## q-SIP incorporators
df.j.dis.qSIP = df.j %>%
    filter(atom_CI_low > 0) %>%     # just incorporators identified by q-SIP
    mutate(delta_excess = atom_fraction_excess * 100 - true_atom_fraction_excess * 100) %>%
    group_by(percIncorp, frac_mu) %>%
    summarize(mean_delta_excess = mean(delta_excess),
              sd_delta_excess = sd(delta_excess)) %>%
    ungroup() %>%
    mutate(percIncorp = percIncorp %>% reorder(percIncorp %>% as.numeric),
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.numeric))

# plotting
options(repr.plot.width=6, repr.plot.height=3)
p_qSIP = ggplot(df.j.dis.qSIP, aes(percIncorp, mean_delta_excess,
                      color=frac_mu, group=frac_mu,
                      ymin=mean_delta_excess-sd_delta_excess,
                     ymax=mean_delta_excess+sd_delta_excess)) +
    geom_linerange(alpha=0.4, size=1) +
    geom_point() +
    geom_line() +
    #facet_grid(true_incorporator ~ .) +
    scale_color_discrete('Mean fraction size') +
    labs(x='13C atom % excess (truth)',
         y='13C atom % excess\n(truth - estimate)') +
    theme_bw()

p_qSIP_____no_output_____# difference between true and estimated

## q-SIP incorporators
df.j.dis.qSIP = df.j %>%
    filter(atom_CI_low > 0) %>%     # just incorporators identified by q-SIP
    filter(HWHRSIP_incorp == TRUE) %>%    # just MW-HR-SIP incorporators
    mutate(delta_excess = atom_fraction_excess * 100 - true_atom_fraction_excess * 100) %>%
    group_by(percIncorp, frac_mu) %>%
    summarize(mean_delta_excess = mean(delta_excess),
              sd_delta_excess = sd(delta_excess)) %>%
    ungroup() %>%
    mutate(percIncorp = percIncorp %>% reorder(percIncorp %>% as.numeric),
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.numeric))

# plotting
options(repr.plot.width=6, repr.plot.height=2.5)
p_qSIP = ggplot(df.j.dis.qSIP, aes(percIncorp, mean_delta_excess,
                      color=frac_mu, group=frac_mu,
                      ymin=mean_delta_excess-sd_delta_excess,
                     ymax=mean_delta_excess+sd_delta_excess)) +
    geom_linerange(alpha=0.4, size=1) +
    geom_point() +
    geom_line() +
    #facet_grid(true_incorporator ~ .) +
    scale_color_discrete('Mean fraction size') +
    labs(x='13C atom % excess (truth)',
         y='13C atom % excess\n(truth - estimate)') +
    theme_bw()

p_qSIPWarning message:
“Removed 1 rows containing missing values (geom_linerange).”# difference between true and estimated
## q-SIP incorporators
tmp1 = df.j %>%
    filter(atom_CI_low > 0) %>%     # just incorporators identified by q-SIP
    mutate(delta_excess = atom_fraction_excess * 100 - true_atom_fraction_excess * 100) %>%
    group_by(percIncorp, frac_mu) %>%
    summarize(mean_delta_excess = mean(delta_excess),
              sd_delta_excess = sd(delta_excess)) %>%
    ungroup() %>%
    mutate(percIncorp = percIncorp %>% reorder(percIncorp %>% as.numeric),
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.numeric),
           incorp_called = 'No filter')
## MW-HR-SIP incorporators
tmp2 = df.j %>%
    filter(atom_CI_low > 0) %>%     # just incorporators identified by q-SIP
    filter(HWHRSIP_incorp == TRUE) %>%    # just MW-HR-SIP incorporators
    mutate(delta_excess = atom_fraction_excess * 100 - true_atom_fraction_excess * 100) %>%
    group_by(percIncorp, frac_mu) %>%
    summarize(mean_delta_excess = mean(delta_excess),
              sd_delta_excess = sd(delta_excess)) %>%
    ungroup() %>%
    mutate(percIncorp = percIncorp %>% reorder(percIncorp %>% as.numeric),
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.numeric),
           incorp_called = 'MW-HR-SIP filter')

# combining tables
df.j.dis.qSIP = rbind(tmp1, tmp2) %>%
    mutate(incorp_called = factor(incorp_called, levels=c('No filter', 'MW-HR-SIP filter')))

# plotting
options(repr.plot.width=6, repr.plot.height=4)
p_qSIP = ggplot(df.j.dis.qSIP, aes(percIncorp, mean_delta_excess,
                      color=frac_mu, group=frac_mu,
                      ymin=mean_delta_excess-sd_delta_excess,
                     ymax=mean_delta_excess+sd_delta_excess)) +
    geom_line() +
    geom_linerange(alpha=0.4, size=1) +
    geom_point() +
    facet_grid(incorp_called ~ .) +
    scale_color_discrete('Mean fraction size') +
    labs(x='13C atom % excess (truth)',
         y='13C atom % excess\n(truth - estimate)') +
    theme_bw()

p_qSIPWarning message:
“Removed 1 rows containing missing values (geom_linerange).”

## Delta-BD_____no_output_____

dBD_files = list.files(path=workDir, pattern='*_dBD.txt', full.names=TRUE, recursive=TRUE)
dBD_files %>% length %>% print[1] 240
df_dBD = list()
for(F in dBD_files){
    tmp = read.delim(F, sep='\t')
    FF = strsplit(F, '/') %>% unlist
    FFl = length(FF)
    tmp$percIncorp = FF[FFl-3]
    tmp$frac_mu = FF[FFl-2]
    tmp$rep = FF[FFl-1]
    tmp$file = FF[FFl]
    df_dBD[[F]] = tmp
}

df_dBD = do.call(rbind, df_dBD)
rownames(df_dBD) = 1:nrow(df_dBD)
df_dBD %>% head(n=3) %>% print                                 taxon mean_CM_control mean_CM_treatment
1       Acaryochloris_marina_MBIC11017        1.716308          1.717000
2 Acetobacter_pasteurianus_IFO_3283-03        1.725560          1.718431
3       Acetobacterium_woodii_DSM_1030        1.709902          1.711889
  stdev_CM_control stdev_CM_treatment      delta_BD percIncorp frac_mu rep
1     0.0053270980        0.002163313  0.0006922526          0   0.003   1
2     0.0044030676        0.003998680 -0.0071298129          0   0.003   1
3     0.0004085352        0.002156095  0.0019871258          0   0.003   1
                        file
1 OTU_abs1e9_PCR_sub_dBD.txt
2 OTU_abs1e9_PCR_sub_dBD.txt
3 OTU_abs1e9_PCR_sub_dBD.txt

### Joining estimate with truth_____no_output_____

df.j = inner_join(df_dBD, df_shift, c('taxon' = 'taxon',
                                       'percIncorp'='percIncorp',
                                       'frac_mu'='frac_mu',
                                       'rep'='rep')) %>%
    filter(!is.na(delta_BD)) %>%
    mutate(true_incorporator = ifelse(median_true_BD_shift > 0.002, TRUE, FALSE),
           true_atom_fraction_excess = median_true_BD_shift / 0.036,
           atom_fraction_excess = delta_BD / 0.036)
df.j %>% head(n=3)Warning message in inner_join_impl(x, y, by$x, by$y, suffix$x, suffix$y):
“joining factors with different levels, coercing to character vector”

### Joining with MR-HR-SIP_____no_output_____

df.j = left_join(df.j, df_incorp, c('taxon' = 'taxon',
                                    'percIncorp'='percIncorp',
                                    'frac_mu'='frac_mu',
                                    'rep'='rep')) %>%
    mutate(HWHRSIP_incorp = ifelse(is.na(HWHRSIP_incorp), FALSE, TRUE))

df.j %>% nrow %>% print
df.j %>% head(n=3)Warning message in left_join_impl(x, y, by$x, by$y, suffix$x, suffix$y):
“joining factor and character vector, coercing into character vector”

### Plotting results_____no_output_____

# difference between true and estimated
tmp1 = df.j %>%
    mutate(delta_excess = atom_fraction_excess * 100 - true_atom_fraction_excess * 100) %>%
    group_by(percIncorp, frac_mu) %>%
    summarize(mean_delta_excess = mean(delta_excess),
              sd_delta_excess = sd(delta_excess)) %>%
    ungroup() %>%
    mutate(percIncorp = percIncorp %>% reorder(percIncorp %>% as.numeric),
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.numeric),
           incorp_called = 'No filter')

tmp2 = df.j %>%
    filter(HWHRSIP_incorp == TRUE) %>%    # just MW-HR-SIP incorporators
    mutate(delta_excess = atom_fraction_excess * 100 - true_atom_fraction_excess * 100) %>%
    group_by(percIncorp, frac_mu) %>%
    summarize(mean_delta_excess = mean(delta_excess),
              sd_delta_excess = sd(delta_excess)) %>%
    ungroup() %>%
    mutate(percIncorp = percIncorp %>% reorder(percIncorp %>% as.numeric),
           frac_mu = frac_mu %>% reorder(frac_mu %>% as.numeric),
           incorp_called = 'MW-HR-SIP filter')

# combining tables
df.j.dis.dBD = rbind(tmp1, tmp2) %>%
    mutate(incorp_called = factor(incorp_called, levels=c('No filter', 'MW-HR-SIP filter')))

# plotting
options(repr.plot.width=8, repr.plot.height=4)
p_dBD = ggplot(df.j.dis.dBD, aes(percIncorp, mean_delta_excess,
                      color=frac_mu, group=frac_mu,
                      ymin=mean_delta_excess-sd_delta_excess,
                     ymax=mean_delta_excess+sd_delta_excess)) +
    geom_line() +
    geom_linerange(alpha=0.4, size=1) +
    geom_point() +
    facet_grid(incorp_called ~ .) +
    scale_color_discrete('Mean fraction size') +
    labs(x='13C atom % excess (truth)',
         y='13C atom % excess\n(truth - estimate)') +
    theme_bw()

p_dBD _____no_output_____

## Combined plot_____no_output_____

df.jj = rbind(df.j.dis.qSIP %>% mutate(method='qSIP'),
              df.j.dis.dBD %>% mutate(method='Delta BD')) %>%
    mutate(method = gsub('qSIP', 'q-SIP', method))

p.comb = ggplot(df.jj, aes(percIncorp, mean_delta_excess,
                      color=frac_mu, group=frac_mu,
                      ymin=mean_delta_excess-sd_delta_excess,
                     ymax=mean_delta_excess+sd_delta_excess)) +
    geom_line() +
    geom_linerange(alpha=0.5, size=1) +
    geom_point() +
    scale_color_discrete('Mean fraction size') +
    labs(x='atom % excess 13C (truth)',
         y='atom % excess 13C\n(truth - estimate)') +
    facet_grid(incorp_called ~ method) +
    theme_bw()
p.combWarning message:
“Removed 1 rows containing missing values (geom_linerange).”outF = file.path(workDir, 'atomIncorp_fracSize_BDshift.pdf')
ggsave(outF, p.comb, width=8, height=4)
cat('File written:', outF, '\n')Warning message:
“Removed 1 rows containing missing values (geom_linerange).”outF = file.path(workDir, 'atomIncorp_fracSize_BDshift.jpeg')
ggsave(outF, p.comb, width=8, height=4)
cat('File written:', outF, '\n')Warning message:
“Removed 1 rows containing missing values (geom_linerange).”


# Notebook from gorkazl/NetDynFlow
Path: Examples/Tutorial_1_Basics.ipynb

# Tutorial: Dynamic communicability and flow. Principles and basic examples.
Authors: **Matthieu Gilson** and **Gorka Zamora-López**

---------------------

This notebook is an introductory tutorial for the use of dynamic communicability and flow to study complex networks.

#### Outline
* DynComFlow library installation.
* Overview of basic concepts.
* Example-1: Path graphs.
* Example-2: Ring-lattice and randomization.

--------------------

### Dependencies

The tutorial requires a Python 3.X installation together with NumPy and Matplotlib libraries. It also requires further in-house modules. [GAlib](https://github.com/gorkazl/pyGAlib) is a generic library for graph analysis. [NetDynFlow](https://github.com/mb-BCA/NetDynFlow) to estimate and study dynamic communicability and flow.
_____no_output_____#### Import the main libraries

To get started, the first thing we need is to load the libraries we will need to work. Start importing the built-in and third party libraries.

_____no_output_____

from __future__ import division, print_function

from timeit import default_timer as timer
#import matplotlib.pyplot as plt
import matplotlib.pyplot as plt

%matplotlib inline
import numpy as np
import numpy.linalg
_____no_output_____

#### Import personal libraries

The following cells will check whether *GAlib* and *NetDynFlow* are already installed in the current working environment, otherwise, `pip` will be called for the installation (a working internet connection is required). **NOTE**: The exclamation mark ( ! ) before ``pip install galib`` indicates the Jupyter Notebook that the current line is a system command, which is then treated as if it were run from a terminal.
_____no_output_____

## Check if GAlib is installed in the current environment, otherwise install
try:
    import galib
except:
    # Install from PyPI
    print('Installing GAlib...')
    ! pip install galib
    import galib_____no_output_____import galib.models
import galib.tools_____no_output_____## Check if NetDynFlow is installed in the current environment, otherwise install
try:
    import netdynflow
except:
    # Install from the GitHub repository
    print('Installing NetDynFlow...')
    ! pip install git+https://github.com/mb-BCA/NetDynFlow.git@master

import netdynflow as ndf_____no_output_____# Explore the help functions of NetDynFlow
# ndf?
# ndf.core?
# ndf.metrics?_____no_output_____

## Overview of basic concepts

The aim of the present framework is to study and to characterise complex networks overcoming limitations of graph theory, specially concerning the study of weighted and directed networks. We characterise the structure by observing how perturbations propagate along a network. To do so, we need to assume a dynamic model on the network governing that propagation. We assume that every node behaves as a linear Ornstein-Uhlenbeck process fed by Gaussian noise. Given that $A$ is the connectivity matrix (weighted or unweighted, directed or undirected) the dynamics of a node follows:

$\dot{x}_i = - \frac{x_i}{\tau} + \sum_{j=1}^N x_j + \xi_i $

where $\tau$ is a time-constant and $\xi_i$ is a Gaussian white noise, independent at each node. The role of $\tau$ is to control the rate of leakage through the nodes, thus counter-balancing the tendency of the linear connectivity to cause the divergence of the system.

**Dynamic flow** quantifies the generalised interactions between nodes over time in a network for arbitrary noisy inputs, considering either non-identical noise levels or even correlated inputs. **Dynamic communicability** corresponds to the special case in which all nodes receive independent noise of unit variance. Thus, dynamic communicability quantifies the network response to a unit stimulus (via the Green function). The following examples will emphasize a few basic properties:

- Dynamic communicability captures the evolution of the global network feedback over (integration) time.
- Node-wise input and output communicabilities capture the listening and broadcasting nature of nodes.
- Two nodes without a direct connection use to have non-zero interactions due to the network effects.
- Along integration time the dynamic communicability matrix is first dominated by node weights, then by network feedback and finally it becomes homogeneous.

For further reference and citation, see:

- M. Gilson, N. Kouvaris, G. Deco and G. Zamora-Lopez "*[Framework based on communicability and flow to analyze complex networks](https://journals.aps.org/pre/abstract/10.1103/PhysRevE.97.052301)*" Phys. Rev. E **97**, 052301 (2018).
- M. Gilson, N. Kouvaris, et al. "*[Network analysis of whole-brain fMRI dynamics: A new framework based on dynamic communicability](https://doi.org/10.1016/j.neuroimage.2019.116007)*" NeuroImage **201**, 116007 (2019).

_____no_output_____

## Example 1: First steps,  path (di)graphs

### Dynamic flow
We start by studying the flow and dynamic communicability on very simple graphs in order to understand basic concepts. Consider a chain graphs as the one with directed arcs in the figure below. We will characterise how a perturbation applied to the first node propagates along the chain.

In this first example we will:
1. Create the digraph,
2. calculate its spectral diameter to estimate the largest time-constant $\tau_{max}$ acceptable,
3. apply a perturbation at the first node,
4. explore the dynamic flow matrices over time, and
5. visualise the node-wise input and output flows._____no_output_____

# Create a directed path graph of N = 6 nodes
N = 6
dirnet = galib.models.PathGraph(N, directed=True)

# Find the spectral radius and the largest time constant
evs = numpy.linalg.eigvals(dirnet)
evsnorms = np.zeros(N, float)
for i in range(N):
    evsnorms[i] = numpy.linalg.norm(evs[i])
evmax = evsnorms.max()
maxtau = 1./evmax

print( 'Spectral radius:\t%2.5f' %evmax )
print( 'Largest possible tau:\t%2.5f' %maxtau )Spectral radius:	0.00000
Largest possible tau:	inf

For a given network represented by $A$, the tendency of the network dynamics to diverge is represented by the largest eigenvalue (spectral radious) $\lambda_{max}$ of $A$. To assure the convergence of the system the leakage along each node needs to counterbalance this tendency. For that, the condicion $\tau < \tau_{max}$ with $\tau_{max} = 1 \,/\, \lambda_{max}$ needs to be fulfilled.
In this particular case, all the eigenvalues of the directed path graph are zero, thus, $\tau_{max} = \infty$ and the system converges for any value of $\tau$. But this will not be the case in general.

In order to have a flow, we need to define an _**external perturbation**_, which is encoded in the $\Sigma$ matrix as the covariances between the external Gaussian noisy inputs. Since here we only want to excite the first node, $\Sigma$ will be all zeros exept for the element $\Sigma_{00} = 1$._____no_output_____

# Set the matrix of noisy inputs
sigma = np.zeros((N,N), np.float)
sigma[0,0] = 1.0

# Set a time-constant
tau = 1.0

# Define the temporal resolution for the "simulation"
tfinal = 10
dt = 0.01
tpoints = np.arange(0.0, tfinal+dt, dt)
nsteps = len(tpoints)

# Calculate the dynamic flow for a time span between 0 and tmax
fullflow_dir = ndf.FullFlow(dirnet, tau, sigma, tmax=tfinal, timestep=dt, normed=True)
dynflow_dir = ndf.DynFlow(dirnet, tau, sigma, tmax=tfinal, timestep=dt, normed=True) _____no_output_____# Visualise the flow matrices at different times
plt.figure()
# Check for (temporal) indices: 0, 100, 200, 400, 600, 800
tidx = 100
plt.imshow(dynflow_dir[tidx])
plt.colorbar()
_____no_output_____# Calculate the in-/out- full-flow for every node
# infullflow and outfullflow are two arrays of shape (N,nsteps)
infullflow_dir, outfullflow_dir = ndf.NodeEvolution(fullflow_dir)

# Plot a figure wit the input and output flows of every node
plt.figure()
plt.subplot(2,1,1)
plt.title('Full-flow for every node. tau = %1.2f' %tau, fontsize=12)
for i in range(N):
    plt.plot(tpoints, infullflow_dir[:,i], label='i = %d' %(i+1))
plt.ylim(-0.005,0.1)
plt.ylabel('in-flow', fontsize=14)
plt.legend(frameon=False)

plt.subplot(2,1,2)
for i in range(N):
    plt.plot(tpoints, outfullflow_dir[:,i], label='i = %d' %(i+1))
plt.ylabel('out-flow', fontsize=14)
plt.xlabel('Time (a.u.)', fontsize=14)_____no_output_____# Calculate the in-/out- dynamic-flow for every node
# inflow and outflow are two arrays of shape (N,nsteps)
inflow_dir, outflow_dir = ndf.NodeEvolution(dynflow_dir)

# Plot the input and output full-flow of every node
plt.figure()
plt.subplot(2,1,1)
plt.title('Dynamic-flow for every node. tau = %1.2f' %tau, fontsize=12)
for i in range(N):
    plt.plot(tpoints, inflow_dir[:,i], label='i = %d' %(i+1))
plt.ylim(-0.005,0.1)
plt.ylabel('in-flow', fontsize=14)
plt.legend(loc='center right', frameon=False)

plt.subplot(2,1,2)
for i in range(N):
    plt.plot(tpoints, outflow_dir[:,i], label='i = %d' %(i+1))
plt.ylabel('out-flow', fontsize=14)
plt.xlabel('Time (a.u.)', fontsize=14)
_____no_output_____

### Dynamic communicability

Flow is the general concept since it doesn't make any specific assumptions on which nodes are excited and even accepts correlated inputs across nodes (encoded as the extra-diagonal entries in $\Sigma$). We name *dynamic communicability* to the special case of the flow in which every node is perturbed by independent Gaussian noise of unit variance. The idea is illustrated as follows:

In this case, $\Sigma$ is the unitary matrix **1**. Therefore, the `ndf.DynCom()` function does not require the $\Sigma$ matrix as an explicit input.

We will now repeat the previous analysis for the new perturbation scheme._____no_output_____

# Calculate the dynamic communicability for a time span between 0 and tmax
dyncom_dir = ndf.DynCom(dirnet, tau, tmax=tfinal, timestep=dt, normed=True)_____no_output_____# Visualise the dynamic communicability matrices at different times
plt.figure()
# Check for (temporal) indices: 0, 100, 200, 400, 600, 800
tidx = 100
plt.imshow(dyncom_dir[tidx])
plt.colorbar()
_____no_output_____# Calculate the in-/out- dynamic communicability for every node
# indyncom and outdyncom are two arrays of shape (N,nsteps)
indyncom_dir, outdyncom_dir = ndf.NodeEvolution(dyncom_dir)

# Plot the input and output dynamic communicability of every node
plt.figure()
plt.subplot(2,1,1)
plt.title('Dynamic communicability for every node. tau = %1.2f' %tau, fontsize=12)
for i in range(N):
    plt.plot(tpoints, indyncom_dir[:,i], label='i = %d' %(i+1))
plt.ylabel('in-communic.', fontsize=14)
plt.legend(frameon=False)

plt.subplot(2,1,2)
for i in range(N):
    plt.plot(tpoints, outdyncom_dir[:,i], label='i = %d' %(i+1))
plt.ylabel('out-communic.', fontsize=14)
plt.xlabel('Time (a.u.)', fontsize=14)
_____no_output_____

We end this introductory example by repeating the calculations on chains with _**undirected links**_ and following the same perturbation protocols.

In this case, the perturbations can diffuse in both directions. Also, the largest eigenvalue $λ_{max}$ of the path graph is finite, thus imposing a limitation on $\tau$.
$$$$
_____no_output_____

# Create the undirected path graph of N = 6 nodes
N = 6
undnet = galib.models.PathGraph(N, directed=False)

# Find the spectral radius and the largest time constant
evs = numpy.linalg.eigvals(undnet)
evsnorms = np.zeros(N, float)
for i in range(N):
    evsnorms[i] = numpy.linalg.norm(evs[i])
evmax = evsnorms.max()
maxtau = 1./evmax

print( 'Spectral radius:\t%2.5f' %evmax )
print( 'Largest possible tau:\t%2.5f' %maxtau)Spectral radius:	1.80194
Largest possible tau:	0.55496
# Set the matrix of noisy inputs
sigma = np.zeros((N,N), np.float)
sigma[0,0] = 1.0
# Set a time-constant
tau = 0.45

# Define the temporal resolution
tfinal = 10
dt = 0.01
tpoints = np.arange(0.0, tfinal+dt, dt)
nsteps = len(tpoints)

# Calculate the dynamic flow for a time span between 0 and tmax
dynflow_und = ndf.DynFlow(undnet, tau, sigma, tmax=tfinal, timestep=dt, normed=True)
# Calculate the dynamic communicability for a time span between 0 and tmax
dyncom_und = ndf.DynCom(undnet, tau, tmax=tfinal, timestep=dt, normed=True)_____no_output_____# Visualise the dynamic communicability matrices at different times
plt.figure(figsize=(10,3.8))

# Check for (temporal) indices: 0, 100, 200, 400, 600, 800
tidx = 100

# Plot the flow matrix
plt.subplot(1,2,1)
plt.title('Dynamic flow', fontsize=14)
plt.imshow(dynflow_und[tidx], aspect='auto')
plt.colorbar()

# Plot the flow matrix
plt.subplot(1,2,2)
plt.title('Dynamic communicability', fontsize=14)
plt.imshow(dyncom_und[tidx], aspect='auto')
plt.colorbar()

#plt.tight_layout()
_____no_output_____# Calculate the in-/out- dynamic flow and communicability for every node
inflow_und, outflow_und = ndf.NodeEvolution(dynflow_und)
indyncom_und, outdyncom_und = ndf.NodeEvolution(dyncom_und)

# Plot the input and output results for every node
plt.figure(figsize=(10,5))
# Plot the results for the in-/out-flow
plt.subplot(2,2,1)
plt.title('Dynamic-flow for every node. tau = %1.2f' %tau, fontsize=12)
for i in range(N):
    plt.plot(tpoints, inflow_und[:,i], label='i = %d' %(i+1))
#pp.ylim(-0.005,0.1)
plt.ylabel('in-flow', fontsize=14)
plt.legend(loc='center right', frameon=False)

plt.subplot(2,2,3)
for i in range(N):
    plt.plot(tpoints, outflow_und[:,i], label='i = %d' %(i+1))
plt.ylabel('out-flow', fontsize=14)
plt.xlabel('Time (a.u.)', fontsize=14)

# Plot the results for the in-/out-dynamic communicability
plt.subplot(2,2,2)
plt.title('Dynamic communicability for every node. tau = %1.2f' %tau, fontsize=12)
for i in range(N):
    plt.plot(tpoints, indyncom_und[:,i], label='i = %d' %(i+1))
plt.ylabel('in-communic.', fontsize=14)
plt.legend(loc='center right', frameon=False)

plt.subplot(2,2,4)
for i in range(N):
    plt.plot(tpoints, outdyncom_und[:,i], label='i = %d' %(i+1))
plt.ylabel('out-communic.', fontsize=14)
plt.xlabel('Time (a.u.)', fontsize=14)

plt.tight_layout()
_____no_output_____

## Example 2: Weighted ring lattices and comparison to surrogates

Now we will consider ring networks where links are weighted. We will:

1. Create and study a directed ring with random link weights.
2. Create and study an undirected ring with random link weights.
3. Compare to weighted open loops (weighted path graphs).
4. Randomise links and compare.
5. Add further connectivity.

### Directed ring with weighted links

We start by studying a directed ring whose links are weighted, see illustration below. The link weights will be randomly assigned from a normal distribution with mean `c0` and variance `w_spread`. In this case the global feedback is thus controled by `c0` and the heterogeneity of weights by `w_spread`.

Setting the number of nodes to *N = 4* would reproduce results in Figs. 2 and 3 in the paper:  Gilson M, Kouvaris NE, Deco G, Zamora-López G (2018) *Phys Rev E* **97**, 052301 (https://doi.org/10.1103/PhysRevE.97.052301).

_____no_output_____

# Create a directed ring with random link weights
N = 10
c0 = 0.8
w_spread = 0.4

C_dir = np.zeros([N,N])
for i in range(N):
    C_dir[i-1,i] = c0 * (1 + 2*w_spread*(np.random.rand() - 0.5))

# Visualise the connectivity matrix
axticks = np.arange(0,N,2)

plt.figure()
plt.title('Weighted, directed ring', fontsize=14)
plt.imshow(C_dir, interpolation='nearest')
plt.xticks(axticks, axticks+1, fontsize=10)
plt.yticks(axticks, axticks+1, fontsize=10)
plt.colorbar()
plt.xlabel('Target node', fontsize=14)
plt.ylabel('Source node', fontsize=14)

_____no_output_____# Calculate the dynamic communicability
# Define the temporal resolution for the "simulation"
tfinal = 20
dt = 0.2
tpoints = np.arange(0.0, tfinal+dt, dt)
nsteps = len(tpoints)

# Set a time-constant for the leakage at the nodes
tau = 1.0

# Calculate dynamic communicability
dc_dir = ndf.DynCom(C_dir, tau, tmax=tfinal, timestep=dt)

# Visualise the communicability matrix at three different integration times
plt.figure(figsize=(9,3))
axticks = np.arange(0,N,2)
for idx, t in enumerate([1,3,6]):
    plt.subplot(1,3,idx+1)
    plt.title('t = %1.1f' %t, fontsize=12)
    plt.imshow(dc_dir[int(t/dt)], interpolation='nearest', aspect='auto', cmap='Reds')
    plt.clim(0, 0.04)
    plt.xticks(axticks, axticks+1, fontsize=10)
    plt.yticks(axticks, axticks+1, fontsize=10)
    plt.colorbar(ticks=[0,0.01,0.02, 0.03, 0.04])
    plt.xlabel('Target node', fontsize=14)
    if idx==0: plt.ylabel('Source node', fontsize=14)

plt.tight_layout()
_____no_output_____

#### Network and node-wise characterization with dynamic communicability

The coarse-grained, general behaviour of the network over time can be evaluated by _**total communicability**_, which is defined as the sum of all pair-wise interactions between nodes. Thus, total communicability characterises the global network feedback. In this example it depends on the mean link weight `c0`.

The communicability passing through a node depends not only on its closest neighbours, but reflects the role that each node plays in the global communication of the network. The temporal evolution of a node's communicability is calculated as the "row" or "column" sum of the communicability matrix, at each time point. If the network is symmetric, both sums will be the same. If the network is directed (asymmetic), then input (column sum) and output (row sum) communicabilies reflect a node's capacity to integrate or to broadcast information respectively.

In the following, we will show the total and node-wise communicability of the weigthed and directed ring we just created._____no_output_____

# Calculate total communicability. Use the generic function to estimate tensor sum over time.
totcomm_dir = ndf.TotalEvolution(dc_dir)

plt.figure()
plt.plot(tpoints, totcomm_dir, color='red')
plt.xlabel('integration time', fontsize=14)
plt.ylabel('total communicability', fontsize=14)
_____no_output_____# Calculate the input and output communicabilities for each node, over integration time
# Use the generic function to estimate node-wise averages from the time-flow tensor
in_dc, out_dc = ndf.NodeEvolution(dc_dir)

# Visualise the in-/out-communicabilities as an image
xtick_idx = np.arange(0,nsteps+1,10)
xtick_labs = tpoints[xtick_idx]

# Plot the input communicabilities
plt.figure(figsize=(8,3))
plt.imshow(in_dc.T, aspect='auto', cmap='Reds', vmin=0, vmax=0.1)
plt.xticks(xtick_idx, xtick_labs)
plt.xlabel('Integration time',fontsize=14)
plt.ylabel('Node', fontsize=14)
plt.colorbar()
plt.title('Input communicability', fontsize=14)

# Plot the output communicabilities
plt.figure(figsize=(8,3))
plt.imshow(out_dc.T, aspect='auto', cmap='Reds', vmin=0, vmax=0.1)
plt.xticks(xtick_idx, xtick_labs)
plt.xlabel('Integration time', fontsize=14)
plt.ylabel('Node',fontsize=14)
plt.colorbar()
plt.title('Output communicability', fontsize=14)
_____no_output_____

#### Diversity

As we saw before, the communicability matrices change over time. Initially the interactions tend to be aligned with the links in the connectivity matrix. At later integration times the interactions capture nontrivial network feedback, and finally the pattern of interactions homogenises. We quantify the heterogeneity of pair-wise communicabiltiy values as the coefficient of variance in the communicability matrix at each time; a metric we refer as _**diversity**_.

We now show a direct comparison between the original link weights in the connectivity matrix `C` and the values that the pair-wise communicability take; illustrating its process of homogeneization over time._____no_output_____

# Plot the comparison between the connectivity weights and communicability over time.
plt.figure()
for i, t in enumerate([1,3,6]):
    plt.subplot(1,3,i+1)
    plt.plot(C_dir, dc_dir[int(t/dt)], '.r')
    if i==1:
        plt.xlabel('Connectivity weight (C)', fontsize=14)
    if i==0:
        plt.ylabel('Pair-wise communicability', fontsize=14)
    else:
        plt.yticks([])
    plt.axis(xmin=-0.001, xmax=1, ymin=0.001, ymax=0.04)
    plt.title('t = %1.1f' %t)

# Compute the diversity over time from the dyn. comm. tensor
divers_dir = ndf.Diversity(dc_dir)

# Plot the temporal evolution of the diversity
plt.figure()
plt.plot(tpoints, divers_dir, color='r')
plt.axis(ymin=0)
plt.xlabel('Integration time', fontsize=14)
plt.ylabel('Diversity', fontsize=14)
_____no_output_____

### Undirected ring with weighted links

We now repeat the analysis considering the ring network is **undirected**, as in the figure below. The undirected ring will be constructed symmetrising the directed ring we created before. **Notice that** the symmetrization conserves the total weight of matrix.

_____no_output_____

# Create the symmetric version of the original directed ring
C_sym = 0.5 * (C_dir + C_dir.T)

# Visualise the new connectivity matrix
axticks = np.arange(0,N,2)

plt.figure()
plt.title('Weighted, undirected ring', fontsize=14)
plt.imshow(C_sym, interpolation='nearest')
plt.xticks(axticks, axticks+1, fontsize=10)
plt.yticks(axticks, axticks+1, fontsize=10)
plt.colorbar()
plt.xlabel('Target node', fontsize=14)
plt.ylabel('Source node', fontsize=14)

_____no_output_____# Compare the dynamic communicability for directed and undirected cases
# Calculate DC for the symmetric network - using same temporal resolution
dc_sym =  ndf.DynCom(C_sym, tau, tmax=tfinal, timestep=dt)

# Calculate total communicability. Use the generic function to estimate tensor sum over time.
totcomm_sym = ndf.TotalEvolution(dc_sym)

# Visualise the comparison
plt.figure()
plt.plot(tpoints, totcomm_dir, c='r', label='Directed ring')
plt.plot(tpoints, totcomm_sym, c='m', label='Undirected ring')
plt.legend(frameon=False, fontsize=12)
plt.xlabel('Integration time', fontsize=14)
plt.ylabel('Total communicability', fontsize=14)
_____no_output_____

As seen, the undirected ring achieves larger total communicability due to the reinforced recurrent feedback created when making the links bidirectional.
Finally, we confirm that in the symmetrised case the input and output node communicabilities are symmetric, and different from the original directed case._____no_output_____

# Calculate the communicabilities for each node, over integration time
# Use the generic function to estimate node-wise averages from the time-flow tensor
in_dcsym, out_dcsym = ndf.NodeEvolution(dc_sym)

# Visualise the in-/out-communicabilities as an image
xtick_idx = np.arange(0,nsteps+1,10)
xtick_labs = tpoints[xtick_idx]

# Plot the input communicabilities
plt.figure(figsize=(8,3))
plt.imshow(in_dcsym.T, aspect='auto', cmap='Reds', vmin=0, vmax=0.1)
plt.xticks(xtick_idx, xtick_labs)
plt.xlabel('Integration time',fontsize=14)
plt.ylabel('Node', fontsize=14)
plt.colorbar()
plt.title('Input communicability', fontsize=14)

# Plot the output communicabilities
plt.figure(figsize=(8,3))
plt.imshow(out_dcsym.T, aspect='auto', cmap='Reds', vmin=0, vmax=0.1)
plt.xticks(xtick_idx, xtick_labs)
plt.xlabel('Integration time', fontsize=14)
plt.ylabel('Node',fontsize=14)
plt.colorbar()
plt.title('Output communicability', fontsize=14)

_____no_output_____

### Weighted open loops (path graphs)

Finally, we will compare the results obtained for the loops to those of open chains, as the ones studied in the Example-1 but with the same link weights as in the present examples. To create the open chains we will remove the links between the first and the last node in the two loops generated. The link weights will also be rescaled in order to assure all the networks have equal total weight.

We will (i) calculate their dynamic communicability and diversity, and (ii) plot the comparison of all the cases._____no_output_____

# Create the directed open chain from the directed loop
C_opendir = np.copy(C_dir)
C_opendir[-1,0] = 0
# Renormalise the total weight
C_opendir *= C_dir.sum() / C_opendir.sum()

# Create the undirected open chain from the symmetric loop
C_opensym = np.copy(C_sym)
C_opensym[-1,0] = 0
C_opensym[0,-1] = 0
# Renormalise the total weight
C_opensym *= C_sym.sum() / C_opensym.sum()

print( 'Confirm total weights:' )
print( C_dir.sum(), C_opendir.sum(), C_sym.sum(), C_opensym.sum() )Confirm total weights:
7.943118027451687 7.943118027451688 7.943118027451686 7.943118027451686
# Calculate dynamic communicability for the open chains
dc_opendir =  ndf.DynCom(C_opendir, tau, tmax=tfinal, timestep=dt)
dc_opensym =  ndf.DynCom(C_opensym, tau, tmax=tfinal, timestep=dt)

# Calculate the total communicabilities of the open chains
totcomm_opendir = ndf.TotalEvolution(dc_opendir)
totcomm_opensym = ndf.TotalEvolution(dc_opensym)

# Calcultate the diversities
divers_sym = ndf.Diversity(dc_sym)
divers_opendir = ndf.Diversity(dc_opendir)
divers_opensym = ndf.Diversity(dc_opensym)

# Visualise the comparisons
# Plot the total communicability
plt.figure()
plt.plot(tpoints, totcomm_dir, c='r', label='Directed ring')
plt.plot(tpoints, totcomm_opendir, c='r', ls='--', label='Directed path')
plt.plot(tpoints, totcomm_sym, c='m', label='Undirected ring')
plt.plot(tpoints, totcomm_opensym, c='m', ls='--', label='Undirected path')
plt.legend(frameon=False, fontsize=12)
plt.xlabel('Integration time', fontsize=14)
plt.ylabel('Total communicability', fontsize=14)

# GORKA: I ADDED THE DIVERSITY BUT... NOT SURE IT WAS A GOOD IDEA
# Plot the diversity
plt.figure()
plt.plot(tpoints, divers_dir, c='r', label='Directed ring')
plt.plot(tpoints, divers_opendir, c='r', ls='--', label='Directed path')
plt.plot(tpoints, divers_sym, c='m', label='Undirected ring')
plt.plot(tpoints, divers_opensym, c='m', ls='--', label='Undirected path')
plt.legend(frameon=False, fontsize=12)
plt.xlabel('Integration time', fontsize=14)
plt.ylabel('Total communicability', fontsize=14)
_____no_output_____

TO BE CONTINUED ..._____no_output_____