#' ---
#' title: "Uvod u R"
#' subtitle: Kontrola toka i oblikovanje podataka
#' author:
#'    - "Milutin Pejovic, Petar Bursac"
#' date: "`r format(Sys.time(), '%d %B %Y')`"
#' output:
#'    html_document:
#'      keep_md: true
#'      theme: "simplex"
#'      highlight: tango
#'      toc: true
#'      toc_depth: 5
#'      toc_float: true
#'      fig_caption: yes
#' ---
#'
#' # Kontrola toka u R-u
#'
#' U okviru ovog predavanja upoznaćemo se sa mogućnostima kontrole toka i automatizacije izvršavanja komandi primenom komandi `if` i `for`.
#'
#' ## `if` - grananje toka
#'
#' Komanda `if` omogućava da postavimo uslov kojim će izvršavanje neke komande zavisiti od rezultata nekog logičkog upita. Na taj način moguće je stvoriti razgranatu strukturu toka izvršavanja komandi (algoritma). Komanda `if` se koristi na sledeći način:
#'
#' $$if(condition) \quad \textrm{{true_expression}} \quad elsе \quad \textrm{{false_expression}}$$
#'
#' Na primer, ukoliko promenljiva `x` sadrži numerički podatak, podeliti ga sa 2, a ukoliko je neki drugi podatak ispisati "Nije moguće izvršiti komandu jer x ne sadrži numerički podatak"
#'
#'
x <- 5
if(is.numeric(x)) x/2 else print("Nije moguće izvršiti komandu jer x ne sadrži numerički podatak")

x <- "a"
if(is.numeric(x)) x/2 else print("Nije moguće izvršiti komandu jer x ne sadrži numerički podatak")
#'
#' Ukoliko ne postoji određena operacija koja se uzvršava u slučaju `else`, nije potrebno pisati taj deo. Na primer:
#'
x <- 5
if(is.numeric(x)) x/2

x <- "a"
if(is(x, "numeric")) x/2

#'
#' Međutim, pravi smisao `if` komande se vidi tek kada očekujemo da prilikom izvršavanja niza komandi računar sam odluči šta treba uraditi u određenom trenutku u zavisnosti od ulazinih parametara.
#'
#'
#' ## `for` petlja
#'
#' `for` petlja se koristi kada želimo da automatizujemo izvršavanje neke komande ili niza komandi određeni broj puta. `for` petlja se koristi na sledeći način:
#'
#' $$ for(i \quad in \quad list) \quad \textrm{{expression}} $$
#'
#' Na primer, ako želimo da podacima `studenti` dodamo jednu kolonu pod nazivom "ispit" u vidu logičkog vektora koji će sadržati vrednost TRUE za studente koji su položili oba ispita (IG1 i Praksu) i FALSE za one koji nisu.
#'
#'
#'
studenti <- read.csv(file = "D:/R_projects/Nauka_R/Slides/data/Students_IG1.txt", header = TRUE, stringsAsFactors = FALSE)

studenti$ispit <- NA # Prvo cemo kreirati kolonu "ispit" koja ima sve NA vrednosti

# Komanda dim(studenti) vraca broj dimenzija, prvi se odnosi na broj vrsta.
dim(studenti)

for(i in 1:dim(studenti)[1]){
  # Sekvenca `1:dim(studenti)[1]` sadrzi niz brojeva od 1 do ukupnog broja vrsta u data.frame-u studenti.
  # i ide kroz svaku vrstu data.frame-a `studenti`
  studenti$ispit[i] <- if(is.na(studenti$Ocena[i]) | is.na(studenti$Praksa[i])){FALSE}else{TRUE}
}

head(studenti, 15)

sum(studenti$ispit) # Koliko studenata je polozilo oba ispita

#'
#'
#' >
Zadatak

#'
#' > Potrebno je oblikovati fajl sa podacima nivelanja geometrijskim nivelmanom. Fajl sadrži sledeće informacije:
#'
#' > > + Prva kolona: tacka - sadrzi imena repera i veznih tačaka. Reperi su B1, B3, B4 i B5, a vezne tacke su 1:n.
#'
#' > > + Druga kolona: merenje - sadrži čitanja na letvi.
#'
#' > > + Treća kolona: duzina - sadrži merenu dužinu od instrumenta do letve.
#'
#' > U zadatku se traži sledeće:
#' > 1. Učitati podatke
#'
#' > 2. Kreirati `data.frame` sa sledećim kolonama: od, do, dh i dužina. Pri tome, treba imati u vidu da je na svakoj stanici merena visinska razlika po principu zadnja-prednja-prednja-zadnja. To znači da svakoj stanici pripada po četiri reda izvornih podataka. Rezultujući `data.frame` treba da sadrži po jedan red po stanici, koji će u prvoj koloni ("od") imati "zadnju" tačku, u drugoj koloni ("do") imati prednju tačku, u trećoj koloni ("dh") imati srednju visinsku razliku sračunatu iz čitanja na letvi i u poslednjoj koloni ("duzina") imatu srednju dužinu od letve do letve sračunatu iz merenih dužina. Prilikom rada, pokušajte da se vodite sledećim principima:
#'
#'
#' > > *Generalna preporuka je da podelite zadatak na više manjih zadataka. Na primer, izdvojite podatke za samo jednu stanicu i pokušajte da kreirate željeni `data.frame` sa podacima samo jedne stanice. Uvidite šta su problemi u tom slučaju. Na početku, napravite rezultujući `data.frame` koji će sadržati samo `NA` vrednosti koje će biti zamenjene vrednostima koje treba da sadrži. Taj `data.frame` će imati broj redova onoliko koliko ima stanica i četiri kolone. Za te potrebe možete koristiti komandu `rep(NA, broj stanica)` za svaku kolonu `data.frame`-a. Komanda `rep` (repeat) pravi vektor određene dužine od vektora (sa jednom ili više) vrednosti. Za tim u taj `data.frame` upišite vrednosti koje izračunate ili isčitate iz ulaznih podataka. Kada rešite problem za jednu stanicu, razmislite kako ćete to automatizovati koristeći petlju `for`. Tu je preporuka da u ulaznim podacima napravite još jednu kolonu (faktorsku) koja će označiti koji red pripada kojoj stanici. Pa zatim, transformisati ulazne podatke u `list`-u prema toj promenljivoj i onda u okviru petlje `for`, na svaki član liste primeniti korake koje ste smislili kada ste rešavali samo jednu stanicu.*
#'
#' > 3. Za tim, potrebno je rezultujući `data.frame` dodatno transformisati (sumirati) tako da sadrži samo merenja od repera do repera, sa sledećim kolonama: od (reper), do (reper), dh (ukupna visinska razlika između repera), n (broj stanica od repera do repera) i duzina (ukupna duzina od repera do repera).
#'
#' ## Prvi način - korišćenjem petlje `for`
#'
#' ### Kreiranje prve tabele - visinske razlike po stanici
#'
#+ eval = TRUE, include = TRUE

# Ucitavanje merenja
merenja <- read.table(file = "D:/R_projects/Nauka_R/Slides/data/nivelman.txt", header = TRUE, sep = ",", stringsAsFactors = FALSE)

# Kreiranje pomocne kolone (faktorske) koja pokazuje pripadnost redova stanici
merenja$stanica <- factor(rep(1:(dim(merenja)[1]/4), each = 4))

# Kreiranje liste prema pomocnoj faktorskoj promenljivoj (stanica)
merenja_list <- split(merenja, merenja$stanica)

# Kreiranje praznog data.frame-a
merenja_df <- data.frame( od = rep(NA, length(merenja_list)),
                          do = rep(NA, length(merenja_list)),
                          dh = rep(NA, length(merenja_list)),
                          duzina = rep(NA, length(merenja_list)))

# popunjavanje data.frame-a
for(i in 1:length(merenja_list)){
  merenja_df$od[i] <- as.character(merenja_list[[i]]$tacka[1])
  merenja_df$do[i] <- as.character(merenja_list[[i]]$tacka[2])
  merenja_df$dh[i] <- ((merenja_list[[i]]$merenje[1]-merenja_list[[i]]$merenje[2])+(merenja_list[[i]]$merenje[4]-merenja_list[[i]]$merenje[3]))/2
  merenja_df$duzina[i] <- sum(merenja_list[[i]]$duzina)/2
}

merenja_df

#' Kreiranje fajla
#+ eval = FALSE
writexl::write_xlsx(merenja_df, path = "merenja_df.xlsx")

#'
#' ### Kreiranje sumarne tabele - visinske razlike od repera do repera

reperi <- c("B1", "B3", "B4", "B5")

merenja_df$isReperOD <- merenja_df$od %in% reperi
merenja_df$isReperDO <- merenja_df$do %in% reperi

# broj vis razlika izmedju repera
ndh <- sum(merenja_df$isReperOD)

#indeks pozicije "od" repera,
ind.od <- which(merenja_df$isReperOD == T)
# indeks pozicije "do" repera
ind.do <- which(merenja_df$isReperDO == T)

# Visinske razlike

# Naziv "od" repera
od <- merenja_df$od[ind.od]

# Naziv "do" repera
do <- merenja_df$do[ind.do]

# Racunanje visinskih razlika od repera do repera
dh <- rep(NA, ndh)

for(i in 1:ndh){
  dh[i] <- sum(merenja_df$dh[ind.od[i]:ind.do[i]])
}

# Racunanje broja stanica od repera do repera
n <- rep(NA, ndh)

for(i in 1:ndh){
  n[i] <- length(ind.od[i]:ind.do[i])
}

# Racunanje duzina od repera do repera
duzina <- rep(NA, ndh)

for(i in 1:ndh){
  duzina[i] <- sum(merenja_df$duzina[ind.od[i]:ind.do[i]])
}

# Kreiranje data.frame-a
merenja_df_sum <- data.frame(od = od, do = do, dh = dh, n = n, duzina = duzina)

merenja_df_sum

#' Kreiranje fajla
#+ eval = FALSE
writexl::write_xlsx(merenja_df_sum, path = "merenja_df_reperi.xlsx")

