text:
module TestSelect

using DataFrames, Test, Random, Statistics, CategoricalArrays

const ≅ = isequal

"""Check if passed data frames are `isequal` and have the same types of columns"""
isequal_coltyped(df1::AbstractDataFrame, df2::AbstractDataFrame) =
    isequal(df1, df2) && typeof.(eachcol(df1)) == typeof.(eachcol(df2))

Random.seed!(1234)

@testset "select! Not" begin
    df = DataFrame(a=1, b=2, c=3, d=4, e=5)
    @test_throws BoundsError select!(df, Not(0))
    @test_throws BoundsError select!(df, Not(6))
    @test_throws ArgumentError select!(df, Not([1, 1]))
    @test_throws ArgumentError select!(df, Not(:f))
    @test_throws BoundsError select!(df, Not([true, false]))

    d = copy(df)
    select!(d, Not([:a, :e, :c]))
    @test d == DataFrame(b=2, d=4)
    DataFrames._check_consistency(d)
    select!(d, Not(:b))
    @test d == DataFrame(d=4)
    DataFrames._check_consistency(d)

    d = copy(df)
    select!(d, Not(r"[aec]"))
    @test d == DataFrame(b=2, d=4)
    DataFrames._check_consistency(d)
    select!(d, Not(r"b"))
    @test d == DataFrame(d=4)
    DataFrames._check_consistency(d)

    d = copy(df)
    select!(d, Not([2, 5, 3]))
    @test d == DataFrame(a=1, d=4)
    DataFrames._check_consistency(d)
    select!(d, Not(2))
    @test d == DataFrame(a=1)
    DataFrames._check_consistency(d)

    d = copy(df)
    select!(d, Not(2:3))
    @test d == DataFrame(a=1, d=4, e=5)
    DataFrames._check_consistency(d)

    d = copy(df)
    select!(d, Not([false, true, true, false, false]))
    @test d == DataFrame(a=1, d=4, e=5)
    DataFrames._check_consistency(d)
end

@testset "select Not" begin
    df = DataFrame(a=1, b=2, c=3, d=4, e=5)
    @test_throws BoundsError select(df, Not(0))
    @test_throws BoundsError select(df, Not(6))
    @test_throws ArgumentError select(df, Not([1, 1]))
    @test_throws ArgumentError select(df, Not(:f))
    @test_throws BoundsError select(df, Not([true, false]))

    df2 = copy(df)
    d = select(df, Not([:a, :e, :c]))
    @test d == df[:, [:b, :d]]
    @test d.b !== df.b
    @test d.d !== df.d
    @test df == df2

    df2 = copy(df)
    d = select(df, Not(r"[aec]"))
    @test d == df[:, [:b, :d]]
    @test d == df[:, r"[bd]"]
    @test d.b !== df.b
    @test d.d !== df.d
    @test df == df2

    d = select(df, Not([2, 5, 3]))
    @test d.a !== df.a
    @test d.d !== df.d
    @test d == df[:, [:a, :d]]
    @test df == df2

    d = select(df, Not(2:3))
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a !== df.a
    @test d.d !== df.d
    @test d.e !== df.e
    @test df == df2

    d = select(df, Not([false, true, true, false, false]))
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a !== df.a
    @test d.d !== df.d
    @test d.e !== df.e
    @test df == df2

    d = select(df, Not(1))
    @test d == DataFrame(b=2, c=3, d=4, e=5)
    @test d.b !== df.b
    @test d.b == df.b
    @test df == df2

    d = select(df, Not([:a, :e, :c]), copycols=false)
    @test d == df[:, [:b, :d]]
    @test d.b === df.b
    @test d.d === df.d
    @test df == df2

    d = select(df, Not(r"[aec]"), copycols=false)
    @test d == df[:, [:b, :d]]
    @test d == df[:, r"[bd]"]
    @test d.b === df.b
    @test d.d === df.d
    @test df == df2

    d = select(df, Not([2, 5, 3]), copycols=false)
    @test d.a === df.a
    @test d.d === df.d
    @test d == df[:, [:a, :d]]
    @test df == df2

    d = select(df, Not(2:3), copycols=false)
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a === df.a
    @test d.d === df.d
    @test d.e === df.e
    @test df == df2

    d = select(df, Not([false, true, true, false, false]), copycols=false)
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a === df.a
    @test d.d === df.d
    @test d.e === df.e
    @test df == df2

    d = select(df, Not(1), copycols=false)
    @test d == DataFrame(b=2, c=3, d=4, e=5)
    @test d.b === df.b
    @test df == df2
end

@testset "select Not on SubDataFrame" begin
    df = view(DataFrame(a=1, b=2, c=3, d=4, e=5), :, :)
    @test_throws BoundsError select(df, Not(0))
    @test_throws BoundsError select(df, Not(6))
    @test_throws ArgumentError select(df, Not([1, 1]))
    @test_throws ArgumentError select(df, Not(:f))
    @test_throws BoundsError select(df, Not([true, false]))

    df2 = copy(df)
    d = select(df, Not([:a, :e, :c]))
    @test d isa DataFrame
    @test d == df[:, [:b, :d]]
    @test d.b !== df.b
    @test d.d !== df.d
    @test df == df2

    df2 = copy(df)
    d = select(df, Not(r"[aec]"))
    @test d isa DataFrame
    @test d == df[:, [:b, :d]]
    @test d == df[:, r"[bd]"]
    @test d.b !== df.b
    @test d.d !== df.d
    @test df == df2

    d = select(df, Not([2, 5, 3]))
    @test d isa DataFrame
    @test d.a !== df.a
    @test d.d !== df.d
    @test d == df[:, [:a, :d]]
    @test df == df2

    d = select(df, Not(2:3))
    @test d isa DataFrame
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a !== df.a
    @test d.d !== df.d
    @test d.e !== df.e
    @test df == df2

    d = select(df, Not([false, true, true, false, false]))
    @test d isa DataFrame
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a !== df.a
    @test d.d !== df.d
    @test d.e !== df.e
    @test df == df2

    d = select(df, Not(1))
    @test d isa DataFrame
    @test d == DataFrame(b=2, c=3, d=4, e=5)
    @test d.b !== df.b
    @test d.b == df.b
    @test df == df2

    d = select(df, Not([:a, :e, :c]), copycols=false)
    @test d isa SubDataFrame
    @test d == df[:, [:b, :d]]
    @test d.b === df.b
    @test d.d === df.d
    @test df == df2

    d = select(df, Not(r"[aec]"), copycols=false)
    @test d isa SubDataFrame
    @test d == df[:, [:b, :d]]
    @test d == df[:, r"[bd]"]
    @test d.b === df.b
    @test d.d === df.d
    @test df == df2

    d = select(df, Not([2, 5, 3]), copycols=false)
    @test d isa SubDataFrame
    @test d.a === df.a
    @test d.d === df.d
    @test d == df[:, [:a, :d]]
    @test df == df2

    d = select(df, Not(2:3), copycols=false)
    @test d isa SubDataFrame
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a === df.a
    @test d.d === df.d
    @test d.e === df.e
    @test df == df2

    d = select(df, Not([false, true, true, false, false]), copycols=false)
    @test d isa SubDataFrame
    @test d == DataFrame(a=1, d=4, e=5)
    @test d.a === df.a
    @test d.d === df.d
    @test d.e === df.e
    @test df == df2

    d = select(df, Not(1), copycols=false)
    @test d isa SubDataFrame
    @test d == DataFrame(b=2, c=3, d=4, e=5)
    @test d.b === df.b
    @test df == df2
end

@testset "select!" begin
    df = DataFrame(a=1, b=2, c=3, d=4, e=5)
    @test_throws BoundsError select!(df, 0)
    @test_throws BoundsError select!(df, 6)
    @test_throws ArgumentError select!(df, [1, 1])
    @test_throws ArgumentError select!(df, :f)
    @test_throws BoundsError select!(df, [true, false])

    @test_throws MethodError select!(view(df, :, :), 1:2)

    d = copy(df, copycols=false)
    @test select!(d, 1:0) == DataFrame()
    @test select!(d, Not(r"")) == DataFrame()

    d = copy(df, copycols=false)
    select!(d, [:a, :e, :c])
    @test propertynames(d) == [:a, :e, :c]
    @test d.a === df.a
    @test d.e === df.e
    @test d.c === df.c

    d = copy(df, copycols=false)
    select!(d, r"[aec]")
    @test propertynames(d) == [:a, :c, :e]
    @test d.a === df.a
    @test d.e === df.e
    @test d.c === df.c

    d = copy(df, copycols=false)
    select!(d, [true, false, true, false, true])
    @test propertynames(d) == [:a, :c, :e]
    @test d.a === df.a
    @test d.c === df.c
    @test d.e === df.e

    d = copy(df, copycols=false)
    select!(d, [:d, :e, :a, :c, :b])
    @test propertynames(d) == [:d, :e, :a, :c, :b]
    for i in [:d, :e, :a, :c, :b]
        @test d[!, i] === df[!, i]
    end

    d = copy(df, copycols=false)
    select!(d, [2, 5, 3])
    @test propertynames(d) == [:b, :e, :c]
    @test d.b === df.b
    @test d.e === df.e
    @test d.c === df.c

    d = copy(df, copycols=false)
    select!(d, 2:3)
    @test propertynames(d) == [:b, :c]
    @test d.b === df.b
    @test d.c === df.c

    d = copy(df, copycols=false)
    select!(d, 2)
    @test propertynames(d) == [:b]
    @test d.b === df.b
end

@testset "select" begin
    df = DataFrame(a=1, b=2, c=3, d=4, e=5)
    @test_throws BoundsError select(df, 0)
    @test_throws BoundsError select(df, 6)
    @test_throws ArgumentError select(df, [1, 1])
    @test_throws ArgumentError select(df, :f)
    @test_throws BoundsError select!(df, [true, false])

    @test select(df, 1:0) == DataFrame()
    @test select(df, Not(r"")) == DataFrame()
    @test select(df, 1:0, copycols=false) == DataFrame()
    @test select(df, Not(r""), copycols=false) == DataFrame()

    d = select(df, [:a, :e, :c])
    @test propertynames(d) == [:a, :e, :c]
    @test d.a !== df.a
    @test d.e !== df.e
    @test d.c !== df.c
    @test d.a == df.a
    @test d.e == df.e
    @test d.c == df.c

    d = select(df, r"[aec]")
    @test propertynames(d) == [:a, :c, :e]
    @test d.a !== df.a
    @test d.e !== df.e
    @test d.c !== df.c
    @test d.a == df.a
    @test d.e == df.e
    @test d.c == df.c

    d = select(df, [true, false, true, false, true])
    @test propertynames(d) == [:a, :c, :e]
    @test d.a !== df.a
    @test d.c !== df.c
    @test d.e !== df.e
    @test d.a == df.a
    @test d.c == df.c
    @test d.e == df.e

    d = select(df, [2, 5, 3])
    @test propertynames(d) == [:b, :e, :c]
    @test d.b !== df.b
    @test d.e !== df.e
    @test d.c !== df.c
    @test d.b == df.b
    @test d.e == df.e
    @test d.c == df.c

    d = select(df, 2:3)
    @test propertynames(d) == [:b, :c]
    @test d.b !== df.b
    @test d.c !== df.c
    @test d.b == df.b
    @test d.c == df.c

    d = select(df, 2)
    @test propertynames(d) == [:b]
    @test d.b !== df.b
    @test d.b == df.b

    d = select(df, [:a, :e, :c], copycols=false)
    @test propertynames(d) == [:a, :e, :c]
    @test d.a === df.a
    @test d.e === df.e
    @test d.c === df.c

    d = select(df, r"[aec]", copycols=false)
    @test propertynames(d) == [:a, :c, :e]
    @test d.a === df.a
    @test d.e === df.e
    @test d.c === df.c

    d = select(df, [true, false, true, false, true], copycols=false)
    @test propertynames(d) == [:a, :c, :e]
    @test d.a === df.a
    @test d.c === df.c
    @test d.e === df.e

    d = select(df, [2, 5, 3], copycols=false)
    @test propertynames(d) == [:b, :e, :c]
    @test d.b === df.b
    @test d.e === df.e
    @test d.c === df.c

    d = select(df, 2:3, copycols=false)
    @test propertynames(d) == [:b, :c]
    @test d.b === df.b
    @test d.c === df.c

    d = select(df, 2, copycols=false)
    @test propertynames(d) == [:b]
    @test d.b === df.b
end

@testset "select on SubDataFrame" begin
    df = view(DataFrame(a=1, b=2, c=3, d=4, e=5), :, :)
    @test_throws BoundsError select(df, 0)
    @test_throws BoundsError select(df, 6)
    @test_throws ArgumentError select(df, [1, 1])
    @test_throws ArgumentError select(df, :f)
    @test_throws MethodError select!(df, [true, false])

    @test select(df, 1:0) == DataFrame()
    @test select(df, Not(r"")) == DataFrame()
    @test select(df, 1:0, copycols=false) == DataFrame()
    @test select(df, Not(r""), copycols=false) == DataFrame()

    d = select(df, [:a, :e, :c])
    @test d isa DataFrame
    @test propertynames(d) == [:a, :e, :c]
    @test d.a !== df.a
    @test d.e !== df.e
    @test d.c !== df.c
    @test d.a == df.a
    @test d.e == df.e
    @test d.c == df.c

    d = select(df, r"[aec]")
    @test d isa DataFrame
    @test propertynames(d) == [:a, :c, :e]
    @test d.a !== df.a
    @test d.e !== df.e
    @test d.c !== df.c
    @test d.a == df.a
    @test d.e == df.e
    @test d.c == df.c

    d = select(df, [true, false, true, false, true])
    @test d isa DataFrame
    @test propertynames(d) == [:a, :c, :e]
    @test d.a !== df.a
    @test d.c !== df.c
    @test d.e !== df.e
    @test d.a == df.a
    @test d.c == df.c
    @test d.e == df.e

    d = select(df, [2, 5, 3])
    @test d isa DataFrame
    @test propertynames(d) == [:b, :e, :c]
    @test d.b !== df.b
    @test d.e !== df.e
    @test d.c !== df.c
    @test d.b == df.b
    @test d.e == df.e
    @test d.c == df.c

    d = select(df, 2:3)
    @test d isa DataFrame
    @test propertynames(d) == [:b, :c]
    @test d.b !== df.b
    @test d.c !== df.c
    @test d.b == df.b
    @test d.c == df.c

    d = select(df, 2)
    @test d isa DataFrame
    @test propertynames(d) == [:b]
    @test d.b !== df.b
    @test d.b == df.b

    d = select(df, [:a, :e, :c], copycols=false)
    @test d isa SubDataFrame
    @test propertynames(d) == [:a, :e, :c]
    @test d.a === df.a
    @test d.e === df.e
    @test d.c === df.c

    d = select(df, r"[aec]", copycols=false)
    @test d isa SubDataFrame
    @test propertynames(d) == [:a, :c, :e]
    @test d.a === df.a
    @test d.e === df.e
    @test d.c === df.c

    d = select(df, [true, false, true, false, true], copycols=false)
    @test d isa SubDataFrame
    @test propertynames(d) == [:a, :c, :e]
    @test d.a === df.a
    @test d.c === df.c
    @test d.e === df.e

    d = select(df, [2, 5, 3], copycols=false)
    @test d isa SubDataFrame
    @test propertynames(d) == [:b, :e, :c]
    @test d.b === df.b
    @test d.e === df.e
    @test d.c === df.c

    d = select(df, 2:3, copycols=false)
    @test d isa SubDataFrame
    @test propertynames(d) == [:b, :c]
    @test d.b === df.b
    @test d.c === df.c

    d = select(df, 2, copycols=false)
    @test d isa SubDataFrame
    @test propertynames(d) == [:b]
    @test d.b === df.b
