https://github.com/javierbarbero/DataEnvelopmentAnalysis.jl
Raw File
Tip revision: 48c782a09f14b708153f8f8228b1d9e452637f5b authored by Javier Barbero on 29 October 2023, 09:20:13 UTC
Add compat requirements for Julia standard libraries
Tip revision: 48c782a
deaboot.jl
# This file contains functions for the Bootstrap Radial DEA model
"""
AbstractBootstrapDEAModel
An abstract type representing a bootstrap DEA model.
"""
abstract type AbstractBootstrapDEAModel <: AbstractTechnicalDEAModel end

"""
BootstrapRadialDEAModel
An data structure representing a bootstrap radial DEA model.
"""
struct BootstrapRadialDEAModel <: AbstractBootstrapDEAModel
    n::Int64
    m::Int64
    s::Int64
    orient::Symbol
    rts::Symbol
    disposX::Symbol
    disposY::Symbol
    dmunames::Union{Vector{AbstractString},Nothing}
    eff::Vector
    effref::Vector
    effbias::Vector
    effB::Matrix
    h::Float64
end

"""
    deaboot(X, Y)
Compute the bootstrap radial model using data envelopment analysis for inputs X and outputs Y.

# Optional Arguments
- `nreps=200`: number of bootstrap replications.
- `rng=default_rng()`: random number generator.
- `orient=:Input`: chooses the radially oriented input mode. For the radially oriented output model choose `:Output`.
- `rts=:CRS`: chooses constant returns to scale. For variable returns to scale choose `:VRS`.
- `Xref=X`: Identifies the reference set of inputs against which the units are evaluated.
- `Yref=Y`: Identifies the reference set of outputs against which the units are evaluated.
- `disposX=:Strong`: chooses strong disposability of inputs. For weak disposability choose `:Weak`.
- `disposY=:Strong`: chooses strong disposability of outputs. For weak disposability choose `:Weak`.
- `names`: a vector of strings with the names of the decision making units.
"""
function deaboot(X::Union{Matrix,Vector}, Y::Union{Matrix,Vector};
    nreps::Int = 200, rng::AbstractRNG = default_rng(), effref::Union{Vector,Nothing} = nothing,
    orient::Symbol = :Input, rts::Symbol = :CRS,
    Xref::Union{Matrix,Vector,Nothing} = nothing, Yref::Union{Matrix, Vector,Nothing} = nothing,
    disposX::Symbol = :Strong, disposY::Symbol = :Strong,
    names::Union{Vector{<: AbstractString},Nothing} = nothing,
    optimizer::Union{DEAOptimizer,Nothing} = nothing, progress::Bool = false)::BootstrapRadialDEAModel

    # Check parameters
    nx, m = size(X, 1), size(X, 2)
    ny, s = size(Y, 1), size(Y, 2)

    if Xref === nothing Xref = X end
    if Yref === nothing Yref = Y end

    nrefx, mref = size(Xref, 1), size(Xref, 2)
    nrefy, sref = size(Yref, 1), size(Yref, 2)

    if nx != ny
        throw(DimensionMismatch("number of rows in X and Y ($nx, $ny) are not equal"));
    end
    if nrefx != nrefy
        throw(DimensionMismatch("number of rows in Xref and Yref ($nrefx, $nrefy) are not equal"));
    end
    if m != mref
        throw(DimensionMismatch("number of columns in X and Xref ($m, $mref) are not equal"));
    end
    if s != sref
        throw(DimensionMismatch("number of columns in Y and Yref ($s, $sref) are not equal"));
    end

    if disposX != :Strong && disposX != :Weak
        throw(ArgumentError("`disposX` must be :Strong or :Weak"));
    end

    if disposY != :Strong && disposY != :Weak
        throw(ArgumentError("`disposY` must be :Strong or :Weak"));
    end

    # Default optimizer
    if optimizer === nothing 
        optimizer = DEAOptimizer(:LP)
    end

    # Radial DEA Model
    n = nx

    if isnothing(effref)
        effref = efficiency(dea(X, Y, orient = orient, rts = rts, slack = false, 
            Xref = Xref, Yref = Yref, disposX = disposX, disposY = disposY, optimizer = optimizer))
    end

    h = dea_bandwidth(effref, orient)

    if orient == :Input
        effi = 1 ./ deepcopy(effref)
    else
        effi = deepcopy(effref)
    end

    # Boostrap replications
    effB = SharedMatrix{Float64}(n, nreps)
    fill!(effB, 0.0)

    rng2 = deepcopy(rng)
    eff2m = [2 .- effi; effi]

    sampB = zeros(n, nreps)
    randB = zeros(n, nreps)
    for b in 1:nreps
        sampB[:, b] = sample(rng, eff2m, n, replace = true)
        randB[:, b] = rand(rng2, Normal(0, 1), n);
    end
    
    p = progressbarmeter(nreps, desc = "Computing Bootsrap Radial DEA Model...", progress = progress)

    @sync @distributed for b in 1:nreps
        # 1. Get a sample from the 2m set
        effn = sampB[:, b]

        # 2. Perturbate
        effn_star = effn + h .* randB[:,b]

        # 3. Refine and correct for the mean and variance of smoothed values
        effn_star = mean(effn) .+ (effn .- mean(effn)) ./ (sqrt( 1 + h^2 / var(eff2m)));

        # 4 .Reflect values
        effn_starr = effn_star
        effn_starr[effn_star .< 1] = 2 .- effn_star[effn_star .< 1]

        # Generate inefficient inputs or outputs and perform DEA
        if orient == :Input
            effn_starr = 1 ./ effn_starr
            Xrefb = repeat(effref ./ effn_starr, 1, m) .* Xref
            Yrefb = Yref
        else
            Xrefb = Xref
            Yrefb = repeat(effref ./ effn_starr, 1, s) .* Yref
        end

        effB[:, b] = efficiency(dea(X, Y, orient = orient, rts = rts, slack = false, 
            Xref = Xrefb, Yref = Yrefb, disposX = disposX, disposY = disposY, optimizer = optimizer))

        if orient == :Input
            effB[:, b] = 1 ./ effB[:, b]
        end

        next!(p)
    end

    # Bootstrap efficiency
    effbias = vec(mean(effB, dims = 2) .- effi)
    effbc = vec(effi .- effbias)

    # Invert if input oriented
    if orient == :Input
        effB = 1 ./ effB
        effbc = 1 ./ effbc
        effbias = effref .- effbc
    end

    return BootstrapRadialDEAModel(n, m, s, orient, rts, disposX, disposY, names, effbc, effref, effbias, effB, h)

