2  Objects, data structures, and functions

2.1 Learning outcomes

Create and subset vectors, matrices, data frames, and lists; recognize numeric, character, logical, and factor variables; and write a small reusable function.

In this section we will be dealing with basic operations using R. One should be aware of these basics before proceeding to any kind of data analysis using R.

The RStudio interface is simple. You type R code into the bottom line of the RStudio console pane (see figure Figure 1.3) and then click Enter to run it. The code you type is called a command, because it will command your computer to do something for you. The line you type it into is called the command line.

‘#’ hashtag in R
R treats the hashtag character, ’#’in a special way; R will not run anything that follows a hashtag on a line. This makes hashtags very useful for adding comments and annotations to your code. We will be able to read the comments, but your computer will not process it.

2.2 Basic arithmetic operations

# Try these codes in R, Type these codes in console and hit enter
# result of R code is also shown here  

# + (Addition)   
7 + 4  
[1] 11
# - (Subtraction)  
7 - 4  
[1] 3
# * (Multiplication)
7 * 2  
[1] 14
# / (division) 
7 / 2  
[1] 3.5
# ^ (exponentiation) 
7 ^ 2
[1] 49

The colon operator (:) returns every integer between two integers. It is an easy way to create a sequence of numbers.

100:130
 [1] 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
[20] 119 120 121 122 123 124 125 126 127 128 129 130

2.3 Basic arithmetic functions:

# Logarithm to the base e  
log(4)  
[1] 1.386294
# Logarithm to the base 10  
log10(4)  
[1] 0.60206
# Logarithm to the base 2  
log2(4)  
[1] 2
# absolute value
abs(-4)  
[1] 4
# square root
sqrt(4) 
[1] 2

2.4 Assigning values to variables

x <- 2
# it is that value 2 is assigned to variable x  

y <- 5  

# it is that value 5 is assigned to variable y

# Now if you give x+5 or using any operator between x and y, you can see as below  

x + y  
[1] 7
x * y  
[1] 10
x / y  
[1] 0.4
x + 2*y  
[1] 12

Note that R is case sensitive, i.e. x and X are not equal.

2.5 Basic data types

  • numeric
  • character
  • logical
my_age <- 32   
# Numeric variable 32 is assigned to my_age

# while writing your code it is preffered to use '_' instead of space  

my_name <- "Dr Pratheesh" # Character variable  

#  Are you a data scientist?: (yes/no) <=> (TRUE/FALSE)  

is_datascientist <- TRUE # logical variable

2.5.1 Vectors

A combination of multiple values (numeric, character or logical)

How to create a vector

Create a vector: c() for concatenate

Case of missing values: NA (not available) and NaN (not a number)

Get a subset of a vector: my_vector[i] to get the ith element

# Create a numeric vector
student_ages <- c(27, 25, 29, 26, 20, 21, 23, 25)  

# Create a character vector
student_name <- c("asha", "adhi", "aravind", 
                  "mary", "peter", "daisy", 
                  "papu", "ramu")  

# subset of a vector  

# obtain 3 rd element from student_name  

student_name[3]
[1] "aravind"

Case of missing values: NA (not available) and NaN (not a number) Get a subset of a vector: my_vector[i] to get the ith element

Calculations with vectors

max(x), min(x), range(x), length(x), sum(x), mean(x), prod(x): product of the elements in x, sd(x): standard deviation, var(x): variance, sort(x)

# Create a numeric vector
student_ages <- c(27, 25, 29, 26, 20, 21, 23, 25)  

# Maximum value of the vector
max(student_ages)
[1] 29
# Minimum value of the vector
min(student_ages)
[1] 20
# Range of the vector
range(student_ages)  
[1] 20 29
# Length of the vector
length(student_ages)  
[1] 8
# Total of the values in the vector
sum(student_ages)
[1] 196
# Mean of the vector
mean(student_ages)  
[1] 24.5
# Product of the elements in the vector
prod(student_ages)
[1] 122911425000
# Standard deviation  
sd(student_ages)
[1] 3.023716
# Variance of the vector
var(student_ages)
[1] 9.142857
# Sort the values of the vector

