R Programming and Reproducible ComputingIntroductionData science is not only the selection of statistical models. It is also the disciplined construction of computational artefacts that can be inspected, rerun, tested, extended and communicated. R is particularly valuable in this context because it combines a programming language, a statistical computing environment, a large package ecosystem and tools for literate, reproducible reporting. A reproducible analysis is one in which another analyst, using the stated data, code, software dependencies and computational procedures, can obtain the same substantive results. Reproducibility does not imply that every result is universally correct. It means that the path from input to output is transparent and repeatable. This distinction is important: an analysis may be perfectly reproducible yet methodologically flawed, or scientifically valid yet difficult to reproduce because its computational environment was inadequately recorded. This chapter develops the programming foundations required for postgraduate work in data science with R. It covers R and RStudio, packages, vectors and data structures, functions, control flow, object-oriented programming, debugging and environments. It then connects programming practice with Quarto or R Markdown, Git, testing and dependency management. Learning ObjectivesBy the end of this chapter, the learner should be able to: 1. Explain the roles of R, RStudio, packages and project-oriented workflows. 2. Select suitable R data structures for common analytical tasks. 3. Use vectorised operations, indexing, functions and control-flow constructs correctly. 4. Explain R's object-oriented systems and the implications of method dispatch. 5. Diagnose errors using debugging tools and understand environments and scoping. 6. Construct reproducible reports with Quarto or R Markdown. 7. Use Git, automated tests and dependency-management tools to improve analytical reliability. 8. Critically evaluate the limitations and governance risks of reproducible computational work. 1. R as a Computational EnvironmentR is an interpreted, dynamically typed programming language designed originally for statistical computing and graphics. It is also a complete programming environment: objects can be created and transformed, functions can be defined, files can be read and written, and external packages can extend the language. RStudio is an integrated development environment (IDE) for R. It provides an editor, console, workspace browser, history, plot viewer, package interface, debugging facilities and project management. RStudio is not a replacement for R; it is a client that makes R development more convenient. The same R script should generally run from a terminal or automated system if it has been written with appropriate assumptions. An R session contains objects in environments. When R starts, it creates a global environment and attaches several package environments. Commands entered in the console affect the current session, but console history is not a sufficient record of an analysis. A script, project structure and dependency specification are more reliable than a sequence of interactive commands. A professional project commonly contains directories such as: ```text project/ ├── data-raw/ ├── data/ ├── R/ ├── tests/ ├── reports/ ├── renv.lock └── project.Rproj ``` The exact structure may vary. The principle is to separate raw inputs, derived data, reusable code, tests and reports. Raw data should normally be treated as immutable. Transformations should create new outputs or clearly document modifications. 2. Packages and Namespace ManagementAn R package is a structured collection of functions, documentation, data and sometimes compiled code. Packages are installed once, generally with `install.packages()`, and loaded into a session with `library()`. ```r install.packages("dplyr") library(dplyr) ``` Installation changes the software available on a computer; loading attaches package functions for use in the current session. These are different operations and should not be confused. In a script, it is good practice to make package requirements explicit. A package function can be called with its namespace: ```r dplyr::filter(data, score > 50) ``` Namespace qualification reduces ambiguity when packages contain functions with the same name. It also documents the source of an important operation. The `conflicts()` function can help identify masking, although explicit namespaces are usually preferable for critical code. Packages have versions, and changes between versions can alter results or behaviour. Reproducible projects should record package versions and, where feasible, the R version and operating-system assumptions. The `renv` package creates a project-specific library and a lockfile recording package versions: ```r renv::init() renv::snapshot() renv::restore() ``` `snapshot()` records the project state; `restore()` attempts to recreate it elsewhere. Dependency management does not guarantee reproducibility if external data, random seeds, system libraries or undocumented manual steps are missing, but it substantially reduces uncertainty. 3. Values, Objects and Core Data StructuresR is object-based. Almost every useful result is assigned to an object. Assignment is commonly written with `<-`: ```r n <- 12 label <- "sample" ``` The operator `=` is also accepted in many assignment contexts, but `<-` conventionally distinguishes assignment from argument specification in function calls. 3.1 Atomic vectorsThe fundamental data structure in R is the atomic vector. Its elements have one basic type: logical, integer, double (numeric), character, complex or raw. ```r logical_vector <- c(TRUE, FALSE, TRUE) integer_vector <- 1:5 numeric_vector <- c(2.5, 4.0, 7.25) character_vector <- c("low", "medium", "high") ``` Vectors are indexed from 1: ```r x <- c(10, 20, 30, 40) x[2] # 20 x[c(1, 4)] # 10 40 x[-2] # all except the second element ``` A logical vector can be used as a filter: ```r x[x > 20] ``` Vectorisation means that an operation is applied to corresponding elements without writing an explicit loop: ```r x * 2 x + c(1, 1, 1, 1) ``` If vector lengths differ, R may recycle the shorter vector. Recycling is useful when intentional, but accidental recycling can silently produce incorrect results. For example, adding a vector of length two to one of length four repeats the shorter vector. Modern R may warn when the longer length is not a multiple of the shorter length, but analysts should not rely on warnings as a substitute for checking dimensions. Missingness is represented by `NA`. It is not the same as zero, an empty string or a false value. Many functions require explicit handling: ```r mean(c(2, 4, NA), na.rm = TRUE) is.na(c(2, NA, 4)) ``` The value `NaN` represents an undefined numerical result, while `Inf` and `-Inf` represent infinite values. `is.na()` is true for `NA` and `NaN`; `is.nan()` specifically identifies `NaN`. 3.2 Factors and datesA factor represents categorical data through a set of levels. Factors are useful when categories have a defined vocabulary, especially in modelling, but careless conversion can cause errors. A factor should not be converted to numeric with `as.numeric()` when the intended values are the labels; that operation returns internal level codes. Dates and times should use appropriate classes rather than character strings. Base R provides `Date` and `POSIXct` classes. Packages such as `lubridate` offer convenient parsing and manipulation. Time zones, daylight-saving changes and ambiguous date formats are important sources of analytical error. 3.3 Lists, matrices and arraysA list can contain objects of different types and lengths: ```r record <- list(id = 101, name = "Asha", scores = c(72, 81, 77)) record$name record[["scores"]] ``` The distinction between `[` and `[[` is fundamental. `[` generally returns a subset that retains a container structure, whereas `[[` extracts a single element from a list. The `$` operator is convenient for named elements but is less programmable. A matrix is a two-dimensional atomic vector with dimensions. All elements must have the same underlying type: ```r m <- matrix(1:6, nrow = 2, byrow = TRUE) m[1, 2] ``` An array generalises a matrix to more than two dimensions. Matrices are suitable for numerical linear algebra; they are not automatically equivalent to data frames. 3.4 Data frames and tibblesA data frame is a rectangular list of equal-length columns. Columns may have different types, making data frames appropriate for observational data: ```r students <- data.frame( id = 1:3, programme = c("IT", "IT", "DS"), mark = c(68, 74, 81) ) ``` A tibble, provided by the tidyverse, is a modern data-frame-like structure with more predictable printing and stricter subsetting behaviour. Both data frames and tibbles can contain list-columns, which are useful for nested results but require careful handling. The choice of structure should reflect the problem. Use an atomic vector for one homogeneous sequence, a list for heterogeneous components, a matrix for homogeneous rectangular numerical data, and a data frame or tibble for heterogeneous tabular observations. 4. Indexing, Transformation and Functional ProgrammingIndexing is central to R programming. Names can make code more interpretable: ```r sales <- c(Jan = 120, Feb = 135, Mar = 128) sales["Feb"] ``` Data transformation often uses functions such as `subset()`, `transform()`, `within()` or tidyverse verbs. A robust transformation should make assumptions explicit, particularly assumptions about missing values, duplicate identifiers and data types. Functions such as `lapply()`, `sapply()`, `vapply()` and `purrr::map()` apply an operation across elements. `vapply()` is safer than `sapply()` when a specific return type is required because it checks the expected output structure. ```r values <- list(a = 1:3, b = 4:6) vapply(values, mean, numeric(1)) ``` The `apply()` family is useful but should be understood carefully: `apply()` may coerce a data frame to a matrix, potentially converting values to character. Applying a function to a data frame by column is often clearer with `lapply()` or a data-manipulation framework. 5. Functions, Arguments and Control FlowA function encapsulates a computation and creates a reusable interface: ```r weighted_mean <- function(x, w) { stopifnot(length(x) == length(w)) if (anyNA(c(x, w))) { stop("x and w must not contain missing values") } sum(x * w) / sum(w) } ``` This function assumes that `x` and `w` have equal lengths, contain no missing values and that the sum of weights is nonzero. These conditions should be documented or validated. A function should generally have one clear responsibility and should return a value rather than relying on hidden side effects. R uses lazy evaluation for function arguments: an argument is usually evaluated when it is needed, not necessarily when the function is called. Default arguments can depend on earlier arguments, but excessive non-standard evaluation can make code difficult to understand. Functions should use informative argument names and avoid relying on objects in the global environment. Conditional execution uses `if`, `else if` and `else`: ```r if (score >= 50) { result <- "pass" } else { result <- "fail" } ``` `if` requires a condition of length one. For vector conditions, use vectorised tools such as `ifelse()` or `dplyr::if_else()`. `ifelse()` may unexpectedly coerce types; `if_else()` is stricter and therefore often safer in data pipelines. Loops are appropriate when an iterative process is clearer or when state must be updated sequentially: ```r total <- 0 for (value in c(3, 5, 7)) { total <- total + value } ``` A `while` loop repeats while a condition remains true and must be designed to terminate. `break` exits a loop, while `next` skips to the next iteration. Vectorisation is often concise and efficient, but a loop is not inherently poor programming. Clarity, correctness and testability take priority over stylistic preference. 6. Object-Oriented Programming in RR supports multiple object-oriented systems. The two most important are S3 and S4; reference classes and the R6 package provide more classically encapsulated alternatives. S3 is informal and widely used. An object typically has a `class` attribute, and generic functions dispatch methods based on that class: ```r print.report <- function(x, ...) { cat("Report with", length(x$values), "values\n") } report <- structure(list(values = c(2, 4, 6)), class = "report") print(report) ``` S3 is flexible but permits inconsistent object structures. S4 provides formal class definitions, slots and validity methods. It is more verbose but useful in large systems requiring explicit contracts. R6 supports encapsulated mutable objects, which can be appropriate for stateful systems but differs from R's usual copy-on-modify style. Method dispatch can be surprising when several classes are present or when a package defines methods for a generic function. Analysts should inspect objects with `class()`, `str()`, `methods()` and, where needed, `getS3method()`. Object-oriented design is relevant to data science because modelling packages often return structured objects with class-specific methods for `print()`, `summary()`, `predict()` and `plot()`. 7. Environments, Scope and EvaluationAn environment is a mapping between names and objects, together with a parent environment. Environments support lexical scoping: when a function looks for a name, R searches the function's local environment and then its enclosing environments. ```r make_adder <- function(k) { function(x) x + k } add_five <- make_adder(5) add_five(3) # 8 ``` The inner function retains access to `k` after `make_adder()` has finished. This is a closure. Closures are useful for factories, configuration and controlled state, but hidden state can make systems difficult to test. The global environment is convenient for exploration but risky for production scripts. A script that depends on objects created manually earlier in a session is not reproducible. Prefer functions with explicit inputs and outputs. Project-oriented execution, such as opening a fresh R session and running a complete script, reveals undeclared dependencies. The `<<-` operator assigns in an enclosing environment and should be used sparingly. It can implement stateful functions, but it may make reasoning about program state more difficult. Environments are also useful for caches and registries because they provide reference semantics, unlike most ordinary R objects. 8. Errors, Warnings, Debugging and TestingR distinguishes errors, warnings and messages. An error normally stops execution; a warning indicates a potentially problematic condition but may allow execution to continue; a message communicates information. Suppressing warnings or messages without understanding them is poor practice. A systematic debugging process is preferable to random code changes: 1. Reproduce the problem with the smallest possible input. 2. Read the complete error message and identify the failing call. 3. Inspect object classes, dimensions, names and missingness. 4. Check assumptions at function boundaries. 5. Add a test that captures the failure. 6. Correct the cause and rerun both the new test and the broader test suite. Useful tools include `traceback()` after an error, `debugonce()` to step through a function, `browser()` to pause execution, and `recover()` to inspect calling frames. `rlang::last_trace()` and `rlang::last_error()` are valuable when using tidyverse functions. Assertions make assumptions executable: ```r calculate_rate <- function(events, exposure) { stopifnot(is.numeric(events), is.numeric(exposure)) if (length(events) != length(exposure)) stop("Lengths differ") if (any(exposure <= 0)) stop("Exposure must be positive") events / exposure } ``` Unit testing verifies small components. The `testthat` package is widely used: ```r test_that("weighted mean is calculated", { expect_equal(weighted_mean(c(2, 4), c(1, 3)), 3.5) }) ``` The expected value is calculated as `(21 + 43)/(1+3) = 14/4 = 3.5`. Tests should include ordinary cases, boundary cases, invalid inputs and missing values where relevant. Tests are not proof of correctness; they provide evidence against specified behaviours. Property-based tests can verify general relationships, such as the fact that a permutation of paired observations should not change a weighted mean when weights are permuted identically. 9. Literate and Reproducible Analysis with Quarto and R MarkdownLiterate programming combines explanatory text, source code and computed results in one document. Quarto and R Markdown support this workflow through code cells, often called chunks. A document can contain narrative, tables, figures, citations and executable R code. A minimal Quarto document may begin: ```yaml --- title: "Customer Retention Analysis" format: html execute: freeze: auto --- ``` An R code cell can load data, transform it and produce a figure. Rendering executes the document and generates an output format such as HTML, PDF or a Word document. The rendered report should be generated from the source rather than manually editing results. Reproducible documents should: - declare packages and options explicitly;
- use relative project paths rather than personal absolute paths;
- set random seeds when stochastic procedures are used;
- record data provenance and access dates;
- avoid hidden objects from an interactive workspace;
- separate exploratory output from final claims;
- include units, definitions and meaningful captions;
- preserve the source document and rendered output appropriately.
A random seed makes a pseudorandom sequence repeatable under the same software conditions; it does not make a sampling method scientifically appropriate. For parallel computation, different random-number-generation streams may need explicit management. 10. Git and Collaborative Analytical WorkGit is a distributed version-control system. It records snapshots of files, identifies changes and supports branching and collaboration. A basic workflow is: ```bash git init git add . git commit -m "Create initial analysis structure" git status git log ``` A good commit is small enough to understand and describes a coherent change. Analytical projects should generally exclude secrets, credentials, large generated files and restricted data from the repository. A `.gitignore` file can prevent accidental inclusion, but it cannot remove information already committed; sensitive history may require specialised rewriting and governance review. Git records code and documents, not necessarily large datasets or external database states. Data versioning tools, checksums, metadata files or secure archival systems may be required. Commit messages should explain substantive changes, while README files should describe installation, execution, data sources and limitations. Branches support parallel work, but merging analytical code does not guarantee that results remain comparable. Teams should agree on coding conventions, review changes and rerun tests and reports after merges. 11. An Illustrative Case: Reproducible Student-Retention AnalysisBackgroundThe following is a fictional illustrative case. A university wishes to understand factors associated with first-year student retention. The data include programme, entry route, attendance, assessment marks and a binary retention outcome. ProblemAn analyst initially produces a spreadsheet and manually copies model results into a presentation. A second analyst cannot reproduce the figures because the filtering decisions, missing-value rules and package versions are undocumented. Analysis and approachThe team creates an R project. Raw data are stored in a controlled location and are not overwritten. An import script validates column names, date formats and permitted categories. A cleaning function explicitly documents how missing attendance values are handled and stops if student identifiers are duplicated. Exploratory transformations are written as functions. A modelling script records the formula, selected variables, random seed where applicable and session information. Unit tests verify that the cleaning function preserves row counts when no duplicates exist, rejects invalid programme codes and returns the required columns. A Quarto report contains the narrative, code, tables and diagnostic plots. `renv` records package versions, and Git stores scripts, tests, the report source and metadata rather than confidential student-level data. Access controls are applied because educational records are personal data. Results are reported in aggregate, and small cells are suppressed where re-identification is plausible. The team distinguishes association from causation: a relationship between attendance and retention does not establish that changing attendance alone will produce a specified retention improvement. OutcomeA reviewer can clone the repository, restore the project environment, obtain authorised data and render the report. When a data-quality error is discovered, the team traces it to a specific commit and reruns the tests. The analysis becomes auditable and maintainable rather than dependent on one analyst's workstation. Learning pointsThe case demonstrates that reproducibility is a socio-technical practice. Code, packages, data governance, documentation, version control and statistical interpretation must work together. Reproducible computation improves accountability but does not remove privacy risks or methodological bias. 12. Best Practices, Limitations and Contemporary DevelopmentsEffective R practice combines simplicity with explicitness. Prefer meaningful names, small functions, stable interfaces and deterministic transformations. Validate imported data early. Avoid copying values manually between software systems. Use code review and automated tests for important projects. Record software versions, seeds, data provenance and execution instructions. Reproducibility has limits. Proprietary data may not be shareable. Cloud services and operating-system libraries may change. Numerical results can differ across hardware, compiler or package versions. Floating-point arithmetic introduces finite-precision effects, especially in large or ill-conditioned calculations. Some analyses depend on external APIs that change or disappear. Reproducing a result may therefore require preserving containers, virtual machines or archived execution environments in addition to source code. Computational notebooks and AI-assisted programming introduce further governance questions. Generated code should be reviewed, tested and checked for licensing, security and privacy concerns. Confidential data should not be sent to an external model without authorisation. AI tools can accelerate boilerplate generation but cannot replace domain knowledge, validation or responsibility for analytical claims. A mature reproducibility standard should therefore include at least four dimensions: computational repeatability, methodological transparency, data governance and interpretive accountability. The strongest workflow is not the one with the most tools, but the one whose assumptions and dependencies are visible and whose results can be independently challenged. Chapter SummaryR is both a programming language and a statistical computing environment, while RStudio provides an integrated interface for developing R projects. Reliable analysis begins with explicit project structure, controlled package dependencies and appropriate selection of vectors, lists, matrices, data frames and tibbles. Vectorisation is powerful, but recycling, missing values, coercion and indexing require careful attention. Functions provide reusable, testable units of computation. Conditional statements and loops support control flow, while functional programming tools apply operations across collections. R's S3, S4 and reference-oriented systems support different forms of object-oriented design. Environments and lexical scoping explain closures, name lookup and the dangers of hidden global state. Debugging should be systematic and supported by assertions and unit tests. Quarto and R Markdown integrate narrative, code and results into executable reports. Git records the evolution of source files, while `renv` records package environments. Together with data provenance, privacy controls, code review and testing, these practices form the foundation of reproducible computing. Reproducibility strengthens auditability but does not by itself guarantee valid inference, ethical data use or causal interpretation. Key TermsR: A programming language and environment for statistical computing, data analysis and graphics. RStudio: An integrated development environment for working with R. Package: A structured collection of R functions, documentation, data and related resources. Namespace: The mechanism that identifies functions and objects supplied by a package. Vectorisation: Applying an operation to vector elements without explicitly writing an iteration for each element. Recycling: Reuse of elements from a shorter vector during a vectorised operation. NA: R's representation of a missing value. Factor: A categorical data structure with a set of defined levels. List: A heterogeneous R object whose elements may have different types and lengths. Data frame: A rectangular list of equal-length columns. Tibble: A modern data-frame-like structure with stricter and clearer behaviour. Function: A reusable object that accepts inputs, performs computation and returns a result. Lexical scoping: Name lookup based on a function's enclosing environments. Closure: A function together with the environment in which it was created. S3: R's informal class-and-generic object-oriented system. S4: R's formal object-oriented system with class definitions and validity rules. Quarto: A document system for combining text, executable code and output. Git: A distributed version-control system. Unit test: An automated test of a small, specified component of a program. Dependency management: Recording and controlling software libraries required by a project. Review QuestionsShort Answer Questions1. Distinguish between installing an R package and loading it into a session. 2. Why can accidental vector recycling be dangerous? 3. What is the difference between `[` and `[[` when working with lists? 4. What information does a `renv` lockfile provide? 5. State two advantages of using functions instead of relying on global objects. Long Answer Questions1. Explain how R vectors, lists, matrices, data frames and tibbles differ, and recommend an appropriate structure for a heterogeneous student-record dataset. 2. Describe R's lexical scoping model and explain how closures are created, using an example. 3. Compare S3 and S4 object-oriented systems in terms of flexibility, formal specification and typical use. 4. Develop a reproducible Quarto-based workflow for a statistical analysis, including data provenance, package management and testing. 5. Discuss why Git, tests and dependency management improve reproducibility but cannot guarantee scientific validity. Analytical / Application Questions1. A function calculates a rate by dividing event counts by exposure. Design input checks that would identify mismatched lengths, missing values and non-positive exposure, and explain why each check is necessary. 2. An R report gives different results when run on two computers. Construct a diagnostic plan covering random seeds, package versions, R versions, input data, time zones and hidden workspace objects. 3. A university wants to publish a reproducible retention analysis using confidential student data. Propose a workflow that balances computational transparency with privacy, access control, aggregation and protection against re-identification. Multiple Choice Questions with AnswersQuestion 1Which statement best distinguishes RStudio from R? A. RStudio is the statistical language, while R is only a package manager. B. R is the computational environment, while RStudio is an integrated development environment for working with it. C. RStudio replaces the R interpreter in production systems. D. R and RStudio are two names for the same package. Correct Answer: B. R is the computational environment, while RStudio is an integrated development environment for working with it. Question 2What does `NA` represent in R? A. A numerical zero B. An empty character string C. A missing value D. A false logical value Correct Answer: C. A missing value Question 3Which data structure can naturally contain elements of different types and lengths? A. Atomic vector B. Matrix C. Array D. List Correct Answer: D. List Question 4What is a principal risk of accidental vector recycling? A. It always produces a syntax error. B. It can produce a plausible but incorrect result. C. It converts every vector to a factor. D. It permanently deletes the shorter vector. Correct Answer: B. It can produce a plausible but incorrect result. Question 5Which operator extracts a single element from a list? A. `[[` B. `[` C. `%in%` D. `::` Correct Answer: A. `[[` Question 6What is the main purpose of `renv::restore()`? A. To delete all project packages B. To recreate the package environment recorded for a project C. To render a Quarto document D. To commit files to Git Correct Answer: B. To recreate the package environment recorded for a project Question 7Which tool is most directly used to pause execution inside a function for inspection? A. `browser()` B. `c()` C. `mean()` D. `library()` Correct Answer: A. `browser()` Question 8What does lexical scoping determine? A. How plots are physically displayed B. How R searches for names in enclosing environments C. How Git stores binary files D. How factors are ordered alphabetically Correct Answer: B. How R searches for names in enclosing environments Question 9What is the primary purpose of a unit test? A. To replace all documentation B. To test a small component against specified expected behaviour C. To guarantee that a model is causally valid D. To install every package on a computer Correct Answer: B. To test a small component against specified expected behaviour Question 10Which practice most directly supports an executable analytical report? A. Manually copying results into slides B. Editing rendered tables without changing source code C. Combining narrative and executable code in Quarto or R Markdown D. Storing only the final image files Correct Answer: C. Combining narrative and executable code in Quarto or R Markdown
|