Activity: Getting Started
From a question in the field to your first plot in R
Worksheet: Getting Started
How to use this worksheet
Work through each part in order, at your own pace. Type every line of code yourself into a plain R script — do not copy-paste. Blocks marked ▶ Run this are code you should type and execute. Blocks marked ✏️ Your turn ask you to write, modify, or answer something. Boxes marked 🚀 If you finish early and Part 15 (Going further) are optional bonus material.
Part 1 · Today’s question
Recap
- We are asking whether pine needle length differs between the shady side and the sunny side of a tree.
- The tree, not the needle, is our unit of replication — one tree is not enough (this is called pseudoreplication).
✏️ Your turn: Write your own null and alternate hypotheses in your own words.
H0:
Ha:
✏️ Your turn: Is the following statement inductive or deductive reasoning? “I measured needles on three trees, all showed the sheltered side longer, so I expect a fourth tree to show the same pattern.” Why can’t we test a hypothesis like this using only one tree?
Reasoning type:
Why one tree isn't enough:
Field collection plan
As a group, agree on and record your answers before you go outside:
Sample height on tree:
Definition of "mature" needle:
Needles per side, per tree:
Label convention (n/s? shady/sunny?):
Part 2 · Build your project folder
Why a project folder?
Everything for this project lives in one folder, so every file path is relative to it — no more hunting for where a file went.
Create the folder structure
Make a new folder called pine_project. Inside it, create four sub-folders:
pine_project/
├── data/ ← your CSV and Excel files go here
├── scripts/ ← your .R code goes here
├── figures/ ← plots you save go here
└── output/ ← place to save modified dataframes
Copy pine_needles.csv into pine_project/data/.
⚠️ Important: Leave this copy alone — it is raw data. We will read it but never overwrite it. (Your own field data will get cleaned up and added here starting in Wrangling Your Data.)
✏️ Your turn: Full path to your pine_project folder:
_________________________________________________________________________________________________________________________________________________________________
Part 3 · Enter your data in Excel
Before R ever sees your numbers, they have to get from your field notes into a spreadsheet. How you do that now determines how much pain you’re in later — so we do it deliberately.
Set up your header row first
Open Excel and make row 1 your column headers — nothing else goes in that row. Use the same controlled vocabulary your group already agreed on in Part 1, written the lower_snake_case way (no spaces, no units hidden in the text): what will they be?
| Column | units? | What goes in it |
|---|---|---|
⚠️ Watch out!
Needle Length (mm)andlength_mmlook equally clear to a human — only one of them is a header R can use without a fight. Spaces, parentheses, and units in the header text all cause problems later.
One row = one measurement
This is the part people get wrong under time pressure: every single needle you measured gets its own row. Not one row per tree with six length columns crammed in — one row per needle, with tree_no, n_s, and sun repeated on every row that needle belongs to. Yes, that means typing n and shady over and over. That repetition is doing real work — it’s what lets R filter, group, and plot by any of those columns later.
⚠️ Watch out! — Common Excel data-entry mistakes
- Merged cells. They look nice, they break every import. Never merge.
- Blank rows or blank columns used as visual spacers. R reads them as missing data, not as whitespace.
- More than one header row, or notes typed above row 1. R will try to read your note as a column name.
- Numbers stored as text.
20 mmin a cell is text;20is a number. Keep units in the header, not in the cell. - Inconsistent spelling of the same category —
Sunny,sunny(trailing space), andsunare three different values to R, even though they mean the same thing to you. - Color-coding instead of a column. Highlighting a row yellow to mean something is invisible to R. If it matters, it needs its own column.
Enter your group’s data
▶ Do this now: Using the measurements your group collected today, enter one row per needle into Excel using the header row above.
✏️ Your turn: How many rows should your sheet have, if your group measured ______________ needles per
side, per tree, on _____________ trees? Show your arithmetic: ________________________
🖐 Honest preview — this probably isn’t “tidy” yet
Excel invites you to spread related numbers across columns — one column per tree, or one column per needle. That’s a completely normal way to enter data quickly, and it is not the same as tidy data: the rule (from R for Data Science) that every variable is a column, every observation is a row, and every value is a cell. If your sheet doesn’t fully follow that yet, that’s fine — turning a wide, human-friendly layout into a tidy one is exactly what we’ll do with pivot_longer() in Wrangling Your Data. Today, just get the numbers in accurately.
Save it — both formats
File → Save As, into your pine_project/data/ folder, twice:
- Once as an Excel workbook (
.xlsx) — keeps any formatting you added. - Once as CSV UTF-8 (
.csv) — plain text, no hidden formatting, and the format every other tool (including R) can read without a special library.
⚠️ Important: Save both into
data/, and — likepine_needles.csv— treat them as raw data from this point on: read them, never hand-edit them again. Corrections happen in R, where every change is a line of code you can see and undo.
✏️ Your turn: What are the two file names you just saved, and are they both sitting in pine_project/data/?
______________________________________________________________________________________
Part 4 · Orient yourself in Positron
Open your project folder as your workspace: File → Open Folder…
Four panes you’ll use constantly:
| Pane | Where | What it does |
|---|---|---|
| Editor | top-left | your scripts live here |
| Console | bottom | where code actually runs |
| Environment | right | every object you’ve stored |
| Plots / Files | right (tab) | your figures and project files |
✏️ Your turn: Click the Console tab. Type 1 + 1 and press Enter. Result: _____________
Part 5 · R as a calculator, and storing values
▶ Run this in the Console:
3 + 5
12 / 7
2 ^ 10
sqrt(144)⚠️ Watch out! A
+at the start of a console line (instead of the usual>prompt) means R is still waiting for you to finish typing something. Press Esc to cancel.
Now store a value with the assignment operator <- (shortcut: Alt/Option + -):
▶ Run this:
x <- 7 # store 7 under the name x
x # read it back
x * 2 # use it in mathLook for x in the Environment pane.
✏️ Your turn: Store the number 42 as my_number, then multiply it by x. Result: __________________
# Write your code here:🚀 If you finish early: Try x / my_number and my_number %% x (the remainder operator). Predict each result before you run it.
Part 6 · Naming things well
Good names make code readable months later: be explicit (needle_length, not x2), never start with a number, and R is case-sensitive (length_mm ≠ Length_mm). Use lower_snake_case.
✏️ Your turn: Which of these are valid R object names? Circle Y or N.
needle_length Y / N
3rd_needle Y / N
Length_mm Y / N
my.needle.data Y / N
n_s Y / N
Part 7 · Comments
Anything after # is ignored by R — a note for humans.
▶ Run this in your Script:
# average of four needle lengths, in mm
needle_length <- c(20, 21, 23, 25)
mean(needle_length) # average needle length💡 Key idea: If a line confused you while writing it, comment why you did it — future you will forget.
Part 8 · Functions and arguments
A function is called by name and takes arguments in ().
▶ Run this in the Console:
sqrt(10)
round(3.14159) # default: 0 decimal places
round(3.14159, 2) # 2 decimal places
round(x = 3.14159, digits = 2)Stuck? ?round opens the help page.
✏️ Your turn: Use round() to round pi (R knows this by name) to 4 decimal places. Result: _______________
# Write your code here:Part 9 · Vectors and data types
A vector is a series of values built with c() which is also called a concatenated list and we will see it more. A spreadsheet column is really just a vector.
▶ Run this in your Script:
length_mm <- c(20, 21, 23, 25) # numeric vector
side <- c("n", "s") # character vector — needs quotes
length(length_mm) # how many values?
class(length_mm) # what type?⚠️ Watch out!
"n"in quotes is text. Without quotes, R looks for an object callednand errors if none exists.
✏️ Your turn: Make a character vector called my_sides with the values "n" and "s". Check its length() and class().
# Write your code here:🚀 If you finish early: Make a numeric vector of 5 needle lengths you make up. Run mean() and sd() on it — both work directly on a vector, no data frame needed.
Part 10 · Packages and libraries
Install once, in the Console:
install.packages("tidyverse")Load every session — put this at the very top of every script:
▶ Run this at the top of your Script:
# ── Packages ──────────────────────────────
library(tidyverse)✏️ Your turn: First line R prints after library(tidyverse): ________________________
Part 11 · Load the pine needle data
Create your script
File → New File → R Script. Save it immediately as 01_getting_started.R inside pine_project/scripts/.
▶ Run this in your Script:
# ── Load data ─────────────────────────────
pine_df <- read_csv("data/pine_needles.csv")
pine_df # print to consoleThe path "data/..." is relative to your project folder — it works on anyone’s computer, not just yours.
Always look before you trust it
▶ Run each of these:
head(pine_df) # first 6 rows
dim(pine_df) # (rows, columns)
names(pine_df) # column names
glimpse(pine_df) # one line per column: name, type, first values✏️ Your turn: Answer from the output above.
Rows: Columns:
Type of n_s: Values in sun:
✏️ Your turn: Look closely at n_s and sun. Do they ever disagree (n paired with sunny, or s paired with shady)? Why might a dataset include two columns that encode the same grouping? ________________________
Part 12 · Your first plot
Every ggplot is built from three pieces, joined with +: data, aes() (which columns map to x/y), and a geom (how to draw it).
▶ Run this — the simplest possible plot:
ggplot(pine_df, aes(x = n_s, y = length_mm)) +
geom_point()⚠️ Watch out! The
+sits at the end of a line, never the start.
Improve it one layer at a time
▶ Run this:
ggplot(pine_df, aes(x = sun, y = length_mm)) +
geom_boxplot() +
geom_jitter(width = 0.15, alpha = 0.6, color = "tomato") +
labs(x = "Sun exposure",
y = "Needle length (mm)",
title = "Needle length by sun exposure")✏️ Your turn: Make the same plot using n_s instead of sun on the x-axis. Copy the code above and change what’s needed.
# Write your modified code here:What pattern do you see — does the sheltered or exposed side tend to have longer needles? ________________________
🚀 If you finish early: Try geom_violin() instead of geom_boxplot(), or add color = group inside aes() to see each field team’s data separately.
Part 13 · Save your plot
▶ Run this:
needle_plot <- ggplot(pine_df, aes(x = sun, y = length_mm)) +
geom_boxplot() +
geom_jitter(width = 0.15, alpha = 0.6, color = "tomato") +
labs(x = "Sun exposure", y = "Needle length (mm)")
ggsave("figures/needle_length.png",
plot = needle_plot,
width = 3,
height = 3,
units = "in",
dpi = 300)Check your figures/ folder — the PNG should be there.
⚠️ Watch out!
ggsave()wants the filename first, thenplot =. Always usedpi = 300.
Part 14 · Review and checkpoint
At this point you can:
✏️ Your turn — before you move on: Run your whole script top to bottom (or line by line). Ran cleanly? Y / N — if not, the error was: ________________________
📤 What to turn in before next class
Upload both of these to the course management system:
- Your code — the
scripts/folder (or just01_getting_started.R) - This worksheet, with your written answers
Part 15 · Going further — optional in class, or take-home if you’d like more practice
Work through this if you finish early. There are no wrong answers — the goal is to see what
ggplotcan do.
Histograms, split by group
▶ Try this:
ggplot(pine_df, aes(x = length_mm, fill = sun)) +
geom_histogram(binwidth = 2, position = position_dodge2(width = 0.5)) +
labs(x = "Needle length (mm)", y = "Count", fill = "Sun exposure")✏️ Your turn: Are the two distributions similar in shape, or different? ________________________
Facets — one panel per group
ggplot(pine_df, aes(x = length_mm)) +
geom_histogram(binwidth = 2, fill = "darkblue", color = "white") +
facet_wrap(~group)Violin plots
ggplot(pine_df, aes(x = sun, y = length_mm, fill = sun)) +
geom_violin(alpha = 0.5) +
geom_jitter(width = 0.1, size = 2) +
theme(legend.position = "none")Themes — change the look
Add any of these to a plot above:
+ theme_bw()
+ theme_minimal()
+ theme_classic()✏️ Your turn: Which theme do you like best for this kind of biological comparison? Why? ________________________
What your finished project folder looks like
pine_project/
├── data/
│ └── pine_needles.csv <- never touch this
├── scripts/
│ └── 01_getting_started.R <- your complete script
└── figures/
└── needle_length.png <- Part 13
This is the structure we will use for every analysis this term.
| Folder | Contents | Rule |
|---|---|---|
data/ |
raw files | read-only, never overwrite |
scripts/ |
.R code |
one script per module |
figures/ |
saved plots | always PNG, always dpi = 300 |
Getting unstuck
When code breaks — and it will, that is normal:
- Read the error message out loud. R usually names the line and the problem.
- Check the usual suspects: did you run
library(tidyverse)? Spelling? A missing)or a+at the start of a line? ?function_nameopens the built-in help page.- Bring the exact error (copy-paste it) to class or office hours.
💡 Key idea: Every working scientist googles error messages daily. Getting stuck is not failing — it is the job.