vec1 <- c(1, 2, 3, 4, 5, 6)
vec2 <- c("A", "B", "C", "D", "E", "F")Exercise 2
In this practical, you will play around with the different types of R objects.
1 Creating Data Objects
Create two vectors
- One named
vec1containing the integers 1 through 6 - One named
vec2with letters A through F
We can use concatenation function, c(), to create a vector. A vector is just a series of numbers, letters, or other data elements. An alternative solution is vec1 <- 1:6 vec2 <- LETTERS[1:6]
Create two matrices
- One named
mat1fromvec1 - One named
mat2fromvec2
Define both matrices to have 3 rows and 2 columns.
mat1 <- matrix(vec1, nrow = 3, ncol = 2)
mat2 <- matrix(vec2, nrow = 3, ncol = 2)We can use the matrix() function to create a matrix. When we create a matrix, we need to specify the dimensions (in this case 3 \(\times\) 2).
Inspect vec1, vec2, mat1, and mat2
Are they all numeric?
vec1[1] 1 2 3 4 5 6
vec2[1] "A" "B" "C" "D" "E" "F"
mat1 [,1] [,2]
[1,] 1 4
[2,] 2 5
[3,] 3 6
mat2 [,1] [,2]
[1,] "A" "D"
[2,] "B" "E"
[3,] "C" "F"
No. vec1 and mat1 contain numbers, but vec2 and mat2 contain characters.
Make a 6 \(\times\) 2 matrix named mat3 from both vec1 and vec2
- Use
vec1to populate the first column - Use
vec2to populate the second column
Inspect this matrix.
mat3 <- matrix(c(vec1, vec2), 6, 2)
mat3 [,1] [,2]
[1,] "1" "A"
[2,] "2" "B"
[3,] "3" "C"
[4,] "4" "D"
[5,] "5" "E"
[6,] "6" "F"
OR
mat3b <- cbind(vec1, vec2)
mat3b vec1 vec2
[1,] "1" "A"
[2,] "2" "B"
[3,] "3" "C"
[4,] "4" "D"
[5,] "5" "E"
[6,] "6" "F"
All elements in a matrix must have the same type. So, if one or more elements in the matrix are characters, all other elements are converted to characters.
Notice that the second approach (using the column bind function, cbind()) returns a matrix with the column names set to the names of the bound objects.
To create a rectangular dataset that contains different types of data (e.g., both numeric and character variables), we can create a data frame. When working with real datasets (which often contain diverse data types), data frames are usually preferred over matrices. We only really use matrices when doing pure numerical calculation, such as in matrix algebra.
Make a two-column data frame called dat3
- Use function
data.frame(). - Set
vec1andvec2as the columns. - Name the columns
v1andv2, respectively.
dat3 <- data.frame(v1 = vec1, v2 = vec2)
dat3 v1 v2
1 1 A
2 2 B
3 3 C
4 4 D
5 5 E
6 6 F
2 Data Manipulation
Make a 2-column data frame called dat3b
- Use the
as.data.frame()function with the matrix created in 1 as input. - Set the argument
stringsAsFactors = TRUE. - Name the columns
v1andv2, respectively.
Are the types of the v1 and v2 columns the same as the types of vec1 and vec2, respectively?
dat3b <- as.data.frame(mat3, stringsAsFactors = TRUE)
(colnames(dat3b) <- c("v1", "v2"))[1] "v1" "v2"
This is a tricky situation. At face value, everything may seem fine, but, we need to be careful. The above code may not be doing what you expected. Because every element of mat3 is a character, we have lost the numeric nature of vec1.
Check if the first columns in the data frames from 2 and 3 are numeric
If these columns are not numeric, determine their type.
is.numeric(dat3[, 1])[1] TRUE
is.numeric(dat3b[, 1])[1] FALSE
The first column in dat3b (from 3 is not numeric. As a matter of fact, it is not a character vector, either.
is.character(dat3b[, 1])[1] FALSE
Rather tricky; the function as.data.frame() has converted the first variable to a factor.
is.factor(dat3b[, 1])[1] TRUE
Factors are used to represent categorical variables. Character vectors are not converted to factors by default, but the argument stringsAsFactors = TRUE forces the conversion. Hopefully, you can now see that there is a difference between character vectors and factors. Now, you also know how to force the conversion from character vectors to factors when creating a data frame.
Select the following elements from the data frame you created in 2
- The third row
- The second column
- The intersection of the above
dat3[3, ] # 3rd row v1 v2
3 3 C
dat3[, 2] # 2nd column[1] "A" "B" "C" "D" "E" "F"
dat3$v2 # Also 2nd column[1] "A" "B" "C" "D" "E" "F"
dat3[3, 2] # Intersection[1] "C"
The matrix-style subsetting operator, [ , ], is very useful in R. For matrices and data frames, the first number (before the comma) indexes rows and the second number (after the comma) indexes columns. Vectors don’t have dimensions, so we only need to provide one number for subsetting. For example, vec1[3] would yield 3. Try it.
In data frames, columns can also be extracted with the $ sign, but only if a name has been assigned to the column. If you do not name the columns of your data frame, R will assign default column names.
Inspect the structure of the data frame that you created in 2.
The structure function, str(), allows us to inspect the structure of an R object. Try using it here.
str(dat3)'data.frame': 6 obs. of 2 variables:
$ v1: num 1 2 3 4 5 6
$ v2: chr "A" "B" "C" "D" ...
Inspecting the structure of your data is vital. If, when we start analyzing our data, we assume the wrong measurement level for some of our variables, we may run into serious problems. One frequent problem occurs when categorical variables are represented on the data as numeric vectors and not factors.
Notice how the information returned by str() mirrors the information in RStudio’s environment pane. The environment pane is simply reported the ouptut from the str() function in a beautified format.
Let’s pretend the first variable (v1) in the data frame you created in 2 (dat3) is not coded correctly, and it actually represents grouping information about cities.
Convert the v1 variable into a factor with the levels:
- Utrecht
- New York,
- London
- Singapore
- Rome
- Cape Town
dat3$v1 <- factor(
dat3$v1,
labels = c("Utrecht", "New York", "London", "Singapore", "Rome", "Cape Town")
)
dat3 v1 v2
1 Utrecht A
2 New York B
3 London C
4 Singapore D
5 Rome E
6 Cape Town F
3 Working with Real Data
Load the workspace boys.RData
You can download the boys.RData workspace here.
There are a few ways to go about load workspaces that are available on the internet. We’ll use the boys.RData workspace to demonstrate.
- You can download the boys.RData file via the link above and use the
load()function to load it into your environment.- Note that the following code assumes you have saved boys.RData in a subdirectory of your project folder callyed “data”.
- Note that the following code assumes you have saved boys.RData in a subdirectory of your project folder callyed “data”.
load("data/boys.RData")- You can double-click the
boys.RDatafile in your operating system’s file browser- If the file opens in R and not RStudio, you will need to right-click and select the menu option: “Open with” > “RStudio”.
- You can import workspaces directly from the internet by creating and loading a connection.
con <- url("https://www.kylemlang.com/prepR/data/boys.RData")
load(con)In the above code, we store the connection in the object con, and then we load the connection with load(con).
After executing any of the above options, the boys object will be added to your Global Environment. You can then use the boys data in your analyses.
Most R packages ship with datasets included (these datasets are most often used for examples to demonstrate the functionality of the package). Since you have not yet learned how to load packages, you get the boys data (which comes from the mice package) as a stand-alone workspace.
View the boys dataset two ways
- By executing
boysin the console - By using the
View()function
boys
View(boys)The output is not displayed here as it is simply too large.
Using View() is preferred for inspecting all but the smallest datasets. View() shows the contents of the dataset in a spreadsheet-like window. View() is only or viewing the data; you can not edit the dataset’s contents through the window generated by View()..
Find the dimensions of the boys dataset
dim(boys)[1] 748 9
There are r nrow(boys) cases on r ncol(boys) variables.
Inspect the first 6 cases and the final 6 cases in the dataset
boys[1:6, ] age hgt wgt bmi hc gen phb tv reg
3 0.035 50.1 3.650 14.54 33.7 <NA> <NA> NA south
4 0.038 53.5 3.370 11.77 35.0 <NA> <NA> NA south
18 0.057 50.0 3.140 12.56 35.2 <NA> <NA> NA south
23 0.060 54.5 4.270 14.37 36.7 <NA> <NA> NA south
28 0.062 57.5 5.030 15.21 37.3 <NA> <NA> NA south
36 0.068 55.5 4.655 15.11 37.0 <NA> <NA> NA south
boys[743:748, ] age hgt wgt bmi hc gen phb tv reg
7410 20.372 188.7 59.8 16.79 55.2 <NA> <NA> NA west
7418 20.429 181.1 67.2 20.48 56.6 <NA> <NA> NA north
7444 20.761 189.1 88.0 24.60 NA <NA> <NA> NA west
7447 20.780 193.5 75.4 20.13 NA <NA> <NA> NA west
7451 20.813 189.0 78.0 21.83 59.9 <NA> <NA> NA north
7475 21.177 181.8 76.5 23.14 NA <NA> <NA> NA east
OR
head(boys) age hgt wgt bmi hc gen phb tv reg
3 0.035 50.1 3.650 14.54 33.7 <NA> <NA> NA south
4 0.038 53.5 3.370 11.77 35.0 <NA> <NA> NA south
18 0.057 50.0 3.140 12.56 35.2 <NA> <NA> NA south
23 0.060 54.5 4.270 14.37 36.7 <NA> <NA> NA south
28 0.062 57.5 5.030 15.21 37.3 <NA> <NA> NA south
36 0.068 55.5 4.655 15.11 37.0 <NA> <NA> NA south
tail(boys) age hgt wgt bmi hc gen phb tv reg
7410 20.372 188.7 59.8 16.79 55.2 <NA> <NA> NA west
7418 20.429 181.1 67.2 20.48 56.6 <NA> <NA> NA north
7444 20.761 189.1 88.0 24.60 NA <NA> <NA> NA west
7447 20.780 193.5 75.4 20.13 NA <NA> <NA> NA west
7451 20.813 189.0 78.0 21.83 59.9 <NA> <NA> NA north
7475 21.177 181.8 76.5 23.14 NA <NA> <NA> NA east
The functions head() and tail() are very useful. For example, by comparing the output from these functions, we can infer that the data are very likely sorted on age.
Check if the boys data are sorted on age
To verify if the data are sorted, we can use the is.unsorted() function to test the inverse of that statement.
Remember that we can always search the help for functions. For example, we could have searched using ?sort or ??sorted and quickly found the function is.unsorted().
is.unsorted(boys$age)[1] FALSE
Since this function returns FALSE, we know that boys$age is sorted. To directly test if boys$age is sorted, we could have used:
!is.unsorted(boys$age)[1] TRUE
This expression tests if boys$age is NOT unsorted, so the return value of TRUE tells us that boys$age is sorted.
Inspect the boys dataset with str()
str(boys)'data.frame': 748 obs. of 9 variables:
$ age: num 0.035 0.038 0.057 0.06 0.062 0.068 0.068 0.071 0.071 0.073 ...
$ hgt: num 50.1 53.5 50 54.5 57.5 55.5 52.5 53 55.1 54.5 ...
$ wgt: num 3.65 3.37 3.14 4.27 5.03 ...
$ bmi: num 14.5 11.8 12.6 14.4 15.2 ...
$ hc : num 33.7 35 35.2 36.7 37.3 37 34.9 35.8 36.8 38 ...
$ gen: Ord.factor w/ 5 levels "G1"<"G2"<"G3"<..: NA NA NA NA NA NA NA NA NA NA ...
$ phb: Ord.factor w/ 6 levels "P1"<"P2"<"P3"<..: NA NA NA NA NA NA NA NA NA NA ...
$ tv : int NA NA NA NA NA NA NA NA NA NA ...
$ reg: Factor w/ 5 levels "north","east",..: 4 4 4 4 4 4 4 3 3 2 ...
Use one or more functions to generate numeric summaries of each variable’s distribution
At least show the minimum, the maximum, the mean, and the median for all of the variables.
summary(boys) age hgt wgt bmi
Min. : 0.035 Min. : 50.00 Min. : 3.14 Min. :11.77
1st Qu.: 1.581 1st Qu.: 84.88 1st Qu.: 11.70 1st Qu.:15.90
Median :10.505 Median :147.30 Median : 34.65 Median :17.45
Mean : 9.159 Mean :132.15 Mean : 37.15 Mean :18.07
3rd Qu.:15.267 3rd Qu.:175.22 3rd Qu.: 59.58 3rd Qu.:19.53
Max. :21.177 Max. :198.00 Max. :117.40 Max. :31.74
NA's :20 NA's :4 NA's :21
hc gen phb tv reg
Min. :33.70 G1 : 56 P1 : 63 Min. : 1.00 north: 81
1st Qu.:48.12 G2 : 50 P2 : 40 1st Qu.: 4.00 east :161
Median :53.00 G3 : 22 P3 : 19 Median :12.00 west :239
Mean :51.51 G4 : 42 P4 : 32 Mean :11.89 south:191
3rd Qu.:56.00 G5 : 75 P5 : 50 3rd Qu.:20.00 city : 73
Max. :65.00 NA's:503 P6 : 41 Max. :25.00 NA's : 3
NA's :46 NA's:503 NA's :522
Give the standard deviations for age and bmi
Tip: Use the help (?) and help search (??) functionality in R, if you get stuck.
sd(boys$age) # SD for age[1] 6.894052
sd(boys$bmi, na.rm = TRUE) # SD for bmi[1] 3.053421
Note that bmi contains 21 missing values (you can see these missing values in the summary information). Therefore, we need to use na.rm = TRUE to calculate the standard deviation from the observed cases only.
4 Logical Subsetting
Create a new data frame containing only the boys that are 20 years old or older
How many boys are at least 20 years old?
boys2 <- boys[boys$age >= 20, ]
nrow(boys2)[1] 12
OR
boys2 <- subset(boys, age >= 20)
nrow(boys2)[1] 12
Logical vectors can be very powerful tools in R. For example, in the first solution, we selected the boys that are at least 20 by indexing the rows of the data frame with an appropriately defined logical vector.
Select all boys that are older than 19 but younger than 19.5
How many boys are between the ages of 19 and 19.5?
boys3 <- boys[boys$age > 19 & boys$age < 19.5, ]
nrow(boys3)[1] 18
OR
boys3.2 <- subset(boys, age > 19 & age < 19.5)
nrow(boys3.2)[1] 18
Compute the mean age of boys younger than 15 years old that do not live in region north
mean(boys$age[boys$age < 15 & boys$reg != "north" ], na.rm = TRUE)[1] 6.044461
OR
mean(subset(boys, age < 15 & reg != "north")$age, na.rm = TRUE)[1] 6.044461
In this exercise, you have learned some basic R usage. The approaches we used for this exercise offer tremendous flexibility but may also be inefficient in complex analyses or data manipulation. Doing advanced operations in basic R can require lots of code. In the next exercise, we will start using packages that allow us to do more complicated operations with fewer lines of code.
As you start using R in your own research, you will quickly find yourself in need of packages that are not part of the default R installation. The beauty of R is that its functionality is community-driven. Anyone can add packages to CRAN, and other people can use and improve these packages. There’s a good chance that a function and/or package has been already developed for the analysis or operation you need. If not, maybe you’re interested in filling the gap by submitting your own package?
End of Exercise 2
—
Copyright Hanne Oberman, 2025 - CC BY-NC-SA 4.0
Materials developed by Amices team - Methodology & Statistics - Utrecht University