end

@testset "select! on all columns" begin
    a, b, c = 1:5, 2:6, 3:7
    original = DataFrame(a=a, b=b, c=c)

    df = deepcopy(original)
    expected = deepcopy(original)
    @test select!(df, [:a, :b, :c]) === df
    @test df == expected
    @test select!(df, 1:3) === df
    @test df == expected

    df = deepcopy(original)
    expected = DataFrame(b=b, c=c, a=a)
    select!(df, [:b, :c, :a]) === df
    @test df == expected
    df = deepcopy(original)
    select!(df, [2, 3, 1]) === df
    @test df == expected

    df = deepcopy(original)
    expected = DataFrame(c=c, a=a, b=b)
    select!(df, [:c, :a, :b]) === df
    @test df == expected
    df = deepcopy(original)
    select!(df, [3, 1, 2]) === df
    @test df == expected

    df = deepcopy(original)
    expected = DataFrame(a=a, c=c, b=b)
    select!(df, [:a, :c, :b]) === df
    @test df == expected
    df = deepcopy(original)
    select!(df, [1, 3, 2]) === df
    @test df == expected

    df = deepcopy(original)
    expected = DataFrame(b=b, a=a, c=c)
    select!(df, [:b, :a, :c]) === df
    @test df == expected
    df = deepcopy(original)
    select!(df, [2, 1, 3]) === df
    @test df == expected

    df = deepcopy(original)
    expected = DataFrame(c=c, b=b, a=a)
    select!(df, [:c, :b, :a]) === df
    @test df == expected
    df = deepcopy(original)
    select!(df, [3, 2, 1]) === df
    @test df == expected

    df = DataFrame(a=a, b=b, c=c)
    @test_throws BoundsError select!(df, 1:4)
    @test_throws ArgumentError select!(df, [:a, :b, :c, :d])
    @test_throws ArgumentError select!(df, [1, 2, 3, 1])
    @test_throws ArgumentError select!(df, [:a, :b, :c, :a])

    # but this works
    @test select!(copy(df), [:a, :c]) == df[:, [:a, :c]]
    @test select!(copy(df), [:a, :b]) == df[:, [:a, :b]]
    @test select!(copy(df), [1, 3]) == df[:, [1, 3]]
end

@testset "select and select! with multiple selectors passed" begin
    df = DataFrame(rand(10, 4), :auto)
    @test select(df, :x2, :x4, All()) == select(df, :x2, :x4, :x1, :x3)
    @test select(df, :x2, :x4, Cols(:)) == select(df, :x2, :x4, :x1, :x3)
    @test select(df, :x2, :x4, Cols()) == select(df, :x2, :x4)
    @test select(df, :x4, Between(:x2, :x4), All()) == select(df, :x4, :x2, :x3, :x1)

    dfv = view(df, :, :)
    @test select(dfv, :x2, :x4, All()) == select(df, :x2, :x4, :x1, :x3)
    @test select(dfv, :x2, :x4, Cols(:)) == select(df, :x2, :x4, :x1, :x3)
    @test select(dfv, :x2, :x4, Cols()) == select(df, :x2, :x4)
    @test select(dfv, :x4, Between(:x2, :x4), All()) == select(df, :x4, :x2, :x3, :x1)
    @test select(dfv, :x4, Between(:x2, :x4), Cols(:)) == select(df, :x4, :x2, :x3, :x1)
    @test select(dfv, :x4, Between(:x2, :x4), Cols()) == select(df, :x4, :x2, :x3)
    @test select(dfv, :x2, :x4, All()) == select(dfv, :x2, :x4, :x1, :x3)
    @test select(dfv, :x4, Between(:x2, :x4), All()) == select(dfv, :x4, :x2, :x3, :x1)
    @test select(dfv, :x4, Between(:x2, :x4), Cols(:)) == select(dfv, :x4, :x2, :x3, :x1)
    @test select(dfv, :x4, Between(:x2, :x4), Cols()) == select(dfv, :x4, :x2, :x3)

    dfc = copy(df)
    @test select!(dfc, :x2, :x4, All()) == dfc
    @test select!(dfc, :x2, :x4, Cols(:)) == dfc
    @test dfc == select(df, :x2, :x4, :x1, :x3)
    dfc = copy(df)
    @test select!(dfc, :x4, Between(:x2, :x4), All()) == dfc
    @test select!(dfc, :x4, Between(:x2, :x4), Cols(:)) == dfc
    @test dfc == select(df, :x4, :x2, :x3, :x1)

    @test select(df, Not([:x2, :x3]), All()) == select(df, :x1, :x4, :x2, :x3)
    @test select(df, Not([:x2, :x3]), Cols(:)) == select(df, :x1, :x4, :x2, :x3)
end

@testset "select and select! renaming" begin
    df = DataFrame(rand(10, 4), :auto)
    @test select(df, :x1 => :x2, :x2 => :x1) == rename(df[:, 1:2], [:x2, :x1])
    @test select(df, :x2 => :x1, :x1 => :x2) == DataFrame(x1=df.x2, x2=df.x1)
    @test_throws ArgumentError select(df, [:x1, :x2] => :x3)
    @test_throws ArgumentError select!(df, [:x1, :x2] => :x3)
    @test_throws BoundsError select(df, 0 => :x3)
    @test_throws BoundsError select!(df, 0 => :x3)

    df2 = select(df, :x1 => :x2, :x2 => :x1)
    @test df2.x1 == df.x2
    @test df2.x1 !== df.x2
    df2 = select(df, :x1 => :x2, :x2 => :x1, copycols=false)
    @test df2.x1 === df.x2

    df2 = select(df, :x1, :x1 => :x2)
    @test df2.x1 == df2.x2
    @test df2.x1 !== df2.x2

    df2 = select(df, :x1, :x1 => :x2, copycols=false)
    @test df2.x1 === df2.x2

    x1 = df.x1
    x2 = df.x2
    select!(df, :x1 => :x2, :x2 => :x1)
    @test x1 === df.x2
    @test x2 === df.x1
    @test names(df) == ["x2", "x1"]

    df = DataFrame(rand(10, 4), :auto)
    select!(df, :x1, :x1 => :x2)
    @test df2.x1 === df2.x2

    df = DataFrame(rand(10, 4), :auto)
    df2 = select(df, :, :x1 => :x3)
    @test df2 == DataFrame(collect(eachcol(df))[[1, 2, 1, 4]], :auto)
    @test df2.x1 !== df2.x3
    df2 = select(df, :, :x1 => :x3, copycols=false)
    @test df2 == DataFrame(collect(eachcol(df))[[1, 2, 1, 4]], :auto)
    @test df2.x1 === df2.x3
    @test select(df, :x1 => :x3, :) == DataFrame(collect(eachcol(df))[[1, 1, 2, 4]],
                                                 [:x3, :x1, :x2, :x4])
    select!(df, :, :x1 => :x3)
    @test df2 == df
    @test all(i -> df2[!, i] === df[!, i], ncol(df2))
end

@testset "select and select! many columns naming" begin
    df = DataFrame(rand(10, 4), :auto)
    for fun in (+, ByRow(+)), copycols in [true, false]
        @test select(df, 1 => fun, copycols=copycols) ==
              DataFrame(Symbol("x1_+") => df.x1)
        @test select(df, 1:2 => fun, copycols=copycols) ==
              DataFrame(Symbol("x1_x2_+") => df.x1 + df.x2)
        @test select(df, 1:3 => fun, copycols=copycols) ==
              DataFrame(Symbol("x1_x2_x3_+") => df.x1 + df.x2 + df.x3)
        @test select(df, 1:4 => fun, copycols=copycols) ==
              DataFrame(Symbol("x1_x2_etc_+") => sum.(eachrow(df)))
    end
    for fun in (+, ByRow(+))
        dfc = copy(df)
        select!(dfc, 1 => fun)
        @test dfc == DataFrame(Symbol("x1_+") => df.x1)
        dfc = copy(df)
        select!(dfc, 1:2 => fun)
        @test dfc == DataFrame(Symbol("x1_x2_+") => df.x1 + df.x2)
        dfc = copy(df)
        select!(dfc, 1:3 => fun)
        @test dfc == DataFrame(Symbol("x1_x2_x3_+") => df.x1 + df.x2 + df.x3)
        dfc = copy(df)
        select!(dfc, 1:4 => fun)
        @test dfc == DataFrame(Symbol("x1_x2_etc_+") => sum.(eachrow(df)))
    end
end

@testset "select and select! many different transforms" begin
    df = DataFrame(rand(10, 4), :auto)

    df2 = select(df, :x2, :, :x1 => ByRow(x -> x^2) => :r1, :x1 => (x -> x .^ 2) => :r2,
                 [:x1, :x2] => (+) => :x1, 1:2 => ByRow(/) => :x3, :x1 => :x4)
    @test propertynames(df2) == [:x2, :x1, :x3, :x4, :r1, :r2]
    @test df.x2 == df2.x2
    @test df.x2 !== df2.x2
    @test df.x1 == df2.x4
    @test df.x4 !== df2.x1
    @test df2.r1 == df.x1 .^ 2
    @test df2.r1 == df2.r2
    @test df2.x1 == df.x1 + df.x2
    @test df2.x3 == df.x1 ./ df.x2

    @test select(df, [:x1, :x1] => +) == DataFrame(Symbol("x1_x1_+") => 2*df.x1)
    @test select(df, [1, 1] => +) == DataFrame(Symbol("x1_x1_+") => 2*df.x1)

    df2 = select(df, :x2, :, :x1 => ByRow(x -> x^2) => :r1, :x1 => (x -> x .^ 2) => :r2,
                 [:x1, :x2] => (+) => :x1, 1:2 => ByRow(/) => :x3, :x1 => :x4, copycols=false)
    @test propertynames(df2) == [:x2, :x1, :x3, :x4, :r1, :r2]
    @test df.x2 === df2.x2
    @test df.x1 === df2.x4
    @test df2.r1 == df.x1 .^ 2
    @test df2.r1 == df2.r2
    @test df2.x1 == df.x1 + df.x2
    @test df2.x3 == df.x1 ./ df.x2

    x1, x2, x3, x4 = df.x1, df.x2, df.x3, df.x4
    select!(df, :x2, :, :x1 => ByRow(x -> x^2) => :r1, :x1 => (x -> x .^ 2) => :r2,
            [:x1, :x2] => (+) => :x1, 1:2 => ByRow(/) => :x3, :x1 => :x4)
    @test propertynames(df2) == [:x2, :x1, :x3, :x4, :r1, :r2]
    @test x2 === df.x2
    @test x1 === df.x4
    @test df.r1 == x1 .^ 2
    @test df.r1 == df.r2
    @test df.x1 == x1 + x2
    @test df.x3 == x1 ./ x2
end

@testset "nrow in select" begin
    df_ref = DataFrame(ones(3, 4), :auto)
    for df in [df_ref, view(df_ref, 1:2, 1:2),
               df_ref[1:2, []], view(df_ref, 1:2, []),
               df_ref[[], 1:2], view(df_ref, [], 1:2)]
        @test select(df, nrow => :z, nrow, [nrow => :z2]) ==
              repeat(DataFrame(z=nrow(df), nrow=nrow(df), z2=nrow(df)), nrow(df))
        @test combine(df, nrow => :z, nrow, [nrow => :z2]) ==
              DataFrame(z=nrow(df), nrow=nrow(df), z2=nrow(df))
        @test_throws ArgumentError select(df, nrow, nrow)
        @test_throws ArgumentError select(df, [nrow])
    end
end

@testset "select and select! reserved return values" begin
    df = DataFrame(x=1)
    df2 = copy(df)
    for retval in [df2, (a=1, b=2), df2[1, :], ones(2, 2)]
        @test_throws ArgumentError select(df, :x => x -> retval)
        @test_throws ArgumentError select(df, :x => x -> retval, copycols=false)
        @test_throws ArgumentError select!(df, :x => x -> retval)

        @test select(df, :x => ByRow(x -> retval)) == DataFrame(x_function = [retval])
        cdf = copy(df)
        select!(cdf, :x => ByRow(x -> retval))
        @test cdf == DataFrame(x_function = [retval])

        if retval isa Union{NamedTuple, DataFrameRow}
            @test select(df, :x => ByRow(x -> retval) => AsTable) == DataFrame(;retval...)
        elseif retval isa DataFrame
            @test_throws MethodError select(df, :x => ByRow(x -> retval) => AsTable)
        else # Matrix: wrong type of keys
            @test_throws ArgumentError select(df, :x => ByRow(x -> retval) => AsTable)
            @test_throws ArgumentError select(df, :x => ByRow(x -> retval) => [:a, :b, :c, :d])
        end
    end

    for retval in [(1, 2), ones(2, 2, 2)]
        @test select(df, :x => x -> retval) == DataFrame(x_function = [retval])
        @test select(df, :x => ByRow(x -> retval)) == DataFrame(x_function = [retval])
        if retval isa Tuple
            @test select(df, :x => ByRow(x -> retval) => AsTable) == DataFrame(x1=1, x2=2)
        else
            @test_throws ArgumentError select(df, :x => ByRow(x -> retval) => AsTable)
        end
        cdf = copy(df)
        select!(cdf, :x => x -> retval)
        @test cdf == DataFrame(x_function = [retval])
        cdf = copy(df)
        select!(cdf, :x => ByRow(x -> retval))
        @test cdf == DataFrame(x_function = [retval])
    end
end