end

function dea_bandwidth(x::Vector{Float64}, orient::Symbol)::Float64
    # Get only inefficient units
    effn = x[.! isapprox.(x, 1.0, atol = 1e-5)]

    n = length(x)
    m = length(effn)

    # Build the 2m set (reflected data)
    eff2m = [2 .- effn; effn]
    
    s2m = std(eff2m)
    r2m = iqr(eff2m)

    # Compute the optimmal bandwidth (After equation 3.24)
    hm = 0.9 * minimum([s2m, r2m ./ 1.349]) * (2 * m)^(-1/5);

    # Adjust bandwidth for scale and sample size (Equation 3.26) 
    if orient == :Input
        sx = std( 1 ./ x)
    else
        sx = std(x)
    end

    h = hm * ((2 * m) / n)^(1/5) * (sx / s2m ); 

    return h
end

"""
    confint(model::BootstrapRadialDEAModel; level::Real=0.95)
Compute confidence intervals for efficiency scores, with confidence level `level` (by default 95%).
"""
function confint(model::BootstrapRadialDEAModel; level::Real=0.95)
    n = nobs(model)
    orient = model.orient
    effref = model.effref
    effB = model.effB
    alpha = 1 - level

    effc = zeros(n, 2)
    for i in 1:n
        if orient == :Input
            effc[i, 1:2] = (1 ./ effref[i]) .+ quantile(( 1 ./ effref[i]) .- (1 ./ effB[i,:]), [0.5 * alpha, 1 - 0.5 * alpha])
        else
            effc[i, 1:2] = effref[i] .+ quantile(effref[i] .- effB[i,:], [0.5 * alpha, 1 - 0.5 * alpha])
        end
    end

    if orient == :Input
        effc = 1 ./ effc
        effc = effc[:, [2, 1]]
    end

    return effc
end

"""
    bandwidth(model::BootstrapRadialDEAModel)
Return the optimal bandwidth of a bootstrap DEA model.
"""
function bandwidth(model::BootstrapRadialDEAModel)
    return model.h
end

"""
    bias(model::BootstrapRadialDEAModel)
Return the bias from bootstrap DEA model.
"""
function bias(model::BootstrapRadialDEAModel)
    return model.effbias
end

function Base.show(io::IO, x::BootstrapRadialDEAModel)
    compact = get(io, :compact, false)

    n = nobs(x)
    m = ninputs(x)
    s = noutputs(x)
    disposX = x.disposX
    disposY = x.disposY
    dmunames = names(x)

    effref = x.effref
    eff = efficiency(x)
    effbias = bias(x)
    effc = confint(x, level = 0.95)
    effL = effc[:, 1]
    effU = effc[:, 2]

    if !compact
        print(io, "Bootstrap Radial DEA Model \n")
        print(io, "DMUs = ", n)
        print(io, "; Inputs = ", m)
        print(io, "; Outputs = ", s)
        print(io, "\n")
        print(io, "Orientation = ", string(x.orient))
        print(io, "; Returns to Scale = ", string(x.rts))
        print(io, "\n")
        print(io, "Bootstrap replications = ", size(x.effB, 2))
        print(io, "\n")
        if disposX == :Weak print(io, "Weak disposability of inputs \n") end
        if disposY == :Weak print(io, "Weak disposability of outputs \n") end

        show(io, CoefTable(hcat(effref, eff, effbias, effL, effU), ["Reference", "Corrected", "Bias", "Lower 95%", "Upper 95%"], dmunames))   
        
        print(io, "\nBandwidth = ", round(x.h, digits = 5))
    end

end
back to top