Skip to content

Commit f4569d4

Browse files
authored
Merge pull request #81 from drizk1/adding-@relocate-
Adding `@relocate`
2 parents 62e1689 + 5df95f6 commit f4569d4

File tree

7 files changed

+168
-2
lines changed

7 files changed

+168
-2
lines changed

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# TidierData.jl updates
22

3+
## v0.14.5 - 2024-01-23
4+
- Adds `@relocate()`
5+
36
## v0.14.4 - 2023-12-30
47
- Adds `@unnest_wider()`
58
- Adds `@unnest_longer()`

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "TidierData"
22
uuid = "fe2206b3-d496-4ee9-a338-6a095c4ece80"
33
authors = ["Karandeep Singh"]
4-
version = "0.14.4"
4+
version = "0.14.5"
55

66
[deps]
77
Chain = "8be319e6-bccf-4806-a6f7-6fae938471bc"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ TidierData.jl currently supports the following top-level macros:
8686
- `@slice()`, `@slice_sample()`, `@slice_min()`, `@slice_max()`, `@slice_head()`, and `@slice_tail()`
8787
- `@group_by()` and `@ungroup()`
8888
- `@arrange()`
89+
- `@relocate()`
8990
- `@pull()`
9091
- `@count()` and `@tally()`
9192
- `@left_join()`, `@right_join()`, `@inner_join()`, `@full_join()`, `@anti_join()`, and `@semi_join()`

docs/src/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ TidierData.jl currently supports the following top-level macros:
9898
- `@slice()`, `@slice_sample()`, `@slice_min()`, `@slice_max()`, `@slice_head()`, and `@slice_tail()`
9999
- `@group_by()` and `@ungroup()`
100100
- `@arrange()`
101+
- `@relocate()`
101102
- `@pull()`
102103
- `@count()` and `@tally()`
103104
- `@left_join()`, `@right_join()`, `@inner_join()`, `@full_join()`, `@anti_join()`, and `@semi_join()`

src/TidierData.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export TidierData_set, across, desc, n, row_number, everything, starts_with, end
2121
@group_by, @ungroup, @slice, @arrange, @distinct, @pull, @left_join, @right_join, @inner_join, @full_join, @anti_join, @semi_join,
2222
@pivot_wider, @pivot_longer, @bind_rows, @bind_cols, @clean_names, @count, @tally, @drop_missing, @glimpse, @separate,
2323
@unite, @summary, @fill_missing, @slice_sample, @slice_min, @slice_max, @slice_head, @slice_tail, @rename_with, @separate_rows,
24-
@unnest_longer, @unnest_wider, @nest
24+
@unnest_longer, @unnest_wider, @nest, @relocate
2525

2626
# Package global variables
2727
const code = Ref{Bool}(false) # output DataFrames.jl code?
@@ -53,6 +53,7 @@ include("summary.jl")
5353
include("is_type.jl")
5454
include("missings.jl")
5555
include("nests.jl")
56+
include("relocate.jl")
5657

5758
# Function to set global variables
5859
"""

src/docstrings.jl

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3316,3 +3316,67 @@ julia> @chain df begin
33163316
15 │ e 15 45 30
33173317
```
33183318
"""
3319+
3320+
const docstring_relocate =
3321+
"""
3322+
@relocate(df, columns, before = nothing, after = nothing)
3323+
3324+
Rearranges the columns of a data frame. This function allows for moving specified columns to a new position within the data frame, either before or after a given target column. The `columns`, `before`, and `after` arguments all accept tidy selection functions. Only one of `before` or `after` should be specified. If neither are specified, the selected columns will be moved to the beginning of the data frame.
3325+
3326+
# Arguments
3327+
- `df`: The data frame.
3328+
- `columns`: Column or columns to to be moved.
3329+
- `before`: (Optional) Column or columns before which the specified columns will be moved. If not provided or `nothing`, this argument is ignored.
3330+
- `after`: (Optional) Column or columns after which the specified columns will be moved. If not provided or `nothing`, this argument is ignored.
3331+
3332+
# Examples
3333+
```jldoctest
3334+
julia> df = DataFrame(A = 1:5, B = 6:10, C = ["A", "b", "C", "D", "E"], D = ['A', 'B','A', 'B','C'],
3335+
E = 1:5, F = ["A", "b", "C", "D", "E"]);
3336+
3337+
julia> @relocate(df, where(is_string), before = where(is_integer))
3338+
5×6 DataFrame
3339+
Row │ C F A B E D
3340+
│ String String Int64 Int64 Int64 Char
3341+
─────┼───────────────────────────────────────────
3342+
1 │ A A 1 6 1 A
3343+
2 │ b b 2 7 2 B
3344+
3 │ C C 3 8 3 A
3345+
4 │ D D 4 9 4 B
3346+
5 │ E E 5 10 5 C
3347+
3348+
3349+
julia> @relocate(df, B, C, D, after = E)
3350+
5×6 DataFrame
3351+
Row │ A E B C D F
3352+
│ Int64 Int64 Int64 String Char String
3353+
─────┼───────────────────────────────────────────
3354+
1 │ 1 1 6 A A A
3355+
2 │ 2 2 7 b B b
3356+
3 │ 3 3 8 C A C
3357+
4 │ 4 4 9 D B D
3358+
5 │ 5 5 10 E C E
3359+
3360+
julia> @relocate(df, B, C, D, after = starts_with("E"))
3361+
5×6 DataFrame
3362+
Row │ A E B C D F
3363+
│ Int64 Int64 Int64 String Char String
3364+
─────┼───────────────────────────────────────────
3365+
1 │ 1 1 6 A A A
3366+
2 │ 2 2 7 b B b
3367+
3 │ 3 3 8 C A C
3368+
4 │ 4 4 9 D B D
3369+
5 │ 5 5 10 E C E
3370+
3371+
julia> @relocate(df, B:C) # bring columns to the front
3372+
5×6 DataFrame
3373+
Row │ B C A D E F
3374+
│ Int64 String Int64 Char Int64 String
3375+
─────┼───────────────────────────────────────────
3376+
1 │ 6 A 1 A 1 A
3377+
2 │ 7 b 2 B 2 b
3378+
3 │ 8 C 3 A 3 C
3379+
4 │ 9 D 4 B 4 D
3380+
5 │ 10 E 5 C 5 E
3381+
```
3382+
"""