@testset "select and select! empty selection" begin
    df = DataFrame(rand(10, 4), :auto)
    x = [1:10;]
    y = [1, 2, 3]

    @test select(df, r"z") == DataFrame()
    @test select(df, r"z" => () -> x) == DataFrame(:function => x)
    @test_throws ArgumentError select(df, r"z" => () -> y)
    @test combine(df, r"z" => () -> y) == DataFrame(:function => y)
    @test select(df, r"z" => () -> x)[!, 1] === x # no copy even for copycols=true
    @test_throws MethodError select(df, r"z" => x -> 1)
    @test select(df, r"z" => ByRow(() -> 1)) == DataFrame(:function => fill(1, 10))

    @test select(df, r"z", copycols=false) == DataFrame()
    @test select(df, r"z" => () -> x, copycols=false) == DataFrame(:function => x)
    @test select(df, r"z" => () -> x, copycols=false)[!, 1] === x
    @test_throws MethodError select(df, r"z" => x -> 1, copycols=false)
    @test select(df, r"z" => ByRow(() -> 1)) == DataFrame(:function => fill(1, 10), copycols=false)

    @test_throws MethodError select!(df, r"z" => x -> 1)
    @test select!(df, r"z" => ByRow(() -> 1)) == DataFrame(:function => fill(1, 10))
    @test_throws MethodError select!(df, r"z" => () -> x, copycols=false)

    select!(df, r"z" => () -> x)
    @test df == DataFrame(:function => x)
end

@testset "wrong selection patterns" begin
    df = DataFrame(rand(10, 4), :auto)

    @test_throws ArgumentError select(df, "z")
    @test_throws ArgumentError select(df, "z" => :x1)
    @test_throws ArgumentError select(df, "z" => identity)
    @test_throws ArgumentError select(df, "z" => identity => :x1)
end

@testset "select and select! duplicates" begin
    df = DataFrame(rand(10, 4), :auto)
    df_ref = copy(df)

    @test_throws ArgumentError select(df, :x1, :x1)
    @test_throws ArgumentError select(df, :x1, :x5)
    @test select(df, :x2, r"x", :x1, :) == df[:, [:x2, :x1, :x3, :x4]]

    @test_throws ArgumentError select(df, :x1, :x2 => :x1)
    @test_throws ArgumentError select(df, :x3 => :x1, :x2 => :x1)
    @test_throws ArgumentError select(df, :x1, :x2 => identity => :x1)
    @test_throws ArgumentError select(df, :x1 => :x1, :x2 => identity => :x1)
    @test_throws ArgumentError select(df, :x3 => identity => :x1, :x2 => identity => :x1)
    @test select(df, [:x1], :x2 => :x1) == DataFrame(x1 = df.x2)

    @test_throws ArgumentError select!(df, :x1, :x1)
    @test_throws ArgumentError select!(df, :x1, :x5)
    @test df == df_ref

    select!(df, :x2, r"x", :x1, :)
    @test df == df_ref[:, [:x2, :x1, :x3, :x4]]

    df = DataFrame(rand(10, 2), [:x1, :x2])
    @test select(df, [:x1, :x1] => -) == DataFrame(Symbol("x1_x1_-") => zeros(10))
    select!(df, [:x1, :x1] => -)
    @test df == DataFrame(Symbol("x1_x1_-") => zeros(10))
end

@testset "SubDataFrame selection" begin
    df = DataFrame(rand(12, 5), :auto)
    sdf = view(df, 1:10, 1:4)
    df_ref = copy(sdf)

    @test select(sdf, :x2, :, :x1 => ByRow(x -> x^2) => :r1, :x1 => (x -> x .^ 2) => :r2,
                 [:x1, :x2] => (+) => :x1, 1:2 => ByRow(/) => :x3, :x1 => :x4) ==
          select(df_ref, :x2, :, :x1 => ByRow(x -> x^2) => :r1, :x1 => (x -> x .^ 2) => :r2,
                 [:x1, :x2] => (+) => :x1, 1:2 => ByRow(/) => :x3, :x1 => :x4)

    for fun in (+, ByRow(+))
        @test select(sdf, 1 => fun) ==
              DataFrame(Symbol("x1_+") => sdf.x1)
        @test select(sdf, 1:2 => fun) ==
              DataFrame(Symbol("x1_x2_+") => sdf.x1 + sdf.x2)
        @test select(sdf, 1:3 => fun) ==
              DataFrame(Symbol("x1_x2_x3_+") => sdf.x1 + sdf.x2 + sdf.x3)
        @test select(sdf, 1:4 => fun) ==
              DataFrame(Symbol("x1_x2_etc_+") => sum.(eachrow(sdf)))
    end

    @test_throws ArgumentError select(sdf, :x1, :x1)
    @test_throws ArgumentError select(sdf, :x1, :x1, copycols=false)
    @test select(sdf, :x1, [:x1]) == sdf[:, [:x1]]
    @test select(sdf, :x1, [:x1]) isa DataFrame
    @test select(sdf, :x1, [:x1], copycols=false) == sdf[:, [:x1]]
    @test select(sdf, :x1, [:x1], copycols=false) isa SubDataFrame
    @test_throws ArgumentError select(sdf, :x1 => :r1, copycols=false)
    @test_throws ArgumentError select(sdf, :x1 => identity => :r1, copycols=false)
end

@testset "pseudo-broadcasting" begin
    df = DataFrame([1 2 3
                    4 5 6], :auto)
    df2 = DataFrame([1 2 3], :auto)
    df3 = DataFrame(x1=Char[], x2=Int[], x3=Int[])
    for v in [9, Ref(9), view([9], 1)]
        @test select(df, [] => (() -> v) => :a, :, (:) => (+) => :d) ==
              DataFrame([9 1 2 3 6
                         9 4 5 6 15], [:a, :x1, :x2, :x3, :d])
        @test select(df, (:) => (+) => :d, :, r"z" => (() -> v)  => :a) ==
              DataFrame([6  1 2 3 9
                         15 4 5 6 9], [:d, :x1, :x2, :x3, :a])
        @test select(df, [] => (() -> v) => :a, :x1 => :b, (:) => (+) => :d) ==
              DataFrame([9 1 6
                         9 4 15], [:a, :b, :d])
        @test select(df, (:) => (+) => :d, :x1 => :b, [] => (() -> v) => :a) ==
              DataFrame([6  1 9
                         15 4 9], [:d, :b, :a])
        @test select(df, [] => (() -> v) => :a, :x1 => (x -> x) => :b, (:) => (+) => :d) ==
              DataFrame([9 1 6
                         9 4 15], [:a, :b, :d])
        @test select(df, (:) => (+) => :d, :x1 => (x -> x) => :b, [] => (() -> v) => :a) ==
              DataFrame([6  1 9
                         15 4 9], [:d, :b, :a])
        @test select(df2, [] => (() -> v) => :a, :, (:) => (+) => :d) ==
              DataFrame([9 1 2 3 6], [:a, :x1, :x2, :x3, :d])
        @test select(df2, (:) => (+) => :d, :, r"z" => (() -> v)  => :a) ==
              DataFrame([6 1 2 3 9], [:d, :x1, :x2, :x3, :a])
        @test select(df2, [] => (() -> v) => :a, :x1 => :b, (:) => (+) => :d) ==
              DataFrame([9 1 6], [:a, :b, :d])
        @test select(df2, (:) => (+) => :d, :x1 => :b, [] => (() -> v) => :a) ==
              DataFrame([6 1 9], [:d, :b, :a])
        @test select(df2, [] => (() -> v) => :a, :x1 => (x -> x) => :b, (:) => (+) => :d) ==
              DataFrame([9 1 6], [:a, :b, :d])
        @test select(df2, (:) => (+) => :d, :x1 => (x -> x) => :b, [] => (() -> v) => :a) ==
              DataFrame([6 1 9], [:d, :b, :a])

        @test isequal_coltyped(select(df3, [] => (() -> v) => :a, :x1 => x -> []),
                               DataFrame(a=Int[], x1_function=Any[]))
        @test isequal_coltyped(select(df3, :x1 => x -> [], [] => (() -> v) => :a),
                               DataFrame(x1_function=Any[], a=Int[]))
        @test isequal_coltyped(select(df3, [] => (() -> v) => :a, :x1),
                               DataFrame(a=Int[], x1=Char[]))
        @test isequal_coltyped(select(df3, :x1, [] => (() -> v) => :a),
                               DataFrame(x1=Char[], a=Int[]))
    end
    @test_throws ArgumentError select(df, [] => (() -> [9]) => :a, :)
    @test_throws ArgumentError select(df, :, [] => (() -> [9]) => :a)
    @test transform(df, names(df) .=> (x -> 9) .=> names(df)) ==
          repeat(DataFrame([9 9 9], :auto), nrow(df))
    @test combine(df, names(df) .=> (x -> 9) .=> names(df)) ==
          DataFrame([9 9 9], :auto)
    @test transform(df, names(df) .=> (x -> 9) .=> names(df), :x1 => :x4) ==
          DataFrame([9 9 9 1; 9 9 9 4], :auto)
    @test transform(df3, names(df3) .=> (x -> 9) .=> names(df3)) ==
          repeat(DataFrame([9 9 9], :auto), nrow(df3))
    @test combine(df3, names(df3) .=> (x -> 9) .=> names(df3)) ==
          DataFrame([9 9 9], :auto)
    @test transform(df3, names(df3) .=> (x -> 9) .=> names(df3), :x1 => :x4) ==
          DataFrame(ones(0, 4), :auto)

    df = DataFrame(x1=1:2, x2=categorical(1:2),
                   x3=[missing, 2], x4=categorical([missing, 2]))

    df2 = select(df, names(df) .=> first)
    @test df2 ≅ repeat(DataFrame(x1_first=1, x2_first=1, x3_first=missing,
                                 x4_first=missing), nrow(df2))
    @test df2.x1_first isa Vector{Int}
    @test df2.x2_first isa CategoricalVector{Int}
    @test df2.x3_first isa Vector{Missing}
    @test df2.x4_first isa Vector{Missing}

    df2 = combine(df, names(df) .=> first)
    @test df2 ≅ DataFrame(x1_first=1, x2_first=1, x3_first=missing,
                          x4_first=missing)
    @test df2.x1_first isa Vector{Int}
    @test df2.x2_first isa CategoricalVector{Int}
    @test df2.x3_first isa Vector{Missing}
    @test df2.x4_first isa Vector{Missing}

    df2 = select(df, names(df) .=> last)
    @test df2 ≅ repeat(DataFrame(x1_last=2, x2_last=2, x3_last=2,
                                 x4_last=2), nrow(df2))
    @test df2.x1_last isa Vector{Int}
    @test df2.x2_last isa CategoricalVector{Int}
    @test df2.x3_last isa Vector{Int}
    @test df2.x4_last isa CategoricalVector{Int}

    df2 = combine(df, names(df) .=> last)
    @test df2 ≅ DataFrame(x1_last=2, x2_last=2, x3_last=2,
                          x4_last=2)
    @test df2.x1_last isa Vector{Int}
    @test df2.x2_last isa CategoricalVector{Int}
    @test df2.x3_last isa Vector{Int}
    @test df2.x4_last isa CategoricalVector{Int}

    for v in [:x1, :x1 => (x -> x) => :x1]
        df2 = select(df, names(df) .=> first, v)
        @test df2 ≅ DataFrame(x1_first=1, x2_first=1, x3_first=missing,
                              x4_first=missing, x1=[1, 2])
        @test df2.x1_first isa Vector{Int}
        @test df2.x2_first isa CategoricalVector{Int}
        @test df2.x3_first isa Vector{Missing}
        @test df2.x4_first isa Vector{Missing}

        df2 = select(df, names(df) .=> last, v)
        @test df2 ≅ DataFrame(x1_last=2, x2_last=2, x3_last=2,
                              x4_last=2, x1=[1, 2])
        @test df2.x1_last isa Vector{Int}
        @test df2.x2_last isa CategoricalVector{Int}
        @test df2.x3_last isa Vector{Int}
        @test df2.x4_last isa CategoricalVector{Int}

        df2 = select(df, v, names(df) .=> first)
        @test df2 ≅ DataFrame(x1=[1, 2], x1_first=1, x2_first=1, x3_first=missing,
                              x4_first=missing)
        @test df2.x1_first isa Vector{Int}
        @test df2.x2_first isa CategoricalVector{Int}
        @test df2.x3_first isa Vector{Missing}
        @test df2.x4_first isa Vector{Missing}

        df2 = select(df, v, names(df) .=> last)
        @test df2 ≅ DataFrame(x1=[1, 2], x1_last=2, x2_last=2, x3_last=2,
                              x4_last=2)
        @test df2.x1_last isa Vector{Int}
        @test df2.x2_last isa CategoricalVector{Int}
        @test df2.x3_last isa Vector{Int}
        @test df2.x4_last isa CategoricalVector{Int}
    end

    @test_throws ArgumentError select(df, names(df) .=> first, [] => (() -> Int[]) => :x1)
    df2 = combine(df, names(df) .=> first, [] => (() -> Int[]) => :x1)
    @test size(df2) == (0, 5)
    @test df2.x1_first isa Vector{Int}
    @test df2.x2_first isa CategoricalVector{Int}
    @test df2.x3_first isa Vector{Missing}
    @test df2.x4_first isa Vector{Missing}

    @test_throws ArgumentError select(df, names(df) .=> last, [] => (() -> Int[]) => :x1)
    df2 = combine(df, names(df) .=> last, [] => (() -> Int[]) => :x1)
    @test size(df2) == (0, 5)
    @test df2.x1_last isa Vector{Int}
    @test df2.x2_last isa CategoricalVector{Int}
    @test df2.x3_last isa Vector{Int}
    @test df2.x4_last isa CategoricalVector{Int}

    @test_throws ArgumentError select(df, [] => (() -> Int[]) => :x1, names(df) .=> first)
    df2 = combine(df, [] => (() -> Int[]) => :x1, names(df) .=> first)
    @test size(df2) == (0, 5)
    @test df2.x1_first isa Vector{Int}
    @test df2.x2_first isa CategoricalVector{Int}
    @test df2.x3_first isa Vector{Missing}
    @test df2.x4_first isa Vector{Missing}

    @test_throws ArgumentError select(df, [] => (() -> Int[]) => :x1, names(df) .=> last)
    df2 = combine(df, [] => (() -> Int[]) => :x1, names(df) .=> last)
    @test size(df2) == (0, 5)
    @test df2.x1_last isa Vector{Int}
    @test df2.x2_last isa CategoricalVector{Int}
    @test df2.x3_last isa Vector{Int}
    @test df2.x4_last isa CategoricalVector{Int}
end

