Before we begin this lesson, we want you to be clear on the goal of the workshop and these lessons. We believe that every learner can achieve competency with R. You have reached competency when you find that you are able to use R to handle common analysis challenges in a reasonable amount of time (which includes time needed to look at learning materials, search for answers online, and ask colleagues for help). As you spend more time using R you will find yourself gaining competency and even expertise. There is no subsititute for regular use and practice. The more familiar you get, the more complex the analyses you will be able to carry out, with less frustration, and in less time!
Few people want to learn how to use R for its own sake. People want to learn how to use R to analyze their own research questions! These lessons assume that you want to start analyzing data as soon as possible. Given this, there are many valuable pieces of information about R that we simply won’t have time to cover. Hopefully, we will clear the hurdle of giving you just enough knowledge to be dangerous, which can be a high bar in R! We suggest you look into the additional learning materials below.
Here are some R skills we will not cover in these lessons
ggplot2
).Tip: Where to learn more
The following are good resources for learning more about R. Some of them can be quite technical, but if you are a regular R user you may ultimately need this technical knowledge.
- R for Beginners: By Emmanuel Paradis and a great starting point
- The R Manuals: Maintained by the R project
- R contributed documentation: Also linked to the R project; importantly there are materials available in several languages
- R for Data Science: A wonderful collection by noted R educators and developers Garrett Grolemund and Hadley Wickham
- Practical Data Science for Stats: Not exclusively about R usage, but a nice collection of pre-prints on data science and applications for R
- Programming in R Software Carpentry lesson: There are several Software Carpentry lessons in R to choose from
Reminder
At this point you should be coding along in the “r_basics.R” script we created in the last episode. Writing your commands and comments in the script will make it easier to record what you did and why.
We should also be regularly saving the file by clicking the single floppy disk icon or typing Ctrl + S.
What might be called a variable in many languages is called an object in R (but you can also call it a variable).
To create an object you need:
In your script, “r_basics.R”, using the R assignment operator ‘<-’, assign ‘1’ to the object ‘a’ as shown. Remember to leave a comment in the line above (using the ‘#’) to explain what you are doing:
# this line creates the object 'a' and assigns it the value '1'
a <- 1
Next, run this line of code in your script. In the RStudio ‘Console’ you should see:
> a <- 1
>
The ‘Console’ will display lines of code run from a script and any outputs or status/warning/error messages (usually in red).
In the ‘Environment’ window you will also get a table:
Values | |
---|---|
a | 1 |
The ‘Environment’ window allows you to keep track of the objects you have created in R.
Exercise: Create some objects in R
Create the following objects; give each object an appropriate name (your best guess at what name to use is fine):
- Create an object that has the value of number of pairs of human chromosomes
- Create an object that has a value of your favorite gene name
- Create an object that has this URL as its value: “https://genome.ucsc.edu”
- Create an object that has the value of the number of chromosomes in a diploid human cell
Here as some possible answers to the challenge:
human_chr_number <- 23
gene_name <- 'pten'
ensemble_url <- 'https://genome.ucsc.edu'
human_diploid_chr_num <- 2 * human_chr_number
Comment on the assignment operator
Many people use
=
as their preferred assignment operator in R instead of<-
, and this is just as well. Here is a blog post discussing the differences.
Here are some important details about naming objects in R.
-
). You can use
’_’ to make names more readable. You should avoid using special
characters in your object name (e.g. ! @ # . , etc.). Also, object names
cannot begin with a number.There are a few more suggestions about naming and style you may want to learn more about as you write more R code. There are several “style guides” that have advice, and one to start with is the tidyverse R style guide.
Tip: Pay attention to warnings in the script console
If you enter a line of code in your script that contains an error, RStudio may give you an error message and underline this mistake. Sometimes these messages are easy to understand, but often the messages may need some figuring out. Paying attention to these warnings will help you avoid mistakes. In the example below, our object name has a space, which is not allowed in R. The error message does not say this directly, but R is “not sure” about how to assign the name to “human_ chr_number” when the object name we want is “human_chr_number”.
Once an object has a value, you can change that value by overwriting it. R will not give you a warning or error if you overwriting an object, which may or may not be a good thing depending on how you look at it.
# gene_name has the value 'pten' or whatever value you used in the challenge.
# We will now assign the new value 'tp53'
gene_name <- 'tp53'
You can also remove an object from R’s memory entirely. The
rm()
function will delete the object.
# delete the object 'gene_name'
rm(gene_name)
If you run a line of code that has only an object name, R will normally display the contents of that object. In this case, we are told the object no longer exists.
Error: object 'gene_name' not found
In R, every object has two properties:
We will get to the “length” property later in the lesson. The “mode” property **corresponds to the type of data an object represents. The most common modes you will encounter in R are:
Mode (abbreviation) | Type of data |
---|---|
Numeric (num) | Numbers such floating point/decimals (1.0, 0.5, 3.14), there are also more specific numeric types (dbl - Double, int - Integer). These differences are not relevant for most beginners and pertain to how these values are stored in memory |
Character (chr) | A sequence of letters/numbers in single ’’ or double ” ” quotes |
Logical | Boolean values - TRUE or FALSE |
There are a few other modes (i.e. “complex”, “raw” etc.) but these are the three we will work with in this lesson.
Data types are familiar in many programming languages, but also in natural language where we refer to them as the parts of speech, e.g. nouns, verbs, adverbs, etc. Once you know if a word - perhaps an unfamiliar one - is a noun, you can probably guess you can count it and make it plural if there is more than one (e.g. 1 parrot, or 2 parrots). If something is an adjective, you can usually change it into an adverb by adding “-ly” (e.g. slow and slowly). Depending on the context, you may need to decide if a word is in one category or another (e.g “cut” may be a noun when it’s on your finger, or a verb when you are preparing vegetables). These concepts have important analogies when working with R objects.
Exercise: Create objects and check their modes
Create the following objects in R, then use the
mode()
function to verify their modes. Try to guess what the mode will be before you look at the solution
chromosome_name <- 'chr02'
od_600_value <- 0.47
chr_position <- '1001701'
spock <- TRUE
pilot <- Earhart
chromosome_name <- 'chr02'
od_600_value <- 0.47
chr_position <- '1001701'
spock <- TRUE
pilot <- Earhart
Error in eval(expr, envir, enclos): object 'Earhart' not found
mode(chromosome_name)
[1] "character"
mode(od_600_value)
[1] "numeric"
mode(chr_position)
[1] "character"
mode(spock)
[1] "logical"
mode(pilot)
Error in mode(pilot): object 'pilot' not found
Notice from the solution that even if a series of numbers is given as
a value R will consider them to be in the “character” mode if they are
enclosed as single or double quotes. Also, notice that you cannot take a
string of alphanumeric characters (e.g. Earhart) and assign as a value
for an object. In this case, R looks for an object named
Earhart
but since there is no object, no assignment can be
made. If Earhart
did exist, then the mode of
pilot
would be whatever the mode of Earhart
was originally. If we want to create an object called pilot
that was the name “Earhart”, we need to enclose
Earhart
in quotation marks.
pilot <- "Earhart"
mode(pilot)
[1] "character"
Once an object exists (which by definition also means it has a mode), R can appropriately manipulate that object. For example, objects of the numeric modes can be added, multiplied, divided, etc. R provides several mathematical (arithmetic) operators including:
Operator | Description |
---|---|
+ | addition |
- | subtraction |
* | multiplication |
/ | division |
^ or ** | exponentiation |
a%/%b | integer division (division where the remainder is discarded) |
a%%b | modulus (returns the remainder after division) |
These can be used with literal numbers:
(1 + (5 ** 0.5))/2
[1] 1.618034
and importantly, can be used on any object that evaluates to (i.e. interpreted by R) a numeric object:
# multiply the object 'human_chr_number' by 2
human_chr_number * 2
[1] 46
Exercise: Compute the golden ratio
One approximation of the golden ratio (φ) can be found by taking the sum of 1 and the square root of 5, and dividing by 2 as in the example above. Compute the golden ratio to 3 digits of precision using the
sqrt()
andround()
functions. Hint: remember theround()
function can take 2 arguments.
round((1 + sqrt(5))/2, digits = 3)
[1] 1.618
Notice that you can place one function inside of another.
Vectors are probably the most used commonly used object type in R.
A vector is a collection of values that are all of the same type
(numbers, characters, etc.). One of the most common ways to
create a vector is to use the c()
function - the
“concatenate” or “combine” function. Inside the function you may enter
one or more values; for multiple values, separate each value with a
comma:
# Create a vector of country names
countries <- c("Thailand","Lesotho","Suriname","Canada")
Vectors always have a mode and a
length. You can check these with the
mode()
and length()
functions respectively.
Another useful function that gives both of these pieces of information
is the str()
(structure) function.
# Check the mode, length, and structure of 'countries'
mode(countries)
[1] "character"
length(countries)
[1] 4
str(countries)
chr [1:4] "Thailand" "Lesotho" "Suriname" "Canada"
Vectors are quite important in R. Another data type that we will work with later in this lesson, data frames, are collections of vectors. What we learn here about vectors will pay off even more when we start working with data frames.
Let’s create a few more vectors to play around with:
# Top level domain of country
country_tld <- c('th', 'ls', 'sr', 'ca')
# Continent of country
continents <- c('Asia', 'Africa', 'South America', 'North America')
# Population of country
country_population <- c(69950850, 2108328, 575990, 38436447) # Source: Wikipedia
Once we have vectors, one thing we may want to do is specifically retrieve one or more values from our vector. To do so, we use bracket notation. We type the name of the vector followed by square brackets. In those square brackets we place the index (e.g. a number) in that bracket as follows:
# get the 3rd value in the countries vector
countries[3]
[1] "Suriname"
In R, every item in a vector is indexed, starting from the first item (1) through the final number of items in your vector. You can also retrieve a range of numbers:
# get the 1st through 3rd value in the countries vector
countries[1:3]
[1] "Thailand" "Lesotho" "Suriname"
If you want to retrieve several (but not necessarily sequential) items from a vector, you pass a vector of indices; a vector that has the numbered positions you wish to retrieve.
# get the 1st, 3rd, and 4th value in the countries vector
countries[c(1, 3, 4)]
[1] "Thailand" "Suriname" "Canada"
There are additional (and perhaps less commonly used) ways of subsetting a vector (see these examples). Also, several of these subsetting expressions can be combined:
# get the 4th value, and then the 1st through the 3rd value
# in other words, you can reorder the countries
countries[c(4, 1:3)]
[1] "Canada" "Thailand" "Lesotho" "Suriname"
Once you have an existing vector, you may want to add a new item to
it. To do so, you can use the c()
function again to add
your new value:
# add the countries 'Bolivia' and 'Tonga' to our list of countries
# this overwrites our existing vector
countries <- c(countries, "Bolivia", "Tonga")
We can verify that “countries” contains the new gene entry
countries
[1] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia" "Tonga"
Using a negative index will return a version of a vector with that index’s value removed:
countries[-6]
[1] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia"
We can remove that value from our vector by overwriting it with this expression:
countries <- countries[-6]
countries
[1] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia"
We can also explicitly rename or add a value to our index using double bracket notation:
countries[7]<- "Estonia"
countries
[1] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia" NA "Estonia"
Notice in the operation above that R inserts an NA
value
to extend our vector so that the country “Estonia” is an index 7. This
may be a good or not-so-good thing depending on how you use this.
Exercise: Examining and subsetting vectors
Are the following true or false of vectors in R?
- All vectors have a mode or a length
- All vectors have a mode and a length
- Vectors may have different lengths
- Items within a vector may be of different modes
- You can use the
c()
function to add one or more items to an existing vector- You can use the
c()
function to add a vector to an exiting vector
There is one last set of subsetting capabilities we want to introduce. It is possible within R to retrieve items in a vector based on a logical evaluation or numerical comparison. For example, let’s say we wanted get all of the countries in our vector of country populations that were less than 10,000,000. We could index using the ‘<’ (less than) logical operator:
country_population[country_population < 10000000]
[1] 2108328 575990
In the square brackets you place the name of the vector followed by the comparison operator and (in this case) a numeric value.
Some of the most common logical operators you will use in R are:
Operator | Description |
---|---|
< | less than |
<= | less than or equal to |
> | greater than |
>= | greater than or equal to |
== | exactly equal to |
!= | not equal to |
!x | not x |
a | b | a or b |
a & b | a and b |
The power of programming
The reason why the expression
country_population[country_population < 10000000]
works can be better understood if you examine what the expressioncountry_population < 10000000
evaluates to:country_population < 10000000
[1] FALSE TRUE TRUE FALSE
The output above is a logical vector, the 2nd and 3rd elements of which are TRUE. When you pass a logical vector as an index, R will return the TRUE values:
country_population[c(FALSE, TRUE, TRUE, FALSE)]
[1] 2108328 575990
If you have never coded before, this type of situation starts to expose the power of programming. We mentioned before that in the bracket notation you take your named vector followed by brackets which contain an index: named_vector[index]. The index needs to evaluate to a number. So, even if it does not appear to be an integer (e.g. 1, 2, 3), as long as R can evaluate it, we will get a result. That our expression
country_population < 10000000
evaluates to a number can be seen in the following situation. If you wanted to know which indices (1, 2, 3, or 4) in our vector of country populations was less than 10,000,000, you could use thewhich()
function.The
which()
function to returns the indices of any item that evaluates as TRUE in our comparison:which(country_population < 10000000)
[1] 2 3
Why this is important
Often in programming we will not know what inputs and values will be used when our code is executed. Rather than put in a pre-determined value (e.g 10000000) we can use an object that can take on whatever value we need. So for example:
population_cutoff <- 10000000 country_population[country_population < population_cutoff]
[1] 2108328 575990
Ultimately, it’s putting together flexible, reusable code like this that gets at the power of programming.
Finally, there are a few other common retrieve or replace operations
you may want to know about. First, you can check to see if any of the
values of your vector are missing (i.e. are NA
). The
is.NA()
function will return a logical vector, with TRUE
for any NA
value:
# current value of 'countries':
# chr [1:7] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia" NA "Estonia"
is.na(countries)
[1] FALSE FALSE FALSE FALSE FALSE TRUE FALSE
Sometimes, you may wish to find out if a specific value (or several
values) is present a vector. You can do this using the comparison
operator %in%
, which will return TRUE for any value in your
collection that is in the vector you are searching:
# current value of 'countries':
# chr [1:7] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia" NA "Estonia"
# test to see if "Lesotho" or "Canada" is in the countries vector
# if you are looking for more than one value, you must pass this as a vector
c("Lesotho","Canada") %in% countries
[1] TRUE TRUE
Review Exercise 1
What data types/modes are the following vectors?
country_tld
continents
country_population
typeof(country_tld)
[1] "character"
typeof(continents)
[1] "character"
typeof(country_population)
[1] "double"
Review Exercise 2
Add the following values to the specified vectors:
- To the
country_tld
vector add: “bo”- To the
continents
vector add: “South America”- To the
country_population
vector add: 11428245
country_tld <- c(country_tld, 'bo')
country_tld
[1] "th" "ls" "sr" "ca" "bo"
continents <- c(continents, "South America") # did you use quotes?
continents
[1] "Asia" "Africa" "South America" "North America"
[5] "South America"
country_population <- c(country_population, 11428245)
country_population
[1] 69950850 2108328 575990 38436447 11428245
Review Exercise 3
Make the following change to the
countries
vector:
- Create a new version of
countries
that does not contain Bolivia and then- Add 2 NA values to the end of
countries
Hint: Before starting, your vector should look like this in ‘Environment’:
chr [1:7] "Thailand" "Lesotho" "Suriname" "Canada" "Bolivia" NA "Estonia"
.If not recreate the vector by running this expression:
countries <- c("Thailand", "Lesotho", "Suriname", "Canada", "Bolivia", NA, "Estonia")
countries <- countries[-5]
countries <- c(countries, NA, NA)
countries
[1] "Thailand" "Lesotho" "Suriname" "Canada" NA "Estonia" NA
[8] NA
Review Exercise 4
Using indexing, create a new vector named
combined
that contains:
- The the 1st value in
countries
- The 1st value in
country_tld
- The 1st value in
continents
- The 1st value in
country_population
combined <- c(countries[1], country_tld[1], continents[1], country_population[1])
combined
[1] "Thailand" "th" "Asia" "69950850"
Review Exercise 5
What type of data is
combined
? Why?
typeof(combined)
[1] "character"
Lists are quite useful in R, but we won’t be using them in the these lessons. That said, you may come across lists in the way that some bioinformatics programs may store and/or return data to you. One of the key attributes of a list is that, unlike a vector, a list may contain data of more than one mode. Learn more about creating and using lists using this nice tutorial. In this one example, we will create a named list and show you how to retrieve items from the list.
# Create a named list using the 'list' function and our country examples
# Note, for easy reading we have placed each item in the list on a separate line
# Nothing special about this, you can do this for any multiline commands
# To run this command, make sure the entire command (all 4 lines) are highlighted before running
# Note also, as we are doing all this inside the list() function use of the '=' sign is good style
country_data <- list(countries = countries,
country_tld = country_tld,
continents = continents,
country_population = country_population)
# Examine the structure of the list
str(country_data)
List of 4
$ countries : chr [1:8] "Thailand" "Lesotho" "Suriname" "Canada" ...
$ country_tld : chr [1:5] "th" "ls" "sr" "ca" ...
$ continents : chr [1:5] "Asia" "Africa" "South America" "North America" ...
$ country_population: num [1:5] 69950850 2108328 575990 38436447 11428245
To get all the values for the position
object in the
list, we use the $
notation:
# return all the values of country_population object
country_data$country_population
[1] 69950850 2108328 575990 38436447 11428245
To get the first value in the country_population
object,
use the []
notation to index:
# return first value of the country_population object
country_data$country_population[1]
[1] 69950850