Quick summary
Summarize this blog with AI
Introduction
Learn R by completing one small analysis: import supplied sales records, inspect their types, calculate revenue, summarize it and draw a chart. Every object used below is created in the lesson. Run from the top in a fresh session so the result does not depend on forgotten workspace objects.
Tested with R 4.5.1, dplyr 1.1.4 and ggplot2 4.0.0. Run the code blocks in order. Install the named packages before starting; inputs are supplied below.
Start with an R script
Install R from CRAN. RStudio is an optional editor available from Posit; R is the language runtime. Save a script, type into its source pane and use Run or Source to execute it. The console shows text results and the plot pane shows figures. Install dplyr and ggplot2 once with install.packages(c("dplyr", "ggplot2")); library() loads an installed package in a session.
print("Hello, R!")
x <- c(2, 3, 0)
print(x * 10)
print(mean(x))
stopifnot(identical(x * 10, c(20, 30, 0)))
The first result is “Hello, R!”; the vector multiplication returns 20,30,0. The mean is approximately 1.6667. <- assigns a value, c() combines values into a vector, and # begins a comment. R names are case-sensitive: sales and Sales are different objects.
Read an embedded CSV with explicit column types
A data frame is a table whose columns may have different types. This fixture has character IDs, text dates and regions, and numeric quantities/prices. Reading IDs as text preserves leading zeros. NA is a missing value, not zero.
csv <- "id,date,region,units,unit_price
001,2026-01-01,East,2,10
002,2026-01-01,West,3,10
003,2026-01-02,East,NA,10
004,2026-01-02,West,0,12
005,2026-01-03,East,1,20
006,2026-01-03,West,2,15"
sales <- read.csv(text = csv, na.strings = "NA", strip.white = TRUE,
colClasses = c("character", "character", "character", "numeric", "numeric"))
sales$date <- as.Date(sales$date)
str(sales)
print(head(sales))
stopifnot(nrow(sales) == 6, sales$id[1] == "001", inherits(sales$date, "Date"),
sum(is.na(sales$units)) == 1)
There are 6 rows and 5 columns. units has one missing entry, while row 004 has a genuine observed zero. For a real file, use its path as read.csv()’s file argument and keep the same explicit schema.
Select columns, rows and values deliberately
An atomic vector has one underlying type; a matrix adds dimensions to a vector. A list can hold objects of different types and sizes. A data frame is a list of equal-length columns. R indexes start at 1. Use drop = FALSE when a selected data frame must remain a table.
unit_vector <- sales$units
one_column <- sales[, "units", drop = FALSE]
first_unit <- unit_vector[1]
notes <- list(rows = nrow(sales), ids = sales$id, first = first_unit)
print(notes$rows)
stopifnot(is.numeric(unit_vector), is.data.frame(one_column), first_unit == 2,
identical(dim(as.matrix(one_column)), c(6L, 1L)))
Transform without erasing missing observations
dplyr::mutate() adds columns and filter() keeps rows whose condition is TRUE. The native pipe |> passes the result of one step into the next. Keep the full derived table and create a separate analysis subset; a missing quantity means unknown revenue rather than zero revenue.
library(dplyr)
derived <- sales |> mutate(revenue = units * unit_price)
observed <- derived |> filter(!is.na(revenue))
print(observed[c("id", "revenue")])
stopifnot(nrow(derived) == 6, nrow(observed) == 5,
identical(observed$revenue, c(20, 30, 0, 20, 30)), is.na(derived$revenue[3]))
observed_mean <- function(x) {
if (!is.numeric(x)) stop("Expected numeric data")
x <- x[!is.na(x)]
if (length(x)) mean(x) else NA_real_
}
print(observed_mean(derived$revenue))
stopifnot(observed_mean(derived$revenue) == 20,
is.na(observed_mean(numeric())), is.na(observed_mean(c(NA_real_, NA_real_))))
The observed mean revenue is 20 across 5 records, including the zero. The omitted sixth record remains visible in derived. Functions encapsulate a decision—in this case returning NA when no observations exist—so later steps can reuse it consistently.
Summarize with a visible denominator
Grouping changes the unit of analysis from an individual record to a region. Report observed and missing counts beside observed revenue totals. A total of observed entries is not necessarily the total revenue of a region with missing measurements.
regional <- derived |>
group_by(region) |>
summarise(records = n(), observed = sum(!is.na(revenue)), missing = sum(is.na(revenue)),
observed_revenue = if (all(is.na(revenue))) NA_real_ else sum(revenue, na.rm = TRUE),
.groups = "drop")
print(regional)
stopifnot(identical(regional$region, c("East", "West")),
identical(regional$observed_revenue, c(40, 60)),
identical(regional$missing, c(1L, 0L)))
East has 3 records,2 observed revenues,1 missing revenue and 40 observed units of revenue. West has 3 observed revenues totaling 60. This description is about these six supplied records, not a broader market estimate.
Plot the result you actually calculated
geom_col() uses supplied heights; geom_bar() counts rows by default. The chart below therefore uses the regional summary, with a caption warning that East has an unknown revenue record.
library(ggplot2)
p <- ggplot(regional, aes(region, observed_revenue)) +
geom_col(fill = "#377e99", width = 0.65) +
geom_text(aes(label = observed_revenue), vjust = -0.4) +
scale_y_continuous(limits = c(0, 70)) +
labs(title = "Observed revenue by region", x = "Region", y = "Revenue (units)",
caption = "East has one unknown revenue record; totals include observed values only.") +
theme_minimal(base_size = 12)
print(p)
stopifnot(identical(ggplot_build(p)$data[[1]]$y, c(40, 60)))
Export and rerun the script from a clean session
This round-trip demonstration writes only a newly allocated temporary file. For a deliverable, choose a new output path explicitly and avoid overwriting your source data. When a script fails, read the first error, inspect names(), str() and the affected rows, and rerun the preceding steps. stopifnot() checks assumptions and is silent when they hold.
output <- tempfile(fileext = ".csv")
write.csv(regional, output, row.names = FALSE)
imported <- read.csv(output)
stopifnot(identical(imported$region, c("East", "West")),
all(imported$observed_revenue == c(40, 60)), !"X" %in% names(imported))
unlink(output)
stopifnot(nrow(derived[FALSE, , drop = FALSE]) == 0)
print("Analysis checks passed")
Next, use the focused lessons on detecting missing values, left joins and chart ordering. The R manuals explain the underlying language and data-import behavior.