@testset "copycols special cases" begin
    df = DataFrame(a=1:3, b=4:6)
    c = [7, 8]
    @test_throws ArgumentError select(df, :a => (x -> c) => :c1, :b => (x -> c) => :c2)
    df2 = combine(df, :a => (x -> c) => :c1, :b => (x -> c) => :c2)
    @test df2.c1 === df2.c2
    df2 = select(df, :a => identity => :c1, :a => :c2)
    @test df2.c1 !== df2.c2
    df2 = select(df, :a => identity => :c1)
    @test df2.c1 !== df.a
    df2 = select(df, :a => (x -> df.b) => :c1)
    @test df2.c1 === df.b
    @test_throws ArgumentError select(view(df, 1:2, :), :a => parent => :c1)
    df2 = combine(view(df, 1:2, :), :a => parent => :c1)
    @test df2.c1 !== df.a
    @test_throws ArgumentError select(view(df, 1:2, :), :a => (x -> view(x, 1:1)) => :c1)
    df2 = combine(view(df, 1:2, :), :a => (x -> view(x, 1:1)) => :c1)
    @test df2.c1 isa Vector
    df2 = select(df, :a, :a => :b, :a => identity => :c, copycols=false)
    @test df2.b === df2.c === df.a
    a = df.a
    select!(df, :a, :a => :b, :a => identity => :c)
    @test df.b === df.c === a
end

@testset "empty select" begin
    df_ref = DataFrame(rand(10, 4), :auto)

    for df in (df_ref, view(df_ref, 1:9, 1:3))
        @test ncol(select(df)) == 0
        @test ncol(select(df, copycols=false)) == 0
    end
    select!(df_ref)
    @test ncol(df_ref) == 0
end

@testset "transform and transform!" begin
    df = DataFrame(rand(10, 4), :auto)

    for dfx in (df, view(df, :, :))
        df2 = transform(dfx, [:x1, :x2] => +, :x2 => :x3)
        @test df2 == select(dfx, :, [:x1, :x2] => +, :x2 => :x3)
        @test df2.x2 == df2.x3
        @test df2.x2 !== df2.x3
        @test dfx.x2 == df2.x3
        @test dfx.x2 !== df2.x3
        @test dfx.x2 !== df2.x2
    end

    df2 = transform(df, [:x1, :x2] => +, :x2 => :x3, copycols=false)
    @test df2 == select(df, :, [:x1, :x2] => +, :x2 => :x3)
    @test df.x2 === df2.x2 === df2.x3
    @test_throws ArgumentError transform(view(df, :, :), [:x1, :x2] => +, :x2 => :x3, copycols=false)

    x2 = df.x2
    transform!(df, [:x1, :x2] => +, :x2 => :x3)
    @test df == df2
    @test x2 === df.x2 === df.x3

    @test transform(df) == df
    df2 = transform(df, copycols=false)
    @test df2 == df
    for (a, b) in zip(eachcol(df), eachcol(df2))
        @test a === b
    end
    cols = collect(eachcol(df))
    transform!(df)
    @test df2 == df
    for (a, b) in zip(eachcol(df), cols)
        @test a === b
    end
end

@testset "vectors of pairs" begin
    df_ref = DataFrame(a=1:3, b=4:6)
    for df in [df_ref, view(df_ref, :, :)]
        @test select(df, [] .=> sum) == DataFrame()
        @test select(df, names(df) .=> sum) == repeat(DataFrame(a_sum=6, b_sum=15), nrow(df))
        @test combine(df, names(df) .=> sum) == DataFrame(a_sum=6, b_sum=15)
        @test transform(df, names(df) .=> ByRow(-)) ==
              DataFrame(:a => 1:3, :b => 4:6,
                        Symbol("a_-") => -1:-1:-3,
                        Symbol("b_-") => -4:-1:-6)
        @test select(df, :a, [] .=> sum, :b => :x, [:b, :a] .=> identity) ==
              DataFrame(a=1:3, x=4:6, b_identity=4:6, a_identity=1:3)
        @test select(df, names(df) .=> sum .=> [:A, :B]) == repeat(DataFrame(A=6, B=15), nrow(df))
        @test combine(df, names(df) .=> sum .=> [:A, :B]) == DataFrame(A=6, B=15)
        @test Base.broadcastable(ByRow(+)) isa Base.RefValue{ByRow{typeof(+)}}
        @test identity.(ByRow(+)) == ByRow(+)
    end
end

@testset "AsTable tests" begin
    df = DataFrame(a=1:3, b=4:6, c=7:9)
    @test select(df, AsTable(:) => sum) ==
          DataFrame(a_b_c_sum=map(sum, eachrow(df)))
    @test transform(df, AsTable(:) => sum) ==
          DataFrame(a=1:3, b=4:6, c=7:9, a_b_c_sum=map(sum, eachrow(df)))

    @test select(df, AsTable(:) => sum ∘ sum) ==
          repeat(DataFrame(a_b_c_sum_sum=45), nrow(df))
    @test combine(df, AsTable(:) => sum ∘ sum) ==
          DataFrame(a_b_c_sum_sum=45)
    @test transform(df, AsTable(:) => sum ∘ sum) ==
          DataFrame(a=1:3, b=4:6, c=7:9, a_b_c_sum_sum=45)

    @test select(df, AsTable(:) => ByRow(x -> [x])) ==
          DataFrame(a_b_c_function=[[(a = 1, b = 4, c = 7)],
                                    [(a = 2, b = 5, c = 8)],
                                    [(a = 3, b = 6, c = 9)]])
    @test transform(df, AsTable(:) => ByRow(x -> [x])) ==
          hcat(df, DataFrame(a_b_c_function=[[(a = 1, b = 4, c = 7)],
                                             [(a = 2, b = 5, c = 8)],
                                             [(a = 3, b = 6, c = 9)]]))
    @test select(df, AsTable(:) => ByRow(identity)) ==
          DataFrame(a_b_c_identity=[(a = 1, b = 4, c = 7), (a = 2, b = 5, c = 8), (a = 3, b = 6, c = 9)])
    @test select(df, AsTable(:) => ByRow(identity) => AsTable) == df
    @test select(df, AsTable(:) => ByRow(x -> df[1, :])) ==
          DataFrame(a_b_c_function=fill(df[1, :], 3))
    @test select(df, AsTable(:) => ByRow(x -> df[1, :]) => AsTable) ==
          DataFrame(a=[1, 1, 1], b=4, c=7)
    @test transform(df, AsTable(Not(:)) =>
          ByRow(identity)) == [df DataFrame(:identity => fill(NamedTuple(), nrow(df)))]

    @test select(df, AsTable(Not(:)) => Ref) == repeat(DataFrame(Ref = NamedTuple()), nrow(df))
    @test combine(df, AsTable(Not(:)) => Ref) == DataFrame(Ref = NamedTuple())
    @test transform(df, AsTable(Not(:)) => Ref) ==
          DataFrame(a=1:3, b=4:6, c=7:9, Ref=NamedTuple())

    if VERSION >= v"1.4.0"
        df = DataFrame(x=[1, 2, missing], y=[1, missing, missing])
        @test transform(df, AsTable(:) .=>
                            ByRow.([sum∘skipmissing,
                                    x -> count(!ismissing, x),
                                    mean∘skipmissing]) .=>
                            [:sum, :n, :mean]) ≅
              [df DataFrame(sum=[2, 2, 0], n=[2, 1, 0], mean=[1, 2, NaN])]
    else
        df = DataFrame(x=[1, 2], y=[1, missing])
        @test transform(df, AsTable(:) .=>
                            ByRow.([sum∘skipmissing,
                                    x -> count(!ismissing, x),
                                    mean∘skipmissing]) .=>
                            [:sum, :n, :mean]) ≅
              [df DataFrame(sum=[2, 2], n=[2, 1], mean=[1, 2])]
    end
end

@testset "make sure select! is safe on error" begin
    a = [1]
    df = DataFrame()
    df.a = a
    @test_throws DomainError select!(df, :a => x -> sqrt(-1))
    @test df.a === a
    @test propertynames(df) == [:a, ]
end

@testset "combine AbstractDataFrame" begin
    df = DataFrame(x=1:3, y=4:6)

    @test combine(x -> Matrix(x), df) == rename(df, [:x1, :x2])
    @test combine(x -> Ref(1:3), df) == DataFrame(x1=[1:3])
    @test combine(df, x -> Ref(1:3)) == DataFrame(x1=[1:3])

    @test_throws ArgumentError combine(df, AsTable(:) => identity)
    @test combine(df, AsTable(:) => identity => AsTable) == df
    @test combine(df, (:) => cor) == DataFrame(x_y_cor = 1.0)
    @test combine(df, :x => x -> Ref(1:3)) == DataFrame(x_function=[1:3])
    @test_throws ArgumentError combine(df, :x => x -> ones(1, 1))
    @test combine(df, :x => (x -> ones(1, 1)) => AsTable) == DataFrame(x1=1.0)

    df2 = combine(df, :x => identity)
    @test df2[:, 1] == df.x
    @test df2[:, 1] !== df.x

    @test combine(df, :x => sum, :y => collect ∘ extrema) ==
          DataFrame(x_sum=[6, 6], y_collect_extrema = [4, 6])
    @test combine(df, :y => collect ∘ extrema, :x => sum) ==
          DataFrame(y_collect_extrema = [4, 6], x_sum=[6, 6])
    @test combine(df, :x => sum, :y => x -> []) ==
          DataFrame(x_sum=[], y_function = [])
    @test combine(df, :y => x -> [], :x => sum) ==
          DataFrame(y_function = [], x_sum=[])

    dfv = view(df, [2, 1], [2, 1])

    @test combine(x -> Matrix(x), dfv) == rename(dfv, [:x1, :x2])

    @test_throws ArgumentError combine(dfv, AsTable(:) => identity)
    @test combine(dfv, AsTable(:) => identity => AsTable) == dfv
    @test combine(dfv, (:) => cor) == DataFrame(y_x_cor = 1.0)

    df2 = combine(dfv, :x => identity)
    @test df2[:, 1] == dfv.x
    @test df2[:, 1] !== dfv.x

    @test combine(dfv, :x => sum, :y => collect ∘ extrema) ==
          DataFrame(x_sum=[3, 3], y_collect_extrema = [4, 5])
    @test combine(dfv, :y => collect ∘ extrema, :x => sum) ==
          DataFrame(y_collect_extrema = [4, 5], x_sum=[3, 3])
end

@testset "select and transform AbstractDataFrame" begin
    df = DataFrame(x=1:3, y=4:6)

    @test select(df) == DataFrame()

    @test select(df, :x => first) == DataFrame(x_first=fill(1, 3))
    df2 = select(df, :x, :x => first, copycols=true)
    @test df2 == DataFrame(x=df.x, x_first=fill(1, 3))
    @test df2.x !== df.x
    df2 = select(df, :x, :x => first, copycols=false)
    @test df2 == DataFrame(x=df.x, x_first=fill(1, 3))
    @test df2.x === df.x
    @test_throws ArgumentError select(df, :x => x -> [first(x)], copycols=true)
    @test_throws ArgumentError select(df, :x => x -> [first(x)], copycols=false)

    df2 = transform(df, :x => first, copycols=true)
    @test df2 == [df DataFrame(x_first=fill(1, 3))]
    @test df2.x !== df.x
    @test df2.y !== df.y
    df2 = transform(df, :x => first, copycols=false)
    @test df2 == [df DataFrame(x_first=fill(1, 3))]
    @test df2.x === df.x
    @test df2.y === df.y
    @test transform(df, names(df) .=> first .=> names(df)) ==
          DataFrame(x=fill(1, 3), y=fill(4, 3))
    @test_throws ArgumentError transform(df, :x => x -> [first(x)], copycols=true)
    @test_throws ArgumentError transform(df, :x => x -> [first(x)], copycols=false)

    dfv = view(df, [2, 1], [2, 1])
    @test select(dfv, :x => first) == DataFrame(x_first=fill(2, 2))
    df2 = select(dfv, :x, :x => first, copycols=true)
    @test df2 == DataFrame(x=dfv.x, x_first=fill(2, 2))
    @test df2.x !== dfv.x
    @test_throws ArgumentError select(dfv, :x, :x => first, copycols=false)
    @test_throws ArgumentError select(dfv, :x => x -> [first(x)], copycols=true)
    @test_throws ArgumentError select(dfv, :x => x -> [first(x)], copycols=false)

    df2 = transform(dfv, :x => first, copycols=true)
    @test df2 == [dfv DataFrame(x_first=fill(2, 2))]
    @test df2.x !== dfv.x
    @test df2.y !== dfv.y
    @test_throws ArgumentError transform(dfv, :x => first, copycols=false)
    @test transform(dfv, names(dfv) .=> first .=> names(dfv)) ==
          DataFrame(y=fill(5, 2), x=fill(2, 2))
    @test_throws ArgumentError transform(df, :x => x -> [first(x)], copycols=true)
    @test_throws ArgumentError transform(df, :x => x -> [first(x)], copycols=false)
end

@testset "select! and transform! AbstractDataFrame" begin
    df = DataFrame(x=1:3, y=4:6)
    select!(df, :x => first)
    @test df == DataFrame(x_first = fill(1, 3))

    # if we select! we do copycols=false, so we can get aliases
    df = DataFrame(x=1:3, y=4:6)
    x = df.x
    select!(df, :x => (x->x), :x)
    @test x === df.x_function === df.x

    df = DataFrame(x=1:3, y=4:6)
    @test_throws ArgumentError select!(df, :x => x -> [1])
    @test df == DataFrame(x=1:3, y=4:6)

    df = DataFrame(x=1:3, y=4:6)
    x = df.x
    y = df.y
    transform!(df, :x => first)
    @test df == DataFrame(x=x, y=y, x_first=fill(1, 3))
    @test df.x == x
    @test df.y == y

    df = DataFrame(x=1:3, y=4:6)
    transform!(df, names(df) .=> first .=> names(df))
    @test df == DataFrame(x=fill(1, 3), y=fill(4, 3))

    df = DataFrame(x=1:3, y=4:6)
    @test_throws ArgumentError transform!(df, :x => x -> [1])
    @test df == DataFrame(x=1:3, y=4:6)

    dfv = view(df, [2, 1], [2, 1])
    @test_throws MethodError select!(dfv, 1)
    @test_throws MethodError transform!(dfv, 1)
end

