Package Function Structure

UC Davis Seroepidemiology Research Group (UCD-SERG)

2026-02-03

Code

1 Overview

This article demonstrates how to visualize the structure and dependencies of package functions using the foodwebr package. Understanding function dependencies helps developers:

  • Navigate and understand codebases more quickly
  • Identify potential refactoring opportunities
  • Document architecture and design decisions
  • Onboard new contributors more effectively

2 What is foodwebr?

The foodwebr package creates dependency graphs showing which functions call which other functions. These visualizations are particularly useful for:

  • Exploring unfamiliar codebases
  • Understanding function relationships
  • Identifying central or isolated functions
  • Planning refactoring efforts

3 Installing foodwebr

If you don’t have foodwebr installed, you can install it from CRAN:

Code
install.packages("foodwebr")

Or from GitHub for the latest development version:

Code
devtools::install_github("lewinfox/foodwebr")

4 Visualizing Package Structure

4.1 Basic Usage

The foodweb() function analyzes function dependencies. When examining a package, you typically want to look at specific functions or the entire package namespace:

Code
library(foodwebr)

# Create a foodweb for the example_function
fw <- foodweb(FUN = example_function)
fw
#> # A `foodweb`: 6 vertices and 7 edges 
#> digraph 'foodweb' {
#>   example_function() -> { calculate_statistic(), format_result() }
#>   calculate_summary() -> { example_function(), format_result(), clean_data() }
#>   calculate_statistic() -> { clean_data() }
#>   format_result()
#>   clean_data() -> { validate_input() }
#>   validate_input()
#> }

4.2 Plotting the Dependency Graph

The plot() method creates an interactive visualization using DiagrammeR:

Code
plot(fw)
Figure 1: Basic function dependency graph for example_function
NoteUnderstanding the Graph
  • Nodes represent individual functions
  • Edges (arrows) show function calls
  • An arrow from A to B means “A calls B”
  • Isolated nodes indicate functions with no dependencies or dependents

4.3 Filtering Options

By default, foodwebr filters to show only functions directly related to the specified function. You can control this behavior:

Code
# Show only connected functions (default)
fw_filtered <- foodweb(FUN = example_function, filter = TRUE)

# Show all functions in the environment
fw_all <- foodweb(FUN = example_function, filter = FALSE)

4.4 Examining the Entire Package

To see all exported and internal functions in the package:

Code
# Get the package namespace environment
pkg_env <- asNamespace("rpt")

# Create foodweb for entire package
fw_pkg <- foodweb(env = pkg_env)
fw_pkg
#> # A `foodweb`: 6 vertices and 7 edges 
#> digraph 'foodweb' {
#>   calculate_statistic() -> { clean_data() }
#>   calculate_summary() -> { clean_data(), example_function(), format_result() }
#>   clean_data() -> { validate_input() }
#>   example_function() -> { calculate_statistic(), format_result() }
#>   format_result()
#>   validate_input()
#> }
Code
plot(fw_pkg)
Figure 2: Complete dependency graph for all package functions

5 Analyzing Dependencies

5.1 Understanding Function Roles

From the dependency graph, you can identify different types of functions:

  • Leaf functions: Functions that don’t call any other package functions (only base R or external packages)
  • Root functions: Functions that are called by many others but don’t call package functions themselves
  • Hub functions: Functions that both call and are called by many functions
  • Isolated functions: Functions with no connections to others

5.2 Example Analysis

For our package:

Code
# Convert to text representation for inspection
cat(as.character(fw_pkg))
#> digraph 'foodweb' {
#>   "calculate_statistic()" -> { "clean_data()" }
#>   "calculate_summary()" -> { "clean_data()", "example_function()", "format_result()" }
#>   "clean_data()" -> { "validate_input()" }
#>   "example_function()" -> { "calculate_statistic()", "format_result()" }
#>   "format_result()"
#>   "validate_input()"
#> }
TipDependency Patterns

In well-designed packages, you typically see:

  1. Clear hierarchy: Lower-level utility functions are called by higher-level API functions
  2. Limited cross-dependencies: Functions in the same “layer” don’t call each other excessively
  3. Focused functions: Each function has a clear purpose with limited dependencies

In the rpt package, we can see this pattern:

  • validate_input() is a low-level helper (no package dependencies)
  • clean_data() calls validate_input()
  • calculate_statistic() calls clean_data()
  • format_result() is a low-level formatter (no package dependencies)
  • example_function() is a high-level API that calls calculate_statistic() and format_result()
  • calculate_summary() is another high-level API that calls clean_data() and example_function()

This creates a clear layered architecture with well-separated concerns.

6 Package Architecture

6.1 Current Structure

Let’s examine the actual structure of the rpt package:

