Data Manipulation, Part 1

Indexing, subsetting, transforming, and cleaning data frames in base R, using AI-assisted prompting

Author
JP Adjadeh
Published
July 30, 2026

1. Introduction


The previous module established what a data frame, vector, factor, and list are, and introduced class() as the tool for confirming which one you are working with. This module builds directly on that foundation: rather than identifying structure, the focus now shifts to manipulating it — selecting specific rows and columns, filtering by condition, transforming values, and handling missing data.

The same discipline continues to apply. Each task below is completed by prompting an AI model for code, using the same role, task, context, and format elements established earlier — and every result is checked before it is trusted, not accepted simply because it ran.

This module uses base R only. A separate module introduces dplyr, which offers a different syntax for many of the same operations.

2. Indexing and Subsetting


Dataset: life_exp
Already imported as: life_exp

A data frame can be indexed using the pattern data[row, column]. Leaving either position blank selects all rows or all columns. R uses 1-based indexing — the first row or column is position 1, not 0.

ⓘ Note — Single vs. Double Brackets
life_exp[1, 1] and life_exp[[1, 1]] return the same value but not the same type. A single bracket keeps the result inside a data frame; a double bracket extracts the raw value on its own. This distinction becomes relevant when a later step expects a plain value rather than a one-cell data frame. Run both and compare with class() before continuing.
Code
class(life_exp[1, 1])
class(life_exp[[1, 1]])

Segment 1: Selecting rows and columns

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [return only the Year and Average_Life_Expectancy columns for the first 10 rows].
⚠ Exercise 1
Run your prompt. Then, without asking AI, write a second line of indexing code that returns the same result using column names instead of column numbers. Confirm both approaches produce identical output.
Guide code
life_exp[1:10, c("Year", "Average_Life_Expectancy")]
life_exp[1:10, c(1, 3)]

Segment 2: Extracting a single value

Mission Card — complete the prompt, then run it
You are a [role]. Using the life_exp data frame, write R code to [extract the Average_Life_Expectancy value from row 50 as a plain number rather than a one-cell data frame], then confirm its class.
⚠ Exercise 2
Run your prompt. Then run life_exp[50, "Average_Life_Expectancy"] using a single bracket instead, and check its class as well. State which of the two approaches returned a plain value automatically, and which required the double bracket to do so.
Guide code
life_exp[[50, "Average_Life_Expectancy"]]
class(life_exp[[50, "Average_Life_Expectancy"]])

life_exp[50, "Average_Life_Expectancy"]
class(life_exp[50, "Average_Life_Expectancy"])

3. Subsetting with Conditions


Dataset: air
Already imported as: air

Rows can be filtered using a logical condition in place of a row number. R evaluates the condition for every row and keeps only the ones where it is TRUE. Multiple conditions can be combined with & (and) and | (or).

Segment 1: A single condition

Mission Card — complete the prompt, then run it
You are a [role]. Using the air data frame, write R code to [return only the rows where Year is 2015].
⚠ Exercise 3
Run your prompt. Record how many rows meet the condition.
Guide code
air_2015 <- air[air$Year == 2015, ]
nrow(air_2015)

Segment 2: Combining conditions

Mission Card — complete the prompt, then run it
You are a [role]. Using the air data frame, write R code to [return the Place_Name and Data.Value columns for rows where Type_Name is "UHF42" and Data.Value is greater than 10].
⚠ Exercise 4
Run your prompt. Identify which operator the code uses to require both conditions to hold at once, rather than either one.
Guide code
air[air$Type_Name == "UHF42" & air$Data.Value > 10,
    c("Place_Name", "Data.Value")]

Segment 3: A three-part condition

Mission Card — complete the prompt, then run it
You are a [role]. Using the air data frame, write R code to [return the Name and Data.Value columns for rows where Type_Name is "UHF42", and Year is either 2015 or 2020].
⚠ Exercise 5
Run your prompt. Identify which part of the condition is joined with & and which part is joined with |, and explain why parentheses are needed around the year condition.
Guide code
air[air$Type_Name == "UHF42" & (air$Year == 2015 | air$Year == 2020),
    c("Name", "Data.Value")]

4. Transforming Data


