In this file, we will import our already semi-cleaned up stable isotope dataset and explore it using some basic summary statistics, visualisation and tests.

Import the data

Your data should already have been tidied up, probably in Excel, so as to remove any of the additional lines at the start that often come back from analytical laboratories. As with all data files, your first line (row) should be the names of each of the columns. When naming columns, it is important to not use any unusual, non-standard characters, as these can confuse the functions that are parsing (reading and processing) the datafile: if you need to separate words in a column header you should use “_” or “.”, e.g. “delta_C” or “delta.C” but not “delta/C”.

Thereafter, unless otherwise stated (as is the case when supplying source data for siar for example, which we will come to later in the course) each row should represent a single, unique observation (sample) with corresponding name of the sample, \(\delta^{13}\text{C}\), \(\delta^{15}\text{N}\) values etc…

You then have a choice of how to save so that R can more easily import it. I favour comma separated files (*.csv) whereby each cell is delimited (separated) by a comma “,”. Depending on where in the world you are from, you may need to be very careful about this, as some regions often us the comma to indicate thousands in numers, so 1000 might entered or appear as 1,000 in Excel. Similarly, some regions use the comma to indicate a decimal place, so 10.34 might be 10,34. This can seriously mess up the importation process. Sometimes you have to change the region settings on your computer in order to change this automatic behaviour in Excel, or you can do a “find and replace” in the *.csv file if you open it in a basic text editor after saving it. Alternatively again, you might chose to use a different delimiter, such as “;” to separate the cells in your dataset, and if you do so, you can tell R this is the case by exploring the help files associated with ?read.table.

The function read.csv() is a shortcut to importing *.csv files and in my experience is the most stable function to use. When calling it, I specify the name of the file (and location if it is not in the current working directory) and that the first row contain the header names (header = TRUE). I also like to not let the function automatically interpret strings of letters in cells as being special factor type data which is R’s way of encoding categorical variables. My view is that if we want a column to be a factor, we should do it in the code ourselves, as we may not want all our character data to be factors, and in any case, most R functions will convert them to factors internally if when presented with them.

# import the data. 
# If you get an error about "No such file or directory" then you 
# need to take care about where R is currently working, and where your 
# data file is in relation to this.
mydata <- read.csv("Practical01.csv", 
                   header = TRUE, stringsAsFactors = FALSE)

# verify that our data looks correct by printing the first1 10 lines
# to screen
head(mydata)

# if the data is not a massive dataset, you might like to look  
# at it all
# mydata

What are my data?

One of the first things you should do with an imported dataset, is to take some time to understand how it is encoded, and how R is going to treat each variable. The function str does this by telling us what type of encoding each of the columns in the data are.

In this case, mydata is a data.frame object, and inside it, Taxon and ID are character vectors, while the rest are numeric.

str(mydata)
'data.frame':   60 obs. of  5 variables:
 $ Taxon: chr  "POM" "POM" "POM" "POM" ...
 $ ID   : chr  "AF_POM01" "AF_POM02" "AF_POM03" "AF_POM04" ...
 $ d13C : num  -20.5 -19.9 -19.4 -20.3 -19.1 ...
 $ d15N : num  13.6 12.5 14.1 13.1 12.5 ...
 $ C.N  : num  9.5 8 9.1 8.1 7.3 7.7 9.3 9 9.2 10 ...

The next obvious thing we might want to know is how big are these numbers, and how many observations we have etc… Accessing columns within a data.frame object is achieved using the $ notation, so mydata$Taxon is the vector of characters with the taxon identifier for each observation. N.B. in a previous existance I advocated the use of the function attach() but no longer: it can get you into all manner of mischief thats best avoided.


# prove to ourselves that the $ notation works.
mydata$Taxon
 [1] "POM"           "POM"           "POM"           "POM"          
 [5] "POM"           "Benthic.algae" "Benthic.algae" "Benthic.algae"
 [9] "Benthic.algae" "Benthic.algae" "Fissurella"    "Fissurella"   
[13] "Fissurella"    "Fissurella"    "Fissurella"    "Fissurella"   
[17] "Fissurella"    "Fissurella"    "Fissurella"    "Fissurella"   
[21] "Perumytilus"   "Perumytilus"   "Perumytilus"   "Perumytilus"  
[25] "Perumytilus"   "Perumytilus"   "Perumytilus"   "Perumytilus"  
[29] "Perumytilus"   "Perumytilus"   "Cabrilla"      "Cabrilla"     
[33] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
[37] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
[41] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
[45] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
[49] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
[53] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
[57] "Cabrilla"      "Cabrilla"      "Cabrilla"      "Cabrilla"     
# a basic summary of each column in the data.frame
summary(mydata)
    Taxon                ID                 d13C             d15N      
 Length:60          Length:60          Min.   :-23.10   Min.   :11.84  
 Class :character   Class :character   1st Qu.:-18.95   1st Qu.:15.62  
 Mode  :character   Mode  :character   Median :-17.47   Median :17.92  
                                       Mean   :-17.25   Mean   :17.80  
                                       3rd Qu.:-15.57   3rd Qu.:20.58  
                                       Max.   : -6.20   Max.   :23.38  
      C.N        
 Min.   : 3.100  
 1st Qu.: 3.575  
 Median : 4.050  
 Mean   : 5.275  
 3rd Qu.: 6.125  
 Max.   :12.500  