#'
#' # Transformacija podataka korišćenjem `dplyr` paketa
#'
#'
#'
#' Kao što ste već videli, oblikovanje podataka često podrazumeva kreiranje novih promenljivih (atribute ili kolone), sumiranje podataka u novu tabelu, reimenovati ili rasporediti podatke u tabeli. Za te potrebe razvijeni su brojni alati koji omogućavaju laku manupulaciju podacima, a najpoznatiji paket u R okruženju je `dplyr` paket iz `tidyverse` famijije paketa. Da bi se koristio paket `dplyr` neophodno ga je instalirati. Medjutim, preporučuje se instalacija celog `tidyverse` paketa, koji uključuje celu grupu korisnih paketa.
#'
#+ echo = FALSE, fig.align="center", out.width = '25%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/tidyverse-logo.png")
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/tidyverse_website.png")
#'
#'
#' Instalacija `tidyverse` paketa
#+ eval = FALSE
install.packages("tidyverse")
library(tidyverse)
#'
#'
#' `dplyr` paket ima nekoliko osnovnih funkcionalnosti koje rešavaju najčešće probleme, kao što su:
#'
#' **selekcija pojedinačnih merenja (instanci ili vrsta u tabeli) komandom `dplyr::filter()`**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/filter.png")
#'
#' **selekcija atributa (kolona) komandom `dplyr::select()`**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/rstudio-cheatsheet-select.png")
#'
#'
#' **Kreiranje novih promenljivih komandom `dplyr::mutate()`**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/mutate.png")
#'
#'
#' **Sumiranje podataka komandom `dplyr::summarise()`**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/summarise.png")
#'
#'
#' **Grupisanje podataka `dplyr::group_by()` (često u kombinaciji sa `summarise`)**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/group_by.png")
#'
#'
#' **Kombninovanje (spajanje) tabela komandom `dplyr::_join`**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/combine-options1.png")
#'
#'
#' **Sortiranje podataka komandom `dplyr::arrange()`**
#'
#+ echo = FALSE, fig.align="center", out.width = '70%'
knitr::include_graphics("D:/R_projects/Nauka_R/Slides/Figures/reorder-data-frame-rows-in-r.png")
#'
#'
#'
#' ## Drugi način - korišćenjem `dplyr` paketa
#'
#'
#'
#+ warning = FALSE, message = FALSE
library(tidyverse)
library(magrittr)

# Ucitavanje merenja
merenja <- read.table(file = "D:/R_projects/Nauka_R/Slides/data/nivelman.txt", header = TRUE, sep = ",", stringsAsFactors = FALSE)

merenja_df <- merenja %>%
  dplyr::mutate(stanica = factor(rep(1:(dim(merenja)[1]/4), each = 4))) %>%  # Kreiranje novih kolona
  dplyr::group_by(stanica) %>% # grupisanje podataka prema nekoj promenljivoj
  dplyr::summarise(od = as.character(tacka[1]), # sumiranje podataka
                   do = as.character(tacka[2]),
                   dh = ((merenje[1]-merenje[2])+(merenje[4]-merenje[3]))/2,
                   duzina = sum(duzina)/2)

merenja_df

#'
#'
#' >
Obrati pažnju na `%>%`

#' > Operator `%>%` (`pipe`) je moćan operator koji nam omogućava da sekvencijalno povezujemo operacije i da na taj način izbegnemo kreiranje nepotrebnih promenljivih. `Pipe` je dostupan preko paketa `magrittr`, što znači da je potrebno instalirati paket `magrittr` ako želimo da koristimo `pipe`. Međutim, `pipe` je takođe dostupan preko `tidyverse` paketa. Više detalja na [linku](https://r4ds.had.co.nz/pipes.html).
#'

#'
#' ### Kreiranje sumarne tabele - visinske razlike od repera do repera
#'
reperi <- c("B1", "B3", "B4", "B5")

merenja_df <- merenja_df %>% dplyr::mutate(isReperOD = merenja_df$od %in% reperi,
                             isReperDO = merenja_df$do %in% reperi)

#indeks pozicije "od" repera,
ind.od <- which(merenja_df$isReperOD == T)
# indeks pozicije "do" repera
ind.do <- which(merenja_df$isReperDO == T)

# broj vis razlika izmedju repera
ndh = sum(merenja_df$isReperOD)

# Promenljiva koja pokazuje koja merenja pripadaju odredjenom nivelmanskom vlaku
vlak <- c()
for(i in 1:ndh){
  vlak <- c(vlak, rep(i, ind.do[i]-(ind.od[i]-1)))
}

merenja_df$vlak <- factor(vlak)

# Sumiranje visinskih razlika po vlaku
merenja_df %>% dplyr::select(od, do, dh, duzina, vlak) %>%
               dplyr::group_by(vlak) %>%
               dplyr::summarize(dh_mean = sum(dh))

# Sumiranje visinskih razlika po vlaku sa dodatnim kolonama
merenja_df_sum <- merenja_df %>% dplyr::select(od, do, dh, duzina, vlak) %>%
                                 dplyr::group_by(vlak) %>%
                                 dplyr::summarize(od = od[1],
                                                  do = do[length(vlak)],
                                                  dh = sum(dh),
                                                  stanica = n())

merenja_df_sum

#' # Kreiranje funkcija
#'
#' R omogućava kreranje funkcija koje nam omogućavaju da automatizujemo određene korake u našem algoritmu. Kreiranje funkcija je poželjno u slučajevima kada imamo određeni deo koda koji je potrebno ponoviti više puta. Na taj način, umesto da kopiramo kod više puta, moguće je kreirati funkciju koja će izvršiti taj deo koda pozivanjem kreirane funkcije. Generalno, kreiranje funkcija se sastoji iz tri koraka:
#'
#' + Dodeljivanje `imena`
#' + Definisanje `argumenata`
#' + Programiranje `tela` funckije (body) koje se sastoji od koda koji treba da se izvrši
#'
#' Na primer ukoliko zelimo da napravimo funkciju koja pretvara decimalni zapis ugla u stepenima u radijane, to ćemo učiniti na sledeći način
#'
#+

step2rad <- function(ang_step){
  ang_step*pi/180
}

step2rad(180)

#' Ukoliko zelimo da napravimo funkciju koja pretvara decimalni zapis ugla u zapis step-min-sec to ćemo uraditi na sledeći način:

dec2dms <- function(ang){ # ime funkcije je `dec2dms`, a argument `ang`
  deg <- floor(ang)
  minut <- floor((ang-deg)*60)
  sec <- ((ang-deg)*60-minut)*60
  return(paste(deg, minut, round(sec, 0), sep = " "))
}

dec2dms(ang = 35.26589)

dec2dms(45.52658)

#'
#' Ukoliko želimo da napravimo funkciju od koda koji smo kreirali za potrebe oblikovanja ulaznih podataka to ćemo uraditi na sledeći način.
#'

merenja <- read.table(file = "D:/R_projects/Nauka_R/Slides/data/nivelman.txt", header = TRUE, sep = ",", stringsAsFactors = FALSE)

nivelman <- function(niv_merenja, reperi){ # ime funkcije je nivelman, a argumenti niv_merenja (ulazna merenja) i reperi (naziv repera)

  # kopiramo kod koji smo kreirali ()
  merenja_df <- niv_merenja %>% dplyr::mutate(stanica = factor(rep(1:(dim(niv_merenja)[1]/4), each = 4))) %>%
    dplyr::group_by(stanica) %>%
    dplyr::summarise(od = as.character(tacka[1]),
                     do = as.character(tacka[2]),
                     dh = ((merenje[2]-merenje[1])+(merenje[3]-merenje[4]))/2,
                     duzina = sum(duzina)/2)

  merenja_df <- merenja_df %>% dplyr::mutate(isReperOD = merenja_df$od %in% reperi,
                                             isReperDO = merenja_df$do %in% reperi)

  #indeks pozicije "od" repera,
  ind.od <- which(merenja_df$isReperOD == T)
  # indeks pozicije "do" repera
  ind.do <- which(merenja_df$isReperDO == T)

  # broj vis razlika izmedju repera
  ndh = sum(merenja_df$isReperOD)

  # Promenljiva koja pokazuje koja merenja pripadaju odredjenom nivelmanskom vlaku
  vlak <- c()
  for(i in 1:ndh){
    vlak <- c(vlak, rep(i, ind.do[i]-(ind.od[i]-1)))
  }

  merenja_df$vlak <- factor(vlak)

  merenja_df_sum <- merenja_df %>% dplyr::select(od, do, dh, duzina, vlak) %>%
    dplyr::group_by(vlak) %>%
    dplyr::summarize(od = od[1],
                     do = do[length(vlak)],
                     dh = sum(dh),
                     stanica = length(vlak))

  merenja_df <- merenja_df %>% dplyr::select(od, do, dh, duzina)

  results <- list(tabela_1 = merenja_df, tabela_2 = merenja_df_sum)
  return(results) # Ukoliko je u okviru funkcije kreirano više promenljivih, komandom `return` biramo koja promenljiva će biti rezultat naše funkcije
}

nivelman(niv_merenja = merenja, reperi = c("B1", "B3", "B4", "B5"))

#'
#'
#'
#' >
Zadatak 1

#' > + Učitati excel fajl sa svim merenjima u mreži (`niv_merenja.xlsx`). Fajl je organizovan tako  da su merenja u pojedinačnim vlakovima smeštena u odvojenim excel sheet-ovima. Za učitavanje excel fajla sa više sheet-ova koristiti uputstva data na linku `readxl` paketa: https://readxl.tidyverse.org/articles/articles/readxl-workflows.html#iterate-over-multiple-worksheets-in-a-workbook. Rezultat učitanih merenja je lista!
#' > + Primeniti funkciju na svim elementima liste.
#' > + Spojiti odgovarajuće tabele u jednu, tako da na kraju imamo dve tabele, jednu koja se odnosi na merenja na stanici i jednu sumarnu tabelu.

#'
#'
#' >
Zadatak 1 - Resenje 1

# Imena svih repera u mrezi:
reperi <- c("B1", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "S1", "S2", "S3", "D1", "H1", "J1", "M1", "P1", "D7", "H7", "N7", "D10", "E10", "I10", "P10", "P13", "L116",	"D19", "G19", "J19", "M19", "P19", "E26", "P26", "D29", "J29", "M29", "P29", "D32", "H32", "D38", "G38", "J38", "M38", "P38", "A11", "A12", "A13", "A14", "A21", "A22",	"A23", "A24")

# Putanja ka fajlu (3 nacina)
merenja_path <- "D:/R_projects/Nauka_R/Slides/data/niv_merenja.xlsx"
merenja_path <- here::here("data", "niv_merenja.xlsx")
files_data <- list.files("D:/R_projects/Nauka_R/Slides/data/", full.names = TRUE)
merenja_path <- files_data[6]

# Nazivi sheet-ova
nazivi_sheets <- merenja_path %>%
  readxl::excel_sheets()

