; PSY 1903
PSY 1903 Programming for Psychologists

Suggestion Box

Spot an error or have suggestions for improvement on these notes? Let us know!

Using here::here() for File Paths in Functions

When writing and testing your R scripts in the main project directory, your working directory is automatically the project root (for example, psy1903/web/npt_project).
But when you render a Quarto report stored inside a subfolder like reports/, the working directory changes.
This difference often breaks file paths, especially when functions load or save data files.

This short lesson explains how to fix that problem using the here package and its function here::here().
It builds on what you learned about R Projects, relative paths, and reproducible code organization.


1 · Recap: Project Roots and Relative Paths

When you open an .Rproj file, RStudio automatically sets your working directory to that project’s root folder.
All paths are interpreted relative to that root.

Example structure for npt_project:

npt_project/
├── npt_project.Rproj
├── data/
│   ├── raw/
│   │   └── npt-experiment-2025-11-05-10-33-05.csv
│   │       ...
│   └── cleaned/
│       └── participants/
│       │   └── npt-experiment-2025-11-05-10-33-05_processed.csv
│       │       ...
│       └── study_level.csv
│       └── study_level.rds
├── scripts/
│       └── process_participant.R
│       └── score_questionnaire.R
│       └── summarize_behavior.R
└── reports/
        └── npt_import_files/
        └── npt_import.html
        └── npt_import.qmd
  • In scripts run from the root, "data/raw/file.csv" works. This applies when you are testing code in the console or test new functions in R Scripts before moving to Quarto
  • In Quarto documents inside reports/, you would have to update all paths to "../data/raw/file.csv", which is easy to forget.

That is where here::here() comes in.


2 · What here::here() Does

The here package automatically finds your project’s root directory (the folder containing the .Rproj file) and constructs file paths relative to it.

library(here)

here("data", "raw", "npt-experiment-2025-11-05-10-33-05.csv")
# "psy1903/web/npt_project/data/raw/npt-experiment-2025-11-05-10-33-05.csv"

No matter whether the code runs from scripts/, reports/, or interactively in RStudio,
here() always starts at the project root. This means you never need setwd(), remember whether or not to use ../, or hardcode your paths.

Calling here::here() tells R exactly which package the function comes from, without needing to load it first with library(here).

This makes your code more explicit, avoids naming conflicts across packages, and keeps your functions self-contained.

In other words:

  • here::here() always works as long as the here package is installed.
  • here() alone only works after you have loaded the package with library(here) or require(here) (and you would need to do that each time you start R or render a Quarto report).

3 · Example: Updating process_participant()

Below is the process_participant() function we wrote for the NPT Project with path names replaced with here::here.
This function loads a single participant’s raw data file, processes it, and saves the cleaned result.
Notice how here::here() keeps file paths consistent whether the function is sourced or run from a Quarto report, for example:

 ## Read the raw CSV
  participant_data <- read.csv(
    here::here("data", "raw", file_name),
    stringsAsFactors = FALSE
  )

Full process_participant.R script with here::here() replacements:

process_participant <- function(file_name) {
  #### Load File and Extract ID ------------------------------------------------
  ## Derive a subject id from the filename (no extension)
  subject_id <- sub("\\.csv$", "", basename(file_path))
  
  ## Read the raw CSV
  participant_data <- read.csv(
    here::here("data", "raw", file_name),
    stringsAsFactors = FALSE
  )
  
  #### Questionnaire score -----------------------------------------------------
  ## Score questionnaire with our defaults (reverse 2,4,7 on 0–4 scale)
  tef10_score <- score_questionnaire(
    json_string = participant_data[participant_data$trialType == "questionnaire", "response"],
    reverse = c(2, 4, 7),
    scale_min = 0L,
    scale_max = 4L
  )
  
  
  #### Behavioral summary ------------------------------------------------------
  ## Filter and summarize behavioral data (250–900 ms)
  behavior <- summarize_behavior(participant_data, rt_min = 250, rt_max = 900)
  
  
  #### Save participant summary ------------------------------------------------
  ## Ensure output directory is created
  dir.create(
    here::here("data", "cleaned", "participants"),
    recursive = TRUE,
    showWarnings = FALSE
  )
  
  ## Combine into a single-row participant summary
  df_clean <- data.frame(
    subject_id = subject_id,
    tef10_score = tef10_score,
    behavior = behavior
  )
  
  
  ## Save summary CSV to cleaned/participants
  write.csv(
    df_clean,
    here::here("data", "cleaned", paste0(subject_id, "_processed.csv")),
    row.names = FALSE
  )
  
  #### Return output -----------------------------------------------------------
  stopifnot(nrow(df_clean) == 1)  # one row per participant
  return(df_clean)
}

A few notes:

  • We updated the process_participant function to now use just the file_name as an input argument instead of the full file_path, because that is now handled by here:here()
  • This means we need to update where we build our file_list in our Quarto report to be full.names = FALSE instead of TRUE:
#### 1) Find files -------------------------------------------------------------
```{r}
# Prefer a pattern to avoid accidentally pulling other CSVs
file_list <- list.files(
  here::here("data", "raw"),
  pattern = "^npt-experiment-.*\\.csv$",
  full.names = FALSE
)
```

Key points:

  • All file locations are built with here::here(), so they work from anywhere.
  • No setwd() or ../ paths are needed.
  • The function now runs identically when sourced and tested in RStudio (interactively) or when called inside a Quarto report chunk.

Testing It Interactively

From the Console or an R Script (with the .Rproj open):

source("scripts/process_participant.R")
processed <- process_participant("npt-experiment-2025-11-05-10-08-45.csv")
head(processed)

Using It in a Quarto Report

Inside reports/npt_project.qmd:

```{r}
source(here::here("scripts", "process_participant.R"))
processed <- process_participant("npt-experiment-2025-11-05-10-08-45.csv")
```

Because every path inside the function starts at the project root,
the code runs the same way when rendered.


4 · Applying This Pattern to Other Scripts

You can use the same here::here() pattern anywhere a path appears in your other NPT functions:

Script Where to Add here::here() Example
process_participant.R As shown above, handles both loading and saving participant data. read.csv(here::here("data", "raw", file_name))
write.csv(here::here("data", "cleaned", "participants", paste0(subject_id, ".csv")))
Quarto Report When sourcing scripts or loading data within a .qmd file. source(here::here("scripts", "process_participant.R"))
score_questionnaire.R If you wanted to extend this function to read or write data files, use here::here() for any file paths. jsonlite::read_json(here::here("data", "raw", "questionnaire.json"))
summarize_behavior.R If you wanted to add code that writes or saves summary tables or plots, use here::here() for those output paths. write.csv(summary, here::here("output", "tables", "behavior_summary.csv"))

By including here::here() inside your functions, each script becomes self-contained and portable,
and your Quarto reports can source them directly without path edits.


5 · Summary

Concept Without here::here() With here::here()
Quarto report cannot find files Paths break ("../data/...") Always resolves from project root
Testing vs. rendering behavior Different working directories Identical behavior everywhere
Need for setwd() Often used, brittle Never needed
Reproducibility and portability Dependent on computer setup Fully reproducible across machines

Take-home message:
Use here::here() for every file path in your functions and scripts.
It keeps your workflow reproducible, your functions self-contained, and your Quarto reports consistent.