text:
module TestIndexing

using Test, DataFrames, CategoricalArrays, PooledArrays

const ≅ = isequal

@testset "mutating SubDataFrame with assignment to [!, col]" begin
    df = DataFrame()
    sdf = @view df[:, :]
    @test_throws ArgumentError sdf[!, :a] = [1]
    sdf[!, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(a=[])

    df = DataFrame()
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :a] = Int[]
    @test isempty(df)

    df = DataFrame()
    sdf = @view df[1:0, :]
    @test_throws ArgumentError sdf[!, :a] = [1]
    sdf[!, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(a=[])

    df = DataFrame()
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :a] = Int[]
    @test isempty(df)

    df = DataFrame(x=Int[])
    sdf = @view df[:, :]
    @test_throws ArgumentError sdf[!, :a] = [1]
    sdf[!, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(x=Int[], a=[])
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=Int[])
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :a] = Int[]
    @test df == DataFrame(x=Int[])
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, :]
    @test_throws ArgumentError sdf[!, :a] = [1]
    sdf[!, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(x=Int[], a=[])
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :a] = Int[]
    @test df == DataFrame(x=Int[])
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, :]
    @test_throws ArgumentError sdf[!, :a] = [1]
    sdf[!, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=missing)
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=1:5, a=missing)

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :a] = Int[]
    @test df ≅ DataFrame(x=1:5)
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=1:5)

    df = DataFrame(x=1:5)
    sdf = @view df[:, :]
    @test_throws ArgumentError sdf[!, :a] = [1]
    sdf[!, :a] = 11:15
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=11:15)
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = fill(nothing, 5)
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=nothing, a=11:15)

    df = DataFrame(x=1:5)
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :a] = 11:15
    @test df ≅ DataFrame(x=1:5)
    @test_throws DimensionMismatch sdf[!, :x] = ["a"]
    sdf[!, :x] = fill(nothing, 5)
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=fill(nothing, 5))

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], :]
    sdf[!, :d] = [101, 103]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[101, missing, 103, missing, missing])
    sdf[!, :a] = [-1.0, -3.0]
    @test eltype(df.a) === Float64
    @test df ≅ DataFrame(a=[-1.0, 2, -3.0, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 103, missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], 1:end]
    @test_throws ArgumentError sdf[!, :d] = [101, 103]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    sdf[!, :a] = [-1.0, -3.0]
    @test eltype(df.a) === Float64
    @test df ≅ DataFrame(a=[-1.0, 2, -3.0, 4, 5],
                         b=11:15, c=21:25)

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    sdf[!, :d] = [103, 102]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing])
    sdf[!, "e"] = [1003, 1002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws ArgumentError sdf[!, 0] = [10003, 10002]
    @test_throws ArgumentError sdf[!, 6] = [10003, 10002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    sdf[!, 1] = ["10003", "10002"]
    @test eltype(df.a) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    sdf[!, :b] = [-13.0, -12.0]
    @test eltype(df.b) === Float64
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws ArgumentError sdf[!, :x] = 1
    @test_throws ArgumentError sdf[!, :x] = [1]
    @test_throws ArgumentError sdf[!, :a] = 1
    @test_throws DimensionMismatch sdf[!, :a] = [1]
    sdf[!, :f] = categorical(["3", "2"])
    @test df.f isa CategoricalArray
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    tmpc = df.c
    sdf[!, 3] = [33, 22]
    @test tmpc == 21:25
    @test tmpc != df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=[21, 22, 33, 24, 25],
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    sdf[!, 3] = categorical(["33", "22"])
    @test eltype(df.c) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=[21, "22", "33", 24, 25],
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    @test df.c[2] isa CategoricalValue
    @test df.c[3] isa CategoricalValue

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], 1:3]
    @test_throws ArgumentError sdf[!, :d] = [103, 102]
    @test_throws ArgumentError sdf[!, "e"] = [1003, 1002]
    @test_throws ArgumentError sdf[!, 0] = [10003, 10002]
    @test_throws ArgumentError sdf[!, 6] = [10003, 10002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    sdf[!, 1] = ["10003", "10002"]
    @test eltype(df.a) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=11:15, c=21:25)
    sdf[!, :b] = [-13.0, -12.0]
    @test eltype(df.b) === Float64
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=21:25)
    @test_throws ArgumentError sdf[!, :x] = 1
    @test_throws ArgumentError sdf[!, :x] = [1]
    @test_throws ArgumentError sdf[!, :a] = 1
    @test_throws DimensionMismatch sdf[!, :a] = [1]
    @test_throws ArgumentError sdf[!, :f] = categorical(["3", "2"])
    tmpc = df.c
    sdf[!, 3] = [33, 22]
    @test tmpc == 21:25
    @test tmpc != df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=[21, 22, 33, 24, 25])
    sdf[!, 3] = categorical(["33", "22"])
    @test eltype(df.c) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=[21, "22", "33", 24, 25])
    @test df.c[2] isa CategoricalValue
    @test df.c[3] isa CategoricalValue

    sdf = @view df[[3, 2], 1:2]
    @test_throws ArgumentError sdf[!, :c] = 1:2
end

@testset "mutating SubDataFrame with broadcasting assignment to [!, col]" begin
    df = DataFrame()
    sdf = @view df[:, :]
    sdf[!, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test isempty(df.a)
    sdf[!, :b] .= 1
    @test df.b isa Vector{Union{Missing, Int}}
    @test isempty(df.b)
    @test_throws DimensionMismatch sdf[!, :c] .= 1:2
    @test_throws DimensionMismatch sdf[!, :a] .= 1:2
    sdf[!, :a] .= [1.0]
    @test df.a isa Vector{Union{Missing, Float64}}
    @test isempty(df.a)
    sdf[!, :b] .= 1.0
    @test df.b isa Vector{Union{Missing, Float64}}
    @test isempty(df.b)

    df = DataFrame()
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[!, :a] .= [1]
    @test_throws ArgumentError sdf[!, :b] .= 1
    @test isempty(df)

    df = DataFrame()
    sdf = @view df[1:0, :]
    sdf[!, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test isempty(df.a)
    sdf[!, :b] .= 1
    @test df.b isa Vector{Union{Missing, Int}}
    @test isempty(df.b)
    @test_throws DimensionMismatch sdf[!, :c] .= 1:2
    @test_throws DimensionMismatch sdf[!, :a] .= 1:2
    sdf[!, :a] .= [1.0]
    @test df.a isa Vector{Union{Missing, Float64}}
    @test isempty(df.a)
    sdf[!, :b] .= 1.0
    @test df.b isa Vector{Union{Missing, Float64}}
    @test isempty(df.b)

    df = DataFrame()
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[!, :a] .= [1]
    @test_throws ArgumentError sdf[!, :b] .= 1
    @test isempty(df)

    df = DataFrame(x=Int[])
    sdf = @view df[:, :]
    sdf[!, :x] .= nothing
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=Int[])
    sdf = @view df[:, 1:end]
    sdf[!, :x] .= nothing
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, :]
    sdf[!, :x] .= nothing
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, 1:end]
    sdf[!, :x] .= nothing
    @test df.x isa Vector{Union{Nothing, Int}}

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, :]
    sdf[!, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=missing)
    sdf[!, :x] .= Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=1:5, a=missing)

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[!, :a] .= [1]
    @test df == DataFrame(x=1:5)
    sdf[!, :x] .= Nothing[]
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=1:5)

    df = DataFrame(x=1:5)
    sdf = @view df[:, :]
    sdf[!, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=1)
    sdf[!, :b] .= 2
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=1, b=2)
    sdf[!, :x] .= nothing
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=fill(nothing, 5), a=1, b=2)

    df = DataFrame(x=1:5)
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[!, :a] .= [1]
    @test_throws ArgumentError sdf[!, :b] .= 2
    @test df == DataFrame(x=1:5)
    sdf[!, :x] .= nothing
    @test df.x isa Vector{Union{Nothing, Int}}
    @test df ≅ DataFrame(x=fill(nothing, 5))

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], :]
    sdf[!, :d] .= 101
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing])
    sdf[!, :a] .= -1.0
    @test eltype(df.a) === Float64
    @test df ≅ DataFrame(a=[-1.0, 2, -1.0, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing])
    @test_throws DimensionMismatch sdf[!, :a] .= [-1.0, -2.0, -3.0]
    sdf[!, :a] .= [-1.0, -2.0]
    @test df ≅ DataFrame(a=[-1.0, 2, -2.0, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing])
    sdf[!, :e] .= 1:2
    @test df ≅ DataFrame(a=[-1.0, 2, -2.0, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing],
                         e=[1, missing, 2, missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], 1:end]
    @test_throws ArgumentError sdf[!, :d] .= 101
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    sdf[!, :a] .= -1.0
    @test eltype(df.a) === Float64
    @test df ≅ DataFrame(a=[-1.0, 2, -1.0, 4, 5],
                         b=11:15, c=21:25)
    @test_throws DimensionMismatch sdf[!, :a] .= [-1.0, -2.0, -3.0]
    sdf[!, :a] .= [-1.0, -2.0]
    @test df ≅ DataFrame(a=[-1.0, 2, -2.0, 4, 5],
                         b=11:15, c=21:25)
    @test_throws ArgumentError sdf[!, :e] .= 1:2
    @test df ≅ DataFrame(a=[-1.0, 2, -2.0, 4, 5],
                         b=11:15, c=21:25)

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    sdf[!, :d] .= 102
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing])
    sdf[!, "e"] .= [1003, 1002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws ArgumentError sdf[!, 0] .= [10003, 10002]
    @test_throws ArgumentError sdf[!, 6] .= 10002
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    sdf[!, 1] .= "10002"
    @test eltype(df.a) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10002", 4, 5],
                         b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    sdf[!, :b] .= [-13.0, -12.0]
    @test eltype(df.b) === Float64
    @test df ≅ DataFrame(a=[1, "10002", "10002", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws DimensionMismatch sdf[!, :x] .= 1:3
    @test_throws DimensionMismatch sdf[!, :a] .= 1:3
    sdf[!, :f] .= categorical(["3", "2"])
    @test df.f isa CategoricalArray
    @test df ≅ DataFrame(a=[1, "10002", "10002", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    tmpc = df.c
    sdf[!, 3] .= [33, 22]
    @test tmpc == 21:25
    @test tmpc != df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, "10002", "10002", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=[21, 22, 33, 24, 25],
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    sdf[!, 3] .= categorical(["33", "22"])[2]
    @test eltype(df.c) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10002", 4, 5],
                         b=[11, -12.0, -13.0, 14, 15],
                         c=[21, "22", "22", 24, 25],
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    @test df.c[2] isa CategoricalValue
    @test df.c[3] isa CategoricalValue

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], 1:3]
    @test_throws ArgumentError sdf[!, :d] .= [103, 102]
    @test_throws ArgumentError sdf[!, "e"] .= [1003, 1002]
    @test_throws ArgumentError sdf[!, 0] .= [10003, 10002]
    @test_throws ArgumentError sdf[!, 6] .= [10003, 10002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    sdf[!, 1] .= ["10003", "10002"]
    @test eltype(df.a) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=11:15, c=21:25)
    sdf[!, :b] .= -12.0
    @test eltype(df.b) === Float64
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -12.0, 14, 15],
                         c=21:25)
    @test_throws ArgumentError sdf[!, :x] .= 1
    @test_throws ArgumentError sdf[!, :x] .= [1]
    @test_throws ArgumentError sdf[!, :f] .= categorical(["3", "2"])
    tmpc = df.c
    sdf[!, 3] .= [33, 22]
    @test tmpc == 21:25
    @test tmpc != df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -12.0, 14, 15],
                         c=[21, 22, 33, 24, 25])
    sdf[!, 3] .= categorical(["33", "22"])[2]
    @test eltype(df.c) === Any
    @test df ≅ DataFrame(a=[1, "10002", "10003", 4, 5],
                         b=[11, -12.0, -12.0, 14, 15],
                         c=[21, "22", "22", 24, 25])
    @test df.c[2] isa CategoricalValue
    @test df.c[3] isa CategoricalValue

    sdf = @view df[[3, 2], 1:2]
    @test_throws ArgumentError sdf[!, :c] .= 1:2