# Ucitavanje svakog sheet-a i smestanje u listu
lista_sheets <- list()
for(i in 1:length(nazivi_sheets)){
  lista_sheets[[i]] <- readxl::read_excel(path = merenja_path, sheet = nazivi_sheets[i])
}
names(lista_sheets) <- nazivi_sheets # postavljanje naziva elemenata liste
# lista_sheets

# Primena funkcije nivelman
lista_nivelman <- list()
for(i in 1:length(lista_sheets)){
  lista_nivelman[[i]] <- nivelman(niv_merenja = lista_sheets[[i]], reperi = reperi)
}
# lista_nivelman

# Visinske razlike po stanici - Tabela 1
tabela_1 <- data.frame()
for(i in 1:length(lista_nivelman)){
  tabela_1 <- rbind(tabela_1, lista_nivelman[[i]][[1]])
}
# tabela_1

# Visinske razlike od repera do repera - Tabela 2
tabela_2 <- data.frame()
for(i in 1:length(lista_nivelman)){
  tabela_2 <- rbind(tabela_2, lista_nivelman[[i]][[2]])
}
# tabela_2

#' >
Zadatak 1 - Resenje 2

# Imena svih repera u mrezi:

library(tidyverse)
library(magrittr)
library(here)
library(readxl)
library(purrr)

reperi <- c("B1", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "S1", "S2", "S3", "D1", "H1", "J1", "M1", "P1", "D7", "H7", "N7", "D10", "E10", "I10", "P10", "P13", "L116",	"D19", "G19", "J19", "M19", "P19", "E26", "P26", "D29", "J29", "M29", "P29", "D32", "H32", "D38", "G38", "J38", "M38", "P38", "A11", "A12", "A13", "A14", "A21", "A22",	"A23", "A24")

merenja_path <- here::here("data", "niv_merenja.xlsx")

merenja <- merenja_path %>%
  readxl::excel_sheets() %>%
  purrr::set_names() %>%
  purrr::map(read_excel, path = merenja_path)

# Drugi nacin
# https://stackoverflow.com/questions/12945687/read-all-worksheets-in-an-excel-workbook-into-an-r-list-with-data-frames
library(rio)
data_list <- import_list("D:/R_projects/Nauka_R/Slides/data/niv_merenja.xlsx", rbind = TRUE) # parametrom rbind (row bind - spojiti redove) dobijamo sve clanove liste spojene u jedan data.frame

# Nazivi repera
Tacke <- unique(data_list$tacka)
substr(Tacke,1,1)
toupper(substr(Tacke,1,1))
Tacke_sve <- tapply(Tacke, toupper(substr(Tacke,1,1)),identity)
# tapply() is a very powerful function that lets you break a vector into pieces and then apply some function to each of the pieces
Reperi <- unlist(Tacke_sve[9:21][]) %>% as.data.frame() %>% dplyr::rename(tac = ".")
reperi <- as.vector(Reperi$tac)

# Primena funkcije nivelman nad svim clanovima liste
mreza_all <- lapply(merenja, function(x) nivelman(niv_merenja = x, reperi = reperi))

# Visinske razlike po stanici - Tabela 1
mreza_by_station <- lapply(mreza_all, function(x) x[[1]])

# Visinske razlike od repera do repera - Tabela 2
mreza_oddo <- lapply(mreza_all, function(x) x[[2]])

# Tabela merenja po stanici
mreza_by_station <- do.call(rbind, mreza_by_station)
mreza_by_station %<>% as.data.frame()

# Sumarna tabela
mreza_oddo <- do.call(rbind, mreza_oddo)
mreza_oddo %<>% dplyr::mutate(vlak = row_number()) %>% as.data.frame()

#'


# Goal: Visualisation of 3-dimensional (x,y,z) data using contour
#       plots and using colour to represent the 3rd dimension.
#       The specific situation is: On a grid of (x,y) points, you have
#       evaluated f(x,y). Now you want a graphical representation of
#       the resulting list of (x,y,z) points that you have.

# Setup an interesting data matrix of (x,y,z) points:
points <- structure(c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.15, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.25, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.35, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.45, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.55, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.65, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.75, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.85, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 0.95, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0.998, 0.124, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0.998, 0.71, 0.068, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0.998, 0.898, 0.396, 0.058, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.998, 0.97, 0.726, 0.268, 0.056, 0.006, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0.996, 0.88, 0.546, 0.208, 0.054, 0.012, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.998, 0.964, 0.776, 0.418, 0.18, 0.054, 0.014, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.998, 0.906, 0.664, 0.342, 0.166, 0.056, 0.018, 0.006, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.986, 0.862, 0.568, 0.29, 0.15, 0.056, 0.022, 0.008, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.954, 0.778, 0.494, 0.26, 0.148, 0.056, 0.024, 0.012, 0.004, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.906, 0.712, 0.43, 0.242, 0.144, 0.058, 0.028, 0.012, 0.006, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.878, 0.642, 0.38, 0.222, 0.142, 0.066, 0.034, 0.014, 0.008, 0.004, 0.002, 0, 0, 0, 0, 0, 0, 0, 0, 0.846, 0.586, 0.348, 0.208, 0.136, 0.068, 0.034, 0.016, 0.012, 0.006, 0.004, 0.002, 0, 0, 0, 0, 0, 0, 0, 0.8, 0.538, 0.318, 0.204, 0.136, 0.07, 0.046, 0.024, 0.012, 0.008, 0.004, 0.002, 0.002, 0, 0, 0, 0, 0, 0, 0.762, 0.496, 0.294, 0.2, 0.138, 0.072, 0.05, 0.024, 0.014, 0.012, 0.006, 0.004, 0.002, 0.002, 0, 0, 0, 0, 0, 0.704, 0.472, 0.286, 0.198, 0.138, 0.074, 0.054, 0.028, 0.016, 0.012, 0.008, 0.006, 0.004, 0.002, 0.002, 0, 0, 0, 0, 0.668, 0.438, 0.276, 0.196, 0.138, 0.078, 0.054, 0.032, 0.024, 0.014, 0.012, 0.008, 0.004, 0.004, 0.002, 0.002, 0, 0, 0, 0.634, 0.412, 0.27, 0.194, 0.14, 0.086, 0.056, 0.032, 0.024, 0.016, 0.012, 0.01, 0.006, 0.004, 0.004, 0.002, 0.002, 0, 0, 0.604, 0.388, 0.26, 0.19, 0.144, 0.088, 0.058, 0.048, 0.026, 0.022, 0.014, 0.012, 0.008, 0.006, 0.004, 0.004, 0.002, 0.002, 0, 0.586, 0.376, 0.256, 0.19, 0.146, 0.094, 0.062, 0.052, 0.028, 0.024, 0.014, 0.012, 0.012, 0.008, 0.004, 0.004, 0.004, 0.002, 0.002, 0.566, 0.364, 0.254, 0.192, 0.148, 0.098, 0.064, 0.054, 0.032, 0.024, 0.022, 0.014, 0.012, 0.012, 0.008, 0.004, 0.004, 0.004, 0.002), .Dim = c(399, 3), .Dimnames = list(NULL, c("x", "y", "z")))

# Understand this object --
summary(points)
  # x is a grid from 0 to 1
  # y is a grid from 20 to 200
  # z is the interesting object which will be the 3rd dimension.

# Solution using contourplot() from package 'lattice'
library(lattice)
d3 <- data.frame(points)
contourplot(z ~ x+y, data=d3)
## or nicer
contourplot(z ~ x+y, data=d3, cuts=20, region = TRUE)
## or using logit - transformed z values:
contourplot(qlogis(z) ~ x+y, data=d3, pretty=TRUE, region = TRUE)

# An interesting alternative is levelplot()
levelplot(z ~ x+y, pretty=TRUE, contour=TRUE, data=d3)

# There is a contour() function in R. Even though it sounds obvious
# for the purpose, it is a bit hard to use.
# contour() wants 3 inputs: vectors of x and y values, and a matrix of
# z values, where the x values correspond to the rows of z, and the y
# values to the columns.  A collection of points like `points' above
# needs to be turned into such a grid. It might sound odd, but contour()
# image() and persp() have used this kind of input for the longest time.
#
# For irregular data, there's an interp function in the akima package
# that can convert from irregular data into the grid format.
#
# The `points' object that I have above - a list of (x,y,z) points -
# fits directly into the mentality of lattice::contourplot() but not
# into the requirements of contour()

RFrontEndSolution/Tests Repo/Rscripts All (163 files)/g4.r


########################### BayesKin ###########################
## Bayesian estimates of rigid body kinematics
## Andy Pohl
## UofC - Faculty of Kinesiology

## Written by: Andy Pohl
## UofC - Faculty of Kinesiology
## June-Dec 2020
## Revision 1: April 2021
################################################################

##################### process_results.r  ###################
## processes_results table for a batch of simulations
#############################################################

# preliminaries
rm(list = ls()) # clear workspace

library('rjags') # load rstan package to analyze rstan objects
library('coda') # load coda package for mcmc analysis
WORKING_DIR = "" # Replace with the location of the BayesKin/src directory on the local PC.
setwd(WORKING_DIR)
RESULTS_DIR = paste0(WORKING_DIR, "/src/") # location of results directory
POSE_DIRS = c("SingleLink_Mdl", "DoubleLink_Mdl", "TripleLink_Mdl")  # Folder names for the various model configurations
MODELS = c("LS", "Bayes_p1", "Bayes_p2", "Bayes_p3", "Bayes_p4", "Bayes_p5") # Names of Models used
file_ends = c('LSModel.rda', 'BayesModel_p1.rda', 'BayesModel_p2.rda', 'BayesModel_p3.rda', 'BayesModel_p4.rda', 'BayesModel_p5.rda')

PARMS = c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma') # Parameters of interest

nfiles = 1000 # the number of results files to process
nmodels = length(MODELS)

################################################################################
# Single Link
################################################################################
results_single = data.frame(pose = rep('SingleLink', nmodels*4*nfiles),
                            seed = rep(1:nfiles, each = nmodels*4),
                            model = rep(rep(MODELS, each = 4),nfiles),
                            parameter = rep(c('r1', 'r2', 'theta1', 'sigma'), nfiles*nmodels),
                            est = NA,
                            quantile = matrix(NA, nfiles*4*nmodels, 5),
                            neff = NA,
                            rhat = NA,
                            ts_se = NA,
                            run_time = NA)