# how many obverations do we have per Taxon might also be of use
table(mydata$Taxon)

Benthic.algae      Cabrilla    Fissurella   Perumytilus           POM 
            5            30            10            10             5 

It is not so easy to get summary statistics on each Taxonomic group within our dataset using the basic R functions. There are many packages out there to help, but Hadley Wickham’s suite of packages are impossible to beat in my opinion, and we will be using his ggplot2 package for pretty graphs later on so lets stick with the “tidyverse” set of functions.

# Install tidyverse if required
# install.packages('tidyverse')

# load the library
library(tidyverse)
── Attaching core tidyverse packages ───────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     ── Conflicts ─────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
# generate summary statistics by group
mydata %>% 
  group_by(Taxon) %>% 
  summarise(count = n(),
            mC = mean(d13C), 
            sdC = sd(d13C), 
            mN = mean(d15N), 
            sdN = sd(d15N),
            mCN = mean(C.N),
            sdCN = sd(C.N))
NA
NA

Basic Visualisation

Visualising the data is often the next step. You can generate histograms of the data very easily.

# set up a multi-panel plot, 1 row, 3 columns
par(mfrow = c(1,3)) 
hist(mydata$d13C)
hist(mydata$d15N)
hist(mydata$C.N)

We might also be interested visualising the correlations among variables in our dataset. The pairs function does this very nicely, and should be something you use regularly at the start of analyses since you get the histograms, and the pair-wise scatter plots and correlations all in one figure. The associated help function has lots of examples and explains more about where the following code comes from ?pairs.

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
# These functions are copied from the help file for pairs()
## put histograms on the diagonal
panel.hist <- function(x, ...)
{
    usr <- par("usr"); on.exit(par(usr))
    par(usr = c(usr[1:2], 0, 1.5) )
    h <- hist(x, plot = FALSE)
    breaks <- h$breaks; nB <- length(breaks)
    y <- h$counts; y <- y/max(y)
    rect(breaks[-nB], 0, breaks[-1], y, col = "cyan", ...)
}

panel.cor <- function(x, y, digits = 2, prefix = "", cex.cor, ...)
{
    usr <- par("usr"); on.exit(par(usr))
    par(usr = c(0, 1, 0, 1))
    r <- cor(x, y)
    txt <- format(c(r, 0.123456789), digits = digits)[1]
    txt <- paste0(prefix, txt)
    if(missing(cex.cor)) cex.cor <- 0.8/strwidth(txt)
    # text(0.5, 0.5, txt, cex = cex.cor * r)
    text(0.5, 0.5, txt, cex = 3)
}

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

# A basic pairs() plot of the numeric data
# These two lines of code are identical, but use the 
# dplyr functions slightly differently
# pairs(select(mydata, d13C, d15N, C.N))
# pairs(mydata %>% select(d13C, d15N, C.N))



# this select function comes from the package dplyr and allows us to 
# select a subset of columns to plot.
pairs(select(mydata, d13C, d15N, C.N), 
      diag.panel = panel.hist,
      upper.panel = panel.smooth,
      lower.panel = panel.cor)

NA
NA

Boxplots are a quick way to visualise the variation among taxa. Note the use of the ~ which means d13C ~ Taxon reads as, do something, in this case draw a boxplot, of d13C as a function of Taxon. This formula approach then allows us to tell it to find these variables in mydata and then finally we specify a main label for each graph for this summary so we can keep track of what we are looking at.


# first Carbon
boxplot(d13C ~ Taxon, data = mydata, main = "d13C")


# second Nitrogen
boxplot(d15N ~ Taxon, data = mydata, main = "d15N")


# third the C/N ratio
boxplot(C.N ~ Taxon, data = mydata, main = "C/N ratio")

QQ-plots are also a great way to visualise how normal your data are. If they are perfectly normally distributed then they will sit on the diagonal line.

# another 1x3 panel plot
par(mfrow = c(1,3))

qqnorm(mydata$d13C)
qqline(mydata$d13C)

qqnorm(mydata$d15N)
qqline(mydata$d15N)

qqnorm(mydata$C.N)
qqline(mydata$C.N)

One of the d13C values for the Benthic Algae appears to be very high in relation to the others, both on the boxplots and the qq-plots. If you are satisfied this is an error in the sample process which is entirely possible, then you may choose to remove it. But, I would caution against taking a strict approach based on statistical tests designed to find outliers, especially when your sample size within a group is low (n = 5 in this case).

