Section 6.2 Creating a Data Frame in R
Now let’s figure out how to get these rows and columns into R. One thing you will quickly learn about R is that there is almost always more than one way to accomplish a goal. Sometimes the quickest or most efficient way is not the easiest to understand. In this case we will build each column one by one and then join them together into a single data frame. This is a bit labor intensive, and not the usual way that we would work with a data set, but it is easy to understand. First, run this command to make the column of names:
myFamilyNames <- c("Dad","Mom","Sis","Bro","Dog")
One thing you might notice is that every name is placed within double quotes. This is how you signal to R that you want it to treat something as a string of characters rather than the name of a storage location. If we had asked R to use Dad instead of “Dad” it would have looked for a storage location (a data object) named Dad. Another thing to notice is that the commas separating the different values are outside of the double quotes. If you were writing a regular sentence this is not how things would look, but for computer programming the comma can only do its job of separating the different values if it is not included inside the quotes. Once you have typed the line above, remember that you can check the contents of myFamilyNames by typing it on the next command line:
myFamilyNames
The output should look like this:
[1] "Dad" "Mom" "Sis" "Bro" "Dog"
Next, you can create a vector of the ages of the family members, like this:
myFamilyAges <- c(43, 42, 12, 8, 5)
Note that this is exactly the same command we used in the last chapter, so if you have kept R running between then and now you would not even have to retype this command because myFamilyAges would still be there. Actually, if you closed R since working the examples from the last chapter you will have been prompted to “save the workspace” and if you did so, then R restored all of the data objects you were using in the last session. You can always check by typing myFamilyAges on a blank command line. The output should look like this:
[1] 43 42 12 8 5
Hey, now you have used the
c() function and the assignment arrow to make myFamilyNames and myFamilyAges. If you look at the data table earlier in the chapter you should be able to figure out the commands for creating myFamilyGenders and myFamilyWeights. In case you run into trouble, these commands also appear on the next page, but you should try to figure them out for yourself before you turn the page. In each case after you type the command to create the new data object, you should also type the name of the data object at the command line to make sure that it looks the way it should. Four variables, each one with five values in it. Two of the variables are character data and two of the variables are integer data. Here are those two extra commands in case you need them:
myFamilyGenders <- c("Male","Female","Female","Male","Female")
myFamilyWeights <- c(188,136,83,61,44)
Now we are ready to tackle the data frame. In R, a data frame is a list of columns, where each element in the list is a vector. Each vector is the same length, which is how we get our nice rectangular row and column setup, and generally each vector also has its own name. The command to make a data frame is:
myFamily <- data.frame(myFamilyNames,
myFamilyAges, myFamilyGenders, myFamilyWeights)
Look out! We’re starting to get commands that are long enough that they break onto more than one line. The + at the end of the first line tells R to wait for more input on the next line before trying to process the command. If you want to, you can type the whole thing as one line in R, but if you do, just leave out the plus sign. Anyway, the
data.frame() function makes a data frame from the four vectors that we previously typed in. Notice that we have also used the assignment arrow to make a new stored location where R puts the data frame. This new data object, called myFamily, is our data frame. Once you have gotten that command to work, type myFamily at the command line to get a report back of what the data frame contains. Here’s the output you should see:
myFamilyNames myFamilyAges myFamilyGenders myFamilyWeights
1 Dad 43 Male 188
2 Mom 42 Female 136
3 Sis 12 Female 83
4 Bro 8 Male 61
5 Dog 5 Female 44
This looks great. Notice that R has put row numbers in front of each row of our data. These are different from the output line numbers we saw in brackets before, because these are actual “indices” into the data frame. In other words, they are the row numbers that R uses to keep track of which row a particular piece of data is in.
With a small data set like this one, only five rows, it is pretty easy to take a look at all of the data. But when we get to a bigger data set this won’t be practical. We need to have other ways of summarizing what we have. The first method reveals the type of "structure" that R has used to store a data object.
str(myFamily)
'data.frame': 5 obs. of 4 variables:
$ myFamilyNames : Factor w/ 5 levels "Bro","Dad","Dog",..: 2 4 5 1 3
$ myFamilyAges : num 43 42 12 8 5
$ myFamilyGenders: Factor w/ 2 levels "Female","Male": 2 1 1 2 1
$ myFamilyWeights: num 188 136 83 61 44
Take note that for the first time, the example shows the command prompt “>” in order to differentiate the command from the output that follows. You don’t need to type this: R provides it whenever it is ready to receive new input. From now on in the book, there will be examples of R commands and output that are mixed together, so always be on the lookout for “>” because the command after that is what you have to type.
OK, so the function
str() reveals the structure of the data object that you name between the parentheses. In this case we pretty well knew that myFamily was a data frame because we just set that up in a previous command. In the future, however, we will run into many situations where we are not sure how R has created a data object, so it is important to know str() so that you can ask R to report what an object is at any time.
In the first line of output we have the confirmation that
myFamily is a data frame as well as an indication that there are five observations (“obs.” which is another word that statisticians use instead of cases or instances) and four variables. After that first line of output, we have four sections that each begin with “$”. For each of the four variables, these sections describe the component columns of the myFamily data frame object.
Each of the four variables has a “mode” or type that is reported by R right after the colon on the line that names the variable:
$ myFamilyGenders: Factor w/ 2 levels
For example,
myFamilyGenders is shown as a “Factor.” In the terminology that R uses, Factor refers to a special type of label that can be used to identify and organize groups of cases. R has organized these labels alphabetically and then listed out the first few cases (because our dataframe is so small it actually is showing us all of the cases). For myFamilyGenders we see that there are two “levels,” meaning that there are two different options: female and male. R assigns a number, starting with one, to each of these levels, so every case that is “Female” gets assigned a 1 and every case that is “Male” gets assigned a 2 (because Female comes before Male in the alphabet, so Female is the first Factor label, so it gets a 1). If you have your thinking cap on, you may be wondering why we started out by typing in small strings of text, like “Male,” but then R has gone ahead and converted these small pieces of text into numbers that it calls “Factors.” The reason for this lies in the statistical origins of R. For years, researchers have done things like calling an experimental group “Exp” and a control group “Ctl” without intending to use these small strings of text for anything other than labels. So R assumes, unless you tell it otherwise, that when you type in a short string like “Male” that you are referring to the label of a group, and that R should prepare for the use of Male as a “Level” of a “Factor.” When you don’t want this to happen you can instruct R to stop doing this with an option on the data.frame() function: stringsAsFactors=FALSE. We will look with more detail at options and defaults a little later on.
Phew, that was complicated! By contrast, our two numeric variables,
myFamilyAges and myFamilyWeights, are very simple. You can see that after the colon the mode is shown as “num” (which stands for numeric) and that the first few values are reported:
$ myFamilyAges : num 43 42 12 8 5
Putting it altogether, we have pretty complete information about the
myFamily data frame and we are just about ready to do some more work with it. We have seen firsthand that R has some pretty cryptic labels for things as well as some obscure strategies for converting this to that. R was designed for experts, rather than novices, so we will just have to take our lumps so that one day we can be experts too.
Next, we will examine another very useful function called
summary(). The summary() function provides some overlapping information to str() but also goes a little further, particularly with numeric variables. Here’s what we get:
summary(myFamily)
myFamilyNames myFamilyAges myFamilyGenders myFamilyWeights
Bro:1 Min. : 5.0 Female:3 Min. : 44.0
Dad:1 1st Qu.: 8.0 Male :2 1st Qu.: 61.0
Dog:1 Median :12.0 Median : 83.0
Mom:1 Mean :22.0 Mean :102.4
Sis:1 3rd Qu.:42.0 3rd Qu.:136.0
Max. :43.0 Max. :188.0
In order to fit on the page properly, these columns have been reorganized a bit. The name of a column/variable sits up above the information that pertains to it, and each block of information is independent of the others (so it is meaningless, for instance, that
Bro: 1 and Min. happen to be on the same line of output). Notice, as with str(), that the output is quite different depending upon whether we are talking about a Factor, like myFamilyNames or myFamilyGenders, versus a numeric variable like myFamilyAges and myFamilyWeights. The columns for the Factors list out a few of the Factor names along with the number of occurrences of cases that are coded with that factor. So for instance, under myFamilyGenders it shows three females and two males. In contrast, for the numeric variables we get five different calculated quantities that help to summarize the variable. There’s no time like the present to start to learn about what these are, so here goes:
-
“Min.” refers to the minimum or lowest value among all the cases. For this data frame, 5 is the age of the dog and it is the lowest age of all of the family members.
-
“1st Qu.” refers to the dividing line at the top of the first quartile. If we took all the cases and lined them up side by side in order of age (or weight) we could then divide up the whole into four groups, where each group had the same number of observations. Just like a number line, the smallest cases would be on the left with the largest on the right. If we’re looking at myFamilyAges, the leftmost group, which contains one quarter of all the cases, would start with five on the low end (the dog) and would have eight on the high end (Bro). So the “first quartile” is the value of age (or another variable) that divides the first quarter of the cases from the other three quarters. Note that if we don’t have a number of cases that divides evenly by four, the value is an approximation.
-
“Median” refers to the value of the case that splits the whole group in half, with half of the cases having higher values and half having lower values. If you think about it a little bit, the median is also the dividing line that separates the second quartile from the third quartile.
-
“Mean,” as we have learned before, is the numeric average of all of the values. For instance, the average age in the family is reported as 22.
-
“3rd Qu.” is the third quartile. If you remember back to the first quartile and the median, this is the third and final dividing line that splits up all of the cases into four equal sized parts. You may be wondering about these quartiles and what they are useful for. Statisticians like them because they give a quick sense of the shape of the distribution. Everyone has the experience of sorting and dividing things up — pieces of pizza, playing cards into hands, a bunch of players into teams — and it is easy for most people to visualize four equal sized groups and useful to know how high you need to go in age or weight (or another variable) to get to the next dividing line between the groups.
-
Finally, “Max.” is the maximum value and as you might expect displays the highest value among all of the available cases. For example, in this data frame Dad has the highest weight: 188. Seems like a pretty trim guy.
Just one more topic before ending this chapter: How to access the stored variables in our new data frame. R stores the data frame as a list of vectors and we can use the name of the data frame together with the name of a vector using the
$ to connect the two labels:
> myFamily$myFamilyAges
[1] 43 42 12 8 5
If you’re alert you might wonder why we went to the trouble of typing out that big long thing with the
$ in the middle, when we could have just referred to myFamilyAges as we did earlier when we were setting up the data. Well, this is a very important point. When we created the myFamily data frame, R copied all of the information from the individual vectors into a brand new storage space. So myFamily$myFamilyAges actually refers to a completely separate (but so far identical) vector of values. You can prove this to yourself very easily, and you should, by adding some data to the original vector, myFamilyAges:
> myFamilyAges <- c(myFamilyAges, 11)
> myFamilyAges
[1] 43 42 12 8 5 11
> myFamily$myFamilyAges
[1] 43 42 12 8 5
Look very closely at the five lines above. In the first line, we use the
c() command to add the value 11 to the original list of ages that we had stored in myFamilyAges (perhaps we have adopted an older cat into the family). In the second line we ask R to report what the vector myFamilyAges now contains. Dutifully, R reports that myFamilyAges now contains the original five values and the new value of 11 on the end of the list. When we ask R to report myFamily$myFamilyAges, however, we still have the original list of five values only. This shows that the data frame and its component columns/vectors is now a completely independent piece of data. We must be very careful, if we established a data frame that we want to use for subsequent analysis, that we don’t make a mistake and keep using some of the original data from which we assembled the data frame.
Here’s a puzzle that follows on from this question. We have a nice data frame with five observations and four variables. This is a rectangular shaped data set, as we discussed at the beginning of the chapter. What if we tried to add on a new piece of data on the end of one of the variables? In other words, what if we tried something like this command:
myFamily$myFamilyAges <- c(myFamily$myFamilyAges, 11)
If this worked, we would have a pretty weird situation: The variable in the data frame that contained the family members’ ages would all of a sudden have one more observation than the other variables: no more perfect rectangle! Try it out and see what happens. The result helps to illuminate how R approaches situations like this.
