Project Structure with RStudio Projects

Marc Los Huertos

EA030 · Compiled 2026-04-14

Why It Matters Create a Project Folder Structure File Paths The here Package Naming Files Workflow Verify

What this module covers. A reproducible analysis is one that anyone — including your future self — can re-run from scratch on a new machine and get identical results. The foundation of reproducibility is project structure: keeping your raw data, code, and outputs in predictable locations and referencing them with paths that work everywhere. This module builds that foundation using RStudio Projects and the here package, with examples drawn from the EA030 server at rstudio.pomona.edu.


Why Project Structure Matters

A common beginner workflow looks something like this: all files in one folder (or worse, the Desktop), hardcoded paths like C:/Users/Jamie/Desktop/my_stuff/data.csv, and a folder that has accumulated names like final_final_v3_REAL.R. When you open it six weeks later — or hand it to a collaborator — nothing works.

A structured project solves this:

❌ Unstructured — breaks everywhere

Desktop/
  data.csv
  analysis.R
  analysis_v2.R
  analysis_FINAL.R
  plot.png
  plot_good.png
  notes.docx

✅ Structured — works on any machine

ea030_streams/
  data/raw/stream_sites.csv
  data/processed/sites_clean.csv
  scripts/01_clean.R
  scripts/02_analyse.Rmd
  output/figures/do_vs_temp.png
  ea030_streams.Rproj

The problems with the unstructured approach are not cosmetic — they cause real failures:

📌 The reproducibility test

A project passes the reproducibility test if a colleague can clone or download it, open the .Rproj file, and run every script in order — with zero modifications — and reproduce all your figures and tables. This is the standard you are aiming for in EA030.


1
Create an RStudio Project
File → New Project… · works identically on server and locally

On the Pomona server

  1. Open https://rstudio.pomona.edu and log in
  2. Go to File → New Project…
  3. Choose New Directory → New Project
  4. Enter a project name following the convention coursecode_topic — for example:
ea030_streams
  1. For Create project as subdirectory of, leave the default (~) — this places the project in your home directory on the server
  2. Click Create Project

On a local installation

Steps 2–6 are identical. The project folder appears wherever you chose in step 5.

✅ What the .Rproj file does

The .Rproj file is the anchor of your project. When you open it (by double-clicking in Finder/Explorer, or through File → Open Project in RStudio), three things happen automatically:

  1. The working directory is set to the project root folder
  2. The Files pane opens to the project root
  3. RStudio restores any open files from your last session

You never need to call setwd() again.

One project per analysis

Scenario Project name Notes
EA030 lab on stream chemistry ea030_stream_chemistry One project for each lab keeps labs independent
EA030 lab on bird survey ea030_bird_survey Never mix two labs in one project
Separate independent research project research_chaparral_birds Entirely separate from course work
Homework exercises ea030_homework Reasonable to group all HW in one project with numbered subfolders

2
Build the Standard Folder Structure
Create this once per project — takes under a minute

The EA030 standard structure

ea030_streams.Rproj ← open this to enter the project
data/
  raw/ ← original files, NEVER edited
    stream_sites.csv
    water_quality_2024.xlsx
  processed/ ← cleaned copies produced by scripts
    sites_clean.csv
scripts/
  01_load_clean.R ← numbered in run order
  02_visualise.R
  03_analysis.Rmd
output/
  figures/ ← plots saved by scripts
  tables/ ← CSV summaries, knitr tables
  reports/ ← knitted HTML / PDF files
README.md ← one paragraph: what is this project?

What goes where

data/raw/
Original data files exactly as received or downloaded. Never modified by any script. Treat as read-only.

stream_sites.csv, sensor_log.xlsx

data/processed/
Cleaned, filtered, or joined datasets produced by a script in scripts/. Always reproducible from raw.

sites_clean.csv, merged_2023_2024.csv

scripts/
All .R and .Rmd files, numbered in the order they should be run. Each script has one clear purpose.

01_load_clean.R, 02_eda.Rmd

output/figures/
Plots saved programmatically (never copied by hand). A script should recreate every figure by running from scratch.

do_vs_temp.png, site_map.pdf

output/tables/
Summary statistics, model output tables exported as CSV or plain text.

summary_stats.csv, model_coefs.csv

output/reports/
Rendered HTML or PDF files. These are products of knitting, not sources — don’t edit them directly.

03_analysis.html, lab_report.pdf

Create the folders

Run this once in the Console after creating the project. It builds the entire skeleton instantly:

# Run once from the Console — creates all project folders
dirs <- c(
  "data/raw", "data/processed",
  "scripts",
  "output/figures", "output/tables", "output/reports"
)
invisible(lapply(dirs, dir.create, recursive = TRUE, showWarnings = FALSE))