@testset "renamecols=false tests" begin
    df = DataFrame(a=1:3, b=4:6, c=7:9, d=10:12)
    @test select(df, :a => +, [:a, :b] => +, All() => +, renamecols=false) ==
          DataFrame(a=1:3, a_b=5:2:9, a_b_etc=22:4:30)
    @test select(df, :a => +, [:a, :b] => +, Cols(:) => +, renamecols=false) ==
          DataFrame(a=1:3, a_b=5:2:9, a_b_etc=22:4:30)
    @test_throws ArgumentError select(df, [] => () -> 10, renamecols=false)
    @test transform(df, :a => +, [:a, :b] => +, All() => +, renamecols=false) ==
          DataFrame(a=1:3, b=4:6, c=7:9, d=10:12, a_b=5:2:9, a_b_etc=22:4:30)
    @test combine(df, :a => +, [:a, :b] => +, All() => +, renamecols=false) ==
          DataFrame(a=1:3, a_b=5:2:9, a_b_etc=22:4:30)
    @test combine(df, [:a, :b] => +, renamecols=false) == DataFrame(a_b=5:2:9)
    @test combine(identity, df, renamecols=false) == df

    df = DataFrame(a=1:3, b=4:6, c=7:9, d=10:12)
    @test select!(df, :a => +, [:a, :b] => +, All() => +, renamecols=false) == df
    @test df == DataFrame(a=1:3, a_b=5:2:9, a_b_etc=22:4:30)

    df = DataFrame(a=1:3, b=4:6, c=7:9, d=10:12)
    @test transform!(df, :a => +, [:a, :b] => +, All() => +, renamecols=false) == df
    @test df == DataFrame(a=1:3, b=4:6, c=7:9, d=10:12, a_b=5:2:9, a_b_etc=22:4:30)

    df = DataFrame(a=1:3, b=4:6, c=7:9, d=10:12)
    @test transform!(df, :a => +, [:a, :b] => +, Cols(:) => +, renamecols=false) == df
    @test df == DataFrame(a=1:3, b=4:6, c=7:9, d=10:12, a_b=5:2:9, a_b_etc=22:4:30)
end

@testset "transformation function with a function as first argument" begin
    for df in (DataFrame(a=1:2, b=3:4, c=5:6), view(DataFrame(a=1:3, b=3:5, c=5:7, d=11:13), 1:2, 1:3))
        @test select(sdf -> sdf.b, df) == DataFrame(x1=3:4)
        @test select(sdf -> (b = 2sdf.b,), df) == DataFrame(b=[6, 8])
        @test select(sdf -> (b = 1,), df) == DataFrame(b=[1, 1])
        @test_throws ArgumentError select(sdf -> (b = [1],), df)
        @test select(sdf -> (b = [1, 5],), df) == DataFrame(b=[1, 5])
        @test select(sdf -> 1, df) == DataFrame(x1=[1, 1])
        @test select(sdf -> fill([1]), df) == DataFrame(x1=[[1], [1]])
        @test select(sdf -> Ref([1]), df) == DataFrame(x1=[[1], [1]])
        @test select(sdf -> "x", df) == DataFrame(x1=["x", "x"])
        @test select(sdf -> [[1, 2], [3, 4]], df) == DataFrame(x1=[[1, 2], [3, 4]])
        for ret in (DataFrame(), NamedTuple(), zeros(0, 0), DataFrame(t=1)[1, 1:0])
            @test select(sdf -> ret, df) == DataFrame()
        end
        @test_throws ArgumentError select(sdf -> DataFrame(a=10), df)
        @test_throws ArgumentError select(sdf -> zeros(1, 2), df)
        @test select(sdf -> DataFrame(a=[10, 11]), df) == DataFrame(a=[10, 11])
        @test select(sdf -> [10 11; 12 13], df) == DataFrame(x1=[10, 12], x2=[11, 13])
        @test select(sdf -> DataFrame(a=10)[1, :], df) == DataFrame(a=[10, 10])

        @test transform(sdf -> sdf.b, df) == [df DataFrame(x1=3:4)]
        @test transform(sdf -> (b = 2sdf.b,), df) == DataFrame(a=1:2, b=[6, 8], c=5:6)
        @test transform(sdf -> (b = 1,), df) == DataFrame(a=[1, 2], b=[1, 1], c=[5, 6])
        @test_throws ArgumentError transform(sdf -> (b = [1],), df)
        @test transform(sdf -> (b = [1, 5],), df) == DataFrame(a=[1, 2], b=[1, 5], c=[5, 6])
        @test transform(sdf -> 1, df) == DataFrame(a=1:2, b=3:4, c=5:6, x1=1)
        @test transform(sdf -> fill([1]), df) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[[1], [1]])
        @test transform(sdf -> Ref([1]), df) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[[1], [1]])
        @test transform(sdf -> "x", df) == DataFrame(a=1:2, b=3:4, c=5:6, x1="x")
        @test transform(sdf -> [[1, 2], [3, 4]], df) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[[1, 2], [3, 4]])
        for ret in (DataFrame(), NamedTuple(), zeros(0, 0), DataFrame(t=1)[1, 1:0])
            @test transform(sdf -> ret, df) == df
        end
        @test_throws ArgumentError transform(sdf -> DataFrame(a=10), df)
        @test_throws ArgumentError transform(sdf -> zeros(1, 2), df)
        @test transform(sdf -> DataFrame(a=[10, 11]), df) == DataFrame(a=[10, 11], b=3:4, c=5:6)
        @test transform(sdf -> [10 11; 12 13], df) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[10, 12], x2=[11, 13])
        @test transform(sdf -> DataFrame(a=10)[1, :], df) == DataFrame(a=[10, 10], b=3:4, c=5:6)

        @test combine(sdf -> sdf.b, df) == DataFrame(x1=3:4)
        @test combine(sdf -> (b = 2sdf.b,), df) == DataFrame(b=[6, 8])
        @test combine(sdf -> (b = 1,), df) == DataFrame(b=[1])
        @test combine(sdf -> (b = [1],), df) == DataFrame(b=[1])
        @test combine(sdf -> (b = [1, 5],), df) == DataFrame(b=[1, 5])
        @test combine(sdf -> 1, df) == DataFrame(x1=[1])
        @test combine(sdf -> fill([1]), df) == DataFrame(x1=[[1]])
        @test combine(sdf -> Ref([1]), df) == DataFrame(x1=[[1]])
        @test combine(sdf -> "x", df) == DataFrame(x1=["x"])
        @test combine(sdf -> [[1, 2], [3, 4]], df) == DataFrame(x1=[[1, 2], [3, 4]])
        for ret in (DataFrame(), NamedTuple(), zeros(0, 0), DataFrame(t=1)[1, 1:0])
            @test combine(sdf -> ret, df) == DataFrame()
        end
        @test combine(sdf -> DataFrame(a=10), df) == DataFrame(a=10)
        @test combine(sdf -> zeros(1, 2), df) == DataFrame(x1=0, x2=0)
        @test combine(sdf -> DataFrame(a=[10, 11]), df) == DataFrame(a=[10, 11])
        @test combine(sdf -> [10 11; 12 13], df) == DataFrame(x1=[10, 12], x2=[11, 13])
        @test combine(sdf -> DataFrame(a=10)[1, :], df) == DataFrame(a=[10])
    end

    df = DataFrame(a=1:2, b=3:4, c=5:6)
    @test select!(sdf -> sdf.b, copy(df)) == DataFrame(x1=3:4)
    @test select!(sdf -> (b = 2sdf.b,), copy(df)) == DataFrame(b=[6, 8])
    @test select!(sdf -> (b = 1,), copy(df)) == DataFrame(b=[1, 1])
    @test_throws ArgumentError select!(sdf -> (b = [1],), copy(df))
    @test select!(sdf -> (b = [1, 5],), copy(df)) == DataFrame(b=[1, 5])
    @test select!(sdf -> 1, copy(df)) == DataFrame(x1=[1, 1])
    @test select!(sdf -> fill([1]), copy(df)) == DataFrame(x1=[[1], [1]])
    @test select!(sdf -> Ref([1]), copy(df)) == DataFrame(x1=[[1], [1]])
    @test select!(sdf -> "x", copy(df)) == DataFrame(x1=["x", "x"])
    @test select!(sdf -> [[1, 2], [3, 4]], copy(df)) == DataFrame(x1=[[1, 2], [3, 4]])
    for ret in (DataFrame(), NamedTuple(), zeros(0, 0), DataFrame(t=1)[1, 1:0])
        @test select!(sdf -> ret, copy(df)) == DataFrame()
    end
    @test_throws ArgumentError select!(sdf -> DataFrame(a=10), copy(df))
    @test_throws ArgumentError select!(sdf -> zeros(1, 2), copy(df))
    @test select!(sdf -> DataFrame(a=[10, 11]), copy(df)) == DataFrame(a=[10, 11])
    @test select!(sdf -> [10 11; 12 13], copy(df)) == DataFrame(x1=[10, 12], x2=[11, 13])
    @test select!(sdf -> DataFrame(a=10)[1, :], copy(df)) == DataFrame(a=[10, 10])

    @test transform!(sdf -> sdf.b, copy(df)) == [df DataFrame(x1=3:4)]
    @test transform!(sdf -> (b = 2sdf.b,), copy(df)) == DataFrame(a=1:2, b=[6, 8], c=5:6)
    @test transform!(sdf -> (b = 1,), copy(df)) == DataFrame(a=[1, 2], b=[1, 1], c=[5, 6])
    @test_throws ArgumentError transform!(sdf -> (b = [1],), copy(df))
    @test transform!(sdf -> (b = [1, 5],), copy(df)) == DataFrame(a=[1, 2], b=[1, 5], c=[5, 6])
    @test transform!(sdf -> 1, copy(df)) == DataFrame(a=1:2, b=3:4, c=5:6, x1=1)
    @test transform!(sdf -> fill([1]), copy(df)) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[[1], [1]])
    @test transform!(sdf -> Ref([1]), copy(df)) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[[1], [1]])
    @test transform!(sdf -> "x", copy(df)) == DataFrame(a=1:2, b=3:4, c=5:6, x1="x")
    @test transform!(sdf -> [[1, 2], [3, 4]], copy(df)) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[[1, 2], [3, 4]])
    for ret in (DataFrame(), NamedTuple(), zeros(0, 0), DataFrame(t=1)[1, 1:0])
        @test transform!(sdf -> ret, copy(df)) == df
    end
    @test_throws ArgumentError transform!(sdf -> DataFrame(a=10), copy(df))
    @test_throws ArgumentError transform!(sdf -> zeros(1, 2), copy(df))
    @test transform!(sdf -> DataFrame(a=[10, 11]), copy(df)) == DataFrame(a=[10, 11], b=3:4, c=5:6)
    @test transform!(sdf -> [10 11; 12 13], copy(df)) == DataFrame(a=1:2, b=3:4, c=5:6, x1=[10, 12], x2=[11, 13])
    @test transform!(sdf -> DataFrame(a=10)[1, :], copy(df)) == DataFrame(a=[10, 10], b=3:4, c=5:6)

    @test_throws ArgumentError combine(:x => identity, DataFrame(x=[1, 2, 3]))
end

@testset "transformation function with multiple columns as destination" begin
    for df in (DataFrame(a=1:2, b=3:4, c=5:6), view(DataFrame(a=1:3, b=3:5, c=5:7, d=11:13), 1:2, 1:3))
        for fun in (select, combine, transform),
            res in (DataFrame(), DataFrame(a=1, b=2)[1, :], ones(1, 1),
                    (a=1, b=2), (a=[1], b=[2]), (a=1, b=[2]))
            @test_throws ArgumentError fun(df, :a => x -> res)
            @test_throws ArgumentError fun(df, :a => (x -> res) => :z)
        end
        for res in (DataFrame(x1=1, x2=2)[1, :], (x1=1, x2=2))
            @test select(df, :a => (x -> res) => AsTable) == DataFrame(x1=[1, 1], x2=[2, 2])
            @test transform(df, :a => (x -> res) => AsTable) == [df DataFrame(x1=[1, 1], x2=[2, 2])]
            @test combine(df, :a => (x -> res) => AsTable) == DataFrame(x1=[1], x2=[2])
            @test select(df, :a => (x -> res) => [:p, :q]) == DataFrame(p=[1, 1], q=[2, 2])
            @test transform(df, :a => (x -> res) => [:p, :q]) == [df DataFrame(p=[1, 1], q=[2, 2])]
            @test combine(df, :a => (x -> res) => [:p, :q]) == DataFrame(p=[1], q=[2])
            @test_throws ArgumentError select(df, :a => (x -> res) => [:p, :q, :r])
            @test_throws ArgumentError select(df, :a => (x -> res) => [:p])
        end
        for res in (DataFrame(x1=1, x2=2), [1 2], Tables.table([1 2], header=[:x1, :x2]),
                    (x1=[1], x2=[2]))
            @test combine(df, :a => (x -> res) => AsTable) == DataFrame(x1=1, x2=2)
            @test combine(df, :a => (x -> res) => [:p, :q]) == DataFrame(p=1, q=2)
            @test_throws ArgumentError combine(df, :a => (x -> res) => [:p])
            @test_throws ArgumentError select(df, :a => (x -> res) => AsTable)
            @test_throws ArgumentError transform(df, :a => (x -> res) => AsTable)
        end
        @test combine(df, :a => ByRow(x -> [x, x+1]),
                      :a => ByRow(x -> [x, x+1]) => AsTable,
                      :a => ByRow(x -> [x, x+1]) => [:p, :q],
                      :a => ByRow(x -> (s=x, t=x+1)) => AsTable,
                      :a => (x -> (k=x, l=x.+1)) => AsTable,
                      :a => ByRow(x -> (s=x, t=x+1)) => :z) ==
              DataFrame(a_function=[[1, 2], [2, 3]], x1=[1, 2], x2=[2, 3],
                        p=[1, 2], q=[2, 3], s=[1, 2], t=[2, 3], k=[1, 2], l=[2, 3],
                        z=[(s=1, t=2), (s=2, t=3)])
        @test select(df, :a => ByRow(x -> [x, x+1]),
                     :a => ByRow(x -> [x, x+1]) => AsTable,
                     :a => ByRow(x -> [x, x+1]) => [:p, :q],
                     :a => ByRow(x -> (s=x, t=x+1)) => AsTable,
                     :a => (x -> (k=x, l=x.+1)) => AsTable,
                     :a => ByRow(x -> (s=x, t=x+1)) => :z) ==
              DataFrame(a_function=[[1, 2], [2, 3]], x1=[1, 2], x2=[2, 3],
                        p=[1, 2], q=[2, 3], s=[1, 2], t=[2, 3], k=[1, 2], l=[2, 3],
                        z=[(s=1, t=2), (s=2, t=3)])
        @test transform(df, :a => ByRow(x -> [x, x+1]),
                        :a => ByRow(x -> [x, x+1]) => AsTable,
                        :a => ByRow(x -> [x, x+1]) => [:p, :q],
                        :a => ByRow(x -> (s=x, t=x+1)) => AsTable,
                        :a => (x -> (k=x, l=x.+1)) => AsTable,
                        :a => ByRow(x -> (s=x, t=x+1)) => :z) ==
              [df DataFrame(a_function=[[1, 2], [2, 3]], x1=[1, 2], x2=[2, 3],
                            p=[1, 2], q=[2, 3], s=[1, 2], t=[2, 3], k=[1, 2], l=[2, 3],
                            z=[(s=1, t=2), (s=2, t=3)])]
        @test_throws ArgumentError select(df, :a => (x -> [(a=1, b=2), (a=1, b=2, c=3)]) => AsTable)
        @test_throws ArgumentError select(df, :a => (x -> [(a=1, b=2), (a=1, c=3)]) => AsTable)
        @test_throws ArgumentError combine(df, :a => (x -> (a=1, b=2)) => :x)
    end