Code
# List all functions in the package
pkg_functions <- ls(asNamespace("rpt"))
cat("Package functions:\n")
#> Package functions:
cat(paste("-", sort(pkg_functions), collapse = "\n"))
#> - calculate_statistic
#> - calculate_summary
#> - clean_data
#> - example_function
#> - format_result
#> - validate_input

The dependency relationships are:

  • Exported functions (user-facing API):
  • Internal functions (implementation details):
    • validate_input(): No package dependencies (leaf function)
    • clean_data(): Calls validate_input()
    • calculate_statistic(): Calls clean_data()
    • format_result(): No package dependencies (leaf function)

This architecture demonstrates good separation of concerns with clear data flow from validation through cleaning to calculation and formatting.

7 Advanced Usage

7.1 Using with tidygraph

The tidygraph package provides powerful tools for graph analysis. You can convert a foodweb object to work with these tools:

Code
if (requireNamespace("tidygraph", quietly = TRUE)) {
  tg <- tidygraph::as_tbl_graph(fw_pkg)
  print(tg)
}
#> # A tbl_graph: 6 nodes and 7 edges
#> #
#> # A directed acyclic simple graph with 1 component
#> #
#> # Node Data: 6 × 1 (active)
#>   name               
#>   <chr>              
#> 1 calculate_statistic
#> 2 calculate_summary  
#> 3 clean_data         
#> 4 example_function   
#> 5 format_result      
#> 6 validate_input     
#> #
#> # Edge Data: 7 × 2
#>    from    to
#>   <int> <int>
#> 1     1     3
#> 2     2     3
#> 3     2     4
#> # ℹ 4 more rows

7.2 Customizing Visualizations

You can pass additional arguments to customize the graph appearance:

Code
# These arguments are passed to DiagrammeR::grViz()
plot(fw_pkg, 
     width = 800, 
     height = 600)

7.3 Exporting as Text

Get the graphviz DOT representation:

Code
# As text
foodweb_text <- foodweb(env = asNamespace("rpt"), as.text = TRUE)
cat(foodweb_text)
#> digraph 'foodweb' {
#>   "calculate_statistic()" -> { "clean_data()" }
#>   "calculate_summary()" -> { "clean_data()", "example_function()", "format_result()" }
#>   "clean_data()" -> { "validate_input()" }
#>   "example_function()" -> { "calculate_statistic()", "format_result()" }
#>   "format_result()"
#>   "validate_input()"
#> }

8 Practical Applications

8.1 Code Review

Use dependency graphs during code review to:

  • Verify that new functions follow existing architectural patterns
  • Identify unexpected dependencies
  • Ensure proper separation of concerns
  • Detect circular dependencies

8.2 Refactoring

Before refactoring:

  1. Generate a dependency graph of the affected area
  2. Identify all functions that will be impacted
  3. Plan changes to minimize ripple effects
  4. After refactoring, regenerate the graph to verify improvements

8.3 Documentation

Include dependency graphs in:

  • Developer documentation
  • Architecture decision records
  • Package vignettes (like this one!)
  • README files for complex packages
ImportantKeeping Documentation Updated

When adding new functions or modifying dependencies, regenerate the foodweb graphs to keep documentation current. Consider adding automated checks in your CI/CD pipeline.

9 Exploring Specific Functions

You can focus on specific functions to understand their dependencies:

9.1 Example Function Dependencies

Code
fw_example <- foodweb(FUN = example_function)
plot(fw_example)
Figure 3: Dependency graph for example_function showing all related functions

9.2 Summary Function Dependencies

Code
fw_summary <- foodweb(FUN = calculate_summary)
plot(fw_summary)
Figure 4: Dependency graph for calculate_summary showing its relationships

These focused views help understand:

  • What functions a specific function depends on (its dependencies)
  • What functions depend on a specific function (its dependents)
  • The complete call chain for a given function

10 Best Practices

When designing package architecture:

  1. Minimize dependencies: Each function should have a clear, focused purpose
  2. Avoid circular dependencies: A should not call B if B calls A
  3. Layer your functions: Separate low-level utilities from high-level APIs
  4. Document dependencies: Use foodwebr graphs in your documentation
  5. Review regularly: Check dependency graphs during code review

11 Learn More

12 Summary

This article demonstrated how to use foodwebr to visualize and understand package function dependencies. Key takeaways:

  • foodwebr creates interactive dependency graphs
  • Filtering options help focus on specific functions or show entire packages
  • Dependency graphs aid in code review, refactoring, and documentation
  • Integration with tidygraph enables advanced graph analysis
  • Regular visualization helps maintain clean architecture

The rpt package demonstrates a clean layered architecture with: - Low-level utility functions (validate_input(), format_result()) - Mid-level processing functions (clean_data(), calculate_statistic()) - High-level API functions (example_function(), calculate_summary())

This structure makes the code easier to understand, test, and maintain.

13 References