# Verify
list.dirs(recursive = TRUE)

Or create them manually in the Files pane: click New Folder, name it, then navigate inside and repeat for subfolders.


3
Use Relative File Paths
Code that references files correctly — on any machine

The problem with absolute paths

An absolute path encodes the full location of a file from the root of the file system:

❌ Absolute path — breaks on every other machine

read.csv(“/home/jsmith/ea030_streams/data/raw/stream_sites.csv”)

# On the server it becomes:
read.csv(“/home/mloshuertos/ea030_streams/…”)

# On a Mac:
read.csv(“/Users/jamie/Documents/…”)

✅ Relative path — works everywhere

read.csv(“data/raw/stream_sites.csv”)

# Works because RStudio Projects set the
# working directory to the project root,
# so all paths start from there.

A relative path is measured from the working directory — and because .Rproj always sets the working directory to the project root, "data/raw/stream_sites.csv" resolves correctly whether you are on the Pomona server, your laptop, or a collaborator’s machine.

⚠️ setwd() is a red flag

If you find yourself writing setwd("/some/long/path"), stop. It means a file is being referenced from outside the project, or the project is not open. The fix is to open the project via the .Rproj file and use relative paths — never setwd().


4
Robust Paths with the here Package
install.packages(“here”) · one-time setup

Relative paths like "data/raw/file.csv" work most of the time, but they depend on the working directory being set correctly. Inside a knitted .Rmd, the working directory is the folder containing the .Rmd file — not necessarily the project root. This causes subtle bugs when scripts are in a subfolder.

