From the console to your first plot with the tree leaf data
2026-07-05
data/, scripts/, figures/<-# commentsread_excel() and from CSV with read_csv()head(), glimpse(), and dim()%>% to chain stepsggplot figureHow to use this worksheet
Work through each section at your own pace. Type the code into your script file in Positron and run it line by line. Code blocks marked ▶ Run this are the ones you should execute. Blocks marked ✏️ Your turn ask you to write or modify something. The Going further section is optional — work through it if you finish early.
⚠️ Watch out! Install R first, then Positron. If you installed them in the wrong order, reinstall R and then restart Positron.
Four panes you will use constantly:
| Pane | Where | What it does |
|---|---|---|
| Editor | Top-left | Your scripts live here |
| Console / Terminal | Bottom | Where code runs and results appear |
| Variables / Session | Right | Every object you have stored |
| Plots | Right (tab) | Your figures appear here |
✏️ Your turn: Click the Console tab. Type 1 + 1 and press Enter. What did R return?
Your answer:
In R we keep everything for one project together in one folder. Every file path is then relative to that folder — no more C:/Users/myname/Desktop/random_stuff/final_FINAL2.xlsx nightmares.
On your computer, make a new folder called tree_project. Inside it, create these three sub-folders:
tree_project/
├── data/ ← your Excel and CSV files go here
├── scripts/ ← your .R code goes here
└── figures/ ← plots you save go here
💡 Coming from Excel? In Excel you tend to keep one giant workbook. In R we keep raw data untouched and write new processed files — so you can always trace your steps and never overwrite your original numbers.
In Positron: File → Open Folder… and pick your tree_project folder.
You should see tree_project appear in the Explorer panel on the left.
✏️ Your turn: What is the full path to your tree_project folder on your computer?
Your answer:
Copy 2026_06_25_tree_experiment_raw_data.xlsx into tree_project/data/.
⚠️ Important: Leave this copy alone — it is your raw data. We will read it but never overwrite it.
02_tree_analysis.R,tree_project/scripts/.💡 Key idea: Console = scratch paper. Script = the lab notebook you keep.
✏️ Your turn: What is your script named?
Your answer:
Type each line below into your Console (not the script yet) and press Enter after each one.
▶ Run this in the Console:
⚠️ Watch out! If you see a
+at the start of a console line instead of the>prompt, R is waiting for you to finish a command. Press Esc to cancel and start over.
✏️ Your turn: What does 2 ^ 10 return? What does the ^ operator do?
Your answer:
<-To keep a value, give it a name using the assignment operator <-.
Shortcut: Alt + − (Windows) or Option + − (Mac) types <- for you.
▶ Run this in the Console:
Look at the Variables pane on the right — x should appear there.
✏️ Your turn: Store the number 42 under the name my_number, then multiply my_number by x. What is the result?
Your answer:
Good names make code readable months later:
leaf_mass not x2x2 ✅, 2x ❌weight_g ≠ Weight_glower_snake_case — words separated by underscores, all lowercase✏️ Your turn: Which of these names are valid in R? Write Y or N next to each.
leaf_mass —
3rd_leaf —
Width_cm —
my.leaf.data —
side —
#Anything to the right of # is ignored by R — it is a note for humans. Shortcut to toggle: Ctrl/Cmd + Shift + C.
▶ Run this in your Script:
💡 Key idea: If a line confused you while writing it, write a comment explaining why you did it.
A function is a mini-program you call by name. You feed it arguments (inputs) inside ( ).
▶ Run this in the Console:
To get help on any function:
✏️ Your turn: Use round() to round pi (R knows pi by name — just type it) to 4 decimal places.
Your answer:
A vector is a series of values built with c() — the combine function. A spreadsheet column is really just a vector.
▶ Run this in your Script:
⚠️ Watch out! Quotes matter:
"sunny"is text. Without quotes, R searches for an object namedsunnyand throws an error when it cannot find one.
✏️ Your turn: Make a character vector called my_colors with three color names of your choice. Check its length() and class().
Every vector holds one type:
| Type | Example | Notes |
|---|---|---|
numeric |
4, 3.5 |
All numbers by default |
character |
"sunny" |
Any text, always quoted |
logical |
TRUE / FALSE |
Must be all-caps |
Mix types and R silently converts everything to the most flexible type — numbers become text, TRUE becomes 1. This can cause surprising bugs.
Base R is powerful but lean. Packages add new functions — like apps for your phone.
Install once — run this in the Console (not in your script):
Load every session — put this at the top of every script you write:
⚠️ Watch out! If R says “could not find function
read_excel”, you forgotlibrary(readxl). You must reload libraries every time you restart R.
▶ Run this at the very top of your Script:
✏️ Your turn: What message does R print after library(tidyverse)? Write the first line here:
Your answer:
▶ Run this in your Script:
💡 Coming from Excel?
read_excel()is the bridge from your spreadsheet into R. Your sheet’s first row becomes the column names in R.
The path "data/..." is relative to your project folder. It works on anyone’s computer — not just yours.
read_csv()Many public datasets (NOAA climate data, iNaturalist, eBird) come as .csv files. The tidyverse provides read_csv() for these:
💡 Key difference:
read_excel()needslibrary(readxl).read_csv()comes withlibrary(tidyverse)— no extra install needed.
A data frame is R’s word for a spreadsheet-style table. Each column is a vector of one type; the rows line up.
Our tree_df has five columns:
| Column | Type | What it holds |
|---|---|---|
index |
numeric | leaf number (1–20) |
side |
character | "sunny" or "shady" |
weight_g |
numeric | leaf weight in grams |
width_cm |
numeric | leaf width in centimetres |
height_cm |
numeric | leaf height in centimetres |
Pull a single column with $:
Always eyeball a new dataset before doing any analysis.
▶ Run each of these in your Script:
head(tree_df) # first 6 rows
# glimpse() is the tidyverse way — one line per column
glimpse(tree_df) # column names, types, first values
# Base R equivalents (give similar info)
dim(tree_df) # (rows, columns)
names(tree_df) # column names
str(tree_df) # type of every column
summary(tree_df) # min / mean / max per column💡 Prefer
glimpse()overstr()— it is the tidyverse-friendly version and is easier to read.
✏️ Your turn: Answer these questions from the output above.
How many rows does tree_df have?
How many columns?
What type does R report for `side`?
What is the mean weight_g?
What is the maximum height_cm?
⚠️ Watch out! If a number column shows as
<chr>inglimpse(), a stray letter or comma snuck into your spreadsheet. Check your raw data file.
%>% — read it as “then”The pipe sends a result straight into the next function. Read %>% as the word “then”.
▶ Run this in your Script:
💡 Two pipes — same idea:
%>%— from tidyverse (we use this one throughout the course) The native pipe (a vertical bar then greater-than sign) — built into R 4.1+; you will see it in code online They behave identically for everything in this course.
✏️ Your turn: Using the pipe, write code to show only the last 3 rows of tree_df. Hint: look at tail().
In Lecture 03 we will use %>% with group_by() and summarize() to compute statistics by group — this is where the pipe really pays off.
Every ggplot is built in three pieces, joined with +:
ggplot(data, aes(...)) — which data frame, which columns map to x and ygeom_*() — the shape to draw (points, boxes, bars, …)labs() — axis labels and title▶ Run this in your Script — the simplest possible plot:
⚠️ Watch out! The
+must sit at the end of a line, never the start. A+at the start causes an error.
▶ Run this in your Script:
✏️ Your turn: Make the same plot but for height_cm instead of weight_g. Copy the code above and change the relevant parts.
What pattern do you see — do sunny or shady leaves tend to be taller?
Your answer:
Store the plot in an object, then save it to your figures/ folder.
▶ Run this in your Script:
# Save the weight plot — always use PNG, always use dpi = 300
weight_plot <- ggplot(tree_df, aes(x = side, y = weight_g)) +
geom_boxplot() +
geom_jitter(width = 0.15, alpha = 0.6, color = "tomato") +
labs(x = "Side of tree",
y = "Leaf weight (g)",
title = "Sunny vs. shady leaves")
ggsave("figures/leaf_weight.png",
plot = weight_plot,
width = 6,
height = 6,
units = "in",
dpi = 300)Check your figures/ folder — the PNG should be there.
⚠️ Watch out!
ggsave()wants the filename first, thenplot =. Mixing that order is the classic first-save mistake.
💡 Always use
dpi = 300— that is publication quality (300 dots per inch). The default (72 dpi) looks blurry in reports and on posters.
At this point you can:
✏️ Your turn — before you move on: Run the full script from top to bottom by pressing Ctrl/Cmd + Shift + Enter (runs the whole file). Does it complete without errors?
Did it run cleanly? Y / N
If not, what error did you see?
This section is optional — work through it if you finish early or want to explore. There are no wrong answers here; the goal is to see what ggplot can do.
▶ Try this:
✏️ Your turn: Does width predict height? Do the two groups cluster separately or overlap?
Your observation:
▶ Try this:
✏️ Your turn: Do the slopes look similar or different for sunny vs. shady leaves? What might that mean biologically?
Your observation:
▶ Try this:
✏️ Your turn: What does the width of the violin show you that a boxplot does not?
Your observation:
Facets split one plot into panels, one per group.
▶ Try this:
✏️ Your turn: Do the histograms look roughly symmetric, or skewed? Are the shapes similar between sunny and shady?
Your observation:
ggplot comes with built-in themes. Try swapping by adding one to any plot above:
✏️ Your turn: Which theme do you prefer for this kind of biological comparison data? Why?
Your answer:
▶ Try this:
scatter_plot <- ggplot(tree_df, aes(x = width_cm, y = height_cm, color = side)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE) +
labs(x = "Leaf width (cm)", y = "Leaf height (cm)", color = "Side") +
theme_bw()
ggsave("figures/leaf_dimensions_scatter.png",
plot = scatter_plot,
width = 7,
height = 5,
units = "in",
dpi = 300)✏️ Your turn: Why do we save as .png rather than .pdf for most class submissions?
Your answer:
After working through this worksheet your tree_project/ folder should contain:
tree_project/
├── data/
│ └── 2026_06_25_tree_experiment_raw_data.xlsx <- never touch this
├── scripts/
│ └── 02_tree_analysis.R <- your complete script
└── figures/
├── leaf_weight.png <- Part 8
└── leaf_dimensions_scatter.png <- Going further
This is the project structure we will use for every analysis in this course:
| Folder | Contents | Rule |
|---|---|---|
data/ |
All data files | Read only — never overwrite |
scripts/ |
.R code files |
One script per topic |
figures/ |
Saved plots | Always PNG, always dpi = 300 |
When code breaks — and it will, that is normal — try these in order:
library(tidyverse) and library(readxl)? Spelling? Missing ) or + at the start of a line??function_name opens the built-in help page.💡 Key idea: Every working data scientist googles error messages daily. Getting stuck is not failing — it is the job.