Remember, before you can use the tidyverse, you need to load the package.
library(tidyverse)
Coding Basics
R as a Calculator 1
(Taken form R4DS)
- Create a sequence using the
seq()
function starting at 0 and ending at 100 assigning the output to an object called integers
- Create a new vector
squares
which contains the square of every value in integers
. Remember most mathematical functions in R act on each element individually. You might want to use the ^
operator.
- Create a data frame with these two vectors as columns by running
tibble(integers, squares)
. Assign the output to an object called squares_df
- Use
ggplot()
to produce either a scatter or line plot of these two variables (or both at the same time if you’re feeling brave!)
R as a Calculator 2
- Create a new sequence called
x
containing 1000 numbers spaced uniformly between -6 and 6. You can do this by adding the length.out
parameter to seq()
and setting it to 1000
(this parameter must be specified by name, inside of the brackets)
- Calculate the sine of each value in
x
using sin()
and assign the value to an object called y
- Create a dataframe as before and plot a line graph
- Change the line colour to any one of your choosing, set the
linetype
to 2
(dashed), and size
to 1.5
Specifying Parameters by Position
- Which parameters from the following code can you use without specifying their name?
ggplot(data = mpg) +
geom_point(mapping = aes(x = displ, y = hwy, colour = factor(class)))
Reading CSVs
The ‘People’ Dataset
- Read the
people.csv
file from this session’s data
folder. Make sure you specify that sex
and race
are factors (categorical variables) and that earn
and age
are integers.
- How does level of education affect salary? Make a scatter plot to find out. You should set
position = 'jitter'
in the geom_point()
function to avoid over-plotting and perhaps also set transparency to 0.5
.
- Facet the above plot by
sex
, colouring the points too. Make sure you hide the legend with show.legend = FALSE
- Label the plot using the
labs()
function (see end of exercise sheet one)
- Create a new plot showing how earnings change with age. Use
geom_smooth()
to show the general trend
- Set the colour aesthetic in the above plot to represent the
race
variable. Include errors and set the thickness of each line to 2
In-line CSVs
- Read the following in-line CSV into R (Unknown is used here to represent a missing value)
"
Employee Database Version 3
name, age, job
John, 34, Analyst
Ann, 44, Consultant
Barry, 24, Unknown
Freya, Unknown, Developer"
- Read the following in-line CSV into R. The columns of this dataset are
City
, Area
, Population
. Make sure you don’t try to read the comment line
"
Shanghai, 6341, 24183300
Tokyo, 627, 13515271
Seoul, 605, 9806000
>> Note: Mumbai was previously called Bombai
Mumbai, 438, 12442373"
Datasets from the Web
- Go to the following webpage - https://gist.github.com/tiangechen/b68782efa49a16edaf07dc2cdaa855ea - which contains data on the top grossing movies between 2007 and 2011
- Select
download ZIP
extract the CSV file (if this is causing difficulties just use the copy in the data
folder)
- Import this dataset into R
- Run this code to clean up the dataset (we’ll learn how to make code like this next week)
movies <- movies %>%
mutate(Genre = ifelse(Genre %in% c('Comdy', 'comedy'), 'Comedy', Genre)) %>%
mutate(Genre = ifelse(Genre %in% c('Romence', 'romance'), 'Romance', Genre)) %>%
mutate(`Worldwide Gross` = as.double(str_replace(`Worldwide Gross`, '\\$', '')))
- Make a scatter plot of
Worldwide Gross
against Audience score %
coloured by Genre
. When using a column name with a space you need to surround the name with back-ticks (`)
Line geometries
Groups
- Take a look at the built in
ChickWeight
dataset. What do the columns represent?
- Make a line plot of chick weight against time. Group by chick and colour by diet
- What happens if you forget to group by chick?
- The graph in (2) is very busy. Instead of using
geom_line
, use geom_smooth
with the colour aesthetic set to Diet
. There is no need to specify the group in this case - why? Hide the error regions with se=FALSE
- Overlay the data points coloured by diet also. Add a jitter to prevent over-fitting and reduce transparency so this doesn’t distract from the trend lines
- Label the plot
Smoothing Methods
- Create a plot of sepal length against sepal width for each observation in the
iris
dataset colouring by species
- Add a
geom_smooth
layer with colour mapped to Species still. Inside this call specify method = 'lm'
. This tells geom_smooth
to use a linear model (straight line) for smoothing.
- Other methods include
loess
and gam
. See what these look like
- If no method is specified or
method = 'auto'
is included, a method is chosen automatically. In this case, which method is used?
Going Beyond
Theming Graphs
- Using the
mpg
dataset create any graph of your choosing
- Label the graph by adding a title, caption, and axis labels
- Add a new layer to the graph using the function
theme_minimal()
- Try using other themes. Type
theme_
and look at the various auto-complete options
ggplot Objects
- Run the following code
p <- ggplot(iris, aes(x = Petal.Length, y = Sepal.Length, col = Species)) +
geom_point()
- What do you think this did? What will happen if we type
p
? Give it a go
- Type the following code
p +
geom_smooth(se = FALSE, method = 'lm')
- What did that do? How can you use this to re-use code?
Scripting
- In RStudio, select
File > New File > R Script
- A new panel should open. Type the code required to generate any plot of the
Orange
dataset (Perhaps circumference against age coloured by tree. Add a trend curve if feeling confident)
- On the top bar of the script panel select
source > source
. What does this do?
- Use
File > Save
to save this script somewhere. (Feel free to delete it after)
LS0tDQp0aXRsZTogIkludG8gdGhlIFRpZHl2ZXJzZSINCnN1YnRpdGxlOiAiU2Vzc2lvbiBUd28gRXhlcmNpc2VzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KUmVtZW1iZXIsIGJlZm9yZSB5b3UgY2FuIHVzZSB0aGUgdGlkeXZlcnNlLCB5b3UgbmVlZCB0byBsb2FkIHRoZSBwYWNrYWdlLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpgYGANCg0KIyMgQ29kaW5nIEJhc2ljcw0KDQojIyMgUiBhcyBhIENhbGN1bGF0b3IgMQ0KDQooKipUYWtlbiBmb3JtIFI0RFMqKikNCg0KMS4gQ3JlYXRlIGEgc2VxdWVuY2UgdXNpbmcgdGhlIGBzZXEoKWAgZnVuY3Rpb24gc3RhcnRpbmcgYXQgMCBhbmQgZW5kaW5nIGF0IDEwMCBhc3NpZ25pbmcgdGhlIG91dHB1dCB0byBhbiBvYmplY3QgY2FsbGVkIGBpbnRlZ2Vyc2ANCjIuIENyZWF0ZSBhIG5ldyB2ZWN0b3IgYHNxdWFyZXNgIHdoaWNoIGNvbnRhaW5zIHRoZSBzcXVhcmUgb2YgZXZlcnkgdmFsdWUgaW4gYGludGVnZXJzYC4gUmVtZW1iZXIgbW9zdCBtYXRoZW1hdGljYWwgZnVuY3Rpb25zIGluIFIgYWN0IG9uIGVhY2ggZWxlbWVudCBpbmRpdmlkdWFsbHkuIFlvdSBtaWdodCB3YW50IHRvIHVzZSB0aGUgYF5gIG9wZXJhdG9yLg0KMy4gQ3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIHRoZXNlIHR3byB2ZWN0b3JzIGFzIGNvbHVtbnMgYnkgcnVubmluZyBgdGliYmxlKGludGVnZXJzLCBzcXVhcmVzKWAuIEFzc2lnbiB0aGUgb3V0cHV0IHRvIGFuIG9iamVjdCBjYWxsZWQgYHNxdWFyZXNfZGZgDQo0LiBVc2UgYGdncGxvdCgpYCB0byBwcm9kdWNlIGVpdGhlciBhIHNjYXR0ZXIgb3IgbGluZSBwbG90IG9mIHRoZXNlIHR3byB2YXJpYWJsZXMgKG9yIGJvdGggYXQgdGhlIHNhbWUgdGltZSBpZiB5b3UncmUgZmVlbGluZyBicmF2ZSEpDQoNCiMjIyBSIGFzIGEgQ2FsY3VsYXRvciAyDQoNCjEuIENyZWF0ZSBhIG5ldyBzZXF1ZW5jZSBjYWxsZWQgYHhgIGNvbnRhaW5pbmcgMTAwMCBudW1iZXJzIHNwYWNlZCB1bmlmb3JtbHkgYmV0d2VlbiAtNiBhbmQgNi4gWW91IGNhbiBkbyB0aGlzIGJ5IGFkZGluZyB0aGUgYGxlbmd0aC5vdXRgIHBhcmFtZXRlciB0byBgc2VxKClgIGFuZCBzZXR0aW5nIGl0IHRvIGAxMDAwYCAodGhpcyBwYXJhbWV0ZXIgbXVzdCBiZSBzcGVjaWZpZWQgYnkgbmFtZSwgaW5zaWRlIG9mIHRoZSBicmFja2V0cykNCjIuIENhbGN1bGF0ZSB0aGUgc2luZSBvZiBlYWNoIHZhbHVlIGluIGB4YCB1c2luZyBgc2luKClgIGFuZCBhc3NpZ24gdGhlIHZhbHVlIHRvIGFuIG9iamVjdCBjYWxsZWQgYHlgDQozLiBDcmVhdGUgYSBkYXRhZnJhbWUgYXMgYmVmb3JlIGFuZCBwbG90IGEgbGluZSBncmFwaA0KNC4gQ2hhbmdlIHRoZSBsaW5lIGNvbG91ciB0byBhbnkgb25lIG9mIHlvdXIgY2hvb3NpbmcsIHNldCB0aGUgYGxpbmV0eXBlYCB0byBgMmAgKGRhc2hlZCksIGFuZCBgc2l6ZWAgdG8gMS41DQoNCiMjIyBTcGVjaWZ5aW5nIFBhcmFtZXRlcnMgYnkgUG9zaXRpb24NCg0KMS4gV2hpY2ggcGFyYW1ldGVycyBmcm9tIHRoZSBmb2xsb3dpbmcgY29kZSBjYW4geW91IHVzZSB3aXRob3V0IHNwZWNpZnlpbmcgdGhlaXIgbmFtZT8gDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpnZ3Bsb3QoZGF0YSA9IG1wZykgKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeCA9IGRpc3BsLCB5ID0gaHd5LCBjb2xvdXIgPSBmYWN0b3IoY2xhc3MpKSkNCmBgYA0KDQojIyBSZWFkaW5nIENTVnMNCg0KIyMjIFRoZSAnUGVvcGxlJyBEYXRhc2V0DQoNCjEuIFJlYWQgdGhlIGBwZW9wbGUuY3N2YCBmaWxlIGZyb20gdGhpcyBzZXNzaW9uJ3MgYGRhdGFgIGZvbGRlci4gTWFrZSBzdXJlIHlvdSBzcGVjaWZ5IHRoYXQgYHNleGAgYW5kIGByYWNlYCBhcmUgZmFjdG9ycyAoY2F0ZWdvcmljYWwgdmFyaWFibGVzKSBhbmQgdGhhdCBgZWFybmAgYW5kIGBhZ2VgIGFyZSBpbnRlZ2Vycy4NCjIuIEhvdyBkb2VzIGxldmVsIG9mIGVkdWNhdGlvbiBhZmZlY3Qgc2FsYXJ5PyBNYWtlIGEgc2NhdHRlciBwbG90IHRvIGZpbmQgb3V0LiBZb3Ugc2hvdWxkIHNldCBgcG9zaXRpb24gPSAnaml0dGVyJ2AgaW4gdGhlIGBnZW9tX3BvaW50KClgIGZ1bmN0aW9uIHRvIGF2b2lkIG92ZXItcGxvdHRpbmcgYW5kIHBlcmhhcHMgYWxzbyBzZXQgdHJhbnNwYXJlbmN5IHRvIGAwLjVgLg0KMy4gRmFjZXQgdGhlIGFib3ZlIHBsb3QgYnkgYHNleGAsIGNvbG91cmluZyB0aGUgcG9pbnRzIHRvby4gTWFrZSBzdXJlIHlvdSBoaWRlIHRoZSBsZWdlbmQgd2l0aCBgc2hvdy5sZWdlbmQgPSBGQUxTRWANCjQuIExhYmVsIHRoZSBwbG90IHVzaW5nIHRoZSBgbGFicygpYCBmdW5jdGlvbiAoc2VlIGVuZCBvZiBleGVyY2lzZSBzaGVldCBvbmUpDQo1LiBDcmVhdGUgYSBuZXcgcGxvdCBzaG93aW5nIGhvdyBlYXJuaW5ncyBjaGFuZ2Ugd2l0aCBhZ2UuIFVzZSBgZ2VvbV9zbW9vdGgoKWAgdG8gc2hvdyB0aGUgZ2VuZXJhbCB0cmVuZCANCjYuIFNldCB0aGUgY29sb3VyIGFlc3RoZXRpYyBpbiB0aGUgYWJvdmUgcGxvdCB0byByZXByZXNlbnQgdGhlIGByYWNlYCB2YXJpYWJsZS4gSW5jbHVkZSBlcnJvcnMgYW5kIHNldCB0aGUgdGhpY2tuZXNzIG9mIGVhY2ggbGluZSB0byBgMmANCg0KIyMjIEluLWxpbmUgQ1NWcw0KDQoxLiBSZWFkIHRoZSBmb2xsb3dpbmcgaW4tbGluZSBDU1YgaW50byBSIChVbmtub3duIGlzIHVzZWQgaGVyZSB0byByZXByZXNlbnQgYSBtaXNzaW5nIHZhbHVlKQ0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KIg0KRW1wbG95ZWUgRGF0YWJhc2UgVmVyc2lvbiAzDQoNCm5hbWUsIGFnZSwgam9iDQpKb2huLCAzNCwgQW5hbHlzdA0KQW5uLCA0NCwgQ29uc3VsdGFudA0KQmFycnksIDI0LCBVbmtub3duDQpGcmV5YSwgVW5rbm93biwgRGV2ZWxvcGVyIg0KYGBgDQoNCjIuIFJlYWQgdGhlIGZvbGxvd2luZyBpbi1saW5lIENTViBpbnRvIFIuIFRoZSBjb2x1bW5zIG9mIHRoaXMgZGF0YXNldCBhcmUgYENpdHlgLCBgQXJlYWAsIGBQb3B1bGF0aW9uYC4gTWFrZSBzdXJlIHlvdSBkb24ndCB0cnkgdG8gcmVhZCB0aGUgY29tbWVudCBsaW5lDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQoiDQpTaGFuZ2hhaSwgNjM0MSwgMjQxODMzMDANClRva3lvLCA2MjcsIDEzNTE1MjcxDQpTZW91bCwgNjA1LCA5ODA2MDAwDQo+PiBOb3RlOiBNdW1iYWkgd2FzIHByZXZpb3VzbHkgY2FsbGVkIEJvbWJhaQ0KTXVtYmFpLCA0MzgsIDEyNDQyMzczIg0KYGBgDQoNCiMjIyBEYXRhc2V0cyBmcm9tIHRoZSBXZWINCg0KMS4gR28gdG8gdGhlIGZvbGxvd2luZyB3ZWJwYWdlIC0gaHR0cHM6Ly9naXN0LmdpdGh1Yi5jb20vdGlhbmdlY2hlbi9iNjg3ODJlZmE0OWExNmVkYWYwN2RjMmNkYWE4NTVlYSAtIHdoaWNoIGNvbnRhaW5zIGRhdGEgb24gdGhlIHRvcCBncm9zc2luZyBtb3ZpZXMgYmV0d2VlbiAyMDA3IGFuZCAyMDExDQoyLiBTZWxlY3QgYGRvd25sb2FkIFpJUGAgZXh0cmFjdCB0aGUgQ1NWIGZpbGUgKGlmIHRoaXMgaXMgY2F1c2luZyBkaWZmaWN1bHRpZXMganVzdCB1c2UgdGhlIGNvcHkgaW4gdGhlIGBkYXRhYCBmb2xkZXIpDQozLiBJbXBvcnQgdGhpcyBkYXRhc2V0IGludG8gUg0KNC4gUnVuIHRoaXMgY29kZSB0byBjbGVhbiB1cCB0aGUgZGF0YXNldCAod2UnbGwgbGVhcm4gaG93IHRvIG1ha2UgY29kZSBsaWtlIHRoaXMgbmV4dCB3ZWVrKQ0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCm1vdmllcyA8LSBtb3ZpZXMgJT4lDQogIG11dGF0ZShHZW5yZSA9IGlmZWxzZShHZW5yZSAlaW4lIGMoJ0NvbWR5JywgJ2NvbWVkeScpLCAnQ29tZWR5JywgR2VucmUpKSAlPiUNCiAgbXV0YXRlKEdlbnJlID0gaWZlbHNlKEdlbnJlICVpbiUgYygnUm9tZW5jZScsICdyb21hbmNlJyksICdSb21hbmNlJywgR2VucmUpKSAlPiUNCiAgbXV0YXRlKGBXb3JsZHdpZGUgR3Jvc3NgID0gYXMuZG91YmxlKHN0cl9yZXBsYWNlKGBXb3JsZHdpZGUgR3Jvc3NgLCAnXFwkJywgJycpKSkNCmBgYA0KDQo1LiBNYWtlIGEgc2NhdHRlciBwbG90IG9mIGBXb3JsZHdpZGUgR3Jvc3NgIGFnYWluc3QgYEF1ZGllbmNlIHNjb3JlICVgIGNvbG91cmVkIGJ5IGBHZW5yZWAuIFdoZW4gdXNpbmcgYSBjb2x1bW4gbmFtZSB3aXRoIGEgc3BhY2UgeW91IG5lZWQgdG8gc3Vycm91bmQgdGhlIG5hbWUgd2l0aCBiYWNrLXRpY2tzIChcYCkNCg0KIyMgTGluZSBnZW9tZXRyaWVzDQoNCiMjIyBHcm91cHMNCg0KMS4gVGFrZSBhIGxvb2sgYXQgdGhlIGJ1aWx0IGluIGBDaGlja1dlaWdodGAgZGF0YXNldC4gV2hhdCBkbyB0aGUgY29sdW1ucyByZXByZXNlbnQ/DQoyLiBNYWtlIGEgbGluZSBwbG90IG9mIGNoaWNrIHdlaWdodCBhZ2FpbnN0IHRpbWUuIEdyb3VwIGJ5IGNoaWNrIGFuZCBjb2xvdXIgYnkgZGlldA0KMy4gV2hhdCBoYXBwZW5zIGlmIHlvdSBmb3JnZXQgdG8gZ3JvdXAgYnkgY2hpY2s/DQo0LiBUaGUgZ3JhcGggaW4gKDIpIGlzIHZlcnkgYnVzeS4gSW5zdGVhZCBvZiB1c2luZyBgZ2VvbV9saW5lYCwgdXNlIGBnZW9tX3Ntb290aGAgd2l0aCB0aGUgY29sb3VyIGFlc3RoZXRpYyBzZXQgdG8gYERpZXRgLiBUaGVyZSBpcyBubyBuZWVkIHRvIHNwZWNpZnkgdGhlIGdyb3VwIGluIHRoaXMgY2FzZSAtIHdoeT8gSGlkZSB0aGUgZXJyb3IgcmVnaW9ucyB3aXRoIGBzZT1GQUxTRWANCjUuIE92ZXJsYXkgdGhlIGRhdGEgcG9pbnRzIGNvbG91cmVkIGJ5IGRpZXQgYWxzby4gQWRkIGEgaml0dGVyIHRvIHByZXZlbnQgb3Zlci1maXR0aW5nIGFuZCByZWR1Y2UgdHJhbnNwYXJlbmN5IHNvIHRoaXMgZG9lc24ndCBkaXN0cmFjdCBmcm9tIHRoZSB0cmVuZCBsaW5lcw0KNi4gTGFiZWwgdGhlIHBsb3QNCg0KIyMjIFNtb290aGluZyBNZXRob2RzDQoNCjEuIENyZWF0ZSBhIHBsb3Qgb2Ygc2VwYWwgbGVuZ3RoIGFnYWluc3Qgc2VwYWwgd2lkdGggZm9yIGVhY2ggb2JzZXJ2YXRpb24gaW4gdGhlIGBpcmlzYCBkYXRhc2V0IGNvbG91cmluZyBieSBzcGVjaWVzDQoyLiBBZGQgYSBgZ2VvbV9zbW9vdGhgIGxheWVyIHdpdGggY29sb3VyIG1hcHBlZCB0byBTcGVjaWVzIHN0aWxsLiBJbnNpZGUgdGhpcyBjYWxsIHNwZWNpZnkgYG1ldGhvZCA9ICdsbSdgLiBUaGlzIHRlbGxzIGBnZW9tX3Ntb290aGAgdG8gdXNlIGEgKipsKippbmVhciAqKm1vZGVsKiogKHN0cmFpZ2h0IGxpbmUpIGZvciBzbW9vdGhpbmcuDQozLiBPdGhlciBtZXRob2RzIGluY2x1ZGUgYGxvZXNzYCBhbmQgYGdhbWAuIFNlZSB3aGF0IHRoZXNlIGxvb2sgbGlrZQ0KNC4gSWYgbm8gbWV0aG9kIGlzIHNwZWNpZmllZCBvciBgbWV0aG9kID0gJ2F1dG8nYCBpcyBpbmNsdWRlZCwgYSBtZXRob2QgaXMgY2hvc2VuIGF1dG9tYXRpY2FsbHkuIEluIHRoaXMgY2FzZSwgd2hpY2ggbWV0aG9kIGlzIHVzZWQ/DQoNCiMjIEdvaW5nIEJleW9uZA0KDQojIyMgVGhlbWluZyBHcmFwaHMNCg0KMS4gVXNpbmcgdGhlIGBtcGdgIGRhdGFzZXQgY3JlYXRlIGFueSBncmFwaCBvZiB5b3VyIGNob29zaW5nDQoyLiBMYWJlbCB0aGUgZ3JhcGggYnkgYWRkaW5nIGEgdGl0bGUsIGNhcHRpb24sIGFuZCBheGlzIGxhYmVscw0KMy4gQWRkIGEgbmV3IGxheWVyIHRvIHRoZSBncmFwaCB1c2luZyB0aGUgZnVuY3Rpb24gYHRoZW1lX21pbmltYWwoKWANCjQuIFRyeSB1c2luZyBvdGhlciB0aGVtZXMuIFR5cGUgYHRoZW1lX2AgYW5kIGxvb2sgYXQgdGhlIHZhcmlvdXMgYXV0by1jb21wbGV0ZSBvcHRpb25zDQoNCiMjIyBnZ3Bsb3QgT2JqZWN0cw0KDQoxLiBSdW4gdGhlIGZvbGxvd2luZyBjb2RlDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQpwIDwtIGdncGxvdChpcmlzLCBhZXMoeCA9IFBldGFsLkxlbmd0aCwgeSA9IFNlcGFsLkxlbmd0aCwgY29sID0gU3BlY2llcykpICsNCiAgZ2VvbV9wb2ludCgpDQpgYGANCg0KMi4gV2hhdCBkbyB5b3UgdGhpbmsgdGhpcyBkaWQ/IFdoYXQgd2lsbCBoYXBwZW4gaWYgd2UgdHlwZSBgcGA/IEdpdmUgaXQgYSBnbw0KMy4gVHlwZSB0aGUgZm9sbG93aW5nIGNvZGUNCg0KYGBge3IgZXZhbD1GQUxTRX0NCnAgKw0KICBnZW9tX3Ntb290aChzZSA9IEZBTFNFLCBtZXRob2QgPSAnbG0nKQ0KYGBgDQoNCjQuIFdoYXQgZGlkIHRoYXQgZG8/IEhvdyBjYW4geW91IHVzZSB0aGlzIHRvIHJlLXVzZSBjb2RlPw0KDQojIyMgU2NyaXB0aW5nDQoNCjEuIEluIFJTdHVkaW8sIHNlbGVjdCBgRmlsZSA+IE5ldyBGaWxlID4gUiBTY3JpcHRgDQoyLiBBIG5ldyBwYW5lbCBzaG91bGQgb3Blbi4gVHlwZSB0aGUgY29kZSByZXF1aXJlZCB0byBnZW5lcmF0ZSBhbnkgcGxvdCBvZiB0aGUgYE9yYW5nZWAgZGF0YXNldCAoUGVyaGFwcyBjaXJjdW1mZXJlbmNlIGFnYWluc3QgYWdlIGNvbG91cmVkIGJ5IHRyZWUuIEFkZCBhIHRyZW5kIGN1cnZlIGlmIGZlZWxpbmcgY29uZmlkZW50KQ0KMy4gT24gdGhlIHRvcCBiYXIgb2YgdGhlIHNjcmlwdCBwYW5lbCBzZWxlY3QgYHNvdXJjZSA+IHNvdXJjZWAuIFdoYXQgZG9lcyB0aGlzIGRvPyANCjQuIFVzZSBgRmlsZSA+IFNhdmVgIHRvIHNhdmUgdGhpcyBzY3JpcHQgc29tZXdoZXJlLiAoRmVlbCBmcmVlIHRvIGRlbGV0ZSBpdCBhZnRlcikNCg==