21 From Excel to R
Every result produced across the last nine sections can be produced in R, usually in fewer steps and always with a record of how it was done. This closing section runs the same analyses on the same numbers and compares the two, then hands over to Fundamentals of R and R Studio, where the language itself begins.
The point is not that Excel was a waste of time. Inspecting a new file, checking whether the values are plausible, and building a summary someone can open without installing anything remain faster in Excel than anywhere else. The point is what happens after that first look.
21.1 What Changes When the Analysis Becomes Code
A spreadsheet stores the answer. A script stores the question, which is why the script can be checked, corrected, and rerun while the answer alone cannot. Power Query, described in the previous section, closes much of that gap within Excel itself, and is the right choice when the work stays in Excel.
21.2 Reading the Workbook
The readxl package reads .xlsx files directly, with no need to export to CSV first and no dependency on Java or on Excel being installed.
Run this in RStudio, with agri-analytics-sample.xlsx in the working directory:
install.packages("readxl") # once only
library(readxl)
farms <- read_excel("agri-analytics-sample.xlsx", sheet = "FarmData")
crops <- read_excel("agri-analytics-sample.xlsx", sheet = "CropLookup")
trials <- read_excel("agri-analytics-sample.xlsx", sheet = "TrialResults")
str(farms)
head(farms)
summary(farms)str() is the first thing to run on any imported file. It reports the type R inferred for each column, and it is where a yield column that arrived as chr rather than num announces itself immediately, which is the same numbers-stored-as-text defect from Data Cleaning and Preparation caught at the door rather than three steps later.
Useful arguments on read_excel():
read_excel(path, sheet = "FarmData", range = "A1:K61") # an explicit range
read_excel(path, sheet = 1, skip = 3) # skip title rows above the header
read_excel(path, sheet = "FarmData", na = c("", "N/A", "NA")) # treat these as missing
read_excel(path, sheet = "FarmData", col_types = "text") # read everything as text
excel_sheets(path) # list the sheet names firstThe na argument is the direct answer to defect 9 from the cleaning section. Listing "N/A" there turns that literal text into a genuine missing value at import, so the column arrives numeric instead of character.
The code blocks below run in the browser rather than in RStudio, and the browser has no access to a file on your machine, so they rebuild the same 60 farms from the workbook’s own values instead of reading the file. The numbers are identical either way.
21.3 The Same Analysis, Both Ways
Four of the summaries produced earlier in this topic, reproduced in R. The group summary corresponds to the PivotTable, the correlations to the Correlation tool, and the regression to the Regression tool in Statistical Analysis with the Analysis ToolPak.
21.4 The Regression, Side by Side
The Analysis ToolPak’s Regression tool needed its predictor columns to be adjacent, produced a static block of numbers, and stopped there. The same model in R is one line, and the object it returns can be queried further.
Every number the ToolPak reported appears here under a different heading. Multiple R Squared is Excel’s R Square at 0.5960. Adjusted R-squared is 0.5743. Residual standard error is Excel’s Standard Error at 4.057. The F-statistic line carries the F value of 27.54 on 3 and 56 degrees of freedom and the p-value that Excel calls Significance F. The coefficient table gives the same estimates, standard errors, t values, and p-values, with significance stars added.
What comes free, and had no equivalent in the ToolPak: confint() for the intervals, plot(model) for four diagnostic charts in one command, predict(model, newdata) for predictions on new farms, and the whole fitted object available to any other function that accepts a model.
21.5 The Trial Tests
The t-test and ANOVA from the ToolPak section, on the same TrialResults values.
The Welch t-test returns t of -3.7731 on 23.4 degrees of freedom with p of 0.000960, matching the ToolPak output exactly apart from the degrees of freedom, which Excel rounds to 23 for its critical values while R keeps the fractional value. The ANOVA returns F of 10.175 on 2 and 42 degrees of freedom with p of 0.000249, again matching.
The last block has no Excel equivalent at all. TukeyHSD() answers the question the ANOVA raised and could not settle: not whether some treatment differs, but which ones. It reports each pairwise difference with a confidence interval and an adjusted p-value that accounts for having made three comparisons. A significant ANOVA followed by eyeballing the group means, which is where the Excel version has to stop, is exactly the inference this function exists to replace.
21.6 Function Translation
| Excel | R |
|---|---|
SUM(range) |
sum(x) |
AVERAGE(range) |
mean(x) |
MEDIAN, MIN, MAX
|
median(x), min(x), max(x)
|
STDEV.S, VAR.S
|
sd(x), var(x)
|
COUNT, COUNTA
|
sum(!is.na(x)), length(x)
|
COUNTIF(r, "Guntur") |
sum(x == "Guntur") |
SUMIFS(sum_r, r, crit) |
sum(y[x == crit]) |
AVERAGEIFS |
mean(y[x == crit]) |
IF(test, a, b) |
ifelse(test, a, b) |
IFERROR(expr, alt) |
tryCatch(expr, error = function(e) alt) |
VLOOKUP / XLOOKUP
|
merge(x, y, by = "key") |
TRIM, UPPER, LOWER
|
trimws(x), toupper(x), tolower(x)
|
LEFT, RIGHT, MID
|
substr(x, start, stop) |
LEN |
nchar(x) |
Remove Duplicates |
unique(df) or df[!duplicated(df), ]
|
Sort |
df[order(df$col), ] |
AutoFilter |
subset(df, condition) or df[condition, ]
|
| PivotTable |
tapply(), aggregate(), or table()
|
Text to Columns |
strsplit(x, "-") |
RANK.EQ |
rank(-x) |
PERCENTILE.INC |
quantile(x, p) |
CORREL |
cor(x, y) |
T.TEST |
t.test(x, y) |
F.TEST |
var.test(x, y) |
| ANOVA tool | aov(y ~ group) |
| Regression tool | lm(y ~ x1 + x2) |
| Descriptive Statistics tool | summary(df) |
| Power Query Unpivot |
reshape(), or tidyr::pivot_longer()
|
| Power Query Merge | merge(x, y, all.x = TRUE) |
FORECAST.LINEAR |
predict(model, newdata) |
One structural difference runs through the whole table. An Excel formula operates on one cell and is then copied down; an R function operates on the entire column at once. That is why a lookup across 60 farms is 60 formulas in Excel and one merge() in R, and why the R version reports a failed match as a visible NA in a known row rather than scattering #N/A down a column for someone to notice later.
21.7 What Carries Over
The habits built in this topic do not get discarded at the door.
The tidy structure from Basics of Excel, one variable per column and one observation per row, is exactly what R expects from a data frame. A sheet organised that way imports cleanly; one with merged cells and multi-row headers does not, and the difficulty of importing it is a reasonably good measure of how badly it was organised.
The cleaning checks transfer directly. COUNT against COUNTA becomes sum(!is.na(x)) against length(x). The UNIQUE audit of a category column becomes table(x), which additionally shows how many of each. The duplicate hunt becomes duplicated(df).
Reading regression output transfers with only the labels changing, as the side-by-side above showed.
And the judgement transfers completely. Which chart answers which question, why a correlation is not a cause, when an average needs its sample size shown beside it, whether a significant difference is a difference worth acting on. None of that is a property of the software.
What R adds is that the work can be rerun, reviewed, and trusted by someone who was not in the room when it was done. Fundamentals of R and R Studio starts there.
Summary
| Concept | Description |
|---|---|
| Why Move to R | |
| Manual versus Scripted Workflow | A spreadsheet stores the answer; a script stores the question, so it can be checked and rerun |
| Reading a Workbook with readxl | read_excel() takes a sheet name or range directly, with no CSV export and no Java dependency |
| str() as the First Check | Reports the type R inferred per column, catching numbers-stored-as-text at the door |
| Handling N/A at Import | The na argument turns literal N/A text into genuine missing values during import |
| The Same Analyses | |
| PivotTable as tapply and aggregate | Group-then-aggregate in one call, producing the same cross-tabulation as a PivotTable |
| Correlation Matrix in One Call | cor() across several columns replaces the Correlation tool and its matrix output |
| Regression with lm() | One line fits the same model the Regression tool fits, with no adjacency requirement |
| Matching the ToolPak Output | R Square, adjusted R Square, residual standard error, F and the coefficient table all correspond |
| What lm() Adds | confint, plot for diagnostics, predict for new data, and a fitted object usable elsewhere |
| t.test and aov | The same Welch, paired and one-way tests, returning identical statistics |
| TukeyHSD | The post-hoc test Excel lacks, identifying which specific pairs differ after a significant ANOVA |
| Translation | |
| Function Translation Table | A direct mapping from each Excel function to its R equivalent |
| Column-Wise versus Cell-Wise | Excel formulas act on one cell and are copied; R functions act on the whole column at once |
| What Carries Over | Tidy structure, the cleaning checks, output interpretation, and analytical judgement all transfer |