Griffin R Workshop Notes
Learning Data Structures and Exploring Data Frames in R adapted from R Software Carpentry
Exploring R Data Structures
Setting up Directory and Creating a Toy Dataset
One of R’s most powerful features is its ability to deal with tabular data - such as you may already have in a spreadsheet or a CSV file.
Let’s start by creating a toy dataset of feline-data.csv in data/ directory (folder)
But if data/ directory does not exist, create it first.
Start with orienting yourself in the R environment. Where am I?
getwd() # prints the current working directory "where am I?"
dir.create("data") # creates a new directory called "data"
setwd("working/directory") # sets the working directory to the working directoryCheck if all sticky notes are green
Now, we are inside the data/ directory. We can now create a toy dataset of feline data and save it as feline-data.csv.
First, let’s create a data frame in R:
A data frame is a table or a two-dimensional array-like structure in which each column contains values of one variable and each row contains one set of values from each column. It is similar to a spreadsheet or SQL table, or a data frame in Python’s pandas library.
cats <- data.frame(coat = c("calico", "black", "tabby"),
weight = c(2.1, 5.0, 3.2),
likes_catnip = c(1, 0, 1))We can now save cats as a CSV file using the write.csv() function:
write.csv(cats, "feline-data.csv", row.names = FALSE) # it is important to call the argument row.names = FALSE to avoid writing row numbers to the CSV fileThe contents of the feline-data.csv file will look like this:
coat,weight,likes_catnip
calico,2.1,1
black,5.0,0
tabby,3.2,1We can now load this in our R environment using the read.csv() function:
cats <- read.csv(file = "data/feline-data.csv")
catsread.csv() is a function that reads a CSV file or comma-separated values file and creates a data frame in R. The file argument specifies the path to the CSV file. In this case, we are reading the feline-data.csv file from the data/ directory.
You can check out other function like read.delim for tab-separated values file but for now, we will stick with read.csv.
Check if all sticky notes are green
We should only see numeric and character data types. If you see factors, we can prevent R from automatically creating “factor” data by using the options(stringsAsFactors = FALSE) function. Then re-read the feline-data.csv file for the changes to take effect.
str(cats)Now, We can begin exploring our dataset, pulling out columns by specifying them using the $ operator:
cats$weight # prints the weight column
cats$coat # prints the coat columnWe can also do operation on the columns:
Say we discovered that the scale weighs two Kg lighter, then we have to add 2 Kg to the values in weight column.
cats$weight + 2 # does not change the original data frame, but prints the new values## do not run the code; show only!
cats$weight <- cats$weight + 2 # changes the original data frame by adding 2 Kg to the values in weight columnIf we wanted to write a sentence about our cats, we can use the paste() function to combine the values in the coat column with a string:
paste("My cat is", cats$coat)But what about adding the weight column to the coat column? Let’s try it:
cats$weight + cats$coatError in `cats$weight + cats$coat`:
! non-numeric argument to binary operatorUnderstanding what happened here is key to successfully analyzing data in R.
The error message is telling us that we cannot add a numeric vector (the weight column) to a character vector (the coat column). This is because R does not know how to combine these two different types of data.
Data Types
If you guessed that the last command will return an error because 2.1 plus black is nonsense, you’re right - and you already have some intuition about data types in R. Let’s explore this a bit more by checking the data type of the weight column:
typeof(cats$weight) There are five main types: double, integer, complex, logical and character. For historic reasons, double is also called numeric.
typeof(1L) # The L suffix forces the number to be an integer, since by default R uses float numberstypeof(1+1i)typeof(TRUE)typeof('banana')So no matter how complicated our analyses are, we will always be working with these five basic data types. The key is to understand how to manipulate them and combine them in ways that make sense for our analyses.
What if your labmate provided another details of another cat? We can add an additional row to our cats table using the rbind() function:
We assign the new row to a variable called additional_cat:
additional_cat <- data.frame(coat = "tabby", weight = "2.3 or 2.4", likes_catnip = 1)
additional_catThen we create a new data frame called cats2 by combining the original cats data frame with the additional_cat data frame using the rbind() function:
cats2 <- rbind(cats, additional_cat)
cats2Let’s check what type of data is in the weight column of the new cats2 data frame:
typeof(cats2$weight)It says character! Do you think we can still do the same math we did on them before? Let’s try it:
cats2$weight + 2Error in `cats2$weight + 2`:
! non-numeric argument to binary operatorAside from we cannot add a character type and numerical together, cats and cats2 are data frames. Data frames are one of the most common and versatile types of data structures we will work with in R. A given column in a data frame cannot be composed of different data types. In this case, R cannot store everything in the data frame column weight as a double anymore once we add the row for the additional cat (because its weight is 2.3 or 2.4).
When R reads a csv file, it reads it in as a data frame. Thus, when we loaded the cats csv file, it is stored as a data frame. We can recognize data frames by the first row that is written by the str() function:
str(cats2)'data.frame': 4 obs. of 3 variables:
$ coat : chr "calico" "black" "tabby" "tabby"
$ weight : chr "2.1" "5" "3.2" "2.3 or 2.4"
$ likes_catnip: num 1 0 1 1Data frames are composed of rows and columns, where each column has the same number of rows. Different columns in a data frame can be made up of different data types (this is what makes them so versatile), but everything in a given column needs to be the same type (e.g., vector, factor, or list).
Let’s explore more about different data structures and how they behave. For now, we will focus on our original data frame cats (and we can forget about cats2 for the rest of this lesson).
Vectors and Type Coercion
To better understand this behavior, let’s meet another of the data structures: the vector.
my_vector <- vector(length = 3)
my_vectorA vector in R is essentially an ordered list of things, with the special condition that everything in the vector must be the same basic data type. If no data type is specified, R creates a vector of type logical (TRUE/FALSE) when we use the vector() function.
Let’s try to create a vector of type character:
another_vector <- vector(mode='character', length=3)
another_vectorIt looks weird and doesn’t tell us much. Let’s check the structure using str function.
str(another_vector)Although it looks somewhat cryptic, this command indicates the basic data type found in this vector - in this case chr, character; an indication of the number of things in the vector - actually, the indexes of the vector, in this case [1:3]; and a few examples of what’s actually in the vector - in this case empty character strings. If we similarly do
str(cats$weight)we see that cats$weight is a vector, too - the columns of data we load into R data.frames are all vectors, and that’s the root of why R forces everything in a column to be the same basic data type.
By keeping everything in a column the same, we allow ourselves to make simple assumptions about our data; if you can interpret one entry in the column as a number, then you can interpret all of them as numbers, so we don’t have to check every time. This consistency is what people mean when they talk about clean data; in the long run, strict consistency goes a long way to making our lives easier in R.