The here package always resolves paths from the project root, regardless of where the script lives. {#here}

library(here)

# here() returns the absolute path to the project root
here()
# "/home/jsmith/ea030_streams"

# Construct paths safely
read.csv(here("data", "raw", "stream_sites.csv"))

# Save output to a subfolder
ggsave(here("output", "figures", "do_vs_temp.png"),
       width = 7, height = 4)

⚙️ here() vs. relative paths — when does it matter?

Situation Bare relative path here()
Script in project root ✅ Works ✅ Works
Script in scripts/ subfolder ⚠️ May fail if WD not root ✅ Always works
Inside a knitted .Rmd ⚠️ WD = .Rmd location ✅ Always works
Shared with a collaborator ✅ Works if paths are short ✅ Works

Use here() consistently and you will never debug a path error again.

Verify here is working

library(here)
here()   # should print the path ending in your project name

5
Naming Files and Scripts
Good names make projects self-documenting

The three rules

  1. Machine-readable — no spaces, no special characters, no accented letters
  2. Human-readable — a glance tells you what the file contains
  3. Sortable — numbers and ISO dates keep files in the right order
✅ Good names

01_load_clean.R
02_exploratory_plots.Rmd
03_linear_regression.R
stream_sites_raw_2024-05-12.csv
do_vs_temp_fig1.png
README.md

❌ Problematic names

analysis.R
analysis FINAL (2).R
my data.csv
résultats.csv
Figure 1 (good one!).png
untitled.R

Numbering scripts

Number scripts with a two-digit prefix so they sort in run order in any file browser:

# Correct — sorts as 01, 02, 03 ... 10, 11
01_load_raw_data.R
02_clean_and_filter.R
03_exploratory_analysis.Rmd
04_linear_regression.R
05_report.Rmd

# Wrong — sorts as 1, 10, 11, 2, 3 (lexicographic order)
1_load.R
10_final.R
2_clean.R

Dates in filenames

When a file is versioned by date (raw data downloads, for example), use ISO 8601 format (YYYY-MM-DD) — it sorts chronologically:

# Good — sorts chronologically
stream_discharge_2024-03-15.csv
stream_discharge_2024-04-02.csv
stream_discharge_2024-11-01.csv

# Bad — ambiguous, sorts wrong
stream_discharge_3-15-24.csv
stream_discharge_Nov_data.csv

6
A Worked Workflow
From raw data to knitted report — the full arc

Script 01 — load and clean

Every analysis starts the same way: load raw data, inspect it, clean it, save a processed copy. No output figures yet — just getting the data ready.

# scripts/01_load_clean.R
# Purpose: load raw stream data, check, clean, and save processed copy
# Author:  your name
# Date:    2024-05-12

library(tidyverse)
library(here)
library(janitor)

# ── 1. Load ────────────────────────────────────────────────────────
raw <- read_csv(here("data", "raw", "stream_sites.csv"))

# ── 2. Inspect ─────────────────────────────────────────────────────
glimpse(raw)
summary(raw)

# ── 3. Clean ───────────────────────────────────────────────────────
clean <- raw %>%
  clean_names() %>%                       # snake_case column names
  filter(!is.na(dissolved_oxygen_mgl)) %>% # drop rows missing DO
  mutate(
    date       = as.Date(date, "%Y-%m-%d"),
    site_id    = factor(site_id),
    do_mgl     = dissolved_oxygen_mgl,
    temp_c     = water_temp_c
  ) %>%
  select(date, site_id, do_mgl, temp_c, flow_cms, conductivity_us)

# ── 4. Save processed copy ─────────────────────────────────────────
write_csv(clean, here("data", "processed", "stream_clean.csv"))
cat("Saved:", nrow(clean), "rows to data/processed/stream_clean.csv\n")

Script 02 — exploratory visualisation

# scripts/02_exploratory_plots.R
# Purpose: initial visualisations of cleaned stream data

library(tidyverse)
library(here)

clean <- read_csv(here("data", "processed", "stream_clean.csv"))

# ── DO vs temperature ──────────────────────────────────────────────
p_do_temp <- ggplot(clean, aes(temp_c, do_mgl, colour = site_id)) +
  geom_point(alpha = 0.55, size = 2) +
  geom_smooth(method = "lm", se = FALSE, linewidth = 0.8) +
  scale_colour_brewer(palette = "Dark2", name = "Site") +
  labs(x = "Water temperature (°C)", y = "Dissolved oxygen (mg/L)",
       title = "DO–Temperature Relationship by Site") +
  theme_minimal(base_size = 13) +
  theme(plot.title = element_text(family = "serif", face = "bold",
                                  colour = "#1e3a2f"))

# Save — never use Export button for reproducible figures
ggsave(here("output", "figures", "do_vs_temp.png"),
       plot = p_do_temp, width = 7, height = 4, dpi = 150)

cat("Figure saved to output/figures/do_vs_temp.png\n")

⚠️ Never use the RStudio Export button for final figures

The Export button in the Plots pane lets you save a figure interactively — but the saved size and resolution depend on how you manually resize the window. Use ggsave() with explicit width, height, and dpi arguments so figures are identical every time the script runs.

Script 03 — analysis report (Rmd)

---
title: "Stream Chemistry Analysis"
author: "Your Name"
date: "`r Sys.Date()`"
output:
  html_document:
    self_contained: true
    toc: true
---
```{r load, include=FALSE}
library(tidyverse)
library(here)
clean <- read_csv(here("data", "processed", "stream_clean.csv"))
```

```{r do-temp-plot, fig.cap="DO decreases with temperature across all sites.", echo=FALSE}
ggplot(clean, aes(temp_c, do_mgl, colour = site_id)) +
  geom_point(alpha = 0.55) +
  geom_smooth(method = "lm", se = FALSE) +
  theme_minimal()
```

Knit with Ctrl/⌘+Shift+K. The rendered HTML lands in the same folder as the .Rmd — move it to output/reports/ manually, or set the knit: option in the YAML to write there directly (as all EA030 tutorials do).


Verify Your Project Structure

Run this chunk to audit your project. It checks that all required folders exist, warns about any absolute paths in your scripts, and lists any raw data files.

# Audit the current project structure
required_dirs <- c("data/raw", "data/processed",
                   "scripts", "output/figures",
                   "output/tables", "output/reports")

cat("── Folder audit ──────────────────────────────\n")
## ── Folder audit ──────────────────────────────
for (d in required_dirs) {
  exists <- dir.exists(d)
  icon   <- if (exists) "✅" else "❌ MISSING"
  cat(sprintf("  %s  %s\n", icon, d))
}
##   ❌ MISSING  data/raw
##   ❌ MISSING  data/processed
##   ❌ MISSING  scripts
##   ❌ MISSING  output/figures
##   ❌ MISSING  output/tables
##   ❌ MISSING  output/reports
cat("\n── Working directory ─────────────────────────\n")
## 
## ── Working directory ─────────────────────────
cat(" ", getwd(), "\n")
##   /home/mwl04747/RTricks/01_RGuides
cat("\n── R scripts found in scripts/ ───────────────\n")
## 
## ── R scripts found in scripts/ ───────────────
if (dir.exists("scripts")) {
  scripts <- list.files("scripts", pattern = "\\.(R|Rmd)$",
                        recursive = TRUE)
  if (length(scripts) > 0) {
    cat(paste(" •", scripts), sep = "\n")
  } else {
    cat("  (no scripts yet)\n")
  }
} else {
  cat("  scripts/ folder not found\n")
}
##   scripts/ folder not found

🔑 Project structure — key rules


What’s next?