R: A Beginner's Tutorial¶
R is a programming language and environment for statistics, data analysis, and visualization. On Deceema, an R analysis should run from explicit input files, write results to explicit output files, and execute without depending on an interactive session.
This tutorial assumes no R or programming experience. It uses base R so you can learn the language and workflow before adding packages.
By the end, you will be able to:
- Run R expressions and scripts with
Rscript. - Work with vectors, data frames, factors, and missing values.
- Select, transform, summarize, and visualize data.
- Use conditions, loops, vectorized operations, and functions.
- Read and write CSV files with explicit paths.
- Understand errors, warnings, and debugging basics.
- Accept command-line arguments and validate input.
- Record an R environment for reproducibility.
- Run a tested R analysis through Slurm.
Before you begin
Complete Linux (RHEL) and Bash first if terminals, paths, files, or scripts are unfamiliar. Work only inside the disposable training directory created below.
1. Meet R and Rscript¶
Prepare the Deceema environment and inspect R:
R starts an interactive console. Rscript executes a script without an
interactive prompt, making it the usual entry point for repeatable batch work.
| Term | Plain-language meaning |
|---|---|
| Expression | R code that produces a value. |
| Object | A value stored under a name. |
| Vector | A sequence of values with a common underlying type. |
| Data frame | A rectangular table whose columns can have different types. |
| Function | Reusable code that accepts arguments and returns a value. |
| Package | An installable collection of R functions, data, and documentation. |
| Working directory | The base location used by relative paths. |
| Session information | R, operating-system, and attached package details. |
Run one expression¶
[1] labels the position of the first displayed value. It is not part of the
value itself.
Try the interactive console¶
The > is R's prompt; do not type it when copying examples. Interactive R is
useful for exploration. Move valuable work into a script so it can be repeated.
2. Create a Project Workspace¶
$ mkdir -p "$HOME/training/r-beginner"/{data,src,logs,plots,results}
$ cd "$HOME/training/r-beginner"
$ pwd
Create src/hello.R:
Run it:
R executes a script from top to bottom. <- assigns the text value to
message, and print() displays it.
3. Learn Objects and Basic Types¶
project <- "deceema-training" # character
sample_count <- 4L # integer
mean_value <- 5.0 # double
completed <- TRUE # logical
missing <- NA # missing value
R object names are case-sensitive: sample_count and Sample_Count are
different.
Inspect a value:
typeof()reports how R stores the object.class()describes its object-oriented class.str()gives a compact structural summary.
Convert types¶
text <- "42"
count <- as.integer(text)
measurement <- as.numeric("3.14")
label <- as.character(count)
An invalid conversion can produce NA and a warning:
Warnings deserve investigation even when the process exits successfully.
4. Work with Vectors¶
Create a vector with c():
Most arithmetic is vectorized:
Select vector elements¶
R indexes begin at one:
values[1] # first value
values[length(values)]
values[2:3]
values[c(1, 4)]
values[values >= 4] # logical selection
Negative indexes exclude positions:
Do not mix positive and negative indexes in the same selection.
Name elements¶
Names often communicate meaning more clearly than numeric positions.
5. Understand Missing Values¶
NA represents a missing value:
values <- c(2, 4, NA, 8)
is.na(values)
sum(values)
sum(values, na.rm = TRUE)
mean(values, na.rm = TRUE)
Many summary functions return NA when missing values are present unless
na.rm = TRUE is supplied.
Never test a missing value with value == NA; that result is itself unknown.
Use is.na(value).
Removing missing values is an analytical decision
na.rm = TRUE is not automatically correct. Understand why data is missing
and document the treatment used by the analysis.
6. Create and Inspect Data Frames¶
A data frame stores rectangular data in named columns:
measurements <- data.frame(
sample = c("alpha", "beta", "gamma", "delta"),
group = c("control", "control", "treated", "treated"),
value = c(2, 4, 6, 8)
)
Inspect it:
print(measurements)
str(measurements)
summary(measurements)
names(measurements)
nrow(measurements)
ncol(measurements)
head(measurements)
Select columns and rows¶
measurements$value
measurements[["value"]]
measurements[1:2, ]
measurements[, c("sample", "value")]
measurements[measurements$value >= 4, ]
In data[row, column], the comma separates row and column selection.
Add a column¶
Prefer a new object when you need to preserve the original data unchanged.
7. Recognize Factors¶
A factor represents categorical values with a defined set of levels:
group <- factor(
c("control", "treated", "control"),
levels = c("control", "treated")
)
levels(group)
table(group)
Factors are useful when the set or ordering of categories matters. Inspect column structure after importing data rather than assuming every text-like column behaves the same way.
8. Make Decisions with Conditions¶
value <- 6
if (value > 7) {
category <- "high"
} else if (value >= 4) {
category <- "moderate"
} else {
category <- "low"
}
print(category)
Common logical operators:
| Operator | Meaning |
|---|---|
==, != |
Equal; not equal. |
<, <= |
Less than; less than or equal. |
>, >= |
Greater than; greater than or equal. |
&, | |
Element-wise AND and OR. |
&&, || |
Short-circuit AND and OR for single conditions. |
! |
Logical NOT. |
%in% |
Membership in a set of values. |
An if condition must resolve to one non-missing logical value.
9. Repeat Work with Loops and Vectorization¶
For loop¶
Sequence loop¶
seq_along() safely generates indexes for an object, including an empty one.
Vectorized alternative¶
Prefer clear vectorized operations when they express the intent directly. A loop is appropriate when each iteration needs several steps or diagnostics.
Apply a function¶
vapply() declares the expected result type and can catch inconsistent output.
10. Write Reusable Functions¶
calculate_summary <- function(values) {
if (!is.numeric(values)) {
stop("values must be numeric")
}
if (length(values) == 0L) {
stop("values cannot be empty")
}
data.frame(
count = length(values),
mean = mean(values),
minimum = min(values),
maximum = max(values)
)
}
result <- calculate_summary(c(2, 4, 6, 8))
print(result)
function()defines parameters and a body.- The final evaluated expression is returned automatically.
stop()raises an error with a useful message.
You can also make the return explicit:
Keep functions focused: validate input, do one coherent task, and return a predictable type.
11. Use R's Help System¶
From the interactive console:
Search when you know a concept but not the function name:
Inspect a package's help index with help(package = "packageName").
12. Read and Write CSV Files¶
Create a small dataset:
$ printf 'sample,group,value\nalpha,control,2\nbeta,control,4\ngamma,treated,6\ndelta,treated,8\n' \
> data/measurements.csv
Read it:
input_path <- "data/measurements.csv"
measurements <- read.csv(input_path)
str(measurements)
summary(measurements)
Validate expected columns:
required <- c("sample", "group", "value")
missing_columns <- setdiff(required, names(measurements))
if (length(missing_columns) > 0L) {
stop(sprintf("missing columns: %s", paste(missing_columns, collapse = ", ")))
}
Write a result without row names:
result <- calculate_summary(measurements$value)
dir.create("results", recursive = TRUE, showWarnings = FALSE)
write.csv(result, "results/summary.csv", row.names = FALSE)
Always inspect imported structure and write to an explicit location.
13. Summarize Groups¶
Base R can summarize data by category with aggregate():
group_summary <- aggregate(
value ~ group,
data = measurements,
FUN = mean
)
names(group_summary)[names(group_summary) == "value"] <- "mean_value"
print(group_summary)
For several statistics, split the values and apply your function:
by_group <- split(measurements$value, measurements$group)
group_results <- lapply(by_group, calculate_summary)
Check that grouping columns contain the expected categories before interpreting the result.
14. Save a Plot to a File¶
Batch jobs have no interactive graphics window. Open a file device, draw the plot, and close the device:
dir.create("plots", recursive = TRUE, showWarnings = FALSE)
png("plots/measurements.png", width = 1200, height = 800, res = 150)
plot(
measurements$value,
type = "b",
xlab = "Observation",
ylab = "Value",
main = "Training measurements"
)
dev.off()
dev.off() completes and closes the image file. Verify that the file exists and
is non-empty after the script runs.
15. Understand Errors and Warnings¶
- An error stops the current operation.
- A warning reports a concern but normally allows execution to continue.
- A message communicates progress or context.
message("Starting analysis")
warning("This is a demonstration warning")
stop("This is a demonstration error")
Catch only errors you can handle meaningfully:
result <- tryCatch(
read.csv("data/missing.csv"),
error = function(error) {
message("Could not read input: ", conditionMessage(error))
NULL
}
)
Do not catch every error and report success. In a Slurm job, an unhandled R
error should normally make Rscript return a non-zero status.
Investigate an error¶
In an interactive session, traceback() shows the recent call stack after an
error. Other useful techniques include:
- Print
str(object)before the failing operation. - Test with a tiny input that reproduces the error.
- Use
stopifnot()for internal assumptions. - Use
browser()only during interactive debugging, then remove it before a batch run.
16. Work with Packages Responsibly¶
Inspect installed packages:
Load a package explicitly in a script:
For project packages:
- Use the approved Deceema package source and installation method.
- Prefer a project-specific library or reproducible environment.
- Do not install packages during every compute job.
- Record exact versions and repositories.
- Test package loading in a small job before scaling.
- Never embed repository passwords or tokens in source or logs.
The exact package-management approach depends on Deceema policy and the project's needs; contact Support when unsure.
17. Accept Command-Line Arguments¶
commandArgs(trailingOnly = TRUE) returns values placed after the script name.
Create src/arguments.R:
| src/arguments.R | |
|---|---|
Test both paths:
Use arguments or configuration rather than setwd() and personal hard-coded
paths.
18. Build a Complete Data-Summary Program¶
Create src/summarize.R:
Run it:
$ Rscript src/summarize.R \
data/measurements.csv \
results/summary.csv \
first-analysis
$ cat results/summary.csv
Expected result:
Test a failure path:
$ Rscript src/summarize.R data/missing.csv results/missing.csv
error: input file does not exist: data/missing.csv
The script emits a clear diagnostic and explicitly exits non-zero.
19. Test R Code¶
Begin with a small standalone check. Create src/test_summary_function.R:
Run it:
As the project grows, use an approved R testing framework and keep tests small, deterministic, and independent of production data.
20. Capture the Environment¶
Write R and attached package information with the results:
dir.create("results", recursive = TRUE, showWarnings = FALSE)
writeLines(capture.output(sessionInfo()), "results/session-info.txt")
Record the R version from the shell:
For production work, preserve:
- R and package versions.
- Package repository sources.
- Script and configuration revisions.
- Input identifiers or checksums.
- Random seeds where relevant.
- Slurm job ID, standard output, and standard error.
- Expected and observed result checks.
21. Use Randomness Reproducibly¶
Set a documented seed before a stochastic operation:
The same R version and random-number behavior should reproduce the sequence. Record why a seed was chosen and whether independent parallel streams are required for a multi-task workload.
22. Run R Through Slurm¶
Create scripts/r-job.sh:
Replace PROJECT_CODE and QOS_NAME with the matching values from your
approved Deceema access, then submit from the project directory:
Use the returned ID:
Inspect the result, session information, and both logs after completion.
23. Write R Responsibly on Deceema¶
- Test with small, representative data before scaling.
- Use explicit input, output, configuration, and seed values.
- Do not depend on objects left in an interactive workspace.
- Avoid
setwd()with a personal hard-coded path. - Save tables and plots to explicit files.
- Do not install packages during every compute job.
- Inspect imported column types before analysis.
- Treat missing-value handling as an analytical decision.
- Avoid loading an entire large dataset when chunking is appropriate.
- Keep secrets out of scripts, arguments, histories, and logs.
- Run compute-intensive or long-lived analysis through Slurm.
24. Final Project: Summarize Multiple Datasets¶
Create another dataset:
Build src/summarize_many.R that:
- Accepts an output CSV followed by one or more input CSV files.
- Validates every file and its
valuecolumn. - Writes one summary row per input.
- Includes filename, count, mean, minimum, and maximum.
- Exits non-zero with a clear message if any input is invalid.
One possible solution:
Run it:
$ Rscript src/summarize_many.R \
results/all-summaries.csv \
data/measurements.csv data/more.csv
$ cat results/all-summaries.csv
Expected data:
You can now build a reproducible R workflow
You have learned R's core objects and control flow, transformed and summarized data, saved a plot, handled failures, recorded the environment, and prepared a non-interactive analysis for Slurm.
R Cheat Sheet¶
| Goal | R |
|---|---|
| Assign an object | name <- value |
| Create a vector | c(a, b, c) |
| Inspect structure | str(object) |
| Select values | object[index] |
| Select a column | data[["column"]] |
| Conditional | if (condition) { ... } |
| Loop | for (item in items) { ... } |
| Define a function | function(argument) { ... } |
| Read CSV | read.csv(path) |
| Write CSV | write.csv(data, path, row.names = FALSE) |
| Raise an error | stop("message") |
| Print a diagnostic | message("text") |
| Get script arguments | commandArgs(trailingOnly = TRUE) |
| Record environment | sessionInfo() |
Common Beginner Mistakes¶
| Mistake | Better habit |
|---|---|
Confusing = or <- with == |
Use assignment for names and == for comparison. |
| Forgetting R indexes begin at one | Verify selections on a small object. |
Comparing a value with NA |
Use is.na(). |
| Depending on an interactive workspace | Start scripts from explicit files and objects. |
Using setwd() with a personal path |
Pass paths as arguments or use a documented submission directory. |
| Ignoring warnings because the script completed | Review and resolve unexpected warnings. |
| Installing packages inside every job | Prepare and record the project environment beforehand. |
| Forgetting to close a graphics device | Call dev.off() after writing the plot. |
Omitting row.names = FALSE for data output |
Decide explicitly whether row names belong in the file. |
| Saving code without session details | Capture sessionInfo() with production results. |
Completion Checklist¶
- I can run R interactively and execute a script with
Rscript. - I understand basic types, vectors, missing values, and data frames.
- I can select, transform, group, and summarize data.
- I can use conditions, loops, vectorized operations, and functions.
- I read and write CSV files using explicit paths.
- I save plots to files for non-interactive jobs.
- I understand errors, warnings, and focused error handling.
- My scripts validate command-line arguments and input structure.
- I record
sessionInfo()and random seeds where relevant. - I completed and verified the multi-dataset final project.
Continue Learning¶
Learn Slurm fundamentals Explore the full jobs guide