Dataset: ev
Already imported as: ev

Transformation covers three common operations: renaming a column, creating a new column from existing ones, and converting a column to a different data type.

Segment 1: Renaming a column

Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [rename the Base MSRP column to Price].
⚠ Exercise 6
Run your prompt. Use names(ev) to confirm the column was renamed and that no other column names changed.
Guide code
names(ev)[names(ev) == "Base MSRP"] <- "Price"
names(ev)

Segment 2: Creating a new variable

Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [create a new column called Vehicle_Age, calculated as 2026 minus Model_Year].
⚠ Exercise 7
Run your prompt. Independently verify the calculation for one row by locating its Model_Year and computing the age by hand.
Guide code
ev$Vehicle_Age <- 2026 - ev$Model_Year
head(ev[, c("Model_Year", "Vehicle_Age")])

Segment 3: Changing a data type

Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [convert the Make column to a factor and show me its class before and after].
⚠ Exercise 8
Run your prompt. This mirrors a conversion from the previous module — name which data structure this operation involves.
Guide code
class(ev$Make)
ev$Make <- as.factor(ev$Make)
class(ev$Make)

Segment 4: Normalizing a numeric column

ⓘ Note — Min-Max Normalization
A common way to rescale a numeric column to a 0–1 range is: normalized value = (value − minimum) / (maximum − minimum). The smallest value in the column becomes 0, the largest becomes 1, and everything else falls proportionally in between.
Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [create a new column called normalized_range, rescaling Electric_Range to a 0–1 range using min-max normalization].
⚠ Exercise 9
Run your prompt. Confirm that the minimum value in normalized_range is 0 and the maximum is 1.
Guide code
ev$normalized_range <- (ev$Electric_Range - min(ev$Electric_Range, na.rm = TRUE)) /
  (max(ev$Electric_Range, na.rm = TRUE) - min(ev$Electric_Range, na.rm = TRUE))

min(ev$normalized_range, na.rm = TRUE)
max(ev$normalized_range, na.rm = TRUE)

5. Missing Values


Dataset: ev
Already imported as: ev

Real datasets rarely arrive complete. Before deciding what to do about missing values, it is necessary to know where they are and how many there are.

Segment 1: Counting missing values

Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [report the total number of missing values, and the number of missing values in each column separately].
⚠ Exercise 10
Run your prompt. Identify which column has the most missing values.
Guide code
sum(is.na(ev))
colSums(is.na(ev))

Segment 2: Inspecting and removing missing values

Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [show me the rows where the County column is missing, then remove those rows and confirm the new row count].
⚠ Exercise 11
Before running your prompt, answer this: is it appropriate to remove every row with a missing County value for this dataset, or only rows where County is missing for a reason that matters to your analysis? There is no single correct answer — the point is that this is a judgment call AI cannot make for you.
Guide code
ev[is.na(ev$County), ]
ev_clean <- ev[!is.na(ev$County), ]
nrow(ev_clean)

Segment 3: Comparing results with and without missing data

Mission Card — complete the prompt, then run it
You are a [role]. Using the ev data frame, write R code to [compute the average Electric_Range twice — once using all rows, and once after removing rows where Electric_Range is missing — and show both results together].
⚠ Exercise 12
Run your prompt. State whether removing the missing values changed the average, and explain why the na.rm = TRUE argument used earlier in this module produces the same effect without a separate removal step.
Guide code
mean(ev$Electric_Range, na.rm = FALSE)
mean(ev$Electric_Range, na.rm = TRUE)
ⓘ Note
AI can generate the code to remove missing values in seconds. It cannot tell you whether removing them is the right call for your specific analysis — that decision depends on why the data is missing and what the downstream analysis needs, both of which require judgment the model does not have access to.

Conclusion


This module covered four core manipulation tasks in base R: indexing and subsetting a data frame by position and by name, filtering rows with single and combined logical conditions, transforming a data frame through renaming, new variable creation, and type conversion, and identifying and handling missing values. Each task was completed by prompting an AI model for code and verifying the result — recomputing a value by hand, checking a row count, or comparing an approach against an independent method.

A separate module continues this work using dplyr, which offers an alternative, verb-based syntax for many of the same operations.