files = list.files(path = paste0(RESULTS_DIR, "SingleLink_Mdl/Bayes_p3"))
seeds = as.numeric(gsub("(.+?)(\\_.*)", "\\1", files))
seeds = seeds[order(seeds)]
rowcnt = 1
for(i in 1:nfiles){
    nlinks = 1
    seed = seeds[i]
    print(sprintf("Processing Single Link file %.0f of %.0f", i, nfiles))

    # process LS results
    lsmdl = readRDS(paste0(RESULTS_DIR, "SingleLink_Mdl/LS/", seed, "_LSModel.rda"))
    est = c(lsmdl$r.hat, lsmdl$theta.hat, lsmdl$sigma.hat)
    int = lsmdl$intervals[1:(nlinks+2),]
    results_single[rowcnt:(rowcnt+3), c('est')] = est
    results_single[rowcnt:(rowcnt+2), c('quantile.1', 'quantile.5')] = int
    results_single[rowcnt:(rowcnt+3), c('run_time')] = lsmdl$time
    rowcnt = rowcnt + 4

    # process Bayes P1
    p1mdl =  readRDS(paste0(RESULTS_DIR, "SingleLink_Mdl/Bayes_p1/", seed, "_BayesModel_p1.rda"))
    p1sum = summary(p1mdl$fit)
    results_single[rowcnt:(rowcnt+3),c('est', 'ts_se')] = p1sum$statistics[c('r[1]', 'r[2]', 'theta', 'sigma'),c('Mean', 'Time-series SE')]
    results_single[rowcnt:(rowcnt+3),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p1sum$quantiles[c('r[1]', 'r[2]', 'theta', 'sigma'),]
    results_single[rowcnt:(rowcnt+3), c('neff')] = effectiveSize(p1mdl$fit)[c('r[1]', 'r[2]', 'theta', 'sigma')]
    results_single[rowcnt:(rowcnt+3), c('rhat')] = gelman.diag(p1mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta', 'sigma'),1]
    results_single[rowcnt:(rowcnt+3), c('run_time')] = p1mdl$time
    rowcnt = rowcnt + 4

    # process Bayes P2
    p2mdl =  readRDS(paste0(RESULTS_DIR, "SingleLink_Mdl/Bayes_p2/", seed, "_BayesModel_p2.rda"))
    p2sum = summary(p2mdl$fit)
    results_single[rowcnt:(rowcnt+3),c('est', 'ts_se')] = p2sum$statistics[c('r[1]', 'r[2]', 'theta', 'sigma'),c('Mean', 'Time-series SE')]
    results_single[rowcnt:(rowcnt+3),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p2sum$quantiles[c('r[1]', 'r[2]', 'theta', 'sigma'),]
    results_single[rowcnt:(rowcnt+3), c('neff')] = effectiveSize(p2mdl$fit)[c('r[1]', 'r[2]', 'theta', 'sigma')]
    results_single[rowcnt:(rowcnt+3), c('rhat')] = gelman.diag(p2mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta', 'sigma'),1]
    results_single[rowcnt:(rowcnt+3), c('run_time')] = p2mdl$time
    rowcnt = rowcnt + 4

    # process Bayes P3
    p3mdl =  readRDS(paste0(RESULTS_DIR, "SingleLink_Mdl/Bayes_p3/", seed, "_BayesModel_p3.rda"))
    p3sum = summary(p3mdl$fit)
    results_single[rowcnt:(rowcnt+3),c('est', 'ts_se')] = p3sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'sigma'),c('Mean', 'Time-series SE')]
    results_single[rowcnt:(rowcnt+3),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p3sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'sigma'),]
    results_single[rowcnt:(rowcnt+3), c('neff')] = effectiveSize(p3mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'sigma')]
    results_single[rowcnt:(rowcnt+3), c('rhat')] = gelman.diag(p3mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'sigma'),1]
    results_single[rowcnt:(rowcnt+3), c('run_time')] = p3mdl$time
    rowcnt = rowcnt + 4

    # process Bayes P4
    p4mdl =  readRDS(paste0(RESULTS_DIR, "SingleLink_Mdl/Bayes_p4/", seed, "_BayesModel_p4.rda"))
    p4sum = summary(p4mdl$fit)
    results_single[rowcnt:(rowcnt+3),c('est', 'ts_se')] = p4sum$statistics[c('r[1]', 'r[2]', 'theta', 'sigma'),c('Mean', 'Time-series SE')]
    results_single[rowcnt:(rowcnt+3),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p4sum$quantiles[c('r[1]', 'r[2]', 'theta', 'sigma'),]
    results_single[rowcnt:(rowcnt+3), c('neff')] = effectiveSize(p4mdl$fit)[c('r[1]', 'r[2]', 'theta', 'sigma')]
    results_single[rowcnt:(rowcnt+3), c('rhat')] = gelman.diag(p4mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta', 'sigma'),1]
    results_single[rowcnt:(rowcnt+3), c('run_time')] = p4mdl$time
    rowcnt = rowcnt + 4

    # process Bayes P5
    p5mdl =  readRDS(paste0(RESULTS_DIR, "SingleLink_Mdl/Bayes_p5/", seed, "_BayesModel_p5.rda"))
    p5sum = summary(p5mdl$fit)
    results_single[rowcnt:(rowcnt+3),c('est', 'ts_se')] = p5sum$statistics[c('r[1]', 'r[2]', 'theta', 'sigma'),c('Mean', 'Time-series SE')]
    results_single[rowcnt:(rowcnt+3),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p5sum$quantiles[c('r[1]', 'r[2]', 'theta', 'sigma'),]
    results_single[rowcnt:(rowcnt+3), c('neff')] = effectiveSize(p5mdl$fit)[c('r[1]', 'r[2]', 'theta', 'sigma')]
    results_single[rowcnt:(rowcnt+3), c('rhat')] = gelman.diag(p5mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta', 'sigma'),1]
    results_single[rowcnt:(rowcnt+3), c('run_time')] = p5mdl$time
    rowcnt = rowcnt + 4

}

################################################################################
# Double Link
################################################################################
results_double = data.frame(pose = rep('DoubleLink', nmodels*5*nfiles),
                            seed = rep(1:nfiles, each = nmodels*5),
                            model = rep(rep(MODELS, each = 5),nfiles),
                            parameter = rep(c('r1', 'r2', 'theta1', 'theta2', 'sigma'), nfiles*nmodels),
                            est = NA,
                            quantile = matrix(NA, nfiles*5*nmodels, 5),
                            neff = NA,
                            rhat = NA,
                            ts_se = NA,
                            run_time = NA)

files = list.files(path = paste0(RESULTS_DIR, "DoubleLink_Mdl/Bayes_p3"))
seeds = as.numeric(gsub("(.+?)(\\_.*)", "\\1", files))
seeds = seeds[order(seeds)]
rowcnt = 1
for(i in 1:nfiles){
    nlinks = 2
    seed = seeds[i]
    print(sprintf("Processing Double Link file %.0f of %.0f", i, nfiles))

    # process LS results
    lsmdl = readRDS(paste0(RESULTS_DIR, "DoubleLink_Mdl/LS/", seed, "_LSModel.rda"))
    est = c(lsmdl$r.hat, lsmdl$theta.hat, lsmdl$sigma.hat)
    int = lsmdl$intervals[1:(nlinks+2),]
    results_double[rowcnt:(rowcnt+4), c('est')] = est
    results_double[rowcnt:(rowcnt+3), c('quantile.1', 'quantile.5')] = int
    results_double[rowcnt:(rowcnt+4), c('run_time')] = lsmdl$time
    rowcnt = rowcnt + 5

    # process Bayes P1
    p1mdl =  readRDS(paste0(RESULTS_DIR, "DoubleLink_Mdl/Bayes_p1/", seed, "_BayesModel_p1.rda"))
    p1sum = summary(p1mdl$fit)
    results_double[rowcnt:(rowcnt+4),c('est', 'ts_se')] = p1sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),c('Mean', 'Time-series SE')]
    results_double[rowcnt:(rowcnt+4),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p1sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),]
    results_double[rowcnt:(rowcnt+4), c('neff')] = effectiveSize(p1mdl$fit)[c('r[1]', 'r[2]', 'theta[1]','theta[2]', 'sigma')]
    results_double[rowcnt:(rowcnt+4), c('rhat')] = gelman.diag(p1mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'), 1]
    results_double[rowcnt:(rowcnt+4), c('run_time')] = p1mdl$time
    rowcnt = rowcnt + 5

    # process Bayes P2
    p2mdl =  readRDS(paste0(RESULTS_DIR, "DoubleLink_Mdl/Bayes_p2/", seed, "_BayesModel_p2.rda"))
    p2sum = summary(p2mdl$fit)
    results_double[rowcnt:(rowcnt+4),c('est', 'ts_se')] = p2sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),c('Mean', 'Time-series SE')]
    results_double[rowcnt:(rowcnt+4),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p2sum$quantiles[c('r[1]', 'r[2]', 'theta[1]','theta[2]', 'sigma'),]
    results_double[rowcnt:(rowcnt+4), c('neff')] = effectiveSize(p2mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma')]
    results_double[rowcnt:(rowcnt+4), c('rhat')] = gelman.diag(p2mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),1]
    results_double[rowcnt:(rowcnt+4), c('run_time')] = p2mdl$time
    rowcnt = rowcnt + 5

    # process Bayes P3
    p3mdl =  readRDS(paste0(RESULTS_DIR, "DoubleLink_Mdl/Bayes_p3/", seed, "_BayesModel_p3.rda"))
    p3sum = summary(p3mdl$fit)
    results_double[rowcnt:(rowcnt+4),c('est', 'ts_se')] = p3sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),c('Mean', 'Time-series SE')]
    results_double[rowcnt:(rowcnt+4),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p3sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),]
    results_double[rowcnt:(rowcnt+4), c('neff')] = effectiveSize(p3mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma')]
    results_double[rowcnt:(rowcnt+4), c('rhat')] = gelman.diag(p3mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),1]
    results_double[rowcnt:(rowcnt+4), c('run_time')] = p3mdl$time
    rowcnt = rowcnt + 5

    # process Bayes P4
    p4mdl =  readRDS(paste0(RESULTS_DIR, "DoubleLink_Mdl/Bayes_p4/", seed, "_BayesModel_p4.rda"))
    p4sum = summary(p4mdl$fit)
    results_double[rowcnt:(rowcnt+4),c('est', 'ts_se')] = p4sum$statistics[c('r[1]', 'r[2]','theta[1]', 'theta[2]', 'sigma'),c('Mean', 'Time-series SE')]
    results_double[rowcnt:(rowcnt+4),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p4sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),]
    results_double[rowcnt:(rowcnt+4), c('neff')] = effectiveSize(p4mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]','sigma')]
    results_double[rowcnt:(rowcnt+4), c('rhat')] = gelman.diag(p4mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]','sigma'),1]
    results_double[rowcnt:(rowcnt+4), c('run_time')] = p4mdl$time
    rowcnt = rowcnt + 5

    # process Bayes P5
    p5mdl =  readRDS(paste0(RESULTS_DIR, "DoubleLink_Mdl/Bayes_p5/", seed, "_BayesModel_p5.rda"))
    p5sum = summary(p5mdl$fit)
    results_double[rowcnt:(rowcnt+4),c('est', 'ts_se')] = p5sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),c('Mean', 'Time-series SE')]
    results_double[rowcnt:(rowcnt+4),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p5sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),]
    results_double[rowcnt:(rowcnt+4), c('neff')] = effectiveSize(p5mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma')]
    results_double[rowcnt:(rowcnt+4), c('rhat')] = gelman.diag(p5mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'sigma'),1]
    results_double[rowcnt:(rowcnt+4), c('run_time')] = p5mdl$time
    rowcnt = rowcnt + 5

}