end

@testset "mutating SubDataFrame with assignment to [!, cols]" begin
    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, [:c, :b, :a]] = DataFrame(c=["c", "d"], b=[1.0, 2.0], a=[13, 12])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                            b=[11.0, 2.0, 1.0, 14.0, 15.0],
                            c=[21, "d", "c", 24, 25])
        @test tmpa !== df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Float64
        @test eltype(df.c) == Any

        @test_throws ArgumentError sdf[!, [:c, :b, :a]] = DataFrame(d=["c", "d"], b=[1.0, 2.0], a=[13, 12])
        @test_throws ArgumentError sdf[!, [:c, :b, :a]] = DataFrame(a=["c", "d"], b=[1.0, 2.0], c=[13, 12])
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, cols] = DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                            b=[11.0, 2.0, 1.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Float64

        @test_throws ArgumentError sdf[!, cols] = DataFrame(b=[1.0, 2.0], a=[13, 12])
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[!, cols] = DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                            b=[11.0, 2.0, 1.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Float64
        @test_throws ArgumentError sdf[!, cols] = DataFrame(a=[13, 12], b=[1.0, 2.0], c=1)
    end

    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, [:c, :b, :a]] = ["b" "d" "f"; "a" "c" "e"]
        @test df == DataFrame(a=[1, "e", "f", 4, 5],
                            b=[11.0, "c", "d", 14.0, 15.0],
                            c=[21, "a", "b", 24, 25])
        @test tmpa !== df.a
        @test eltype(df.a) == Any
        @test eltype(df.b) == Any
        @test eltype(df.c) == Any

        @test_throws DimensionMismatch sdf[!, [:c, :b, :a]] = ones(2, 2)
        @test_throws DimensionMismatch sdf[!, [:c, :b, :a]] = ones(1, 3)
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, cols] = [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2.0, 1.0, 4, 5],
                            b=[11.0, 4.0, 3.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Float64
        @test eltype(df.b) == Float64

        @test_throws DimensionMismatch sdf[!, cols] = ones(1, 3)
        @test_throws DimensionMismatch sdf[!, cols] = ones(3, 1)
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[!, cols] = [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2.0, 1.0, 4, 5],
                            b=[11.0, 4.0, 3.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Float64
        @test eltype(df.b) == Float64
        @test_throws DimensionMismatch sdf[!, cols] = ones(1, 3)
        @test_throws DimensionMismatch sdf[!, cols] = ones(3, 1)
    end
end

@testset "mutating SubDataFrame with broadcasting assignment to [!, cols]" begin
    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, [:c, :b, :a]] .= DataFrame(c=["c", "d"], b=[1.0, 2.0], a=[13, 12])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                            b=[11.0, 2.0, 1.0, 14.0, 15.0],
                            c=[21, "d", "c", 24, 25])
        @test tmpa !== df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Float64
        @test eltype(df.c) == Any

        sdf[!, [:c, :b, :a]] .= [100, 200]
        @test df == DataFrame(a=[1, 200, 100, 4, 5],
                            b=[11.0, 200.0, 100.0, 14.0, 15.0],
                            c=[21, 200, 100, 24, 25])

        @test_throws ArgumentError sdf[!, [:c, :b, :a]] .= DataFrame(d=["c", "d"], b=[1.0, 2.0], a=[13, 12])
        @test_throws ArgumentError sdf[!, [:c, :b, :a]] .= DataFrame(a=["c", "d"], b=[1.0, 2.0], c=[13, 12])
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, cols] .= DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                            b=[11.0, 2.0, 1.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Float64

        sdf[!, cols] .= [100 200]
        @test df == DataFrame(a=[1, 100, 100, 4, 5],
                            b=[11.0, 200.0, 200.0, 14.0, 15.0],
                            c=21:25)

        @test_throws ArgumentError sdf[!, cols] .= DataFrame(b=[1.0, 2.0], a=[13, 12])
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[!, cols] .= DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                            b=[11.0, 2.0, 1.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Float64

        sdf[!, cols] .= 100
        @test df == DataFrame(a=[1, 100, 100, 4, 5],
                            b=[11.0, 100.0, 100.0, 14.0, 15.0],
                            c=21:25)

        @test_throws DimensionMismatch sdf[!, cols] .= DataFrame(a=[13, 12], b=[1.0, 2.0], c=1)
    end

    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, [:c, :b, :a]] .= ["b" "d" "f"; "a" "c" "e"]
        @test df == DataFrame(a=[1, "e", "f", 4, 5],
                            b=[11.0, "c", "d", 14.0, 15.0],
                            c=[21, "a", "b", 24, 25])
        @test tmpa !== df.a
        @test eltype(df.a) == Any
        @test eltype(df.b) == Any
        @test eltype(df.c) == Any

        @test_throws DimensionMismatch sdf[!, [:c, :b, :a]] .= ones(2, 2)
        @test_throws DimensionMismatch sdf[!, [:c, :b, :a]] .= ones(4, 3)
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[!, cols] .= [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2.0, 1.0, 4, 5],
                            b=[11.0, 4.0, 3.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Float64
        @test eltype(df.b) == Float64

        @test_throws DimensionMismatch sdf[!, cols] .= ones(4, 3)
        @test_throws DimensionMismatch sdf[!, cols] .= ones(3, 4)
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[!, cols] .= [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2.0, 1.0, 4, 5],
                            b=[11.0, 4.0, 3.0, 14.0, 15.0],
                            c=21:25)
        @test tmpa !== df.a
        @test eltype(df.a) == Float64
        @test eltype(df.b) == Float64
        @test_throws DimensionMismatch sdf[!, cols] .= ones(4, 3)
        @test_throws DimensionMismatch sdf[!, cols] .= ones(3, 4)
    end
end

@testset "mutating SubDataFrame with assignment to [:, col]" begin
    df = DataFrame()
    sdf = @view df[:, :]
    @test_throws ArgumentError sdf[:, :a] = [1]
    sdf[:, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(a=[])

    df = DataFrame()
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :a] = Int[]
    @test isempty(df)

    df = DataFrame()
    sdf = @view df[1:0, :]
    @test_throws ArgumentError sdf[:, :a] = [1]
    sdf[:, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(a=[])

    df = DataFrame()
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :a] = Int[]
    @test isempty(df)

    df = DataFrame(x=Int[])
    sdf = @view df[:, :]
    @test_throws ArgumentError sdf[:, :a] = [1]
    sdf[:, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(x=Int[], a=[])
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    sdf[:, :x] = Nothing[]
    @test df.x isa Vector{Int}

    df = DataFrame(x=Int[])
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :a] = Int[]
    @test df == DataFrame(x=Int[])
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    sdf[:, :x] = Nothing[]
    @test df.x isa Vector{Int}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, :]
    @test_throws ArgumentError sdf[:, :a] = [1]
    sdf[:, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df == DataFrame(x=Int[], a=[])
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    sdf[:, :x] = Nothing[]
    @test df.x isa Vector{Int}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :a] = Int[]
    @test df == DataFrame(x=Int[])
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    sdf[:, :x] = Nothing[]
    @test df.x isa Vector{Int}

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, :]
    @test_throws ArgumentError sdf[:, :a] = [1]
    sdf[:, :a] = Int[]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=missing)
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    sdf[:, :x] = Nothing[]
    @test df.x isa Vector{Int}
    @test df ≅ DataFrame(x=1:5, a=missing)

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :a] = Int[]
    @test df ≅ DataFrame(x=1:5)
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    sdf[:, :x] = Nothing[]
    @test df.x isa Vector{Int}
    @test df ≅ DataFrame(x=1:5)

    df = DataFrame(x=1:5)
    sdf = @view df[:, :]
    @test_throws ArgumentError sdf[:, :a] = [1]
    sdf[:, :a] = 11:15
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=11:15)
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    @test_throws MethodError sdf[:, :x] = fill(nothing, 5)
    @test df ≅ DataFrame(x=1:5, a=11:15)

    df = DataFrame(x=1:5)
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :a] = 11:15
    @test df ≅ DataFrame(x=1:5)
    @test_throws DimensionMismatch sdf[:, :x] = ["a"]
    @test_throws MethodError sdf[:, :x] = fill(nothing, 5)

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], :]
    sdf[:, :d] = [101, 103]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[101, missing, 103, missing, missing])
    sdf[:, :a] = [-1.0, -3.0]
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[-1, 2, -3, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 103, missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], 1:end]
    @test_throws ArgumentError sdf[:, :d] = [101, 103]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    sdf[:, :a] = [-1.0, -3.0]
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[-1, 2, -3, 4, 5],
                         b=11:15, c=21:25)

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    sdf[:, :d] = [103, 102]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing])
    sdf[:, "e"] = [1003, 1002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws BoundsError sdf[:, 0] = [10003, 10002]
    @test_throws BoundsError sdf[:, 6] = [10003, 10002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws MethodError sdf[:, 1] = ["10003", "10002"]
    sdf[:, 1] = [10003, 10002]
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=11:15, c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    sdf[:, :b] = [-13.0, -12.0]
    @test eltype(df.b) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws ArgumentError sdf[:, :x] = 1
    @test_throws ArgumentError sdf[:, :x] = [1]
    @test_throws MethodError sdf[:, :a] = 1
    @test_throws DimensionMismatch sdf[:, :a] = [1]
    sdf[:, :f] = categorical(["3", "2"])
    @test df.f isa CategoricalArray
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=21:25,
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    tmpc = df.c
    sdf[:, 3] = [33, 22]
    @test tmpc === df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=[21, 22, 33, 24, 25],
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    @test_throws MethodError sdf[:, 3] = categorical(["33", "22"])
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=[21, 22, 33, 24, 25],
                         d=[missing, 102, 103, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], 1:3]
    @test_throws ArgumentError sdf[:, :d] = [103, 102]
    @test_throws ArgumentError sdf[:, "e"] = [1003, 1002]
    @test_throws BoundsError sdf[:, 0] = [10003, 10002]
    @test_throws BoundsError sdf[:, 6] = [10003, 10002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    @test_throws MethodError sdf[:, 1] = ["10003", "10002"]
    sdf[:, 1] = [10003, 10002]
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=11:15, c=21:25)
    sdf[:, :b] = [-13.0, -12.0]
    @test eltype(df.b) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=21:25)
    @test_throws ArgumentError sdf[:, :x] = 1
    @test_throws ArgumentError sdf[:, :x] = [1]
    @test_throws MethodError sdf[:, :a] = 1
    @test_throws DimensionMismatch sdf[:, :a] = [1]
    @test_throws ArgumentError sdf[:, :f] = categorical(["3", "2"])
    tmpc = df.c
    sdf[:, 3] = [33, 22]
    @test tmpc === df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=[21, 22, 33, 24, 25])
    @test_throws MethodError sdf[:, 3] = categorical(["33", "22"])
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=[21, 22, 33, 24, 25])

    sdf = @view df[[3, 2], 1:2]
    @test_throws ArgumentError df[!, :c] = 1:2
