CHAPTER ONE
History and Overview of R
1.1 Introduction:
R is a programming language and software environment designed primarily for statistical computing, data analysis, data visualization and related data-driven tasks. It is widely used by statisticians, data scientists, researchers, analysts, academics and professionals who need to manipulate, analyze and communicate information from data.
R is more than a conventional programming language. It combines programming capabilities with a large collection of statistical and graphical tools. A user can import a dataset, clean and transform it, perform statistical calculations, build models, create visualizations and communicate the results within the same computing environment.
For students of Data Science, learning R provides an opportunity to understand programming through practical work with real data. Instead of learning programming only through abstract examples, R allows students to immediately apply programming concepts to tables, vectors, datasets, statistical calculations and visualizations.
1.2 What Is R?:
R is an open-source programming language and software environment developed for statistical computing, data analysis and graphics.
It can be used to perform operations ranging from simple arithmetic to advanced statistical analysis and predictive modelling.
For example, R can calculate the mean of a set of values:
mean(c(10, 20, 30, 40, 50))
The result is:
The example illustrates one of the strengths of R: a statistical operation can be expressed using a relatively simple command.
R as a Programming Language:
R contains the fundamental features expected of a programming language. These include variables, operators, functions, conditional statements, loops, data structures and mechanisms for organizing reusable code.
For example:
x <- 10
y <- 5
result <- x + y
result
The symbol <- is commonly used in R for assigning a value to an object. The value 10 is assigned to x, while 5 is assigned to y. R then adds the two values and stores the result in result.
R as a Statistical Computing Environment:
R provides built-in functions for common statistical operations such as calculating means, medians, standard deviations, variances, correlations and quantiles. It also supports more advanced statistical techniques through its extensive package ecosystem.
For example:
scores <- c(65, 72, 81, 90, 76)
mean(scores)
median(scores)
sd(scores)
This illustrates how the same collection of values can be examined using several statistical measures.
1.3 History of R:
The development of R is closely connected with the history of statistical computing and the earlier programming language S.
The S Language:
In the 1970s, researchers at Bell Laboratories developed the S programming language as an environment for data analysis and statistical computing. S was designed to allow statisticians and researchers to work interactively with data rather than relying entirely on specialized, rigid statistical programs.
The ideas introduced through S had a major influence on later statistical programming languages, including R.
Development of R:
R was created by Ross Ihaka and Robert Gentleman at the University of Auckland in New Zealand. The language was influenced strongly by S and was designed to provide a flexible environment for statistical computing and graphics.
The name R is associated with the first names of its original creators, Ross and Robert, and also reflects its relationship with the S language.
R was released as free software under an open-source license. Its open development model allowed programmers, statisticians and researchers around the world to contribute packages, improvements and documentation.
Growth of the R Community:
R grew from a relatively specialized statistical programming environment into a major tool for data science. Its development has been supported by a large international community that contributes packages for statistics, machine learning, visualization, data manipulation, scientific computing and many other applications.
One important development was the creation of CRAN, the Comprehensive R Archive Network. CRAN provides a large collection of R packages and resources that extend the capabilities of the language.
1.4 Major Characteristics of R:
R possesses several characteristics that make it useful for data science and statistical computing.
| Characteristic | Explanation |
|---|---|
| Open source | R is freely available and its source code can be examined and developed by the community. |
| Statistical capability | It provides extensive tools for statistical analysis and modelling. |
| Data manipulation | R can organize, filter, transform and summarize datasets. |
| Visualization | It provides powerful facilities for producing graphs and other visual representations. |
| Extensibility | Packages can extend R with additional functions and capabilities. |
| Interactive computing | Users can enter commands and immediately inspect their results. |
| Cross-platform support | R is available for major operating systems, including Windows, macOS and Linux. |
1.5 R in Data Science:
Data Science involves obtaining useful knowledge from data. R can support several stages of this process.
- Data acquisition: R can read data from files, databases and other sources.
- Data preparation: Data can be cleaned, transformed and reorganized.
- Exploratory analysis: R can calculate summaries and reveal patterns in datasets.
- Visualization: Data can be represented using charts, graphs and plots.
- Statistical modelling: R supports regression, hypothesis testing and many other statistical methods.
- Machine learning: Numerous R packages provide tools for classification, prediction and other machine-learning tasks.
- Communication: Results can be presented through reports, visualizations and reproducible documents.
Thus, R can serve as a complete working environment for many data-science activities rather than being limited to a single type of calculation.
1.6 Applications of R:
Statistical Analysis:
R is extensively used to calculate descriptive statistics, perform hypothesis tests, estimate relationships between variables and construct statistical models.
Data Visualization:
R can produce a wide range of visualizations, including bar charts, histograms, scatter plots, box plots and line graphs.
Machine Learning:
R can be used to develop and evaluate models for classification, regression, clustering and other machine-learning tasks.
Scientific Research:
Researchers use R to analyze experimental data, conduct statistical tests, produce figures and document analytical procedures.
Business and Economics:
R can be used for forecasting, customer analysis, financial modelling, market research and business intelligence.
Healthcare and Biostatistics:
R is used in areas such as clinical research, epidemiology, public-health analysis and biological data analysis.
Academic Research:
Universities use R for teaching statistics, research methods, data science and quantitative analysis across many disciplines.
1.7 Basic R Concepts:
Objects:
R stores values and data in objects. An object may contain a single value, a collection of values, a table or a more complex data structure.
age <- 21
name <- "Emmanuel"
passed <- TRUE
Here, age, name and passed are objects containing different types of values.
Functions:
A function is a reusable operation that receives inputs, performs a task and may return a result.
sqrt(25)
The function sqrt() calculates the square root. The result is 5.
Vectors:
A vector is one of the fundamental data structures in R. It can contain multiple values of the same basic type.
marks <- c(65, 72, 81, 90, 76)
The function c() combines the values into a vector.
Operators:
R supports arithmetic, comparison and logical operators.
| Operator | Meaning | Example |
|---|---|---|
| + | Addition | 10 + 5 |
| - | Subtraction | 10 - 5 |
| * | Multiplication | 10 * 5 |
| / | Division | 10 / 5 |
| ^ | Exponentiation | 2 ^ 3 |
| == | Equal to | 10 == 10 |
| > | Greater than | 10 > 5 |
| < | Less than | 5 < 10 |
1.8 Why Learn R?:
R is particularly appropriate for students entering Data Science because it brings together programming, statistics, data manipulation and visualization.
A student who learns R develops more than the ability to memorize commands. The student learns how computational instructions can be used to transform raw data into meaningful results.
For example, a dataset containing examination scores can be imported, cleaned, summarized, visualized and analyzed using R. The same workflow can later be applied to scientific, financial, business, healthcare or social datasets.
A Simple R Workflow:
Data → R code → Analysis → Visualization → Interpretation → Decision
1.10 Key Points:
- R is an open-source programming language and environment for statistical computing, data analysis and graphics.
- R was developed by Ross Ihaka and Robert Gentleman.
- R was strongly influenced by the S programming language.
- R is widely used in Data Science, statistics, research and analytics.
- R supports data manipulation, statistical analysis, visualization and modelling.
- Objects are used to store values and data in R.
- Functions perform reusable operations.
- Vectors are fundamental R data structures.
- R's open-source ecosystem allows additional functionality through packages.
1.11 Chapter Summary:
R is a powerful open-source programming language and computing environment developed primarily for statistical computing, data analysis and graphics. Its historical roots can be traced to the S language developed at Bell Laboratories, while R itself was created by Ross Ihaka and Robert Gentleman at the University of Auckland.
R has become an important tool in modern Data Science because it combines programming with statistical analysis, data manipulation and visualization. It is used across research, business, healthcare, finance, education, science and many other fields.
Understanding the nature and history of R provides the foundation for learning how to use the language effectively. In subsequent chapters, students will progress from installing R and understanding the R environment to writing programs, manipulating data and performing data-driven analysis.
CHAPTER TWO
Installation and RStudio Environment
2.1 Introduction:
Before writing substantial programs in R, a student must understand how to obtain the software, install it correctly and work comfortably within the programming environment. This chapter introduces the R software environment and RStudio, a widely used integrated development environment for R.
The distinction between R and RStudio is important. R provides the underlying language and computational engine, while RStudio provides an organized interface that makes it easier to write, execute, manage and document R programs.
2.2 Installing R:
R can be installed on major desktop operating systems, including Windows, macOS and Linux. The appropriate installation procedure depends on the operating system.
Installing R on Windows:
- Open a web browser and visit the official R Project website.
- Navigate to the download section known as CRAN.
- Select a CRAN mirror.
- Select the Windows operating system.
- Choose the base R installation package.
- Download the current installer.
- Run the installer and follow the installation instructions.
- Accept the appropriate default settings unless a specific configuration is required.
- Complete the installation.
After installation, R can be launched directly from the operating system's applications menu.
Installing R on macOS:
macOS users should download the appropriate R installer from CRAN and install the package designed for their version and processor architecture.
Installing R on Linux:
Linux installation procedures vary according to the distribution. Many distributions provide R through their package-management systems, although the official R Project documentation should be consulted for the recommended installation method.
2.3 What Is RStudio?:
RStudio is an integrated development environment (IDE) designed to make programming and data analysis with R easier and more organized.
An IDE brings together tools that a programmer needs to write and execute code, inspect objects, manage files, view plots and work with packages.
RStudio is especially useful for beginners because it provides a graphical environment around the R programming language.
R and RStudio Compared:
| R | RStudio |
|---|---|
| Programming language and computing environment. | Integrated development environment for working with R. |
| Performs computations and executes R code. | Provides tools for writing and managing R code. |
| Can be used without RStudio. | Requires R to perform R computations. |
2.4 The RStudio Interface:
When RStudio is opened, the interface is commonly divided into several panes. The exact arrangement may vary according to the version and user's settings.
The Console:
The Console is the area where R commands can be entered and executed interactively.
> 5 + 3
[1] 8
The symbol > indicates that R is ready to receive a command. The result [1] 8 indicates that the returned value is 8.
The Source or Script Editor:
The Source pane is used to write and save R programs in script files. Script files normally use the .R file extension.
Writing code in a script is generally preferable to entering a long sequence of commands directly into the Console because the script can be saved, modified, reviewed and executed again.
The Environment Pane:
The Environment pane displays objects that have been created in the current R session. For example, after executing:
age <- 21
scores <- c(60, 75, 82, 91)
the objects age and scores may appear in the Environment pane.
The Files Pane:
The Files pane allows users to navigate through directories and manage files within the working environment.
The Plots Pane:
The Plots pane displays graphical output generated by R.
For example:
marks <- c(55, 63, 71, 80, 91)
plot(marks)
The command produces a simple graphical representation of the values.
The Packages Pane:
The Packages pane displays installed R packages and allows users to load or manage packages.
The Help Pane:
The Help pane provides access to documentation for R functions and packages.
2.5 The R Console:
The Console provides an interactive environment in which R commands are executed immediately.
Consider the following commands:
2 + 3
10 * 4
100 / 5
2 ^ 4
The corresponding results are:
2 + 3 = 5
10 × 4 = 40
100 ÷ 5 = 20
24 = 16
The Console is useful for testing individual commands and exploring R interactively. However, important programs should normally be saved in scripts so that they can be reproduced.
2.6 R Objects and Assignment:
R uses objects to store data and other values. Assignment associates a value with an object name.
The commonly used assignment operator is <-.
x <- 25
This statement stores the value 25 in the object named x.
The object can then be used in another expression:
x <- 25
y <- 10
x + y
The result is 35.
Assignment can also be written using = in many contexts, but <- is conventional and widely used in R code.
2.7 Basic Data Types in R:
R supports several fundamental types of data.
| Type | Meaning | Example |
|---|---|---|
| Numeric | Numbers used for quantitative calculations. | 25.5 |
| Integer | Whole-number values explicitly represented as integers. | 25L |
| Character | Text values. | "Data Science" |
| Logical | Logical values representing TRUE or FALSE. | TRUE |
The function class() can be used to inspect the class of an object.
x <- 25
class(x)
2.8 Vectors:
A vector is a fundamental structure in R that stores a sequence of values. The c() function is commonly used to create vectors.
scores <- c(60, 72, 85, 91, 78)
The vector can be examined using:
scores
Individual elements can be accessed using their positions. R uses one-based indexing, meaning the first element has position 1.
scores[1]
The result is 60.
Similarly:
scores[3]
returns 85.
2.9 Arithmetic Operations in R:
R can be used as a calculator for arithmetic operations.
| Operator | Operation | Example | Result |
|---|---|---|---|
| + | Addition | 8 + 2 | 10 |
| - | Subtraction | 8 - 2 | 6 |
| * | Multiplication | 8 * 2 | 16 |
| / | Division | 8 / 2 | 4 |
| ^ | Power | 2 ^ 3 | 8 |
| %% | Remainder | 10 %% 3 | 1 |
| %/% | Integer division | 10 %/% 3 | 3 |
Understanding these operators is important because more advanced R programs are built from basic operations combined into larger expressions.
2.10 Functions in R:
A function is a reusable block of functionality that performs a particular operation. R contains many built-in functions, and programmers can also create their own.
For example:
numbers <- c(10, 20, 30, 40, 50)
mean(numbers)
The mean() function calculates the arithmetic mean.
For the values above:
Mean = (10 + 20 + 30 + 40 + 50) ÷ 5 = 30
Other useful functions include:
sum(numbers)
min(numbers)
max(numbers)
length(numbers)
sort(numbers)
2.11 Getting Help in R:
Learning R does not require memorizing every function. R includes a documentation system that allows programmers to look up information about functions and packages.
The following command opens documentation for a function:
?mean
Another form is:
help(mean)
These commands provide information about the function, its arguments, usage and examples.
2.12 Installing and Loading Packages:
One of R's major strengths is its package system. A package is a collection of R code, data and documentation that extends the capabilities of the language.
A package generally needs to be installed once on a computer before it can be used.
The general installation command is:
install.packages("package_name")
After installation, a package is commonly loaded into the current R session using:
library(package_name)
For example, the widely used ggplot2 package can be installed and loaded with:
install.packages("ggplot2")
library(ggplot2)
Installation and loading are different operations. Installation makes the package available on the computer; loading makes its functions available for use in the current session.
2.13 Comments in R:
Comments are explanatory text placed in a program for human readers. R uses the hash symbol # to begin a comment.
# Calculate the average examination score
scores <- c(60, 70, 80, 90)
mean(scores)
The comment is ignored by R when the program executes. Good comments can make a program easier to understand and maintain.
2.14 Basic R Workflow:
A beginner can approach an R task using the following workflow:
- Identify the problem.
- Determine what data are required.
- Open R or RStudio.
- Create or import the data.
- Store data in appropriate objects.
- Apply suitable functions or operations.
- Inspect the result.
- Visualize or further analyze the result where necessary.
- Save the script and relevant outputs.
Worked Example: Calculating an Average:
Question: Five students obtained scores of 65, 72, 80, 84 and 89. Use R to calculate their average score.
Solution:
scores <- c(65, 72, 80, 84, 89)
mean(scores)
The mathematical calculation is:
Average = (65 + 72 + 80 + 84 + 89) ÷ 5 = 78
Therefore, the average score is 78.
2.16 Key Points:
- R must be installed before RStudio can be used as an R development environment.
- RStudio is an IDE designed to make working with R easier.
- The Console executes R commands interactively.
- The Source pane is used to create and save R scripts.
- The Environment pane displays objects created during an R session.
- The Plots pane displays graphical output.
- R stores information in objects.
- The
<-operator is commonly used for assignment. - Vectors are fundamental R data structures.
- R uses one-based indexing.
- Functions perform specific reusable operations.
- Packages extend the capabilities of R.
install.packages()installs a package, whilelibrary()loads a package.- R is case-sensitive.
- Scripts should be saved so that analytical work can be reproduced.
2.17 Chapter Summary:
Installing R and becoming familiar with the RStudio environment are essential first steps in learning R programming. R provides the computational engine and programming language, while RStudio provides an integrated environment for writing, executing and organizing R code.
The RStudio interface contains several useful components, including the Console, Source editor, Environment, Files, Plots, Packages and Help panes. Understanding the purpose of each component enables students to work efficiently within the environment.
R programs are constructed using objects, functions, operators and data structures. Students should become comfortable with assignment, vectors, arithmetic operations, basic data types and function calls before progressing to more advanced programming concepts.
The package system is another important feature of R. It allows users to extend the language with specialized functionality for visualization, statistics, data manipulation, machine learning and many other applications.
With R and RStudio correctly installed and the basic environment understood, the student is prepared to begin writing increasingly sophisticated R programs and performing practical data-analysis tasks.
CHAPTER THREE
R Evaluation and Objects
3.1 Evaluation in R
Evaluation is the process through which R interprets an expression and determines its result.
For example:
4 + 6
R evaluates the expression and produces:
[1] 10
Assignment also involves evaluation:
x <- 20
R evaluates the value 20 and assigns it to the object named x.
When the expression x + 5 is evaluated, R retrieves the value associated with x and performs the addition.
3.2 R Objects
An object is an entity that can store data or represent a computational structure in R.
age <- 21 name <- "Ada" passed <- TRUE
Here, age, name and passed are objects containing different kinds of values.
Objects can be inspected using functions such as:
class(age) typeof(age) str(age)
3.3 Numbers in R
R supports numeric computation using values such as integers and decimal numbers.
x <- 25 y <- 3.5 x + y x * y x / y x^2
R normally represents ordinary numeric values using double-precision floating-point representation. Integers can also be explicitly represented using the L suffix.
x <- 10L
3.4 Attributes
An attribute is additional information attached to an R object that describes some aspect of its structure or interpretation.
Attributes can be inspected using:
attributes(x)
Examples of attributes include names, dim and class. Attributes are especially important for structured objects such as matrices and data frames.
3.5 Names
Elements of an object can sometimes be given names, allowing them to be identified more clearly.
scores <- c(John=72, Mary=85, Peter=68)
The values can then be accessed using their names:
scores["Mary"]
Named data are often easier to interpret than collections whose elements can only be identified by numerical position.
3.6 Basic Operations on Objects
Objects can be combined in expressions.
a <- 12 b <- 4 a + b a - b a * b a / b
R also allows comparisons:
a > b a == b a != b
These expressions return logical values such as TRUE or FALSE.
Key Points:
- R evaluates expressions to produce results.
- Objects are fundamental units of data and computation in R.
- Objects can contain numbers, text, logical values and more complex structures.
- Attributes provide additional structural information about objects.
- Names can make data easier to identify and manipulate.
CHAPTER FOUR
Vectors and Data Types
4.1 What Is a Vector?
A vector is a basic R data structure containing a sequence of values. In ordinary R usage, a vector is generally homogeneous, meaning its elements have a common underlying atomic type.
A numeric vector can be created with:
numbers <- c(10, 20, 30, 40)
The c() function means combine or concatenate. It combines its arguments into a vector.
4.2 Common R Data Types
| Type | Example | Description |
|---|---|---|
| Numeric | 3.14 | Numeric values |
| Integer | 5L | Integer values |
| Character | "R" | Text values |
| Logical | TRUE | Logical values |
| Complex | 2+3i | Complex numbers |
4.3 Creating Vectors
Numeric vector:
x <- c(2, 4, 6, 8)
Character vector:
names <- c("Ada", "Grace", "Alan")
Logical vector:
passed <- c(TRUE, FALSE, TRUE, TRUE)
Sequences can also be created conveniently:
1:10 seq(0, 1, by=0.2)
4.4 Indexing Vectors
R uses one-based indexing. This means the first element has position 1.
x <- c(10, 20, 30, 40) x[1] x[3]
The results are:
10 30
Multiple elements can be selected:
x[c(1,3,4)]
Elements can also be excluded:
x[-2]
This returns all elements except the second.
4.5 Vector Arithmetic
R can perform arithmetic on entire vectors.
a <- c(2, 4, 6) b <- c(1, 2, 3) a + b a - b a * b a / b
The operations are performed element by element.
For example:
a + b = (2+1, 4+2, 6+3) = (3, 6, 9)
4.6 Vectorised Operations
Vectorisation refers to applying an operation to an entire vector without explicitly writing a loop for each element.
For example:
x <- c(4, 9, 16, 25) sqrt(x)
R returns the square root of every element.
[1] 2 3 4 5
Similarly:
x * 10
multiplies every element of x by 10.
4.7 Recycling
R can perform operations between vectors of unequal lengths through a process called recycling. The shorter vector is reused as necessary.
c(10, 20, 30, 40) + c(1, 2)
The shorter vector is effectively repeated:
(10,20,30,40) + (1,2,1,2) = (11,22,31,42)
When the longer vector length is not an exact multiple of the shorter vector length, R may issue a warning. Students should therefore use recycling deliberately rather than accidentally.
4.8 Logical Operations on Vectors
Logical comparisons can be applied element by element.
scores <- c(45, 62, 78, 39, 91) scores >= 50
The result identifies which scores satisfy the condition.
FALSE TRUE TRUE FALSE TRUE
This is particularly important in data analysis because logical vectors can be used to select observations.
Key Points:
- Vectors are fundamental R data structures.
- R uses one-based indexing.
- The
c()function creates vectors by combining values. - Vectorised operations apply calculations to multiple elements efficiently.
- R can recycle shorter vectors in operations involving vectors of different lengths.
- Logical vectors are useful for selecting data.
CHAPTER FIVE
Matrices, Arrays and Lists
5.1 Matrices
A matrix is a two-dimensional data structure arranged into rows and columns. In R, a matrix is essentially a vector with a dim attribute specifying its dimensions.
A matrix can be created using the matrix() function:
A <- matrix(c(1, 2, 3, 4, 5, 6), nrow=2, ncol=3) A
By default, R fills a matrix column by column:
[,1] [,2] [,3] [1,] 1 3 5 [2,] 2 4 6
To fill the matrix by rows:
A <- matrix(c(1,2,3,4,5,6), nrow=2, byrow=TRUE)
This produces:
[,1] [,2] [,3] [1,] 1 2 3 [2,] 4 5 6
5.2 Matrix Indexing
Matrix elements are accessed using two indices:
A[row, column]
For example:
A[1,2]
returns the value in the first row and second column.
An entire row can be selected using:
A[1,]
An entire column can be selected using:
A[,2]
5.3 Matrix Arithmetic
Ordinary arithmetic operators can be applied element by element to matrices of compatible dimensions.
A <- matrix(c(1,2,3,4), nrow=2) B <- matrix(c(5,6,7,8), nrow=2) A + B A - B A * B
Notice that * performs element-wise multiplication. It is not ordinary matrix multiplication.
Matrix multiplication uses %*%:
A %*% B
This distinction is extremely important in mathematical and statistical programming.
5.4 Other Matrix Operations
R provides functions for common matrix operations.
| Operation | R Expression |
|---|---|
| Dimensions | dim(A) |
| Number of rows | nrow(A) |
| Number of columns | ncol(A) |
| Transpose | t(A) |
| Row sums | rowSums(A) |
| Column sums | colSums(A) |
5.5 Arrays
An array is a multidimensional data structure. While a matrix has two dimensions, an array can have two, three or more dimensions.
For example, a three-dimensional array can represent values according to rows, columns and another dimension such as time.
A <- array(1:12, dim=c(2,3,2))
The dimensions indicate:
- 2 rows;
- 3 columns; and
- 2 layers.
The total number of elements is:
2 × 3 × 2 = 12
Arrays are useful when data naturally have several dimensions, such as measurements recorded across multiple locations, variables and time periods.
5.6 Array Indexing
Elements of an array are accessed using one index for each dimension.
A[1,2,1]
This identifies an element in:
- row 1;
- column 2; and
- layer 1.
The number of indices must correspond to the dimensional structure being accessed.
5.7 Lists
A list is a flexible R data structure that can contain elements of different types and structures.
For example:
student <- list( name = "Ada", age = 21, scores = c(78, 84, 91), passed = TRUE )
This single list contains:
- a character value;
- a numeric value;
- a numeric vector; and
- a logical value.
This flexibility makes lists particularly important for representing complex objects.
5.8 Accessing List Elements
List elements can be accessed using double square brackets or the dollar operator.
student[[1]] student$name student$scores
The expression student$name accesses the element named name.
Single brackets behave differently:
student["name"]
This returns a sublist containing the named element, whereas student[["name"]] returns the element itself.
5.9 Comparing Matrices, Arrays and Lists
| Structure | Dimensions | Typical Contents | Typical Use |
|---|---|---|---|
| Vector | One-dimensional | Generally one atomic type | Sequences of values |
| Matrix | Two-dimensional | Generally one atomic type | Rows and columns |
| Array | Two or more dimensions | Generally one atomic type | Multidimensional data |
| List | Flexible | Can contain different types | Complex and heterogeneous data |
5.10 Worked Example: Matrix Analysis
Question: Create a 2 × 3 matrix containing the values 10, 20, 30, 40, 50 and 60, calculate the sum of each column, and identify the value in row 2, column 3.
Solution:
A <- matrix(c(10,20,30,40,50,60), nrow=2, byrow=TRUE) A colSums(A) A[2,3]
The matrix is:
[,1] [,2] [,3] [1,] 10 20 30 [2,] 40 50 60
The column sums are:
50 70 90
The value at row 2, column 3 is:
60
The example demonstrates how a matrix can be constructed, analysed by columns and indexed by row and column.
Key Points:
- A matrix is a two-dimensional structure.
- An array can have two or more dimensions.
- A list can contain elements of different types and structures.
- Matrix indexing uses
[row, column]. - Matrix multiplication uses
%*%, whereas*performs element-wise multiplication. - Lists are particularly useful for complex and heterogeneous data.
Section Summary: Chapters 1–5
The first five chapters establish the structural foundations of R programming. R developed from the S language family and has become an important environment for statistical computing, data analysis and graphics. RStudio provides a convenient development environment in which students can write and execute R programs.
R evaluates expressions and stores information in objects. Vectors provide a fundamental structure for collections of values, while vectorised operations allow calculations to be performed efficiently across entire collections. Matrices extend vectors into two dimensions, arrays support multidimensional structures, and lists provide flexible containers capable of holding different types of objects.
These concepts form the foundation for more advanced R programming and data-science tasks. A student who understands objects, vectors, indexing, vectorisation, matrices, arrays and lists will be well prepared for subsequent work with data frames, functions, control structures and statistical analysis.
CHAPTER SIX
Factors, Missing Values and Data Frames
Key Terminology:
6.1 Factors in R
A factor is an R data structure designed primarily for representing categorical data. A categorical variable contains values that represent groups or categories rather than measurements on a continuous numerical scale.
Examples include gender categories, departments, education levels, product types, blood groups, regions, and responses such as Yes and No.
For example, consider the following observations:
gender <- c("Male", "Female", "Female", "Male", "Female")
The vector contains character strings. It can be converted into a factor using:
gender_factor <- factor(gender)
R identifies the distinct categories and stores them as levels.
6.1.1 Factor Levels:
The categories represented by a factor are called its levels.
department <- factor(c("Engineering", "Science", "Engineering", "Arts"))
levels(department)
A typical result is:
"Arts" "Engineering" "Science"
Each unique category becomes a level. By default, R generally orders character-based factor levels alphabetically unless another ordering is specified.
The number of levels can be obtained using:
nlevels(department)
If there are three categories, the result is:
3
6.1.2 Creating Factors with Specific Levels:
Sometimes the order of categories is important. For example, an education variable may have an inherent order:
Primary < Secondary < Undergraduate < Postgraduate.
The levels can be explicitly specified:
education <- factor(
c("Secondary", "Undergraduate", "Postgraduate", "Primary"),
levels = c("Primary", "Secondary", "Undergraduate", "Postgraduate")
)
This ensures that the factor levels follow the specified order rather than the default alphabetical order.
Factors can also be declared as ordered factors:
rating <- factor(
c("Poor", "Good", "Excellent", "Good"),
levels = c("Poor", "Good", "Excellent"),
ordered = TRUE
)
An ordered factor is appropriate when the categories possess a meaningful ranking.
6.1.3 Factors and Categorical Data:
Factors are especially useful in statistical analysis because many statistical procedures need to distinguish between numerical measurements and categories.
| Variable | Example Values | Nature |
|---|---|---|
| Age | 18, 21, 25 | Numerical |
| Department | Engineering, Arts | Categorical |
| Grade | A, B, C | Categorical |
| Income | 250000, 400000 | Numerical |
6.2 Inspecting Factors
R provides several functions for examining factors.
class(department) levels(department) nlevels(department) str(department)
The class() function identifies the class of an object, while levels() displays the categories associated with a factor. The str() function provides a compact structural description.
For example:
6.3 Missing Values in R
Real-world datasets frequently contain incomplete observations. A participant may fail to answer a question, a sensor may fail to record a measurement, or information may simply be unavailable.
R represents a missing value using the special value NA, meaning Not Available.
scores <- c(75, 82, NA, 91, 68)
The third observation is missing.
It is important to understand that NA is not the same as zero. A zero is an observed numerical value, whereas NA indicates that the value is unavailable or missing.
6.3.1 Detecting Missing Values:
The is.na() function determines whether values are missing.
scores <- c(75, 82, NA, 91, 68) is.na(scores)
The result identifies the position containing NA.
FALSE FALSE TRUE FALSE FALSE
To count the number of missing observations, a common approach is:
sum(is.na(scores))
Because TRUE is treated numerically as 1 and FALSE as 0 in this context, the result gives the number of missing values.
6.3.2 Missing Values and Calculations:
Missing values can affect calculations. Consider:
scores <- c(75, 82, NA, 91, 68) mean(scores)
Because the vector contains a missing value, the result is normally NA.
To calculate the mean using the available observations, specify na.rm = TRUE:
mean(scores, na.rm = TRUE)
The calculation is:
Mean = (75 + 82 + 91 + 68) / 4 = 79
6.4 Other Forms of Missing or Special Values
R distinguishes NA from several other special values.
| Value | Meaning |
|---|---|
NA |
Missing or unavailable value. |
NaN |
Not a Number; commonly produced by undefined numerical operations. |
Inf |
Positive infinity. |
-Inf |
Negative infinity. |
NULL |
Represents the absence of an object or value in R. |
For example, division by zero can produce an infinite result, while some undefined numerical operations can produce NaN.
10 / 0 0 / 0
6.5 Data Frames
A data frame is one of the most important data structures in R for storing tabular data. It can be viewed as a table in which:
- each row generally represents an observation;
- each column generally represents a variable; and
- different columns can contain different data types.
For example, a student dataset may contain:
| Name | Age | Department | Score |
|---|---|---|---|
| Ada | 20 | Computer Science | 85 |
| John | 22 | Physics | 78 |
| Mary | 21 | Mathematics | 91 |
In R, this dataset can be created using:
students <- data.frame(
Name = c("Ada", "John", "Mary"),
Age = c(20, 22, 21),
Department = c("Computer Science", "Physics", "Mathematics"),
Score = c(85, 78, 91)
)
6.5.1 Structure of a Data Frame:
A data frame is essentially a collection of columns of equal length. Each column is a variable, while the rows align observations across those variables.
The dimensions of a data frame can be inspected using:
dim(students) nrow(students) ncol(students)
If the data frame contains three observations and four variables:
dim(students) returns 3 4.
Therefore, the data frame has 3 rows and 4 columns.
The str() function is particularly useful for understanding the structure of a dataset:
str(students)
6.5.2 Accessing Columns:
There are several ways to access a column in a data frame.
Using the dollar sign:
students$Score
Using double brackets:
students[["Score"]]
Using single brackets:
students["Score"]
These expressions are related but do not always return exactly the same type of object. A single column selected with students["Score"] remains a one-column data frame, whereas students[["Score"]] returns the column itself.
6.5.3 Accessing Rows and Individual Elements:
Rows and columns can be selected using the bracket notation:
students[1, ] students[, 2] students[1, 2]
The first expression selects the first row, the second selects the second column, and the third selects the element located in the first row and second column.
For the student data above, students[1,2] returns the age of the first student.
6.6 Adding and Modifying Data-Frame Columns
New columns can be created using the dollar sign operator.
students$Passed <- students$Score >= 50
This creates a logical column indicating whether each student achieved a score of at least 50.
For example, a score of 85 produces TRUE, while a score below 50 would produce FALSE.
An existing column can also be modified:
students$Score <- students$Score + 5
This adds five marks to every score. Such transformations should only be performed when they are logically justified by the data-analysis task.
6.7 Names in R
Names are important because they allow R objects and their components to be identified clearly.
The names() function can be used to examine or assign names.
names(students)
The result identifies the column names:
"Name" "Age" "Department" "Score"
Names can also be changed:
names(students) <- c("Student", "Age", "Department", "Mark")
Meaningful names make data analysis easier to understand and reduce the likelihood of errors.
6.8 Attributes in R
An attribute is additional information attached to an R object. Attributes provide metadata about the object without necessarily changing the primary data stored in it.
Common attributes include:
names— names associated with elements;dim— dimensions of an object;dimnames— names associated with dimensions;class— the class of an object; andlevels— categories associated with a factor.
The attributes of an object can be inspected using:
attributes(students) attributes(department)
Understanding attributes is useful because R relies heavily on object structure and metadata when determining how an object should behave.
6.9 Handling Missing Values in Data Frames
Missing values commonly occur in real datasets. Consider the following data frame:
employees <- data.frame(
Name = c("Ada", "John", "Mary", "Peter"),
Age = c(25, NA, 31, 29),
Salary = c(250000, 300000, NA, 280000)
)
Missing values can be identified with:
is.na(employees)
The number of missing values in each column can be obtained with:
colSums(is.na(employees))
This is particularly useful when performing an initial inspection of a dataset.
The number of complete rows can be identified using:
complete.cases(employees)
To count complete rows:
sum(complete.cases(employees))
6.10 Removing or Retaining Missing Observations
There is no single correct method for dealing with missing values in every dataset. The appropriate approach depends on the source of the missingness, the amount of missing data, and the purpose of the analysis.
Rows containing missing values can be excluded using:
complete_employees <- employees[complete.cases(employees), ]
This retains only rows with complete observations.
However, automatically deleting all incomplete rows may cause loss of useful information or introduce bias. In more advanced data analysis, missing values may instead be replaced through appropriate imputation methods.
6.11 Factors, Missing Values and Data Frames Together
The real strength of these structures becomes clearer when they are used together in a realistic dataset.
Suppose a university stores information about students:
students <- data.frame(
Name = c("Ada", "John", "Mary", "Peter"),
Department = factor(
c("Computer Science", "Physics", "Computer Science", "Mathematics")
),
Level = factor(
c("100", "200", "200", NA),
levels = c("100", "200", "300", "400")
),
Score = c(82, 75, NA, 91)
)
This dataset demonstrates several important principles:
Namecontains character data;Departmentcontains categorical data represented as a factor;Levelcontains categorical values with explicitly defined possible levels;Scorecontains numerical data; and- missing values occur in both the
LevelandScorevariables.
Such a structure is representative of the type of dataset encountered during practical data analysis.
6.12 Worked Example: Inspecting a Dataset
Question: Given the student dataset below, determine the number of observations, number of variables, number of missing values, and number of departments.
students <- data.frame(
Name = c("Ada", "John", "Mary", "Peter", "Grace"),
Department = factor(
c("Physics", "Computer Science", "Physics", "Mathematics", "Computer Science")
),
Score = c(78, 85, NA, 91, 74)
)
Solution:
nrow(students) ncol(students) sum(is.na(students)) nlevels(students$Department)
There are 5 observations and 3 variables. There is 1 missing value in the Score variable, and there are 3 departments.
6.14 Key Points
- A factor is an R structure used primarily to represent categorical data.
- The distinct categories represented by a factor are called levels.
- Ordered factors are appropriate when categories have a meaningful ranking.
- Missing observations are represented by
NAin R. is.na()can be used to detect missing values.sum(is.na(x))can be used to count missing values in a vector.complete.cases()identifies observations containing no missing values.- Missing values should not automatically be replaced with zero.
- A data frame is a two-dimensional structure suitable for storing tabular datasets.
- Rows generally represent observations, while columns generally represent variables.
- Different columns in a data frame can contain different data types.
nrow(),ncol(),dim(), andstr()are useful for inspecting data frames.- The
$operator provides a convenient way to access named columns. - R objects can possess attributes such as names, dimensions, classes, and levels.
- Factors, missing-value handling, and data frames are fundamental to practical data analysis in R.
6.15 Chapter Summary
Factors, missing values, and data frames are fundamental components of practical R programming. Factors provide an appropriate structure for categorical variables by associating observations with defined levels. They are especially important in statistical and data-analysis applications where categorical variables must be distinguished from numerical measurements.
Missing values are an unavoidable feature of many real-world datasets. R represents missing observations using NA. Functions such as is.na(), complete.cases(), and na.rm = TRUE provide basic mechanisms for identifying and handling missing observations. However, the decision to remove or replace missing values should be based on the nature and purpose of the data rather than applied mechanically.
Data frames provide a flexible structure for storing datasets in rows and columns. Their ability to contain different data types in different columns makes them particularly useful for data science. By combining data frames with factors and appropriate missing-value handling, an R programmer can construct, inspect, and prepare realistic datasets for subsequent analysis.
Finally, names and attributes provide important information about the structure and meaning of R objects. A sound understanding of these concepts provides the foundation for the data-cleaning and transformation techniques introduced in subsequent chapters.
CHAPTER SEVEN
Getting Data In and Out of R
7.1 Understanding Data Input and Output
In programming, input refers to information supplied to a program, while output refers to information produced by the program. In R, data input and output are commonly described as data import and data export.
For example, suppose a researcher has a CSV file containing the examination scores of 500 students. The researcher may import the file into R, calculate the mean score, identify the highest score and create a graph. The resulting summary or modified dataset may then be exported to another file.
Basic workflow:
External data → Import into R → Analyse or transform → Export results
Data may enter R from many different sources, including:
- CSV files;
- plain text files;
- tab-delimited files;
- spreadsheets;
- databases;
- web resources;
- APIs;
- statistical software files; and
- other programs that can exchange data with R.
The appropriate import method depends on the structure and location of the data.
7.2 File Paths and Working Directories
Before reading or writing a file, R needs to know where the file is located. This location is represented by a file path.
For example, a Windows computer might contain a file at:
C:/Users/Student/Documents/data.csv
In R, forward slashes can conveniently be used in file paths even on Windows systems.
R also maintains a working directory. The working directory is the default location from which R reads files and to which it writes files when a complete path is not supplied.
Checking the Working Directory:
The getwd() function returns the current working directory.
getwd()
A possible result might be:
[1] "C:/Users/Student/Documents"
Changing the Working Directory:
The setwd() function changes the working directory.
setwd("C:/Users/Student/Documents/Data")
The new location can then be verified:
getwd()
It is often preferable in reproducible projects to use carefully organized project directories rather than repeatedly changing the working directory manually.
7.3 Listing Files in a Directory
The list.files() function can be used to examine the files available in a directory.
list.files()
For example, if the working directory contains three files, R may return:
[1] "students.csv" [2] "scores.txt" [3] "results.csv"
This is useful when checking whether the file required for analysis is located in the expected directory.
7.4 Getting Data into R
Data import involves reading information stored outside R and converting it into R objects. The most suitable function depends on the structure of the source data.
For introductory work, an important distinction is between:
- reading structured tabular data; and
- reading raw lines of text.
Tabular data is normally organized into rows and columns. Raw text may instead consist of sentences, paragraphs or lines that require additional processing.
7.5 Reading Tabular Text Files
R provides functions for reading tabular data from text files. One traditional base R function is read.table().
The basic form is:
read.table(file, header = FALSE, sep = "", stringsAsFactors = FALSE)
The exact default arguments can vary by R version, so important options should be specified explicitly when appropriate.
Important Arguments:
- file: Specifies the file to read.
- header: Indicates whether the first row contains column names.
- sep: Specifies the field separator.
- stringsAsFactors: Controls how character data is treated in versions of R where this argument applies.
Example:
Suppose a file named students.txt contains:
Name Age Score Ada 20 85 John 21 74 Mary 19 91 David 22 68
The data can be imported using:
students <- read.table("students.txt", header = TRUE)
R will create an object called students containing the imported data.
The object can then be displayed:
students
The resulting data can be treated as a data frame and accessed using familiar indexing operations.
7.6 Reading CSV Files
A CSV file is one of the most common formats for exchanging tabular data. CSV stands for Comma-Separated Values. In a simple CSV file, each row represents a record and commas separate the fields.
For example:
Name,Age,Score Ada,20,85 John,21,74 Mary,19,91 David,22,68
Base R provides the read.csv() function for importing this type of data.
students <- read.csv("students.csv")
Because the first row commonly contains column names, read.csv() is convenient for standard CSV files.
Checking Imported Data:
After importing data, it is good practice to inspect it rather than immediately performing calculations.
head(students) str(students) summary(students)
The head() function displays the first several rows, str() describes the structure of the object and summary() provides basic summaries.
7.7 Delimiters and Tabular Data
Not all text files use commas to separate values. A file may use spaces, tabs, semicolons or another character.
The separating character is called a delimiter.
| Format | Typical Delimiter | Example |
|---|---|---|
| CSV | Comma | Ada,20,85 |
| Tab-delimited | Tab | Ada 20 85 |
| Semicolon-delimited | Semicolon | Ada;20;85 |
When the delimiter is not the standard delimiter expected by a function, it should be specified explicitly.
For example:
data <- read.table("students.txt",
header = TRUE,
sep = "\t")
Here, "\t" represents a tab character.
7.8 Handling Headers
A tabular file may or may not contain a header row. A header provides names for the columns.
Consider the following file:
Name,Age,Score Ada,20,85 John,21,74 Mary,19,91
The first row is a header because it identifies the columns as Name, Age and Score.
Therefore:
data <- read.csv("students.csv", header = TRUE)
If a file contains no header, the first row should not be treated as column names.
data <- read.csv("students.csv", header = FALSE)
When no header is supplied, R will generally assign default column names such as V1, V2 and V3.
7.9 Reading Lines from a Text File
Sometimes the objective is not to import a table but to read the actual lines of a text file. For example, a file might contain a paragraph, a list of messages or a log file.
The readLines() function is designed for this purpose.
lines <- readLines("notes.txt")
The result is generally a character vector in which each element corresponds to a line of the file.
Example:
Suppose notes.txt contains:
Data science combines several disciplines. R is widely used for statistical computing. Data visualization helps communicate results.
The command:
notes <- readLines("notes.txt")
stores the lines in the object notes.
The first line can then be accessed using:
notes[1]
The second line can be accessed using:
notes[2]
This approach is useful when the structure of the file is textual rather than tabular.
7.10 Reading Only a Selected Number of Lines
When working with a large text file, it may be useful to read only a specified number of lines.
The n argument of readLines() can be used for this purpose.
first_lines <- readLines("notes.txt", n = 3)
This requests the first three lines of the file.
This can be useful for examining the beginning of a file before deciding how the entire file should be processed.
7.11 Connections in R
R uses the concept of a connection to provide a controlled interface for reading from or writing to an external source.
A connection can represent a file, a URL or another data stream. This provides a general mechanism for communicating between an R program and an external resource.
For example, a file connection can be created with:
con <- file("notes.txt", open = "r")
The "r" mode indicates that the connection is opened for reading.
After the connection has been used, it should be closed:
close(con)
Explicit connections are particularly useful when a program needs to perform several operations on the same external source.
7.12 Connection Modes
Connections can be opened in different modes depending on the intended operation.
| Mode | Meaning |
|---|---|
"r" |
Read from the connection. |
"w" |
Write to the connection, replacing existing contents where applicable. |
"a" |
Append data to the existing contents where supported. |
Using the correct mode is important because an inappropriate mode may cause data to be overwritten or prevent the desired operation.
7.13 Reading from a URL Connection
A URL identifies the location of a resource on a network. R can use connections to read information from URLs when the resource is accessible and its contents are suitable for the requested operation.
A URL connection can be created with the url() function.
con <- url("https://example.com/data.txt", open = "r")
Lines can then be read from the connection:
data <- readLines(con)
After the operation is complete, the connection should be closed:
close(con)
The complete sequence is therefore:
con <- url("https://example.com/data.txt", open = "r")
data <- readLines(con)
close(con)
The exact protocol and access requirements depend on the remote resource. A URL does not guarantee that the resource is publicly accessible or that its contents are in a format that R can directly interpret as a table.
7.14 Reading Tabular Data Directly from a URL
If a remote resource contains a suitable tabular file, R can sometimes read it directly by supplying the URL to a data-reading function.
For example, a CSV resource may be read using:
data <- read.csv("https://example.com/data.csv")
This approach avoids the need to manually download the file first, provided the remote resource is accessible and compatible with the function being used.
The general idea is:
Local file: R → computer storage → file
Remote file: R → network → URL resource
When working with remote data, researchers should consider whether the resource is stable, publicly accessible, trustworthy and appropriate for the analysis.
7.15 Getting Data Out of R
Data export is the process of transferring information from R to an external file or system. Export is useful when results need to be shared, archived, imported into another program or used in a later stage of a project.
Common export targets include:
- CSV files;
- text files;
- R-specific data files;
- spreadsheets and other structured formats; and
- external databases or services.
The choice of format should depend on how the resulting data will be used.
7.16 Writing CSV Files
The base R function write.csv() can be used to export a data frame to a CSV file.
Suppose the following data frame exists:
students <- data.frame(
Name = c("Ada", "John", "Mary"),
Age = c(20, 21, 19),
Score = c(85, 74, 91)
)
The data can be exported using:
write.csv(students, "students_output.csv", row.names = FALSE)
The argument row.names = FALSE prevents R from adding row numbers as an additional column.
The resulting CSV file can be opened by many spreadsheet programs and data-analysis applications.
7.17 Writing Text Files
R can also write character data to a text file using writeLines().
For example:
messages <- c( "Data analysis begins with reliable data.", "R provides tools for statistical computing.", "Good data preparation improves analysis." ) writeLines(messages, "messages.txt")
Each element of the character vector is written as a separate line.
7.18 Saving R Objects
Sometimes the objective is not to create a general-purpose text file but to preserve an R object so that it can later be restored in R.
The save() function can be used to save one or more R objects.
save(students, file = "students.RData")
The saved object can later be restored using load():
load("students.RData")
This approach preserves R objects in a form suitable for continued work in R.
Another useful function is saveRDS(), which saves a single R object.
saveRDS(students, "students.rds")
The object can later be retrieved using readRDS():
students_copy <- readRDS("students.rds")
The important distinction is that save() and load() are designed to save and restore one or more named objects, whereas saveRDS() and readRDS() are commonly used to save and retrieve an individual object.
7.19 Importing and Exporting: A Practical Workflow
A reliable data-import workflow should not stop immediately after the file has been read. Imported data should be checked before analysis.
A practical workflow is:
- Identify the data source.
- Determine the file format and delimiter.
- Locate the file or URL.
- Import the data into R.
- Inspect its structure.
- Check column names and data types.
- Check for missing or unexpected values.
- Perform the required analysis or transformation.
- Export the required results.
- Verify that the exported file contains the expected information.
For example:
students <- read.csv("students.csv")
head(students)
str(students)
summary(students)
write.csv(students, "students_checked.csv", row.names = FALSE)
The principle is simple: import, inspect, process and verify.
7.20 Common Import Problems
Data import frequently causes errors because external files are not always structured exactly as expected. Several problems occur repeatedly.
Incorrect File Path:
If R cannot find the specified file, it may produce an error indicating that the file does not exist.
For example:
read.csv("student.csv")
If the actual file is named students.csv, the import will fail.
Solution: Check the filename, directory and file path.
Incorrect Delimiter:
If a tab-delimited file is read as though it were comma-separated, the data may be imported incorrectly, with the entire row appearing as a single field.
Solution: Identify the actual delimiter and specify it appropriately.
Incorrect Header Setting:
If a file has column names but header = FALSE is used, the column names may be treated as ordinary data.
If a file does not have a header but header = TRUE is used, the first row of actual data may incorrectly become the column names.
Inconsistent Number of Fields:
Some rows may contain more or fewer values than other rows. This can cause warnings or unexpected results.
Solution: Inspect the original file and determine why the rows have different structures.
Encoding Problems:
Text containing special characters may appear incorrectly if the file's character encoding is not handled properly.
This issue is particularly relevant when datasets contain names, languages or symbols outside basic ASCII characters.
7.21 Common Export Problems
Export operations can also introduce problems.
- The destination directory may not exist.
- The program may not have permission to write to the selected location.
- An existing file may be overwritten unintentionally.
- Row names may be exported when they were not intended to be included.
- Data may lose some information when converted into a simpler format.
For example, exporting a data frame to CSV is convenient for interoperability, but a CSV file does not preserve every feature of an R object in the same way that an R-specific format can.
7.22 Comparing Common Data Input and Output Methods
| Function | Primary Purpose | Typical Use |
|---|---|---|
read.table() |
Read tabular text data | General delimited text files |
read.csv() |
Read comma-separated data | CSV datasets |
readLines() |
Read lines of text | Plain text and log files |
write.csv() |
Write tabular data | Exporting data frames as CSV |
writeLines() |
Write lines of text | Creating text files |
save() |
Save R objects | Preserving multiple R objects |
load() |
Restore saved R objects | Reloading .RData files |
saveRDS() |
Save one R object | Preserving an individual object |
readRDS() |
Read one saved R object | Restoring an object from an RDS file |
7.23 Worked Example: Importing, Inspecting and Exporting Student Data
Question: A researcher has a file named students.csv containing student names, ages and examination scores. Explain how the data can be imported, inspected and exported after processing.
Given:
The file contains the columns Name, Age and Score.
Required:
Import the data, inspect its structure and export the resulting data.
Solution:
students <- read.csv("students.csv")
head(students)
str(students)
summary(students)
write.csv(students,
"students_processed.csv",
row.names = FALSE)
Explanation:
read.csv() imports the CSV file. head() allows the researcher to see the first observations. str() reveals the structure and data types, while summary() provides basic descriptive information. Finally, write.csv() creates a new CSV file containing the data.
7.24 Worked Example: Reading a Text File Line by Line
Question: A text file called research.txt contains five lines of text. The first three lines are required for preliminary analysis. How can they be imported?
Solution:
research_lines <- readLines("research.txt", n = 3)
research_lines
Explanation: The n = 3 argument instructs R to read the first three lines. The result is a character vector containing those lines.
7.25 Worked Example: Using a URL Connection
Question: A researcher wants to read a text resource available at a URL.
Solution:
con <- url("https://example.com/data.txt", open = "r")
lines <- readLines(con)
close(con)
Explanation: The url() function creates a connection to the remote resource. readLines() reads the text, and close() releases the connection after the operation is complete.
7.26 Good Practices for Data Import and Export
Reliable data handling requires more than knowing the correct function. Good programming practice reduces errors and improves reproducibility.
- Inspect before analysis: Do not assume that imported data has the expected structure.
- Use explicit paths when necessary: This reduces ambiguity about which file is being accessed.
- Check delimiters: Make sure the selected import function matches the actual file structure.
- Check headers: Confirm whether the first row contains variable names.
- Preserve original data: Avoid overwriting the original dataset unless there is a deliberate reason to do so.
- Use meaningful filenames: Names such as
survey_cleaned.csvare more informative thanfile2.csv. - Verify exports: After exporting, confirm that the output file contains the expected data.
- Close connections: Explicit connections should be closed after use.
- Document data sources: Record where the data came from and how it was obtained.
7.28 Key Points
- Data import brings information from an external source into R.
- Data export transfers information from R to an external destination.
- R uses file paths and working directories to locate local files.
getwd()reports the current working directory.setwd()changes the working directory.list.files()lists files in a directory.read.table()is used to read general tabular text data.read.csv()is commonly used for CSV data.- A delimiter separates fields in a text-based dataset.
- The
headerargument determines whether the first row is interpreted as column names. readLines()reads text as individual lines.- R connections provide a mechanism for communicating with external data sources.
url()can establish a connection to a remote resource.write.csv()exports tabular data as CSV.writeLines()writes character data to text files.save()andload()can preserve and restore R objects.saveRDS()andreadRDS()provide another mechanism for saving and retrieving individual R objects.- Imported data should always be inspected before serious analysis.
- Original data should normally be preserved before transformations are performed.
7.29 Chapter Summary
Data input and output are fundamental components of practical R programming. Although R can create data internally, most real-world data-science projects require information to be obtained from external files, systems or network resources.
Local files can be accessed using file paths and working directories. Tabular text data can be imported using functions such as read.table() and read.csv(), while unstructured or line-oriented text can be read using readLines(). Correct interpretation of delimiters and headers is essential for importing tabular data accurately.
R also provides the concept of connections, which creates a general interface for communicating with external resources. File and URL connections can be opened, read from or written to, and closed when the operation is complete.
Data can be exported using functions such as write.csv() and writeLines(). R-specific objects can also be preserved using save(), load(), saveRDS() and readRDS().
The central principle is that data movement should be deliberate and verifiable. A competent R programmer does not merely import a file and assume that everything is correct. The programmer identifies the source, understands its structure, imports it using an appropriate method, inspects the resulting R object, processes the data and verifies any exported results.
Core idea to remember
External data → Locate → Import → Inspect → Process → Export → Verify
CHAPTER EIGHT
Cleaning and Transforming Data
8.1 Introduction
Data analysis rarely begins with a perfectly organized dataset. Data collected from surveys, databases, spreadsheets, websites, sensors and other sources may contain missing values, duplicate records, inconsistent spellings, incorrect data types, unnecessary variables and values that are unsuitable for analysis.
Data cleaning is the process of identifying and correcting, removing or appropriately handling problems in a dataset. Data transformation involves changing data from one form or representation into another so that it is suitable for analysis.
In R, data cleaning and transformation make extensive use of vectors, data frames, logical conditions, functions, indexing and other data structures introduced in earlier chapters. These operations are fundamental because the quality of an analysis depends heavily on the quality and suitability of the data supplied to it.
8.2 Learning Objectives
After studying this chapter, the student should be able to:
- explain the meaning and importance of data cleaning;
- identify common data-quality problems;
- inspect a dataset in R before modifying it;
- identify and handle missing values;
- identify and remove duplicate observations when appropriate;
- detect inconsistent and invalid values;
- select, filter and modify data in R;
- transform variables using R expressions and functions;
- create new variables from existing variables;
- apply common functions to data frames and vectors;
- explain the difference between cleaning and transformation; and
- prepare a dataset for subsequent analysis.
8.3 Key Terminology
| Term | Meaning |
|---|---|
| Data cleaning | Identifying and appropriately correcting, removing or handling problems in data. |
| Data transformation | Changing data into a different form or representation for analysis. |
| Missing value | A value that is unavailable or not recorded for an observation. |
| Duplicate | A repeated observation or record that may represent the same entity or event more than once. |
| Variable | A characteristic recorded for observations in a dataset. |
| Observation | A single record, case or unit represented in a dataset. |
| Filtering | Selecting observations that satisfy specified conditions. |
| Standardization | Putting values into a consistent representation or format. |
8.4 What Is Data Cleaning?
Data cleaning is the systematic process of examining a dataset and dealing with values or records that are missing, incorrect, inconsistent, duplicated, incorrectly formatted or otherwise unsuitable for the intended analysis.
Consider the following small dataset:
| ID | Name | Age | Gender | Score |
|---|---|---|---|---|
| 101 | Ada | 20 | Female | 75 |
| 102 | Bola | 21 | F | 82 |
| 103 | Chidi | NA | Male | 68 |
| 104 | Dayo | 19 | male | 72 |
| 104 | Dayo | 19 | male | 72 |
This dataset contains several issues. The age for Chidi is missing, gender is represented using different forms such as "Male", "male" and "F", and the record for Dayo appears twice.
Before analyzing this dataset, the researcher should determine how these issues should be handled. Cleaning is therefore not simply deleting unusual values. It requires understanding the data and the purpose for which it was collected.
8.5 Common Data-Quality Problems
8.5.1 Missing Data:
Missing data occurs when a value that should have been recorded is unavailable. In R, a missing value is commonly represented by NA.
scores <- c(75, 82, NA, 68, 91) is.na(scores)
The expression is.na(scores) returns a logical vector indicating which elements are missing.
scores[is.na(scores)]
To count missing values:
sum(is.na(scores))
The result is 1, because the vector contains one missing value.
8.5.2 Duplicate Data:
A duplicate occurs when the same observation appears more than once. R provides the duplicated() function for identifying duplicated elements or rows.
students <- data.frame(
id = c(101, 102, 103, 103),
name = c("Ada", "Bola", "Chidi", "Chidi")
)
duplicated(students)
The result identifies the repeated row. If removing exact duplicate rows is justified, unique() can be used:
students_clean <- unique(students)
8.5.3 Inconsistent Data:
Inconsistency occurs when the same concept is represented in different ways. For example:
gender <- c("Male", "male", "M", "Female", "female", "F")
These values may represent only two categories. A cleaning operation can standardize the representation.
gender_clean <- ifelse(
gender %in% c("Male", "male", "M"),
"Male",
"Female"
)
The appropriate transformation depends on the original data and its documentation. A value should never be changed merely because it looks unusual.
8.5.4 Invalid Values:
An invalid value is a value that violates the rules or expected range of a variable.
For example, if an age variable represents human ages, a value such as 250 may require investigation.
age <- c(19, 21, 20, 250, 22) age[age < 0 | age > 120]
The result identifies 250 as a value requiring investigation. Whether it should be corrected, removed or treated as missing depends on the source of the error.
8.6 Inspecting Data Before Cleaning
A sound cleaning process begins with inspection. The analyst should understand the structure and contents of the dataset before making changes.
Suppose a data frame is called students.
head(students)
head() displays the first rows of the dataset.
str(students)
str() provides information about the structure of an R object, including the data types of variables.
summary(students)
summary() provides a statistical or descriptive summary appropriate to the variables in the object.
The dimensions of a data frame can be inspected using:
dim(students) nrow(students) ncol(students)
These functions help answer basic questions such as: How many observations are present? How many variables are present? What types of variables exist? Are there obvious missing or unusual values?
8.7 Handling Missing Values
Missing values require careful treatment because simply deleting them can remove useful information, while replacing them without justification can introduce bias.
Identifying Missing Values:
data <- c(10, 15, NA, 20, NA, 30) is.na(data)
To determine the number of missing observations:
sum(is.na(data))
To obtain the number of non-missing observations:
sum(!is.na(data))
Removing Missing Values:
For some analyses, observations containing missing values may be excluded.
clean_data <- na.omit(data)
This produces an object with missing observations removed.
Ignoring Missing Values in Calculations:
Many R functions return NA when their input contains a missing value. The na.rm = TRUE argument can be used by functions that support it.
mean(data, na.rm = TRUE) sum(data, na.rm = TRUE)
Here, R calculates the result using the available non-missing values.
Replacing Missing Values:
In some situations, missing values may be replaced using an appropriate rule. For example, a missing value could be replaced with a known value from another reliable source, or a statistically justified estimate such as a median could be used.
scores <- c(60, 70, 80, NA, 90) median_score <- median(scores, na.rm = TRUE) scores[is.na(scores)] <- median_score scores
The median of the available scores is used to replace the missing value. This is an example of imputation.
8.8 Selecting and Filtering Data
Cleaning often requires selecting particular rows or columns. R's indexing system provides a simple way to do this.
Consider:
students <- data.frame(
name = c("Ada", "Bola", "Chidi", "Dayo"),
age = c(20, 21, 19, 22),
score = c(75, 82, 68, 91)
)
To select students with scores greater than or equal to 80:
students[students$score >= 80, ]
The expression students$score >= 80 produces a logical vector. R uses it to select only rows satisfying the condition.
Multiple conditions can also be used:
students[students$age >= 20 & students$score > 70, ]
The operator & means AND, while | means OR.
8.9 Transforming Data
Data transformation changes the representation of data while preserving or creating information required for analysis.
Common transformations include:
- creating new variables;
- changing units;
- recoding categories;
- converting data types;
- creating groups or categories;
- calculating totals, averages or differences;
- scaling numerical variables; and
- restructuring data for analysis.
Creating a New Variable:
Suppose a student's score is stored as a percentage. A new variable representing the student's grade category can be created.
students$grade <- ifelse( students$score >= 70, "A", ifelse(students$score >= 60, "B", ifelse(students$score >= 50, "C", "F")) )
The new variable is derived from the existing score variable.
Unit Conversion:
Suppose temperature is recorded in Celsius and a Fahrenheit variable is required.
The conversion formula is:
temperature_c <- c(0, 20, 30, 40) temperature_f <- (9/5) * temperature_c + 32
Because R performs arithmetic directly on vectors, the formula is applied to every element.
Recoding Values:
Suppose a variable contains numerical codes for gender:
gender_code <- c(1, 2, 2, 1, 1)
If the documentation states that 1 represents Male and 2 represents Female, the values can be recoded:
gender <- ifelse(gender_code == 1, "Male", "Female")
Recoding should always be based on the documented meaning of the original codes.
8.10 Vectorised Operations in Data Transformation
One of R's most useful features is vectorisation. Many operations can be applied to an entire vector without writing an explicit loop.
prices <- c(100, 200, 300, 400) new_prices <- prices * 1.10
Each price is increased by 10 percent.
The result is equivalent to applying the calculation individually to every value, but R performs the operation on the vector as a whole.
Other examples include:
x <- c(1, 4, 9, 16) sqrt(x) x^2 x + 10 x / 2
Vectorised operations are particularly important in data science because datasets often contain thousands or millions of observations.
8.11 Useful Functions for Data Transformation
| Function | Purpose | Example |
|---|---|---|
ifelse() | Conditional transformation | ifelse(x > 50, "Pass", "Fail") |
is.na() | Identify missing values | is.na(x) |
na.omit() | Remove observations containing missing values | na.omit(df) |
unique() | Obtain unique values or rows | unique(x) |
duplicated() | Identify duplicates | duplicated(df) |
sort() | Sort values | sort(x) |
order() | Obtain ordering indices | df[order(df$score), ] |
as.numeric() | Convert to numeric representation where appropriate | as.numeric(x) |
as.character() | Convert to character | as.character(x) |
8.12 Cleaning and Transforming a Data Frame
The following example combines several operations into a simple data-preparation workflow.
Suppose the original data are:
students <- data.frame(
id = c(101, 102, 103, 103, 104),
name = c("Ada", "Bola", "Chidi", "Chidi", "Dayo"),
score = c(75, 82, NA, NA, 91)
)
Step 1: Inspect the data.
str(students) summary(students)
Step 2: Check for duplicated rows.
duplicated(students)
Step 3: Remove exact duplicate rows if justified.
students <- unique(students)
Step 4: Examine missing scores.
is.na(students$score)
Step 5: Calculate an available-value summary.
mean(students$score, na.rm = TRUE)
Step 6: Create a transformed variable.
students$passed <- ifelse( students$score >= 50, "Yes", "No" )
The result is a dataset that has been inspected, checked for duplication, examined for missing values and extended with a new analytical variable.
8.13 Using the subset() Function
R also provides the subset() function for selecting observations and variables in a readable form.
students_high <- subset( students, score >= 70 )
This selects observations whose score is at least 70.
Specific columns can also be selected:
subset( students, score >= 70, select = c(name, score) )
The result contains only the requested variables for observations satisfying the condition.
8.14 Transforming Data with transform()
The base R function transform() can be used to create or modify variables in a data frame.
students2 <- transform( students, percentage = score, pass = score >= 50 )
Here, two variables are created from the existing score variable. The pass variable contains logical values: TRUE when the score is at least 50 and FALSE otherwise.
This illustrates an important principle of data transformation: derived variables should have a clear relationship with the original data and a meaningful purpose in the analysis.
8.15 Sorting Data
Sorting can make patterns and unusual observations easier to identify.
students[order(students$score), ]
To sort from highest to lowest:
students[order(students$score, decreasing = TRUE), ]
Sorting is useful during exploratory data analysis because extreme values and unexpected observations may become easier to recognize.
8.16 Changing Data Types
Correct data types are important because R treats different types of objects differently.
For example, numerical values stored as character strings cannot be directly used in numerical calculations in the same way as numeric values.
x <- c("10", "20", "30")
class(x)
x_numeric <- as.numeric(x)
class(x_numeric)
sum(x_numeric)
The function as.numeric() converts character representations that can be interpreted as numbers into numeric values.
NA when a value cannot be interpreted as the requested type. Always inspect the result after conversion.
8.17 Standardizing Text Data
Text variables frequently contain differences in capitalization and spacing that can cause apparently identical categories to be treated as different values.
city <- c("Lagos", "lagos", "LAGOS", " Abuja ", "Abuja")
Functions such as tolower() and toupper() can standardize capitalization.
tolower(city)
Whitespace can be removed with trimws():
trimws(city)
These functions can be combined when appropriate:
city_clean <- tolower(trimws(city))
The resulting values have consistent capitalization and unnecessary leading or trailing spaces removed.
8.18 A Practical Data-Preparation Workflow
A useful general workflow for preparing data in R is:
- Understand the data. Determine what each variable represents and how the values were collected.
- Inspect the structure. Use functions such as
str(),head(),summary()anddim(). - Check data quality. Look for missing values, duplicates, invalid values and inconsistent representations.
- Clean the data. Correct, remove or appropriately handle identified problems.
- Transform variables. Convert units, recode categories or create variables required for analysis.
- Validate the result. Check that the cleaning and transformations produced sensible values.
- Save the prepared dataset. Preserve a clean version separately from the original data whenever possible.
8.19 Cleaning Versus Transformation
| Data Cleaning | Data Transformation |
|---|---|
| Focuses on improving data quality. | Focuses on changing data into a useful form. |
| Deals with missing, duplicate, invalid or inconsistent data. | May involve recoding, calculation, scaling or unit conversion. |
| Attempts to preserve the intended meaning of the data. | Creates a representation appropriate for a particular analysis. |
| Example: removing an unintended duplicate. | Example: converting Celsius to Fahrenheit. |
8.21 Key Points
- Data cleaning improves the quality and consistency of a dataset.
- Data transformation changes data into a form suitable for a particular analytical purpose.
- Common data-quality problems include missing values, duplicates, inconsistent values and invalid observations.
- R represents missing values using
NA. is.na()can be used to identify missing values.na.omit()can remove observations containing missing values, although removal should be justified.duplicated()identifies duplicate observations andunique()can remove exact duplicates.- Logical conditions are central to selecting and transforming data.
- R's vectorised operations make it possible to transform many observations efficiently.
- Functions such as
ifelse(),subset(),transform(),sort()andorder()are useful for data preparation. - Data types should be inspected and converted carefully when necessary.
- The original dataset should normally be preserved before cleaning and transformation.
- Clean and appropriately transformed data provide a stronger foundation for analysis, visualization and modelling.
8.22 Chapter Summary
Data collected for analysis is rarely ready for immediate use. It may contain missing values, duplicate observations, inconsistent representations, invalid values or inappropriate data types. Data cleaning addresses these problems by identifying and appropriately handling issues that affect data quality.
R provides a range of functions for inspecting, selecting, filtering and modifying data. Functions such as is.na(), na.omit(), duplicated(), unique(), subset() and ifelse() are particularly useful when working with data frames and vectors.
Data transformation allows an analyst to create new variables, convert units, recode categories, change data types and otherwise restructure information for analysis. R's vectorised operations make many transformations concise and efficient.
A reliable preparation process begins with understanding and inspecting the original data, identifying quality problems, applying justified cleaning procedures, transforming variables where necessary and validating the resulting dataset. The ultimate goal is not simply to produce a dataset that looks tidy, but to produce data that accurately represents the underlying observations and is appropriate for the intended analysis.
8.23 Practical Review Examples
Example 1: Identifying Missing Values:
Question: Given the vector below, determine the number of missing values.
x <- c(12, NA, 18, 21, NA, 30, 35)
Solution:
sum(is.na(x))
Answer: There are 2 missing values.
Example 2: Calculating a Mean with Missing Data:
Question: Calculate the mean of the following values while ignoring the missing observation.
scores <- c(60, 70, 80, NA, 90)
Solution:
mean(scores, na.rm = TRUE)
The available values are 60, 70, 80 and 90.
The mean is:
Answer: The mean is 75.
Example 3: Creating a New Variable:
Question: Create a variable called pass that indicates whether each student scored at least 50.
score <- c(45, 67, 52, 39, 81) pass <- ifelse(score >= 50, "Pass", "Fail") pass
Answer: The resulting vector is:
"Fail" "Pass" "Pass" "Fail" "Pass"
Example 4: Converting Units:
Question: Convert the temperatures 10°C, 20°C and 30°C to Fahrenheit.
Formula:
celsius <- c(10, 20, 30) fahrenheit <- (9/5) * celsius + 32 fahrenheit
The results are 50°F, 68°F and 86°F, respectively.
8.24 Practical Exercises
Basic Exercises:
- Create a numeric vector containing five values and use R to calculate its mean.
- Create a vector containing at least two
NAvalues. Useis.na()to identify them andsum()to count them. - Create a vector containing duplicate values and use
duplicated()to identify the repeated values. - Use
unique()to obtain the unique values from a vector. - Create a vector of student scores and use
ifelse()to classify each student as "Pass" or "Fail".
Intermediate Exercises:
- Create a data frame containing student names, ages and scores for at least six students. Include one missing score and one duplicate row. Inspect the dataset using
str(),head()andsummary(). - Identify and count the missing score.
- Identify the duplicate row and create a version of the data frame with exact duplicates removed.
- Select all students whose score is greater than 70.
- Create a new variable that classifies students into "High" and "Low" score groups using an appropriate condition.
- Create a new variable representing scores as proportions rather than percentages.
Applied Exercises:
- A dataset contains temperatures in Celsius. Write R code to convert all values to Fahrenheit.
- A customer dataset contains the values
"LAGOS","Lagos","lagos"and" Lagos ". Write R code to standardize the values. - A dataset contains ages of 18, 22, 21, 19, 250 and 24. Identify the suspicious value and explain what should be done before deleting or modifying it.
- A sales dataset contains missing sales values. Explain two possible approaches for handling the missing observations and discuss why the appropriate method depends on the context.
Higher-Order Questions:
- Explain why removing every missing observation from a dataset may produce misleading analytical results.
- Why is it dangerous to replace all missing numerical values with zero?
- Explain why an analyst should preserve the original dataset before performing extensive cleaning.
- Two records have exactly the same values in every column. Explain why this is stronger evidence of duplication than two records that merely have the same name.
- Explain the difference between correcting an error and transforming a valid value.
Chapter at a Glance:
Inspect → Identify → Clean → Transform → Validate → Analyze
Effective data preparation in R is a disciplined process. The objective is to preserve the meaning of the original observations while producing a dataset that is accurate, consistent and suitable for analysis.
CHAPTER NINE
Displaying and Plotting Data
9.1 Why Display Data?
Raw data can contain large numbers of observations that are difficult to understand when viewed individually. Data display transforms numerical or categorical values into forms that allow the analyst to recognize important features quickly.
For example, consider the following examination scores:
The values can be read directly, but a graphical representation can make their overall distribution easier to understand.
In R, a simple vector can be created as follows:
scores <- c(62, 65, 67, 68, 70, 72, 73, 74, 75, 78)
scores
R displays the values so that the analyst can inspect them directly.
Importance of Data Display:
- It makes large datasets easier to understand.
- It helps reveal patterns and trends.
- It allows differences between groups to be identified.
- It helps identify unusually high or low observations.
- It supports exploratory data analysis.
- It improves communication of analytical findings.
- It can reveal relationships that are difficult to notice in raw data.
9.2 Displaying Data in R
R provides several ways to inspect and display data. The simplest method is to enter the name of an object in the console.
x <- c(10, 20, 30, 40, 50)
x
The expression x causes R to display the contents of the object.
Using print():
The print() function explicitly instructs R to display an object.
x <- c(10, 20, 30, 40, 50)
print(x)
This is particularly useful inside functions, loops and other programming structures where explicitly printing an object may be necessary.
Displaying a Data Frame:
students <- data.frame(
Name = c("Ada", "John", "Mary"),
Score = c(78, 65, 91)
)
students
R displays the rows and columns of the data frame in the console.
Using head():
When a dataset contains many observations, displaying the entire dataset may be inconvenient. The head() function displays the first few observations.
head(students)
The function is particularly useful when inspecting large datasets after importing them into R.
Using summary():
The summary() function provides a statistical summary of many R objects.
summary(students$Score)
For numerical data, the output commonly includes measures such as the minimum, first quartile, median, mean, third quartile and maximum.
9.3 What Is Plotting?
Plotting is the process of representing data graphically. Instead of examining only numerical values, the analyst uses visual elements to communicate information.
Suppose the following values represent monthly sales:
sales <- c(120, 135, 128, 150, 165, 180)
A plot can show whether sales are generally increasing, decreasing or fluctuating.
R's basic graphics system provides the plot() function for creating many common graphical representations.
plot(sales)
When a single numerical vector is supplied, R plots the values against their observation positions.
9.4 The Basic plot() Function
The general form of the basic plotting function is:
plot(x, y)
Here, x usually represents the horizontal-axis variable and y represents the vertical-axis variable.
For example:
hours <- c(1, 2, 3, 4, 5)
scores <- c(52, 58, 65, 72, 80)
plot(hours, scores)
The resulting graph places study hours on the horizontal axis and examination scores on the vertical axis.
This allows the analyst to investigate whether the two variables appear to have a relationship.
Adding Labels:
plot(
hours,
scores,
main = "Study Hours and Examination Scores",
xlab = "Study Hours",
ylab = "Score"
)
The arguments main, xlab and ylab specify the title, horizontal-axis label and vertical-axis label respectively.
9.5 Common Types of Graphical Representations
Different types of data require different graphical representations. Choosing an appropriate plot is an important part of data analysis.
| Graph | Typical Purpose | Suitable Data |
|---|---|---|
| Bar chart | Compare categories | Categorical data |
| Histogram | Examine distribution | Numerical data |
| Line graph | Show change or trend | Ordered or time-based data |
| Scatter plot | Examine relationships | Two numerical variables |
| Box plot | Compare distributions | Numerical data grouped by categories |
9.6 Bar Plots
A bar plot represents quantities associated with categories using rectangular bars. The length or height of each bar corresponds to the value being represented.
Suppose a university records the number of students enrolled in five programmes:
students <- c(120, 85, 150, 70, 100)
programmes <- c(
"Computer Science",
"Mathematics",
"Engineering",
"Physics",
"Statistics"
)
barplot(
students,
names.arg = programmes,
main = "Students by Programme",
ylab = "Number of Students"
)
The bar plot makes comparison between the programmes easier than reading the five numerical values alone.
When to Use a Bar Plot:
- Comparing categories.
- Displaying frequencies.
- Comparing quantities associated with discrete groups.
- Presenting survey results by category.
A common mistake is to use a bar plot to represent the continuous distribution of numerical observations. A histogram is generally more appropriate for that purpose.
9.7 Pie Charts
A pie chart represents parts of a whole. Each sector represents a category's contribution to the total.
values <- c(40, 30, 20, 10)
labels <- c(
"Books",
"Transport",
"Food",
"Other"
)
pie(
values,
labels = labels,
main = "Monthly Expenditure"
)
Pie charts are most useful when there are relatively few categories and the purpose is to emphasize proportions rather than precise numerical comparison.
9.8 Histograms
A histogram displays the distribution of numerical observations by grouping values into intervals called bins.
For example:
scores <- c(
45, 52, 55, 58, 60,
62, 64, 65, 67, 68,
70, 72, 73, 75, 78,
80, 82, 85, 88, 92
)
hist(
scores,
main = "Distribution of Examination Scores",
xlab = "Score",
ylab = "Frequency"
)
The histogram helps answer questions such as:
- Where are most observations concentrated?
- Is the distribution approximately symmetric?
- Are there unusually low or high observations?
- Does the distribution appear to have more than one concentration?
Bins:
The choice of bin width can affect the appearance of a histogram. Too few bins may hide important features, while too many bins may make the distribution appear unnecessarily irregular.
For example:
hist(scores, breaks = 5)
Here, breaks influences how the observations are divided into intervals.
9.9 Line Graphs
A line graph is useful when the order of observations matters, particularly when examining changes over time.
Consider monthly rainfall measurements:
months <- 1:6
rainfall <- c(35, 48, 62, 80, 74, 55)
plot(
months,
rainfall,
type = "o",
main = "Monthly Rainfall",
xlab = "Month",
ylab = "Rainfall"
)
The argument type = "o" tells R to display points and lines together.
Other common values of type include:
| Value | Meaning |
|---|---|
"p" | Points |
"l" | Lines |
"b" | Points and lines |
"o" | Points and lines overlaid |
9.10 Scatter Plots
A scatter plot displays individual observations for two numerical variables. It is commonly used to investigate whether two variables appear to be related.
Consider study hours and examination scores:
hours <- c(1, 2, 3, 4, 5, 6, 7)
scores <- c(48, 53, 61, 68, 73, 81, 88)
plot(
hours,
scores,
main = "Study Hours and Examination Scores",
xlab = "Study Hours",
ylab = "Examination Score"
)
If the points generally move upward from left to right, the variables may have a positive association.
If the points generally move downward, the variables may have a negative association.
If the points form no obvious pattern, the relationship may be weak or absent.
9.11 Customising R Plots
Basic plots can be improved by adding informative titles, labels and visual properties.
x <- c(1, 2, 3, 4, 5)
y <- c(10, 15, 13, 20, 25)
plot(
x,
y,
type = "o",
main = "Example Data",
xlab = "Observation",
ylab = "Value",
pch = 16,
lwd = 2
)
Some useful graphical arguments include:
| Argument | Purpose |
|---|---|
main | Plot title |
xlab | Horizontal-axis label |
ylab | Vertical-axis label |
type | Controls the type of display |
pch | Controls plotting symbols |
lwd | Controls line width |
9.12 Choosing an Appropriate Plot
The choice of graphical representation should be based on the question being investigated and the structure of the data.
| Question | Recommended Representation |
|---|---|
| How do categories compare? | Bar plot |
| How are numerical observations distributed? | Histogram |
| How does a variable change over time? | Line graph |
| Are two numerical variables associated? | Scatter plot |
| What proportion does each category contribute? | Pie chart or bar plot |
9.13 Interpreting Graphical Representations
Creating a graph is only the first step. The analyst must interpret what the graph communicates.
When interpreting a graph, examine:
- the title and purpose of the graph;
- the variables represented;
- the units of measurement;
- the scale of each axis;
- patterns and trends;
- differences between groups;
- unusually high or low observations;
- clusters or concentrations;
- possible relationships between variables; and
- limitations of the graphical representation.
Example of Interpretation:
Suppose a scatter plot shows study hours on the horizontal axis and examination scores on the vertical axis. If the observations generally rise from left to right, a reasonable interpretation is:
“The data show a positive association between study hours and examination scores. Students with higher recorded study hours tend to have higher scores in this dataset.”
This statement is preferable to claiming that studying more hours necessarily causes higher scores, because other variables may influence examination performance.
9.14 Common Mistakes in Data Plotting
- Using the wrong graph: A histogram should not automatically be replaced by a bar chart simply because both contain rectangular bars.
- Missing axis labels: A graph without meaningful labels can be difficult to interpret.
- Ignoring units: A graph should make the measurement units clear where they are relevant.
- Misleading scales: Poorly selected axis limits can exaggerate or hide differences.
- Overloading a graph: Too many categories, labels or visual elements can make the graph difficult to read.
- Confusing association with causation: A visible relationship between variables does not automatically establish cause and effect.
- Using a pie chart for too many categories: Many small sectors are difficult to compare accurately.
- Ignoring outliers: An unusual observation may contain important information about the data.
Key Points:
- Data display makes information easier to inspect and communicate.
- R can display objects directly in the console.
print(),head()andsummary()are useful for inspecting data.- The
plot()function provides a general mechanism for creating basic graphics. - Bar plots are useful for comparing categories.
- Histograms show the distribution of numerical data.
- Line graphs are useful for ordered observations and trends.
- Scatter plots show the relationship between two numerical variables.
- Pie charts can show parts of a whole when there are relatively few categories.
- Good graphs require appropriate labels, scales and graphical forms.
- A graph must be interpreted carefully rather than merely observed.
9.15 Chapter Summary
Displaying and plotting data are essential parts of data analysis. R provides functions for displaying individual objects, inspecting datasets and creating graphical representations.
The basic plot() function can be used to represent relationships between variables, while specialised functions such as barplot(), hist() and pie() provide specific graphical forms. The choice of plot should depend on the structure of the data and the analytical question.
A well-designed graph should communicate information clearly, accurately and efficiently. The data scientist must therefore understand not only how to produce a graph in R, but also how to select an appropriate representation and interpret the resulting visual evidence correctly.
CHAPTER TEN
Dates and Times
10.1 Dates in R
R provides the Date class for representing calendar dates. A date can be created using the as.Date() function.
date1 <- as.Date("2026-08-24")
date1
The standard representation is:
For example:
- 2026-01-15 represents 15 January 2026.
- 2026-08-24 represents 24 August 2026.
- 2027-12-31 represents 31 December 2027.
R internally stores a Date value differently from how it displays the value. This allows arithmetic and comparison operations to be performed correctly.
10.2 Checking the Class of a Date
The class() function can be used to determine how R is treating an object.
date1 <- as.Date("2026-08-24")
class(date1)
The result identifies the object as belonging to the Date class.
This is important because a value that merely looks like a date may actually be stored as character data.
text_date <- "2026-08-24"
class(text_date)
In this case, R treats the value as character data rather than as a date.
10.3 Converting Character Values to Dates
Suppose dates are stored as character values:
dates <- c("2026-01-10", "2026-02-15", "2026-03-20")
dates
They can be converted to Date objects using:
dates <- as.Date(dates)
dates
Once converted, R can perform operations such as sorting, comparison and subtraction.
10.4 Working with Different Date Formats
Dates are not always supplied in the ISO format YYYY-MM-DD. For example, a dataset may contain:
"24/08/2026"
The format must be specified when converting such a value.
date2 <- as.Date("24/08/2026", format = "%d/%m/%Y")
date2
Important format codes include:
| Code | Meaning | Example |
|---|---|---|
%Y | Four-digit year | 2026 |
%m | Month as number | 08 |
%d | Day of month | 24 |
%H | Hour | 14 |
%M | Minute | 30 |
%S | Second | 45 |
10.5 Date Arithmetic
One of the important advantages of using genuine date objects is that R can perform arithmetic involving dates.
Adding Days:
date1 <- as.Date("2026-08-24")
date1 + 7
This produces the date seven days after 24 August 2026.
Subtracting Days:
date1 - 10
This produces the date ten days before 24 August 2026.
Finding the Difference Between Dates:
start <- as.Date("2026-08-01")
end <- as.Date("2026-08-24")
end - start
The result represents the number of days between the two dates.
10.6 Comparing Dates
Date objects can be compared using ordinary comparison operators.
date_a <- as.Date("2026-05-10")
date_b <- as.Date("2026-08-24")
date_a < date_b
date_a > date_b
date_a == date_b
The results are logical values: TRUE or FALSE.
This is useful when filtering datasets. For example, an analyst may want to identify all records occurring after a particular date.
10.7 Times in R
Dates identify calendar days, whereas times identify positions within a day. When both are required, R provides date-time classes.
A date-time may contain information such as:
This represents 24 August 2026 at 14:30:45.
The as.POSIXct() function is commonly used to create date-time objects.
datetime <- as.POSIXct(
"2026-08-24 14:30:45"
)
datetime
10.8 POSIXct and POSIXlt
R provides two important date-time representations: POSIXct and POSIXlt.
POSIXct:
POSIXct represents a date-time as an amount of elapsed time from an origin. It is convenient for calculations, comparisons and storing date-time values.
time1 <- as.POSIXct("2026-08-24 14:30:00")
class(time1)
POSIXlt:
POSIXlt represents a date-time in terms of its individual components, such as seconds, minutes, hours, day and month. It can be useful when individual components need to be accessed.
Conversion can be performed with:
time2 <- as.POSIXlt(time1)
time2
For introductory R programming, it is sufficient to understand that both classes represent date-time information but organise that information differently.
10.9 Extracting Date and Time Components
Date-time values contain several components that may be useful during analysis. These include the year, month, day, hour, minute and second.
For example:
datetime <- as.POSIXlt("2026-08-24 14:35:20")
datetime$year
datetime$mon
datetime$mday
datetime$hour
datetime$min
datetime$sec
The POSIXlt components use zero-based conventions for some fields. For example, the month component ranges from 0 for January to 11 for December, while the year component represents the number of years since 1900.
POSIXlt is represented exactly as a human-readable calendar value. In particular, the month and year components require careful interpretation.
10.10 Formatting Dates and Times
Formatting changes how a date or time is displayed without necessarily changing the underlying date-time information.
The format() function can be used to produce a particular display.
date1 <- as.Date("2026-08-24")
format(date1, "%d/%m/%Y")
This produces a day/month/year representation.
A date-time can also be formatted:
datetime <- as.POSIXct("2026-08-24 14:35:20")
format(datetime, "%d/%m/%Y %H:%M:%S")
Formatting is particularly useful when preparing analytical results for reports, tables or user interfaces.
10.11 Time Zones
A date-time may depend on the time zone in which an event is observed. For example, the same moment can have different local clock readings in Lagos, London, New York or Tokyo.
R allows a time zone to be specified when working with date-time values.
datetime <- as.POSIXct(
"2026-08-24 14:30:00",
tz = "Africa/Lagos"
)
datetime
Time-zone handling is particularly important in international applications, distributed systems, financial transactions, web applications and datasets collected from multiple geographical locations.
10.12 Date-Time Arithmetic
Date-time values can also be used in calculations.
Example: Adding Time:
start_time <- as.POSIXct("2026-08-24 10:00:00")
start_time + 3600
Here, 3,600 seconds are added, representing one hour.
Example: Finding Elapsed Time:
start_time <- as.POSIXct("2026-08-24 09:15:00")
end_time <- as.POSIXct("2026-08-24 11:45:00")
end_time - start_time
The difference represents the elapsed time between the two observations.
10.13 Dates and Times in Data Analysis
Date and time operations become especially important when working with real datasets.
Consider a dataset containing customer transactions:
transactions <- data.frame(
customer = c("A", "B", "C"),
date = as.Date(c(
"2026-08-01",
"2026-08-05",
"2026-08-12"
)),
amount = c(500, 750, 620)
)
transactions
Once the date column is stored correctly, the analyst can answer questions such as:
- Which transactions occurred after a specified date?
- How many days elapsed between transactions?
- What was the total value of transactions during a particular period?
- Which month had the highest transaction activity?
- How long did customers remain active?
This illustrates why dates and times are not merely formatting concerns. They are analytical variables that can be used to filter, group, compare and model data.
10.14 Common Mistakes with Dates and Times
- Keeping dates as character values: Character strings may look like dates but do not automatically support reliable date arithmetic.
- Using the wrong format: A value such as
24/08/2026requires the appropriate format specification. - Confusing date and date-time objects: A calendar date does not contain the same information as a complete date-time.
- Ignoring time zones: Date-time calculations involving international data may be incorrect if time zones are not considered.
- Misinterpreting POSIXlt components: Some components use conventions different from ordinary human calendar notation.
- Confusing display format with data type: Formatting a date changes how it appears; it does not necessarily change the underlying date representation.
- Performing arithmetic on unconverted text: Arithmetic should be performed on appropriate date or date-time objects rather than raw character strings.
Key Points:
- R provides specialised classes for handling dates and date-time values.
- The
Dateclass is used for calendar dates. as.Date()converts appropriate values into dates.- Date formats can be specified using format codes such as
%Y,%mand%d. - Dates can be compared and used in arithmetic operations.
- Date-time values combine calendar dates with times of day.
POSIXctandPOSIXltare important R date-time representations.- Date-time components can include year, month, day, hour, minute and second.
- The
format()function is useful for controlling the displayed representation of dates and times. - Time zones are important when working with date-time data across geographical regions.
- Correct date-time representation is essential for reliable data analysis.
10.15 Chapter Summary
Dates and times are fundamental components of many real-world datasets. R provides specialised data structures that allow dates and times to be stored, compared, manipulated and displayed correctly.
The Date class is suitable for calendar dates, while POSIXct and POSIXlt provide mechanisms for working with date-time information. Functions such as as.Date(), as.POSIXct(), format() and class() are useful for creating, inspecting and displaying date-time objects.
Date arithmetic allows analysts to determine elapsed periods, compare events and perform time-based filtering. In practical data science, correct handling of dates and times is essential for analysing transactions, experiments, sensor readings, events, financial records and other time-dependent information.
CHAPTER ELEVEN
Control Structures and Looping
11.1 Program Execution and Control Flow
When a simple R program is written as a sequence of statements, R normally evaluates the statements from top to bottom. This is called sequential execution.
x <- 10 y <- 5 z <- x + y print(z)
The statements are evaluated in order. However, practical programs frequently require a different execution pattern. A program may need to:
- execute an instruction only when a condition is satisfied;
- choose one operation from several alternatives;
- repeat an operation several times;
- stop a repetition when a particular condition occurs; or
- skip certain values while continuing to process others.
These requirements are handled through control structures.
| Control pattern | Purpose | Typical R construct |
|---|---|---|
| Sequence | Execute statements in order | Ordinary R statements |
| Selection | Choose which statements to execute | if, else, ifelse() |
| Iteration | Repeat statements | for, while, repeat |
11.2 Conditional Execution
A conditional statement evaluates a logical condition and uses its result to determine what should happen next.
In R, logical expressions generally produce TRUE, FALSE, or sometimes NA. Common comparison operators include:
| Operator | Meaning | Example |
|---|---|---|
== | Equal to | x == 10 |
!= | Not equal to | x != 10 |
> | Greater than | x > 10 |
< | Less than | x < 10 |
>= | Greater than or equal to | x >= 10 |
<= | Less than or equal to | x <= 10 |
== tests equality, while = is generally used for argument matching or assignment in some contexts. Do not use = when you mean to ask whether two values are equal.
11.3 The if Statement
The if statement executes a block of code only when its condition is TRUE.
The general structure is:
if (condition) {
statement
}
Example 1: Checking a Value:
age <- 20
if (age >= 18) {
print("Adult")
}
Since age >= 18 evaluates to TRUE, the message is printed.
Example 2: Checking a Data Value:
score <- 75
if (score >= 50) {
print("Pass")
}
If score is 75, the condition is true and R executes the statement inside the braces.
11.4 The if...else Statement
An if...else statement provides two alternative paths. If the condition is true, the first block is executed; otherwise, the second block is executed.
if (condition) {
statement_if_true
} else {
statement_if_false
}
Worked Example: Pass or Fail:
Question: Write an R program that determines whether a student has passed when the pass mark is 50.
Solution:
score <- 62
if (score >= 50) {
print("Pass")
} else {
print("Fail")
}
The condition is:
62 >= 50
which evaluates to TRUE. Therefore, the output is Pass.
Multiple Conditions:
Several alternatives can be represented using else if.
score <- 78
if (score >= 70) {
grade <- "A"
} else if (score >= 60) {
grade <- "B"
} else if (score >= 50) {
grade <- "C"
} else {
grade <- "F"
}
print(grade)
Because 78 is greater than or equal to 70, the first condition is satisfied and the resulting grade is "A".
11.5 Logical Operators in Conditions
Conditions can be combined using logical operators.
| Operator | Meaning | Example |
|---|---|---|
&& | Element-wise logical AND for scalar-style conditions | x > 0 && x < 100 |
|| | Element-wise logical OR for scalar-style conditions | x < 0 || x > 100 |
! | Logical NOT | !is.na(x) |
& | Element-wise AND | x > 0 & x < 100 |
| | Element-wise OR | x < 0 | x > 100 |
For a single value, && and || are commonly used in conditional logic. For vectors, & and | operate element by element.
age <- 25
if (age >= 18 && age <= 60) {
print("Working-age range")
}
11.6 Vectorised Conditional Operations with ifelse()
R is designed to work efficiently with vectors. The ifelse() function is especially useful when the same conditional rule must be applied to many values.
Its basic form is:
ifelse(test, yes, no)
For each element, R evaluates test. If it is true, the corresponding value from yes is selected; otherwise, the corresponding value from no is selected.
Example:
scores <- c(35, 48, 52, 67, 81) result <- ifelse(scores >= 50, "Pass", "Fail") print(result)
The result is a vector containing:
"Fail" "Fail" "Pass" "Pass" "Pass"
This demonstrates an important feature of R: the same operation can be applied to an entire vector without writing a separate loop.
11.7 Introduction to Loops
A loop repeatedly executes a block of statements. Loops are useful when the same operation must be performed for multiple values or until a particular condition is reached.
For example, suppose a vector contains the examination scores of 1,000 students and a program must examine each score. Writing 1,000 separate statements would be impractical. A loop can process the values systematically.
R provides three principal looping constructs:
forwhilerepeat
11.8 The for Loop
A for loop is appropriate when a programmer wants to iterate over the elements of a vector, sequence or other object for a known set of values.
The general structure is:
for (variable in sequence) {
statements
}
Example 1: Printing Numbers:
for (i in 1:5) {
print(i)
}
The loop executes five times. The value of i is successively 1, 2, 3, 4 and 5.
Example 2: Processing a Vector:
scores <- c(45, 72, 61, 39, 85)
for (score in scores) {
print(score)
}
The loop visits each element of scores in sequence.
Example 3: Calculating Squares:
for (i in 1:5) {
print(i^2)
}
The values produced are 1, 4, 9, 16 and 25.
11.9 Worked Example: Calculating a Total with a Loop
Question: Use a for loop to calculate the total of the values 10, 20, 30 and 40.
Given:
x = c(10, 20, 30, 40)
Required: The sum of all values.
Solution:
x <- c(10, 20, 30, 40)
total <- 0
for (value in x) {
total <- total + value
}
print(total)
The calculation proceeds as follows:
| Iteration | Value | Running total |
|---|---|---|
| 1 | 10 | 10 |
| 2 | 20 | 30 |
| 3 | 30 | 60 |
| 4 | 40 | 100 |
Answer: The total is 100.
In practice, R already provides the vectorised sum() function, so sum(x) would normally be preferred. The loop is presented here to demonstrate the mechanics of iteration.
11.10 The while Loop
A while loop repeatedly executes its body while a condition remains true.
The general form is:
while (condition) {
statements
}
The condition is tested before each iteration. If it is false before the loop begins, the loop body may execute zero times.
Example:
x <- 1
while (x <= 5) {
print(x)
x <- x + 1
}
The value of x begins at 1. After each iteration it is increased by 1. When x becomes 6, the condition x <= 5 becomes false and the loop terminates.
while loop can continue indefinitely if its condition never becomes false. Always ensure that the variables controlling the condition can change appropriately.
11.11 The repeat Loop
The repeat loop executes its body repeatedly until it is explicitly terminated, normally with a break statement.
Its basic structure is:
repeat {
statements
if (condition) {
break
}
}
Example:
x <- 1
repeat {
print(x)
x <- x + 1
if (x > 5) {
break
}
}
The loop prints 1 through 5 and then terminates when the stopping condition becomes true.
The repeat structure is useful when the number of iterations is not known beforehand and the termination condition is naturally expressed inside the loop.
11.12 Comparing the Three Main Loops
| Loop | How it works | Typical use |
|---|---|---|
for |
Iterates over a specified sequence | Processing each element of a vector or known sequence |
while |
Continues while a condition is true | When continuation depends on a condition |
repeat |
Repeats until explicitly stopped | When the stopping condition naturally occurs inside the loop |
11.13 The break Statement
The break statement immediately terminates the current loop. Program execution continues with the statement following the loop.
Example:
for (i in 1:10) {
if (i == 6) {
break
}
print(i)
}
The numbers 1 through 5 are printed. When i becomes 6, the condition is true and break terminates the loop.
11.14 The next Statement
The next statement skips the remainder of the current iteration and immediately proceeds to the next iteration.
Example: Skip an Invalid Value:
values <- c(10, 20, -5, 30, 40)
for (x in values) {
if (x < 0) {
next
}
print(x)
}
The negative value -5 is skipped, while the other values are printed.
This can be useful when processing data and certain observations should be ignored without terminating the entire operation.
11.15 Nested Control Structures
A control structure can be placed inside another control structure. Such structures are called nested control structures.
Example: Conditional Logic Inside a Loop:
scores <- c(45, 72, 61, 39, 85)
for (score in scores) {
if (score >= 50) {
print("Pass")
} else {
print("Fail")
}
}
The loop processes each score, while the conditional statement determines the result for that score.
Nesting is powerful, but excessive nesting can make programs difficult to understand. Where possible, a complex operation should be divided into smaller functions, a topic examined in the next chapter.
11.16 Practical Application: Finding the Largest Value
Consider a simple problem in which the largest value in a vector must be found using a loop.
Question: Find the largest value in c(14, 7, 29, 18, 23) using a for loop.
Solution:
values <- c(14, 7, 29, 18, 23)
largest <- values[1]
for (x in values) {
if (x > largest) {
largest <- x
}
}
print(largest)
The algorithm begins by assuming that the first value is the largest. Each subsequent value is compared with the current largest value. Whenever a larger value is found, largest is updated.
The sequence of comparisons is:
- 14 becomes the initial largest value.
- 7 is not greater than 14.
- 29 is greater than 14, so largest becomes 29.
- 18 is not greater than 29.
- 23 is not greater than 29.
Answer: The largest value is 29.
R also provides the vectorised max() function, so max(values) would normally be used in practical code. The loop demonstrates how the underlying reasoning works.
11.17 Practical Application: Counting Values That Meet a Condition
Question: Count how many values in c(45, 72, 61, 39, 85, 50) are at least 50.
Solution:
scores <- c(45, 72, 61, 39, 85, 50)
count <- 0
for (score in scores) {
if (score >= 50) {
count <- count + 1
}
}
print(count)
The values satisfying the condition are 72, 61, 85 and 50.
Answer: There are 4 values that are at least 50.
Again, R provides a more direct approach, such as sum(scores >= 50), because logical values can be used in numerical calculations. However, the loop is valuable for understanding the underlying procedure.
11.18 Looping and Vectorisation in R
R differs from many introductory programming environments because it is designed heavily around vectorised computation. A task that could be implemented with a loop may often be expressed more concisely using a vectorised function.
For example:
x <- c(1, 2, 3, 4, 5) x_squared <- x^2
There is no explicit loop in the code, yet every element is squared.
Similarly:
x <- c(10, 20, 30, 40) sum(x)
is preferable to manually writing a loop to calculate the sum.
This does not mean loops are unimportant. Loops remain useful when:
- the operation is sequential or state-dependent;
- each iteration requires different logic;
- a suitable vectorised function is unavailable;
- the programmer needs explicit control over iteration; or
- the loop improves the clarity of a particular algorithm.
11.20 Key Points
- Control structures determine the order in which program statements are executed.
- Sequential execution follows the normal order of statements.
- Conditional structures allow programs to make decisions.
- The
ifstatement executes code when a condition is true. - The
if...elsestructure provides alternative execution paths. ifelse()applies a conditional rule efficiently to vector elements.- The
forloop iterates over a specified sequence. - The
whileloop continues while its condition remains true. - The
repeatloop continues until explicitly terminated. breakterminates a loop.nextskips the current iteration and continues with the next one.- Control structures can be nested to solve more complex problems.
- R's vectorised operations can often replace simple loops.
- Loops remain important when explicit iteration and conditional processing are required.
11.21 Chapter Summary
Control structures provide the mechanism through which an R program determines what to execute, when to execute it, and how many times to execute it. Without control structures, programs would be restricted largely to fixed sequences of operations.
Conditional structures such as if, if...else and ifelse() allow programs to respond to logical conditions. This is particularly important in data processing, where different observations may need to be treated differently.
Loops provide repetition. The for loop is useful when processing a known sequence of values, while while is appropriate when repetition depends on a condition. The repeat construct provides a loop whose termination condition is explicitly controlled from within the loop.
The break and next statements provide additional control over iteration. Finally, because R is a vector-oriented language, programmers should recognize when vectorised operations provide a simpler alternative to explicit loops.
11.22 Further Practice
Basic Exercises:
- Write an R statement that prints
"Positive"when a number is greater than zero. - Write an
if...elsestatement that determines whether a number is even or odd. - Use a
forloop to print the numbers from 1 to 10. - Use a
whileloop to print the numbers from 10 down to 1. - Use
ifelse()to classify the valuesc(12, 4, 18, 7, 21)as either"High"when they are at least 10 or"Low"otherwise.
Intermediate Exercises:
- Write a program that assigns grades A, B, C, D or F based on a student's score.
- Write a
forloop that calculates the product of the values inc(2, 3, 4, 5). - Write a loop that counts the number of even values in
c(12, 7, 8, 15, 20, 21, 30). - Write a program that prints only the positive values in
c(-4, 7, -2, 9, 12, -8). - Use
breakto stop a loop when the value 7 is reached.
Applied Exercises:
- A vector contains examination scores. Write an R program that calculates the number of students who passed and the number who failed.
- Write a program that examines a vector of temperatures and reports whether each value is below, within, or above a specified acceptable range.
- Write a loop that determines the smallest value in a numeric vector without using
min(). - Write a program that calculates the average of a vector using a loop rather than
mean(). - Write a program that processes a vector of transaction amounts and skips negative values using
next.
Higher-Order Thinking:
- Explain why vectorisation is often preferable to an explicit loop in R.
- Compare
forandwhileloops and provide a situation in which each is appropriate. - Design a program that repeatedly accepts or processes values until a specified stopping condition is reached. Explain why a
whileorrepeatloop is appropriate. - Explain the difference between terminating a loop with
breakand skipping an iteration withnext. - A program contains a loop that never terminates. Explain at least two possible causes and describe how you would diagnose the problem.
CHAPTER TWELVE
Functions and Scoping Rules
Chapter Introduction
As R programs become larger, writing every operation as a separate sequence of commands quickly becomes inefficient. A well-designed program therefore divides a complex task into smaller, reusable units. In R, one of the principal mechanisms for achieving this is the function.
A function is a named piece of R code designed to perform a particular task. It can receive input through arguments, perform calculations or other operations, and return a result. Functions make programs easier to organize, test, reuse and maintain.
This chapter also introduces scoping, which explains how R determines which object a name refers to when the same name exists in different environments. Understanding functions and scoping is essential for writing reliable R programs and for understanding how existing R functions behave.
12.1 Understanding Functions in R
A function is an object in R that contains instructions for carrying out an operation. R provides many built-in functions, such as mean(), sum(), length(), sqrt() and print(). Programmers can also create their own functions.
The basic structure of a user-defined function is:
function_name <- function(argument1, argument2) {
statements
result
}
The name of the function appears on the left side of the assignment operator. The keyword function tells R that a function is being created. The arguments are placed inside parentheses, while the statements that make up the function body are enclosed in braces.
Example 1: A Simple Function:
greet <- function() {
print("Welcome to R programming")
}
greet()
The first statement creates a function named greet. The second statement calls the function. Because the function has no arguments, empty parentheses are used when it is called.
12.2 Why Functions Are Important
Functions are fundamental to good programming because they allow a programmer to divide a large problem into smaller, manageable tasks.
- Reusability: A function can be called many times without rewriting its instructions.
- Modularity: A complex program can be divided into logical components.
- Readability: Meaningful function names make programs easier to understand.
- Maintainability: A function can be modified without rewriting every part of the program that uses it.
- Testing: Individual functions can be tested separately.
- Abstraction: A programmer can use a function without needing to know every detail of its internal implementation.
Example 2: Reusing a Function:
square <- function(x) {
x^2
}
square(4)
square(7)
square(10)
The function performs the same operation for different values. Instead of writing 4^2, 7^2 and 10^2 separately, the operation is defined once and reused.
12.3 Function Arguments
An argument is information supplied to a function when the function is called. Arguments allow a function to operate on different data.
Positional Arguments:
R normally matches supplied arguments to function parameters according to their position.
add_numbers <- function(a, b) {
a + b
}
add_numbers(8, 5)
Here, a receives 8 and b receives 5. The result is 13.
Named Arguments:
Arguments may also be supplied by explicitly specifying their parameter names.
add_numbers(a = 8, b = 5)
add_numbers(b = 5, a = 8)
Named arguments make function calls clearer and allow arguments to be supplied in a different order.
Default Arguments:
A function parameter may have a default value. If the caller does not provide a value, R uses the default.
power_number <- function(x, power = 2) {
x^power
}
power_number(5)
power_number(5, 3)
The first call uses the default value power = 2, producing 25. The second call supplies 3, producing 125.
12.4 Returning Values from Functions
A function normally produces a result. In R, the value of the last evaluated expression in a function is returned automatically unless an explicit return() statement is used.
Implicit Return:
multiply <- function(x, y) {
x * y
}
result <- multiply(6, 4)
result
The expression x * y is the final expression in the function, so its value is returned automatically. The variable result therefore contains 24.
Explicit Return:
absolute_value <- function(x) {
if (x < 0) {
return(-x)
}
return(x)
}
The return() statement immediately returns the specified value and stops evaluation of the function body at that point.
return(). In R, implicit return of the final expression is common and idiomatic.
12.5 Functions with Multiple Operations
A function can contain several statements and perform a sequence of related operations before producing a result.
Example 3: Calculating an Average:
calculate_average <- function(x) {
total <- sum(x)
n <- length(x)
average <- total / n
average
}
scores <- c(70, 65, 80, 75, 90)
calculate_average(scores)
The function first calculates the total, determines the number of observations, calculates the average and then returns the average. The result is 76.
This example illustrates an important programming principle: a function should normally have a clear purpose. A function named calculate_average() should perform operations directly related to calculating an average.
12.6 Functions and Vectorised Operations
R is designed to work efficiently with vectors. A function can therefore often operate on an entire vector rather than requiring a loop for every element.
double_values <- function(x) {
x * 2
}
numbers <- c(2, 4, 6, 8)
double_values(numbers)
The result is 4, 8, 12, 16. The multiplication is applied element by element because multiplication is vectorised in R.
This feature is particularly important in Data Science because datasets commonly contain many observations that must be transformed or analysed efficiently.
12.7 Local Variables and Function Scope
One of the most important ideas associated with functions is scope. Scope determines where a name can be found and which object a name refers to.
Consider the following example:
calculate <- function(x) {
result <- x * 10
result
}
calculate(5)
The name result is created inside the function. It is therefore a local variable associated with the function's evaluation environment.
In ordinary use, an object created inside a function is not automatically available as a variable in the global environment after the function finishes.
Example 4: Local Variable:
calculate <- function(x) {
answer <- x + 20
answer
}
calculate(10)
The function returns 30. However, answer is not thereby created as an ordinary global variable.
12.8 Global Variables
A variable created in the global environment can be accessed by expressions evaluated there and may also be found by a function when R searches for a name.
tax_rate <- 0.075
calculate_tax <- function(amount) {
amount * tax_rate
}
calculate_tax(100000)
The function does not contain a local object named tax_rate. R therefore searches for the name in the appropriate enclosing environment and can find the global object. The result is 7,500.
12.9 Lexical Scoping in R
R primarily uses lexical scoping, also called static scoping. This means that the environment in which a function was created influences where R searches for names used by that function.
When R evaluates an expression containing a name, it searches environments according to its scoping rules. If the name is not found in the current environment, R continues searching in an enclosing environment.
Example 5: Searching for a Name:
x <- 10
my_function <- function() {
x
}
my_function()
The function does not define x locally. R therefore searches an enclosing environment and finds the global value 10.
Now consider a local variable with the same name:
x <- 10
my_function <- function() {
x <- 50
x
}
my_function()
The function returns 50 because the local x takes precedence over the global x during evaluation inside the function.
12.10 Environments and Functions
An environment is a structure that associates names with objects. Environments are central to R's scoping system.
Every function has an associated environment. When a function is created, R records the environment in which it was created. This information helps R resolve names when the function is later executed.
The concept can be represented simply as:
This relationship is one reason R can support advanced programming techniques such as nested functions, closures and functional programming.
12.11 Name Masking
Name masking occurs when objects with the same name exist in different environments. The object found first during R's search takes precedence.
x <- 100
test <- function() {
x <- 25
x
}
test()
The result is 25, because the local x masks the global x within the function.
Name masking is not necessarily an error. It becomes problematic when programmers do not understand which object R is using.
12.12 Nested Functions
A function can be defined inside another function. Such functions are called nested functions.
outer_function <- function(x) {
inner_function <- function(y) {
y * 2
}
inner_function(x) + 5
}
outer_function(10)
The inner function receives 10, multiplies it by 2 to produce 20, and the outer function adds 5. The final result is 25.
Nested functions are useful when a helper operation is needed only within a larger function.
12.13 Functions as Objects
In R, functions are objects. This means that functions can be stored in variables, passed to other functions and returned by functions.
Example 6: Storing a Function:
operation <- function(x) {
x^2
}
operation(6)
The object named operation refers to a function. The function can therefore be called using operation(6).
This property contributes to R's support for functional programming, in which functions can be treated as data and passed around as computational objects.
12.14 Passing Functions to Other Functions
Because functions are objects, one function can receive another function as an argument.
apply_operation <- function(x, operation) {
operation(x)
}
square <- function(x) {
x^2
}
apply_operation(5, square)
The function apply_operation() receives both a value and another function. It then applies the supplied function to the value. The result is 25.
This principle is widely used in R's data-processing functions, including functions that apply operations to vectors, lists and other data structures.
12.15 The ... Argument
R functions can use ..., pronounced “dot-dot-dot”, to accept a variable number of additional arguments.
For example, many built-in R functions use ... to allow additional options to be passed to another function.
show_values <- function(x, ...) {
print(x, ...)
}
The use of ... is particularly useful when designing wrapper functions that should pass additional arguments to another function.
12.16 Functions and Data Science
Functions are particularly important in Data Science because data-processing workflows often involve repeated operations. A data scientist may need to clean a variable, calculate a statistic, transform a dataset, evaluate a model or generate a result repeatedly.
Example 7: Data Transformation Function:
standardize <- function(x) {
(x - mean(x)) / sd(x)
}
scores <- c(60, 70, 80, 90, 100)
standardize(scores)
The function transforms the values by subtracting their mean and dividing by their standard deviation. This is an example of how a reusable function can encapsulate a data-preparation procedure.
Functions therefore provide a bridge between basic R programming and larger Data Science workflows. Instead of repeatedly entering individual commands, a programmer can create well-defined functions that implement reusable analytical procedures.
12.17 Common Function Errors and Mistakes
Mistake 1: Forgetting to Call the Function:
square <- function(x) {
x^2
}
square
The statement square refers to the function object itself. To execute it, an argument must be supplied:
square(5)
Mistake 2: Using an Undefined Object:
calculate <- function(x) {
x + y
}
If y cannot be found in the function's environment chain, R produces an error indicating that the object cannot be found.
Mistake 3: Incorrect Number of Arguments:
add <- function(a, b) {
a + b
}
add(5)
The function requires both a and b. Supplying only one argument normally results in an error because the required value for b is missing.
Mistake 4: Confusing Local and Global Variables:
A common programming mistake is assuming that a variable created inside a function automatically changes a similarly named variable outside the function. Ordinary local assignment does not work this way.
12.18 Good Practices for Writing R Functions
- Give functions clear and meaningful names.
- Design each function around a clearly defined task.
- Use arguments to provide the information the function needs.
- Prefer self-contained functions that do not depend unnecessarily on global variables.
- Use default arguments when a sensible default exists.
- Keep functions reasonably small and focused.
- Use comments where the purpose or logic is not immediately obvious.
- Test functions using ordinary, boundary and unexpected inputs.
- Use vectorised operations where appropriate.
- Choose names that make the purpose of the function apparent.
12.19 Worked Examples
Worked Example 1: Calculate the Area of a Circle:
Question: Create an R function that calculates the area of a circle.
Given: Radius r.
Required: Area.
Formula:
A = πr²
circle_area <- function(r) {
pi * r^2
}
circle_area(5)
For r = 5:
A = π(5²) = 25π ≈ 78.54
Answer: The area is approximately 78.54 square units.
Worked Example 2: Classifying a Score:
Question: Create a function that receives a student's score and returns “Pass” if the score is at least 50 and “Fail” otherwise.
classify_score <- function(score) {
if (score >= 50) {
"Pass"
} else {
"Fail"
}
}
classify_score(72)
classify_score(43)
The first call returns “Pass”, while the second returns “Fail”. This example demonstrates how functions can combine arguments, conditional statements and returned values.
12.20 Key Points
- A function is a reusable unit of R code designed to perform a particular task.
- User-defined functions are created using the
functionkeyword. - A function can receive values through arguments.
- Arguments may be supplied positionally or by name.
- Default arguments provide values when the caller does not supply them.
- R normally returns the value of the final evaluated expression in a function.
- The
return()function can be used when an explicit early return is required. - Functions can operate on vectors and take advantage of R's vectorised operations.
- Variables created inside functions are normally local to the function's evaluation environment.
- R uses lexical scoping to determine where names are searched for.
- Environments associate names with objects and are fundamental to R's scoping mechanism.
- A local object can mask an object with the same name in an enclosing environment.
- Functions are themselves objects in R and can be stored, passed to other functions and returned by functions.
- Well-designed functions improve reusability, readability, testing and maintainability.
Chapter Summary
Functions provide one of the most important mechanisms for organizing R programs. A function encapsulates a sequence of operations and allows those operations to be reused with different inputs. User-defined functions are created using the function keyword and may contain parameters, calculations, conditional statements and other R expressions.
Arguments provide information to functions, while the result of the final evaluated expression is normally returned automatically. Functions may also use explicit return() statements when an immediate return is required. Default and named arguments make function calls more flexible and readable.
Functions also provide the context in which R's scoping rules operate. R uses lexical scoping, meaning that when a name is evaluated, R searches the current environment and then appropriate enclosing environments according to the function's environment structure. Local variables generally take precedence over objects with the same name in enclosing environments.
Understanding environments, local variables, global variables and name masking is therefore essential for understanding how R programs behave. Because functions are objects in R, they can also be passed to other functions, stored in variables and used as building blocks for advanced data-processing techniques.
Core idea to remember: A good R function takes clearly defined inputs, performs a focused operation, and produces a predictable result. Understanding the environment in which that function operates explains how R finds the objects and names used by the function.
CHAPTER THIRTEEN
Coding Standards, Debugging and Profiling
13.1 Chapter Introduction
Writing a program that produces an answer is only part of professional programming. A good R program should also be understandable, maintainable, reproducible and reasonably efficient. As programs become longer and datasets become larger, poorly organized code becomes difficult to understand and errors become harder to locate.
This chapter introduces three important aspects of professional R programming: coding standards, debugging and profiling. Coding standards provide conventions for writing consistent and readable programs. Debugging is the systematic process of locating and correcting errors. Profiling examines how a program uses computational resources so that inefficient sections can be identified and improved.
These skills are particularly important in Data Science because R programs frequently involve data importation, transformation, statistical analysis, visualization and repeated computation. A small programming mistake can therefore affect thousands or millions of observations.
13.2 Coding Standards in R
A coding standard is a collection of rules or conventions that guide how programs are written. Coding standards do not normally change what a program does; instead, they improve the way the program is organized and communicated.
Consider the following two pieces of code:
x<-10
y<-20
z<-x+y
print(z)
The code is valid, but its formatting is not particularly readable. A clearer version is:
x <- 10
y <- 20
z <- x + y
print(z)
The second version uses spaces around operators and separates logical sections of the program. Both programs perform the same calculation, but the second is easier to read and maintain.
Why Coding Standards Matter:
Professional R code is often read by other people, revisited months later or modified to support a new analysis. Consistent coding practices reduce the mental effort required to understand the program.
Good coding standards help to:
- improve readability;
- reduce ambiguity;
- make errors easier to identify;
- simplify collaboration;
- make programs easier to maintain;
- support reproducible analysis; and
- reduce unnecessary duplication and complexity.
13.3 Naming Conventions
Names should communicate the purpose of variables, functions and other objects. A name such as total_sales communicates considerably more information than x.
For example:
total_sales <- 250000
number_of_customers <- 1250
average_sales <- total_sales / number_of_customers
The names make the calculation easier to understand without requiring additional comments.
Good Naming Practices:
- Use meaningful names.
- Use a consistent naming style throughout a program.
- Avoid unnecessarily abbreviated names.
- Avoid names that are difficult to distinguish from one another.
- Use names that describe the data or operation represented by the object.
For example, customer_count is preferable to cc when the meaning of cc is not obvious.
Avoid Ambiguous Names:
a <- 450
b <- 15
c <- a / b
The calculation may be correct, but the purpose of the objects is unclear.
A clearer implementation is:
total_distance <- 450
travel_time <- 15
average_speed <- total_distance / travel_time
The second version makes the relationship between the variables immediately understandable.
13.4 Indentation and Spacing
Indentation is the practice of arranging code so that its logical structure is visually apparent. Although R can execute code without carefully formatted indentation in many situations, proper indentation significantly improves readability.
Consider a conditional statement:
score <- 72
if (score >= 50) {
result <- "Pass"
} else {
result <- "Fail"
}
The indentation makes the statements belonging to the if and else blocks easy to identify.
Spacing around operators also improves readability:
average <- total / count
percentage <- (correct / total) * 100
Readable formatting becomes increasingly important as programs contain nested conditions, loops and functions.
13.5 Comments and Documentation
Comments are explanatory text written within source code for human readers. R ignores comments during normal execution.
A comment begins with the # symbol.
# Calculate the average examination score
average_score <- total_score / number_of_students
Comments should explain information that is not immediately obvious from the code. They should not unnecessarily describe every simple operation.
For example:
# Convert temperature from Celsius to Fahrenheit
fahrenheit <- (celsius * 9 / 5) + 32
This comment provides useful context.
A comment such as the following adds little value:
x <- 10 # Assign 10 to x
The code itself already communicates that information.
Documentation Should Explain Purpose:
For functions, documentation should describe the function's purpose, inputs, outputs and any important assumptions.
# Calculate the percentage of successful cases.
#
# cases_successful: number of successful cases
# total_cases: total number of cases
#
# Returns the percentage as a numeric value.
success_rate <- function(cases_successful, total_cases) {
(cases_successful / total_cases) * 100
}
13.6 Avoiding Unnecessary Complexity
Good code should solve the problem without introducing unnecessary complexity. Complex code is not automatically better code. In many cases, a simple expression is easier to understand, test and debug.
For example:
total <- 10 + 20 + 30
is clearer than creating several unnecessary intermediate objects for a simple calculation.
However, intermediate variables are useful when they improve meaning:
total_sales <- 450000
number_of_customers <- 150
average_sales_per_customer <- total_sales / number_of_customers
Here, the intermediate variables make the calculation easier to understand.
13.7 Common Types of Programming Errors
Errors in R programs can be classified in several ways. Three particularly important categories are syntax errors, runtime errors and logical errors.
| Error Type | Meaning | Typical Result |
|---|---|---|
| Syntax error | The code violates R's grammatical rules. | R cannot correctly interpret the expression. |
| Runtime error | An error occurs while the program is executing. | Execution may stop and R may display an error message. |
| Logical error | The program's reasoning or algorithm is incorrect. | The program runs but produces an incorrect result. |
13.8 Syntax Errors
A syntax error occurs when R cannot interpret an expression because it does not follow the rules of the language.
For example:
if (x > 10 {
print("Large")
}
The opening parenthesis is not correctly matched. R therefore cannot parse the expression correctly.
The corrected version is:
if (x > 10) {
print("Large")
}
Common causes of syntax errors include:
- missing parentheses;
- unmatched braces;
- missing quotation marks;
- incorrect operators;
- incorrect commas;
- misspelled function syntax; and
- incomplete expressions.
13.9 Runtime Errors
A runtime error occurs when R encounters a problem while evaluating an expression.
For example, an operation may require an object that does not exist:
total <- price * quantity
If price or quantity has not been defined, R may produce an error indicating that the object cannot be found.
Another example involves inappropriate input to a function:
sqrt("hello")
The square-root function expects an appropriate numeric input, so this expression does not represent a meaningful numerical calculation.
Warnings and Errors:
An error generally prevents the requested operation from completing normally. A warning may allow execution to continue while informing the programmer that something potentially problematic occurred.
Warnings should not automatically be ignored. They may reveal missing values, inappropriate data or unexpected calculations.
13.10 Logical Errors
A logical error is often more difficult to identify because R may execute the code successfully. The problem is that the program implements the wrong reasoning.
Suppose a programmer wants to calculate an average:
scores <- c(70, 80, 90, 100)
average <- sum(scores) / 3
average
The code executes, but the answer is incorrect because there are four scores, not three.
The correct calculation is:
average <- sum(scores) / length(scores)
average
The important lesson is that a program can be syntactically correct and still be logically wrong.
13.11 The Debugging Process
Debugging should be approached systematically rather than by randomly changing lines of code.
A practical debugging process consists of the following stages:
- Reproduce the problem. Determine exactly what operation causes the unexpected behavior.
- Read the error or warning message. R often provides useful information about what went wrong.
- Identify the relevant section of code. Determine which expression or function produced the problem.
- Inspect the inputs. Check the values, data types and structures involved.
- Test assumptions. Verify that the program is using the values and conditions you expect.
- Locate the cause. Determine the specific statement responsible for the problem.
- Correct the problem. Modify the code appropriately.
- Run the program again. Confirm that the correction solves the original problem.
- Check for side effects. Ensure that the correction has not introduced another problem.
13.12 Reading R Error Messages
Error messages are important debugging information. A programmer should read the message carefully instead of immediately changing code without understanding the problem.
For example:
mean_value <- total / count
If count has not been defined, R can indicate that an object cannot be found.
The correct response is not simply to suppress the message. The programmer should determine:
- Was
countsupposed to exist? - Was it given a different name?
- Was it accidentally deleted?
- Should its value have been calculated earlier?
Debugging therefore requires both technical knowledge and logical reasoning.
13.13 Inspecting Objects During Debugging
One of the simplest debugging techniques is to inspect the objects used by a calculation.
Suppose:
price <- c(100, 150, 200)
quantity <- c(2, 3, 4)
total <- price * quantity
The programmer can inspect the objects directly:
price
quantity
total
length(price)
length(quantity)
str(price)
str(quantity)
Inspection may reveal problems such as unexpected values, incorrect data types or mismatched structures.
13.14 Using traceback()
When an error occurs inside a chain of function calls, it may not be immediately obvious which function caused the problem. The traceback() function can show the sequence of function calls that led to an error.
For example, consider:
calculate_result <- function(x) {
step_one(x)
}
step_one <- function(x) {
step_two(x)
}
step_two <- function(x) {
x + unknown_value
}
calculate_result(10)
If the execution fails because unknown_value does not exist, traceback() can help reveal the function-call sequence that led to the failure.
The idea is conceptually similar to following a chain:
calculate_result() → step_one() → step_two() → error
This is particularly useful when functions call other functions and the source of an error is not immediately visible.
13.15 Debugging Functions with debug()
The debug() function places a function into debugging mode. When the function is subsequently called, R enters an interactive debugging environment that allows the programmer to inspect execution step by step.
Consider:
calculate_average <- function(x) {
total <- sum(x)
count <- length(x)
total / count
}
debug(calculate_average)
calculate_average(c(10, 20, 30))
During debugging, the programmer can inspect variables as execution progresses and determine where an unexpected result occurs.
Debugging mode can be removed using:
undebug(calculate_average)
13.16 Using debugonce()
debugonce() is useful when a function needs to be examined during only its next execution.
debugonce(calculate_average)
calculate_average(c(10, 20, 30))
Unlike persistent debugging with debug(), debugonce() is designed for a single execution. This is convenient when investigating a problem without permanently changing the debugging state of a function.
13.17 The browser() Function
The browser() function allows a programmer to pause execution at a particular point inside a function and inspect the current environment.
For example:
calculate_total <- function(price, quantity) {
total <- price * quantity
browser()
total
}
When the function is executed, R pauses at browser(). The programmer can then inspect objects such as price, quantity and total.
This is useful when the programmer knows approximately where the problem occurs and wants to examine the state of the program at that point.
13.18 Debugging by Checking Intermediate Results
One of the most practical debugging techniques is to inspect intermediate results rather than looking only at the final output.
Consider:
sales <- c(100, 200, 150, 300)
total <- sum(sales)
count <- length(sales)
average <- total / count
A programmer can inspect:
sales
total
count
average
If the final result is wrong, examining each intermediate value can help identify exactly where the calculation departed from expectations.
This technique is especially useful for data-processing programs where one incorrect transformation can affect every subsequent operation.
13.19 A Systematic Debugging Example
Problem:
A programmer wants to calculate the average of three examination scores.
scores <- c(65, 70, 85)
total <- sum(scores)
number <- 2
average <- total / number
average
Step 1: Check the Data:
scores
length(scores)
The result of length(scores) is 3.
Step 2: Check the Intermediate Values:
total
number
The total is correct, but number is incorrectly set to 2.
Step 3: Correct the Logic:
number <- length(scores)
average <- total / number
average
The problem was not a syntax error. R executed the original program successfully. It was a logical error.
13.20 Defensive Programming
Defensive programming involves writing code that anticipates inappropriate or unexpected inputs and responds to them appropriately.
Suppose a function calculates a percentage:
percentage <- function(correct, total) {
(correct / total) * 100
}
The function assumes that total is meaningful and non-zero. A more defensive implementation can explicitly check the input:
percentage <- function(correct, total) {
if (total == 0) {
stop("Total cannot be zero.")
}
(correct / total) * 100
}
Now the function produces a meaningful error message when an invalid denominator is supplied.
Defensive programming is particularly valuable when functions will be reused with data from different sources.
13.21 Testing Before Debugging
Testing and debugging are related but different activities. Testing attempts to determine whether a program behaves correctly under specified conditions. Debugging investigates the cause of an identified problem.
For example, a function that calculates an average should be tested with several types of input:
calculate_average <- function(x) {
sum(x) / length(x)
}
calculate_average(c(10, 20, 30))
calculate_average(c(5))
calculate_average(c(1, 2, 3, 4, 5))
Testing different inputs can reveal assumptions or weaknesses before the function is used in a larger analysis.
13.22 Introduction to Profiling
Debugging focuses primarily on correctness, whereas profiling focuses on performance. A program can produce the correct answer and still be inefficient.
For example, suppose two programs calculate the same result. If one takes 0.2 seconds and the other takes 30 seconds on a large dataset, the second program may require optimization.
Profiling helps answer questions such as:
- Which function consumes the most execution time?
- Which section of code is called repeatedly?
- Where is unnecessary computation occurring?
- Which operations become expensive as the dataset grows?
- Where should optimization efforts be concentrated?
The fundamental principle is simple:
13.23 Execution Time and Performance
One basic measure of program performance is execution time. If a section of code takes a long time to execute, it may be a candidate for optimization.
R provides timing functions such as system.time().
For example:
system.time({
x <- rnorm(1000000)
y <- x^2
mean(y)
})
The expression measures the time required to execute the enclosed operations.
The exact result depends on the computer, operating system, R version and current system workload.
13.24 Understanding system.time()
system.time() reports several measures related to execution. Important components include:
- user CPU time: processor time used by the R process itself;
- system CPU time: processor time spent by the operating system on behalf of the process; and
- elapsed time: the approximate real-world time that passes during execution.
For example:
system.time({
result <- sum(runif(1000000))
})
The returned timing information can be used to compare alternative implementations.
13.25 Profiling Functions with Rprof()
R provides a sampling profiler through Rprof(). It can record information about where execution time is being spent during program execution.
A basic profiling sequence is:
Rprof("profile.out")
# Code to be profiled
result <- some_function()
Rprof(NULL)
The profiling process is started with Rprof() and stopped by calling Rprof(NULL).
The resulting profiling information can then be examined using R's profiling-summary tools, such as summaryRprof().
summaryRprof("profile.out")
Profiling is useful when a program contains several functions and the programmer needs evidence about where the computational cost is concentrated.
13.26 Identifying Bottlenecks
A performance bottleneck is a part of a program that significantly limits overall performance.
Consider a program containing three major stages:
| Stage | Execution Time |
|---|---|
| Data import | 2 seconds |
| Data transformation | 3 seconds |
| Model calculation | 95 seconds |
The model calculation is clearly the dominant source of execution time. Optimizing the two-second data-import stage is unlikely to produce a meaningful improvement in total execution time.
This illustrates an important principle of performance optimization: concentrate effort where the largest performance gains are possible.
13.27 Common Sources of Inefficient R Code
Several programming practices can cause unnecessary computation or memory usage.
Repeated Unnecessary Computation:
Suppose a calculation is performed repeatedly even though its result does not change:
for (i in 1:1000) {
average <- sum(data) / length(data)
print(i)
}
If data never changes inside the loop, the average does not need to be recalculated 1,000 times.
A better approach is:
average <- sum(data) / length(data)
for (i in 1:1000) {
print(i)
}
Unnecessary Loops:
R is designed to work effectively with vectors. Many operations can therefore be performed using vectorised expressions rather than explicit loops.
For example:
x <- 1:5
y <- x^2
This directly squares all elements of x.
The same task could be performed using a loop, but the vectorised form is concise and expresses the intended operation clearly.
13.28 Vectorisation and Performance
Vectorisation refers to performing an operation on a complete vector or set of values rather than manually processing each element with an explicit loop.
For example:
x <- c(1, 2, 3, 4, 5)
y <- x * 10
The multiplication is applied to every element of the vector.
For many data-processing tasks, vectorised operations are preferable because they can be concise and efficient.
However, vectorisation should not be treated as an absolute rule. The appropriate implementation depends on the problem, data structure, algorithm and computational requirements.
13.29 Debugging Versus Profiling
| Aspect | Debugging | Profiling |
|---|---|---|
| Main purpose | Find and correct errors. | Identify performance bottlenecks. |
| Main concern | Correctness. | Efficiency. |
| Typical question | Why is the program producing this result? | Why is the program taking so long? |
| Useful tools | traceback(), debug(), browser(), debugonce() |
system.time(), Rprof(), summaryRprof() |
13.30 A Practical Workflow for Reliable R Programs
A professional R development workflow can combine coding standards, testing, debugging and profiling.
1. Plan
Clearly define the problem and the expected result.
2. Write readable code
Use meaningful names, consistent formatting and appropriate comments.
3. Test
Use representative and boundary inputs.
4. Debug
Investigate errors systematically and verify intermediate results.
5. Validate
Confirm that the program produces results consistent with the intended method.
6. Profile
Measure performance when execution becomes slow or computationally expensive.
7. Optimize
Improve the sections identified by profiling, then test again to confirm that the changes preserve correctness.
13.31 Common Mistakes in R Programming
Mistake 1: Ignoring error messages. Error messages often contain valuable information about the source of a problem.
Mistake 2: Assuming that successful execution means correct output. Logical errors can produce incorrect results without generating an error.
Mistake 3: Using unclear variable names. Poor names make programs harder to understand and debug.
Mistake 4: Writing extremely long functions. Large functions can be difficult to test and debug. Separating logically distinct tasks into smaller functions often improves maintainability.
Mistake 5: Changing many things simultaneously during debugging. If several lines are changed at once, it becomes difficult to determine which change corrected the problem.
Mistake 6: Optimizing without measuring. A programmer may spend significant time improving a section that has little effect on total execution time.
Mistake 7: Ignoring warnings. Warnings can reveal unexpected values, coercion or missing data that may affect results.
Mistake 8: Using loops when a simple vectorised operation is available. This can make data-processing code unnecessarily verbose and, in some cases, slower.
Mistake 9: Writing comments that become inconsistent with the code. Comments should be maintained whenever the code changes.
Mistake 10: Failing to test edge cases. A function that works for ordinary input may fail for empty vectors, missing values, zero denominators or unexpected data types.
13.32 Key Points
- Coding standards promote readable, consistent and maintainable R programs.
- Meaningful names make programs easier to understand and debug.
- Consistent indentation and spacing make program structure clearer.
- Comments should explain important reasoning, assumptions and purpose rather than merely repeat the code.
- Syntax errors occur when code violates the grammatical rules of R.
- Runtime errors occur while expressions are being executed.
- Logical errors cause incorrect results even when the program executes successfully.
- Debugging is the systematic process of locating and correcting errors.
- Intermediate results should be inspected when investigating unexpected output.
traceback()can help identify the sequence of function calls associated with an error.debug()allows a function to be examined during execution.debugonce()provides debugging for a function's next execution.browser()can pause execution at a selected point for inspection.- Defensive programming anticipates invalid or unexpected inputs.
- Testing and debugging are related but serve different purposes.
- Profiling measures program performance and helps locate computational bottlenecks.
system.time()can be used to measure execution time.Rprof()andsummaryRprof()provide tools for profiling R code.- Optimization should be based on measurement rather than guesswork.
- Vectorised operations can provide concise and efficient solutions for many data-processing tasks.
13.33 Chapter Summary
Professional R programming requires more than writing code that executes. Programs should be readable, consistent, maintainable and reliable. Coding standards provide conventions for naming objects, formatting expressions, organizing code and documenting important decisions.
Errors in R programs can be broadly understood as syntax errors, runtime errors and logical errors. Syntax errors prevent R from correctly interpreting code, runtime errors occur during execution, and logical errors produce incorrect results despite successful execution.
Debugging provides a systematic approach to identifying and correcting these problems. Reading error messages, inspecting objects, checking intermediate results and using tools such as traceback(), debug(), debugonce() and browser() can make the debugging process considerably more effective.
Profiling addresses a different question: whether a program is performing efficiently. Tools such as system.time() and Rprof() can reveal where computational resources are being used. Once a bottleneck has been identified, the programmer can consider appropriate improvements such as reducing unnecessary computation, improving algorithms or using vectorised operations.
The central principle of professional R development is therefore a combination of clarity, correctness and efficiency. Clear coding standards make programs understandable; systematic debugging makes them reliable; and profiling helps make them efficient.
CHAPTER FOURTEEN
Creating Data Products Using R Packages
Chapter Introduction
R is widely used not only for individual calculations and statistical analysis but also for developing reusable software, analytical tools, reports, dashboards and other data products. As a data scientist becomes more experienced, the goal shifts from simply writing code that produces an answer to developing code and analytical systems that can be reused, maintained, shared and applied to real-world problems.
R packages provide an important mechanism for organizing and distributing reusable R code. A package can contain functions, datasets, documentation, examples and other resources required to perform a particular set of tasks. Packages therefore form a major part of the R ecosystem and allow developers to build on existing functionality rather than repeatedly writing the same code.
This chapter introduces R packages, explains how they are structured and used, and examines how R can be applied to create practical data products. It also brings together major concepts from the course, showing how R objects, vectors, data frames, functions, control structures, data import, transformation and visualization can work together in a complete data-driven application.
14.1 Understanding R Packages
An R package is a structured collection of files containing R functions, datasets, documentation and other resources that are distributed as a unit. Packages allow programmers and data scientists to organize related functionality and make it available for reuse.
Without packages, programmers would frequently have to place all their functions into individual scripts and manually copy or modify code whenever the same functionality was required elsewhere. Packages provide a more systematic solution.
Think of a Package as a Toolkit:
A useful way to understand an R package is to think of it as a toolkit. Instead of carrying individual tools separately, related tools are organized into one collection. When the toolkit is installed, the programmer can access the tools when needed.
For example, a package designed for data visualization may contain functions for creating charts, modifying graphical elements and working with different types of data. A package designed for statistical modelling may provide functions for fitting models, evaluating them and generating predictions.
14.2 Why R Packages Are Important
Packages are important because they make software development more efficient, organized and reusable.
Code reuse: A function developed once can be reused in many programs.
Organization: Related functions and datasets can be grouped logically.
Collaboration: Packages provide a standard way for developers to share functionality with other users.
Reliability: Widely used packages may benefit from testing, documentation and review by a broader community.
Productivity: Developers can use existing functionality instead of implementing every algorithm from the beginning.
Maintainability: Well-structured packages make it easier to update and maintain software.
Reproducibility: Packaging code and its dependencies can help other users reproduce analytical procedures.
14.3 Base R and Contributed Packages
R includes a collection of fundamental functionality that is available with the standard R installation. This functionality is commonly referred to as base R and includes many fundamental operations involving vectors, matrices, data frames, functions, statistics, graphics and programming structures.
Beyond base R, thousands of additional packages have been developed to extend the language.
| Aspect | Base R | Additional Packages |
|---|---|---|
| Availability | Included with R | Usually installed separately |
| Purpose | Core programming and statistical functionality | Specialized or extended functionality |
| Examples of uses | Basic calculations, data structures, functions and graphics | Advanced visualization, data manipulation, modelling and applications |
The distinction does not mean that contributed packages are separate from R. They extend the capabilities of the R programming environment.
14.4 Installing and Loading Packages
Before a package can normally be used, it must be installed. Installation downloads the package and makes it available in the user's R environment.
A commonly used command for installing a package is:
install.packages("ggplot2")
After installation, the package is normally loaded into the current R session using library():
library(ggplot2)
Installation and loading are therefore different operations.
Important distinction: install.packages() generally installs the package on the computer. library() makes the installed package available for use in the current R session.
For example:
# Install once, when necessary
install.packages("ggplot2")
# Load when using the package
library(ggplot2)
A package usually does not need to be installed every time R is started. However, it normally needs to be loaded into a new R session when its functions are required.
14.5 Using Functions from Packages
Once a package has been loaded, its exported functions can normally be called directly.
For example, after loading a package that provides a particular function, the function can be used according to its documented syntax.
R also allows a function to be accessed explicitly using the double-colon operator:
package_name::function_name()
For example:
ggplot2::ggplot()
This form makes it clear which package supplies the function and can be useful when different packages contain functions with similar names.
14.6 Common Package Management Operations
Several R commands are useful when working with packages.
| Command | Purpose |
|---|---|
install.packages() |
Installs a package. |
library() |
Loads a package for use in the current session. |
require() |
Attempts to load a package and returns a logical result indicating success or failure. |
package_name::function() |
Calls an exported function from a package explicitly. |
packageVersion() |
Checks the installed version of a package. |
installed.packages() |
Provides information about installed packages. |
14.7 Structure of an R Package
A package is more than a collection of .R files. A properly structured package contains several components that allow R and other users to understand, install and use the package.
Important components include:
- DESCRIPTION: Contains metadata such as the package name, version, author information and dependencies.
- NAMESPACE: Defines how functions and other objects are made available and how package dependencies are accessed.
- R directory: Contains R source code, including functions.
- man directory: Contains documentation files for functions and other objects.
- data directory: May contain datasets distributed with the package.
- tests directory: May contain automated tests used to check package behaviour.
- vignettes: Long-form documents that explain package functionality and demonstrate practical use.
The exact contents of a package can vary, but the package structure provides a standardized way of organizing software.
14.8 Creating a Simple R Package
Creating a professional package requires more structure than writing an ordinary R script. A package normally begins with a package skeleton containing the files and directories required for development.
Modern R development commonly uses tools such as devtools and usethis to simplify package creation and development.
For example, after installing the relevant development tools, a package project can be initialized with commands such as:
usethis::create_package("myPackage")
This creates a structured package project in the specified location.
A developer can then add functions to the package, document them, specify dependencies and test the package.
14.9 Adding Functions to a Package
Functions are the principal building blocks of many R packages. A package may contain a small number of specialized functions or a large collection of related functions.
Consider a simple function that calculates the percentage change between two values:
percentage_change <- function(old_value, new_value) {
((new_value - old_value) / old_value) * 100
}
The function could be used as follows:
percentage_change(200, 250)
The result is:
25
In a package, such a function would normally be documented and tested before being distributed for use.
14.10 Documentation in R Packages
Documentation is essential because users must be able to understand what a function does, what arguments it expects and what result it produces.
Good function documentation should normally explain:
- the purpose of the function;
- the arguments accepted by the function;
- the expected data types or structures;
- the value returned by the function;
- important assumptions or limitations;
- examples of use; and
- related functions or concepts where appropriate.
R package documentation commonly uses the Roxygen2 system. Documentation comments are placed above functions and can be converted into standard R documentation.
#' Calculate percentage change
#'
#' @param old_value Original value.
#' @param new_value New value.
#' @return Percentage change.
#' @examples
#' percentage_change(200, 250)
percentage_change <- function(old_value, new_value) {
((new_value - old_value) / old_value) * 100
}
Documentation transforms code from something that merely works into something that other people can understand and use.
14.11 Package Dependencies
A package may rely on functionality provided by another package. Such a package is called a dependency.
For example, suppose package A requires functions supplied by package B. Package A must identify package B as a dependency so that the necessary functionality is available when package A is used.
Dependencies are important because failing to manage them properly can cause an application to work on one computer but fail on another computer.
Good package development therefore requires careful specification of dependencies and awareness of package versions.
14.12 Testing R Packages
Testing is the process of checking whether software behaves as intended. Testing is particularly important when functions will be reused by other people or become part of a larger data product.
Suppose a function is intended to calculate an average. A basic test could check whether it produces the expected result for a known input.
x <- c(10, 20, 30)
mean(x)
The expected result is:
20
For professional package development, automated testing frameworks such as testthat can be used to create repeatable tests.
Testing should consider normal inputs as well as unusual or invalid inputs. For example, a function that expects numeric values should be tested with appropriate numeric inputs and, where relevant, with missing, empty or invalid values.
14.13 What Is a Data Product?
A data product is a usable software application, analytical system, report, dashboard, service or other computational product that uses data to provide information, support decisions, automate tasks or solve a practical problem.
The important distinction is that a data product is intended for use, not merely for analysis during development.
For example, a data scientist may analyze customer transactions and discover that certain products are frequently purchased together. The analysis itself is useful, but a system that automatically recommends related products to customers can be considered a data product.
Data → Analysis → Insight → Product → Decision or Action
This progression illustrates how raw data can eventually become something that provides practical value.
14.14 Examples of Data Products
Data products can take many forms.
- Dashboards: Applications that display important metrics and trends interactively.
- Recommendation systems: Systems that recommend products, articles, videos or other content based on data.
- Forecasting applications: Systems that estimate future sales, demand, prices or other quantities.
- Fraud detection systems: Applications that identify transactions that may require further investigation.
- Monitoring systems: Applications that continuously track measurements and identify unusual behaviour.
- Analytical reports: Automated reports that transform data into structured information for decision-makers.
- Decision-support tools: Applications that combine data and analytical models to assist users in making decisions.
- Interactive visualization applications: Applications that allow users to explore datasets through charts, filters and other controls.
14.15 R in Data-Driven Applications
R is particularly useful when an application requires statistical analysis, data manipulation, visualization or modelling.
A typical R-based data application may involve the following sequence:
- Acquire data.
- Inspect the data.
- Clean the data.
- Transform the data.
- Analyze the data.
- Build or apply a model where appropriate.
- Visualize the results.
- Present the results to users.
- Support a decision or action.
The individual concepts studied throughout this course therefore form parts of a larger system.
14.16 Example: Building a Simple Sales Analysis Product
Consider a company that wants to monitor monthly sales.
The raw data may contain columns such as:
- product;
- month;
- quantity sold;
- unit price; and
- sales region.
The first step is to import the data into R.
sales <- read.csv("sales.csv")
The data can then be inspected:
head(sales)
str(sales)
summary(sales)
Suppose revenue is calculated as quantity multiplied by unit price:
sales$revenue <- sales$quantity * sales$unit_price
The resulting data can then be summarized and visualized.
For example, the total revenue can be calculated as:
sum(sales$revenue, na.rm = TRUE)
A product-oriented application could then display the results in a dashboard or automated report.
The important lesson is that the data product is not simply the line of code calculating revenue. It is the complete system that takes data, processes it and provides useful information to its intended users.
14.17 R Structures Used in Data Products
The data structures studied throughout this course provide the foundation for practical R applications.
| R Structure | Typical Use |
|---|---|
| Vector | Stores a sequence of values of a common type. |
| Matrix | Represents data arranged in rows and columns of one type. |
| Array | Represents multidimensional data. |
| List | Stores collections of potentially different types and structures. |
| Factor | Represents categorical data. |
| Data frame | Represents tabular datasets with columns that may have different types. |
Understanding these structures is essential because data products frequently operate on structured datasets.
14.18 Functions as Building Blocks of Data Products
Functions allow complex applications to be divided into smaller, manageable operations.
Consider a simple function for calculating a profit margin:
profit_margin <- function(revenue, cost) {
profit <- revenue - cost
margin <- (profit / revenue) * 100
return(margin)
}
The function can then be reused:
profit_margin(100000, 70000)
The result is:
30
This approach is preferable to repeatedly writing the same calculation because the logic exists in one place and can be tested and maintained more easily.
14.19 Control Structures in Data Products
Control structures allow an application to respond differently depending on the data or circumstances.
For example, a simple function could classify sales performance:
classify_sales <- function(sales) {
if (sales >= 100000) {
"High"
} else if (sales >= 50000) {
"Medium"
} else {
"Low"
}
}
Calling:
classify_sales(75000)
produces:
"Medium"
In a larger data product, similar logic could be used to trigger warnings, classify records, assign categories or support decisions.
14.20 Data Visualization in R Products
Visualization is an important component of many data products because users often understand patterns more easily through appropriate graphical representations.
For example, a line graph can show changes in sales over time, while a bar chart can compare sales between products.
A simple base R plot can be created using:
months <- c("Jan", "Feb", "Mar", "Apr")
sales <- c(45000, 52000, 61000, 58000)
plot(
sales,
type = "o",
xaxt = "n",
xlab = "Month",
ylab = "Sales",
main = "Monthly Sales"
)
axis(1, at = 1:4, labels = months)
A well-designed data product should not merely display a graph. The graph should help the intended user understand a meaningful aspect of the data.
14.21 Interactive Data Products
R can also be used to develop interactive applications. One important framework is Shiny, which allows R users to create interactive web applications using R.
A Shiny application commonly contains two major components:
- User interface (UI): Defines what the user sees and interacts with.
- Server: Contains the computational logic that responds to user inputs.
A very simple Shiny application can be structured as follows:
library(shiny)
ui <- fluidPage(
titlePanel("Simple Data Product"),
sliderInput(
"number",
"Choose a number:",
min = 1,
max = 100,
value = 50
),
textOutput("result")
)
server <- function(input, output) {
output$result <- renderText({
paste("You selected:", input$number)
})
}
shinyApp(ui = ui, server = server)
The application accepts user input and produces a dynamic result. More sophisticated applications can include datasets, charts, statistical models, filters, maps and interactive reports.
14.22 A Complete Data-Driven Workflow in R
The concepts covered throughout DTS211 can be combined into a complete workflow.
Data Acquisition → Data Structures → Cleaning → Transformation → Analysis → Visualization → Functions/Models → Product → Decision
Step 1 — Acquire data: Data may be imported from CSV files, databases, text files, spreadsheets, APIs or other sources.
Step 2 — Inspect data: The structure, dimensions, data types, names and attributes are examined.
Step 3 — Clean data: Missing values, duplicates, incorrect values and inconsistencies are addressed.
Step 4 — Transform data: Variables may be created, renamed, converted, combined, filtered or summarized.
Step 5 — Analyze data: Statistical calculations, descriptive analysis or models can be applied.
Step 6 — Visualize results: Charts and graphs are used to communicate important patterns.
Step 7 — Build reusable functions: Repeated operations can be converted into functions.
Step 8 — Package functionality: Related functions can be organized into an R package where appropriate.
Step 9 — Create the product: The analytical process can become a report, dashboard, application or other usable system.
Step 10 — Communicate and support decisions: The final product provides information that users can interpret and act upon.
14.23 Application Example: Student Performance Analysis
Consider a university department that wants to identify students who may require additional academic support.
A dataset may contain:
student_id
gender
attendance
assignment_score
test_score
exam_score
The data can be imported:
students <- read.csv("students.csv")
A total score could be calculated:
students$total <-
students$assignment_score +
students$test_score +
students$exam_score
A function could then classify academic performance:
performance_level <- function(score) {
if (score >= 70) {
"Excellent"
} else if (score >= 50) {
"Satisfactory"
} else {
"Needs Support"
}
}
The function can be applied to the total scores:
students$performance <-
sapply(students$total, performance_level)
The resulting information could then be displayed in a report or interactive dashboard.
In a production environment, the system would require additional considerations such as data privacy, validation, testing, secure access and appropriate interpretation of results. A student's classification should not automatically be treated as a complete judgement of academic ability.
14.24 Good Practices for Developing R Data Products
A working program is not automatically a good data product. Professional development requires attention to code quality, usability, reliability and maintainability.
Use meaningful names: Names such as total_sales are easier to understand than names such as x1.
Write modular code: Divide complex operations into smaller functions.
Avoid unnecessary duplication: Repeated logic should often be converted into reusable functions.
Document important functions: Users and future developers need to understand how the code works.
Validate inputs: A data product should not blindly assume that every input is correct.
Handle missing values: Missing observations should be considered explicitly where they affect calculations or decisions.
Test important functionality: Critical functions should be tested with appropriate inputs and edge cases.
Keep dependencies under control: Only necessary packages should be used, and dependencies should be documented.
Separate data, logic and presentation where appropriate: This makes applications easier to understand and maintain.
Protect sensitive information: Personal, financial, medical and other sensitive datasets require appropriate security and privacy measures.
Make results reproducible: Analytical workflows should be structured so that another qualified user can understand and, where practical, reproduce the analysis.
14.25 Common Mistakes When Working with R Packages
1. Installing a package every time R starts. Installation is normally a one-time operation for a given package installation. Loading is what is usually required in a new session.
2. Forgetting to load a package. A package may be installed but its functions may not be available directly until the package is loaded.
3. Using functions without checking their documentation. Similar functions can have different arguments, defaults and return values.
4. Installing unnecessary packages. Excessive dependencies can make projects harder to maintain.
5. Ignoring package versions. Code may behave differently when dependencies change substantially.
6. Writing one extremely large script. Large scripts are often harder to test, understand and reuse than well-organized functions and modules.
7. Treating a data product as merely a graph. A graph may be part of a data product, but the product normally includes a broader workflow and intended practical use.
8. Failing to validate data. Incorrect input can produce apparently reasonable but invalid results.
9. Ignoring users. A technically correct application can still be ineffective if its interface and outputs do not meet the needs of its intended users.
14.26 Major Course Concepts Reviewed
This final chapter provides an opportunity to connect the major ideas introduced throughout DTS211.
| Topic | Central Idea |
|---|---|
| History and Overview of R | R is a programming language and environment strongly associated with statistical computing and data analysis. |
| R Evaluation and Objects | R evaluates expressions and represents information using objects. |
| Vectors and Data Types | Vectors are fundamental structures used to store collections of values. |
| Matrices, Arrays and Lists | R provides structures for representing multidimensional and heterogeneous collections of objects. |
| Factors and Data Frames | Factors represent categorical variables, while data frames provide a central structure for tabular data. |
| Data Input and Output | R can import and export data through files, connections and other sources. |
| Data Cleaning and Transformation | Raw data must often be cleaned and transformed before analysis. |
| Displaying and Plotting Data | R provides facilities for communicating patterns through graphical representations. |
| Dates and Times | R provides specialized structures and functions for representing and manipulating temporal data. |
| Control Structures and Looping | Programs can make decisions and repeat operations using control structures. |
| Functions and Scoping | Functions provide reusable units of computation, while scoping determines how R resolves names. |
| Coding, Debugging and Profiling | Professional R programming requires readable code, error identification, testing and performance analysis. |
| Packages and Data Products | Reusable R functionality can be organized into packages and integrated into practical data-driven applications. |
14.27 Integrating the Major R Concepts
A student who has completed this course should now be able to see that the different topics are not isolated programming techniques. They form a connected system for working with data.
For example, a data scientist may begin with a CSV file containing thousands of observations. The file is imported into R and stored in a data frame. The programmer examines the data using functions such as str(), summary() and head(). Missing values and incorrect entries are identified and handled.
The cleaned data may then be transformed by creating new variables, filtering observations or calculating summaries. Functions can be written to automate repeated calculations. Conditional statements can be used to classify observations, while loops or vectorized operations can automate repeated processing.
Dates and times can be converted into appropriate representations when temporal analysis is required. Graphical functions can then be used to communicate important patterns.
If the same analytical functions are required repeatedly, they can be organized into an R package. If the complete workflow is intended for practical use by other people, it can become a data product such as an interactive application, dashboard, analytical report or decision-support tool.
The progression can therefore be summarized as:
R Programming → Data Structures → Data Preparation → Analysis → Visualization → Reusable Functions → Packages → Data Products
14.28 Chapter Summary
R packages provide a systematic way of organizing and distributing reusable R code, datasets, documentation and related resources. They are fundamental to the modern R ecosystem because they allow programmers and data scientists to extend the capabilities of R without repeatedly implementing common functionality.
A package can contain functions, documentation, datasets, tests and dependency information. Packages are generally installed using install.packages() and loaded using library(). Functions can also be accessed explicitly using the package::function() syntax.
Professional package development requires more than writing functions. Good packages should be appropriately structured, documented, tested and maintained. Dependencies should be managed carefully, and users should be provided with sufficient information to understand how the package works.
A data product is a practical software application, analytical system, dashboard, report, service or other usable output that applies data and computation to solve a problem or support a decision. R is particularly suitable for data products involving statistical analysis, data manipulation, visualization and modelling.
Developing a data product involves integrating many of the concepts studied throughout this course. Data must be acquired, represented using appropriate R structures, cleaned, transformed and analyzed. Functions and control structures can automate operations, while visualization can communicate results. Reusable functionality can be organized into packages, and complete analytical workflows can be delivered through reports, dashboards and interactive applications.
The central lesson of this course is therefore not simply how to write individual R commands. It is how to use R systematically to represent data, manipulate information, perform computation, communicate results and develop reliable, reusable solutions to data-driven problems.
Chapter 14 Key Points
- An R package is a structured collection of reusable R code and related resources.
- Packages extend the functionality available in base R.
- Installing a package and loading a package are different operations.
- Package dependencies must be managed carefully.
- Professional packages require appropriate documentation and testing.
- Functions are important building blocks of reusable R software.
- A data product applies data and computation to provide practical value to users.
- Examples of data products include dashboards, recommendation systems, forecasting tools and decision-support applications.
- R structures such as vectors, matrices, lists, factors and data frames form the foundation of data manipulation.
- Control structures allow applications to make decisions and repeat operations.
- Visualization helps communicate analytical results to users.
- R can be used to develop interactive applications, including applications built with Shiny.
- The major concepts of R programming can be integrated into complete data-driven workflows.
- Good data products require more than correct code; they require maintainability, documentation, testing, usability and appropriate handling of data.