tidyfast R Package

repository·master·Indexed 41 days ago

https://github.com/tysonstanley/tidyfast

An R package providing high-performance, tidyverse-style alternatives to common tidyr and dplyr functions by leveraging data.table and C++ (via cpp11). It includes fast implementations of nesting (dt_nest), unnesting (dt_unnest, dt_hoist), pivoting (dt_pivot_longer, dt_pivot_wider), conditional logic (dt_case_when), filling missing values (dt_fill), counting (dt_count, dt_uncount), and column splitting (dt_separate). All functions use the dt_ prefix and return data.table objects.

Tokens
3.3K
Snippets
12
Records
21
Agent score
51%

What's inside tidyfast

  1. Overview of tidyfast

    master

    tidyfast provides fast and efficient alternatives to certain tidyr and dplyr functions by using data.table under the hood.

    Key characteristics:

    • Prefixing: All functions use the dt_ prefix (e.g., dt_nest()) to enable easy autocomplete in IDEs like RStudio.
    • Automatic Coercion: Any function that takes data as its first argument will automatically coerce the input to a data.table using as.data.table() if it isn't one already.
    • Return Type: All functions return a data.table.
    • Relationship to dtplyr: While tidyfast complements dtplyr, it does not use the lazy_dt() framework. Note that some dtplyr functionality may overlap with tidyfast.
    • Dependencies: The package imports data.table and cpp11.
  2. Install tidyfast

    master

    You can install the stable version of tidyfast from CRAN or the development version from GitHub.

    From CRAN:

    install.packages("tidyfast")

    From GitHub (Development version):

    # install.packages("remotes")
    remotes::install_github("TysonStanley/tidyfast")
    install.packages("tidyfast")
  3. How tidyfast works with data.table

    master

    The goal of tidyfast is to provide fast and efficient alternatives to tidyr and dplyr functions using data.table under the hood.

    Key Characteristics:

    • Prefixing: All functions use the dt_ prefix (e.g., dt_nest()) to allow for easy autocomplete in IDEs like RStudio.
    • Automatic Coercion: Any function that takes data as its first argument will automatically coerce it to a data.table using as.data.table() if it isn't one already.
    • Return Type: All functions return a data.table.
    • Grammar: It attempts to maintain tidyr syntax and grammar while translating it into high-performance data.table calls.

    Note: Some functionality in tidyfast may be redundant with dtplyr. Check dtplyr for a list of functions handled within that framework.

  4. Perform conditional logic with dt_case_when()

    master

    The dt_case_when() function provides a syntax similar to dplyr::case_when() but is built on the highly efficient data.table::fifelse(). It is significantly faster and more memory-efficient than dplyr::case_when() for large datasets.

    # Syntax: dt_case_when(condition1 ~ label1, condition2 ~ label2, ...)
    
    x_cat <- dt_case_when(x < medianx ~ "low",
                   x >= medianx ~ "high",
                   is.na(x) ~ "unknown")
  5. Filling missing values with dt_fill

    master
    dt_fill() is a fast alternative to tidyr::fill(). It allows you to fill NA values with values appearing before, after, or both, and can be performed within specific groups (e.g., filling NA values within an individual ID).
  6. Splitting columns with dt_separate

    master

    dt_separate() is a fast alternative to tidyr::separate(). It splits a single column into multiple columns based on a separator (e.g., splitting "A.B" into "A" and "B" using the period). It is built on data.table::tstrsplit().

    Note: This function is currently less feature-rich than tidyr::separate() and is noted as being less tested.

  7. Separate columns with dt_separate()

    master

    The dt_separate() function is a high-performance alternative to tidyr::separate(). It is significantly faster and more memory-efficient, especially for large datasets.

    Important Usage Note: Currently, the into argument must be supplied with the maximum number of possible columns that could be created by the separation. If the number of columns provided in into is less than the actual number of items produced by the split, an error will be thrown.

    # Basic usage
    dt_to_split <- data.table(x = paste(letters, LETTERS, sep = "."))
    dt_separate(dt_to_split, x, into = c("lower", "upper"))
    
    # Note: If separation produces 3 items but 'into' only has 2, it will error:
    # dt_separate(data.table(col = "A.B.C"), col, into = c("A", "B"))
  8. Nesting and Unnesting with dt_nest, dt_unnest, and dt_hoist

    master

    tidyfast provides high-performance alternatives for managing nested data structures, similar to dplyr::group_nest() and tidyr::unnest().

    • dt_nest(): Nests a data.table by one or more grouping variables.
    • dt_unnest(): Unnests list-columns containing data.table objects back into a flat data.table.
    • dt_hoist(): Specifically used for unnesting vectors within a list-column. It preserves all other non-list-column variables.

    Example: Nesting and Unnesting data tables

    library(tidyfast)
    library(data.table)
    
    dt <- data.table(x = 1:10, grp = c(1, 1, 2, 2, 3, 3, 4, 4, 5, 5))
    
    # Nesting
    nested <- dt_nest(dt, grp)
    
    # Unnesting
    dt_unnest(nested, col = data)

    Example: Hoisting vectors from list-columns

    # If list-columns contain vectors instead of data.tables
    dt_hoist(dt, list_column_name)
    library(tidyfast)
    library(data.table)
    
    dt <- data.table(
       x = rnorm(1e5),
       y = runif(1e5),
       grp = sample(1L:5L, 1e5, replace = TRUE),
       nested1 = lapply(1:10, sample, 10, replace = TRUE),
       nested2 = lapply(c("thing1", "thing2"), sample, 10, replace = TRUE),
       id = 1:1e5)
    
    # Nesting
    nested <- dt_nest(dt, grp)
    
    # Unnesting
    dt_unnest(nested, col = data)
    
    # Hoisting vectors
    dt_hoist(dt, nested1, nested2)
  9. Count and Uncount rows with dt_count() and dt_uncount()

    master

    The dt_count() and dt_uncount() functions provide tidyverse-style counting operations optimized for data.table.

    • dt_count(dt, ...): Wraps data.table logic to provide concise grouping and counting, similar to dplyr::count().
    • dt_uncount(dt, n): Expands rows based on a count column, similar to tidyr::uncount().
    # Count occurrences of a group
    counted <- dt_count(dt, grp)
    
    # Uncount (expand) based on a count column
    uncounted <- dt_uncount(counted, N)
  10. Fill NA values with dt_fill()

    master

    The dt_fill() function replaces NA values with values from surrounding rows (up, down, or both). It utilizes efficient C++ code from tidyr (fillUp() and fillDown()) and is faster and more memory-efficient than tidyr::fill().

    Arguments:

    • ...: The columns to fill.
    • id: A list of variables to group by (e.g., id = list(grp)).
    • .direction: The direction of filling. Options include "down", "up", or "downup" (which tries both).
    • immutable: Boolean indicating whether to modify the object in place.
    # Fill all columns by default
    dt_fill(dt_with_nas, y, z, a, immutable = FALSE)
    
    # Fill by group (id) using 'downup' direction
    dt_fill(dt_with_nas, 
            y, z, a, 
            id = list(id), 
            .direction = "downup")
  11. Pivot data using dt_pivot_longer() and dt_pivot_wider()

    master

    The dt_pivot_longer() and dt_pivot_wider() functions provide high-performance translations of tidyr::pivot_longer() and tidyr::pivot_wider() using data.table::melt() and data.table::dcast(). These functions are extremely fast and memory-efficient compared to their tidyr counterparts.

    Limitations:

    1. tidyselect techniques (like start_with()) do not work across the board.
    2. The functions are new and may contain edge-case bugs.

    Note on Data Types: When using dt_pivot_longer(), if the columns being melted have different data types, data.table::melt() will coerce them to a common type (usually double) based on a hierarchy. Check the warning messages to see how coercion is handled.

    # Pivot longer
    longer <- billboard %>%
      dt_pivot_longer(
         cols = c(-artist, -track, -date.entered),
         names_to = "week",
         values_to = "rank"
      )
    
    # Pivot wider
    wider <- longer %>% 
      dt_pivot_wider(
        names_from = week,
        values_from = rank
      )