Activity: Getting Started

From a question in the field to your first plot in R

Hands-on worksheet: forming a testable question, organizing a project, entering field data in Excel, and writing your first R script and ggplot.
Author

Bill Perry

Worksheet: Getting Started

πŸ“₯ Download the data (.csv) πŸ“₯ Download the data (.xlsx)

πŸ“₯ Script skeleton (.R)

ImportantColumn names in pine_data.csv / pine_data.xlsx

Both files hold the same six columns, with exactly these names. Type them exactly as written β€” R is case-sensitive, and Needle_Length or needle_length will not work:

Column What it is
source_file the raw team file the row came from
team field team β€” one team = one tree
replicate needle number within a tree side (1-8)
side sun exposure β€” "shady" or "sunny"
needle_length_mm needle length, millimetres
needle_width_mm needle width, millimetres

After you read the file in, run names(pine_df) and check the names on your screen against this table before you write any other code.


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 shady 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 for the side column (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.

πŸ“– Read more: R4DS Β§6 β€” Workflow: scripts and projects, and Gotelli & Ellison Ch. 8 (Managing and Curating Data) on Canvas.

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_data.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) and needle_length_mm look 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 eight length columns crammed in β€” one row per needle, with team, side, and replicate repeated on every row that needle belongs to. Yes, that means typing 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.

Warning

⚠️ 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 mm in a cell is text; 20 is a number. Keep units in the header, not in the cell.
  • Inconsistent spelling of the same category β€” Sunny, sunny (trailing space), and sunny are 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:

________________________

Tip

πŸ– 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:

  1. Once as an Excel workbook (.xlsx) β€” keeps any formatting you added.
  2. 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 β€” like pine_data.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:

# ── Part 5.1 Β· R as a calculator, and storing values ────────────────────
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:

# ── Part 5.2 Β· R as a calculator, and storing values ────────────────────
x <- 7      # store 7 under the name x
x           # read it back
x * 2       # use it in math

Look for x in the Environment pane.

✏️ Your turn: Store the number 42 as my_number, then multiply it by x. Result:

__________________

# ── Part 5.3 Β· R as a calculator, and storing values ────────────────────
# Write your code in the R script:
Tip

πŸš€ 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 (needle_length_mm β‰  Needle_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
Needle_Length_mm  Y / N
my.needle.data    Y / N
side              Y / N

Part 7 Β· Comments

Anything after # is ignored by R β€” a note for humans.

β–Ά Run this in your Script:

# ── Part 7.1 Β· Comments ─────────────────────────────────────────────────
# 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:

# ── Part 8.1 Β· Functions and arguments ──────────────────────────────────
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:

_______________

# ── Part 8.2 Β· Functions and arguments ──────────────────────────────────
# Write your code in the R script:

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:

# ── Part 9.1 Β· Vectors and data types ───────────────────────────────────
needle_length_mm <- c(20, 21, 23, 25)     # numeric vector
side      <- c("n", "s")           # character vector β€” needs quotes

length(needle_length_mm)   # how many values?
class(needle_length_mm)    # what type?

⚠️ Watch out! "n" in quotes is text. Without quotes, R looks for an object called n and errors if none exists.

✏️ Your turn: Make a character vector called my_sides with the values "n" and "s". Check its length() and class().

# ── Part 9.2 Β· Vectors and data types ───────────────────────────────────
# Write your code in the R script:
Tip

πŸš€ 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:

# Console only β€” do NOT put this line in your script
install.packages("tidyverse")

Load every session β€” put this at the very top of every script:

β–Ά Run this at the top of your Script:

# ── Part 10.2 Β· Packages and libraries ──────────────────────────────────
library(tidyverse)

✏️ Your turn: First line R prints after library(tidyverse):

________________________

πŸ“– Read more: R4DS Β§2 β€” Workflow: basics and Β§4 β€” Workflow: code style.


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:

# ── Part 11.1 Β· Load the pine needle data ───────────────────────────────
pine_df <- read_csv("data/pine_data.csv")

pine_df    # print to console

The path "data/..." is relative to your project folder β€” it works on anyone’s computer, not just yours.

πŸ“– Read more: R4DS Β§7 β€” Data import.

Always look before you trust it

β–Ά Run each of these:

# ── Part 11.2 Β· Load the pine needle data ───────────────────────────────
head(pine_df)       # first 6 rows
dim(pine_df)         # (rows, columns)
names(pine_df)       # column names

✏️ Your turn: Answer from the output above.

Rows:              Columns:
Type of side:       Values in side:

✏️ Your turn: The file has 64 rows. But how many trees were actually sampled? (Hint: count the teams, and remember each team measured one tree.)

________________________

✏️ Your turn: If two needles come off the same branch of the same tree, are they really two independent pieces of evidence about sun exposure? Why or why not?

________________________


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).