################################################################################
# Triple Link
################################################################################
results_triple = data.frame(pose = rep('TripleLink', nmodels*6*nfiles),
                            seed = rep(1:nfiles, each = nmodels*6),
                            model = rep(rep(MODELS, each = 6),nfiles),
                            parameter = rep(c('r1', 'r2', 'theta1', 'theta2', 'theta3', 'sigma'), nfiles*nmodels),
                            est = NA,
                            quantile = matrix(NA, nfiles*6*nmodels, 5),
                            neff = NA,
                            rhat = NA,
                            ts_se = NA,
                            run_time = NA)

files = list.files(path = paste0(RESULTS_DIR, "TripleLink_Mdl/Bayes_p3"))
seeds = as.numeric(gsub("(.+?)(\\_.*)", "\\1", files))
seeds = seeds[order(seeds)]
rowcnt = 1
for(i in 1:nfiles){
    nlinks = 3
    seed = seeds[i]
    print(sprintf("Processing Triple Link file %.0f of %.0f", i, nfiles))

    # process LS results
    lsmdl = readRDS(paste0(RESULTS_DIR, "TripleLink_Mdl/LS/", seed, "_LSModel.rda"))
    est = c(lsmdl$r.hat, lsmdl$theta.hat, lsmdl$sigma.hat)
    int = lsmdl$intervals[1:(nlinks+2),]
    results_triple[rowcnt:(rowcnt+5), c('est')] = est
    results_triple[rowcnt:(rowcnt+4), c('quantile.1', 'quantile.5')] = int
    results_triple[rowcnt:(rowcnt+5), c('run_time')] = lsmdl$time
    rowcnt = rowcnt + 6

    # process Bayes P1
    p1mdl =  readRDS(paste0(RESULTS_DIR, "TripleLink_Mdl/Bayes_p1/", seed, "_BayesModel_p1.rda"))
    p1sum = summary(p1mdl$fit)
    results_triple[rowcnt:(rowcnt+5),c('est', 'ts_se')] = p1sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),c('Mean', 'Time-series SE')]
    results_triple[rowcnt:(rowcnt+5),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p1sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),]
    results_triple[rowcnt:(rowcnt+5), c('neff')] = effectiveSize(p1mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma')]
    results_triple[rowcnt:(rowcnt+5), c('rhat')] = gelman.diag(p1mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'), 1]
    results_triple[rowcnt:(rowcnt+5), c('run_time')] = p1mdl$time
    rowcnt = rowcnt + 6

    # process Bayes P2
    p2mdl =  readRDS(paste0(RESULTS_DIR, "TripleLink_Mdl/Bayes_p2/", seed, "_BayesModel_p2.rda"))
    p2sum = summary(p2mdl$fit)
    results_triple[rowcnt:(rowcnt+5),c('est', 'ts_se')] = p2sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),c('Mean', 'Time-series SE')]
    results_triple[rowcnt:(rowcnt+5),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p2sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),]
    results_triple[rowcnt:(rowcnt+5), c('neff')] = effectiveSize(p2mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma')]
    results_triple[rowcnt:(rowcnt+5), c('rhat')] = gelman.diag(p2mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),1]
    results_triple[rowcnt:(rowcnt+5), c('run_time')] = p2mdl$time
    rowcnt = rowcnt + 6

    # process Bayes P2
    p3mdl =  readRDS(paste0(RESULTS_DIR, "TripleLink_Mdl/Bayes_p3/", seed, "_BayesModel_p3.rda"))
    p3sum = summary(p3mdl$fit)
    results_triple[rowcnt:(rowcnt+5),c('est', 'ts_se')] = p3sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),c('Mean', 'Time-series SE')]
    results_triple[rowcnt:(rowcnt+5),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p3sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),]
    results_triple[rowcnt:(rowcnt+5), c('neff')] = effectiveSize(p3mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma')]
    results_triple[rowcnt:(rowcnt+5), c('rhat')] = gelman.diag(p3mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),1]
    results_triple[rowcnt:(rowcnt+5), c('run_time')] = p3mdl$time
    rowcnt = rowcnt + 6

    # process Bayes P4
    p4mdl =  readRDS(paste0(RESULTS_DIR, "TripleLink_Mdl/Bayes_p4/", seed, "_BayesModel_p4.rda"))
    p4sum = summary(p4mdl$fit)
    results_triple[rowcnt:(rowcnt+5),c('est', 'ts_se')] = p4sum$statistics[c('r[1]', 'r[2]','theta[1]', 'theta[2]', 'theta[3]', 'sigma'),c('Mean', 'Time-series SE')]
    results_triple[rowcnt:(rowcnt+5),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p4sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),]
    results_triple[rowcnt:(rowcnt+5), c('neff')] = effectiveSize(p4mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]','sigma')]
    results_triple[rowcnt:(rowcnt+5), c('rhat')] = gelman.diag(p4mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]','sigma'),1]
    results_triple[rowcnt:(rowcnt+5), c('run_time')] = p4mdl$time
    rowcnt = rowcnt + 6

    # process Bayes P5
    p5mdl =  readRDS(paste0(RESULTS_DIR, "TripleLink_Mdl/Bayes_p5/", seed, "_BayesModel_p5.rda"))
    p5sum = summary(p5mdl$fit)
    results_triple[rowcnt:(rowcnt+5),c('est', 'ts_se')] = p5sum$statistics[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),c('Mean', 'Time-series SE')]
    results_triple[rowcnt:(rowcnt+5),c('quantile.1', 'quantile.2','quantile.3','quantile.4','quantile.5')] = p5sum$quantiles[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),]
    results_triple[rowcnt:(rowcnt+5), c('neff')] = effectiveSize(p5mdl$fit)[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma')]
    results_triple[rowcnt:(rowcnt+5), c('rhat')] = gelman.diag(p5mdl$fit)$psrf[c('r[1]', 'r[2]', 'theta[1]', 'theta[2]', 'theta[3]', 'sigma'),1]
    results_triple[rowcnt:(rowcnt+5), c('run_time')] = p5mdl$time
    rowcnt = rowcnt + 6

}

################################################################################
# Combine and save
################################################################################
print("Combing files")
results = rbind(results_single, results_double, results_triple)
print("Saving Results...")

saveRDS(results, file = paste0(RESULTS_DIR, 'results/results_', format(Sys.time(), "%d%m%Y"),".rda"))
write.table(results, file = paste0(RESULTS_DIR, 'results/results_', format(Sys.time(), "%d%m%Y"),".csv"), sep = ',', col.names = TRUE, row.names = FALSE)
print("Complete!")


# Function to find nodes that can be realistically dated by the Hedman technique:
find.dateable.nodes <- function(tree, tip.ages) {

	# Requires ape library for reading tree structure:
	require(ape)

	# List all internal nodes except root:
	list.nodes <- (Ntip(tree) + 2):(Ntip(tree) + Nnode(tree))

	# Create vector to store nodes that are dateable:
	defo.nodes <- vector(mode="numeric")

	# For each node in the list:
	for(i in length(list.nodes):1) {

		# Find descendant nodes:
		descs <- tree$edge[grep(TRUE, tree$edge[, 1] == list.nodes[i]), 2]

		# If all immediate descendants are internal nodes than it cannot be dated and is removed:
		if(length(which(descs > Ntip(tree))) == length(descs)) list.nodes <- list.nodes[-i]

		# If all immediate descendants are terminal nodes than it can definitely be dated...:
		if(length(which(descs <= Ntip(tree))) == length(descs)) {

			# ...and is retained...:
			defo.nodes <- c(defo.nodes, list.nodes[i])

			# ...but can be removed from list.nodes:
			list.nodes <- list.nodes[-i]

		}

	}

	# For each internal node with both a terminal and internal node descendant:
	for(i in length(list.nodes):1) {

		# Find node age:
		node.age <- max(tip.ages[FindDescendants(list.nodes[i], tree)])

		# List its descendants:
		descs.ages <- descs <- tree$edge[grep(TRUE, tree$edge[, 1] == list.nodes[i]), 2]

		# For each descendant:
		for(j in length(descs.ages):1) {

			# If an internal node date as oldest descendant:
			if(descs[j] > Ntip(tree)) descs.ages[j] <- max(tip.ages[FindDescendants(descs[j], tree)])

			# Remove if terminal node:
			if(descs[j] <= Ntip(tree)) descs.ages <- descs.ages[-j]

		}

		# If node age is older than any descendant internal nodes:
		if(node.age > max(descs.ages)) {

			# Then can be dated so add to list:
			defo.nodes <- c(defo.nodes, list.nodes[i])

		}

	}

	# Output all dateable nodes:
	return(defo.nodes)

}