end

@testset "mutating SubDataFrame with broadcasting assignment to [:, col]" begin
    df = DataFrame()
    sdf = @view df[:, :]
    sdf[:, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test isempty(df.a)
    sdf[:, :b] .= 1
    @test df.b isa Vector{Union{Missing, Int}}
    @test isempty(df.b)
    @test_throws DimensionMismatch sdf[:, :c] .= 1:2
    @test_throws DimensionMismatch sdf[:, :a] .= 1:2
    sdf[:, :a] .= [1.0]
    @test df.a isa Vector{Union{Missing, Int}}
    @test isempty(df.a)
    sdf[:, :b] .= 1.0
    @test df.b isa Vector{Union{Missing, Int}}
    @test isempty(df.b)

    df = DataFrame()
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[:, :a] .= [1]
    @test_throws ArgumentError sdf[:, :b] .= 1
    @test isempty(df)

    df = DataFrame()
    sdf = @view df[1:0, :]
    sdf[:, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test isempty(df.a)
    sdf[:, :b] .= 1
    @test df.b isa Vector{Union{Missing, Int}}
    @test isempty(df.b)
    @test_throws DimensionMismatch sdf[:, :c] .= 1:2
    @test_throws DimensionMismatch sdf[:, :a] .= 1:2
    sdf[:, :a] .= [1.0]
    @test df.a isa Vector{Union{Missing, Int}}
    @test isempty(df.a)
    sdf[:, :b] .= 1.0
    @test df.b isa Vector{Union{Missing, Int}}
    @test isempty(df.b)

    df = DataFrame()
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[:, :a] .= [1]
    @test_throws ArgumentError sdf[:, :b] .= 1
    @test isempty(df)

    df = DataFrame(x=Int[])
    sdf = @view df[:, :]
    @test_throws MethodError sdf[:, :x] .= nothing
    @test df.x isa Vector{Int}

    df = DataFrame(x=Int[])
    sdf = @view df[:, 1:end]
    @test_throws MethodError sdf[:, :x] .= nothing
    @test df.x isa Vector{Int}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, :]
    @test_throws MethodError sdf[:, :x] .= nothing
    @test df.x isa Vector{Int}

    df = DataFrame(x=Int[])
    sdf = @view df[1:0, 1:end]
    @test_throws MethodError sdf[:, :x] .= nothing
    @test df.x isa Vector{Int}

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, :]
    sdf[:, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=missing)
    sdf[:, :x] .= Nothing[]
    @test df.x isa Vector{Int}
    @test df ≅ DataFrame(x=1:5, a=missing)

    df = DataFrame(x=1:5)
    sdf = @view df[1:0, 1:end]
    @test_throws ArgumentError sdf[:, :a] .= [1]
    @test df == DataFrame(x=1:5)
    sdf[:, :x] .= Nothing[]
    @test df.x isa Vector{Int}
    @test df ≅ DataFrame(x=1:5)

    df = DataFrame(x=1:5)
    sdf = @view df[:, :]
    sdf[:, :a] .= [1]
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=1)
    sdf[:, :b] .= 2
    @test df.a isa Vector{Union{Missing, Int}}
    @test df ≅ DataFrame(x=1:5, a=1, b=2)
    @test_throws MethodError sdf[:, :x] .= nothing
    @test df.x isa Vector{Int}
    sdf[:, :x] .= 1
    @test df ≅ DataFrame(x=fill(1, 5), a=1, b=2)

    df = DataFrame(x=1:5)
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError sdf[:, :a] .= [1]
    @test_throws ArgumentError sdf[:, :b] .= 2
    @test df == DataFrame(x=1:5)
    @test_throws MethodError sdf[:, :x] .= nothing
    @test df.x isa Vector{Int}
    sdf[:, :x] .= 1
    @test df ≅ DataFrame(x=fill(1, 5))

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], :]
    sdf[:, :d] .= 101
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing])
    sdf[:, :a] .= -1.0
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[-1, 2, -1, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing])
    @test_throws DimensionMismatch sdf[:, :a] .= [-1.0, -2.0, -3.0]
    sdf[:, :a] .= [-1.0, -2.0]
    @test df ≅ DataFrame(a=[-1, 2, -2, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing])
    sdf[:, :e] .= 1:2
    @test df ≅ DataFrame(a=[-1, 2, -2, 4, 5],
                         b=11:15, c=21:25,
                         d=[101, missing, 101, missing, missing],
                         e=[1, missing, 2, missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[1, 3], 1:end]
    @test_throws ArgumentError sdf[:, :d] .= 101
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    sdf[:, :a] .= -1.0
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[-1, 2, -1, 4, 5],
                         b=11:15, c=21:25)
    @test_throws DimensionMismatch sdf[:, :a] .= [-1.0, -2.0, -3.0]
    sdf[:, :a] .= [-1.0, -2.0]
    @test df ≅ DataFrame(a=[-1, 2, -2, 4, 5],
                         b=11:15, c=21:25)
    @test_throws ArgumentError sdf[:, :e] .= 1:2
    @test df ≅ DataFrame(a=[-1, 2, -2, 4, 5],
                         b=11:15, c=21:25)

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    sdf[:, :d] .= 102
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing])
    sdf[:, "e"] .= [1003, 1002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws ArgumentError sdf[:, 0] .= [10003, 10002]
    @test_throws ArgumentError sdf[:, 6] .= 10002
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws MethodError sdf[:, 1] .= "10002"
    sdf[:, 1] .= 10002
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10002, 4, 5],
                         b=11:15, c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    sdf[:, :b] .= [-13.0, -12.0]
    @test eltype(df.b) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10002, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing])
    @test_throws DimensionMismatch sdf[:, :x] .= 1:3
    @test_throws DimensionMismatch sdf[:, :a] .= 1:3
    sdf[:, :f] .= categorical(["3", "2"])
    @test df.f isa CategoricalArray
    @test df ≅ DataFrame(a=[1, 10002, 10002, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=21:25,
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    tmpc = df.c
    sdf[:, 3] .= [33, 22]
    @test tmpc === df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10002, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=[21, 22, 33, 24, 25],
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])
    @test_throws MethodError sdf[:, 3] .= categorical(["33", "22"])[2]
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10002, 4, 5],
                         b=[11, -12, -13, 14, 15],
                         c=[21, 22, 33, 24, 25],
                         d=[missing, 102, 102, missing, missing],
                         e=[missing, 1002, 1003, missing, missing],
                         f=[missing, "2", "3", missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], 1:3]
    @test_throws ArgumentError sdf[:, :d] .= [103, 102]
    @test_throws ArgumentError sdf[:, "e"] .= [1003, 1002]
    @test_throws ArgumentError sdf[:, 0] .= [10003, 10002]
    @test_throws ArgumentError sdf[:, 6] .= [10003, 10002]
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25)
    @test_throws MethodError sdf[:, 1] .= ["10003", "10002"]
    sdf[:, 1] .= [10003, 10002]
    @test eltype(df.a) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=11:15, c=21:25)
    sdf[:, :b] .= -12.0
    @test eltype(df.b) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -12, 14, 15],
                         c=21:25)
    @test_throws ArgumentError sdf[:, :x] .= 1
    @test_throws ArgumentError sdf[:, :x] .= [1]
    @test_throws ArgumentError sdf[:, :f] .= categorical(["3", "2"])
    tmpc = df.c
    sdf[:, 3] .= [33, 22]
    @test tmpc === df.c
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -12, 14, 15],
                         c=[21, 22, 33, 24, 25])
    @test_throws MethodError sdf[:, 3] .= categorical(["33", "22"])[2]
    @test eltype(df.c) === Int
    @test df ≅ DataFrame(a=[1, 10002, 10003, 4, 5],
                         b=[11, -12, -12, 14, 15],
                         c=[21, 22, 33, 24, 25])

    sdf = @view df[[3, 2], 1:2]
    @test_throws ArgumentError sdf[:, :c] .= 1:2
end

