Activity — Introduction to R and Positron
From the console to your first plot with the leaf data
Install R and Positron, organize a project folder, and take your first steps with vectors, functions, and loading the leaf data into R.
Meeting R
Recap from Activity 1
- Collected leaf samples from sunny and shady sides of trees
- Identified the independent variable (shade of tree) and dependent variables (leaf mass, dimensions)
- Measured and recorded leaf mass, petiole length, and thickness in a spreadsheet
- Agreed on column names, units, and metadata conventions
- Finding: shady leaves appeared larger — today we start exploring this in R
Today’s Objectives
- Get R and Positron installed and oriented
- Organize a project folder the right way —
data/,scripts/,figures/ - Use R as a calculator and store values with
<- - Write and run a script with
#comments - Call functions, build vectors, understand data types
- Load the leaf data from Excel with
read_excel()(and see howread_csv()differs) - Take a first look at a data frame with
glimpse() - Use the pipe
%>%to chain steps - Make and save a first
ggplotfigure
How this activity works
You are building one R script this whole class, and you turn it in. Create it in Part 2 and name it
scripts/02_leaf_analysis.R.
- Every line you run goes in that script — the Console is scratch paper, the script is the lab notebook you keep and hand in. (A few Part 3 warm-ups say “in the Console” — those are the only exceptions.)
- Start every code chunk in your script with a short
#comment saying what it does. The comments are part of the grade.- The top of your script, in order: a title comment, then your
library()calls, then the line that loads the data intoleaf_df.- Code marked ▶ Run this is typed into your script exactly as shown. Code marked ✏️ Your turn is a change you make and run. Type it — don’t paste.
- The Going further section is optional — work through it if you finish early.
Part 1 · Set up R and Positron
Download and install — in this order
- R (the engine) — https://cran.r-project.org/
- Choose your operating system, run the installer, accept all defaults
- Positron (the editor) — https://positron.posit.co/download.html
- Install after R — Positron needs to find R already on your machine
- Open Positron and verify it finds R (check the bottom status bar)
⚠️ Watch out! Install R first, then Positron. If you installed them in the wrong order, reinstall R and then restart Positron.
Orient yourself in 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:
Part 2 · Build your project folder
Why a project folder?
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.
Create the folder structure
On your computer, make a new folder called tree_project. Inside it, create these sub-folders:
tree_project/
├── data/ ← your Excel and CSV files go here
├── scripts/ ← your .R code goes here
├── output/ ← your cleaned data go 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.
Open the folder as your workspace in Positron
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:
Get the data file
2026_09_03_data_sci_leaf_area.xlsx— the Excel version (this is the one we use)2026_09_03_data_sci_leaf_area.csv— the same data as a CSV
Download both and put them in tree_project/data/.
⚠️ Important: Leave this copy alone — it is your raw data. We will read it but never overwrite it.
Create your first script
- In Positron:
- File → New File → R File.
- Save it immediately with Ctrl/Cmd + S, name it
02_leaf_analysis.R, - and save it inside
tree_project/scripts/.
💡 Key idea: Console = scratch paper. Script = the lab notebook you keep and turn in.
Part 3 · How R works
R as a calculator
Type each line below into your Console and press Enter after each one. (These few warm-ups are the exception to the “everything goes in the script” rule.)
▶ Run this in the Console:
3 + 5
12 / 7
2 ^ 10
sqrt(144)⚠️ 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:
Storing values with <-
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:
x <- 7 # store 7 under the name x
x # type the name to see it
x * 2 # use it in mathLook 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:
Naming things well
Good names make code readable months later:
- Be explicit but not too long:
leaf_massnotx2 - Cannot start with a number —
x2✅,2x❌ - R is case-sensitive —
mass_g≠Mass_g - Use
lower_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. You can try it too!
leaf_mass —
3rd_leaf —
Petiole_mm —
my.leaf.data —
shade —
Functions and arguments
A function is a mini-program you call by name. You feed it arguments (inputs) inside ( ).
▶ Run this in the Console:
sqrt(10) # one argument
round(3.14159) # default: 0 decimal places → 3
round(3.14159, 2) # two decimal places → 3.14
round(x = 3.14159, digits = 2) # same, with named argumentsVectors — a row of values
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:
# build one numeric and one character vector
mass_g <- c(4, 3, 5, 7) # numeric vector
shade <- c("sunny", "shady") # character vector — needs quotes
length(mass_g) # how many values?
class(mass_g) # what type is it?⚠️ Watch out! Quotes matter:
"sunny"is text. Without quotes, R searches for an object namedsunnyand throws an error when it cannot find one.
Data types inside vectors
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.
Part 4 · Packages and libraries
What is a package?
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):
install.packages("tidyverse")
install.packages("readxl")
install.packages("janitor")Load every session — these go 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.
▶ Type this at the very top of 02_leaf_analysis.R:
# ---- Activity 02: leaf data ------------------------------
# your name, today's date
# ---- Libraries ------------------------------------------
library(tidyverse) # data wrangling + ggplot2 for plotting
library(readxl) # reading Excel files
library(janitor) # cleans up messy column namesPart 5 · Load the leaf data
read_excel() — the bridge from a spreadsheet into R
▶ Add this to your script, just below the libraries:
# ---- Load data -----------------------------------------
# path is relative to your project folder; clean_names() tidies the headers
leaf_df <- read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
clean_names()
leaf_df # print it to the Console💡 Coming from Excel?
read_excel()is the bridge from your spreadsheet into R. Your sheet’s first row becomes the column names.clean_names()(fromjanitor) then tidies messy headers like"Leaf Mass (g)"intoleaf_mass_gautomatically.
The path "data/..." is relative to your project folder. It works on anyone’s computer — not just yours.
The same data also comes as a CSV
Many public datasets (NOAA climate data, iNaturalist, eBird) come as .csv files. The tidyverse provides read_csv() for those — no extra package needed:
# read_csv() comes with tidyverse — no library(readxl) required
leaf_csv_df <- read_csv("data/2026_09_03_data_sci_leaf_area.csv") %>%
clean_names()💡 Key difference:
read_excel()needslibrary(readxl).read_csv()comes withlibrary(tidyverse). We useleaf_df(the Excel one) for the rest of the course.
Meet the data frame
A data frame is R’s word for a spreadsheet-style table. Each column is a vector of one type; the rows line up.
leaf_df has these columns:
| Column | Type | What it holds |
|---|---|---|
twig_id |
character | which twig the leaf came from (often blank) |
leaf_id |
character | leaf identifier within a twig (often blank) |
teams |
character | student team that collected the sample |
shade |
character | "sunny" or "shady" |
mass_g |
numeric | leaf mass in grams |
petiole_mm |
numeric | petiole length in millimetres |
thickness_mm |
numeric | leaf thickness in millimetres |
paper_mass_g |
numeric | mass of the paper cutout used to estimate leaf area |
Pull a single column with $:
leaf_df$mass_g # just the mass column, as a vectorPart 6 · Take a first look
Always eyeball a new dataset before doing any analysis. For that, reach for one function: glimpse().
▶ Run this in your Script:
# one row per column: name, type, and the first few values
glimpse(leaf_df)💡 Why
glimpse()? It shows every column name, its type (<chr>,<dbl>), and its first values on one screen. That is almost always what you want first. (head(),summary(), andstr()exist too — we’ll use them when we actually need them.)
✏️ Your turn: From the glimpse() output:
How many rows (observations) does leaf_df have?
How many columns?
What type does R report for shade? ( <chr> / <dbl> )
⚠️ Watch out! If a number column shows as
<chr>inglimpse(), a stray letter or comma snuck into your spreadsheet. Check your raw data file.
Part 7 · The pipe %>% — read it as “then”
The pipe sends a result straight into the next function. Read %>% as the word “then”.
▶ Run this in your Script:
# take leaf_df, THEN list its column names
leaf_df %>% names()
# take leaf_df, THEN show the glimpse
leaf_df %>% glimpse()💡 Two pipes — same idea:
%>%(from the tidyverse — we use this one all course) and|>(the native pipe, built into R 4.1+, which you’ll see in code online) behave identically for everything in this course.
You already used the pipe once, in Part 5, to send the freshly-read data into clean_names():
# read the file, THEN clean the column names
leaf_df <- read_excel("data/2026_09_03_data_sci_leaf_area.xlsx") %>%
clean_names()In Activity 03 we use %>% with group_by() and summarize() to compute statistics by group — that is where the pipe really pays off.
Part 8 · Your first plot
ggplot builds pictures in layers
Every ggplot is built in 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:
# leaf mass by shade, one point per leaf
ggplot(leaf_df, aes(x = shade, y = mass_g)) +
geom_point()⚠️ Watch out! The
+must sit at the end of a line, never the start.
Improve it one layer at a time
▶ Run this in your Script:
# define the point position ONCE so every layer can reuse it
jitter_pos <- position_jitter(width = 0.15, seed = 42)
# boxplot + raw points + axis labels
ggplot(leaf_df, aes(x = shade, y = mass_g)) +
geom_boxplot() +
geom_point(position = jitter_pos, color = "tomato") +
labs(x = "Side of tree",
y = "Leaf mass (g)")💡
position_jitter(width = 0.15)spreads the stacked points sideways so you can see each leaf;seed = 42makes that spread identical every time you run it. Storing it injitter_poslets the next layer land in the exact same place.
✏️ Your turn — in your script: Make the same plot for thickness_mm instead of mass_g.
Do sunny or shady leaves tend to be thicker?
Save your plot
Store the plot in an object, then save it to your figures/ folder.
▶ Run this in your Script:
# build the plot, store it, then write it to figures/ at print resolution
mass_plot <- ggplot(leaf_df, aes(x = shade, y = mass_g)) +
geom_boxplot() +
geom_point(position = jitter_pos, alpha = 0.6, color = "tomato") +
labs(x = "Side of tree",
y = "Leaf mass (g)",
title = "Sunny vs. shady leaves")
ggsave("figures/leaf_mass.png",
plot = mass_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.
Part 9 · Review and checkpoint
At this point you can:
✏️ Your turn — before you move on: Run the full script top to bottom with Ctrl/Cmd + Shift + Enter (Source). Does it complete without errors?
Ran cleanly? Y / N
If not, what error appeared:
Part 10 · Going further — more plotting
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. Keep adding to the same script.
Scatter plots — two numeric variables
▶ Try this:
# petiole length vs thickness, colored by shade
ggplot(leaf_df, aes(x = petiole_mm, y = thickness_mm, color = shade)) +
geom_point(size = 3, alpha = 0.7) +
labs(x = "Petiole length (mm)",
y = "Leaf thickness (mm)",
color = "Side of tree")Does petiole length predict thickness? Do the groups cluster separately or overlap?
Add a trend line
▶ Try this:
# same scatter, with a linear trend line per group
ggplot(leaf_df, aes(x = petiole_mm, y = thickness_mm, color = shade)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE) +
labs(x = "Petiole length (mm)",
y = "Leaf thickness (mm)",
color = "Side of tree")Do the slopes look similar or different for sunny vs shady leaves?
Violin plots
▶ Try this:
# a violin shows the full shape of the distribution
ggplot(leaf_df, aes(x = shade, y = mass_g, fill = shade)) +
geom_violin(alpha = 0.5) +
geom_point(position = jitter_pos, size = 2) +
labs(x = "Side of tree",
y = "Leaf mass (g)")What does the width of the violin show you that a boxplot does not?
Facets — small multiples
Facets split one plot into panels, one per group.
▶ Try this:
# one histogram panel per shade
ggplot(leaf_df, aes(x = mass_g)) +
geom_histogram(binwidth = 0.05, fill = "darkblue", color = "white") +
facet_wrap(~shade, ncol = 1) +
labs(x = "Leaf mass (g)", y = "Count")Do the histograms look symmetric or skewed? Similar between sunny and shady?
Themes — change the look
ggplot comes with built-in themes. Try swapping by adding one to any plot above:
+ theme_bw() # clean, white background
+ theme_minimal() # very minimal, no border
+ theme_classic() # classic axis lines, no grid
+ theme_dark() # dark backgroundWhich theme do you prefer for this kind of comparison data? Why?
Save a scatter plot as PNG
▶ Try this:
# build, store, and save the scatter
scatter_plot <- ggplot(leaf_df, aes(x = petiole_mm, y = thickness_mm, color = shade)) +
geom_point(size = 3, alpha = 0.7) +
geom_smooth(method = "lm", se = TRUE) +
labs(x = "Petiole length (mm)", y = "Leaf thickness (mm)", color = "Side") +
theme_bw()
ggsave("figures/leaf_dimensions_scatter.png",
plot = scatter_plot,
width = 7,
height = 5,
units = "in",
dpi = 300)What your finished project folder looks like
After working through this activity your tree_project/ folder should contain:
tree_project/
├── data/
│ ├── 2026_09_03_data_sci_leaf_area.csv <- never touch this
│ └── 2026_09_03_data_sci_leaf_area.xlsx <- never touch this
├── scripts/
│ └── 02_leaf_analysis.R <- your complete script (turn this in)
└── figures/
├── leaf_mass.png <- Part 8
└── leaf_dimensions_scatter.png <- Going further
This is the project structure we 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 |
Getting unstuck
When code breaks — and it will, that is normal — try these in order:
- Read the error message out loud. R usually names the line and the problem.
- Check the usual suspects: Did you load
library(tidyverse)andlibrary(readxl)? Spelling? Missing)or+at the start of a line? ?function_nameopens the built-in help page.- Tidyverse cheat sheets — https://posit.co/resources/cheatsheets/
- Bring the exact error (copy-paste it) to class or office hours.
💡 Key idea: Every working data scientist googles error messages daily. Getting stuck is not failing — it is the job.
Comments with
#Anything to the right of
#is ignored by R — it is a note for humans. Shortcut to toggle: Ctrl/Cmd + Shift + C.From here on, every code chunk you add to your script starts with a
#comment saying what it does.▶ Run this in your Script: