As your R projects grow in complexity, adhering to coding best practices and optimization techniques becomes essential. Clean, consistent code not only scales better, it’s easier to debug, review, and share. In this comprehensive guide, we’ll cover:
Tidyverse style guide and naming conventions
Profiling and performance tuning with profvis and other tools
Organizing scripts, data, and outputs for maintainability
Additional tips: caching, testing, documentation, and version control
By the end, you’ll have a robust framework to keep your R projects efficient, reproducible, and team-ready.
1. Tidyverse Style Guide and Naming Conventions
Consistency in style reduces cognitive load when reading or reviewing code. The Tidyverse style guide by Hadley Wickham offers clear rules for indentation, naming, and formatting.
1.1 Indentation and Line Breaks
Use two spaces, not tabs, for indentation.
Keep line length under 80 characters for readability.
When breaking long pipelines, start new lines after the pipe (
%>%):
result <- raw_data %>%
filter(status == "active") %>%
mutate(
revenue = quantity * unit_price,
profit = revenue - cost
) %>%
group_by(region) %>%
summarise(
total_revenue = sum(revenue),
avg_profit = mean(profit)
)
1.2 Naming Conventions
Objects (variables, functions): snake_case or dot.case, consistently within a project. For example,
sales_summaryorsales.summary.Functions: verbs describing action, e.g.,
calculate_profit(),fetch_data().Constants: use ALL_CAPS if needed, e.g.,
MAX_ITERATIONS.Package names: all lowercase, no special characters.
# Good
load_raw_data <- function(path) { … }
region_codes <- c("NA", "EU", "APAC")
# Avoid
LOADRAW <- function() { … }
x1 <- data
1.3 Spacing and Operators
Surround binary operators (
+,-,%in%,==) with spaces.No spaces after unary operators (
-x,!flag).Place commas immediately after values, not before:
# Good
x <- 1 + 2
y <- -x
filtered <- df %>% filter(x > 0, y < 10)
# Avoid
x<-1+2
y <- - x
filtered <- df %>% filter( x>0 , y<10 )
1.4 Function Arguments
One argument per line when function calls or definitions exceed 80 characters:
process_data <- function(
input_data,
filter_column,
threshold = 0.5,
verbose = FALSE
) {
…
}
Default values aligned vertically for readability.
1.5 Commenting and Documentation
Use comments sparingly to explain why, not what:
r# Remove rows with missing revenue because they distort average cleaned <- raw_data %>% filter(!is.na(revenue))For longer explanations, use a block comment at the top of scripts.
Document functions with roxygen2 for automatic help file generation.
2. Profiling and Performance Tuning with profvis
Profiling pinpoints bottlenecks in your code. The profvis package offers an interactive flame graph to visualize where time is spent.
2.1 Installing and Loading profvis
install.packages("profvis")
library(profvis)
2.2 Profiling a Script
Wrap the code you want to profile in profvis({ ... }):
profvis({
# Example computation
result <- large_df %>%
mutate(
score = complex_calculation(x, y),
flag = str_detect(text, "pattern")
) %>%
group_by(category) %>%
summarise(
avg_score = mean(score),
count = n()
) %>%
inner_join(metadata, by = "category")
})
A browser window will display an interactive timeline showing:
Execution time per line and function
Memory allocation hotspots
Call stacks
2.3 Interpreting Results
Flat graph: colors denote functions, width indicates time consumed.
Data table: sorted by total time. Identify “hot” functions or heavy loops.
2.4 Optimization Strategies
Vectorize operations: replace explicit loops with vectorized functions or
purrr::map()calls.Avoid repeated computations: cache intermediate results.
Use for very large tables: its syntax and C implementations can be orders of magnitude faster.
Preallocate memory: for growing objects use functions like
vector(),numeric(), or initialize data frames before a loop.
# Bad: growing inside a loop
out <- NULL
for (i in seq_len(nrow(df))) {
out <- c(out, heavy_function(df$x[i]))
}
# Better: preallocate
out <- vector("numeric", nrow(df))
for (i in seq_len(nrow(df))) {
out[i] <- heavy_function(df$x[i])
}
Parallelize CPU-intensive tasks with packages like future, foreach, or parallel.
3. Organizing Scripts, Data, and Outputs
A well-structured project folder makes it easy for collaborators (and future you) to navigate.
3.1 Standard Directory Layout
my_project/
├─ data/
│ ├─ raw/ # immutable original data
│ ├─ processed/ # cleaned and transformed data
├─ R/ # custom functions (.R files)
├─ scripts/ # analysis scripts (e.g., 01_data_prep.R)
├─ reports/ # output reports (HTML, PDF)
├─ figures/ # plot images and dashboards
├─ tests/ # unit tests with testthat
├─ docs/ # vignettes, presentations
├─ renv/ # renv package library
├─ .gitignore # files to exclude from Git
├─ my_project.Rproj # RStudio project file
└─ README.md
Benefits
Clarity: know where to find raw versus processed data.
Modularity: separate function definitions (
R/) from execution scripts (scripts/).Reproducibility: locking dependencies with renv.
3.2 Script Naming and Ordering
Prefix scripts with numbers to define execution order:
Codice01_data_import.R 02_data_cleaning.R 03_feature_engineering.R 04_model_training.R 05_reporting.REach script ends by saving key outputs (datasets, plots, models) to
data/processedormodels/.
3.3 Centralized Configuration
Create a single config.R or 01-wrangle.R to:
Define file paths via
here::here().Load required packages:
rlibrary(tidyverse) library(lubridate) library(stringr)Set global options:
roptions(stringsAsFactors = FALSE) theme_set(theme_minimal())
Other scripts then start with:
source("scripts/config.R")
source("R/utils.R") # custom helper functions
4. Additional Optimization Tips
Beyond profiling and structure, these practices further enhance performance and maintainability.
4.1 Caching Intermediate Results
Use cache chunks in R Markdown or explicit saveRDS() / readRDS() to avoid recomputing expensive steps:
if (!file.exists("data/processed/cleaned.rds")) {
cleaned <- raw_data %>% ...heavy pipeline...
saveRDS(cleaned, "data/processed/cleaned.rds")
} else {
cleaned <- readRDS("data/processed/cleaned.rds")
}
4.2 Automated Testing
Integrate testthat tests to catch regressions when refactoring:
# tests/testthat/test-utils.R
test_that("calculate_profit returns numeric", {
expect_type(calculate_profit(100, 60), "double")
})
Regular testing ensures new optimizations don’t break existing logic.
4.3 Documentation and Vignettes
Document functions with roxygen2.
Create vignettes (
usethis::use_vignette()) to demonstrate workflows from raw data to results.Well-written docs reduce onboarding time for new collaborators.
4.4 Version Control Integration
Commit often with clear messages:
bashgit add R/utils.R git commit -m "Refactor calculate_profit for vectorized inputs"Tag releases when hitting stable milestones.
Use GitHub Actions to run tests and render reports on push.
5. Putting It All Together: Case Study
Imagine a project processing 10 million retail transactions. Here’s how you apply these best practices:
Project Initialization
Create an RStudio project with the standard layout.
Initialize renv and Git.
Data Ingestion
Script
01_data_import.Rreads raw CSV via readr and savesraw_transactions.rds.Profile import with profvis to optimize chunk size.
Data Cleaning & Feature Engineering
Script
02_data_cleaning.Ruses vectorized dplyr pipelines.Cache intermediate results with
saveRDS().Write custom functions in
R/clean_helpers.R, documented via roxygen2.
Modeling
Script
03_model_training.Rloads cleaned data, uses for speed, and future for parallel cross-validation.Profile model fitting to identify slow folds.
Reporting
report.Rmdsourcesconfig.R, loads results, and generates HTML with interactive plotly charts.Cache heavy plots to speed up knitting.
Review & Deployment
Commit changes at each step.
Push to GitHub and open pull requests for code review.
GitHub Actions run tests and render the report automatically.
Conclusion
Adopting best practices and optimization strategies pays dividends as your R projects scale. You’ve learned to:
Follow the Tidyverse style guide for consistent, readable code
Profile and tune performance with profvis and vectorization
Organize scripts, data, and outputs in a maintainable folder structure
Leverage caching, testing, documentation, and version control for robust workflows
Implement these techniques in your next project to reduce bugs, accelerate development, and foster collaboration. Clean, efficient R code isn’t just a nicety—it’s the foundation of reproducible, high-impact data science.

Comments
Post a Comment