@testset "mutating SubDataFrame with assignment to [:, cols]" begin
    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, [:c, :b, :a]] = DataFrame(c=[5, 6], b=[1.0, 2.0], a=[13, 12])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                              b=[11.0, 2.0, 1.0, 14.0, 15.0],
                              c=[21, 6, 5, 24, 25])
        @test tmpa === df.a

        @test_throws ArgumentError sdf[:, [:c, :b, :a]] = DataFrame(d=["c", "d"], b=[1.0, 2.0], a=[13, 12])
        @test_throws ArgumentError sdf[:, [:c, :b, :a]] = DataFrame(a=["c", "d"], b=[1.0, 2.0], c=[13, 12])
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, cols] = DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                              b=[11, 2, 1, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int

        @test_throws ArgumentError sdf[:, cols] = DataFrame(b=[1.0, 2.0], a=[13, 12])
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[:, cols] = DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                              b=[11, 2, 1, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int
        @test_throws ArgumentError sdf[:, cols] = DataFrame(a=[13, 12], b=[1.0, 2.0], c=1)
    end

    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, [:c, :b, :a]] = [100 101 102; 103 104 105]
        @test df == DataFrame(a=[1, 105, 102, 4, 5],
                              b=[11.0, 104, 101, 14.0, 15.0],
                              c=[21, 103, 100, 24, 25])
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int
        @test eltype(df.c) == Int

        @test_throws DimensionMismatch sdf[:, [:c, :b, :a]] = ones(2, 2)
        @test_throws DimensionMismatch sdf[:, [:c, :b, :a]] = ones(1, 3)
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, cols] = [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2, 1, 4, 5],
                              b=[11, 4, 3, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int

        @test_throws DimensionMismatch sdf[:, cols] = ones(1, 3)
        @test_throws DimensionMismatch sdf[:, cols] = ones(3, 1)
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[:, cols] = [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2, 1, 4, 5],
                              b=[11, 4, 3, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int
        @test_throws DimensionMismatch sdf[:, cols] = ones(1, 3)
        @test_throws DimensionMismatch sdf[:, cols] = ones(3, 1)
    end
end

@testset "mutating SubDataFrame with broadcasting assignment to [:, cols]" begin
    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, [:c, :b, :a]] .= DataFrame(c=[100, 101], b=[1.0, 2.0], a=[13, 12])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                              b=[11, 2, 1, 14, 15],
                              c=[21, 101, 100, 24, 25])
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int
        @test eltype(df.c) == Int

        sdf[:, [:c, :b, :a]] .= [100, 200]
        @test df == DataFrame(a=[1, 200, 100, 4, 5],
                              b=[11, 200, 100, 14, 15],
                              c=[21, 200, 100, 24, 25])

        @test_throws ArgumentError sdf[:, [:c, :b, :a]] .= DataFrame(d=["c", "d"], b=[1.0, 2.0], a=[13, 12])
        @test_throws ArgumentError sdf[:, [:c, :b, :a]] .= DataFrame(a=["c", "d"], b=[1.0, 2.0], c=[13, 12])
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, cols] .= DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                              b=[11, 2, 1, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int

        sdf[:, cols] .= [100 200]
        @test df == DataFrame(a=[1, 100, 100, 4, 5],
                              b=[11, 200, 200, 14, 15],
                              c=21:25)

        @test_throws ArgumentError sdf[:, cols] .= DataFrame(b=[1.0, 2.0], a=[13, 12])
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[:, cols] .= DataFrame(a=[13, 12], b=[1.0, 2.0])
        @test df == DataFrame(a=[1, 12, 13, 4, 5],
                              b=[11, 2, 1, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int

        sdf[:, cols] .= 100
        @test df == DataFrame(a=[1, 100, 100, 4, 5],
                              b=[11, 100, 100, 14, 15],
                              c=21:25)

        @test_throws DimensionMismatch sdf[:, cols] .= DataFrame(a=[13, 12], b=[1.0, 2.0], c=1)
    end

    for sel in (:, 1:3)
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, [:c, :b, :a]] .= [100 101 102; 103 104 105]
        @test df == DataFrame(a=[1, 105, 102, 4, 5],
                              b=[11.0, 104, 101, 14.0, 15.0],
                              c=[21, 103, 100, 24, 25])
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int
        @test eltype(df.c) == Int

        @test_throws DimensionMismatch sdf[:, [:c, :b, :a]] .= ones(2, 2)
        @test_throws DimensionMismatch sdf[:, [:c, :b, :a]] .= ones(4, 3)
    end

    for sel in (:, 1:3), cols in (Between(:a, :b), Not(:c), r"[ab]", [true, true, false])
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], sel]
        tmpa = df.a
        sdf[:, cols] .= [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2, 1, 4, 5],
                              b=[11, 4, 3, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int

        @test_throws DimensionMismatch sdf[:, cols] .= ones(4, 3)
        @test_throws DimensionMismatch sdf[:, cols] .= ones(3, 4)
    end

    for cols in (All(), :, Cols(:a, :b))
        df = DataFrame(a=1:5, b=11:15, c=21:25)
        sdf = @view df[[3, 2], 1:2]
        tmpa = df.a
        sdf[:, cols] .= [1.0 3.0; 2.0 4.0]
        @test df == DataFrame(a=[1, 2, 1, 4, 5],
                              b=[11, 4, 3, 14, 15],
                              c=21:25)
        @test tmpa === df.a
        @test eltype(df.a) == Int
        @test eltype(df.b) == Int
        @test_throws DimensionMismatch sdf[:, cols] .= ones(4, 3)
        @test_throws DimensionMismatch sdf[:, cols] .= ones(3, 4)
    end
end

@testset "mutating SubDataFrame with assignment to sdf.col" begin
    df = DataFrame(a=1:3)
    sdf = @view df[[3, 2], :]
    sdf.c = [5, 6]
    @test df ≅ DataFrame(a=1:3, c=[missing, 6, 5])
    sdf.a = [13.0, 12.0]
    @test eltype(sdf.a) === Float64
    @test df ≅ DataFrame(a=[1.0, 12.0, 13.0], c=[missing, 6, 5])

    df = DataFrame(a=1:3)
    sdf = @view df[[3, 2], 1:1]
    @test_throws ArgumentError sdf.c = [5, 6]
    sdf.a = [13.0, 12.0]
    @test eltype(sdf.a) === Float64
    @test df ≅ DataFrame(a=[1.0, 12.0, 13.0])
end

@testset "mutating SubDataFrame with broadcasting assignment to sdf.col" begin
    df = DataFrame(a=1:3)
    sdf = @view df[[3, 2], :]
    sdf.a .= 12.0
    @test eltype(sdf.a) === Int
    @test df ≅ DataFrame(a=[1, 12, 12])

    if VERSION >= v"1.7"
        sdf.c .= 100
        @test df ≅ DataFrame(a=[1, 12, 12], c=[missing, 100, 100])
    else
        @test_throws ArgumentError sdf.c .= 100
    end

    df = DataFrame(a=1:3)
    sdf = @view df[[3, 2], 1:1]
    @test_throws ArgumentError sdf.c = [5, 6]
    sdf.a .= 12.0
    @test eltype(sdf.a) === Int
    @test df ≅ DataFrame(a=[1, 12, 12])
end

@testset "insertcols! for SubDataFrame" begin
    df = DataFrame(a=1:5, b=11:15)
    sdf = @view df[:, 1:end]
    @test_throws ArgumentError insertcols!(sdf, :c => 1)
    @test df == DataFrame(a=1:5, b=11:15)

    df = DataFrame(a=1:5, b=11:15)
    sdf = @view df[:, :]
    @test_throws ArgumentError insertcols!(sdf, :c => 1, copycols=false)
    @test df == DataFrame(a=1:5, b=11:15)
    insertcols!(sdf, :c => 1)
    @test df == DataFrame(a=1:5, b=11:15, c=1)
    @test eltype(df.c) === Union{Int, Missing}
    @test_throws DimensionMismatch insertcols!(sdf, :d => [1])
    insertcols!(sdf, :d => 101:105)
    @test df == DataFrame(a=1:5, b=11:15, c=1, d=101:105)
    @test eltype(df.d) === Union{Int, Missing}

    df = DataFrame(a=1:5, b=11:15)
    sdf = @view df[[3, 2], :]
    insertcols!(sdf, :c => 1)
    @test df ≅ DataFrame(a=1:5, b=11:15, c=[missing, 1, 1, missing, missing])
    @test eltype(df.c) === Union{Int, Missing}
    @test_throws DimensionMismatch insertcols!(sdf, :d => [1])
    insertcols!(sdf, :d => [103, 102])
    @test df ≅ DataFrame(a=1:5, b=11:15, c=[missing, 1, 1, missing, missing],
                         d=[missing, 102, 103, missing, missing])
    @test eltype(df.d) === Union{Int, Missing}

    df = DataFrame(a=1:5, b=11:15)
    sdf = @view df[3:2, :]
    insertcols!(sdf, :c => 1)
    @test df ≅ DataFrame(a=1:5, b=11:15, c=[missing, missing, missing, missing, missing])
    @test eltype(df.c) === Union{Int, Missing}
    @test_throws DimensionMismatch insertcols!(sdf, :d => [1])
    insertcols!(sdf, :d => Int[])
    @test df ≅ DataFrame(a=1:5, b=11:15, c=[missing, missing, missing, missing, missing],
                         d=[missing, missing, missing, missing, missing])
    @test eltype(df.d) === Union{Int, Missing}
end

@testset "select! on SubDataFrame" begin
    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    @test_throws ArgumentError select!(sdf, :c => :b, :b => :c)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)
    select!(sdf, :b => :c, :c => :b)
    @test df == DataFrame(a=1:5,
                          b=[11, 22, 23, 14, 15],
                          c=[21, 12, 13, 24, 25])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    @test_throws ArgumentError select!(sdf, :b => x -> ["b3", "b2"], :c => (x -> ["c3", "c2"]), renamecols=false)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)
    select!(sdf, :c => x -> ["c3", "c2"], :b => (x -> ["b3", "b2"]), renamecols=false)
    @test df == DataFrame(a=1:5,
                          b=[11, "b2", "b3", 14, 15],
                          c=[21, "c2", "c3", 24, 25])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    select!(sdf, :a, :b => :c, :c => :b)
    @test df == DataFrame(a=1:5,
                          c=[21, 12, 13, 24, 25],
                          b=[11, 22, 23, 14, 15])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    select!(sdf, :b => :d, :c => :b)
    @test df ≅ DataFrame(d=[missing, 12, 13, missing, missing],
                         b=[11, 22, 23, 14, 15])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    @test_throws ArgumentError select!(sdf)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    select!(sdf)
    @test df == DataFrame()

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[3:2, [3, 2]]
    @test_throws ArgumentError select!(sdf, :c => (x -> Int[]) => :d)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[3:2, :]
    select!(sdf, :c => (x -> Int[]) => :d)
    @test df ≅ DataFrame(d=missings(Int, 5))
    @test df.d isa Vector{Union{Int, Missing}}
end

@testset "transform! on SubDataFrame" begin
    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    transform!(sdf, :c => :b, :b => :c)
    @test df == DataFrame(a=1:5,
                          b=[11, 22, 23, 14, 15],
                          c=[21, 12, 13, 24, 25])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    transform!(sdf, :b => :c, :c => :b)
    @test df == DataFrame(a=1:5,
                          b=[11, 22, 23, 14, 15],
                          c=[21, 12, 13, 24, 25])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    transform!(sdf, :b => x -> ["b3", "b2"], :c => (x -> ["c3", "c2"]), renamecols=false)
    @test df == DataFrame(a=1:5,
                          b=[11, "b2", "b3", 14, 15],
                          c=[21, "c2", "c3", 24, 25])
    transform!(sdf, :c => x -> ["c3", "c2"], :b => (x -> ["b3", "b2"]), renamecols=false)
    @test df == DataFrame(a=1:5,
                          b=[11, "b2", "b3", 14, 15],
                          c=[21, "c2", "c3", 24, 25])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    transform!(sdf, :b => :c, :c => :b)
    @test df == DataFrame(a=1:5,
                          b=[11, 22, 23, 14, 15],
                          c=[21, 12, 13, 24, 25])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    transform!(sdf, :b => :d, :c => :b)
    @test df ≅ DataFrame(a=1:5,
                         b=[11, 22, 23, 14, 15],
                         c=21:25,
                         d=[missing, 12, 13, missing, missing])

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], [3, 2]]
    transform!(sdf)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[[3, 2], :]
    transform!(sdf)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)

    df = DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[3:2, [3, 2]]
    @test_throws ArgumentError transform!(sdf, :c => (x -> Int[]) => :d)
    @test df == DataFrame(a=1:5, b=11:15, c=21:25)
    sdf = @view df[3:2, :]
    transform!(sdf, :c => (x -> Int[]) => :d)
    @test df ≅ DataFrame(a=1:5, b=11:15, c=21:25, d=missings(Int, 5))
    @test df.d isa Vector{Union{Int, Missing}}
end

@testset "select! on GroupedDataFrame{SubDataFrame}" begin
    df = DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[2:4, [2, 1, 3]]
    gsdf = groupby(sdf, :a)
    @test_throws ArgumentError select!(gsdf, :b => x -> x .+ 100, renamecols=false)
    @test df == DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[2:4, [2, 1]]
    gsdf = groupby(sdf, :a)
    @test_throws ArgumentError select!(gsdf, :b => x -> x .+ 100, renamecols=false)
    @test df == DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[2:4, [1, 3, 2]]
    gsdf = groupby(sdf, :a)
    @test select!(gsdf, :c, :b => x -> x .+ 100, renamecols=false, ungroup=false) === gsdf
    @test df == DataFrame(a=[1, 1, 1, 2, 2, 3],
                          b=[11, 112, 113, 114, 15, 16], c=21:26, d=31:36)

    df = DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[[3, 4, 2], :]
    gsdf = groupby(sdf, :a)
    @test select!(gsdf, :b => x -> x .+ 100, :c => :e, renamecols=false) === sdf
    @test df ≅ DataFrame(a=[1, 1, 1, 2, 2, 3],
                         b=[11, 112, 113, 114, 15, 16],
                         e=[missing, 22, 23, 24, missing, missing])
end

@testset "transform! on GroupedDataFrame{SubDataFrame}" begin
    df = DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[2:4, [2, 1, 3]]
    gsdf = groupby(sdf, :a)
    transform!(gsdf, :b => x -> x .+ 100, renamecols=false)
    @test df == DataFrame(a=[1, 1, 1, 2, 2, 3],
                          b=[11, 112, 113, 114, 15, 16], c=21:26, d=31:36)

    df = DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[2:4, [2, 1]]
    gsdf = groupby(sdf, :a)
    transform!(gsdf, :b => x -> x .+ 100, renamecols=false)
    @test df == DataFrame(a=[1, 1, 1, 2, 2, 3],
                          b=[11, 112, 113, 114, 15, 16], c=21:26, d=31:36)

    df = DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[2:4, [1, 3, 2]]
    gsdf = groupby(sdf, :a)
    @test transform!(gsdf, :c, :b => x -> x .+ 100, renamecols=false, ungroup=false) == gsdf
    @test df == DataFrame(a=[1, 1, 1, 2, 2, 3],
                          b=[11, 112, 113, 114, 15, 16], c=21:26, d=31:36)

    df = DataFrame(a=[1, 1, 1, 2, 2, 3], b=11:16, c=21:26, d=31:36)
    sdf = @view df[[3, 4, 2], :]
    gsdf = groupby(sdf, :a)
    @test transform!(gsdf, :b => x -> x .+ 100, :c => :e, renamecols=false) === sdf
    @test df ≅ DataFrame(a=[1, 1, 1, 2, 2, 3],
                         b=[11, 112, 113, 114, 15, 16],
                         c=21:26, d=31:36,
                         e=[missing, 22, 23, 24, missing, missing])
