Exercise 2

In this practical, you will play around with the different types of R objects.


1 Creating Data Objects


Create two vectors

  1. One named vec1 containing the integers 1 through 6
  2. One named vec2 with letters A through F
vec1 <- c(1, 2, 3, 4, 5, 6)
vec2 <- c("A", "B", "C", "D", "E", "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

  1. One named mat1 from vec1
  2. One named mat2 from vec2

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

 1
  1. Use vec1 to populate the first column
  2. Use vec2 to 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

 2
  1. Use function data.frame().
  2. Set vec1 and vec2 as the columns.
  3. Name the columns v1 and v2, 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

 3
  1. Use the as.data.frame() function with the matrix created in  1 as input.
  2. Set the argument stringsAsFactors = TRUE.
  3. Name the columns v1 and v2, 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

  1. The third row
  2. The second column
  3. 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.

  1. 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”.
load("data/boys.RData")
  1. You can double-click the boys.RData file 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”.
  2. 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

  1. By executing boys in the console
  2. 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