DataFramesMeta.jl

repository·master·Indexed 19 days ago

https://github.com/juliadata/dataframesmeta.jl

A Julia package providing macro-based syntactic sugar for DataFrames.jl objects to simplify common data manipulation tasks. Inspired by R's dplyr, it offers a domain-specific language (DSL) with verbs such as @select, @transform, @subset, @orderby, @combine, and @groupby. It includes row-wise versions of macros (e.g., @rtransform), support for piping operations via @chain, and utilities like @with for high-performance column operations and @passmissing for handling missing values.

Tokens
9.1K
Snippets
33
Records
36
Agent score
66%

What's inside DataFramesMeta.jl

  1. What is DataFramesMeta.jl?

    master

    DataFramesMeta.jl is a Julia package designed to transform and summarize tabular data. It provides a domain-specific language (DSL) using Julia macros to create a convenient syntax for working with DataFrames.jl objects.

    Key characteristics:

    • Inspired by R's dplyr: It mirrors dplyr concepts to provide a familiar workflow for R users.
    • Macro-based: It uses macros (often prefixed with @) to simplify common data manipulation tasks.
    • Built on DataFrames.jl: It does not implement new data structures but provides a more ergonomic syntax for existing DataFrames.jl features.
  2. Work with multiple columns using `AsTable`

    master

    The AsTable(cols) function allows you to treat a selection of columns as a single NamedTuple. This is useful for performing operations on a group of columns at once.

    Usage Rules

    • Right-hand side: When used on the right-hand side of an operation (e.g., :y = sum(AsTable(vars))), no other columns may be referenced in that specific expression. The command :y = sum(AsTable(cols)) + :d will fail.
    • Selectors: AsTable supports column selectors like Not, Between, regular expressions, and lists of names/symbols.
    • Escaping: Everything inside AsTable is escaped by default; you do not need to use the $ prefix for column names inside AsTable on the right-hand side.
    # Example: Summing a list of variable names
    vars = ["a", "b"]
    @rtransform df :y = sum(AsTable(vars))
    
    # Example: Using AsTable with a custom function
    function fun_with_new_name(x::NamedTuple)
               nms = string.(propertynames(x))
               new_name = Symbol(join(nms, "_"), "_sum")
               s = sum(x)
               (; new_name => s)
           end
    
    @rtransform df $AsTable = fun_with_new_name(AsTable([:a, :b]))
  3. How to reference columns in DataFramesMeta.jl macros

    master

    To reference columns inside DataFramesMeta macros, use Symbols. For example, use :x to refer to the column df.x. If you have a variable varname that holds a Symbol, use the interpolation syntax $varname to refer to that column.

    # Direct symbol
    @transform(df, :new_col = :old_col + 1)
    
    # Using a variable containing a symbol
    varname = :old_col
    @transform(df, :new_col = $varname + 1)
  4. Perform group operations using `@groupby` and `@combine`

    master

    DataFramesMeta.jl supports the "split-apply-combine" workflow using @groupby.

    1. Split-Apply-Combine for Summaries: Use @groupby followed by @combine to calculate statistics for each group (e.g., calculating average sleep per taxonomic order).
    2. Split-Apply-Combine for Transformations: Use @groupby followed by @transform to add new columns where calculations are performed relative to the group (e.g., de-meaning a value relative to its group mean).
    # Grouped summary statistics
    @chain msleep begin 
        @groupby :order
        @combine begin 
            :avg_sleep = mean(:sleep_total)
            :min_sleep = minimum(:sleep_total)
            :max_sleep = maximum(:sleep_total)
            :total = length(:sleep_total)
        end
    end
    
    # Grouped transformations
    @chain msleep begin 
        @groupby :order
        @transform :sleep_genus = :sleep_total .- mean(:sleep_total)
    end
  5. Pipe operations with `@chain`

    master

    DataFramesMeta.jl re-exports the @chain macro from Chain.jl. This allows you to pipe the output of one operation into the next, creating a readable, left-to-right data processing pipeline. This avoids the need to create multiple temporary named DataFrames.

    @chain msleep begin 
        @select :name :sleep_total
        @rsubset :sleep_total > 16
    end
  6. Understand AsTable and @astable usage

    master

    DataFramesMeta uses AsTable in three distinct ways depending on its position in a transformation:

    1. $AsTable on the Left-Hand Side (LHS): Used to create multiple columns at once where the column names are determined programmatically. Note: This requires escaping with $ until the deprecation period for unquoted column names on the LHS ends.
    2. @astable macro-flag: Used within a transformation to create multiple columns at once when the number of columns is known in advance.
    3. AsTable(cols) on the Right-Hand Side (RHS): Used for multi-column transformations. Unlike the LHS version, this requires input columns to be provided.
  7. Perform row-wise operations with `@byrow` and row-wise macros

    master

    To apply operations row-by-row without manual vectorization, use the @byrow macro within DataFramesMeta.jl macros. Alternatively, you can use specific row-wise macros that use @byrow by default: @rtransform, @rtransform!, @rselect, @rselect!, @rorderby, @rsubset, and @rsubset!.

    Usage Patterns

    1. Inside a macro: Use @byrow as an argument to @transform, @transform!, @select, @select!, @combine, @subset, @subset!, @orderby, or @with.
    2. Block syntax: To apply @byrow to multiple operations in a single block, place it at the beginning of the block.
    3. Grouped DataFrames: When using @byrow with a GroupedDataFrame, the functions operate on individual rows and do not take the grouping into account (similar to ByRow in DataFrames.jl).

    Note: @byrow is not a standalone macro and must be used within DataFramesMeta.jl macros.

    # Example: Using @byrow inside @transform
    @transform(df, @byrow :y = :x == 1 ? true : false)
    
    # Example: Using @byrow in a block for multiple operations
    @subset df @byrow begin
               :a > 1
               :b < 5
           end
  8. Pass DataFrames.jl mini-language via $()

    master

    If an entire argument is wrapped in $() or $, DataFramesMeta bypasss its internal anonymous function creation and passes the argument directly to the underlying DataFrames.jl function. This allows you to use the DataFrames.jl "mini-language", such as src => fun => dest pairs.

    Example: Using multiple functions across multiple columns

    You can use the . operator with a list of functions to apply multiple operations to multiple columns simultaneously:

    @transform df $([:a, :b] .=> [sum mean])

    Example: Using src => fun => dest pairs

    You can pass complex transformation pairs directly into macros like @transform or @subset:

    my_transformation = :a => (t -> t .+ 100) => :c
    @transform df $my_transformation
    
    @subset df $(:a => t -> t .>= 2)

    Warnings:

    • @orderby and @with do not transparently call underlying DataFrames.jl functions; escaping entire transformations here is considered unstable.
    • Row-wise macros (@rtransform, @rsubset) will not automatically wrap src => fun => dest in ByRow.
    using Statistics
    using DataFrames
    
    df = DataFrame(a = [1, 2], b = [30, 40]);
    
    # Apply multiple functions to multiple columns
    @transform df $([:a, :b] .=> [sum mean])
    
    # Use a pre-defined transformation pair
    my_transformation = :a => (t -> t .+ 100) => :c
    @transform df $my_transformation
    
    # Use a transformation pair in @subset
    @subset df $(:a => t -> t .>= 2)
  9. Install DataFramesMeta.jl

    master

    To use DataFramesMeta.jl, install it via the Julia package manager. It will automatically install DataFrames.jl as a dependency. For the tutorial workflow, it is recommended to use a temporary environment and also install CSV and HTTP for data loading.

    # Enter pkg-mode by pressing ]
    pkg> activate --temp
    pkg> add DataFramesMeta
    pkg> add CSV HTTP

    To use the package in your script or REPL, load it using:

    using DataFramesMeta
    using CSV, HTTP, Statistics
  10. Use the $ syntax for programmatic column names

    master

    The $ syntax allows you to refer to columns via a Symbol, String, or column position using a variable or literal. This is essential for working with column names that are not hardcoded.

    Referencing existing columns

    You can use $ to reference a column name stored in a variable:

    nameA = :A
    df2 = @transform(df, :C = :B - $nameA)

    Creating new columns programmatically

    You can use $ on the left-hand side to name new columns using variables or complex expressions:

    newcol = "C"
    @select(df, $newcol = :A + :B)
    
    # Using complex expressions requires parentheses
    @by(df, :B, $("A complicated" * " new name") = first(:A))

    Important Restrictions

    • Mixing Types: You cannot mix integer column references (e.g., $1) with Symbol or String references in @transform, @with, or @eachrow. However, you can mix Symbols and Strings.
    • Complex Expressions: If a column reference involves a complex expression (like string concatenation or a function call), you must wrap it in parentheses: $(get_column_name(x)).
    df = DataFrame(A = 1:3, :B = [2, 1, 2])
    
    nameA = :A
    df2 = @transform(df, :C = :B - $nameA)
    
    nameA_string = "A"
    df3 = @transform(df, :C = :B - $nameA_string)
    
    newcol = "C"
    @select(df, $newcol = :A + :B)
    
    @by(df, :B, $("A complicated" * " new name") = first(:A))
  11. Propagate missing values with `@passmissing`

    master

    Many Julia functions (like parse) error when encountering missing values. To ensure functions return missing instead of erroring when any input is missing, wrap your row-wise transformations in the @passmissing macro. This uses Missings.passmissing under the hood.

    This is typically used in conjunction with @byrow or row-wise macros like @rtransform.

    # Example: Using @passmissing with @rtransform to handle missing strings
    @rtransform df @passmissing :x = parse(Int, :x_str)