πŸ“– Read more: R4DS Β§1 β€” Data visualization. For why we plot before we test, see Whitlock & Schluter Ch. 2 (Displaying Data) on Canvas.

β–Ά Run this β€” the simplest possible plot:

# ── Part 12.1 Β· Your first plot ─────────────────────────────────────────
ggplot(pine_df, aes(x = side, y = needle_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:

# ── Part 12.2 Β· Your first plot ─────────────────────────────────────────
ggplot(pine_df, aes(x = side, y = needle_length_mm)) +
  geom_boxplot() +
  geom_jitter(width = 0.15, alpha = 0.6) +
  labs(x = "Sun exposure", y = "Needle length (mm)")

✏️ Your turn: Make the same plot using team instead of side on the x-axis. Copy the code above and change what’s needed.

# ── Part 12.3 Β· Your first plot ─────────────────────────────────────────
# Write your modified code in the R script:

What pattern do you see β€” does the shady or sunny side tend to have longer needles?

________________________

✏️ Your turn: In your team plot, how much do the four teams differ from one another compared with how much the two sides differ?

________________________

Tip

πŸš€ If you finish early: Try geom_violin() instead of geom_boxplot(), or add color = team inside aes() to see each field team’s data separately.


Part 13 Β· Save your plot

β–Ά Run this:

# ── Part 13.1 Β· Save your plot ──────────────────────────────────────────
needle_plot <- ggplot(pine_df, aes(x = side, y = needle_length_mm)) +
  geom_boxplot() +
  geom_jitter(width = 0.15, alpha = 0.6) +
  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, then plot =. Always use dpi = 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:

________________________

Note

πŸ“€ What to turn in before next class

Upload both of these to the course management system:

  1. Your code β€” the scripts/ folder (or just 01_getting_started.R)
  2. 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 ggplot can do.

Histograms, split by team

β–Ά Try this:

# ── Part 15.1 Β· Going further ───────────────────────────────────────────
ggplot(pine_df, aes(x = needle_length_mm, fill = side)) +
  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 team

# ── Part 15.2 Β· Going further ───────────────────────────────────────────
ggplot(pine_df, aes(x = needle_length_mm)) +
  geom_histogram(binwidth = 2) +
  facet_wrap(~team)

Violin plots

# ── Part 15.3 Β· Going further ───────────────────────────────────────────
ggplot(pine_df, aes(x = side, y = needle_length_mm, fill = side)) +
  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:

# ── Part 15.4 Β· Going further ───────────────────────────────────────────
+ 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_data.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

Readings

Before next class β€” chapter PDFs are posted on Canvas:

  • Whitlock & Schluter, The Analysis of Biological Data β€” Ch. 1, Statistics and Samples (populations vs. samples, why we sample, sampling error)
  • Gotelli & Ellison, A Primer of Ecological Statistics β€” Ch. 8, Managing and Curating Data (raw data, metadata, project organization)

If you want more:

  • Whitlock & Schluter, Ch. 2 β€” Displaying Data
  • Gotelli & Ellison, Ch. 4 β€” Framing and Testing Hypotheses

R for Data Science (2nd ed.) β€” free online at r4ds.hadley.nz. The chapters behind today’s worksheet:

Chapter Covers Worksheet part
Β§1 β€” Data visualization ggplot(), aes(), geoms Parts 12, 15
Β§2 β€” Workflow: basics objects, <-, functions Parts 5-8
Β§4 β€” Workflow: code style naming things, comments Parts 6-7
Β§6 β€” Workflow: scripts and projects scripts, project folders, relative paths Parts 2, 11
Β§7 β€” Data import read_csv(), checking what you loaded Part 11
Β§8 β€” Workflow: getting help reading errors, asking good questions reading errors


Homework

Homework 1 β€” Intro to R

You will build a project from scratch β€” directories, a Quarto file, and a script β€” and read in a dataset you have not seen in class: Arctic grayling from lakes I-3 and I-8, measured on the North Slope of Alaska. You read the same data twice, once from .csv and once from .xlsx, then summarise and plot it and save the figure to PDF.

Data: gray_I3_I8.csv and gray_I3_I8.xlsx β€” a course field dataset, not a textbook one, so there is no chapter to look up.