# Ascending order
sort(student_ages,decreasing = FALSE)
[1] 20 21 23 25 25 26 27 29
# Descending order
sort(student_ages,decreasing = TRUE)
[1] 29 27 26 25 25 23 21 20

2.5.2 Matrices

It’s a homogeneous collection of data sets which is arranged in a two dimensional organisation. It’s a m*n array with similar data type. It is created using a vector input. It has a fixed number of rows and columns. You can perform many arithmetic operations on R matrix like – addition, subtraction, multiplication, and divisions.

Create and naming matrix: matrix(), cbind(), rbind(), rownames(), colnames() Transpose a matrix: t() Dimensions of a matrix: ncol(), nrow(), dim() Get a subset of a matrix: my_data[row, col] Calculations with numeric matrices: rowSums(), colSums(), rowMeans(), colMeans()

# make three vectors c1, c2 and c3
c1<-c(3,4,5)
c2<-c(7,8,9)
c3<-c(11,12,13)

# Creating matrix by binding column wise  
A <- cbind(c1,c2,c3)
print(A)
     c1 c2 c3
[1,]  3  7 11
[2,]  4  8 12
[3,]  5  9 13
# Creating matrix by binding row wise  
B <- rbind(c1,c2,c3)
print(B)  
   [,1] [,2] [,3]
c1    3    4    5
c2    7    8    9
c3   11   12   13
# Adding two matrices
A+B
     c1 c2 c3
[1,]  6 11 16
[2,] 11 16 21
[3,] 16 21 26
# Simple element by element multiplication up to matrices.

A*B
     c1  c2  c3
[1,]  9  28  55
[2,] 28  64 108
[3,] 55 108 169
# Matrix multiplication  

A%*%B
     [,1] [,2] [,3]
[1,]  179  200  221
[2,]  200  224  248
[3,]  221  248  275
# Using matrix() function
# Elements are arranged sequentially by row.
M <- matrix(c(3:14), nrow = 4, byrow = TRUE)
print(M)
     [,1] [,2] [,3]
[1,]    3    4    5
[2,]    6    7    8
[3,]    9   10   11
[4,]   12   13   14
# Elements are arranged sequentially by column.
N <- matrix(c(3:14), nrow = 4, byrow = FALSE)
print(N)
     [,1] [,2] [,3]
[1,]    3    7   11
[2,]    4    8   12
[3,]    5    9   13
[4,]    6   10   14
# Define the column and row names.
rownames = c("row1", "row2", "row3", "row4")
colnames = c("col1", "col2", "col3")

P <- matrix(c(3:14), nrow = 4, byrow = TRUE, dimnames = list(rownames, colnames))
print(P)  
     col1 col2 col3
row1    3    4    5
row2    6    7    8
row3    9   10   11
row4   12   13   14
# Access the element at 3rd column and 1st row.
P[1,3]
[1] 5
# Access the element at 2nd column and 4th row.
P[4,2]
[1] 13
# Access only the  2nd row.
P[2,]
col1 col2 col3 
   6    7    8 
# Access only the 3rd column.  
P[,3]  
row1 row2 row3 row4 
   5    8   11   14 
# Assign element at 1st row and 3rd column of matrix P to the variable x  

x<-P[1,3]  
print(x)
[1] 5
# create a vector from the second column of matrix P  

y<-P[,2]
y<-as.vector(y)

#Transpose a matrix
Q<-t(P)
print(Q)
     row1 row2 row3 row4
col1    3    6    9   12
col2    4    7   10   13
col3    5    8   11   14
# Knowing dimensions of a matrix

#Number of columns in A
ncol(A)  
[1] 3
#Number of rows in A
nrow(A)
[1] 3
# Number of rows and columns
dim(A)  
[1] 3 3
# Calculations with numeric matrices  

# Compute row sums of matrix A
rowSums(A)  
[1] 21 24 27
# Compute column sums of matrix A
colSums(A)  
c1 c2 c3 
12 24 36 
# Compute row means of matrix A
rowMeans(A)  
[1] 7 8 9
# Compute column means of matrix A
colMeans(A)
c1 c2 c3 
 4  8 12 