# Function that retrieves dates for use as tnodes variable in Hedman function
get.tnodes <- function(node, tree, tip.ages) {

	# Require ape library:
	require(ape)

	# Number of root node (point at which searching for ancestral nodes stops):
	root.node <- Ntip(tree) + 1

	# If the input node is the root node:
	if(node == root.node) {

		# Error and warning:
		stop("ERROR: Input node is root node - tnodes has length one!")

	# If input node is not root node:
	} else {

		# Find first ancestor node:
		internodes <- tree$edge[match(node, tree$edge[, 2]), 1]

		# As long as we have not yet reached the root:
		while(internodes[length(internodes)] != root.node) {

			# Add next internode to set:
			internodes <- c(internodes, tree$edge[match(internodes[length(internodes)], tree$edge[, 2]), 1])
		}

		# Add in original node as that is counted too!:
		internodes <- c(node, internodes)

		# Create tnodes vector for storage:
		tnodes <- vector(mode="numeric")

		# Date first (original) node:
		tnodes[1] <- max(tip.ages[FindDescendants(internodes[1], tree)])

		# For each internode:
		for(i in 2:length(internodes)) {

			# Find descendants:
			descs <- tree$edge[grep(TRUE, tree$edge[, 1] == internodes[i]), 2]

			# Remove previously dated node:
			descs <- descs[-match(internodes[(i - 1)], descs)]

			# For each other descendant:
			for(j in 1:length(descs)) {

				# If descendant node is internal:
				if(descs[j] > Ntip(tree)) {

					# Get maximum possible age:
					descs[j] <- max(tip.ages[FindDescendants(descs[j], tree)])

				# If descendant node is terminal:
				} else {

					# Get maximum possible age:
					descs[j] <- tip.ages[descs[j]]

				}

			}

			# Store in tnodes:
			tnodes[i] <- max(descs)

		}

		# Vector for storing node ages to be deleted:
		deletes <- vector(mode="numeric")

		# For each potential tnode:
		for(i in 2:length(tnodes)) {

			# Find nodes too young for dating and store in deletes:
			if(max(tnodes[1:(i-1)]) > tnodes[i]) deletes <- c(deletes, i)

		}

		# Remove these nodes (if there are any) from tnodes:
		if(length(deletes) > 0) tnodes <- tnodes[-deletes]

		# Give in order from oldest to youngest:
		tnodes <- rev(tnodes)

		# Return tnodes:
		return(tnodes)

	}

}

# Hedman (2010) method for estimating confidence given ages of ougroups (tnodes is the sequence of ages, from oldest to youngest; t0 is the arbitrary lower stratigraphic bound; resolution is the number of steps to take between the FAD and the lower stratigraphic bound):
Hedman.2010 <- function (tnodes, t0, resolution) { # Outputs estimate with two-tailed 95% CIs

    # Check t0 is older than any other node age:
    if(!all(t0 > tnodes)) stop("t0 must be older than any tnode value.")

	# Store requested resolution:
	requested.resolution <- resolution

	# Function returns p.d.f. for node ages	(t0 is an arbitrary oldest age to consider, tnodes are the ages of oldest known fossil stemming from each node, and tsteps is a vector of arbitrary time steps on which the p.d.f. is calculated):
	nodeage <- function(tsteps, tnodes, t0) {

		# Get number of outgroups (tnodes):
		nn  <- length(tnodes)

		# Get number of time steps (resolution):
		nt  <- length(tsteps)

		# Initialize array:
		pnodes  <- matrix(0, nn, nt)

		# First get pdf for node 1, at discrete values:
		ii  <- which(tsteps > t0 & tsteps < tnodes[1])

		# Assume uniform distribution for oldest node:
		pnodes[1, ii] <- 1.0 / length(ii)

		# Cycle through remaining nodes:
		for (i in 2:nn) {

			# Cycle through series of time steps:
			for (j in 1:nt) {

				# Initialize vector:
				p21  <- rep(0, nt)

				# Get p.d.f. for ith node ay discrete values:
				ii <- which(tsteps >= tsteps[j] & tsteps < tnodes[i])

				p21[ii]  <- 1.0 / length(ii) * pnodes[(i - 1), j]

				# Conditional probability of this age, given previous node age, times probability of previous node age, added to cumulative sum:
				pnodes[i, ii]  <- pnodes[i, ii] + p21[ii]

			}

		}

		# Get just the p.d.f. vector for the youngest node:
		out <- pnodes[nn, ]

		# Return output:
		return(out)

	}

	# Calculate c.d.f. from p.d.f., find median and 95% credibility limits:
	HedmanAges <- function(tnodes, t0, resolution) {

		# Store input resolution for later reuse:
		old.resolution <- resolution

		# Get uniformly spaced p-values (including 95% limits adn median) for calculating CIs:
		CIs <- sort(unique(c(0.025, 0.975, 0.5, c(1:old.resolution) * (1 / old.resolution))))

		# Get enw resolution (may be longer by adding limits and/or median):
		resolution <- length(CIs)

		# Get oldest possible age to consider:
		first <- t0

		# Get youngest possible age to consider:
		last <- min(tnodes)

		# Make tnodes negative for Hedman function:
		tnodes  <- -tnodes

		# Get uniformly spaced time steps for Hedman function:
		tsteps <- seq(-first, -last, length=resolution)

		# Make t0 negative for Hedman function:
		t0 <- -t0

		# Run Hedman function to get p.d.f.:
		vector <- nodeage(tsteps, tnodes, t0)

		# Convert from p-value scale to age scale:
		integral.vector <- vector * ((abs(first - last)) / resolution)

		# Get sum of probabilities in order to re-scale as p.d.f.:
		probability.sum <- sum(integral.vector)

		# Get re-scaled p-values for CIs:
		ps.CIs <- probability.sum * CIs

		# Cretae empty vector to store dates:
		date.distribution <- vector(mode="numeric")

		# Set initial value:
		value <- 0

		# For each re-scaled p-value:
		for(i in length(ps.CIs):1) {

			# Update value:
			value <- value + integral.vector[i]

			# If in age window:
			if(length(which(value >= ps.CIs)) > 0) {

				# Store dates:
				date.distribution <- c(date.distribution, rep(tsteps[i], length(which(value >= ps.CIs))))

				# Update re-scaled p-values:
				ps.CIs <- ps.CIs[-grep(TRUE, value >= ps.CIs)]

			}

		}

		# Add t0 at end if length is short:
		while(length(date.distribution) < resolution) date.distribution <- c(date.distribution, t0)

		# Find median of distribution:
		Best.guess <- date.distribution[CIs == 0.5]

		# Get upper 95% CI:
		Best.guess.lower <- date.distribution[CIs == 0.975]

		# Get lower 95% CI:
		Best.guess.upper <- date.distribution[CIs == 0.025]

		# Combine results and reverse sign to give palaeo ages:
		results <- list(-Best.guess, -Best.guess.lower, -Best.guess.upper, -date.distribution[match(c(c(1:old.resolution) * (1 / old.resolution)), CIs)])

		# Name subvariables:
		names(results) <- c("Best.guess", "Best.guess.lower", "Best.guess.upper", "Age.distribution")

		# Return result
		return(results)

	}

	# Get Hedman ages:
	out <- HedmanAges(tnodes, t0, resolution)

	# If Hedman ages represent less than five unique values (leading to downstream problem of flat distributions):
	while(length(unique(out$Age.distribution[round(seq(1, length(out$Age.distribution), length.out=requested.resolution))])) < 5) {

		# Double the resolution size:
		resolution <- resolution * 2

		# Get Hedman ages:
		out <- HedmanAges(tnodes, t0, resolution)

	}

	# Update output:
	out$Age.distribution <- out$Age.distribution[round(seq(1, length(out$Age.distribution), length.out=requested.resolution))]

	# Return output:
	return(out)

}