end

@testset "promote_type tests" begin
    df = DataFrame(a=1:4)
    a = df.a
    sdf = @view df[1:1, :]
    sdf[!, 1] = [0]
    @test df.a == [0, 2, 3, 4]
    @test df.a != a
    @test a == 1:4
    @test eltype(df.a) === Int
    a = df.a
    sdf[!, 1] = [1.5]
    @test df.a == [1.5, 2, 3, 4]
    @test df.a != a
    @test a == [0, 2, 3, 4]
    @test eltype(df.a) === Float64

    # note that CategoricalVector is dropped as
    # similar(::CategoricalVector, String, length)
    # produces a Vector{String}
    df = DataFrame(a=categorical(string.(1:4)))
    sdf = @view df[1:1, :]
    sdf[!, 1] = ["a"]
    @test df.a == ["a", "2", "3", "4"]
    @test df.a isa Vector{String}

    df = DataFrame(a=categorical(string.(1:4)))
    sdf = @view df[1:1, :]
    sdf[!, 1] = categorical(["a"])
    @test df.a isa CategoricalVector{String}
    @test df.a == ["a", "2", "3", "4"]
    @test levels(df.a) == ["1", "2", "3", "4", "a"]

    df = DataFrame(a=1:4)
    a = df.a
    sdf = @view df[1:1, :]
    select!(sdf, :a => (x -> x) => :a)
    @test df.a !== a
    @test df.a == a == 1:4
    @test eltype(df.a) === Int
    a = df.a
    select!(sdf, :a => (x -> [1]) => :a)
    @test df.a !== a
    @test df.a == a == 1:4
    @test eltype(df.a) === Int
    a = df.a
    select!(sdf, :a => (x -> [1.5]) => :a)
    @test df.a == [1.5, 2, 3, 4]
    @test eltype(df.a) === Float64
    @test a == 1:4

    df = DataFrame(a=collect(Any, 1:4))
    a = df.a
    sdf = @view df[1:1, :]
    select!(sdf, :a => (x -> x) => :a)
    @test df.a == a
    @test a == 1:4
    @test df.a !== a
    @test eltype(df.a) === Any
    a = df.a
    select!(sdf, :a => (x -> [1]) => :a)
    @test df.a == a
    @test a == 1:4
    @test df.a !== a
    @test eltype(df.a) === Any
    a = df.a
    select!(sdf, :a => (x -> [1.5]) => :a)
    @test df.a == [1.5, 2, 3, 4]
    @test df.a !== a
    @test a == 1:4
    @test eltype(df.a) === Any

    df = DataFrame(a=PooledArray(1:4))
    a = df.a
    sdf = @view df[1:1, :]
    select!(sdf, :a => (x -> x) => :a)
    @test df.a == a
    @test df.a !== a
    @test a == 1:4
    # we keep PooledVector though as similar of PooledVector is PooledVector
    @test df.a isa PooledVector{Int}
    select!(sdf, :a => (x -> [1]) => :a)
    @test df.a == a
    @test a == 1:4
    @test df.a !== a
    @test df.a isa PooledVector{Int}
    a = df.a
    select!(sdf, :a => (x -> [1.5]) => :a)
    @test df.a == [1.5, 2, 3, 4]
    @test df.a !== a
    @test a == 1:4
    @test df.a isa PooledVector{Float64}
end

end # module


text:
__precompile__()

module LiterateOrg

const codefile = joinpath(dirname(@__FILE__), "literate_org_tangled_code.jl")
if isfile(codefile)
    include(codefile)
else
    error("LiterateOrg not properly installed. Please run Pkg.build(\"LiterateOrg\") then restart Julia.")
end

end # module

ext: jl


text:
"import and export"
macro imexport(body)
    importf = body.head
    @assert importf == :using || importf == :import
    args = body.args[]
    @assert args.head == :(:)
    ex = Expr(:export, map(x->x.args[], args.args[2:end])...)
    return quote
        $body
        $ex
    end
end


text:
numbers,folds = split(read("input.txt", String), "\n\n")
re = r"(\w)=(\d+)"

xys = @. map(x -> parse(Int, x) + 1, Tuple(split($split(numbers, '\n'), ',')))
N = maximum(reduce(hcat, collect.(xys)))
w,h = maximum(map(t -> t[1], xys)), maximum(map(t -> t[2], xys))
field = falses((w,h))
field[CartesianIndex.(xys)] .= true

for fold = eachline(IOBuffer(folds))
    local w,h = size(field)
    axis,pos = match(re, fold)
    pos = parse(Int, pos) + 1
    if axis == "y"
        A,B = view(field, :, 1:pos-1), view(field, :, h:-1:(pos+1))
    elseif axis == "x"
        A,B = view(field, 1:pos-1, :), view(field, w:-1:(pos+1), :)
    end
    global field = A .| B
    println(sum(field))
    break
end


text:
__precompile__()

# sparse matrix with dictionary (a.k.a. hash matrix)

# The sparse dictionary matrix (smd) is assumed to be used as
# temporary storage to set up a CSC matrix. So this package
# only supports limited functions for the purpose.

# Some functions are derived from sparsematrix.jl.

module SparseMatrixDicts

import LinearAlgebra: Symmetric
import SparseArrays: AbstractSparseArray, SparseMatrixCSC,
                     nnz, nzrange, sparse, findnz

import Base: size, show, setindex!, getindex, copy, vec, transpose,
             haskey, fill, fill!, similar

export SparseMatrixDict

"""
    SparseMatrixDict{Tv,Ti<:Integer} <: AbstractSparseMatrix{Tv,Ti}
    s = SparseMatrixDict{Tv,Ti}(m,n)

Matrix type for storing sparse matrices in the
Dictionary format as Dict{Tuple{Ti,Ti},Tv}.
The constructor accepts two arguments: the number of rows (m) and
the number of columns (n).
"""
struct SparseMatrixDict{Tv,Ti<:Integer} <: AbstractSparseArray{Tv,Ti,2}
   m::Int                      # Number of rows
   n::Int                      # Number of columns
   dict::Dict{Tuple{Ti,Ti},Tv} # Stored values

   function SparseMatrixDict{Tv,Ti}(m::Integer, n::Integer) where {Tv,Ti<:Integer}
      m < 0 && throw( ArgumentError("invalid rows") )
      n < 0 && throw( ArgumentError("invalid columns") )
      new(Int(m), Int(n), Dict{Tuple{Ti,Ti},Tv}())
   end
   function SparseMatrixDict{Tv,Ti}(m::Integer, n::Integer, dict::Dict{Tuple{Ti,Ti},Tv}) where {Tv,Ti<:Integer}
      m < 0 && throw( ArgumentError("invalid rows") )
      n < 0 && throw( ArgumentError("invalid columns") )
      new(Int(m), Int(n), dict)
   end
end

@inbounds function SparseMatrixDict{Tv,Ti}(S::SparseMatrixCSC{Tv,Tj}) where {Tv,Ti<:Integer,Tj<:Integer}
   (m, n) = size(S)
   A = SparseMatrixDict{Tv,Ti}(m, n)
   for Sj in 1:S.n
      for Sk in nzrange(S, Sj)
         Si = S.rowval[Sk]
         Sv = S.nzval[Sk]
         A[Si, Sj] = Sv
      end
   end
   return A
end

@inbounds function SparseMatrixDict{Tv,Ti}(M::AbstractArray) where {Tv,Ti<:Integer}
   (m, n) = size(M)
   #(I, J, V) = findnz(M)
   (I, J, V) = (begin; I=findall(!iszero,M); (getindex.(I, 1), getindex.(I, 2), M[I]); end)
   nnz = length(I)
   A = SparseMatrixDict{Tv,eltype(I)}(m, n)
   for i=1:nnz
      A[(I[i],J[i])] = convert(Tv,V[i])
   end
   return A
end

# the default constructor
SparseMatrixDict(m::Integer, n::Integer) =
   SparseMatrixDict{Float64,Int64}(m, n)
SparseMatrixDict(S::SparseMatrixCSC{Tv,Ti}) where {Tv,Ti<:Integer} =
   SparseMatrixDict{Tv,Ti}(S)
SparseMatrixDict{Tv}(M::AbstractArray) where {Tv} =
   SparseMatrixDict{Tv,Int64}(M)
SparseMatrixDict(M::AbstractArray{Tv}) where {Tv} =
   SparseMatrixDict{Tv,Int64}(M)

# output

function show(io::IO, ::MIME"text/plain", A::SparseMatrixDict)
   xnnz = nnz(A)
   print(io, A.m, "×", A.n, " ", typeof(A), " with ", xnnz, " stored ",
         xnnz == 1 ? "entry" : "entries")
   if xnnz != 0
      print(io, ":")
      show(io, A)
   end
end

show(io::IO, A::SparseMatrixDict) = Base.show(convert(IOContext, io), A::SparseMatrixDict)
function show(io::IOContext, A::SparseMatrixDict)
   if nnz(A) == 0
      return show(io, MIME("text/plain"), A)
   end
   for key in sort(collect(keys(A.dict)))
      print(io, "\n  [", key[1], ", ", key[2], "]  =  ", A.dict[key])
   end
end

# standard functions

copy(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer} = SparseMatrixDict{Tv,Ti}(A.m, A.n, copy(A.dict))
size(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer} = (A.m, A.n)
nnz(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer} = A.dict.count
haskey(A::SparseMatrixDict{Tv,Ti},idx::Tuple{Ti,Ti}) where {Tv,Ti<:Integer} = haskey(A.dict,idx)
haskey(A::SparseMatrixDict{Tv,Ti},i::Ti,j::Ti) where {Tv,Ti<:Integer} = haskey(A.dict,(i,j))
haskey(A::SparseMatrixDict{Tv,Ti},i,j) where {Tv,Ti<:Integer} = haskey(A.dict,(convert(Ti,i),convert(Ti,j)))

function vec(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer}
   v::Vector{Tv} = zeros(Tv,A.m*A.n)
   k::Int = 0
   for j=1:A.n
      for i=1:A.m
         k = k + 1
         @inbounds v[k] = A[(i,j)]
      end
   end
   return v
end

function transpose(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer}
   B = SparseMatrixDict{Tv,Ti}(A.n, A.m)
   for key in collect(keys(A.dict))
      @inbounds B[(key[2],key[1])] = A[key]
   end
   return B
end

function fill(v, A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer}
   B = copy(A)
   x = convert(Tv,v)
   for key in collect(keys(B.dict))
      B[key] = x
   end
   return B
end

function fill!(v, A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer}
   x = convert(Tv,v)
   for key in collect(keys(A.dict))
      A[key] = x
   end
end

similar(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer} = fill(0.0, A)