If you wanted, you could remove this data point, which we will do in this case.

subset_mydata <- mydata %>% filter(d13C < max(d13C))

# check our boxplots again
boxplot(d13C ~ Taxon, data = subset_mydata, main = "d13C")

LS0tCnRpdGxlOiAiRmlyc3QgTG9vayBBdCBTSUEgRGF0YSIKYXV0aG9yOiAiQW5kcmV3IEwgSmFja3NvbiAmIENocmlzIEhhcnJvZCIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIsICVZJylgIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBmaWcud2lkdGggPSA5LCBmaWcuaGVpZ2h0ID0gNikKYGBgCgoKSW4gdGhpcyBmaWxlLCB3ZSB3aWxsIGltcG9ydCBvdXIgYWxyZWFkeSBzZW1pLWNsZWFuZWQgdXAgc3RhYmxlIGlzb3RvcGUgZGF0YXNldCBhbmQgZXhwbG9yZSBpdCB1c2luZyBzb21lIGJhc2ljIHN1bW1hcnkgc3RhdGlzdGljcywgdmlzdWFsaXNhdGlvbiBhbmQgdGVzdHMuCgojIyBJbXBvcnQgdGhlIGRhdGEKCllvdXIgZGF0YSBzaG91bGQgYWxyZWFkeSBoYXZlIGJlZW4gdGlkaWVkIHVwLCBwcm9iYWJseSBpbiBFeGNlbCwgc28gYXMgdG8gcmVtb3ZlIGFueSBvZiB0aGUgYWRkaXRpb25hbCBsaW5lcyBhdCB0aGUgc3RhcnQgdGhhdCBvZnRlbiBjb21lIGJhY2sgZnJvbSBhbmFseXRpY2FsIGxhYm9yYXRvcmllcy4gQXMgd2l0aCBhbGwgZGF0YSBmaWxlcywgeW91ciBmaXJzdCBsaW5lIChyb3cpIHNob3VsZCBiZSB0aGUgbmFtZXMgb2YgZWFjaCBvZiB0aGUgY29sdW1ucy4gV2hlbiBuYW1pbmcgY29sdW1ucywgaXQgaXMgaW1wb3J0YW50IHRvIG5vdCB1c2UgYW55IHVudXN1YWwsIG5vbi1zdGFuZGFyZCBjaGFyYWN0ZXJzLCBhcyB0aGVzZSBjYW4gY29uZnVzZSB0aGUgZnVuY3Rpb25zIHRoYXQgYXJlIHBhcnNpbmcgKHJlYWRpbmcgYW5kIHByb2Nlc3NpbmcpIHRoZSBkYXRhZmlsZTogaWYgeW91IG5lZWQgdG8gc2VwYXJhdGUgd29yZHMgaW4gYSBjb2x1bW4gaGVhZGVyIHlvdSBzaG91bGQgdXNlICJfIiBvciAiLiIsIGUuZy4gImRlbHRhX0MiIG9yICJkZWx0YS5DIiBidXQgbm90ICJkZWx0YS9DIi4KClRoZXJlYWZ0ZXIsIHVubGVzcyBvdGhlcndpc2Ugc3RhdGVkIChhcyBpcyB0aGUgY2FzZSB3aGVuIHN1cHBseWluZyBzb3VyY2UgZGF0YSBmb3IgYHNpYXJgIGZvciBleGFtcGxlLCB3aGljaCB3ZSB3aWxsIGNvbWUgdG8gbGF0ZXIgaW4gdGhlIGNvdXJzZSkgZWFjaCByb3cgc2hvdWxkIHJlcHJlc2VudCBhIHNpbmdsZSwgdW5pcXVlIG9ic2VydmF0aW9uIChzYW1wbGUpIHdpdGggY29ycmVzcG9uZGluZyBuYW1lIG9mIHRoZSBzYW1wbGUsICRcZGVsdGFeezEzfVx0ZXh0e0N9JCwgJFxkZWx0YV57MTV9XHRleHR7Tn0kIHZhbHVlcyBldGMuLi4KCllvdSB0aGVuIGhhdmUgYSBjaG9pY2Ugb2YgaG93IHRvIHNhdmUgc28gdGhhdCBSIGNhbiBtb3JlIGVhc2lseSBpbXBvcnQgaXQuIEkgZmF2b3VyIGNvbW1hIHNlcGFyYXRlZCBmaWxlcyAoYCouY3N2YCkgd2hlcmVieSBlYWNoIGNlbGwgaXMgZGVsaW1pdGVkIChzZXBhcmF0ZWQpIGJ5IGEgY29tbWEgIiwiLiBEZXBlbmRpbmcgb24gd2hlcmUgaW4gdGhlIHdvcmxkIHlvdSBhcmUgZnJvbSwgeW91IG1heSBuZWVkIHRvIGJlIHZlcnkgY2FyZWZ1bCBhYm91dCB0aGlzLCBhcyBzb21lIHJlZ2lvbnMgb2Z0ZW4gdXMgdGhlIGNvbW1hIHRvIGluZGljYXRlIHRob3VzYW5kcyBpbiBudW1lcnMsIHNvIGAxMDAwYCBtaWdodCBlbnRlcmVkIG9yIGFwcGVhciBhcyBgMSwwMDBgIGluIEV4Y2VsLiBTaW1pbGFybHksIHNvbWUgcmVnaW9ucyB1c2UgdGhlIGNvbW1hIHRvIGluZGljYXRlIGEgZGVjaW1hbCBwbGFjZSwgc28gYDEwLjM0YCBtaWdodCBiZSBgMTAsMzRgLiBUaGlzIGNhbiBzZXJpb3VzbHkgbWVzcyB1cCB0aGUgaW1wb3J0YXRpb24gcHJvY2Vzcy4gU29tZXRpbWVzIHlvdSBoYXZlIHRvIGNoYW5nZSB0aGUgKnJlZ2lvbiogc2V0dGluZ3Mgb24geW91ciBjb21wdXRlciBpbiBvcmRlciB0byBjaGFuZ2UgdGhpcyBhdXRvbWF0aWMgYmVoYXZpb3VyIGluIEV4Y2VsLCBvciB5b3UgY2FuIGRvIGEgImZpbmQgYW5kIHJlcGxhY2UiIGluIHRoZSBgKi5jc3ZgIGZpbGUgaWYgeW91IG9wZW4gaXQgaW4gYSBiYXNpYyB0ZXh0IGVkaXRvciBhZnRlciBzYXZpbmcgaXQuIEFsdGVybmF0aXZlbHkgYWdhaW4sIHlvdSBtaWdodCBjaG9zZSB0byB1c2UgYSBkaWZmZXJlbnQgZGVsaW1pdGVyLCBzdWNoIGFzICI7IiB0byBzZXBhcmF0ZSB0aGUgY2VsbHMgaW4geW91ciBkYXRhc2V0LCBhbmQgaWYgeW91IGRvIHNvLCB5b3UgY2FuIHRlbGwgUiB0aGlzIGlzIHRoZSBjYXNlIGJ5IGV4cGxvcmluZyB0aGUgaGVscCBmaWxlcyBhc3NvY2lhdGVkIHdpdGggYD9yZWFkLnRhYmxlYC4KClRoZSBmdW5jdGlvbiBgcmVhZC5jc3YoKWAgaXMgYSBzaG9ydGN1dCB0byBpbXBvcnRpbmcgYCouY3N2YCBmaWxlcyBhbmQgaW4gbXkgZXhwZXJpZW5jZSBpcyB0aGUgbW9zdCBzdGFibGUgZnVuY3Rpb24gdG8gdXNlLiBXaGVuIGNhbGxpbmcgaXQsIEkgc3BlY2lmeSB0aGUgbmFtZSBvZiB0aGUgZmlsZSAoYW5kIGxvY2F0aW9uIGlmIGl0IGlzIG5vdCBpbiB0aGUgY3VycmVudCB3b3JraW5nIGRpcmVjdG9yeSkgYW5kIHRoYXQgdGhlIGZpcnN0IHJvdyBjb250YWluIHRoZSBoZWFkZXIgbmFtZXMgKGBoZWFkZXIgPSBUUlVFYCkuIEkgYWxzbyBsaWtlIHRvIG5vdCBsZXQgdGhlIGZ1bmN0aW9uIGF1dG9tYXRpY2FsbHkgaW50ZXJwcmV0IHN0cmluZ3Mgb2YgbGV0dGVycyBpbiBjZWxscyBhcyBiZWluZyBzcGVjaWFsIGBmYWN0b3JgIHR5cGUgZGF0YSB3aGljaCBpcyBSJ3Mgd2F5IG9mIGVuY29kaW5nIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gTXkgdmlldyBpcyB0aGF0IGlmIHdlIHdhbnQgYSBjb2x1bW4gdG8gYmUgYSBmYWN0b3IsIHdlIHNob3VsZCBkbyBpdCBpbiB0aGUgY29kZSBvdXJzZWx2ZXMsIGFzIHdlIG1heSBub3Qgd2FudCBhbGwgb3VyIGNoYXJhY3RlciBkYXRhIHRvIGJlIGZhY3RvcnMsIGFuZCBpbiBhbnkgY2FzZSwgbW9zdCBSIGZ1bmN0aW9ucyB3aWxsIGNvbnZlcnQgdGhlbSB0byBmYWN0b3JzIGludGVybmFsbHkgaWYgd2hlbiBwcmVzZW50ZWQgd2l0aCB0aGVtLgoKCmBgYHtyIGltcG9ydC1kYXRhfQojIGltcG9ydCB0aGUgZGF0YS4gCiMgSWYgeW91IGdldCBhbiBlcnJvciBhYm91dCAiTm8gc3VjaCBmaWxlIG9yIGRpcmVjdG9yeSIgdGhlbiB5b3UgCiMgbmVlZCB0byB0YWtlIGNhcmUgYWJvdXQgd2hlcmUgUiBpcyBjdXJyZW50bHkgd29ya2luZywgYW5kIHdoZXJlIHlvdXIgCiMgZGF0YSBmaWxlIGlzIGluIHJlbGF0aW9uIHRvIHRoaXMuCm15ZGF0YSA8LSByZWFkLmNzdigiUHJhY3RpY2FsMDEuY3N2IiwgCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIHZlcmlmeSB0aGF0IG91ciBkYXRhIGxvb2tzIGNvcnJlY3QgYnkgcHJpbnRpbmcgdGhlIGZpcnN0MSAxMCBsaW5lcwojIHRvIHNjcmVlbgpoZWFkKG15ZGF0YSkKCiMgaWYgdGhlIGRhdGEgaXMgbm90IGEgbWFzc2l2ZSBkYXRhc2V0LCB5b3UgbWlnaHQgbGlrZSB0byBsb29rICAKIyBhdCBpdCBhbGwKIyBteWRhdGEKYGBgCgojIyBXaGF0IGFyZSBteSBkYXRhPwoKT25lIG9mIHRoZSBmaXJzdCB0aGluZ3MgeW91IHNob3VsZCBkbyB3aXRoIGFuIGltcG9ydGVkIGRhdGFzZXQsIGlzIHRvIHRha2Ugc29tZSB0aW1lIHRvIHVuZGVyc3RhbmQgaG93IGl0IGlzIGVuY29kZWQsIGFuZCBob3cgUiBpcyBnb2luZyB0byB0cmVhdCBlYWNoIHZhcmlhYmxlLiBUaGUgZnVuY3Rpb24gYHN0cmAgZG9lcyB0aGlzIGJ5IHRlbGxpbmcgdXMgd2hhdCB0eXBlIG9mIGVuY29kaW5nIGVhY2ggb2YgdGhlIGNvbHVtbnMgaW4gdGhlIGRhdGEgYXJlLgoKSW4gdGhpcyBjYXNlLCBgbXlkYXRhYCBpcyBhIGBkYXRhLmZyYW1lYCBvYmplY3QsIGFuZCBpbnNpZGUgaXQsIGBUYXhvbmAgYW5kIGBJRGAgYXJlIGNoYXJhY3RlciB2ZWN0b3JzLCB3aGlsZSB0aGUgcmVzdCBhcmUgYG51bWVyaWNgLgoKYGBge3Igd2hhdC1hcmUtZGF0YX0Kc3RyKG15ZGF0YSkKYGBgCgpUaGUgbmV4dCBvYnZpb3VzIHRoaW5nIHdlIG1pZ2h0IHdhbnQgdG8ga25vdyBpcyBob3cgYmlnIGFyZSB0aGVzZSBudW1iZXJzLCBhbmQgaG93IG1hbnkgb2JzZXJ2YXRpb25zIHdlIGhhdmUgZXRjLi4uIEFjY2Vzc2luZyBjb2x1bW5zIHdpdGhpbiBhIGRhdGEuZnJhbWUgb2JqZWN0IGlzIGFjaGlldmVkIHVzaW5nIHRoZSBgJGAgbm90YXRpb24sIHNvIGBteWRhdGEkVGF4b25gIGlzIHRoZSB2ZWN0b3Igb2YgY2hhcmFjdGVycyB3aXRoIHRoZSB0YXhvbiBpZGVudGlmaWVyIGZvciBlYWNoIG9ic2VydmF0aW9uLiAqTi5CLiBpbiBhIHByZXZpb3VzIGV4aXN0YW5jZSBJIGFkdm9jYXRlZCB0aGUgdXNlIG9mIHRoZSBmdW5jdGlvbiBhdHRhY2goKSBidXQgbm8gbG9uZ2VyOiBpdCBjYW4gZ2V0IHlvdSBpbnRvIGFsbCBtYW5uZXIgb2YgbWlzY2hpZWYgdGhhdHMgYmVzdCBhdm9pZGVkLioKCmBgYHtyIHN1bW1hcnktZGF0YX0KCiMgcHJvdmUgdG8gb3Vyc2VsdmVzIHRoYXQgdGhlICQgbm90YXRpb24gd29ya3MuCm15ZGF0YSRUYXhvbgoKIyBhIGJhc2ljIHN1bW1hcnkgb2YgZWFjaCBjb2x1bW4gaW4gdGhlIGRhdGEuZnJhbWUKc3VtbWFyeShteWRhdGEpCgojIGhvdyBtYW55IG9idmVyYXRpb25zIGRvIHdlIGhhdmUgcGVyIFRheG9uIG1pZ2h0IGFsc28gYmUgb2YgdXNlCnRhYmxlKG15ZGF0YSRUYXhvbikKYGBgCgpJdCBpcyBub3Qgc28gZWFzeSB0byBnZXQgc3VtbWFyeSBzdGF0aXN0aWNzIG9uIGVhY2ggVGF4b25vbWljIGdyb3VwIHdpdGhpbiBvdXIgZGF0YXNldCB1c2luZyB0aGUgYmFzaWMgUiBmdW5jdGlvbnMuIFRoZXJlIGFyZSBtYW55IHBhY2thZ2VzIG91dCB0aGVyZSB0byBoZWxwLCBidXQgSGFkbGV5IFdpY2toYW0ncyBzdWl0ZSBvZiBwYWNrYWdlcyBhcmUgaW1wb3NzaWJsZSB0byBiZWF0IGluIG15IG9waW5pb24sIGFuZCB3ZSB3aWxsIGJlIHVzaW5nIGhpcyBgZ2dwbG90MmAgcGFja2FnZSBmb3IgcHJldHR5IGdyYXBocyBsYXRlciBvbiBzbyBsZXRzIHN0aWNrIHdpdGggdGhlICJ0aWR5dmVyc2UiIHNldCBvZiBmdW5jdGlvbnMuCgpgYGB7ciBzdW1tYXJpc2UtZGF0YS1ieS1ncm91cH0KIyBJbnN0YWxsIHRpZHl2ZXJzZSBpZiByZXF1aXJlZAojIGluc3RhbGwucGFja2FnZXMoJ3RpZHl2ZXJzZScpCgojIGxvYWQgdGhlIGxpYnJhcnkKbGlicmFyeSh0aWR5dmVyc2UpCgojIGdlbmVyYXRlIHN1bW1hcnkgc3RhdGlzdGljcyBieSBncm91cApteWRhdGEgJT4lIAogIGdyb3VwX2J5KFRheG9uKSAlPiUgCiAgc3VtbWFyaXNlKGNvdW50ID0gbigpLAogICAgICAgICAgICBtQyA9IG1lYW4oZDEzQyksIAogICAgICAgICAgICBzZEMgPSBzZChkMTNDKSwgCiAgICAgICAgICAgIG1OID0gbWVhbihkMTVOKSwgCiAgICAgICAgICAgIHNkTiA9IHNkKGQxNU4pLAogICAgICAgICAgICBtQ04gPSBtZWFuKEMuTiksCiAgICAgICAgICAgIHNkQ04gPSBzZChDLk4pKQoKCmBgYAoKIyMgQmFzaWMgVmlzdWFsaXNhdGlvbgoKVmlzdWFsaXNpbmcgdGhlIGRhdGEgaXMgb2Z0ZW4gdGhlIG5leHQgc3RlcC4gWW91IGNhbiBnZW5lcmF0ZSBoaXN0b2dyYW1zIG9mIHRoZSBkYXRhIHZlcnkgZWFzaWx5LgoKYGBge3IgaGlzdG99CiMgc2V0IHVwIGEgbXVsdGktcGFuZWwgcGxvdCwgMSByb3csIDMgY29sdW1ucwpwYXIobWZyb3cgPSBjKDEsMykpIApoaXN0KG15ZGF0YSRkMTNDKQpoaXN0KG15ZGF0YSRkMTVOKQpoaXN0KG15ZGF0YSRDLk4pCmBgYAoKV2UgbWlnaHQgYWxzbyBiZSBpbnRlcmVzdGVkIHZpc3VhbGlzaW5nIHRoZSBjb3JyZWxhdGlvbnMgYW1vbmcgdmFyaWFibGVzIGluIG91ciBkYXRhc2V0LiBUaGUgYHBhaXJzYCBmdW5jdGlvbiBkb2VzIHRoaXMgdmVyeSBuaWNlbHksIGFuZCBzaG91bGQgYmUgc29tZXRoaW5nIHlvdSB1c2UgcmVndWxhcmx5IGF0IHRoZSBzdGFydCBvZiBhbmFseXNlcyBzaW5jZSB5b3UgZ2V0IHRoZSBoaXN0b2dyYW1zLCBhbmQgdGhlIHBhaXItd2lzZSBzY2F0dGVyIHBsb3RzIGFuZCBjb3JyZWxhdGlvbnMgYWxsIGluIG9uZSBmaWd1cmUuIFRoZSBhc3NvY2lhdGVkIGhlbHAgZnVuY3Rpb24gaGFzIGxvdHMgb2YgZXhhbXBsZXMgYW5kIGV4cGxhaW5zIG1vcmUgYWJvdXQgd2hlcmUgdGhlIGZvbGxvd2luZyBjb2RlIGNvbWVzIGZyb20gYD9wYWlyc2AuCgpgYGB7ciBwYWlycy12aXN9CiMgPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0KIyBUaGVzZSBmdW5jdGlvbnMgYXJlIGNvcGllZCBmcm9tIHRoZSBoZWxwIGZpbGUgZm9yIHBhaXJzKCkKIyMgcHV0IGhpc3RvZ3JhbXMgb24gdGhlIGRpYWdvbmFsCnBhbmVsLmhpc3QgPC0gZnVuY3Rpb24oeCwgLi4uKQp7CiAgICB1c3IgPC0gcGFyKCJ1c3IiKTsgb24uZXhpdChwYXIodXNyKSkKICAgIHBhcih1c3IgPSBjKHVzclsxOjJdLCAwLCAxLjUpICkKICAgIGggPC0gaGlzdCh4LCBwbG90ID0gRkFMU0UpCiAgICBicmVha3MgPC0gaCRicmVha3M7IG5CIDwtIGxlbmd0aChicmVha3MpCiAgICB5IDwtIGgkY291bnRzOyB5IDwtIHkvbWF4KHkpCiAgICByZWN0KGJyZWFrc1stbkJdLCAwLCBicmVha3NbLTFdLCB5LCBjb2wgPSAiY3lhbiIsIC4uLikKfQoKcGFuZWwuY29yIDwtIGZ1bmN0aW9uKHgsIHksIGRpZ2l0cyA9IDIsIHByZWZpeCA9ICIiLCBjZXguY29yLCAuLi4pCnsKICAgIHVzciA8LSBwYXIoInVzciIpOyBvbi5leGl0KHBhcih1c3IpKQogICAgcGFyKHVzciA9IGMoMCwgMSwgMCwgMSkpCiAgICByIDwtIGNvcih4LCB5KQogICAgdHh0IDwtIGZvcm1hdChjKHIsIDAuMTIzNDU2Nzg5KSwgZGlnaXRzID0gZGlnaXRzKVsxXQogICAgdHh0IDwtIHBhc3RlMChwcmVmaXgsIHR4dCkKICAgIGlmKG1pc3NpbmcoY2V4LmNvcikpIGNleC5jb3IgPC0gMC44L3N0cndpZHRoKHR4dCkKICAgICMgdGV4dCgwLjUsIDAuNSwgdHh0LCBjZXggPSBjZXguY29yICogcikKICAgIHRleHQoMC41LCAwLjUsIHR4dCwgY2V4ID0gMykKfQoKIyA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPSA9ID0gPQoKIyBBIGJhc2ljIHBhaXJzKCkgcGxvdCBvZiB0aGUgbnVtZXJpYyBkYXRhCiMgVGhlc2UgdHdvIGxpbmVzIG9mIGNvZGUgYXJlIGlkZW50aWNhbCwgYnV0IHVzZSB0aGUgCiMgZHBseXIgZnVuY3Rpb25zIHNsaWdodGx5IGRpZmZlcmVudGx5CiMgcGFpcnMoc2VsZWN0KG15ZGF0YSwgZDEzQywgZDE1TiwgQy5OKSkKIyBwYWlycyhteWRhdGEgJT4lIHNlbGVjdChkMTNDLCBkMTVOLCBDLk4pKQoKCgojIHRoaXMgc2VsZWN0IGZ1bmN0aW9uIGNvbWVzIGZyb20gdGhlIHBhY2thZ2UgZHBseXIgYW5kIGFsbG93cyB1cyB0byAKIyBzZWxlY3QgYSBzdWJzZXQgb2YgY29sdW1ucyB0byBwbG90LgpwYWlycyhzZWxlY3QobXlkYXRhLCBkMTNDLCBkMTVOLCBDLk4pLCAKICAgICAgZGlhZy5wYW5lbCA9IHBhbmVsLmhpc3QsCiAgICAgIHVwcGVyLnBhbmVsID0gcGFuZWwuc21vb3RoLAogICAgICBsb3dlci5wYW5lbCA9IHBhbmVsLmNvcikKCgpgYGAKCkJveHBsb3RzIGFyZSBhIHF1aWNrIHdheSB0byB2aXN1YWxpc2UgdGhlIHZhcmlhdGlvbiBhbW9uZyB0YXhhLiBOb3RlIHRoZSB1c2Ugb2YgdGhlIGB+YCB3aGljaCBtZWFucyBgZDEzQyB+IFRheG9uYCByZWFkcyBhcywgZG8gc29tZXRoaW5nLCBpbiB0aGlzIGNhc2UgZHJhdyBhIGJveHBsb3QsIG9mIGBkMTNDYCBhcyBhIGZ1bmN0aW9uIG9mIGBUYXhvbmAuIFRoaXMgYGZvcm11bGFgIGFwcHJvYWNoIHRoZW4gYWxsb3dzIHVzIHRvIHRlbGwgaXQgdG8gZmluZCB0aGVzZSB2YXJpYWJsZXMgaW4gYG15ZGF0YWAgYW5kIHRoZW4gZmluYWxseSB3ZSBzcGVjaWZ5IGEgbWFpbiBsYWJlbCBmb3IgZWFjaCBncmFwaCBmb3IgdGhpcyBzdW1tYXJ5IHNvIHdlIGNhbiBrZWVwIHRyYWNrIG9mIHdoYXQgd2UgYXJlIGxvb2tpbmcgYXQuCgpgYGB7ciBib3hwbG90cy1ieS1ncm91cH0KCiMgZmlyc3QgQ2FyYm9uCmJveHBsb3QoZDEzQyB+IFRheG9uLCBkYXRhID0gbXlkYXRhLCBtYWluID0gImQxM0MiKQoKIyBzZWNvbmQgTml0cm9nZW4KYm94cGxvdChkMTVOIH4gVGF4b24sIGRhdGEgPSBteWRhdGEsIG1haW4gPSAiZDE1TiIpCgojIHRoaXJkIHRoZSBDL04gcmF0aW8KYm94cGxvdChDLk4gfiBUYXhvbiwgZGF0YSA9IG15ZGF0YSwgbWFpbiA9ICJDL04gcmF0aW8iKQoKYGBgCgpRUS1wbG90cyBhcmUgYWxzbyBhIGdyZWF0IHdheSB0byB2aXN1YWxpc2UgaG93IG5vcm1hbCB5b3VyIGRhdGEgYXJlLiBJZiB0aGV5IGFyZSBwZXJmZWN0bHkgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgdGhlbiB0aGV5IHdpbGwgc2l0IG9uIHRoZSBkaWFnb25hbCBsaW5lLgoKYGBge3IgcXEtcGxvdHN9CiMgYW5vdGhlciAxeDMgcGFuZWwgcGxvdApwYXIobWZyb3cgPSBjKDEsMykpCgpxcW5vcm0obXlkYXRhJGQxM0MpCnFxbGluZShteWRhdGEkZDEzQykKCnFxbm9ybShteWRhdGEkZDE1TikKcXFsaW5lKG15ZGF0YSRkMTVOKQoKcXFub3JtKG15ZGF0YSRDLk4pCnFxbGluZShteWRhdGEkQy5OKQoKYGBgCgoKT25lIG9mIHRoZSBkMTNDIHZhbHVlcyBmb3IgdGhlIEJlbnRoaWMgQWxnYWUgYXBwZWFycyB0byBiZSB2ZXJ5IGhpZ2ggaW4gcmVsYXRpb24gdG8gdGhlIG90aGVycywgYm90aCBvbiB0aGUgYm94cGxvdHMgYW5kIHRoZSBxcS1wbG90cy4gSWYgeW91IGFyZSBzYXRpc2ZpZWQgdGhpcyBpcyBhbiBlcnJvciBpbiB0aGUgc2FtcGxlIHByb2Nlc3Mgd2hpY2ggaXMgZW50aXJlbHkgcG9zc2libGUsIHRoZW4geW91IG1heSBjaG9vc2UgdG8gcmVtb3ZlIGl0LiBCdXQsIEkgd291bGQgY2F1dGlvbiBhZ2FpbnN0IHRha2luZyBhIHN0cmljdCBhcHByb2FjaCBiYXNlZCBvbiBzdGF0aXN0aWNhbCB0ZXN0cyBkZXNpZ25lZCB0byBmaW5kIG91dGxpZXJzLCBlc3BlY2lhbGx5IHdoZW4geW91ciBzYW1wbGUgc2l6ZSB3aXRoaW4gYSBncm91cCBpcyBsb3cgKG4gPSBgciB0YWJsZShteWRhdGEkVGF4b24pWyJCZW50aGljLmFsZ2FlIl1gIGluIHRoaXMgY2FzZSkuCgpJZiB5b3Ugd2FudGVkLCB5b3UgY291bGQgcmVtb3ZlIHRoaXMgZGF0YSBwb2ludCwgd2hpY2ggd2Ugd2lsbCBkbyBpbiB0aGlzIGNhc2UuCgpgYGB7cn0Kc3Vic2V0X215ZGF0YSA8LSBteWRhdGEgJT4lIGZpbHRlcihkMTNDIDwgbWF4KGQxM0MpKQoKIyBjaGVjayBvdXIgYm94cGxvdHMgYWdhaW4KYm94cGxvdChkMTNDIH4gVGF4b24sIGRhdGEgPSBzdWJzZXRfbXlkYXRhLCBtYWluID0gImQxM0MiKQpgYGAKCgoKCgoKCgo=