src/relocate.jl

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
function relocate(df, columns; before=nothing, after=nothing)
2+
cols_expr = columns isa Expr ? (columns,) : columns
3+
column_symbols = names(df, Cols(cols_expr...))
4+
column_symbols = Symbol.(column_symbols)
5+
# Handle before and after as collections
6+
before_cols = before isa Symbol ? [before] : before
7+
after_cols = after isa Symbol ? [after] : after
8+
before_col_symbols = isnothing(before_cols) ? [] : Symbol.(names(df, Cols(before_cols...)))
9+
after_col_symbols = isnothing(after_cols) ? [] : Symbol.(names(df, Cols(after_cols...)))
10+
# Convert all DataFrame column names to symbols
11+
df_column_names = Symbol.(names(df))
12+
# Reorder the columns
13+
new_order = Symbol[]
14+
inserted = false
15+
for col in df_column_names
16+
if !isempty(before_col_symbols) && col == first(before_col_symbols) && !inserted
17+
append!(new_order, column_symbols) # Place all specified columns
18+
new_order = vcat(new_order, setdiff(before_col_symbols, column_symbols)) # Then all before columns, excluding duplicates
19+
inserted = true
20+
elseif !isempty(after_col_symbols) && col == first(after_col_symbols) && !inserted
21+
new_order = vcat(new_order, setdiff(after_col_symbols, column_symbols)) # Place all after columns, excluding duplicates
22+
append!(new_order, column_symbols) # Then all specified columns
23+
inserted = true
24+
end
25+
if col column_symbols && col before_col_symbols && col after_col_symbols
26+
push!(new_order, col)
27+
end
28+
end
29+
# Move columns to the leftmost position if neither before nor after is specified
30+
if isempty(before_col_symbols) && isempty(after_col_symbols)
31+
new_order = vcat(column_symbols, filter(x -> !(x in column_symbols), df_column_names))
32+
end
33+
select!(df, new_order)
34+
end
35+
36+
"""
37+
$docstring_relocate
38+
"""
39+
macro relocate(df, args...)
40+
before_col_expr = :nothing
41+
after_col_expr = :nothing
42+
# Extract the columns_to_move expression and keyword arguments
43+
col_exprs = args[1:end-1]
44+
last_arg = args[end]
45+
# Check if the last argument is a keyword argument
46+
if last_arg isa Expr && last_arg.head == :(=)
47+
if last_arg.args[1] == :after || last_arg.args[1] == :after
48+
after_col_expr = last_arg.args[2]
49+
elseif last_arg.args[1] == :before || last_arg.args[1] == :before
50+
before_col_expr = last_arg.args[2]
51+
else
52+
error("Invalid keyword argument: only 'before' or 'after' are accepted.")
53+
end
54+
col_exprs = args[1:end-1]
55+
else
56+
col_exprs = args
57+
end
58+
59+
# Additional check for invalid keyword arguments in the rest of args
60+
for arg in col_exprs
61+
if arg isa Expr && arg.head == :(=) && !(arg.args[1] in [:before, :before, :after, :after])
62+
error("Invalid keyword argument: only 'before' or 'after' are accepted.")
63+
end
64+
end
65+
# Parse the column expressions
66+
interpolated_col_exprs = parse_interpolation.(col_exprs)
67+
tidy_col_exprs = [parse_tidy(i[1]) for i in interpolated_col_exprs]
68+
# Parse before and after
69+
if before_col_expr != :nothing
70+
interpolated_before_col = parse_interpolation(before_col_expr)
71+
tidy_before_col_exprs = [parse_tidy(interpolated_before_col[1])]
72+
else
73+
tidy_before_col_exprs = []
74+
end
75+
if after_col_expr != :nothing
76+
interpolated_after_col = parse_interpolation(after_col_expr)
77+
tidy_after_col_exprs = [parse_tidy(interpolated_after_col[1])]
78+
else
79+
tidy_after_col_exprs = []
80+
end
81+
relocation_expr =
82+
quote
83+
if $(esc(df)) isa GroupedDataFrame
84+
local df_copy = transform($(esc(df)), ungroup = false)
85+
relocate(df_copy, [$(tidy_col_exprs...)], before=[$(tidy_before_col_exprs...)], after=[$(tidy_after_col_exprs...)])
86+
local grouped_df = groupby(parent(df_copy), groupcols($(esc(df))))
87+
grouped_df
88+
else
89+
local df_copy = copy($(esc(df)))
90+
relocate(df_copy, [$(tidy_col_exprs...)], before=[$(tidy_before_col_exprs...)], after=[$(tidy_after_col_exprs...)])
91+
df_copy
92+
end
93+
end
94+
95+
return relocation_expr
96+
end

0 commit comments

Comments
 (0)