function findnz(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer}
   nnzA = nnz(A)
   I = zeros(Int, nnzA)
   J = zeros(Int, nnzA)
   NZs = Vector{Tv}(undef, nnzA)
   #NZs = Vector{Tv}(nnzA)
   cnt = 1
   if nnzA>1
      for (i,j) in collect(keys(A.dict))
         I[cnt] = i
         J[cnt] = j
         NZs[cnt] = A[(i,j)]
         cnt += 1
      end
   end
   return (I, J, NZs)
end

#function findnz(A::AbstractMatrix)
#   return (begin; I=findall(!iszero,A); (getindex.(I, 1), getindex.(I, 2), A[I]); end)
#end

function sparse(A::SparseMatrixDict{Tv,Ti}) where {Tv,Ti<:Integer}
   (I, J, NZs) = findnz(A)
   return sparse(I, J, NZs)
end

# setindex

function setindex!(A::SparseMatrixDict{Tv,Ti}, v, i, j) where {Tv,Ti<:Integer}
   setindex!( A, convert(Tv, v), (convert(Ti,i),convert(Ti, j)) )
end
function setindex!(A::SparseMatrixDict{Tv,Ti}, v::Tv, i::Ti, j::Ti) where {Tv,Ti<:Integer}
   setindex!( A, v, (i,j) )
end
function setindex!(A::SparseMatrixDict{Tv,Ti}, v, idx::Tuple{Any,Any}) where {Tv,Ti<:Integer}
   setindex!( A, convert(Tv, v), (convert(Ti,idx[1]),convert(Ti,idx[2])) )
end
function setindex!(A::SparseMatrixDict{Tv,Ti}, v::Tv, idx::Tuple{Ti,Ti}) where {Tv,Ti<:Integer}
   if !((1 <= idx[1] <= A.m) & (1 <= idx[2] <= A.n))
      throw(BoundsError(A, idx))
   end
   A.dict[idx] = v
end

function setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number,
   I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer}) where {Tv,Ti<:Integer}
   if isempty(I) || isempty(J); return A; end
   if (I[1] < 1 || I[end] > A.m) || (J[1] < 1 || J[end] > A.n)
      throw(BoundsError(A, (I, J)))
   end
   x = convert(Tv,v)
   for j=collect(J)
      for i=collect(I)
         A[(i,j)] = x
      end
   end
end

setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, I::AbstractVector{<:Integer}, j::Integer) where {Tv,Ti<:Integer} =
   setindex!(A, v, I, j:j)
setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, i::Integer, J::AbstractVector{<:Integer}) where {Tv,Ti<:Integer} =
   setindex!(A, v, i:i, J)
setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, I::AbstractVector{<:Integer}, ::Colon) where {Tv,Ti<:Integer} =
   setindex!(A, v, I, 1:size(A,2))
setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, ::Colon, J::AbstractVector{<:Integer}) where {Tv,Ti<:Integer} =
   setindex!(A, v, 1:size(A,1), J)
setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, i::Integer, ::Colon) where {Tv,Ti<:Integer} =
   setindex!(A, v, i:i, 1:size(A,2))
setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, ::Colon, j::Integer) where {Tv,Ti<:Integer} =
   setindex!(A, v, 1:size(A,1), j:j)
setindex!(A::SparseMatrixDict{Tv,Ti}, v::Number, ::Colon, ::Colon) where {Tv,Ti<:Integer} =
   setindex!(A, v, 1:size(A,1), 1:size(A,2))

function setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv},
   I::AbstractVector{<:Integer}, J::AbstractVector{<:Integer}) where {Tv,Ti<:Integer}
   if (size(B,1)!=length(I) || size(B,2)!=length(J))
      throw(DimensionMismatch("Cannot do this"))
   end
   if (I[1] < 1 || I[end] > A.m) || (J[1] < 1 || J[end] > A.n)
      throw(BoundsError(A, (I, J)))
   end
   if typeof(B)<:AbstractSparseArray
      (IB, JB, NZs) = findnz(B)
      n = length(I)
      for k=1:n
         i = I[IB[k]]
         j = J[JB[k]]
         key = (i,j)
         if !haskey(A.dict,key) && iszero(NZs[k])
            # skip
         else
            A[key] = NZs[k]
         end
      end
   else
      p = length(I)
      q = length(J)
      for j=1:q
         for i=1:p
            key = (I[i],J[j])
            if !haskey(A.dict,key) && iszero(B[i,j])
               # skip
            else
               A[key] = B[i,j]
            end
         end
      end
   end
end

setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, I::AbstractVector{<:Integer}, j::Integer) where {Tv,Ti<:Integer} =
   setindex!(A, B, I, j:j)
setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, i::Integer, J::AbstractVector{<:Integer}) where {Tv,Ti<:Integer} =
   setindex!(A, B, i:i, J)
setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, I::AbstractVector{<:Integer}, ::Colon) where {Tv,Ti<:Integer} =
   setindex!(A, B, I, 1:size(A,2))
setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, ::Colon, J::AbstractVector{<:Integer}) where {Tv,Ti<:Integer} =
   setindex!(A, B, 1:size(A,1), J)
setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, i::Integer, ::Colon) where {Tv,Ti<:Integer} =
   setindex!(A, B, i:i, 1:size(A,2))
setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, ::Colon, j::Integer) where {Tv,Ti<:Integer} =
   setindex!(A, B, 1:size(A,1), j:j)
setindex!(A::SparseMatrixDict{Tv,Ti}, B::AbstractArray{Tv}, ::Colon, ::Colon) where {Tv,Ti<:Integer} =
   setindex!(A, B, 1:size(A,1), 1:size(A,2))

# for symmetric matrix
#
# In general, it should be avoided to put off-diagonals in a symmetric matrix
# because the result is not unique (several possible implementations there).
# Here, this function simply ignores the input if the index is out of range.
#
function setindex!(SA::Symmetric{Tv,SparseMatrixDict{Tv,Ti}}, v, i, j) where {Tv,Ti<:Integer}
   setindex!( SA, convert(Tv, v), (convert(Ti,i),convert(Ti, j)) )
end
function setindex!(SA::Symmetric{Tv,SparseMatrixDict{Tv,Ti}}, v::Tv, i::Ti, j::Ti) where {Tv,Ti<:Integer}
   setindex!( SA, v, (i,j) )
end
function setindex!(SA::Symmetric{Tv,SparseMatrixDict{Tv,Ti}}, v, idx::Tuple{Any,Any}) where {Tv,Ti<:Integer}
   setindex!( SA, convert(Tv, v), (convert(Ti,idx[1]),convert(Ti,idx[2])) )
end
function setindex!(SA::Symmetric{Tv,SparseMatrixDict{Tv,Ti}}, v::Tv, idx::Tuple{Ti,Ti}) where {Tv,Ti<:Integer}
   if SA.uplo=='U' && idx[1]<=idx[2]
      setindex!(SA.data, v, idx)
   elseif idx[1]>=idx[2]
      setindex!(SA.data, v, idx)
   end
end

# getindex

function getindex(A::SparseMatrixDict{Tv,Ti}, i, j) where {Tv,Ti<:Integer}
   return getindex( A, (convert(Ti,i),convert(Ti,j)) )
end
function getindex(A::SparseMatrixDict{Tv,Ti}, i::Ti, j::Ti) where {Tv,Ti<:Integer}
   return getindex( A, (i,j) )
end
function getindex(A::SparseMatrixDict{Tv,Ti}, idx::Tuple{Any,Any}) where {Tv,Ti<:Integer}
   return getindex( A, (convert(Ti,t[1]),convert(Ti,t[2])) )
end
function getindex(A::SparseMatrixDict{Tv,Ti}, idx::Tuple{Ti,Ti}) where {Tv,Ti<:Integer}
   if !((1 <= idx[1] <= A.m) & (1 <= idx[2] <= A.n))
      throw(BoundsError(A, idx))
   end
   if haskey(A.dict,idx)
      return A.dict[idx]
   else
      return convert(Tv,0.0)
   end
end

function getindex(A::SparseMatrixDict{Tv,Ti}, I::AbstractVector, J::AbstractVector) where {Tv,Ti<:Integer}
   (m, n) = size(A)
   if !isempty(J)
      minj, maxj = extrema(J)
      ((minj < 1) || (maxj > n)) && throw(BoundsError())
   end
   if !isempty(I)
      mini, maxi = extrema(I)
      ((mini < 1) || (maxi > m)) && throw(BoundsError())
   end
   if isempty(I) || isempty(J) || (0 == nnz(A))
      error("empty matrix")
   end
   return getindex_general(A, I, J)
end

@inbounds function getindex_general(A::SparseMatrixDict{Tv,Ti}, I::AbstractVector, J::AbstractVector) where {Tv,Ti<:Integer}
   p = length(I)
   q = length(J)
   B = SparseMatrixDict{Tv,Ti}(p,q)
   for j=1:q
      for i=1:p
         idx = (I[i],J[j])
         if haskey(A.dict,idx)
            B[(i,j)] = A[idx]
         end
      end
   end
   return B
end

getindex(A::SparseMatrixDict{Tv,Ti}, i::Integer, J::AbstractVector) where {Tv,Ti<:Integer} =
   getindex_general(A, i:i, J)
getindex(A::SparseMatrixDict{Tv,Ti}, I::AbstractVector, j::Integer) where {Tv,Ti<:Integer} =
   getindex_general(A, I, j:j)
getindex(A::SparseMatrixDict{Tv,Ti}, ::Colon, J::AbstractVector) where {Tv,Ti<:Integer} =
   getindex_general(A, 1:size(A,1), J)
getindex(A::SparseMatrixDict{Tv,Ti}, I::AbstractVector, ::Colon) where {Tv,Ti<:Integer} =
   getindex_general(A, I, 1:size(A,2))
getindex(A::SparseMatrixDict, ::Colon, ::Colon) = copy(A)
getindex(A::SparseMatrixDict, i::Integer, ::Colon) = getindex(A, i, 1:size(A,2))
getindex(A::SparseMatrixDict, ::Colon, j::Integer) = getindex(A, 1:size(A,1), j)

#getindex(A::SparseMatrixDict, I::AbstractRange{<:Integer}, J::AbstractVector{Bool}) = A[I,findall(J)]
#getindex(A::SparseMatrixDict, I::Integer, J::AbstractVector{Bool}) = A[I,findall(J)]
#getindex(A::SparseMatrixDict, I::AbstractVector{Bool}, J::Integer) = A[findall(I),J]
#getindex(A::SparseMatrixDict, I::AbstractVector{Bool}, J::AbstractVector{Bool}) = A[findall(I),findall(J)]
#getindex(A::SparseMatrixDict, I::AbstractVector{<:Integer}, J::AbstractVector{Bool}) = A[I,findall(J)]
#getindex(A::SparseMatrixDict, I::AbstractVector{Bool}, J::AbstractVector{<:Integer}) = A[findall(I),J]

#getindex(A::SparseMatrixDict, I::AbstractRange{<:Integer}, J::AbstractVector{Bool}) = A[I,find(J)]
getindex(A::SparseMatrixDict, I::Integer, J::AbstractVector{Bool}) = A[I,find(J)]
getindex(A::SparseMatrixDict, I::AbstractVector{Bool}, J::Integer) = A[find(I),J]
getindex(A::SparseMatrixDict, I::AbstractVector{Bool}, J::AbstractVector{Bool}) = A[find(I),find(J)]
getindex(A::SparseMatrixDict, I::AbstractVector{<:Integer}, J::AbstractVector{Bool}) = A[I,find(J)]
getindex(A::SparseMatrixDict, I::AbstractVector{Bool}, J::AbstractVector{<:Integer}) = A[find(I),J]

end


