Julius Schmid
Dataframes
On an intuitive level, a data frame is like a matrix, with a
two-dimensional rows-and-columns structure. However, it differs from a
matrix in that each column may have a different
mode. For instance, one column may consist of numbers, and
another column might have character strings. In this sense, just as
lists are the heterogeneous analogs of vectors in one dimension, data
frames are the heterogeneous analogs of matrices for two-dimensional
data.
Creating Data Frames
To begin, let’s take another look at our simple data frame example
from Section 1.4.5:
kids <- c("Colin","Cosmo")
ages <- c(14,16)
d <- data.frame(kids,ages,stringsAsFactors=FALSE)
d # matrix-like viewpoint
The first two arguments in the call to data.frame() are clear: We
wish to produce a data frame from our two vectors: kids and ages.
However, that third argument, stringsAsFactors=FALSE requires more
comment.
If the named argument stringsAsFactors is not specified, then by
default, stringsAsFactors will be TRUE. (You can also use options() to
arrange the opposite default.) This means that if we create a data frame
from a character vector — in this case, kids — R will convert that
vector to a factor. Because our work with character data will typically
be with vectors rather than factors, we’ll set stringsAsFactors to
FALSE. We’ll cover factors in Chapter 6.
Accessing Data Frames
Now that we have a data frame, let’s explore a bit. Since d is a
list, we can access it as such via component index values or component
names:
In this example, we want to return the first column. We can do this
either by using the column index (df[[index]]) or by calling the
specific column name (df[[column_name]]). In this case, the column index
is 1, and the corresponding column name is kids.
d[[1]] # use column index
[1] "Colin" "Cosmo"
d$kids # use column name
[1] "Colin" "Cosmo"
But we can treat it in a matrix-like fashion as well. For example, we
can view column 1. We do this by using the syntax df[desired_rows,
desired_columns]. If we want to print all rows or all columns, we leave
the corresponding spot empty. So, with the call d[2,] we would return
the second row, and with the follwing call we can return the first
column:
d[,1]
[1] "Colin" "Cosmo"
This matrix-like quality is also seen when we take d apart using the
structure function str():
str(d)
'data.frame': 2 obs. of 2 variables:
$ kids: chr "Colin" "Cosmo"
$ ages: num 14 16
R tells us here that d consists of two observations — our two rows —
that store data on two variables — our two columns.
Consider three ways to access the first column of our data frame
above:d[[1]], d[,1], and d$kids. Of these, the third would generally
considered to be clearer and, more importantly, safer than the first
two. This better identifies the column and makes it less likely that you
will reference the wrong column. But in writing general code — say
writing R packages — matrix-like notation d[,1] is needed, and it is
especially handy if you are extracting subdata frames.
Extended Example: Regression Analysis of Exam Grades
Continued
Let us print out the working directory first:
getwd()
[1] "/cloud/project"
Since this notebook is created in RStudio Cloud, the working
directory is just “/cloud/project”.
Next, we download the examsquiz.csv file from Canvas, upload it to
RStudio Cloud and import it, using the read.csv() function:
examsquiz <- read.csv("ExamsQuiz.csv",sep=",",header=TRUE)
In order to see what the examsquiz data frame looks like, we return
the first 10 rows, using the head() funtion with input argument 10:
head(examsquiz,10)
We observe that there are three columns: Exam1, Exam2, and Quiz.
Other Matrix-Like Operations
Various matrix operations also apply to data frames. Most notably and
usefully, we can do filtering to extract various subdata frames of
interest.
Extracting Subdata Frames
As mentioned, a data frame can be viewed in row-and-column terms. In
particular, we can extract subdata frames by rows or columns. Here’s an
example, returning the rows 4-8 of the examsquiz data frame:
examsquiz[4:8,]
Now, we are not interested in the results of students 4-8 for all
tests, but only for the Quiz:
examsquiz[4:8,3]
[1] 4 2 3 4 2
R returns an array in this case, not a single-column data frame! We
confirm this by returning the class of our just generated output:
class(examsquiz[4:8,3])
[1] "numeric"
In order to return our filtered output as a data frame, we set the
optional input argument “drop” to FALSE, which guarantees us that the
filtered data will be returned as a data frame, as well.
examsquiz[4:8,3,drop=FALSE]
Again, return the class of our recent output, when setting the drop
parameter to FALSE:
class(examsquiz[4:8,3,drop=FALSE])
[1] "data.frame"
Indeed, the filtered data is still interpreted as a data frame
now.
Note that in that second call, since examsquiz[4:8,3] is a vector, R
created a vector instead of another data frame. By specifying
drop=FALSE, as described for the matrix case in Section 3.6, we can keep
it as a (onecolumn) data frame.
We can also do filtering. Here’s how to extract the subframe of all
students whose first exam score was at least 3.6:
examsquiz[examsquiz$Exam1 >= 3.6,]
More on Treatment of NA Values
Suppose the second exam score for the first student had been missing.
Then we would have typed the following into that line when we were
preparing the data file:
#2.0 NA 3.0
In any subsequent statistical analyses, R would do its best to cope
with the missing data. However, in some situations, we need to set the
option na.rm=TRUE, explicitly telling R to ignore NA values. For
instance, with the missing exam score, calculating the mean score on
exam 2 by calling R’s mean() function would skip that first student in
finding the mean. Otherwise, R would just report NA for the mean.
Here’s a little example:
Create an array x, containing a NA component:
x <- c(1,NA,9)
mean(x)
[1] NA
The mean is determined as NA, since NA + x = NA for all real numbers
x. We cannot perform useful arithmetic operations, taking NA into
consideration. This is why we have to ignore NA values for these
calculations, setting na.rm = TRUE:
mean(x,na.rm=TRUE)
[1] 5
In Section 2.8.2, you were introduced to the subset() function, which
saves you the trouble of specifying na.rm=TRUE. You can apply it in data
frames for row selection. The column names are taken in the context of
the given data frame. In our example, instead of typing this:
examsquiz[examsquiz$Exam1 >= 3.6,]
We could just use the subset function instead, applying the same
filter, and getting the same output:
subset(examsquiz,Exam1 >= 3.6)
NA
NA
LS0tCnRpdGxlOiAiUiBEYXRhZnJhbWVzIHBhcnQgMSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKSnVsaXVzIFNjaG1pZAoKKipEYXRhZnJhbWVzKioKCk9uIGFuIGludHVpdGl2ZSBsZXZlbCwgYSBkYXRhIGZyYW1lIGlzIGxpa2UgYSBtYXRyaXgsIHdpdGggYSB0d28tZGltZW5zaW9uYWwgcm93cy1hbmQtY29sdW1ucyBzdHJ1Y3R1cmUuIEhvd2V2ZXIsIGl0IGRpZmZlcnMgZnJvbQphIG1hdHJpeCBpbiB0aGF0ICoqKmVhY2ggY29sdW1uIG1heSBoYXZlIGEgZGlmZmVyZW50IG1vZGUqKiouIEZvciBpbnN0YW5jZSwgb25lIGNvbHVtbiBtYXkgY29uc2lzdCBvZiBudW1iZXJzLCBhbmQgYW5vdGhlciBjb2x1bW4gbWlnaHQgaGF2ZSBjaGFyYWN0ZXIgc3RyaW5ncy4gSW4gdGhpcyBzZW5zZSwganVzdCBhcyBsaXN0cyBhcmUgdGhlIGhldGVyb2dlbmVvdXMgYW5hbG9ncyBvZiB2ZWN0b3JzIGluIG9uZSBkaW1lbnNpb24sIGRhdGEgZnJhbWVzIGFyZSB0aGUgaGV0ZXJvZ2VuZW91cyBhbmFsb2dzIG9mIG1hdHJpY2VzIGZvciB0d28tZGltZW5zaW9uYWwgZGF0YS4KCgoqKkNyZWF0aW5nIERhdGEgRnJhbWVzKioKClRvIGJlZ2luLCBsZXTigJlzIHRha2UgYW5vdGhlciBsb29rIGF0IG91ciBzaW1wbGUgZGF0YSBmcmFtZSBleGFtcGxlIGZyb20gU2VjdGlvbiAxLjQuNToKYGBge3J9CmtpZHMgPC0gYygiQ29saW4iLCJDb3NtbyIpCmFnZXMgPC0gYygxNCwxNikKZCA8LSBkYXRhLmZyYW1lKGtpZHMsYWdlcyxzdHJpbmdzQXNGYWN0b3JzPUZBTFNFKQpkICMgbWF0cml4LWxpa2Ugdmlld3BvaW50CmBgYAoKVGhlIGZpcnN0IHR3byBhcmd1bWVudHMgaW4gdGhlIGNhbGwgdG8gZGF0YS5mcmFtZSgpIGFyZSBjbGVhcjogV2Ugd2lzaCB0byBwcm9kdWNlIGEgZGF0YSBmcmFtZSBmcm9tIG91ciB0d28gdmVjdG9yczoga2lkcyBhbmQgYWdlcy4gSG93ZXZlciwgdGhhdCB0aGlyZCBhcmd1bWVudCwgc3RyaW5nc0FzRmFjdG9ycz1GQUxTRSByZXF1aXJlcyBtb3JlIGNvbW1lbnQuIAoKSWYgdGhlIG5hbWVkIGFyZ3VtZW50IHN0cmluZ3NBc0ZhY3RvcnMgaXMgbm90IHNwZWNpZmllZCwgdGhlbiBieSBkZWZhdWx0LCBzdHJpbmdzQXNGYWN0b3JzIHdpbGwgYmUgVFJVRS4gKFlvdSBjYW4gYWxzbyB1c2Ugb3B0aW9ucygpIHRvIGFycmFuZ2UgdGhlIG9wcG9zaXRlIGRlZmF1bHQuKSBUaGlzIG1lYW5zIHRoYXQgaWYgd2UgY3JlYXRlIGEgZGF0YSBmcmFtZSBmcm9tIGEgY2hhcmFjdGVyIHZlY3RvciDigJQgaW4gdGhpcyBjYXNlLCBraWRzIOKAlCBSIHdpbGwgY29udmVydCB0aGF0IHZlY3RvciB0byBhIGZhY3Rvci4gQmVjYXVzZSBvdXIgd29yayB3aXRoIGNoYXJhY3RlciBkYXRhIHdpbGwgdHlwaWNhbGx5IGJlIHdpdGggdmVjdG9ycyByYXRoZXIgdGhhbiBmYWN0b3JzLCB3ZeKAmWxsIHNldCBzdHJpbmdzQXNGYWN0b3JzIHRvIEZBTFNFLiBXZeKAmWxsIGNvdmVyIGZhY3RvcnMgaW4gQ2hhcHRlciA2LgoKCioqQWNjZXNzaW5nIERhdGEgRnJhbWVzKioKCk5vdyB0aGF0IHdlIGhhdmUgYSBkYXRhIGZyYW1lLCBsZXTigJlzIGV4cGxvcmUgYSBiaXQuIFNpbmNlIGQgaXMgYSBsaXN0LCB3ZSBjYW4gYWNjZXNzIGl0IGFzIHN1Y2ggdmlhIGNvbXBvbmVudCBpbmRleCB2YWx1ZXMgb3IgY29tcG9uZW50IG5hbWVzOgoKSW4gdGhpcyBleGFtcGxlLCB3ZSB3YW50IHRvIHJldHVybiB0aGUgZmlyc3QgY29sdW1uLiBXZSBjYW4gZG8gdGhpcyBlaXRoZXIgYnkgdXNpbmcgdGhlIGNvbHVtbiBpbmRleCAoZGZbW2luZGV4XV0pIG9yIGJ5IGNhbGxpbmcgdGhlIHNwZWNpZmljIGNvbHVtbiBuYW1lIChkZltbY29sdW1uX25hbWVdXSkuIEluIHRoaXMgY2FzZSwgdGhlIGNvbHVtbiBpbmRleCBpcyAxLCBhbmQgdGhlIGNvcnJlc3BvbmRpbmcgY29sdW1uIG5hbWUgaXMga2lkcy4KYGBge3J9CmRbWzFdXSAjIHVzZSBjb2x1bW4gaW5kZXgKZCRraWRzICMgdXNlIGNvbHVtbiBuYW1lCmBgYAoKQnV0IHdlIGNhbiB0cmVhdCBpdCBpbiBhIG1hdHJpeC1saWtlIGZhc2hpb24gYXMgd2VsbC4gRm9yIGV4YW1wbGUsIHdlIGNhbiB2aWV3IGNvbHVtbiAxLiBXZSBkbyB0aGlzIGJ5IHVzaW5nIHRoZSBzeW50YXggZGZbZGVzaXJlZF9yb3dzLCBkZXNpcmVkX2NvbHVtbnNdLiBJZiB3ZSB3YW50IHRvIHByaW50IGFsbCByb3dzIG9yIGFsbCBjb2x1bW5zLCB3ZSBsZWF2ZSB0aGUgY29ycmVzcG9uZGluZyBzcG90IGVtcHR5LiBTbywgd2l0aCB0aGUgY2FsbCBkWzIsXSB3ZSB3b3VsZCByZXR1cm4gdGhlIHNlY29uZCByb3csIGFuZCB3aXRoIHRoZSBmb2xsd2luZyBjYWxsIHdlIGNhbiByZXR1cm4gdGhlIGZpcnN0IGNvbHVtbjoKYGBge3J9CmRbLDFdCmBgYAoKVGhpcyBtYXRyaXgtbGlrZSBxdWFsaXR5IGlzIGFsc28gc2VlbiB3aGVuIHdlIHRha2UgZCBhcGFydCB1c2luZyB0aGUgc3RydWN0dXJlIGZ1bmN0aW9uIHN0cigpOgpgYGB7cn0Kc3RyKGQpCmBgYApSIHRlbGxzIHVzIGhlcmUgdGhhdCBkIGNvbnNpc3RzIG9mIHR3byBvYnNlcnZhdGlvbnMg4oCUIG91ciB0d28gcm93cyDigJQgdGhhdCBzdG9yZSBkYXRhIG9uIHR3byB2YXJpYWJsZXMg4oCUIG91ciB0d28gY29sdW1ucy4KCkNvbnNpZGVyIHRocmVlIHdheXMgdG8gYWNjZXNzIHRoZSBmaXJzdCBjb2x1bW4gb2Ygb3VyIGRhdGEgZnJhbWUgYWJvdmU6ZFtbMV1dLCBkWywxXSwgYW5kIGQka2lkcy4gT2YgdGhlc2UsIHRoZSB0aGlyZCB3b3VsZCBnZW5lcmFsbHkgY29uc2lkZXJlZCB0byBiZSBjbGVhcmVyIGFuZCwgbW9yZSBpbXBvcnRhbnRseSwgc2FmZXIgdGhhbiB0aGUgZmlyc3QgdHdvLiBUaGlzIGJldHRlciBpZGVudGlmaWVzIHRoZSBjb2x1bW4gYW5kIG1ha2VzIGl0IGxlc3MgbGlrZWx5IHRoYXQgeW91IHdpbGwgcmVmZXJlbmNlIHRoZSB3cm9uZyBjb2x1bW4uIEJ1dCBpbiB3cml0aW5nIGdlbmVyYWwgY29kZSDigJQgc2F5IHdyaXRpbmcgUiBwYWNrYWdlcyDigJQgbWF0cml4LWxpa2Ugbm90YXRpb24gZFssMV0gaXMgbmVlZGVkLCBhbmQgaXQgaXMgZXNwZWNpYWxseSBoYW5keSBpZiB5b3UgYXJlIGV4dHJhY3Rpbmcgc3ViZGF0YSBmcmFtZXMuCgoKKipFeHRlbmRlZCBFeGFtcGxlOiBSZWdyZXNzaW9uIEFuYWx5c2lzIG9mIEV4YW0gR3JhZGVzIENvbnRpbnVlZCoqCgpMZXQgdXMgcHJpbnQgb3V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeSBmaXJzdDoKYGBge3J9CmdldHdkKCkKYGBgClNpbmNlIHRoaXMgbm90ZWJvb2sgaXMgY3JlYXRlZCBpbiBSU3R1ZGlvIENsb3VkLCB0aGUgd29ya2luZyBkaXJlY3RvcnkgaXMganVzdCAiL2Nsb3VkL3Byb2plY3QiLgoKTmV4dCwgd2UgZG93bmxvYWQgdGhlIGV4YW1zcXVpei5jc3YgZmlsZSBmcm9tIENhbnZhcywgdXBsb2FkIGl0IHRvIFJTdHVkaW8gQ2xvdWQgYW5kIGltcG9ydCBpdCwgdXNpbmcgdGhlIHJlYWQuY3N2KCkgZnVuY3Rpb246CmBgYHtyfQpleGFtc3F1aXogPC0gcmVhZC5jc3YoIkV4YW1zUXVpei5jc3YiLHNlcD0iLCIsaGVhZGVyPVRSVUUpCmBgYAoKSW4gb3JkZXIgdG8gc2VlIHdoYXQgdGhlIGV4YW1zcXVpeiBkYXRhIGZyYW1lIGxvb2tzIGxpa2UsIHdlIHJldHVybiB0aGUgZmlyc3QgMTAgcm93cywgdXNpbmcgdGhlIGhlYWQoKSBmdW50aW9uIHdpdGggaW5wdXQgYXJndW1lbnQgMTA6CmBgYHtyfQpoZWFkKGV4YW1zcXVpeiwxMCkKYGBgCldlIG9ic2VydmUgdGhhdCB0aGVyZSBhcmUgdGhyZWUgY29sdW1uczogRXhhbTEsIEV4YW0yLCBhbmQgUXVpei4gCgoqKk90aGVyIE1hdHJpeC1MaWtlIE9wZXJhdGlvbnMqKgoKVmFyaW91cyBtYXRyaXggb3BlcmF0aW9ucyBhbHNvIGFwcGx5IHRvIGRhdGEgZnJhbWVzLiBNb3N0IG5vdGFibHkgYW5kIHVzZWZ1bGx5LCB3ZSBjYW4gZG8gZmlsdGVyaW5nIHRvIGV4dHJhY3QgdmFyaW91cyBzdWJkYXRhIGZyYW1lcyBvZiBpbnRlcmVzdC4KCgoqKkV4dHJhY3RpbmcgU3ViZGF0YSBGcmFtZXMqKgoKQXMgbWVudGlvbmVkLCBhIGRhdGEgZnJhbWUgY2FuIGJlIHZpZXdlZCBpbiByb3ctYW5kLWNvbHVtbiB0ZXJtcy4gSW4gcGFydGljdWxhciwgd2UgY2FuIGV4dHJhY3Qgc3ViZGF0YSBmcmFtZXMgYnkgcm93cyBvciBjb2x1bW5zLiBIZXJl4oCZcyBhbiBleGFtcGxlLCByZXR1cm5pbmcgdGhlIHJvd3MgNC04IG9mIHRoZSBleGFtc3F1aXogZGF0YSBmcmFtZToKYGBge3J9CmV4YW1zcXVpels0OjgsXQpgYGAKCk5vdywgd2UgYXJlIG5vdCBpbnRlcmVzdGVkIGluIHRoZSByZXN1bHRzIG9mIHN0dWRlbnRzIDQtOCBmb3IgYWxsIHRlc3RzLCBidXQgb25seSBmb3IgdGhlIFF1aXo6CmBgYHtyfQpleGFtc3F1aXpbNDo4LDNdCmBgYApSIHJldHVybnMgYW4gYXJyYXkgaW4gdGhpcyBjYXNlLCBub3QgYSBzaW5nbGUtY29sdW1uIGRhdGEgZnJhbWUhIFdlIGNvbmZpcm0gdGhpcyBieSByZXR1cm5pbmcgdGhlIGNsYXNzIG9mIG91ciBqdXN0IGdlbmVyYXRlZCBvdXRwdXQ6CmBgYHtyfQpjbGFzcyhleGFtc3F1aXpbNDo4LDNdKQpgYGAKCkluIG9yZGVyIHRvIHJldHVybiBvdXIgZmlsdGVyZWQgb3V0cHV0IGFzIGEgZGF0YSBmcmFtZSwgd2Ugc2V0IHRoZSBvcHRpb25hbCBpbnB1dCBhcmd1bWVudCAiZHJvcCIgdG8gRkFMU0UsIHdoaWNoIGd1YXJhbnRlZXMgdXMgdGhhdCB0aGUgZmlsdGVyZWQgZGF0YSB3aWxsIGJlIHJldHVybmVkIGFzIGEgZGF0YSBmcmFtZSwgYXMgd2VsbC4KYGBge3J9CmV4YW1zcXVpels0OjgsMyxkcm9wPUZBTFNFXQpgYGAKCkFnYWluLCByZXR1cm4gdGhlIGNsYXNzIG9mIG91ciByZWNlbnQgb3V0cHV0LCB3aGVuIHNldHRpbmcgdGhlIGRyb3AgcGFyYW1ldGVyIHRvIEZBTFNFOgpgYGB7cn0KY2xhc3MoZXhhbXNxdWl6WzQ6OCwzLGRyb3A9RkFMU0VdKQpgYGAKSW5kZWVkLCB0aGUgZmlsdGVyZWQgZGF0YSBpcyBzdGlsbCBpbnRlcnByZXRlZCBhcyBhIGRhdGEgZnJhbWUgbm93LgoKTm90ZSB0aGF0IGluIHRoYXQgc2Vjb25kIGNhbGwsIHNpbmNlIGV4YW1zcXVpels0OjgsM10gaXMgYSB2ZWN0b3IsIFIgY3JlYXRlZCBhIHZlY3RvciBpbnN0ZWFkIG9mIGFub3RoZXIgZGF0YSBmcmFtZS4gQnkgc3BlY2lmeWluZyBkcm9wPUZBTFNFLCBhcyBkZXNjcmliZWQgZm9yIHRoZSBtYXRyaXggY2FzZSBpbiBTZWN0aW9uIDMuNiwgd2UgY2FuIGtlZXAgaXQgYXMgYSAob25lY29sdW1uKSBkYXRhIGZyYW1lLgoKV2UgY2FuIGFsc28gZG8gZmlsdGVyaW5nLiBIZXJl4oCZcyBob3cgdG8gZXh0cmFjdCB0aGUgc3ViZnJhbWUgb2YgYWxsIHN0dWRlbnRzIHdob3NlIGZpcnN0IGV4YW0gc2NvcmUgd2FzIGF0IGxlYXN0IDMuNjoKYGBge3J9CmV4YW1zcXVpeltleGFtc3F1aXokRXhhbTEgPj0gMy42LF0KYGBgCgoKKipNb3JlIG9uIFRyZWF0bWVudCBvZiBOQSBWYWx1ZXMqKgoKU3VwcG9zZSB0aGUgc2Vjb25kIGV4YW0gc2NvcmUgZm9yIHRoZSBmaXJzdCBzdHVkZW50IGhhZCBiZWVuIG1pc3NpbmcuIFRoZW4gd2Ugd291bGQgaGF2ZSB0eXBlZCB0aGUgZm9sbG93aW5nIGludG8gdGhhdCBsaW5lIHdoZW4gd2Ugd2VyZSBwcmVwYXJpbmcgdGhlIGRhdGEgZmlsZToKYGBge3J9CiMyLjAgTkEgMy4wCmBgYAoKCkluIGFueSBzdWJzZXF1ZW50IHN0YXRpc3RpY2FsIGFuYWx5c2VzLCBSIHdvdWxkIGRvIGl0cyBiZXN0IHRvIGNvcGUgd2l0aCB0aGUgbWlzc2luZyBkYXRhLiBIb3dldmVyLCBpbiBzb21lIHNpdHVhdGlvbnMsIHdlIG5lZWQgdG8gc2V0IHRoZSBvcHRpb24gbmEucm09VFJVRSwgZXhwbGljaXRseSB0ZWxsaW5nIFIgdG8gaWdub3JlIE5BIHZhbHVlcy4gRm9yIGluc3RhbmNlLCB3aXRoIHRoZSBtaXNzaW5nIGV4YW0gc2NvcmUsIGNhbGN1bGF0aW5nIHRoZSBtZWFuIHNjb3JlIG9uIGV4YW0gMiBieSBjYWxsaW5nIFLigJlzIG1lYW4oKSBmdW5jdGlvbiB3b3VsZCBza2lwIHRoYXQgZmlyc3Qgc3R1ZGVudCBpbiBmaW5kaW5nIHRoZSBtZWFuLiBPdGhlcndpc2UsIFIgd291bGQganVzdCByZXBvcnQgTkEgZm9yIHRoZSBtZWFuLgoKSGVyZeKAmXMgYSBsaXR0bGUgZXhhbXBsZToKCkNyZWF0ZSBhbiBhcnJheSB4LCBjb250YWluaW5nIGEgTkEgY29tcG9uZW50OgpgYGB7cn0KeCA8LSBjKDEsTkEsOSkKbWVhbih4KQpgYGAKVGhlIG1lYW4gaXMgZGV0ZXJtaW5lZCBhcyBOQSwgc2luY2UgTkEgKyB4ID0gTkEgZm9yIGFsbCByZWFsIG51bWJlcnMgeC4gV2UgY2Fubm90IHBlcmZvcm0gdXNlZnVsIGFyaXRobWV0aWMgb3BlcmF0aW9ucywgdGFraW5nIE5BIGludG8gY29uc2lkZXJhdGlvbi4gVGhpcyBpcyB3aHkgd2UgaGF2ZSB0byBpZ25vcmUgTkEgdmFsdWVzIGZvciB0aGVzZSBjYWxjdWxhdGlvbnMsIHNldHRpbmcgbmEucm0gPSBUUlVFOgpgYGB7cn0KbWVhbih4LG5hLnJtPVRSVUUpCmBgYAoKCkluIFNlY3Rpb24gMi44LjIsIHlvdSB3ZXJlIGludHJvZHVjZWQgdG8gdGhlIHN1YnNldCgpIGZ1bmN0aW9uLCB3aGljaCBzYXZlcyB5b3UgdGhlIHRyb3VibGUgb2Ygc3BlY2lmeWluZyBuYS5ybT1UUlVFLiBZb3UgY2FuIGFwcGx5IGl0IGluIGRhdGEgZnJhbWVzIGZvciByb3cgc2VsZWN0aW9uLiBUaGUgY29sdW1uIG5hbWVzIGFyZSB0YWtlbiBpbiB0aGUgY29udGV4dCBvZiB0aGUgZ2l2ZW4gZGF0YSBmcmFtZS4gSW4gb3VyIGV4YW1wbGUsIGluc3RlYWQgb2YgdHlwaW5nIHRoaXM6CmBgYHtyfQpleGFtc3F1aXpbZXhhbXNxdWl6JEV4YW0xID49IDMuNixdCmBgYAoKV2UgY291bGQganVzdCB1c2UgdGhlIHN1YnNldCBmdW5jdGlvbiBpbnN0ZWFkLCBhcHBseWluZyB0aGUgc2FtZSBmaWx0ZXIsIGFuZCBnZXR0aW5nIHRoZSBzYW1lIG91dHB1dDoKYGBge3J9CnN1YnNldChleGFtc3F1aXosRXhhbTEgPj0gMy42KQoKCmBgYA==