end

@testset "check correctness of duplicate column names" begin
    for df in (DataFrame(a=1:2, b=3:4, c=5:6), view(DataFrame(a=1:3, b=3:5, c=5:7, d=11:13), 1:2, 1:3))
        @test select(df, :b, :) == DataFrame(b=3:4, a=1:2, c=5:6)
        @test select(df, :b => :c, :) == DataFrame(c=3:4, a=1:2, b=3:4)
        @test_throws ArgumentError select(df, :b => [:c, :d], :)
        @test_throws ArgumentError select(df, :a, :a => x -> (a=[1, 2], b=[3, 4]))
        @test_throws ArgumentError select(df, :a, :a => (x -> (a=[1, 2], b=[3, 4])) => AsTable)
        @test select(df, [:b, :a], :a => (x -> (a=[11, 12], b=[13, 14])) => AsTable, :) ==
              DataFrame(b=[13, 14], a=[11, 12], c=[5, 6])
        @test select(df, [:b, :a], :a => (x -> (a=[11, 12], b=[13, 14])) => [:b, :a], :) ==
              DataFrame(b=[11, 12], a=[13, 14], c=[5, 6])
    end
end

@testset "empty ByRow" begin
    df = DataFrame(a=1:3)

    @test select(df, [] => ByRow(() -> 1)) == DataFrame("function" => [1, 1, 1])
    @test combine(df, [] => ByRow(() -> 1)) == DataFrame("function" => [1, 1, 1])
    @test transform(df, [] => ByRow(() -> 1)) == DataFrame("a" => 1:3, "function" => [1, 1, 1])

    for df in (DataFrame(), DataFrame(a=[]))
        @test select(df, [] => ByRow(() -> 1)) == DataFrame("function" => [])
        @test combine(df, [] => ByRow(() -> 1)) == DataFrame("function" => [])
        if ncol(df) == 0
            @test transform(df, [] => ByRow(() -> 1)) == DataFrame("function" => [])
        else
            @test transform(df, [] => ByRow(() -> 1)) == DataFrame("a" => [], "function" => [])
        end
        @test eltype(select(df, [] => ByRow(() -> 1)).function) == Int
        @test eltype(combine(df, [] => ByRow(() -> 1)).function) == Int
        @test eltype(transform(df, [] => ByRow(() -> 1)).function) == Int

        @test isequal_coltyped(select(df, [] => ByRow(() -> (a=1, b="1")) => AsTable),
                               DataFrame(a=Int[], b=String[]))
        @test isequal_coltyped(select(df, [] => ByRow(() -> (a=1, b="1")) => [:p, :q]),
                               DataFrame(p=Int[], q=String[]))

        # here this follows Tables.jl behavior
        @test select(df, [] => ByRow(() -> [1, "1"]) => AsTable) == DataFrame()
        @test_throws ArgumentError select(df, [] => ByRow(() -> [1, "1"]) => [:p, :q])
        @test isequal_coltyped(select(df, [] => ByRow(() -> (1, "1")) => AsTable),
                               DataFrame(Column1=Int[], Column2=String[]))
        @test isequal_coltyped(select(df, [] => ByRow(() -> (1, "1")) => [:p, :q]),
                               DataFrame(p=Int[], q=String[]))
    end

    @test select(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("function" => [1, 1, 1])
    @test combine(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("function" => [1, 1, 1])
    @test transform(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("a" => 1:3, "function" => [1, 1, 1])

    for df in (DataFrame(), DataFrame(a=[]))
        @test select(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("function" => [])
        @test combine(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("function" => [])
        if ncol(df) == 0
            @test transform(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("function" => [])
        else
            @test transform(df, AsTable([]) => ByRow(x -> 1)) == DataFrame("a" => [], "function" => [])
        end
        @test eltype(select(df, AsTable([]) => ByRow(x -> 1)).function) == Int
        @test eltype(combine(df, AsTable([]) => ByRow(x -> 1)).function) == Int
        @test eltype(transform(df, AsTable([]) => ByRow(x -> 1)).function) == Int

        @test isequal_coltyped(select(df, AsTable([]) => ByRow(x -> (a=1, b="1")) => AsTable),
                               DataFrame(a=Int[], b=String[]))
        @test isequal_coltyped(select(df, AsTable([]) => ByRow(x -> (a=1, b="1")) => [:p, :q]),
                               DataFrame(p=Int[], q=String[]))

        # here this follows Tables.jl behavior
        @test select(df, [] => ByRow(() -> [1, "1"]) => AsTable) == DataFrame()
        @test_throws ArgumentError select(df, [] => ByRow(() -> [1, "1"]) => [:p, :q])
        @test isequal_coltyped(select(df, [] => ByRow(() -> (1, "1")) => AsTable),
                               DataFrame(Column1=Int[], Column2=String[]))
        @test isequal_coltyped(select(df, [] => ByRow(() -> (1, "1")) => [:p, :q]),
                               DataFrame(p=Int[], q=String[]))
    end
end

end # module


text:
"""
    mapclimgrid(PI::PlotInfo)

Empty map generator.
"""
function mapclimgrid(PI)

    resolution = "auto"

    fig, ax = subplots()

    if lowercase(PI.region) == "canada" || lowercase(PI.region) == "ca"

        central_lon = -90
        central_lat = 50.0
        extent = [-130, -55, 35, 80]
        projection=cartopy.crs.LambertConformal(central_longitude=central_lon, central_latitude=central_lat)

    elseif lowercase(PI.region) == "usa"
        central_lon = -95
        central_lat = 20.0
        extent = [-120, -70, 22, 51]
        projection=cartopy.crs.AlbersEqualArea(central_longitude=central_lon, central_latitude=central_lat)

    elseif lowercase(PI.region) == "asia"
        central_lon = 100
        central_lat = 20.0
        extent = [70, 130, 0, 50]
        projection=cartopy.crs.LambertConformal(central_longitude=central_lon, central_latitude=central_lat)

    # elseif lowercase(region) == "west-asia" || lowercase(region) == "was"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection = "eqdc", resolution = "l", llcrnrlon=5.0, llcrnrlat=-30.0, urcrnrlon=145.0, urcrnrlat=50.0, lat_0=5.0, lon_0=65,  rsphere=(6378137.00, 6356752.3142))

    elseif lowercase(PI.region) == "quebec" || lowercase(PI.region) == "qc"
        central_lon = -75
        central_lat = 50.0
        extent = [-81.5, -52.6, 41, 62.5]
        projection=cartopy.crs.LambertConformal(central_longitude=central_lon, central_latitude=central_lat)
        resolution = "50m"

    elseif lowercase(PI.region) == "mauricie"
        central_lon = -73
        central_lat = 47.0
        extent = [-76.0, -71.6, 46.1, 49.3]
        projection=cartopy.crs.LambertConformal(central_longitude=central_lon, central_latitude=central_lat)
        resolution = "10m"

    # elseif lowercase(region) == "americas" || lowercase(region) == "ams"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection = "omerc", resolution = "c", width=14000000, height=17000000, lon_0 = -100, lat_0 =    15, lon_1 = -45, lon_2 = -120, lat_1 = -55, lat_2 = 70)

    elseif lowercase(PI.region) == "arctic" || lowercase(PI.region) == "aps"
        extent = [-180, 180, 60, 90]
        proj_extent = cartopy.crs.PlateCarree()
        projection=cartopy.crs.NorthPolarStereo()

    elseif lowercase(PI.region) == "antarctic" || lowercase(PI.region) == "aaps"
        extent = [-180, 180, -90, -60]
        proj_extent = cartopy.crs.PlateCarree()
        projection=cartopy.crs.SouthPolarStereo()

    elseif lowercase(PI.region) == "europe" || lowercase(PI.region) == "eu"
        central_lon = 10.0
        central_lat = 50.0
        extent = [-12, 40, 30, 75]
        projection=cartopy.crs.LambertConformal(central_longitude=central_lon, central_latitude=central_lat)

    elseif lowercase(PI.region) == "africa" || lowercase(PI.region) == "afr"

        central_lon = 12.0
        central_lat = -30.0
        extent = [-20, 55, -32, 36]
        projection=cartopy.crs.PlateCarree()

    elseif lowercase(PI.region) == "northamerica" || lowercase(PI.region) == "na"
        central_lon = -95.0
        central_lat = 50.0
        extent = [-135.5, -55, 10, 80]
        projection=cartopy.crs.LambertConformal(central_longitude=central_lon, central_latitude=central_lat)

    elseif lowercase(PI.region) == "southamerica" || lowercase(PI.region) == "sa"
        central_lon = -60.0
        central_lat = -20.0
        extent = [-100, -30, -60, 17]
        projection=cartopy.crs.Mollweide(central_longitude=central_lon)

    elseif lowercase(PI.region) == "world" || lowercase(PI.region) == "w"
        central_lon = 0.0
        central_lat = 0.0
        extent = [-180, 180, -90, 90]
        projection=cartopy.crs.PlateCarree(central_longitude=central_lon)

    elseif lowercase(PI.region) == "mollweide" || lowercase(PI.region) == "moll"

        projection=cartopy.crs.Mollweide()

    elseif lowercase(PI.region) == "worldeck1" || lowercase(PI.region) == "weck1" || lowercase(PI.region) == "eck1" || lowercase(PI.region) == "eckert1"

        projection=cartopy.crs.EckertI()

    elseif lowercase(PI.region) == "worldeck2" || lowercase(PI.region) == "weck2" || lowercase(PI.region) == "eck2" || lowercase(PI.region) == "eckert2"

        projection=cartopy.crs.EckertII()

    elseif lowercase(PI.region) == "worldeck3" || lowercase(PI.region) == "weck3" || lowercase(PI.region) == "eck3" || lowercase(PI.region) == "eckert3"

        projection=cartopy.crs.EckertIII()

    elseif lowercase(PI.region) == "worldeck4" || lowercase(PI.region) == "weck4" || lowercase(PI.region) == "eck4" || lowercase(PI.region) == "eckert4"

        projection=cartopy.crs.EckertIV()

    elseif lowercase(PI.region) == "worldeck5" || lowercase(PI.region) == "weck5" || lowercase(PI.region) == "eck5" || lowercase(PI.region) == "eckert5"

        projection=cartopy.crs.EckertV()

    elseif lowercase(PI.region) == "worldeck6" || lowercase(PI.region) == "weck6" || lowercase(PI.region) == "eck6" || lowercase(PI.region) == "eckert6"

        projection=cartopy.crs.EckertVI()

    # elseif lowercase(region) == "outaouais"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection="lcc", resolution="h", llcrnrlon=-78.5, llcrnrlat=45., urcrnrlon=-73.866, urcrnrlat=48.0, lon_0=-75, lat_1=44, rsphere=(6378137.00, 6356752.3142))

    # elseif lowercase(region) == "south_quebec" || lowercase(region) == "south_qc"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection = "lcc", resolution = "l", llcrnrlon = -75.9, llcrnrlat = 42.6, urcrnrlon = -61.5, urcrnrlat = 49.5, lon_0 = -70, lat_1 = 50, rsphere = (6378137.00, 6356752.3142))

    # elseif lowercase(region) == "quebec_agricole" || lowercase(region) == "qc_agr"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection = "lcc", resolution="l", llcrnrlon=-80, llcrnrlat=44.2, urcrnrlon=-62.5, urcrnrlat=50.5, lon_0=-72, lat_1=50, rsphere = (6378137.00, 6356752.3142))

    # elseif lowercase(region) == "quebecnsp" || lowercase(region) == "qcnsp"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection = "nsper", resolution= "l", satellite_height = 2000000, lon_0 = -72.5, lat_0 = 55)

    # elseif lowercase(region) == "laurentides"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection="lcc", resolution="h", llcrnrlon=-76.5, llcrnrlat=45., urcrnrlon=-72.866, urcrnrlat=48.0, lon_0=-73.5, lat_1=44, rsphere=(6378137.00, 6356752.3142))

    # elseif lowercase(region) == "estrie"
    #     error("Not yet supported by new API")
    #     m = basemap.Basemap(projection="lcc", resolution="h", llcrnrlon=-73.0, llcrnrlat=44.5, urcrnrlon=-70.0, urcrnrlat=46.3, lon_0=-71.0, lat_1=45, rsphere=(6378137.00, 6356752.3142))

    elseif PI.region == "auto"
        central_lon = (PI.BBox.llon + PI.BBox.rlon)/2
        central_lat = (PI.BBox.nlat + PI.BBox.slat)/2
        extent = [PI.BBox.llon, PI.BBox.rlon, PI.BBox.slat, PI.BBox.nlat]
        projection=cartopy.crs.Robinson()

    end

    ax = plt.axes(projection=projection)
    if @isdefined extent
        if @isdefined proj_extent
            ax.set_extent(extent, proj_extent)
        else
            ax.set_extent(extent)
        end
    end

    if PI.states
        ax.add_feature(cartopy.feature.STATES)
    end
    if PI.rivers
        ax.add_feature(cartopy.feature.RIVERS)
    end
    if PI.borders
        ax.add_feature(cartopy.feature.BORDERS)
    end
    if PI.lakes
        ax.add_feature(cartopy.feature.LAKES)
    end

    # Draw the map properties
    ax.gridlines()
    ax.coastlines(resolution=resolution)

    return true, ax, projection

end

ext: jl


text:
#NB: wlav without relaxation virtually never converges not even with rescaler, 3 buses case and linear formulation. It is therefore not tested

@testset "test different estimation criteria" begin

    msr_path = joinpath(BASE_DIR, "test/data/extra/measurements/case3_meas.csv")
    data = _PMD.parse_file(joinpath(BASE_DIR, "test/data/extra/networks/case3_unbalanced.dss"); data_model=MATHEMATICAL)
    _PMDSE.add_measurements!(data, msr_path, actual_meas = true)
    pf_result = _PMD.solve_mc_pf(data, _PMD.ACPUPowerModel, ipopt_solver)

    @testset "Equivalence of WLS-rWLS" begin

        rescaler = 1

        data["se_settings"] = Dict{String,Any}("criterion" => "rwls", "rescaler" => rescaler)
        se_result_rwls = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)

        data["se_settings"] = Dict{String,Any}("criterion" => "wls", "rescaler" => rescaler)
        se_result_wls = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)

        @test isapprox(se_result_rwls["objective"]-se_result_wls["objective"], 0.0; atol = 1e-5)

    end

    @testset "MLE with normal distr - no error" begin

        rescaler = 1

        data["se_settings"] = Dict{String,Any}("criterion" => "rwls", "rescaler" => rescaler)
        se_result_rwls = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)
        delta, max_err, avg = _PMDSE.calculate_voltage_magnitude_error(se_result_rwls, pf_result)

        data["se_settings"] = Dict{String,Any}("criterion" => "mle", "rescaler" => rescaler)
        se_result_mle = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)
        delta, max_err_mle, avg_mle = _PMDSE.calculate_voltage_magnitude_error(se_result_mle, pf_result)

        @test se_result_mle["termination_status"] ∈ [LOCALLY_SOLVED, ALMOST_LOCALLY_SOLVED]
        @test isapprox(se_result_mle["objective"], se_result_rwls["objective"]; atol = 2e-5)
        @test isapprox(abs(max_err-max_err_mle), 0.0; atol = 2e-5)
        @test isapprox(abs(avg-avg_mle), 0.0; atol = 1e-5)
    end

    @testset "Mixed mle/wls criterion - no error" begin

        rescaler = 1

        data["se_settings"] = Dict{String,Any}("criterion" => "rwls", "rescaler" => rescaler)
        se_result_rwls = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)
        delta, max_err, avg = _PMDSE.calculate_voltage_magnitude_error(se_result_rwls, pf_result)

        data["se_settings"] = Dict{String,Any}("rescaler" => rescaler)
        for (m, meas) in data["meas"]
            if meas["var"] ∈ [:pd, :qd]
               _PMDSE.assign_basic_individual_criteria!(data["meas"][m]; chosen_criterion="mle")
           else
               _PMDSE.assign_basic_individual_criteria!(data["meas"][m]; chosen_criterion="rwls")
           end
        end
        se_result_mixed = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)
        delta, max_err_mixed, avg_mixed = _PMDSE.calculate_voltage_magnitude_error(se_result_mixed, pf_result)

        @test se_result_mixed["termination_status"] ∈ [LOCALLY_SOLVED, ALMOST_LOCALLY_SOLVED]
        @test isapprox(se_result_mixed["objective"], se_result_rwls["objective"]; atol = 2e-5)
        @test isapprox(abs(max_err-max_err_mixed), 0.0; atol = 2e-5)
        @test isapprox(abs(avg-avg_mixed), 0.0; atol = 1e-5)
    end

    _PMDSE.add_measurements!(data, msr_path, actual_meas = false)
    pf_result= _PMD.solve_mc_pf(data, _PMD.ACPUPowerModel, ipopt_solver)
    rescaler = 1

    @testset "MLE with normal distr - with error" begin

        data["se_settings"] = Dict{String,Any}("criterion" => "rwls", "rescaler" => rescaler)
        se_result_rwls = _PMDSE.solve_acr_red_mc_se(data, ipopt_solver)
        delta, max_err, avg = _PMDSE.calculate_voltage_magnitude_error(se_result_rwls, pf_result)

        data["se_settings"] = Dict{String,Any}("criterion" => "mle", "rescaler" => rescaler)
        se_result_mle = _PMDSE.solve_acr_red_mc_se(data, ipopt_solver)
        delta, max_err_mle, avg_mle = _PMDSE.calculate_voltage_magnitude_error(se_result_mle, pf_result)

        @test se_result_mle["termination_status"] == LOCALLY_SOLVED
        @test isapprox(abs(max_err-max_err_mle), 0.0; atol = 1e-4)
        @test isapprox(abs(avg-avg_mle), 0.0; atol = 1e-5)
    end

    @testset "Mixed mle/rwlav criterion - with error" begin

        data["se_settings"] = Dict{String,Any}("criterion" => "rwls", "rescaler" => rescaler)
        se_result_rwls = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)
        delta, max_err, avg = _PMDSE.calculate_voltage_magnitude_error(se_result_rwls, pf_result)

        data["se_settings"] = Dict{String,Any}( "rescaler" => rescaler)
        for (m, meas) in data["meas"]
            if meas["var"] ∈ [:pd, :qd]
               _PMDSE.assign_basic_individual_criteria!(data["meas"][m]; chosen_criterion="mle")
           else
               _PMDSE.assign_basic_individual_criteria!(data["meas"][m]; chosen_criterion="rwls")
           end
        end
        se_result_mixed = _PMDSE.solve_acp_red_mc_se(data, ipopt_solver)
        delta, max_err_mixed, avg_mixed = _PMDSE.calculate_voltage_magnitude_error(se_result_mixed, pf_result)

        @test se_result_mixed["termination_status"] == LOCALLY_SOLVED
        @test isapprox(abs(max_err-max_err_mixed), 0.0; atol = 3e-4)
        @test isapprox(abs(avg-avg_mixed), 0.0; atol = 6e-5)
    end
