Skip to content

Commit

Permalink
Merge branch 'main' into bk/fix_vcat
Browse files Browse the repository at this point in the history
  • Loading branch information
bkamins authored Sep 9, 2024
2 parents d920800 + 9683931 commit c3b9c4e
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 9 deletions.
1 change: 0 additions & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a"
PrettyTables = "08abe8d2-0d0c-5749-adfa-8a2ac140af0d"
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
REPL = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb"
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
SortingAlgorithms = "a2af1166-a08f-5f64-846c-94a0d3cef48c"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Missings = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28"
Query = "1a8c2f83-1ff3-5112-b086-8aa67b057ba1"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
TidierData = "fe2206b3-d496-4ee9-a338-6a095c4ece80"

[compat]
Documenter = "1"
139 changes: 139 additions & 0 deletions docs/src/man/querying_frameworks.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,145 @@ DataFramesMeta.jl, DataFrameMacros.jl and Query.jl. They implement a functionali
These frameworks are designed both to make it easier for new users to start working with data frames in Julia
and to allow advanced users to write more compact code.

## TidierData.jl
[TidierData.jl](https://tidierorg.github.io/TidierData.jl/latest/), part of
the [Tidier](https://tidierorg.github.io/Tidier.jl/dev/) ecosystem, is a macro-based
data analysis interface that wraps DataFrames.jl. The instructions below are for version
0.16.0 of TidierData.jl.

First, install the TidierData.jl package:

```julia
using Pkg
Pkg.add("TidierData")
```

TidierData.jl enables clean, readable, and fast code for all major data transformation
functions including
[aggregating](https://tidierorg.github.io/TidierData.jl/latest/examples/generated/UserGuide/summarize/),
[pivoting](https://tidierorg.github.io/TidierData.jl/latest/examples/generated/UserGuide/pivots/),
[nesting](https://tidierorg.github.io/TidierData.jl/latest/examples/generated/UserGuide/nesting/),
and [joining](https://tidierorg.github.io/TidierData.jl/latest/examples/generated/UserGuide/joins/)
data frames. TidierData re-exports `DataFrame` from DataFrames.jl, `@chain` from Chain.jl, and
Statistics.jl to streamline data operations.

TidierData.jl is heavily inspired by the `dplyr` and `tidyr` R packages (part of the R
`tidyverse`), which it aims to implement using pure Julia by wrapping DataFrames.jl. While
TidierData.jl borrows conventions from the `tidyverse`, it is important to note that the
`tidyverse` itself is often not considered idiomatic R code. TidierData.jl brings
data analysis conventions from `tidyverse` into Julia to have the best of both worlds:
tidy syntax and the speed and flexibility of the Julia language.

TidierData.jl has two major differences from other macro-based packages. First, TidierData.jl
uses tidy expressions. An example of a tidy expression is `a = mean(b)`, where `b` refers
to an existing column in the data frame, and `a` refers to either a new or existing column.
Referring to variables outside of the data frame requires prefixing variables with `!!`.
For example, `a = mean(!!b)` refers to a variable `b` outside the data frame. Second,
TidierData.jl aims to make broadcasting mostly invisible through
[auto-vectorization](https://tidierorg.github.io/TidierData.jl/latest/examples/generated/UserGuide/autovec/). TidierData.jl currently uses a lookup table to decide which functions not to
vectorize; all other functions are automatically vectorized. This allows for
writing of concise expressions: `@mutate(df, a = a - mean(a))` transforms the `a` column
by subtracting each value by the mean of the column. Behind the scenes, the right-hand
expression is converted to `a .- mean(a)` because `mean()` is in the lookup table as a
function that should not be vectorized. Take a look at the
[auto-vectorization](https://tidierorg.github.io/TidierData.jl/latest/examples/generated/UserGuide/autovec/) documentation for details.

One major benefit of combining tidy expressions with auto-vectorization is that
TidierData.jl code (which uses DataFrames.jl as its backend) can work directly on
databases using [TidierDB.jl](https://github.com/TidierOrg/TidierDB.jl),
which converts tidy expressions into SQL, supporting DuckDB and several other backends.

```jldoctest tidierdata
julia> using TidierData
julia> df = DataFrame(
name = ["John", "Sally", "Roger"],
age = [54.0, 34.0, 79.0],
children = [0, 2, 4]
)
3×3 DataFrame
Row │ name age children
│ String Float64 Int64
─────┼───────────────────────────
1 │ John 54.0 0
2 │ Sally 34.0 2
3 │ Roger 79.0 4
julia> @chain df begin
@filter(children != 2)
@select(name, num_children = children)
end
2×2 DataFrame
Row │ name num_children
│ String Int64
─────┼──────────────────────
1 │ John 0
2 │ Roger 4
```

Below are examples showcasing `@group_by` with `@summarize` or `@mutate` - analagous to the split, apply, combine pattern.

```jldoctest tidierdata
julia> df = DataFrame(
groups = repeat('a':'e', inner = 2),
b_col = 1:10,
c_col = 11:20,
d_col = 111:120
)
10×4 DataFrame
Row │ groups b_col c_col d_col
│ Char Int64 Int64 Int64
─────┼─────────────────────────────
1 │ a 1 11 111
2 │ a 2 12 112
3 │ b 3 13 113
4 │ b 4 14 114
5 │ c 5 15 115
6 │ c 6 16 116
7 │ d 7 17 117
8 │ d 8 18 118
9 │ e 9 19 119
10 │ e 10 20 120
julia> @chain df begin
@filter(b_col > 2)
@group_by(groups)
@summarise(median_b = median(b_col),
across((b_col:d_col), mean))
end
4×5 DataFrame
Row │ groups median_b b_col_mean c_col_mean d_col_mean
│ Char Float64 Float64 Float64 Float64
─────┼──────────────────────────────────────────────────────
1 │ b 3.5 3.5 13.5 113.5
2 │ c 5.5 5.5 15.5 115.5
3 │ d 7.5 7.5 17.5 117.5
4 │ e 9.5 9.5 19.5 119.5
julia> @chain df begin
@filter(b_col > 4 && c_col <= 18)
@group_by(groups)
@mutate(
new_col = b_col + maximum(d_col),
new_col2 = c_col - maximum(d_col),
new_col3 = case_when(c_col >= 18 => "high",
c_col > 15 => "medium",
true => "low"))
@select(starts_with("new"))
@ungroup # required because `@mutate` does not ungroup
end
4×4 DataFrame
Row │ groups new_col new_col2 new_col3
│ Char Int64 Int64 String
─────┼─────────────────────────────────────
1 │ c 121 -101 low
2 │ c 122 -100 medium
3 │ d 125 -101 medium
4 │ d 126 -100 high
```

For more examples, please visit the [TidierData.jl](https://tidierorg.github.io/TidierData.jl/latest/) documentation.

## DataFramesMeta.jl

The [DataFramesMeta.jl](https://github.com/JuliaStats/DataFramesMeta.jl) package
Expand Down
2 changes: 1 addition & 1 deletion src/DataFrames.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module DataFrames

using Statistics, Printf, REPL
using Statistics, Printf
using Reexport, SortingAlgorithms, Compat, Unicode, PooledArrays
@reexport using Missings, InvertedIndices
using Base.Sort, Base.Order, Base.Iterators, Base.Threads
Expand Down
10 changes: 10 additions & 0 deletions src/abstractdataframe/abstractdataframe.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1151,6 +1151,11 @@ data frames.
function instead as it is consistent with other DataFrames.jl functions
(as opposed to `filter`).
!!! note
Due to type stability the `filter(cols => fun, df::AbstractDataFrame; view::Bool=false)`
call is preferred in performance critical applications.
$METADATA_FIXED
See also: [`filter!`](@ref)
Expand Down Expand Up @@ -1281,6 +1286,11 @@ data frames.
function instead as it is consistent with other DataFrames.jl functions
(as opposed to `filter!`).
!!! note
Due to type stability the `filter!(cols => fun, df::AbstractDataFrame)`
call is preferred in performance critical applications.
$METADATA_FIXED
See also: [`filter`](@ref)
Expand Down
8 changes: 6 additions & 2 deletions src/groupeddataframe/complextransforms.jl
Original file line number Diff line number Diff line change
Expand Up @@ -269,8 +269,12 @@ function _combine_rows_with_first!((firstrow,)::Ref{Any},
# Create up to one task per thread
# This has lower overhead than creating one task per group,
# but is optimal only if operations take roughly the same time for all groups
basesize = max(1, cld(len - 1, Threads.nthreads()))
partitions = Iterators.partition(2:len, basesize)
if isthreadsafe(outcols, incols)
basesize = max(1, cld(len - 1, Threads.nthreads()))
partitions = Iterators.partition(2:len, basesize)
else
partitions = (2:len,)
end
widen_type_lock = ReentrantLock()
outcolsref = Ref{NTuple{<:Any, AbstractVector}}(outcols)
type_widened = fill(false, length(partitions))
Expand Down
22 changes: 21 additions & 1 deletion src/other/index.jl
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,26 @@ end
@inline Base.getindex(x::AbstractIndex, rx::Regex) =
getindex(x, filter(name -> occursin(rx, String(name)), _names(x)))

# Levenshtein Distance
# taken from https://github.com/JuliaLang/julia/blob/b5af119a6c608de43d6591a6c4129e9369239898/stdlib/REPL/src/docview.jl#L760-L776
function _levenshtein(s1, s2)
a, b = collect(s1), collect(s2)
m = length(a)
n = length(b)
d = Matrix{Int}(undef, m+1, n+1)

d[1:m+1, 1] = 0:m
d[1, 1:n+1] = 0:n

for i = 1:m, j = 1:n
d[i+1,j+1] = min(d[i , j+1] + 1,
d[i+1, j ] + 1,
d[i , j ] + (a[i] != b[j]))
end

return d[m+1, n+1]
end

# Fuzzy matching rules:
# 1. ignore case
# 2. maximum Levenshtein distance is 2
Expand All @@ -302,7 +322,7 @@ end
# Returns candidates ordered by (distance, name) pair
function fuzzymatch(l::Dict{Symbol, Int}, idx::Symbol)
idxs = uppercase(string(idx))
dist = [(REPL.levenshtein(uppercase(string(x)), idxs), x) for x in keys(l)]
dist = [(_levenshtein(uppercase(string(x)), idxs), x) for x in keys(l)]
sort!(dist)
c = [count(x -> x[1] <= i, dist) for i in 0:2]
maxd = max(0, searchsortedlast(c, 8) - 1)
Expand Down
6 changes: 3 additions & 3 deletions test/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@ end
df = DataFrame(
A=Int64[1,4,9,16,25,36,49,64],
B = [
md"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)",
md"ABC",
md"``\frac{x^2}{x^2+y^2}``",
md"`Header`",
md"This is *very*, **very**, very, very, very, very, very, very, very long line" ,
Expand All @@ -781,7 +781,7 @@ end
Row │ A B
│ Int64 MD
─────┼──────────────────────────────────────────
1 │ 1 DataFrames.jl (http://juliadat…
1 │ 1 ABC
2 │ 4 \\frac{x^2}{x^2+y^2}
3 │ 9 Header
4 │ 16 This is very, very, very, very…
Expand All @@ -793,7 +793,7 @@ end
@test sprint(show, "text/csv", df) ==
"""
\"A\",\"B\"
1,\"[DataFrames.jl](http://juliadata.github.io/DataFrames.jl)\"
1,\"ABC\"
4,\"\$\\\\frac{x^2}{x^2+y^2}\$\"
9,\"`Header`\"
16,\"This is *very*, **very**, very, very, very, very, very, very, very long line\"
Expand Down
2 changes: 1 addition & 1 deletion test/select.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3039,7 +3039,7 @@ end
@test size(combine(df, :a => (x -> Any[]) => AsTable)) == (0, 0)
df2 = combine(df, :a => (x -> NamedTuple{(:x,),Tuple{Int64}}[]) => AsTable)
@test size(df2) == (0, 1)
@test eltype(df2.x) === Int
@test eltype(df2.x) === Int64
end

end # module

0 comments on commit c3b9c4e

Please sign in to comment.