Skip to content

R Packages #12

Open
Open

Description

@laxamanaj

Image

Image

Activity

  1. self-assigned this
    on Feb 19, 2025
  2. linked a pull request that will close this issuefirst commit for listing R packages function #20on Mar 29, 2025
  3. pstorozenko commented on May 28, 2025

    @pstorozenko

    Hi, we discussed at Appsilon how to extract all packages and function from the script and those are our findings.

    Insights

    1. NCmisc::list.functions.in.file() -> lists all functions from the script. It needs library() calls to be executed first. For example:
    # main.R
    library(dplyr)
    internal_function <- function() {
      return("This is an internal function")
    }
    internal_function()
    df <- iris %>%
      filter(Petal.Length > 1.5) %>%
      select(Species, Petal.Length)
    
    print(head(df))

    Gives

    Image

    Without loading the library it gives

    Image

    To find all library/require calls you can regular expressions, but for more reliability you can use XML Xpath queries or treesitter.

    This is not {box} compatible though.
    2. For box you can take inspiration from box.linters::box_usage_linter() and box.linters::box_unused_att_pkg_fun_linter() functions implementations in box.linters package.

    A PoC for NCmisc version

    In details, I attach the PoC. Note that since this PoC works on regular expressions, it fails on lines like # library(...), i.e., it recognises them as imports.

    Details
    get_functions_df <- function(file) {
      # Extract packages from file (both library and require)
      code <- readLines(file)
      
      # Match library() and require() with various quote styles
      lib_pattern <- '(library|require)\\s*\\(\\s*["\']?([^"\'\\)\\s]+)["\']?\\s*\\)'
      matches <- regmatches(code, gregexpr(lib_pattern, code))
      
      # Extract package names
      pkgs <- unique(unlist(lapply(matches, function(m) {
        gsub(lib_pattern, '\\2', m)
      })))
      
      # Load packages
      invisible(lapply(pkgs, function(p) {
        suppressPackageStartupMessages(require(p, character.only = TRUE, quietly = TRUE))
      }))
      
      # Get functions from NCmisc
      func_list <- NCmisc::list.functions.in.file(file)
      
      # Process into clean data frame
      df_list <- list()
      
      for (name in names(func_list)) {
        funcs <- func_list[[name]]
        if (length(funcs) == 0) next
        
        # Handle multiple packages
        if (grepl("^c\\(", name)) {
          # Extract individual package names
          pkgs <- gsub('"', '', unlist(strsplit(gsub("c\\(|\\)", "", name), ", ")))
          pkg_str <- paste(pkgs, collapse = ", ")
        } else {
          pkg_str <- gsub('"', '', name)
        }
        
        # Clean up package names
        pkg_str <- gsub("character\\(0\\)", "user_defined", pkg_str)
        
        df_list[[length(df_list) + 1]] <- data.frame(
          func_name = funcs,
          package = pkg_str,
          stringsAsFactors = FALSE
        )
      }
      
      # Combine and sort
      result <- do.call(rbind, df_list)
      result[order(result$package, result$func_name), ]
    }
    
    # Example usage:
    df <- get_functions_df("main.R")
    print(df)

    For the main.R from above we get, note that filter is in both dplyr and stats

    ➜ Rscript script.R
              func_name                      package
    3           library                 package:base
    4             print                 package:base
    5            return                 package:base
    6            select                package:dplyr
    1            filter package:dplyr, package:stats
    7              head                package:utils
    2 internal_function                 user_defined
    

    I hope it helps anyone working on this in the future.

  4. laxamanaj commented on Jun 6, 2025

    @laxamanaj
    CollaboratorAuthor

    Hi, we discussed at Appsilon how to extract all packages and function from the script and those are our findings.

    Insights

    1. NCmisc::list.functions.in.file() -> lists all functions from the script. It needs library() calls to be executed first. For example:

    main.R

    library(dplyr)
    internal_function <- function() {
    return("This is an internal function")
    }
    internal_function()
    df <- iris %>%
    filter(Petal.Length > 1.5) %>%
    select(Species, Petal.Length)

    print(head(df))
    Gives

    Image

    Without loading the library it gives

    Image

    To find all library/require calls you can regular expressions, but for more reliability you can use XML Xpath queries or treesitter.

    This is not {box} compatible though. 2. For box you can take inspiration from box.linters::box_usage_linter() and box.linters::box_unused_att_pkg_fun_linter() functions implementations in box.linters package.

    A PoC for NCmisc version

    In details, I attach the PoC. Note that since this PoC works on regular expressions, it fails on lines like # library(...), i.e., it recognises them as imports.

    get_functions_df <- function(file) {

    Extract packages from file (both library and require)

    code <- readLines(file)

    Match library() and require() with various quote styles

    lib_pattern <- '(library|require)\s*\(\s*["']?([^"\'\\)\\s]+)["']?\s*\)'
    matches <- regmatches(code, gregexpr(lib_pattern, code))

    Extract package names

    pkgs <- unique(unlist(lapply(matches, function(m) {
    gsub(lib_pattern, '\2', m)
    })))

    Load packages

    invisible(lapply(pkgs, function(p) {
    suppressPackageStartupMessages(require(p, character.only = TRUE, quietly = TRUE))
    }))

    Get functions from NCmisc

    func_list <- NCmisc::list.functions.in.file(file)

    Process into clean data frame

    df_list <- list()

    for (name in names(func_list)) {
    funcs <- func_list[[name]]
    if (length(funcs) == 0) next

    # Handle multiple packages
    if (grepl("^c\\(", name)) {
      # Extract individual package names
      pkgs <- gsub('"', '', unlist(strsplit(gsub("c\\(|\\)", "", name), ", ")))
      pkg_str <- paste(pkgs, collapse = ", ")
    } else {
      pkg_str <- gsub('"', '', name)
    }
    
    # Clean up package names
    pkg_str <- gsub("character\\(0\\)", "user_defined", pkg_str)
    
    df_list[[length(df_list) + 1]] <- data.frame(
      func_name = funcs,
      package = pkg_str,
      stringsAsFactors = FALSE
    )
    

    }

    Combine and sort

    result <- do.call(rbind, df_list)
    result[order(result$package, result$func_name), ]
    }

    Example usage:

    df <- get_functions_df("main.R")
    print(df)
    For the main.R from above we get, note that filter is in both dplyr and stats

    ➜ Rscript script.R
              func_name                      package
    3           library                 package:base
    4             print                 package:base
    5            return                 package:base
    6            select                package:dplyr
    1            filter package:dplyr, package:stats
    7              head                package:utils
    2 internal_function                 user_defined
    

    I hope it helps anyone working on this in the future.

    Hi, @pstorozenko .

    I appreciate you looking into this. I like how you are able to extract both the functions and the packages that they are being called from.

    This could potentially be helpful information to merge with what I did here in #20 .

    We can take this back to our R Consortium colleagues to get their feedback to see if they still want this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

documentationImprovements or additions to documentation

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions