@@ -282,11 +282,10 @@ macro byrow(args...)
282
282
throw (ArgumentError (" @byrow is deprecated outside of DataFramesMeta macros." ))
283
283
end
284
284
285
-
286
285
"""
287
- passmissing(args...)
286
+ @ passmissing(args...)
288
287
289
- Propograte missing values inside DataFramesMeta.jl macros.
288
+ Propagate missing values inside DataFramesMeta.jl macros.
290
289
291
290
292
291
`@passmissing` is not a "real" Julia macro but rather serves as a "flag"
@@ -350,6 +349,156 @@ macro passmissing(args...)
350
349
throw (ArgumentError (" @passmissing only works inside DataFramesMeta macros." ))
351
350
end
352
351
352
+ const astable_docstring_snippet = """
353
+ Transformations can also use the macro-flag [`@astable`](@ref) for creating multiple
354
+ new columns at once and letting transformations share the same name-space.
355
+ See `? @astable` for more details.
356
+ """
357
+
358
+ """
359
+ @astable(args...)
360
+
361
+ Return a `NamedTuple` from a single transformation inside the DataFramesMeta.jl
362
+ macros, `@select`, `@transform`, and their mutating and row-wise equivalents.
363
+
364
+ `@astable` acts on a single block. It works through all top-level expressions
365
+ and collects all such expressions of the form `:y = ...` or `$(DOLLAR) y = ...`, i.e. assignments to a
366
+ `Symbol` or an escaped column identifier, which is a syntax error outside of
367
+ DataFramesMeta.jl macros. At the end of the expression, all assignments are collected
368
+ into a `NamedTuple` to be used with the `AsTable` destination in the DataFrames.jl
369
+ transformation mini-language.
370
+
371
+ Concretely, the expressions
372
+
373
+ ```
374
+ df = DataFrame(a = 1)
375
+
376
+ @rtransform df @astable begin
377
+ :x = 1
378
+ y = 50
379
+ :z = :x + y + :a
380
+ end
381
+ ```
382
+
383
+ become the pair
384
+
385
+ ```
386
+ function f(a)
387
+ x_t = 1
388
+ y = 50
389
+ z_t = x_t + y + a
390
+
391
+ (; x = x_t, z = z_t)
392
+ end
393
+
394
+ transform(df, [:a] => ByRow(f) => AsTable)
395
+ ```
396
+
397
+ `@astable` has two major advantages at the cost of increasing complexity.
398
+ First, `@astable` makes it easy to create multiple columns from a single
399
+ transformation, which share a scope. For example, `@astable` allows
400
+ for the following (where `:x` and `:x_2` exist in the data frame already).
401
+
402
+ ```
403
+ @transform df @astable begin
404
+ m = mean(:x)
405
+ :x_demeaned = :x .- m
406
+ :x2_demeaned = :x2 .- m
407
+ end
408
+ ```
409
+
410
+ The creation of `:x_demeaned` and `:x2_demeaned` both share the variable `m`,
411
+ which does not need to be calculated twice.
412
+
413
+ Second, `@astable` is useful when performing intermediate calculations
414
+ and storing their results in new columns. For example, the following fails.
415
+
416
+ ```
417
+ @rtransform df begin
418
+ :new_col_1 = :x + :y
419
+ :new_col_2 = :new_col_1 + :z
420
+ end
421
+ ```
422
+
423
+ This because DataFrames.jl does not guarantee sequential evaluation of
424
+ transformations. `@astable` solves this problem
425
+
426
+ @rtransform df @astable begin
427
+ :new_col_1 = :x + :y
428
+ :new_col_2 = :new_col_1 + :z
429
+ end
430
+
431
+ Column assignment in `@astable` follows similar rules as
432
+ column assignment in other DataFramesMeta.jl macros. The left-
433
+ -hand-side of a column assignment can be either a `Symbol` or any
434
+ expression which evaluates to a `Symbol` or `AbstractString`. For example
435
+ `:y = ...`, and `$(DOLLAR) y = ...` are both valid ways of assigning a new column.
436
+ However unlike other DataFramesMeta.jl macros, multi-column assignments via
437
+ `AsTable` are disallowed. The following will fail.
438
+
439
+ ```
440
+ @transform df @astable begin
441
+ $AsTable = :x
442
+ end
443
+ ```
444
+
445
+ References to existing columns also follow the same
446
+ rules as other DataFramesMeta.jl macros.
447
+
448
+ ### Examples
449
+
450
+ ```
451
+ julia> df = DataFrame(a = [1, 2, 3], b = [4, 5, 6]);
452
+
453
+ julia> d = @rtransform df @astable begin
454
+ :x = 1
455
+ y = 5
456
+ :z = :x + y
457
+ end
458
+ 3×4 DataFrame
459
+ Row │ a b x z
460
+ │ Int64 Int64 Int64 Int64
461
+ ─────┼────────────────────────────
462
+ 1 │ 1 4 1 6
463
+ 2 │ 2 5 1 6
464
+ 3 │ 3 6 1 6
465
+
466
+ julia> df = DataFrame(a = [1, 1, 2, 2], b = [5, 6, 70, 80]);
467
+
468
+ julia> @by df :a @astable begin
469
+ ex = extrema(:b)
470
+ :min_b = first(ex)
471
+ :max_b = last(ex)
472
+ end
473
+ 2×3 DataFrame
474
+ Row │ a min_b max_b
475
+ │ Int64 Int64 Int64
476
+ ─────┼─────────────────────
477
+ 1 │ 1 5 6
478
+ 2 │ 2 70 80
479
+
480
+ julia> new_col = "New Column";
481
+
482
+ julia> @rtransform df @astable begin
483
+ f_a = first(:a)
484
+ $(DOLLAR) new_col = :a + :b + f_a
485
+ :y = :a * :b
486
+ end
487
+ 4×4 DataFrame
488
+ Row │ a b New Column y
489
+ │ Int64 Int64 Int64 Int64
490
+ ─────┼─────────────────────────────────
491
+ 1 │ 1 5 7 5
492
+ 2 │ 1 6 8 6
493
+ 3 │ 2 70 74 140
494
+ 4 │ 2 80 84 160
495
+ ```
496
+
497
+ """
498
+ macro astable (args... )
499
+ throw (ArgumentError (" @astable only works inside DataFramesMeta macros." ))
500
+ end
501
+
353
502
# #############################################################################
354
503
# #
355
504
# # @with
@@ -1097,6 +1246,8 @@ transformations by row, `@transform` allows `@byrow` at the
1097
1246
beginning of a block of transformations (i.e. `@byrow begin... end`).
1098
1247
All transformations in the block will operate by row.
1099
1248
1249
+ $astable_docstring_snippet
1250
+
1100
1251
### Examples
1101
1252
1102
1253
```jldoctest
@@ -1233,6 +1384,8 @@ transform!ations by row, `@transform!` allows `@byrow` at the
1233
1384
beginning of a block of transform!ations (i.e. `@byrow begin... end`).
1234
1385
All transform!ations in the block will operate by row.
1235
1386
1387
+ $astable_docstring_snippet
1388
+
1236
1389
### Examples
1237
1390
1238
1391
```jldoctest
@@ -1345,6 +1498,8 @@ transformations by row, `@select` allows `@byrow` at the
1345
1498
beginning of a block of selectations (i.e. `@byrow begin... end`).
1346
1499
All transformations in the block will operate by row.
1347
1500
1501
+ $astable_docstring_snippet
1502
+
1348
1503
### Examples
1349
1504
1350
1505
```jldoctest
@@ -1465,6 +1620,8 @@ transformations by row, `@select!` allows `@byrow` at the
1465
1620
beginning of a block of select!ations (i.e. `@byrow begin... end`).
1466
1621
All transformations in the block will operate by row.
1467
1622
1623
+ $astable_docstring_snippet
1624
+
1468
1625
### Examples
1469
1626
1470
1627
```jldoctest
@@ -1546,17 +1703,6 @@ function combine_helper(x, args...; deprecation_warning = false)
1546
1703
1547
1704
exprs, outer_flags = create_args_vector (args... )
1548
1705
1549
- fe = first (exprs)
1550
- if length (exprs) == 1 &&
1551
- get_column_expr (fe) === nothing &&
1552
- ! (fe. head == :(= ) || fe. head == :kw )
1553
-
1554
- @warn " Returning a Table object from @by and @combine now requires `$(DOLLAR) AsTable` on the LHS."
1555
-
1556
- lhs = Expr (:$ , :AsTable )
1557
- exprs = ((:($ lhs = $ fe)),)
1558
- end
1559
-
1560
1706
t = (fun_to_vec (ex; gensym_names = false , outer_flags = outer_flags) for ex in exprs)
1561
1707
1562
1708
quote
@@ -1592,6 +1738,8 @@ and
1592
1738
@combine(df, :mx = mean(:x), :sx = std(:x))
1593
1739
```
1594
1740
1741
+ $astable_docstring_snippet
1742
+
1595
1743
### Examples
1596
1744
1597
1745
```julia
@@ -1666,16 +1814,6 @@ end
1666
1814
function by_helper (x, what, args... )
1667
1815
# Only allow one argument when returning a Table object
1668
1816
exprs, outer_flags = create_args_vector (args... )
1669
- fe = first (exprs)
1670
- if length (exprs) == 1 &&
1671
- get_column_expr (fe) === nothing &&
1672
- ! (fe. head == :(= ) || fe. head == :kw )
1673
-
1674
- @warn " Returning a Table object from @by and @combine now requires `\$ AsTable` on the LHS."
1675
-
1676
- lhs = Expr (:$ , :AsTable )
1677
- exprs = ((:($ lhs = $ fe)),)
1678
- end
1679
1817
1680
1818
t = (fun_to_vec (ex; gensym_names = false , outer_flags = outer_flags) for ex in exprs)
1681
1819
@@ -1718,6 +1856,8 @@ and
1718
1856
@by(df, :g, mx = mean(:x), sx = std(:x))
1719
1857
```
1720
1858
1859
+ $astable_docstring_snippet
1860
+
1721
1861
### Examples
1722
1862
1723
1863
```julia
0 commit comments