text:
###########################################################################
# Cross-correlation
#
# Function carries out cross-correlation to determine the bearing
# angles is the vector of angles corresponding to the samples
#
# Returns vector containing shifts at every degree
#	ith index corresponds to (i-1) shift
#
# The idea is that the reference is of length 360,
#  but the sample need not be.
# Therefore, a vector of sampled angles is provided.
# TODO: give this a thorough scrubbing. It is important to me that it works
function bearing_cc(angles, gains, ref_angles, ref_gains)
	cval = 0.0
	max_cval = -Inf
	max_i = 0.0

	norm_gains = copy(gains)
	normalize!(norm_gains)

	for i = 0:359
		cval = cross_correlate(angles, norm_gains, ref_angles, ref_gains, i)
		if (cval > max_cval)
			max_cval = cval
			max_i = i
		end
	end
	return 360.0 - max_i
end
# returns max correlation coefficient
function bearing_ccn(angles, gains, ref_angles, ref_gains)
	cval = 0.0
	max_cval = -Inf
	max_i = 0.0

	norm_gains = copy(gains)
	normalize!(norm_gains)

	for i = 0:359
		cval = cross_correlate(angles, norm_gains, ref_angles, ref_gains, i)
		if (cval > max_cval)
			max_cval = cval
			max_i = i
		end
	end
	return max_cval
end

function bearing_cc(angles, gains, ref_file::AbstractString)
	ref_gp = GainPattern(ref_file)
	return bearing_cc(angles, gains, ref_gp.angles, ref_gp.meangains)
end

# Returns array of bearing values
# Go through all samples in test_file
# Assumes all samples are the same length in test_gp
function bearing_cc(test_file::AbstractString, ref_file::AbstractString)
	test_gp = GainPattern(test_file)
	ref_gp = GainPattern(ref_file)

	num_samples = length(test_gp.angles)
	num_exp = length(test_gp.samples[1])

	aoa_array = zeros(num_exp)
	angles = zeros(num_samples)
	gains = zeros(num_samples)
	for i = 1:num_exp
		for j = 1:num_samples
			# Loop through all samples in this experiment...
			angles[j] = test_gp.angles[j]
			gains[j] = test_gp.samples[j][i]
		end
		aoa_array[i] = bearing_cc(angles, gains, ref_gp.angles, ref_gp.meangains)
	end
	return aoa_array
end

function bearing_ccn(test_file::AbstractString, ref_file::AbstractString)
	test_gp = GainPattern(test_file)
	ref_gp = GainPattern(ref_file)

	num_samples = length(test_gp.angles)
	num_exp = length(test_gp.samples[1])

	aoa_array = zeros(num_exp)
	angles = zeros(num_samples)
	gains = zeros(num_samples)
	for i = 1:num_exp
		for j = 1:num_samples
			# Loop through all samples in this experiment...
			angles[j] = test_gp.angles[j]
			gains[j] = test_gp.samples[j][i]
		end
		aoa_array[i] = bearing_ccn(angles, gains, ref_gp.angles, ref_gp.meangains)
	end
	return aoa_array
end

# Helper function to perform cross-correlation
# This is the cross-correlation function used in Graefenstein 2009
# Handle null observations
function cross_correlate(angles, gains, ref_angles, ref_gains, shift)
	c = 0.0
	len = length(angles)
	for i = 1:len
		if validgain(gains[i])
			c += gains[i] * interp_linear(angles[i], shift, ref_angles, ref_gains)
		end
	end
	return c
end


text:
function representable_ints(nbits)
    return 1:(2^(nbits-1)-1)
end

function representable_floats(nbits,ebits)
    #= returns an array of all representable positive floats exclusive 0 and ComplexInfinity.
    nbits is the total number of bits (sign,exponent,significand)
    ebits is the number of exponent bits
    =#

    # derived constants
    bias = 2^(ebits-1) - 1
    sbits = nbits-ebits-1     # number of significand bits (minus one for sign bit)

    repr_floats = []           # represented as 000...0000

    # subnormal numbers (exponent is 0)
    for f = 1:2^sbits-1
        append!(repr_floats,2.0^(1-bias)*(f/2^sbits))
    end

    # all other numbers
    for e = 1:2^ebits-2     # the exponent cannot be 11...111 this represents NaN
        for f = 0:2^sbits-1
            append!(repr_floats,2.0^(e-bias)*(1.0 + f/2^sbits))
        end
    end

    return Float64.(repr_floats)
end

function wcdp_posit(plist)
    p_am = (plist[1:end-1]+plist[2:end])/2.
    p_wda = -log10.(abs.(log10.(p_am./plist[1:end-1])))

    # extend first and last point, taking no overflow/underflow into account
    p0 = plist[1]/16    # something much smaller than minpos
    pinf = plist[end]*16    # something much bigger than maxpos

    p_wda_0 = -log10.(abs.(log10.(p0/plist[2]))) # worst-case decimal accuracy for these extreme values
    p_wda_inf = -log10.(abs.(log10.(pinf/plist[end])))

    # worst-case decimal accuracy of interpolated on minpos/maxpos
    p_wda_minpos = p_wda_0 + log10(plist[1]/p0)/log10(p0/p_am[1])*(p_wda_0-p_wda[1])
    p_wda_maxpos = p_wda_inf + log10(plist[end]/pinf)/log10(pinf/p_am[end])*(p_wda_inf-p_wda[end])

    p_wda = vcat(p_wda_0,p_wda_minpos,p_wda,p_wda_maxpos,p_wda_inf)
    p_am = vcat(p0,plist[1],p_am,plist[end],pinf)

    return p_am,p_wda,plist
end

function wcdp_float(flist)

    f_am = (flist[1:end-1]+flist[2:end])/2.
    f_wda = -log10.(abs.(log10.(f_am./flist[1:end-1])))

    # extend with zeros due to overflow
    f_wda = vcat(0.55,f_wda)        # extrapolate somehow
    f_am = vcat(flist[1],f_am)

    return f_am,f_wda
end

function wcdp_approx(flist)

    f_am = (flist[1:end-1]+flist[2:end])/2.
    f_wda = -log10.(abs.(log10.(f_am./flist[1:end-1])))

    return f_am,f_wda
end

function wc_dec_acc_int(nbits)

    # assume rounding mode down
    # somehow interpolate with 0.13 onto the smallest representable number 1...

    i_am = [1.,2.,2^(nbits-1)-1]
    i_wda = [0.13,-log10(abs(log10(2))),-log10(abs(log10((2^(nbits-1)-1)/(2^(nbits-1)-2))))]

    return i_am,i_wda
end

decprec(xe,xr) = -log10.(abs.(log10.(xe./xr)))


text:
module WignerD

using OffsetArrays
using LinearAlgebra
using HalfIntegers
using StructArrays
using LoopVectorization

export wignerd!
export wignerd
export wignerD!
export wignerD

abstract type SpecialCoLatitudes <: Real end
Base.promote_rule(::Type{<:SpecialCoLatitudes}, T::Type{<:Real}) = promote_type(Float64, T)
Base.AbstractFloat(p::SpecialCoLatitudes) = Float64(p)

"""
    Equator

Alias for `β = π/2` in spherical polar coordinates.
"""
struct Equator <: SpecialCoLatitudes end
"""
    NorthPole

Alias for `β = 0` in spherical polar coordinates.
"""
struct NorthPole <: SpecialCoLatitudes end
"""
    SouthPole

Alias for `β = π` in spherical polar coordinates.
"""
struct SouthPole <: SpecialCoLatitudes end

Base.Float64(::NorthPole) = zero(Float64)
Base.Float64(::SouthPole) = Float64(pi)
Base.Float64(::Equator) = Float64(π/2)

Base.cos(::NorthPole) = one(Float64)
Base.cos(::SouthPole) = -one(Float64)
Base.sin(::Union{NorthPole, SouthPole}) = zero(Float64)

Base.cos(::Equator) = zero(Float64)
Base.sin(::Equator) = one(Float64)

# Returns exp(i α π/2) = cos(α π/2) + i*sin(α π/2) for integer α
function _sincos(α::Integer, ::Equator)
    αmod4 = mod(Int(α), 4)
    if αmod4 == 0
        res = ComplexF64(1, 0)
    elseif αmod4 == 1
        res = ComplexF64(0, 1)
    elseif αmod4 == 2
        res = ComplexF64(-1, 0)
    elseif αmod4 == 3
        res = ComplexF64(0, -1)
    end
    c, s = real(res), imag(res)
    return s, c
end
function _sincos(α::HalfInteger, ::Equator)
    αmod4 = mod(Int(2α), 8)
    if αmod4 == 0
        res = ComplexF64(1, 0)
    elseif αmod4 == 1
        res = ComplexF64(1, 1)/√2
    elseif αmod4 == 2
        res = ComplexF64(0, 1)
    elseif αmod4 == 3
        res = ComplexF64(-1, 1)/√2
    elseif αmod4 == 4
        res = ComplexF64(-1, 0)
    elseif αmod4 == 5
        res = ComplexF64(-1, -1)/√2
    elseif αmod4 == 6
        res = ComplexF64(0, -1)
    elseif αmod4 == 7
        res = ComplexF64(1, -1)/√2
    end
    c, s = real(res), imag(res)
    return s, c
end

#########################################################################
# Wrapper array to translate the indices
#########################################################################

# This is deliberately not exposed, as indexing into half-integer arrays is not supported in general
struct WignerMatrix{T, J <: Union{Integer, HalfInteger}, A<:AbstractMatrix{T}} <: AbstractMatrix{T}
    j :: J
    D :: A

    function WignerMatrix{T, J, A}(j::J, D::A) where {T, J<:Union{Integer, HalfInteger}, A<:AbstractMatrix{T}}
        @assert j >= 0 "j must be >= 0"
        @assert size(D, 1) == size(D, 2) == 2j+1 "size of matrix must be $(2j+1)×$(2j+1)"
        Base.has_offset_axes(D) && throw(ArgumentError("D must have 1-based indices"))
        new{T, J, A}(j, D)
    end
end

function WignerMatrix(j::Integer, D::AbstractMatrix{T}) where {T}
    D_noof = OffsetArrays.no_offset_view(D)
    WignerMatrix{T, typeof(j), typeof(D_noof)}(j, D_noof)
end
function WignerMatrix(j::Real, D::AbstractMatrix{T}) where {T}
    j_hi = HalfInteger(j)
    D_noof = OffsetArrays.no_offset_view(D)
    WignerMatrix{T, typeof(j_hi), typeof(D_noof)}(j_hi, D_noof)
end

Base.IndexStyle(::Type{<:WignerMatrix{<:Any, <:Any, A}}) where {A} = IndexStyle(A)
Base.parent(w::WignerMatrix) = w.D
Base.size(w::WignerMatrix) = size(parent(w))
_index(j, m) = (x = Integer(j + m); x + oneunit(x))
# CartesianIndexing
Base.@propagate_inbounds Base.getindex(w::WignerMatrix, m::Real, n::Real) = parent(w)[_index(w.j, m), _index(w.j, n)]
# CartesianIndexing
Base.@propagate_inbounds Base.setindex!(w::WignerMatrix, val, m::Real, n::Real) = (parent(w)[_index(w.j, m), _index(w.j, n)] = val; w)

_unitrange(a::Integer, b::Integer) = a:b
_unitrange(a::Real, b::Real) = HalfInteger(a):HalfInteger(b)
_half_or_one_to(j::Integer) = _unitrange(1, j)
function _half_or_one_to(j::Real)
    r = reverse(2HalfInt(j):-2:1)
    half(first(r)):half(last(r))