2.5.3 Data frames

A data frame is the data type, we will be going to use frequently. A data frame is like a matrix but can have columns with different types (numeric, character, logical). Rows are observations (individuals) and columns are variables. Function data.frame() is used to create data frame

friends_data <- data.frame(
  name = c("asha", "adhi", "aravind", 
                  "mary"), 
  age = c(20,23,22,21),
  height = c(180, 170, 185, 169),
  married = c(TRUE, FALSE, TRUE, TRUE)  
)  

# Print
friends_data
     name age height married
1    asha  20    180    TRUE
2    adhi  23    170   FALSE
3 aravind  22    185    TRUE
4    mary  21    169    TRUE

To check whether a data is a data frame, use the is.data.frame() function. Returns TRUE if the data is a data frame:

is.data.frame(friends_data)  
[1] TRUE

To check whether a data is a matrix, data frame or any other class use the function class()

# What is the class of A? --> matrix
class(A)  
[1] "matrix" "array" 
# What is the class of friends_data? --> data.frame
class(friends_data)
[1] "data.frame"

Convert a data class to data frame use the function as.data.frame()

# Convert it as a data frame
A2 <- as.data.frame(A)
# Now, the class is data.frame
class(A2)
[1] "data.frame"

As described in matrix section, you can use the function t() to transpose a data frame:

t(friends_data)
        [,1]   [,2]    [,3]      [,4]  
name    "asha" "adhi"  "aravind" "mary"
age     "20"   "23"    "22"      "21"  
height  "180"  "170"   "185"     "169" 
married "TRUE" "FALSE" "TRUE"    "TRUE"

2.5.3.1 Operations on data frame

Positive indexing by name and by location

# Access the data in 'name' column in friends_data
# dollar sign is used
friends_data$name  
[1] "asha"    "adhi"    "aravind" "mary"   
# or use this
friends_data[, 'name']  
[1] "asha"    "adhi"    "aravind" "mary"   
# Subset columns 1 and 3
friends_data[ , c(1, 3)]  
     name height
1    asha    180
2    adhi    170
3 aravind    185
4    mary    169
# Subset columns 1 to 3  
friends_data[ , c(1:3)]    
     name age height
1    asha  20    180
2    adhi  23    170
3 aravind  22    185
4    mary  21    169

Negative Indexing

# Exclude column 1  

friends_data[, -1]  
  age height married
1  20    180    TRUE
2  23    170   FALSE
3  22    185    TRUE
4  21    169    TRUE

Index by characteristics

We want to select all friends with age greater than or equal to 22.

# Identify rows that meet the condition
friends_data$age >= 22   
[1] FALSE  TRUE  TRUE FALSE
# TRUE specifies that the row contains a value of age >= 22.
# Select the rows that meet the condition
friends_data[friends_data$age >= 22, ]  
     name age height married
2    adhi  23    170   FALSE
3 aravind  22    185    TRUE
# The R code above, tells R to get all rows from friends_data where age >= 22

If you don’t want to see all the column data for the selected rows but are just interested in displaying, for example, friend names and age for friends with age >= 22, you could use the following R code:

# Use column locations
friends_data[friends_data$age >= 22,  c(1, 2)]  
     name age
2    adhi  23
3 aravind  22
# Or use column names
friends_data[friends_data$age >= 22, c("name", "age")]
     name age
2    adhi  23
3 aravind  22

If you’re finding that your selection statement is starting to be inconvenient, you can put your row and column selections into variables first, such as:

age22 <- friends_data$age >= 22
cols <- c("name", "age")
# Then you can select the rows and columns with those variables:

friends_data[age22, cols]
     name age
2    adhi  23
3 aravind  22

It’s also possible to use the function subset() as follow.

# Select friends data with age >= 22
subset(friends_data, age >= 22)  
     name age height married
2    adhi  23    170   FALSE
3 aravind  22    185    TRUE

Another option is to use the functions attach() and detach(). The function attach() takes a data frame and makes its columns accessible by simply giving their names.

# Attach a data frame
attach(friends_data)
# === Data manipulation ====
friends_data[age>=22, ]
     name age height married
