/r-performance
R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when optimizing R code.
$ npx -y skills add ab604/claude-code-r-skills --skill r-performance --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/r-performance
Context preview
The summary Claude sees to decide when to auto-load this skill.
R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when optimizing R code.
SKILL.md
r-performance.SKILL.mdname: r-performance
description: R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when optimizing R code.
R Performance Best Practices
*Profiling, benchmarking, and optimization strategies for R code*
Performance Tool Selection Guide
When to Use Each Performance Tool
Profiling Tools Decision Matrix
| Tool | Use When | Don't Use When | What It Shows | |------|----------|----------------|---------------| | **`profvis`** | Complex code, unknown bottlenecks | Simple functions, known issues | Time per line, call stack | | **`bench::mark()`** | Comparing alternatives | Single approach | Relative performance, memory | | **`system.time()`** | Quick checks | Detailed analysis | Total runtime only | | **`Rprof()`** | Base R only environments | When profvis available | Raw profiling data |
Step-by-Step Performance Workflow
# 1. Profile first - find the actual bottlenecks
library(profvis)
profvis({
# Your slow code here
})
# 2. Focus on the slowest parts (80/20 rule)
# Don't optimize until you know where time is spent
# 3. Benchmark alternatives for hot spots
library(bench)
bench::mark(
current = current_approach(data),
vectorized = vectorized_approach(data),
parallel = map(data, in_parallel(func))
)
# 4. Consider tool trade-offs based on bottleneck typeWhen Each Tool Helps vs Hurts
Parallel Processing (`in_parallel()`)
# Helps when:
# - CPU-intensive computations
# - Embarassingly parallel problems
# - Large datasets with independent operations
# - I/O bound operations (file reading, API calls)
# Hurts when:
# - Simple, fast operations (overhead > benefit)
# - Memory-intensive operations (may cause thrashing)
# - Operations requiring shared state
# - Small datasets
# Example decision point:
expensive_func <- function(x) Sys.sleep(0.1) # 100ms per call
fast_func <- function(x) x^2 # microseconds per call
# Good for parallel
map(1:100, in_parallel(expensive_func)) # ~10s -> ~2.5s on 4 cores
# Bad for parallel (overhead > benefit)
map(1:100, in_parallel(fast_func)) # 100us -> 50ms (500x slower!)
vctrs Backend Tools
# Use vctrs when:
# - Type safety matters more than raw speed
# - Building reusable package functions
# - Complex coercion/combination logic
# - Consistent behavior across edge cases
# Avoid vctrs when:
# - One-off scripts where speed matters most
# - Simple operations where base R is sufficient
# - Memory is extremely constrained
# Decision point:
simple_combine <- function(x, y) c(x, y) # Fast, simple
robust_combine <- function(x, y) vec_c(x, y) # Safer, slight overhead
# Use simple for hot loops, robust for package APIs
Data Backend Selection
# Use data.table when:
# - Very large datasets (>1GB)
# - Complex grouping operations
# - Reference semantics desired
# - Maximum performance critical
# Use dplyr when:
# - Readability and maintainability priority
# - Complex joins and window functions
# - Team familiarity with tidyverse
# - Moderate sized data (<100MB)
# Use base R when:
# - No dependencies allowed
# - Simple operations
# - Teaching/learning contexts
Profiling Best Practices
# 1. Profile realistic data sizes
profvis({
# Use actual data size, not toy examples
real_data |> your_analysis()
})
# 2. Profile multiple runs for stability
bench::mark(
your_function(data),
min_iterations = 10, # Multiple runs
max_iterations = 100
)
# 3. Check memory usage too
bench::mark(
approach1 = method1(data),
approach2 = method2(data),
check = FALSE, # If outputs differ slightly
filter_gc = FALSE # Include GC time
)
# 4. Profile with realistic usage patterns
# Not just isolated function callsPerformance Anti-Patterns to Avoid
# Don't optimize without measuring
# BAD: "This looks slow" -> immediately rewrite
# GOOD: Profile first, optimize bottlenecks
# Don't over-engineer for performance
# BAD: Complex optimizations for 1% gains
# GOOD: Focus on algorithmic improvements
# Don't assume - measure
# BAD: "for loops are always slow in R"
# GOOD: Benchmark your specific use case
# Don't ignore readability costs
# BAD: Unreadable code for minor speedups
# GOOD: Readable code with targeted optimizations
Backend Tools for Performance
- **Consider lower-level tools when speed is critical**
- **Use vctrs, rlang backends when appropriate**
- **Profile to identify true bottlenecks**
# For packages - consider backend tools
# vctrs for type-stable vector operations
# rlang for metaprogramming
# data.table for large data operations
When to Use vctrs
Core Benefits
- **Type stability** - Predictable output types regardless of input values
- **Size stability** - Predictable output sizes from input sizes
- **Consistent coercion rules** - Single set of rules applied everywhere
- **Robust class design** - Proper S3 vector infrastructure
Use vctrs when
Building Custom Vector Classes
# Good - vctrs-based vector class
new_percent <- function(x = double()) {
vec_assert(x, double())
new_vctr(x, class = "pkg_percent")
}
# Automatic data frame compatibility, subsetting, etc.Type-Stable Functions in Packages
# Good - Guaranteed output type
my_function <- function(x, y) {
# Always returns double, regardless of input values
vec_cast(result, double())
}
# Avoid - Type depends on data
sapply(x, function(i) if(condition) 1L else 1.0)Consistent Coercion/Casting
# Good - Explicit casting with clear rules
vec_cast(x, double()) # Clear intent, predictable behavior
# Good - Common type finding
vec_ptype_common(x, y, z) # Finds richest compatible type
# Avoid - Base R inconsistencies
c(factor("a"), "b") # Unpredictable behaviorSize/Length Stability
# Good - Predictable sizing
vec_c(x, y) # size = vec_size(x) + vec_size(y)
vec_rbind(df1, df2) # size = sum of input sizes
#
Read more
name: r-performance description: R performance best practices including profiling, benchmarking, vctrs, and optimization strategies. Use when optimizing R code.
R Performance Best Practices
*Profiling, benchmarking, and optimization strategies for R code*
Performance Tool Selection Guide
When to Use Each Performance Tool
Profiling Tools Decision Matrix
| Tool | Use When | Don't Use When | What It Shows | |------|----------|----------------|---------------| | **`profvis`** | Complex code, unknown bottlenecks | Simple functions, known issues | Time per line, call stack | | **`bench::mark()`** | Comparing alternatives | Single approach | Relative performance, memory | | **`system.time()`** | Quick checks | Detailed analysis | Total runtime only | | **`Rprof()`** | Base R only environments | When profvis available | Raw profiling data |
Step-by-Step Performance Workflow
# 1. Profile first - find the actual bottlenecks
library(profvis)
profvis({
# Your slow code here
})
# 2. Focus on the slowest parts (80/20 rule)
# Don't optimize until you know where time is spent
# 3. Benchmark alternatives for hot spots
library(bench)
bench::mark(
current = current_approach(data),
vectorized = vectorized_approach(data),
parallel = map(data, in_parallel(func))
)
# 4. Consider tool trade-offs based on bottleneck typeWhen Each Tool Helps vs Hurts
Parallel Processing (`in_parallel()`)
# Helps when: # - CPU-intensive computations # - Embarassingly parallel problems # - Large datasets with independent operations # - I/O bound operations (file reading, API calls) # Hurts when: # - Simple, fast operations (overhead > benefit) # - Memory-intensive operations (may cause thrashing) # - Operations requiring shared state # - Small datasets # Example decision point: expensive_func <- function(x) Sys.sleep(0.1) # 100ms per call fast_func <- function(x) x^2 # microseconds per call # Good for parallel map(1:100, in_parallel(expensive_func)) # ~10s -> ~2.5s on 4 cores # Bad for parallel (overhead > benefit) map(1:100, in_parallel(fast_func)) # 100us -> 50ms (500x slower!)
vctrs Backend Tools
# Use vctrs when: # - Type safety matters more than raw speed # - Building reusable package functions # - Complex coercion/combination logic # - Consistent behavior across edge cases # Avoid vctrs when: # - One-off scripts where speed matters most # - Simple operations where base R is sufficient # - Memory is extremely constrained # Decision point: simple_combine <- function(x, y) c(x, y) # Fast, simple robust_combine <- function(x, y) vec_c(x, y) # Safer, slight overhead # Use simple for hot loops, robust for package APIs
Data Backend Selection
# Use data.table when: # - Very large datasets (>1GB) # - Complex grouping operations # - Reference semantics desired # - Maximum performance critical # Use dplyr when: # - Readability and maintainability priority # - Complex joins and window functions # - Team familiarity with tidyverse # - Moderate sized data (<100MB) # Use base R when: # - No dependencies allowed # - Simple operations # - Teaching/learning contexts
Profiling Best Practices
# 1. Profile realistic data sizes
profvis({
# Use actual data size, not toy examples
real_data |> your_analysis()
})
# 2. Profile multiple runs for stability
bench::mark(
your_function(data),
min_iterations = 10, # Multiple runs
max_iterations = 100
)
# 3. Check memory usage too
bench::mark(
approach1 = method1(data),
approach2 = method2(data),
check = FALSE, # If outputs differ slightly
filter_gc = FALSE # Include GC time
)
# 4. Profile with realistic usage patterns
# Not just isolated function callsPerformance Anti-Patterns to Avoid
# Don't optimize without measuring # BAD: "This looks slow" -> immediately rewrite # GOOD: Profile first, optimize bottlenecks # Don't over-engineer for performance # BAD: Complex optimizations for 1% gains # GOOD: Focus on algorithmic improvements # Don't assume - measure # BAD: "for loops are always slow in R" # GOOD: Benchmark your specific use case # Don't ignore readability costs # BAD: Unreadable code for minor speedups # GOOD: Readable code with targeted optimizations
Backend Tools for Performance
- **Consider lower-level tools when speed is critical**
- **Use vctrs, rlang backends when appropriate**
- **Profile to identify true bottlenecks**
# For packages - consider backend tools # vctrs for type-stable vector operations # rlang for metaprogramming # data.table for large data operations
When to Use vctrs
Core Benefits
- **Type stability** - Predictable output types regardless of input values
- **Size stability** - Predictable output sizes from input sizes
- **Consistent coercion rules** - Single set of rules applied everywhere
- **Robust class design** - Proper S3 vector infrastructure
Use vctrs when
Building Custom Vector Classes
# Good - vctrs-based vector class
new_percent <- function(x = double()) {
vec_assert(x, double())
new_vctr(x, class = "pkg_percent")
}
# Automatic data frame compatibility, subsetting, etc.Type-Stable Functions in Packages
# Good - Guaranteed output type
my_function <- function(x, y) {
# Always returns double, regardless of input values
vec_cast(result, double())
}
# Avoid - Type depends on data
sapply(x, function(i) if(condition) 1L else 1.0)Consistent Coercion/Casting
# Good - Explicit casting with clear rules
vec_cast(x, double()) # Clear intent, predictable behavior
# Good - Common type finding
vec_ptype_common(x, y, z) # Finds richest compatible type
# Avoid - Base R inconsistencies
c(factor("a"), "b") # Unpredictable behaviorSize/Length Stability
# Good - Predictable sizing vec_c(x, y) # size = vec_size(x) + vec_size(y) vec_rbind(df1, df2) # size = sum of input sizes #
A curated collection of Claude Code configurations for modern R use. These skills, rules, commands, and agents help Claude Code understand R best practices and generate idiomatic, high-quality R code.
Repo: ab604/claude-code-r-skills
Other skills on r-skills.
- /r-bayes
Patterns for Bayesian inference in R using brms, including multilevel models, DAG validation, and marginal effects. Use when performing Bayesian analysis.
Open skill - /r-oop
R object-oriented programming guide for S7, S3, S4, and vctrs. Use when designing R classes or choosing an OOP system.
Open skill - /r-package-development
R package development guide covering dependencies, API design, testing, and documentation. Use when developing R packages.
Open skill - /r-style-guide
R style guide covering naming conventions, spacing, layout, and function design best practices. Use when writing R code.
Open skill - /rlang-patterns
rlang metaprogramming patterns for data-masking, injection operators, and dynamic dots. Use when writing functions that use tidy evaluation.
Open skill - /tdd-workflow
Test-driven development workflow for R using testthat. Use when writing new features, fixing bugs, or refactoring code. Enforces test-first development with 80%+ coverage.
Open skill