# Over-arching Hedman tree-dating function:
Hedman.tree.dates <- function(tree, tip.ages, outgroup.ages, t0, resolution = 1000, conservative = TRUE) {

# MORE CONDITIONALS NEEDED FOR WHEN RESOLUTION IS LOW

	# Load libraries:
	require(ape)
	require(strap)

    # Check tree is fully bifurcating:
    if(!is.binary.tree(tree)) stop("Tree must be fully bifurcating.")

	# Ensure tip ages are in tree tip order:
	tip.ages <- tip.ages[tree$tip.label]

	# Find root node:
	root.node <- Ntip(tree) + 1

	# Create variables to store age estimates:
	age.estimates <- matrix(nrow = Nnode(tree), ncol = 3)

	# Create variables to store age distributions:
	age.distributions <- matrix(nrow = Nnode(tree), ncol = resolution)

	# Set column headings:
	colnames(age.estimates) <- c("Best.guess", "Best.guess.lower", "Best.guess.upper")

	# Set row names:
	rownames(age.estimates) <- rownames(age.distributions) <- c((Ntip(tree) + 1):(Ntip(tree) + Nnode(tree)))

	# Vector to store outgroup sequences:
	og.seq <- vector(mode = "numeric")

	# Report progress:
	cat("Identifying nodes that are date-able using the Hedman technique_")

	# Find dateable conservative nodes:
	if(conservative) dateable.nodes <- find.dateable.nodes(tree, tip.ages)

	# Find dateable non-conservative nodes:
	if(!conservative) dateable.nodes <- c((Ntip(tree) + 1):(Ntip(tree) + Nnode(tree)))

	# Report progress:
	cat("Done\nDating nodes using Hedman technique_")

	# If not using the conservative approach:
	if(!conservative) {

		# Create ages matrix:
		ages <- cbind(tip.ages, tip.ages)

		# Add rownames:
		rownames(ages) <- names(tip.ages)

		# Add column names:
		colnames(ages) <- c("FAD", "LAD")

		# Sort by taxon order in tree:
		ages <- ages[tree$tip.label, ]

		# Date tree using basic algorithm:
		stree <- DatePhylo(tree, ages, 0, "basic", FALSE)

		# Get node ages:
		sages <- GetNodeAges(stree)

		# Set first outgroup sequence (i.e. root) as this is special case:
		og.seq[1] <- paste(c(outgroup.ages, sages[root.node]), collapse = "%%")

		# For each non-root node:
		for(i in 2:length(dateable.nodes)) {

			# Identify node:
			node <- dateable.nodes[i]

			# Find preceding node:
			internodes <- tree$edge[match(node, tree$edge[, 2]), 1]

			# Keep going until we reach the root and add next internode to set:
			while(internodes[length(internodes)] != root.node) internodes <- c(internodes, tree$edge[match(internodes[length(internodes)], tree$edge[, 2]), 1])

			# Collate nodes and put in order:
			internodes <- sort(c(node, internodes))

			# Find outgroup age sequence and collaspe to single string and store:
			og.seq[i] <- paste(c(outgroup.ages, sages[internodes]), collapse="%%")

		}

	# If using the conservative approach:
	} else {

		# Find age estimates for each dateable node:
		for(i in 1:length(dateable.nodes)) {

			# Find tnodes for a specific node:
			tnodes <- get.tnodes(dateable.nodes[i], tree, tip.ages)

			# Add additional outgroup tnodes:
			tnodes <- c(outgroup.ages, tnodes)

			# Turn outgroup sequence into single string and store:
			og.seq[i] <- paste(tnodes, collapse="%%")

		}

	}

	# Get unique outgroup sequences:
	unq.og.seq <- unique(og.seq)

	# For each unique outgroup sequence:
	for(i in 1:length(unq.og.seq)) {

		# List nodes with this outgroup sequence:
		nodes <- dateable.nodes[which(og.seq == unq.og.seq[i])]

		# Define tnodes:
		tnodes <- as.numeric(strsplit(unq.og.seq[i], "%%")[[1]])

		# Get Hedman dates:
		hedman.out <- Hedman.2010(tnodes = tnodes, t0 = t0, resolution = resolution)

		# Update age distributions:
        for(j in 1:length(nodes)) age.distributions[as.character(nodes[j]), ] <- hedman.out$Age.distribution

	}

	# Separate check to see if root is dateable (first find root descendants that are tips, if any):
	root.desc.tips <- sort(tree$tip.label[tree$edge[which(root.node == tree$edge[, 1]), 2]])

	# Now check that there are descendants that are tips:
	if(length(root.desc.tips) > 0) {

		# Get maximum root descendant age:
		root.tip.age <- max(tip.ages[root.desc.tips])

		# Now check that descendants are older than any other tips and get Hedman dates for root and update age distributions:
		if(root.tip.age > max(tip.ages[setdiff(tree$tip.label, root.desc.tips)])) age.distributions[as.character(root.node), ] <- Hedman.2010(tnodes = c(outgroup.ages, root.tip.age), t0 = t0, resolution = resolution)$Age.distribution

	}

	# If not using the conservative approach:
	if(conservative == FALSE) {

		# Report progress:
		cat("Done\nIdentifying remaining undated nodes_")

		# Report progress:
		cat("Done\nDating remaining undated nodes using randomisation technique_")

	# If using the conservative approach:
	} else {

		# Report progress:
		cat("Done\nIdentifying remaining undated nodes_")

		# Find undated nodes by first establishing actually dated nodes:
		dated.nodes <- sort(dateable.nodes)

		# List all internal nodes:
		all.nodes <- (Ntip(tree) + 1):(Ntip(tree) + Nnode(tree))

		# Get undated nodes:
		undated.nodes <- setdiff(all.nodes, dated.nodes)

		# Find branches that define undated nodes:
		internal.branches <- tree$edge[which(tree$edge[, 2] > Ntip(tree)), ]

		# Is root dated? (default to "yes" before actually checking):
		root.dated <- "Yes"

		# If the root is undated (then it needs to be):
		if(is.na(match(root.node, dated.nodes))) {

			# Update root.dated:
			root.dated <- "no"

			# Create tnodes from outgroup ages ONLY (this will later be used to constrain the root age through the saem randomisation process as the other undated nodes):
			tnodes <- outgroup.ages

			# Date root node:
			hedman.out <- Hedman.2010(tnodes = tnodes, t0 = t0, resolution = resolution)

			# Add to age estimates:
			age.estimates <- rbind(age.estimates, c(hedman.out$Best.guess, hedman.out$Best.guess.lower, hedman.out$Best.guess.upper))

			# Add to age distributions:
			age.distributions <- rbind(age.distributions, hedman.out$Age.distribution)

			# Add node name 0:
			rownames(age.estimates)[length(rownames(age.estimates))] <- rownames(age.distributions)[length(rownames(age.distributions))] <- "0"

		}

		# Vector for storing strings of sequences of undated node(s) bound by dated nodes:
		anc.strings.trimmed <- anc.strings <- vector(mode = "character")

		# Work through dated nodes to find sequences of undated nodes bracketed by them:
		for(i in 1:length(dated.nodes)) {

			# As long as the dated node is not the root (which has no ancestors):
			if(dated.nodes[i] != root.node) {

				# Get ancestors:
				ancestors <- internal.branches[which(internal.branches[, 2] == dated.nodes[i]), 1]

				# As long as the ancestor is neither the root or a dated node:
				if(ancestors != root.node && length(sort(match(ancestors, dated.nodes))) == 0) {

					# And as long as it remains so find next ancestral node:
					while(ancestors[length(ancestors)] != root.node && length(sort(match(ancestors[length(ancestors)], dated.nodes))) == 0) ancestors <- c(ancestors, internal.branches[match(ancestors[length(ancestors)], internal.branches[, 2]), 1])

				}

				# Add initial dated node:
				ancestors <- c(dated.nodes[i], ancestors)

				# Only need retain those with at least one undated node between dated nodes:
				if(length(ancestors) > 2) {

					# Add to ancestor strings:
					anc.strings <- c(anc.strings, paste(ancestors, collapse = " "))

					# Add to trimmed ancestor strings:
					anc.strings.trimmed <- c(anc.strings.trimmed, paste(ancestors[2:(length(ancestors) - 1)], collapse = " "))

				}

			}

		}

		# If root is undated then anc.strings with the root present must be modified to include node 0 at the end:
		if(root.dated == "no") {

			# Go through each anc.string:
			for(i in 1:length(anc.strings)) {

				# Get ancestors vector:
				ancestors <- as.numeric(strsplit(anc.strings[i], " ")[[1]])

				# If last node is the root:
				if(ancestors[length(ancestors)] == root.node) {

					# Add 0 node to end:
					ancestors <- c(ancestors, 0)

					# Re-store in anc.strings:
					anc.strings[i] <- paste(ancestors, collapse = " ")

				}

			}

		}

		# Find oldest node from each ancestral sequence (first set up empty vector):
		oldest.nodes <- vector(mode="numeric")

		# Go through each anc.string:
		for(i in 1:length(anc.strings)) {

			# Get ancestors vector:
			ancestors <- as.numeric(strsplit(anc.strings[i], " ")[[1]])

			# Store oldest node:
			oldest.nodes <- c(oldest.nodes, ancestors[length(ancestors)])

		}

		# Collapse to unique oldest nodes only:
		oldest.nodes <- sort(unique(oldest.nodes))

		# Clump anc.strings by shared oldest node (i.e. blocks that will be dated at the same time) - first create empty vector:
		clumped.anc.strings <- vector(mode="character")

		# For each oldest node find anc.strings with oldest node and collapse with %%:
		for(i in 1:length(oldest.nodes)) clumped.anc.strings <- c(clumped.anc.strings, paste(anc.strings[grep(paste(" ", oldest.nodes[i], sep = ""), anc.strings)], collapse = "%%"))

		# Report progress:
		cat("Done\nDating remaining undated nodes using randomisation technique_")

		# Can now date undated nodes using random draws from bounding Hedman dated nodes:
		for(i in 1:length(clumped.anc.strings)) {

			# First retrieve anc.strings with shared oldest node:
			anc.strings <- strsplit(clumped.anc.strings[i], "%%")[[1]]

			# Get young dated nodes (for drawing from for upper bounds):
			young.dated.nodes <- vector(mode="numeric")

			# For each ancestor string find youngest dated node, i.e., lower bounds::
			for(j in 1:length(anc.strings)) young.dated.nodes <- c(young.dated.nodes, as.numeric(strsplit(anc.strings[j], " ")[[1]][1]))

			# Get age distributions for young dated nodes:
			young.distributions <- age.distributions[as.character(young.dated.nodes), ]

			# Force into a single row matrix if only a vector:
			if(!is.matrix(young.distributions)) young.distributions <- t(as.matrix(young.distributions))

			# Set young distribution half up from young distributions:
			young.distributions.old.half <- young.distributions

			# Get old half as age distributions for young dated nodes:
			for(j in 1:length(apply(young.distributions, 1, median))) {

				# As long as the median is not the maximum:
				if(max(young.distributions[j, ]) > median(young.distributions[j, ])) {

					# Split distribution into older half using median:
					temp.distribution <- young.distributions[j, which(young.distributions[j, ] > median(young.distributions[j, ]))]

				# If the median is the maximum:
				} else {

					# Just use the maximum:
					temp.distribution <- max(young.distributions[j, ])

				}

				# ???:
				young.distributions.old.half[j, ] <- c(temp.distribution, rep(NA, length(young.distributions[j, ]) - length(temp.distribution)))

			}

			# Get old dated node (for drawing from for lower bounds):
			old.dated.node <- as.numeric(strsplit(anc.strings[1], " ")[[1]][length(strsplit(anc.strings[1], " ")[[1]])])

			# Get age distributions for old dated node:
			old.distribution <- age.distributions[as.character(old.dated.node), ]

			# As long as the median is not the minimum:
			if(min(old.distribution) < median(old.distribution)) {

				# Split distribution using median:
				old.distribution.young.half <- old.distribution[which(old.distribution < median(old.distribution))]

			# If the median is the minimum:
			} else {

				# Just use the minimum:
				old.distribution.young.half <- min(old.distribution)

			}

			# Get undated nodes (vector for storing output):
			undated.nodes <- vector(mode="numeric")

			# For each anc.string:
			for(j in 1:length(anc.strings)) {

				# Get ancestors:
				ancestors <- strsplit(anc.strings[j], " ")[[1]]

				# Store undated nodes:
				undated.nodes <- c(undated.nodes, as.numeric(ancestors[2:(length(ancestors) - 1)]))

			}

			# Collapse to unique nodes only:
			undated.nodes <- sort(unique(undated.nodes))

			# Find young constraints for each undated node (vector for storing results):
			young.constraints <- vector(mode = "numeric")

			# For each undated node add young constraints to list:
			for(j in 1:length(undated.nodes)) young.constraints <- c(young.constraints, paste(young.dated.nodes[grep(paste(" ", undated.nodes[j], sep = ""), anc.strings)], collapse = " "))

			# Find old constraints for each undated node (i.e. all nodes that are lower in tree as these will potentially be dated first and replace the current oldest age; vector for storing results):
			old.constraints <- vector(mode="numeric")

			# For each undated node:
			for(j in 1:length(undated.nodes)) {

				# Get just anc.strings where undated node is present:
				node.strings <- anc.strings[grep(paste(" ", undated.nodes[j], sep=""), anc.strings)]

				# Vector for storing older nodes:
				older.nodes <- vector(mode="numeric")

				# For each anc.string where the undated node exists ?????:
				for(k in 1:length(node.strings)) older.nodes <- c(older.nodes, as.numeric(strsplit(strsplit(node.strings[k], undated.nodes[j])[[1]][2], " ")[[1]]))

				# ????:
				old.constraints <- c(old.constraints, paste(unique(sort(older.nodes)), collapse=" "))

			}

			# Find undated nodes constrained by dated nodes (vector for storage):
			young.dated.nodes.constrain <- vector(mode = "character")

			# For each young dated node constraint find undated nodes which it contrains:
			for(j in 1:length(young.dated.nodes)) young.dated.nodes.constrain[j] <- paste(as.numeric(strsplit(anc.strings[j], " ")[[1]][2:(length(strsplit(anc.strings[j], " ")[[1]]) - 1)]), collapse = " ")

			# Main dating loop (repeats random draw N times, where N is defined by the variable resolution):
			for(j in 1:resolution) {

				# Modify age distributions used if medians of undated nodes exceed bounds of medians of dated nodes (needs to have been at least two entries already):
				if(j >= 3 && j >= floor(resolution / 2)) {

					# If there is more than one undated node establish present medians for undated nodes:
					if(length(undated.nodes) > 1) undated.medians <- apply(age.distributions[as.character(undated.nodes), 1:(j - 1)], 1, median)

					# If there is only one undated node
					if(length(undated.nodes) == 1) {

						# Establish present median of undated node:
						undated.medians <- median(age.distributions[as.character(undated.nodes), 1:(j - 1)])

						# Add name for reference later:
						names(undated.medians) <- as.character(undated.nodes)

					}

					# Case if oldest median of the undated nodes exceeds the median of the bounding old dated node:
					if(max(undated.medians) >= median(sort(old.distribution))) {

						# Update active distribution with older half only (to force undated nodes towards median ages consistent with dated nodes):
						active.old.distribution <- old.distribution.young.half

					# If oldest median ages do not conflict:
					} else {

						# Draw from full distribution:
						active.old.distribution <- old.distribution

					}

					# Set default active young distributions:
					active.young.distributions <- young.distributions

					# For each young (top bounding) dated node:
					for(k in 1:length(young.dated.nodes)) {

						# Case if youngest median of the undated nodes exceeds the median of the bounding young dated node - update active distribution with younger half only (to force undated nodes towards median ages consistent with dated nodes):
						if(min(undated.medians[strsplit(young.dated.nodes.constrain[k], " ")[[1]]]) <= median(sort(age.distributions[as.character(young.dated.nodes[k]), ]))) active.young.distributions[k, ] <- young.distributions.old.half[k, ]

					}

				# Case if still in first half of resolution:
				} else {

					# Use uncorrected distribution for upper bound:
					active.old.distribution <- old.distribution

					# Use uncorrected distributions for lower bound:
					active.young.distributions <- young.distributions

				}

				# Special case of last iteration where we want to ensure we draw a date between the constraining medians:
				if(j == resolution) {

					# Set old distribution to young half only:
					active.old.distribution <- old.distribution.young.half

					# Set default active young distributions:
					active.young.distributions <- young.distributions

					# For each young (top bounding) dated node set all young distribution to old half only:
					for(k in 1:length(young.dated.nodes)) active.young.distributions[k, ] <- young.distributions.old.half[k, ]

				}

				# Modify young and old distributions to remove tails which will always violate node order (young node older than old node and vice versa) to speed up the random draw step below; if part of young distributions are older than the oldest part of the old distribution:
				if(max(sort(as.vector(active.young.distributions))) >= max(sort(active.old.distribution))) {

					# Find oldest part of old distribution
					upper.limit <- max(sort(active.old.distribution))

					# ????:
					active.young.distributions[which(active.young.distributions >= upper.limit)] <- NA

				}

				# Vector to store young distribution minima:
				active.young.distribution.mins <- vector(mode="numeric")

				# For each young node store minima:
				for(k in 1:length(active.young.distributions[, 1])) active.young.distribution.mins <- c(active.young.distribution.mins, min(sort(active.young.distributions[k, ])))

				# If part of old distribution is younger than the youngest part of the youngest young distribution:
				if(min(sort(active.old.distribution)) <= min(active.young.distribution.mins)) {

					# Find lower limit (minimum of minima):
					lower.limit <- min(active.young.distribution.mins)

					# Remove values less than or equal to the lower limit from the old distribution:
					active.old.distribution <- active.old.distribution[grep(FALSE, active.old.distribution <= lower.limit)]

				}

				# Draw upper and lower bounds at random from old and young nodes:
				young.random.ages <- vector(mode="numeric")

				# For each young node draw a random age from its distributon:
				for(k in 1:length(young.dated.nodes)) young.random.ages <- c(young.random.ages, sort(active.young.distributions[k, ])[ceiling(runif(1, 0, length(sort(active.young.distributions[k, ]))))])

				# Repeat for old age:
				old.random.age <- sort(active.old.distribution)[ceiling(runif(1, 0, length(sort(active.old.distribution))))]

				# Ensure old node is older than all young nodes; while old node is younger or equal in age to oldest young nodes:
				while(old.random.age <= max(young.random.ages)) {

					# Re-draw ages as above:
					young.random.ages <- vector(mode="numeric")

					# For each young node draw a random age from its distributon:
					for(k in 1:length(young.dated.nodes)) young.random.ages <- c(young.random.ages, sort(active.young.distributions[k, ])[ceiling(runif(1, 0, length(sort(active.young.distributions[k, ]))))])

					# Repeat for old age:
					old.random.age <- sort(active.old.distribution)[ceiling(runif(1, 0, length(sort(active.old.distribution))))]

				}

				# Need to add names so can extract data later:
				names(young.random.ages) <- young.dated.nodes

				# Find oldest (i.e. constraining) young node for each undated node (vector for storing output):
				young.constraints.node <- young.constraints.age <- vector(mode="numeric")

				# For each undated node:
				for(k in 1:length(undated.nodes)) {

					# Store young age:
					young.constraints.age <- c(young.constraints.age, max(young.random.ages[strsplit(young.constraints[k], " ")[[1]]]))

					# Store young node:
					young.constraints.node <- c(young.constraints.node, as.numeric(names(young.random.ages[strsplit(young.constraints[k], " ")[[1]]])[which(young.random.ages[strsplit(young.constraints[k], " ")[[1]]] == max(young.random.ages[strsplit(young.constraints[k], " ")[[1]]]))[1]]))

				}

				# Vector for storing node dates:
				node.dates <- rep(NA, length(undated.nodes))

				# Date undated nodes using randomisation process:
				for(k in 1:length(unique(young.constraints.node)) ) {

					# Find top.node:
					top.node <- unique(young.constraints.node)[k]

					# Find target (.e. undated) nodes:
					target.nodes <- undated.nodes[which(young.constraints.node == top.node)]

					# Find bounding dates (potential old constraints):
					old.constraining.nodes <- as.numeric(sort(unique(strsplit(paste(old.constraints[match(target.nodes, undated.nodes)], collapse=" "), " ")[[1]])))

					# Remove any target nodes present
					if(length(sort(match(target.nodes, old.constraining.nodes))) > 0) old.constraining.nodes <- old.constraining.nodes[-sort(match(target.nodes, old.constraining.nodes))]

					# Find youngest old node as actual constraint:
					old.constraining.node <- max(old.constraining.nodes)

					# If lower bound is the previously dated old node:
					if(old.constraining.node == old.dated.node) {

						# Use that as maximum limit
						max <- old.random.age

					# If lower bound is a previously undated node:
					} else {

						# Use that as maximum limit
						max <- node.dates[match(old.constraining.node, undated.nodes)]

					}

					# Set minimum age:
					min <- young.random.ages[as.character(top.node)]

					# Get node dates:
					node.dates[match(target.nodes, undated.nodes)] <- sort(runif(length(target.nodes), min = min, max = max), decreasing = TRUE) # Draw node ages from bounding minima and maxima

				}

				# Store results:
				age.distributions[as.character(undated.nodes), j] <- node.dates

			}

		}

		# Can now remove node "0" (i.e., if root is undated by Hedman method) if present:
		if(root.dated == "no") {

			# Remove zero node from distributions:
			age.distributions <- age.distributions[-which(rownames(age.distributions) == "0"), ]

			# Remove zero node from estimates:
			age.estimates <- age.estimates[-which(rownames(age.estimates) == 0), ]

		}

	}

	# Report progress:
	cat("Done\nTidying up and returning results_")

	# Create vector of nodes estimated using Hedman method:
	Hedman.estimated <- rep(1, length(rownames(age.estimates)))

	# Update those not estimated using Hedman method:
	if(conservative == TRUE) Hedman.estimated[match(setdiff(rownames(age.estimates), dateable.nodes), rownames(age.estimates))] <- 0

	# Add to age estimates:
	age.estimates <- cbind(age.estimates, Hedman.estimated)

	# Sort age distributions in advance of picking confidence intervals:
	age.distributions <- t(apply(age.distributions, 1, sort))

	# Add median values to age estimates:
	age.estimates[, "Best.guess"] <- apply(age.distributions, 1, median)

	# Add upper CI to age estimates:
	age.estimates[, "Best.guess.upper"] <- age.distributions[, ceiling(0.975 * resolution)]

	# Add lower CI to age estimates:
	age.estimates[, "Best.guess.lower"] <- age.distributions[, max(c(1, floor(0.025 * resolution)))]

	# Create vectors to store node ages for each branch:
	to.ages <- from.ages <- age.estimates[match(tree$edge[, 1], rownames(age.estimates)), "Best.guess"]

	# Get to ages for terminal branches:
	to.ages[which(tree$edge[, 2] <= Ntip(tree))] <- tip.ages[tree$edge[which(tree$edge[, 2] <= Ntip(tree)), 2]]

	# Get to ages for internal branches:
	to.ages[which(tree$edge[, 2] > Ntip(tree))] <- age.estimates[match(tree$edge[which(tree$edge[, 2] > Ntip(tree)), 2], rownames(age.estimates)), "Best.guess"]

	# Update tree with branch lengths scaled to time using median ages:
	tree$edge.length <- from.ages-to.ages

	# Update tree to include root time:
	tree$root.time <- age.estimates[as.character(root.node), "Best.guess"]

	# Collate results:
	results <- list(age.estimates, age.distributions, tree)

	# Add names:
	names(results) <- c("age.estimates", "age.distributions", "tree")

	# Repprt progress
	cat("Done")

	# Return output:
	return(results)

}

Evolutionary rates analysis (Fig 5d-e and Supplementary Fig 2-4)/cal3/Hedman_functions.r