end
_indrange(j::Real) = _unitrange(-j, j)
_matrixsize(j::Real) = (Int(2j + 1), Int(2j + 1))

#########################################################################
# Dictionary to cache the eigenvectors of Jy
# stores (2j+1) => eigvecs(Jy(j))
#########################################################################

const JyEigenDict = Dict(UInt(1) => StructArray{ComplexF64}((ones(1,1), zeros(1,1))))

##########################################################################
# Wigner d matrix
##########################################################################

# Matrix elements of Jy in the eigenbasis of Jz
X(j, n) = sqrt((j + n)*(j-n+1))

function coeffi(j, Jy = zeros(ComplexF64, _matrixsize(j)))
    @assert eltype(Jy) == ComplexF64 "preallocated Jy matrix must have an element type of ComplexF64"
    @assert size(Jy) == (2j + 1, 2j + 1) "preallocated Jy matrix must be of size ($(2j+1), $(2j+1))"

    fill!(Jy, zero(eltype(Jy)))
    for (m, i) in enumerate(diagind(Jy, 1))
        Jy[i] = -X(j, -j + m)/2im
    end
    return Hermitian(Jy)
end

_offsetmatrix(j::Integer, D::AbstractMatrix) = OffsetArray(D, -j:j, -j:j)
_offsetmatrix(j::Real, D::AbstractMatrix) = WignerMatrix(j, D)

_eigen_sorted!(Jy_filled) = eigen!(Jy_filled, sortby = identity)

function Jy_eigen(j, Jy)
    key = UInt(2j + 1)
    if key in keys(JyEigenDict)
        return _indrange(j), JyEigenDict[key]
    end

    Jy_filled = coeffi(j, Jy)
    _, v = _eigen_sorted!(Jy_filled)

    # Store the eigenvectors along rows
    vs = StructArray(permutedims(v))
    JyEigenDict[key] = vs
    return _indrange(j), vs
end
function Jy_eigen(j)
    key = UInt(2j + 1)
    if key in keys(JyEigenDict)
        return _indrange(j), JyEigenDict[key]
    end

    Jy = zeros(ComplexF64, _matrixsize(j))
    Jy_eigen(j, Jy)
end

"""
    wignerdjmn(j, m, n, β::Real, [Jy::AbstractMatrix{ComplexF64}])

Evaluate the Wigner d-matrix element ``d^j_{m,n}(β)``. Optionally a pre-allocated matrix `Jy` may be provided,
which must be of size `(2j+1, 2j+1)`.
"""
wignerdjmn(j, m, n, β::Real) = wignerdjmn(j, m, n, β, Jy_eigen(j)...)
wignerdjmn(j, m, n, β::Real, Jy) = wignerdjmn(j, m, n, β, Jy_eigen(j, Jy)...)
function wignerdjmn(j, m, n, β::Real, λ, v)
    dj_m_n = zero(Float64)

    mind = Int(m + j + 1)
    nind = Int(n + j + 1)

    @assert mind in axes(v, 2) "m = $m (mind = $mind) is inconsitent with axes(v, 2) = $(axes(v, 2))"
    @assert nind in axes(v, 2) "n = $n (nind = $nind) is inconsitent with axes(v, 2) = $(axes(v, 2))"
    @assert size(v, 1) == 2j+1 "size(v, 1) is not $(2j+1) for j = $j"

    jr = _indrange(j)
    @turbo for μind in eachindex(jr)
        μ = jr[μind]
        s, c = sincos(-μ * float(β))
        T1 = v.re[μind, mind] * v.re[μind, nind] + v.im[μind, mind] * v.im[μind, nind]
        T2 = v.im[μind, mind] * v.re[μind, nind] - v.re[μind, mind] * v.im[μind, nind]
        dj_m_n += c * T1 - s * T2
    end

    dj_m_n
end

wignerdjmn(j, m, n, β::NorthPole) = wignerdjmn(j, m, n, β, nothing, nothing)
wignerdjmn(j, m, n, β::NorthPole, Jy) = wignerdjmn(j, m, n, β)
function wignerdjmn(j, m, n, β::NorthPole, λ, v)
    (m == n) ? one(Float64) : zero(Float64)
end

wignerdjmn(j, m, n, β::SouthPole) = wignerdjmn(j, m, n, β, nothing, nothing)
wignerdjmn(j, m, n, β::SouthPole, Jy) = wignerdjmn(j, m, n, β)
function wignerdjmn(j, m, n, β::SouthPole, λ, v)
    (m == -n) ? iseven(Int(j - n)) ? Float64(1) : Float64(-1) : zero(Float64)
end

wignerdjmn(j, m, n, β::Equator) = wignerdjmn(j, m, n, β, Jy_eigen(j)...)
wignerdjmn(j, m, n, β::Equator, Jy) = wignerdjmn(j, m, n, β, Jy_eigen(j, Jy)...)
function wignerdjmn(j, m, n, β::Equator, λ, v)
    dj_m_n = zero(Float64)

    mind = Int(m + j + 1)
    nind = Int(n + j + 1)

    @assert mind in axes(v, 2) "m = $m (mind = $mind) is inconsitent with axes(v, 2) = $(axes(v, 2))"
    @assert nind in axes(v, 2) "n = $n (nind = $nind) is inconsitent with axes(v, 2) = $(axes(v, 2))"
    @assert size(v, 1) == 2j+1 "size(v, 1) is not $(2j+1) for j = $j"

    if !((isodd(Int(j + m)) && n == 0) || (isodd(Int(j + n)) && m == 0))
        jr = _indrange(j)
        @turbo for μind in eachindex(jr)
            μ = jr[μind]
            s, c = sincos(-μ * float(β))
            T1 = v.re[μind, mind] * v.re[μind, nind] + v.im[μind, mind] * v.im[μind, nind]
            T2 = v.im[μind, mind] * v.re[μind, nind] - v.re[μind, mind] * v.im[μind, nind]
            dj_m_n += c * T1 - s * T2
        end
    end
    dj_m_n
end

@doc raw"""
    wignerDjmn(j, m, n, α::Real, β::Real, γ::Real, [Jy::AbstractMatrix{ComplexF64}])

Evaluate the Wigner D-matrix element ``D^j_{m,n}(\alpha,\beta,\gamma)``. Optionally a pre-allocated matrix `Jy` may be provided,
which must be of size `(2j+1, 2j+1)`.
"""
wignerDjmn(j, m, n, α::Real, β::Real, γ::Real, Jy...) = wignerdjmn(j, m, n, β, Jy...) * cis(-(α * m + γ * n))

Base.@propagate_inbounds function wignerd!(dj, j, β::NorthPole, λ, v)
    dj_noof = OffsetArrays.no_offset_view(dj)
    for ind in diagind(dj_noof)
        dj_noof[ind] = one(eltype(dj))
    end
    return dj
end

Base.@propagate_inbounds function wignerd!(dj, j, β::SouthPole, λ, v)
    dj_w = WignerMatrix(j, OffsetArrays.no_offset_view(dj))

    even = true
    for m in _indrange(j)
        dj_w[m, -m] = even ? one(eltype(dj)) : -one(eltype(dj))
        even = !even
    end
    return dj
end

Base.@propagate_inbounds function wignerd!(dj, j, β, λ, v)
    @boundscheck @assert size(dj) == _matrixsize(j) "size of dj must be $(Int(2j+1))×$(Int(2j+1))"

    dj_w = _offsetmatrix(j, dj)

    for n in _unitrange(-j, 0), m in _unitrange(n, -n)
        dj_w[m, n] = wignerdjmn(j, m, n, β, λ, v)
    end

    # Use symmetries to fill up other terms
    # dj[m,n] = (-1)^(m-n) * dj[-m,-n]
    for n in _half_or_one_to(j)
        for m in _unitrange(-n, n)
            dj_w[m, n] = (-1)^(m-n) * dj_w[-m, -n]
        end
    end

    # dj[m,n] = (-1)^(m-n) * dj[n,m]
    for m in _half_or_one_to(j)
        for n in _unitrange(-m, m)
            dj_w[m, n] = (-1)^(m-n) * dj_w[n, m]
        end
    end

    for m in _unitrange(-j, -1), n in _unitrange(m, -m)
        dj_w[m, n] = dj_w[-n, -m]
    end

    return dj
end

"""
    wignerd!(d, j, β::Real, [Jy = zeros(ComplexF64, 2j+1, 2j+1)])

Evaluate the Wigner d-matrix with elements ``d^j_{m,n}(β)`` for the angular momentum ``j`` and the rotation angle ``β``,
and store the result in `d`.
The momentum ``j`` may be an integer or a half-integer, and must be non-negative.
Optionally the pre-allocated matrix `Jy` may be provided,
which must be a `ComplexF64` matrix of size `(2j+1, 2j+1)`, and may be overwritten during the calculation.
"""
Base.@propagate_inbounds function wignerd!(d, j, β::Real, Jy = zeros(ComplexF64, _matrixsize(j)))
    λ, v = Jy_eigen(j, Jy)
    wignerd!(d, j, β, λ, v)
    return d
end

"""
    wignerd(j, β::Real, [Jy = zeros(ComplexF64, 2j+1, 2j+1)])

Evaluate the Wigner d-matrix with elements ``d^j_{m,n}(β)`` for the angular momentum ``j`` and the rotation angle ``β``.
The momentum ``j`` may be an
integer or a half-integer, and must be non-negative. Optionally the pre-allocated matrix `Jy` may be provided,
which must be a `ComplexF64` matrix of size `(2j+1, 2j+1)`, and may be overwritten during the calculation.
"""
function wignerd(j, β::Real, Jy = zeros(ComplexF64, _matrixsize(j)))
    d = zeros(_matrixsize(j))
    @inbounds wignerd!(d, j, β, Jy)
    return d
end

"""
    wignerD!(D, j, α::Real, β::Real, γ::Real, [Jy = zeros(ComplexF64, 2j+1, 2j+1)])

Evaluate the Wigner D-matrix with elements ``D^j_{m,n}(α,β,γ)`` for the angular momentum ``j`` and the
Euler angles ``α``, ``β`` and ``γ``, and store the result in `D`.
The momentum ``j`` may be an integer or a half-integer, and must be non-negative.
Optionally the pre-allocated matrix `Jy` may be provided,
which must be a `ComplexF64` matrix of size `(2j+1, 2j+1)`, and may be overwritten during the calculation.
"""
Base.@propagate_inbounds function wignerD!(D, j, α::Real, β::Real, γ::Real, Jy = zeros(ComplexF64, _matrixsize(j)))
    wignerd!(D, j, β, Jy)
    D_w = _offsetmatrix(j, D)
    for n in _indrange(j), m in _indrange(j)
        D_w[m, n] *= cis(-(m * α + n * γ))
    end
    return D
end

"""
    wignerD(j, α::Real, β::Real, γ::Real, [Jy = zeros(ComplexF64, 2j+1, 2j+1)])

Evaluate the Wigner D-matrix with elements ``D^j_{m,n}(α,β,γ)`` for the angular momentum ``j`` and the
Euler angles ``α``, ``β`` and ``γ``.
The momentum ``j`` may be an integer or a half-integer, and must be non-negative.
Optionally the pre-allocated matrix `Jy` may be provided,
which must be a `ComplexF64` matrix of size `(2j+1, 2j+1)`, and may be overwritten during the calculation.
"""
function wignerD(j, α::Real, β::Real, γ::Real, Jy = zeros(ComplexF64, _matrixsize(j)))
    D = zeros(ComplexF64, _matrixsize(j))
    @inbounds wignerD!(D, j, α, β, γ, Jy)
    return D
end

end