end


text:
## Method definitions for generic eltypes - will make copies

# Direct and transposed algorithms

function ldiv!(A::BandedMatrix{T,Matrix{T}}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    checksquare(A)
    AA, BB = _convert_to_blas_type(A, B)
    ldiv!(lu(AA), BB) # call BlasFloat versions
end

function ldiv!(At::Transpose{T,BandedMatrix{T,Matrix{T}}}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    A = parent(At)
    checksquare(A)
    AA, BB = _convert_to_blas_type(A, B)
    ldiv!(transpose(lu(AA)), BB) # call BlasFloat versions
end

function ldiv!(Ac::Adjoint{T,BandedMatrix{T,Matrix{T}}}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    A = parent(Ac)
    checksquare(A)
    AA, BB = _convert_to_blas_type(A, B)
    ldiv!(adjoint(lu(AA)), BB) # call BlasFloat versions
end

function ldiv!(A::BandedLU{T}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    checksquare(A)
    AA, BB = _convert_to_blas_type(A, B)
    ldiv!(lu(AA), BB) # call BlasFloat versions
end

function ldiv!(At::Transpose{T,BandedLU{T}}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    A = parent(At)
    checksquare(A)
    AA, BB = _convert_to_blas_type(A, B)
    ldiv!(transpose(lu(AA)), BB) # call BlasFloat versions
end

function ldiv!(Ac::Adjoint{T,BandedLU{T}}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    A = parent(Ac)
    checksquare(A)
    AA, BB = _convert_to_blas_type(A, B)
    ldiv!(adjoint(lu(AA)), BB) # call BlasFloat versions
end

# \ is different because it needs a copy, but we have to avoid ambiguity
function \(A::BandedLU{T}, B::VecOrMat{Complex{T}}) where {T<:BlasReal}
    checksquare(A)
    ldiv!(convert(BandedLU{Complex{T}}, A), copy(B)) # goes to BlasFloat call
end
function \(A::BandedLU{T}, B::AbstractVecOrMat{S}) where {T<:Number, S<:Number}
    checksquare(A)
    TS = _promote_to_blas_type(T, S)
    ldiv!(convert(BandedLU{TS}, A), copy_oftype(B, TS)) # goes to BlasFloat call
end

# Method definitions for BlasFloat types - no copies
@eval function ldiv!(A::BandedMatrix{T,Matrix{T}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat}
    checksquare(A)
    ldiv!(lu(A), B)
end
@eval function ldiv!(A::Transpose{T,BandedMatrix{T,Matrix{T}}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat}
    checksquare(A)
    ldiv!(transpose(lu(parent(A))), B)
end
@eval function ldiv!(A::Adjoint{T,BandedMatrix{T,Matrix{T}}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat}
    checksquare(A)
    ldiv!(adjoint(lu(parent(A))), B)
end

# Direct and transposed algorithms
function ldiv!(A::BandedLU{T}, B::AbstractVecOrMat{T}) where {T<:BlasFloat}
    checksquare(A)
    gbtrs!('N', A.m, A.l, A.u, A.data, A.ipiv, B)
end

function ldiv!(At::Transpose{T,BandedLU{T}}, B::AbstractVecOrMat{T}) where {T<:BlasFloat}
    A = parent(At)
    checksquare(A)
    gbtrs!('T', A.m, A.l, A.u, A.data, A.ipiv, B)
end
function ldiv!(Ac::Adjoint{T,BandedLU{T}}, B::AbstractVecOrMat{T}) where {T<:BlasReal}
    A = parent(Ac)
    checksquare(A)
    gbtrs!('T', A.m, A.l, A.u, A.data, A.ipiv, B)
end
function ldiv!(Ac::Adjoint{T,BandedLU{T}}, B::AbstractVecOrMat{T}) where {T<:BlasComplex}
    A = parent(Ac)
    checksquare(A)
    gbtrs!('C', A.m, A.l, A.u, A.data, A.ipiv, B)
end


text:
using Flux
using Zygote: @adjoint
using Statistics
using NNlib

# CPU implementation
"""
  logaddexp(a, b)
Adds log-space `a` and `b` such that the result equals `log(exp(a)+exp(b))`
"""
function logaddexp(a, b)
  isinf(a) && return b
  isinf(b) && return a

  # always want the greater number on the left in the exponentiation;
  # the magnitude difference may end up making the number very positive
  # which will cause exp() to return Inf
  # E.g., a = -900, b = -800, will give exp(-800 - -900), which will be
  # Inf for Float32 values
  if a < b
    a, b = b, a
  end
  return a + log(1+exp(b-a))
end

"""
  add_blanks(z)

Adds blanks to the start and end of `z`, and between items in `z`
"""
function add_blanks(z, blank)
  z′ = fill(blank, 2*length(z) + 1)
  z′[2 .* eachindex(z)] = z
  return z′
end

function ctc_alpha(ŷ::AbstractArray, y)
  typed_zero = zero(ŷ[1])
  ŷ = logsoftmax(ŷ)
  blank = size(ŷ, 1)
  z′ = add_blanks(y, blank)
  T = size(ŷ, 2)
  U′ = length(z′)

  α = fill(log(typed_zero), U′, T)
  α[1,1] = ŷ[blank, 1]
  α[2,1] = ŷ[z′[2], 1]
  for t=2:T
    bound = max(1, U′ - 2(T - t) - 1)
    for u=bound:U′
      if u == 1
        α[u,t] = α[u, t-1]
      else
      α[u,t] = logaddexp(α[u, t-1], α[u-1, t-1])

      # array bounds check and f(u) function from Eq. 7.9
      if u > 2 && !(z′[u] == blank || z′[u-2] == z′[u])
        α[u,t] = logaddexp(α[u,t], α[u-2,t-1])
      end
    end
    α[u,t] += ŷ[z′[u], t]
    end
  end
  return (loss=-1 * logaddexp(α[end,T], α[end-1, T]), alpha=α, zprime=z′, logsoftyhat=ŷ)
end

function ∇ctc_loss(ŷ::AbstractArray, y, out)
  loss, α, z′, ŷ = out
  U′, T = size(α)
  blank = size(ŷ, 1)
  typed_zero = zero(first(α))

  # Calculate beta coefficients, from the bottom-right, to the upper-left
  β = fill(log(typed_zero), U′, T)

  # Fill bottom-right corner so bounding errors can be avoided
  # by starting `u` at `U′-1`
  β[U′, T] = typed_zero
  β[U′-1, T] = typed_zero

  # start at T-1 so that β(T, u) = log(0) for all u < U′ - 1
  for t=(T-1):-1:1
    bound = min(U′, 2t)
    for u=bound:-1:1
      if u == U′
        β[u,t] = ŷ[z′[u], t+1] + β[u, t+1]
      else
        β[u,t] = logaddexp(ŷ[z′[u], t+1] + β[u, t+1], ŷ[z′[u+1], t+1] + β[u+1,t+1])

        # array bounds check and g(u) function from Eq. 7.16
        if u+2 <= U′ && z′[u] != blank && z′[u] != z′[u+2]
          β[u,t] = logaddexp(β[u,t], ŷ[z′[u+2], t+1] + β[u+2, t+1])
        end
      end
    end
  end

  # Accumulate alpha-beta products for each category,
  # then calculate gradients
  accum = fill(log(typed_zero), size(ŷ))
  for t=1:T
    for u=1:U′
      accum[z′[u], t] = logaddexp(accum[z′[u], t], α[u,t] + β[u,t])
    end
  end
  grads = exp.(ŷ) .- exp.(accum .+ loss)
  return grads
end

"""
  ctc_loss(ŷ, y)

Computes the connectionist temporal classification loss between `ŷ`
and `y`.

`ŷ` must be a classes-by-time matrices, i.e., each row
represents a class and each column represents a time step.
Additionally, the `logsoftmax` function will be applied to `ŷ`, so
`ŷ` must be the raw activation values from the neural network and
not, for example, the activations after being passed through a
`softmax` activation function. `y` must be a 1D array of the labels
associated with `ŷ`. The blank label is assumed to be the last label
category in `ŷ`, so it is equivalent to `size(ŷ, 1)`.

Used for sequence-to-sequence classification problems such as
speech recognition and handwriting recognition where the exact
time-alignment of the output (e.g., letters) is not needed to
solve the problem. See [Graves et al. (2006)](https://www.cs.toronto.edu/~graves/icml_2006.pdf)
or [Graves (2012)](https://www.cs.toronto.edu/~graves/preprint.pdf#chapter.7)
for mathematical details.
"""
ctc_loss(ŷ::AbstractArray, y) = ctc_alpha(ŷ, y).loss

@adjoint function ctc_loss(ŷ, y)
  out = ctc_alpha(ŷ, y)
  ctc_loss_pullback(Δ) = (Δ .* ∇ctc_loss(ŷ, y, out), nothing)
  return out.loss, ctc_loss_pullback
end


text:
using Random

function parse_replace(l)
    u, _, v = split(l)
    u => v
end

lines = readlines()
const replacement = parse_replace.(lines[begin:end-2])
const mol = lines[end]

function step_mol(pattern; ker=mol)
    u, v = pattern
    s = Set()
    toks = split(ker, u)
    for i in 1:length(toks)-1
        new_mol = *(join(toks[begin:begin-1+i], u), v, join(toks[begin+i:end], u))
        push!(s, new_mol)
    end
    s
end

function step_parse(pattern; ker=mol)
    u, v = pattern
    s = Set()
    toks = split(ker, v)
    for i in 1:length(toks)-1
        new_mol = *(join(toks[begin:begin-1+i], v), u, join(toks[begin+i:end], v))
        push!(s, new_mol)
    end
    s
end

function gen_mol(ker, limit=10000)
    i = 0
    while true
        i += 1
        next_ker = rand(union(step_parse.(replacement; ker)...))
        if "e" == next_ker
            return i
        end
        if i > limit
            break
        end
        ker = next_ker
    end
    i
end

@show union(step_mol.(replacement)...) |> length

# Not always works
@show gen_mol(mol)

max_stars_repo_name: pLOPeGG/JuliaOfCode

max_issues_repo_name: pLOPeGG/JuliaOfCode

max_forks_repo_name: pLOPeGG/JuliaOfCode


text:
abstract type sCPAAlphaModel <: CPAAlphaModel end

@newmodelsimple sCPAAlpha sCPAAlphaModel CPAAlphaParam

export sCPAAlpha
function sCPAAlpha(components::Vector{String}; userlocations::Vector{String}=String[], verbose::Bool=false)
    params = getparams(components, ["SAFT/CPA/sCPA/sCPA_like.csv"]; userlocations=userlocations, ignore_missing_singleparams=["Mw"], verbose=verbose)
    c1 = params["c1"]
    packagedparams = CPAAlphaParam(c1)
    model = sCPAAlpha(packagedparams, verbose=verbose)
    return model
end


text:
mutable struct Tree{T}
    value::T
    lchild::Nullable{Tree{T}}
    rchild::Nullable{Tree{T}}
end

function replaceall!(t::Tree{T}, v::T) where T
    t.value = v
    isnull(lchild) || replaceall(get(lchild), v)
    isnull(rchild) || replaceall(get(rchild), v)
    return t
end

max_stars_repo_name: ethansaxenian/RosettaDecode

max_issues_repo_name: ethansaxenian/RosettaDecode

max_forks_repo_name: ethansaxenian/RosettaDecode


text:
@testset "CTMCSt1" begin
    Q = @expr [-x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]

    ma = prob(Q)

    x = 2.0
    ex = prob(Float64[-x x 0; 1 -2 1; 0 1 -1])
    @bind x = x
    res = seval(ma)
    @test isapprox(ex, res)
end

@testset "CTMCSt2" begin
    Q = @expr [-x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]
    csc = SparseCSC(Q)
    ma = prob(csc)

    x = 2.0
    ex = prob(Float64[-x x 0; 1 -2 1; 0 1 -1])
    @bind x = x
    res = seval(ma)
    @test isapprox(ex, res)
end

@testset "CTMCSt3" begin
    Q = @expr [-x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]

    ma = dot(prob(Q), v)
    println(ma)
    x = 2.0
    ex = sum(prob(Float64[-x x 0; 1 -2 1; 0 1 -1]) .* [1,0,0])
    @bind x = x
    res = seval(ma)
    println(res, ex)
    @test isapprox(ex, res)
end

@testset "CTMCStsen1" begin
    Q = @expr [
        -x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]
    csc = SparseCSC(Q)
    ma = prob(csc)

    x0 = 2.0
    test = SymbolicEnv()
    @bind test x = x0
    h = 0.0001

    test0 = SymbolicEnv()
    @bind test0 x = x0 + h
    test1 = SymbolicEnv()
    @bind test1 x = x0 - h

    vv0 = seval(ma, test0)
    vv1 = seval(ma, test1)
    ex = (vv0 - vv1) / (2*h)
    @test isapprox(seval(ma, :x, test), ex)
end

@testset "CTMCStsen2" begin
    Q = @expr [
        -x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]
    csc = SparseCSC(Q)
    ma = dot(prob(csc), v)

    x0 = 2.0
    test = SymbolicEnv()
    @bind test x = x0
    h = 0.0001

    test0 = SymbolicEnv()
    @bind test0 x = x0 + h
    test1 = SymbolicEnv()
    @bind test1 x = x0 - h

    vv0 = seval(ma, test0)
    vv1 = seval(ma, test1)
    ex = (vv0 - vv1) / (2*h)
    @test isapprox(seval(ma, :x, test), ex)
end

@testset "CTMCsensen1" begin
    Q = @expr [
        -x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]
    csc = SparseCSC(Q)
    ma = prob(csc)

    x0 = 2.0
    test = SymbolicEnv()
    @bind test x = x0
    h = 0.0001

    test0 = SymbolicEnv()
    @bind test0 x = x0 + h
    test1 = SymbolicEnv()
    @bind test1 x = x0 - h

    vv0 = seval(ma, test0)
    vv1 = seval(ma, test)
    vv2 = seval(ma, test1)
    ex = (vv0 -2*vv1 + vv2) / (h^2)
    @test isapprox(seval(ma, (:x, :x), test), ex, atol = 1.0e-5)
end

@testset "CTMCsensen2" begin
    Q = @expr [
        -x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]
    csc = SparseCSC(Q)
    ma = dot(prob(csc), v)

    x0 = 2.0
    test = SymbolicEnv()
    @bind test x = x0
    h = 0.0001

    test0 = SymbolicEnv()
    @bind test0 x = x0 + h
    test1 = SymbolicEnv()
    @bind test1 x = x0 - h

    vv0 = seval(ma, test0)
    vv1 = seval(ma, test)
    vv2 = seval(ma, test1)
    ex = (vv0 -2*vv1 + vv2) / (h^2)
    @test isapprox(seval(ma, (:x, :x), test), ex, atol = 1.0e-5)
end

@testset "CTMCStsen3" begin
    Q = @expr [
        -x x 0;
        1 -2 1;
        0 1 -1]
    v = @expr [1, 0, 0]
    csc = Q
    ma = dot(prob(csc), v)

    x0 = 2.0
    test = SymbolicEnv()
    @bind test x = x0
    h = 0.0001

    test0 = SymbolicEnv()
    @bind test0 x = x0 + h
    test1 = SymbolicEnv()
    @bind test1 x = x0 - h

    vv0 = seval(ma, test0)
    vv1 = seval(ma, test1)
    ex = (vv0 - vv1) / (2*h)
    @test isapprox(seval(ma, :x, test), ex)
end


text:
using Test
using BigArrays.Infos
using JSON

@testset "test Infos module" begin
    fileName = joinpath(@__DIR__, "../asset/info")
    str = read(fileName, String)
    @test Info(str) != nothing

    data = Vector{UInt8}(str)
    @test Info(data) != nothing

    d = JSON.parsefile(joinpath(@__DIR__, "../asset/info"), dicttype=Dict{Symbol,Any})
    @test Info(d) != nothing

    info = Info(d)
    Infos.get_properties_in_mip_level(info, 1)

    d = Dict(info)
    @test isa(d, Dict{Symbol, Any})

    str = string(info)
    d2 = JSON.parse(str, dicttype=Dict{Symbol, Any})
    # the d will not equal to d2 because the element type of vector is Any in d2
    #@test d == d2

    println("test info construction function")
    infoScale = Infos.InfoScale()
    info = Info(; numMip=2)
    @test info != nothing
end


text:
#TODO: make ε a tensor
"""
    module PumpFunctions

for defining pump functions either as piecewise constant
or with user-supplied functions

To define a user-supplied PumpFunction, define a `struct` which is a subtype
of `AbstractPumpFunction`, and implement `(::Userdefined)(p::Point)` whose
output is a real scalar, the pump.
"""
module PumpFunctions

export AbstractPumpFunction

using ..Points

import ..PRINTED_COLOR_NUMBER
import ..PRINTED_COLOR_DARK

"""
    AbstractPumpFunction <: Function
"""
abstract type AbstractPumpFunction <: Function end

"""
    PiecewiseConstant([F=1]) -> pc
"""
mutable struct PiecewiseConstant <: AbstractPumpFunction F::Float64 end
PiecewiseConstant() = PiecewiseConstant(1)

"""
    PiecewiseConstant(point) -> F
"""
(pc::PiecewiseConstant)(args...) = pc.F

# Base.conj(pc::PiecewiseConstant) = pc

# Pretty Printing
function Base.show(io::IO,d::PiecewiseConstant)
    get(io,:tabbed2,false) ? print(io,"\t\t") : nothing
    printstyled(io, "PiecewiseConstant",color=PRINTED_COLOR_DARK)
    print(io, " (")
    printstyled(io,"F = ",d.F,color=PRINTED_COLOR_NUMBER)
    print(io,")")
end
Base.show(io::IO,::MIME"text/plain",d::PiecewiseConstant) = show(io,d)

end # module


text:
#################################################
###### Integers and Floating-Point Numbers ######
#################################################

# Julia's primitive numeric types:

# Integer types

# Type	Signed?	Number of bits	Smallest value	Largest value
# Int8	   ✓         8	            -2^7	       2^7 - 1
# UInt8		         8	              0	           2^8 - 1
# Int16	   ✓	     16	            -2^15	       2^15 - 1
# UInt16		     16	              0	           2^16 - 1
# Int32	   ✓	     32	            -2^31	       2^31 - 1
# UInt32		     32	              0	           2^32 - 1
# Int64	   ✓	     64	            -2^63	       2^63 - 1
# UInt64		     64	              0	           2^64 - 1
# Int128   ✓	     128	        -2^127	       2^127 - 1
# UInt128		     128	          0	           2^128 - 1
# Bool	  N/A	     8	            false (0)	   true (1)    <- implemented as an 8-bit primitive type

# Floating-point types:

# Type	    Precision	Number of bits
# Float16	half	        16
# Float32	single	        32
# Float64	double	        64

typeof(12) # Int64

# Unsigned integers are input and output using the 0x prefix and hexadecimal (base 16) digits 0-9a-f
# (the capitalized digits A-F also work for input). The size of the unsigned value is determined
# by the number of hex digits used:

x = 0x1
# 0x01

typeof(x) # UInt8

x = 0x123 # UInt16

x = 0x1234567 # UInt32

##################################
###### Arithmetic Operators ######
##################################

# The following arithmetic operators are supported on all primitive numeric types:

# Expression	Name	            Description
#   +x	        unary plus	        the identity operation
#   -x	        unary minus	        maps values to their additive inverses
#   x + y	    binary plus	        performs addition
#   x - y	    binary minus	    performs subtraction
#   x * y	    times	            performs multiplication
#   x / y	    divide	            performs division
#   x ÷ y	    integer divide	    x / y, truncated to an integer
#   x \ y	    inverse divide	    equivalent to y / x
#   x ^ y	    power	            raises x to the yth power
#   x % y	    remainder	        equivalent to rem(x,y)

###############################
###### Boolean Operators ######
###############################

# Expression	Name
#   !x	        negation
#   x && y	    short-circuiting and
#   x || y	    short-circuiting or

###############################
###### Bitwise Operators ######
###############################

# Expression	Name
#    ~x	        bitwise not
#   x & y	    bitwise and
#   x | y	    bitwise or
#   x ⊻ y	    bitwise xor (exclusive or)
#   x >>> y	    logical shift right
#   x >> y	    arithmetic shift right
#   x << y	    logical/arithmetic shift left

#################################
###### Numeric Comparisons ######
#################################

# Operator	    Name
#   ==	        equality
#  !=, ≠	    inequality
#   <	        less than
# <=, ≤	        less than or equal to
#   >	        greater than
#  >=, ≥	    greater than or equal to

################################
###### Updating operators ######
################################

# +=  -=  *=  /=  \=  ÷=  %=  ^=  &=  |=  ⊻=  >>>=  >>=  <<=

#######################
###### Functions ######
#######################

function g(x,y)
    return x + y
end

function hypot(x,y)
    x = abs(x)
    y = abs(y)
    if x > y
        r = y/x
        return x*sqrt(1+r*r)
    end
    if y == 0
        return zero(x)
    end
    r = x/y
    return y*sqrt(1+r*r)
end

function fact(n::Int)
    n >= 0 || error("n must be non-negative")
    n == 0 && return 1
    n * fact(n-1)
end

#################################
###### Anonymous Functions ######
#################################

x -> x + x

function (x)
    x + x
end

max_stars_repo_name: mauricioriva/learn-julia

max_issues_repo_name: mauricioriva/learn-julia

max_forks_repo_name: mauricioriva/learn-julia


text:
# disease parameters
using Distributions

#R0 = 2.14
#beta = R0 / 3.56 - computed using N = 100
beta = R0 / 3.83 # computed using N = 500 - see matlab calibration scripts
p_asymp =  0.33 # fraction of asymptomatic infections
#p_asymp =  1.0 # fraction of asymptomatic infections
#latent period
global latent_period = dt

# incubation period
inc_mu = 1.62
inc_sig = 0.418
inc_dist = LogNormal(inc_mu, inc_sig)
# generate a random sample with rand(rng, inc_dist)

# post-incubation (recovery) period
rec_min = 5.0
rec_max = 10.0
rec_dist = Uniform(rec_min, rec_max)

# infectiousness (to capture secondary case dispersion)
b_dispersion = 0.1
b_scale = beta / b_dispersion
b_dist = Gamma(b_dispersion, b_scale)

Vmax = 7.0
inc_plat_fac = 0.1
rec_plat_fac = 0.0

# function that determines agent's contagiousness (beta)
function compute_kinc(t_inc::Float64)
    t_plat_inc = inc_plat_fac * t_inc
    k_inc = log(Vmax)/(t_inc - t_plat_inc)
    return k_inc
end

function compute_krec(t_rec::Float64)
    t_plat_rec = rec_plat_fac * t_rec
    k_rec = log(1.0/Vmax) / (t_rec - t_plat_rec)
    return k_rec
end

# test sensitivity
c_lims = [1.0, 5.11];
b1_lims = [0.8, 2.31]
b2_lims = [1.26, 3.47]
b3_lims = [1.05, 1.14]


text:
"""
    test_lattice() :: Nothing

Test lattice building for available lattice implementations.
"""
function test_lattice() :: Nothing

    lattice_names = ["square",
                     "honeycomb",
                     "kagome",
                     "triangular",
                     "cubic",
                     "fcc",
                     "bcc",
                     "hyperhoneycomb",
                     "hyperkagome",
                     "pyrochlore",
                     "diamond"]

    # for each lattice give bond distance to test, expected number of sites, minimal Euclidean distance for comparison and maximal Euclidean distance for comparison
    testsizes = [[8,  145, 2, 4],
                 [8,  109, 2, 4],
                 [8,  163, 2, 4],
                 [8,  217, 2, 4],
                 [8,  833, 2, 4],
                 [8, 2057, 2, 4],
                 [8, 1241, 2, 4],
                 [8,  319, 3, 4],
                 [8,  415, 6, 7],
                 [8, 1029, 2, 4],
                 [8,  525, 2, 4]]

    # run tests for lattice implementations with bond distance metric
    @testset "lattices " begin
        for i in eachindex(lattice_names)
            # generate testdata
            current_name = lattice_names[i]
            l            = get_lattice(current_name, testsizes[i][1], verbose = false)

            # check that implementations give right number of sites
            @testset "$current_name no. of sites" begin
                @test length(l.sites) == testsizes[i][2]
            end

            # check that Euclidean metric gives same number of sites as sites cut out from larger lattice
            @testset "$current_name Euclidean" begin
                for j in testsizes[i][3] : testsizes[i][4]
                    l_bond         = get_lattice(current_name, ceil(Int64, 2.5 * j), verbose = false)
                    l_euclidean    = get_lattice(current_name, j, verbose = false, euclidean = true)
                    nn_distance    = norm(l.sites[2].vec)
                    filtered_sites = filter!(x -> norm(x.vec) <= nn_distance * j, l_bond.sites)
                    @test length(l_euclidean.sites) == length(filtered_sites)
                end
            end
        end
    end

    return nothing
end