2    adhi  23    170   FALSE
3 aravind  22    185    TRUE
# === End of data manipulation ====
# Detach the data frame
detach(friends_data)

Extend a data frame
Add new column in a data frame

# Add group column to friends_data
data<-c("BSc", "MSc", "Phd", "Btech")
friends_data$degree <- data
friends_data
     name age height married degree
1    asha  20    180    TRUE    BSc
2    adhi  23    170   FALSE    MSc
3 aravind  22    185    TRUE    Phd
4    mary  21    169    TRUE  Btech

It’s also possible to use the functions cbind() and rbind() to extend a data frame.

roll_no<-c(234,235,236,238)
cbind(friends_data, roll_no)
     name age height married degree roll_no
1    asha  20    180    TRUE    BSc     234
2    adhi  23    170   FALSE    MSc     235
3 aravind  22    185    TRUE    Phd     236
4    mary  21    169    TRUE  Btech     238

Calculations with data frame

With numeric data frame, you can use the function rowSums(), colSums(), colMeans(), rowMeans() and apply().

# Following can be used when it is a numeric data frame

# creating a numeric data frame
c1<-c(3,4,5)
c2<-c(7,8,9)
c3<-c(11,12,13)

# Creating matrix by binding column wise  
ex <- cbind(c1,c2,c3)

# converting matrix to data frame
ex<-as.data.frame(ex)
ex
  c1 c2 c3
1  3  7 11
2  4  8 12
3  5  9 13
rowSums(ex)
[1] 21 24 27
colSums(ex)
c1 c2 c3 
12 24 36 
# calculation in non numeric data frame on selected numeric columns
attach(friends_data)
sum(age)
[1] 86
mean(age)
[1] 21.5

apply() function

apply(X, MARGIN, FUN)
Here:
  -x: an array or matrix
