Activity — Introduction to R and Positron

From the console to your first plot with the leaf data

r-basics
project-setup

Install R and Positron, organize a project folder, and take your first steps with vectors, functions, and loading the leaf data into R.

Author

Bill Perry

Published

September 10, 2026

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

  1. Get R and Positron installed and oriented
  2. Organize a project folder the right way — data/, scripts/, figures/
  3. Use R as a calculator and store values with <-
  4. Write and run a script with # comments
  5. Call functions, build vectors, understand data types
  6. Load the leaf data from Excel with read_excel() (and see how read_csv() differs)
  7. Take a first look at a data frame with glimpse()
  8. Use the pipe %>% to chain steps
  9. Make and save a first ggplot figure

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 into leaf_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

  1. R (the engine) — https://cran.r-project.org/
    • Choose your operating system, run the installer, accept all defaults
  2. 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

Tip📂 Download the leaf data

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 math

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:

Naming things well

Good names make code readable months later:

  • Be explicit but not too long: leaf_mass not x2
  • Cannot start with a number — x2 ✅, 2x
  • R is case-sensitivemass_gMass_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           —

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:

# weights of four leaves, in grams
leaf_mass <- c(4, 3, 5, 7)

mean(leaf_mass)   # average leaf mass

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 arguments

Vectors — 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 named sunny and 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 forgot library(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 names

Part 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() (from janitor) then tidies messy headers like "Leaf Mass (g)" into leaf_mass_g automatically.

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() needs library(readxl). read_csv() comes with library(tidyverse). We use leaf_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 vector

Part 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(), and str() 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> in glimpse(), 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 +:

  1. ggplot(data, aes(...)) — which data frame, which columns map to x and y
  2. geom_*() — the shape to draw (points, boxes, bars, …)
  3. 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 = 42 makes that spread identical every time you run it. Storing it in jitter_pos lets 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, then plot =. 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 background
Which 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:

  1. Read the error message out loud. R usually names the line and the problem.
  2. Check the usual suspects: Did you load library(tidyverse) and library(readxl)? Spelling? Missing ) or + at the start of a line?
  3. ?function_name opens the built-in help page.
  4. Tidyverse cheat sheetshttps://posit.co/resources/cheatsheets/
  5. 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.