While Bogumił Kamiński is the authority here and his answer shows a very nice, short alternative solution I believe there is room for more discussion and examples.
Firstly, your original solution is certainly idiomatic and I personally would prefer it in many cases. Mutation is an important tool for high-performance languages but it is also a source of many problems. The Julia convention (common for certain other languages as well) of adding an exclamation point (or "bang!") to functions that mutate their argument help greatly in keeping track of the places where mutation happens.
In-place assignments (.=) is also commonplace in Julia code but in my own code and in the code of many library authors these would typically be found in functions named in accordance with the convention so that the intent is clear at the call site. If you find that you often needed the kind of operation you're describing you could write a function:
function filter_transform!(df, pred, args...)
fdf = filter(pred, df; view=true)
fdf .= transform(copy(fdf), (col => f => col for (col,f) in args)...)
end
and to use it:
filter_transform!(df, :A => <(3), :B => b -> b*2)
which I think reads nicely. The function works for your original example but does not allow for assignment to another column or using several columns in the calculation or adding columns (like data.table allows, with NA filling the skipped rows of new columns). This slightly more complicated function achieves that:
applyif(cond, f, x, default) = cond ? f(x) : default
function filter_transform!(df, filt_cols, args...)
colset = Set(keys(eachcol(df)))
fcols, pred = filt_cols
for (cols, (f, assign)) in args
df[!, assign] .= in(assign, colset) ? df[!, assign] : missing
@. df[!, assign] = applyif(pred(df[!, fcols]), f, df[!,cols], df[!, assign])
end
end
using it:
filter_transform!(df, :A => <(3), :B => (b -> b*2) => :B)
Finally, you asked about packages there are indeed packages that you could check out:
Query.jl (this example reassigns instead of modifying in-place):
using Query
df = df |> @mutate(B = _.A < 3 ? _.B*2 : _.B)
DataFramesMeta (also reassigning):
using DataFramesMeta
df = @eachrow df :B = :A < 3 ? :B*2 : :B