Skip to content

Commit 5352a63

Browse files
committed
Use @alias for explicit aliasing
1 parent 45c040e commit 5352a63

File tree

14 files changed

+208
-79
lines changed

14 files changed

+208
-79
lines changed

src/DataFrames.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export AbstractDataFrame,
5252
GroupedDataFrame,
5353
SubDataFrame,
5454
Tables,
55+
@alias,
5556
allcombinations,
5657
allowmissing!,
5758
antijoin,
@@ -132,6 +133,7 @@ const METADATA_FIXED =
132133

133134
include("other/utils.jl")
134135
include("other/index.jl")
136+
include("other/alias.jl")
135137

136138
include("abstractdataframe/abstractdataframe.jl")
137139
include("dataframe/dataframe.jl")

src/abstractdataframe/abstractdataframe.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3223,7 +3223,7 @@ function insertcols!(df::AbstractDataFrame, col::ColumnIndex, name_cols::Pair{Sy
32233223
firstindex(item_new) != 1 && _onebased_check_error()
32243224

32253225
if ncol(dfp) == 0
3226-
dfp[!, name] = item_new
3226+
@alias dfp[!, name] = item_new
32273227
else
32283228
if hasproperty(dfp, name)
32293229
@assert makeunique

src/abstractdataframe/selection.jl

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -682,13 +682,13 @@ function _add_col_check_copy(newdf::DataFrame, df::AbstractDataFrame,
682682
@assert parent_cols isa Union{Int, AbstractVector{Int}}
683683
if copycols
684684
if !(fun isa ByRow) && (v isa SubArray || any(i -> vpar === parent(cdf[i]), parent_cols))
685-
newdf[!, newname] = copy(v)
686-
else
687685
newdf[!, newname] = v
686+
else
687+
@alias newdf[!, newname] = v
688688
end
689689
else
690690
if fun isa ByRow
691-
newdf[!, newname] = v
691+
@alias newdf[!, newname] = v
692692
else
693693
must_copy = false
694694
for i in parent_cols
@@ -702,7 +702,11 @@ function _add_col_check_copy(newdf::DataFrame, df::AbstractDataFrame,
702702
end
703703
end
704704
end
705-
newdf[!, newname] = must_copy ? copy(v) : v
705+
if must_copy
706+
newdf[!, newname] = v
707+
else
708+
@alias newdf[!, newname] = v
709+
end
706710
end
707711
end
708712
end
@@ -807,7 +811,7 @@ function select_transform!((nc,)::Ref{Any}, df::AbstractDataFrame, newdf::DataFr
807811
end
808812
newres = DataFrame()
809813
for n in kp1
810-
newres[!, prepend ? Symbol("x", n) : Symbol(n)] = [x[n] for x in res]
814+
@alias newres[!, prepend ? Symbol("x", n) : Symbol(n)] = [x[n] for x in res]
811815
end
812816
res = newres
813817
elseif !(res isa Union{AbstractDataFrame, NamedTuple, DataFrameRow, AbstractMatrix})
@@ -872,7 +876,7 @@ function select_transform!((nc,)::Ref{Any}, df::AbstractDataFrame, newdf::DataFr
872876
# allow squashing a scalar to 0 rows
873877
rows = nrow(newdf)
874878
end
875-
newdf[!, newname] = fill!(Tables.allocatecolumn(typeof(res_unwrap), rows),
879+
@alias newdf[!, newname] = fill!(Tables.allocatecolumn(typeof(res_unwrap), rows),
876880
res_unwrap)
877881
if (col_idx isa Int || (col_idx isa AbstractVector{Int} && length(col_idx) == 1)) &&
878882
(fun === identity || fun === copy || _names(df)[only(col_idx)] == newname)
@@ -1756,7 +1760,7 @@ function _manipulate(df::AbstractDataFrame, normalized_cs::Vector{Any}, copycols
17561760
end
17571761
end
17581762
# here even if keeprows is true all is OK
1759-
newdf[!, newname] = column_to_copy[i] ? df[:, i] : df[!, i]
1763+
@alias newdf[!, newname] = column_to_copy[i] ? df[:, i] : df[!, i]
17601764
column_to_copy[i] = true
17611765
allow_resizing_newdf[] = false
17621766
_copy_col_note_metadata!(newdf, newname, df, i)

src/dataframe/dataframe.jl

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ Base.getindex(df::DataFrame, row_ind::typeof(!), col_inds::MultiColumnIndex) =
632632
##############################################################################
633633

634634
# Will automatically add a new column if needed
635-
function insert_single_column!(df::DataFrame, v::Any, col_ind::ColumnIndex; copycols = false)
635+
function insert_single_column!(df::DataFrame, v::Any, col_ind::ColumnIndex; copycols = true)
636636
dv = _preprocess_column(v, nrow(df), copycols)
637637
if ncol(df) != 0 && nrow(df) != length(dv)
638638
throw(ArgumentError("New columns must have the same length as old columns"))
@@ -664,6 +664,12 @@ function insert_single_entry!(df::DataFrame, v::Any, row_ind::Integer, col_ind::
664664
end
665665
end
666666

667+
# df[!, SingleColumnIndex] = AbstractVector
668+
function Base.setindex!(df::DataFrame, v::AbstractVector, ::typeof(!), col_ind::ColumnIndex; copycols = true)
669+
insert_single_column!(df, v, col_ind; copycols)
670+
return df
671+
end
672+
667673
# df[!, SingleColumnIndex] = value
668674
function Base.setindex!(df::DataFrame, v::Any, ::typeof(!), col_ind::ColumnIndex)
669675
insert_single_column!(df, v, col_ind)
@@ -672,10 +678,10 @@ end
672678

673679
# df.col = value
674680
# separate methods are needed due to dispatch ambiguity
675-
Base.setproperty!(df::DataFrame, col_ind::Symbol, v::AbstractVector) =
676-
(df[!, col_ind] = v)
677-
Base.setproperty!(df::DataFrame, col_ind::AbstractString, v::AbstractVector) =
678-
(df[!, col_ind] = v)
681+
Base.setproperty!(df::DataFrame, col_ind::Symbol, v::AbstractVector; copycols = true) =
682+
setindex!(df, v, !, col_ind; copycols)
683+
Base.setproperty!(df::DataFrame, col_ind::AbstractString, v::AbstractVector; copycols = true) =
684+
setindex!(df, v, !, col_ind; copycols)
679685
Base.setproperty!(df::DataFrame, col_ind::Symbol, v::Any) =
680686
(df[!, col_ind] = v)
681687
Base.setproperty!(df::DataFrame, col_ind::AbstractString, v::Any) =
@@ -716,7 +722,7 @@ for T in (:AbstractVector, :Not, :Colon)
716722
row_inds::$T,
717723
col_ind::ColumnIndex)
718724
if row_inds isa Colon && !haskey(index(df), col_ind)
719-
df[!, col_ind] = copy(v)
725+
df[!, col_ind] = v
720726
return df
721727
end
722728
x = df[!, col_ind]
@@ -1211,7 +1217,11 @@ function hcat!(df1::DataFrame, df2::AbstractDataFrame;
12111217
_drop_all_nonnote_metadata!(df1)
12121218
_keep_matching_table_note_metadata!(df1, df2)
12131219
for i in 1:length(u)
1214-
df1[!, u[i]] = copycols ? df2[:, i] : df2[!, i]
1220+
if copycols
1221+
df1[!, u[i]] = df2[!, i]
1222+
else
1223+
@alias df1[!, u[i]] = df2[!, i]
1224+
end
12151225
_copy_col_note_metadata!(df1, u[i], df2, i)
12161226
end
12171227

src/other/alias.jl

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""
2+
@alias df.col = v
3+
@alias df[!, :col] = v
4+
5+
df::DataFrame
6+
v::AbstractVector
7+
8+
Assigns v to the column `col` without copying. Such that `df.col === v === df]!, :col]```.
9+
Any AbstractVector is permissable, this may limit what operations are possible to do on `df` afterwards.
10+
For instance after `@alias df.col = 1:3` it won't be possible to change the number of rows because UnitRange does not support resizing.
11+
"""
12+
macro alias(ex)
13+
if !Meta.isexpr(ex, :(=))
14+
throw(ArgumentError("Invalid use of @alias macro: argument must be a column assignment `df.col = v` or `df[!, col] = v`"))
15+
end
16+
17+
lhs, rhs = ex.args
18+
19+
if Meta.isexpr(lhs, :ref)
20+
ex = :(setindex!($(lhs.args[1]), $rhs, $(lhs.args[2:end]...); copycols = false); $rhs)
21+
22+
elseif Meta.isexpr(lhs, :.)
23+
ex = :(setproperty!($(lhs.args...), $rhs; copycols = false); $rhs)
24+
25+
else
26+
throw(ArgumentError("Invalid use of @alias macro: argument must be a column assignment `df.col = v` or `df[!, col] = v`"))
27+
28+
end
29+
30+
esc(ex)
31+
end

test/alias.jl

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
module TestAliasAssignment
2+
3+
using Test, DataFrames
4+
const = isequal
5+
6+
@testset "Aliasing asignment" begin
7+
@testset "$init" for init in [[], Matrix([4 5 6]'), [4 5;6 7;8 9]]
8+
dfr = DataFrame(init, :auto)
9+
@testset "$v" for v in [
10+
[1,2,3],
11+
]
12+
@testset "df.x2 = v" begin
13+
df = copy(dfr)
14+
a = @alias df.x2 = v
15+
@test a === v
16+
@test df.x2 === df[!, :x2] === v
17+
df.x2 = v
18+
@test df.x2 !== v
19+
@test df.x2 v
20+
a = @alias df.x2 = v
21+
@test a === v
22+
@test df.x2 === df[!, :x2] === v
23+
end
24+
25+
@testset "df[!, :x2] = v" begin
26+
df = copy(dfr)
27+
a = @alias df[!, :x2] = v
28+
@test a === v
29+
@test df.x2 === df[!, :x2] === v
30+
df[!, :x2] = v
31+
@test df[!, :x2] !== v
32+
@test df[!, :x2] v
33+
a = @alias df[!, :x2] = v
34+
@test a === v
35+
@test df.x2 === df[!, :x2] === v
36+
end
37+
end
38+
39+
@testset "Invalid use of alias macro" begin
40+
ex = try
41+
eval(:(@alias 1))
42+
catch ex
43+
ex
44+
end
45+
@test ex.error isa ArgumentError
46+
47+
df = copy(dfr)
48+
ex = try
49+
eval(:(@alias df.x .= 1))
50+
catch ex
51+
ex
52+
end
53+
@test ex.error isa ArgumentError
54+
55+
struct S; x; end
56+
s = S(1)
57+
@test_throws MethodError @alias s.x = 1
58+
@test_throws MethodError @alias s.x2 = 1
59+
60+
@test_throws MethodError @alias df.x2 = 1
61+
@test_throws MethodError @alias df[!, :x2] = 1
62+
@test_throws MethodError @alias df[:, :x2] = 1
63+
end
64+
end
65+
end
66+
67+
end # module

test/broadcasting.jl

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1159,7 +1159,7 @@ end
11591159
df1 = DataFrame(rand(100, 100), :auto)
11601160
df2 = copy(df1)
11611161
for i in 1:100
1162-
df2[!, rand(1:100)] = df1[!, i]
1162+
@alias df2[!, rand(1:100)] = df1[!, i]
11631163
end
11641164
df3 = copy(df2)
11651165
df1 .= df2
@@ -1171,7 +1171,7 @@ end
11711171
df1 = DataFrame(rand(100, 100), :auto)
11721172
df2 = copy(df1)
11731173
for i in 1:100
1174-
df2[!, rand(1:100)] = df1[!, i]
1174+
@alias df2[!, rand(1:100)] = df1[!, i]
11751175
end
11761176
df3 = copy(df2)
11771177
df1 .= view(df2, :, :)
@@ -1183,7 +1183,7 @@ end
11831183
df1 = DataFrame(rand(100, 100), :auto)
11841184
df2 = copy(df1)
11851185
for i in 1:100
1186-
df2[!, rand(1:100)] = df1[!, i]
1186+
@alias df2[!, rand(1:100)] = df1[!, i]
11871187
end
11881188
df3 = copy(df2)
11891189
view(df1, :, :) .= df2
@@ -1196,8 +1196,8 @@ end
11961196
df2 = copy(df1)
11971197
df3 = copy(df1)
11981198
for i in 1:100
1199-
df2[!, rand(1:100)] = df1[!, i]
1200-
df3[!, rand(1:100)] = df1[!, i]
1199+
@alias df2[!, rand(1:100)] = df1[!, i]
1200+
@alias df3[!, rand(1:100)] = df1[!, i]
12011201
end
12021202
df6 = copy(df2)
12031203
df7 = copy(df3)
@@ -1214,8 +1214,8 @@ end
12141214
df2 = copy(df1)
12151215
df3 = copy(df1)
12161216
for i in 1:100
1217-
df2[!, rand(1:100)] = df1[!, i]
1218-
df3[!, rand(1:100)] = df1[!, i]
1217+
@alias df2[!, rand(1:100)] = df1[!, i]
1218+
@alias df3[!, rand(1:100)] = df1[!, i]
12191219
end
12201220
df6 = copy(df2)
12211221
df7 = copy(df3)
@@ -1232,8 +1232,8 @@ end
12321232
df2 = copy(df1)
12331233
df3 = copy(df1)
12341234
for i in 1:100
1235-
df2[!, rand(1:100)] = df1[!, i]
1236-
df3[!, rand(1:100)] = df1[!, i]
1235+
@alias df2[!, rand(1:100)] = df1[!, i]
1236+
@alias df3[!, rand(1:100)] = df1[!, i]
12371237
end
12381238
df6 = copy(df2)
12391239
df7 = copy(df3)
@@ -1937,21 +1937,33 @@ end
19371937
@test df.z == fill("abc", 3)
19381938
@test_throws ArgumentError df[:, 6] .= z
19391939
@test_throws MethodError df[:, 1] .= z
1940+
dz = df.z
1941+
df[:, "z"] .= z
1942+
@test dz === df.z
1943+
@test dz == df[:, "z"]
19401944

19411945
df = DataFrame(ones(3, 4), :auto)
19421946
z = fill("abc", 1, 1, 2)
19431947
@test_throws DimensionMismatch df[:, :z] .= z
19441948

19451949
df = DataFrame(ones(3, 4), :auto)
19461950
z = ["a", "b", "c"]
1947-
df[:, "z"] .= z
1951+
df[:, "z"] = z
19481952
@test df.z == z
19491953
@test df.z !== z
1954+
dz = df.z
1955+
df[:, "z"] .= z
1956+
@test dz === df.z
1957+
@test dz == df[:, "z"]
19501958

19511959
df = DataFrame(ones(3, 4), :auto)
19521960
z = "abc"
19531961
df[:, "z"] .= z
19541962
@test df.z == fill("abc", 3)
1963+
dz = df.z
1964+
df[:, "z"] .= z
1965+
@test dz === df.z
1966+
@test dz == df[:, "z"]
19551967

19561968
df = DataFrame(ones(3, 4), :auto)
19571969
z = fill("abc", 1, 1, 2)

0 commit comments

Comments
 (0)