-MARGIN:  take a value or range between 1 and 2 to define where to apply the function:
  -MARGIN=1`: the manipulation is performed on rows
-MARGIN=2`: the manipulation is performed on columns
-MARGIN=c(1,2)` the manipulation is performed on rows and columns
-FUN: tells which function to apply. Built functions like mean, median, sum, min, max and even user-defined functions can be applied>
apply(ex,2, mean)
c1 c2 c3 
 4  8 12 
apply(ex,1, mean)
[1] 7 8 9
# Close the final attach() demonstration before continuing.
detach(friends_data)

The attach() examples explain commands encountered in existing scripts. In new analyses, prefer friends_data$age, with(friends_data, mean(age)), or a function with a data = argument. These make the variable source explicit and avoid masking another object with the same name.

2.6 Agricultural applications, interpretation, and further practice

2.6.1 Values and vectors

An atomic vector holds values of one underlying type. Combining numbers and text causes coercion, usually to character. Numeric values support arithmetic; character values describe names; logical values represent TRUE or FALSE. A factor records categories with a specified set of levels.

yield <- c(4.2, 4.8, 5.1, 4.5)
variety <- c("A", "A", "B", "B")
irrigated <- c(TRUE, TRUE, FALSE, FALSE)
class(yield)
[1] "numeric"
yield[2]
[1] 4.8
yield[c(1, 3)]
[1] 4.2 5.1
yield[yield > 4.5]
[1] 4.8 5.1
yield * 1000                    # t/ha to kg/ha
[1] 4200 4800 5100 4500
factor(variety, levels = c("A", "B"))
[1] A A B B
Levels: A B

R indexes from 1. Square brackets select elements. > compares values; == tests equality; != tests inequality; and %in% tests membership. Use & and | for elementwise “and” and “or”. Arithmetic is vectorized. R can recycle a shorter vector, so check lengths when combining measurements; recycling does not establish which observations belong together.

plot_id <- 1:4
plot_id[plot_id %in% c(2, 4)]
[1] 2 4
yield[yield >= 4.5 & variety == "B"]
[1] 5.1 4.5
length(yield)
[1] 4

2.6.2 Matrices

A matrix is a rectangular atomic vector with dimensions. It is useful for numeric arrays such as soil measurements at grid positions. A data frame is usually better for a table containing measurements and treatment labels.

soil_grid <- matrix(21:32, nrow = 4, byrow = TRUE)
colnames(soil_grid) <- c("west", "centre", "east")
soil_grid
     west centre east
[1,]   21     22   23
[2,]   24     25   26
[3,]   27     28   29
[4,]   30     31   32
soil_grid[2, 3]
east 
  26 
soil_grid[, "west"]
[1] 21 24 27 30
rowMeans(soil_grid)
[1] 22 25 28 31
colMeans(soil_grid)
  west centre   east 
  25.5   26.5   27.5 
t(soil_grid)
       [,1] [,2] [,3] [,4]
west     21   24   27   30
centre   22   25   28   31
east     23   26   29   32

x * y multiplies corresponding elements, whereas x %*% y performs matrix multiplication and requires conformable dimensions. Transposing a mixed data frame can coerce its contents to character; select numeric columns before numerical matrix operations.

2.6.3 Data frames and experimental units

A data frame can hold a different type in each column. For a simple field trial, each row should represent one harvested experimental plot, and each column one variable. Treatment labels are ordinary columns and may repeat. Plot identifiers should uniquely identify experimental units; do not use treatment names as unique row names.

field <- data.frame(plot_id, variety = factor(variety), yield_t_ha = yield,
                    irrigated = irrigated)
str(field)
'data.frame':   4 obs. of  4 variables:
 $ plot_id   : int  1 2 3 4
 $ variety   : Factor w/ 2 levels "A","B": 1 1 2 2
 $ yield_t_ha: num  4.2 4.8 5.1 4.5
 $ irrigated : logi  TRUE TRUE FALSE FALSE
field$yield_t_ha
[1] 4.2 4.8 5.1 4.5
field[field$yield_t_ha >= 4.5, c("plot_id", "yield_t_ha")]
  plot_id yield_t_ha
2       2        4.8
3       3        5.1
4       4        4.5
field$yield_kg_ha <- field$yield_t_ha * 1000
summary(field)
    plot_id     variety   yield_t_ha    irrigated        yield_kg_ha  
 Min.   :1.00   A:2     Min.   :4.200   Mode :logical   Min.   :4200  
 1st Qu.:1.75   B:2     1st Qu.:4.425   FALSE:2         1st Qu.:4425  
 Median :2.50           Median :4.650   TRUE :2         Median :4650  
 Mean   :2.50           Mean   :4.650                   Mean   :4650  
 3rd Qu.:3.25           3rd Qu.:4.875                   3rd Qu.:4875  
 Max.   :4.00           Max.   :5.100                   Max.   :5100  

field[rows, columns] separates row and column selection by a comma. A blank position means all rows or all columns. Prefer explicit names and data = arguments over attach(), which can obscure where a variable comes from.

2.6.4 Lists, missing values, and functions

A list can contain objects of different structures. Statistical model results are often lists. $ or [[ ]] extracts a component, while [ ] keeps a sublist.

trial <- list(site = "Teaching farm", observations = field,
              units = "t/ha")
trial$site
[1] "Teaching farm"
trial[["observations"]]
  plot_id variety yield_t_ha irrigated yield_kg_ha
1       1       A        4.2      TRUE        4200
2       2       A        4.8      TRUE        4800
3       3       B        5.1     FALSE        5100
4       4       B        4.5     FALSE        4500
x <- c(4.2, NA, 5.1)
is.na(x)
[1] FALSE  TRUE FALSE
mean(x)
[1] NA
mean(x, na.rm = TRUE)
[1] 4.65

NA records an unknown value. It is neither zero nor the character string "NA". Use is.na(x), not x == NA. NaN is an undefined numeric result and Inf represents infinity. Removing missing values from a calculation changes its denominator and may change the population represented by the result.

A function names a repeatable calculation. Arguments supply inputs and the final expression supplies the return value.

to_t_ha <- function(mass_kg, area_m2) {
  stopifnot(is.numeric(mass_kg), all(is.finite(mass_kg)),
            all(mass_kg >= 0), is.numeric(area_m2),
            length(area_m2) == 1, is.finite(area_m2), area_m2 > 0)
  mass_kg / area_m2 * 10
}
to_t_ha(c(9.6, 11.2), 20)
[1] 4.8 5.6

The checks stop the calculation for invalid inputs. Later, the pipe |> passes a result into the first argument of the next function: sqrt(16) and 16 |> sqrt() are equivalent. A pipe expresses a sequence, not a new statistical method.

2.6.5 Practical 2 and exercises

  1. Create a vector from 1 to 30. Select values below 8, then values below 8 or above 12, and finally 2, 6, and 12. This extends the earlier self-assessment exercises.
  2. Create a four-row, five-column matrix from 1 to 20. Calculate its row means and select row 2, column 3.
  3. Construct a six-plot data frame with a unique identifier, variety, and yield. Select yields above the overall mean.
  4. Store the data frame and a description in a list. Explain why an atomic vector is unsuitable for this task.
  5. Predict the type of c(1, "2"); verify it. Why could this matter when importing data?

Chapter review. Choose structures according to the information they represent. An identifier, a category, and a measurement may all look like numbers but play different analytical roles.

2.7 Original self-assessment exercises

The following exercises retain the tasks from the author’s original self-assessment document. Attempt them before looking at a solution. For each task, save the command, its output, and a short explanation. The later chapters return to the import and statistical-summary tasks with more detail.

  1. Calculate the sum of two numbers. Create variables num1 and num2, assign them two numbers, and then calculate their sum.
  2. Convert temperature from Celsius to Fahrenheit. Create temp_celsius, assign a temperature in Celsius, and calculate Fahrenheit using (temp_celsius * 9/5) + 32. State the units of the input and output.
  3. Calculate the area of a rectangle. Create variables for length and width, assign the dimensions, and calculate their product. If dimensions are in metres, state the unit of the area. Prefer plot_length and plot_width to names that shadow common functions.
  4. Calculate the factorial of a number. Assign a nonnegative integer to num and calculate its factorial using a built-in function. Explain the meaning of the answer for a small integer.
  5. Determine whether a number is even or odd. Assign an integer to num, use the remainder operator %%, and print an appropriate result. What does num %% 2 == 0 test?
  6. Calculate a square root. Assign a nonnegative number to num and calculate its square root using a built-in function. Explain why a negative input requires a different numerical interpretation.
  7. Generate a sequence of numbers. Create a sequence from 1 to 10 using a built-in function; compare it with 1:10.
  8. Create a vector and calculate its length. Use elements of your choice, inspect the vector’s class, and calculate its length.
  9. Filter elements in a vector. Create a numeric vector and remove elements greater than a chosen threshold using logical indexing. State whether your condition selects values to keep or values to discard.
  10. Create a CSV file of the iris dataset. Load iris, export it with row.names = FALSE, and identify where the file was saved.
  11. Perform basic statistical calculations on iris. Import the CSV, calculate the mean, median, and standard deviation for each numeric measurement column, and summarize species as a category. Explain why it is inappropriate to average the species names.

For a further agricultural application, write a function that converts a vector of harvested grain masses to t/ha using a common harvested area. Check that the area is positive and demonstrate the function on at least three observations. This connects the original programming exercises with the yield calculation in the opening chapter.

2.8 Live R laboratory

Build a field observation table

Edit the code, then select Run R. The first run downloads R into your browser and may take a little time. Copy code also lets you use this example in RStudio.

field <- data.frame(
  plot = 1:6,
  variety = factor(rep(c("A", "B"), each = 3)),
  yield_t_ha = c(3.1, 3.4, 3.2, 3.8, 4.0, 3.9)
)
str(field)
aggregate(yield_t_ha ~ variety, field, mean)

Investigate. Identify the class of each column. Change one yield and explain why only one variety mean changes. These are simulated observations.

From the browser to your practical notebook

Record the input you changed, the output you obtained, and your interpretation. These live examples use base R and work independently. The full chapter examples, including package-based analyses, remain available with copy buttons for RStudio. Browser objects last until this page is reloaded; Reset code restores the editor text. Internet access is needed on first use; long or interactive desktop tasks belong in RStudio.