Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Minimal Support for Reliability Scores (aka Crombach's alpha) #701

Merged
merged 35 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
056b250
added reliability
Jul 25, 2021
fdec3f2
updated docstring
Jul 25, 2021
0afd1aa
Update src/reliability.jl
storopoli Jul 25, 2021
2220f8d
updated naming to crombach_alpha
Jul 25, 2021
120e1a6
Update src/reliability.jl
storopoli Jul 25, 2021
1ec3213
aligned the comment
Jul 26, 2021
6a91ebf
changed calculations and using AbstractFloat
Jul 26, 2021
a4cb7cf
changed for loop to avoid allocations
Jul 26, 2021
3363974
added better performant implementation of crombach_alpha function
Jul 26, 2021
35b1f13
Update src/reliability.jl
storopoli Jul 26, 2021
3c84291
Update src/reliability.jl
storopoli Jul 26, 2021
0a134a1
Update src/reliability.jl
storopoli Jul 26, 2021
02f17f1
Update src/reliability.jl
storopoli Jul 26, 2021
a0dd59b
Update src/reliability.jl
storopoli Jul 26, 2021
07bf0aa
updated docstring for 4 digits
Jul 26, 2021
ff8da3e
corrected tests
Jul 26, 2021
3a0e9a7
expand tests to 1col 2cols Rational and BigFloat
Jul 26, 2021
9131d6b
Update test/reliability.jl
storopoli Jul 26, 2021
d376b62
Update src/reliability.jl
storopoli Jul 26, 2021
f3e9ca0
Update test/reliability.jl
storopoli Jul 26, 2021
6557421
Update test/reliability.jl
storopoli Jul 26, 2021
65a9c63
Update test/reliability.jl
storopoli Jul 26, 2021
45b1150
Update test/reliability.jl
storopoli Jul 26, 2021
d750e54
Update src/reliability.jl
storopoli Jul 26, 2021
440879b
added show io tests
Jul 26, 2021
fc4f4f8
remove Diagonal unused function
Jul 26, 2021
ccfae49
Apply suggestions from code review
storopoli Aug 21, 2021
2f63ba2
changed to crombachalpha and more tests
Aug 21, 2021
a920c41
Apply suggestions from code review
storopoli Aug 21, 2021
6be402b
Added docs,changed Crombach to Cronbach and new struct
Aug 21, 2021
d0afc19
renamed struct in StatsBase.jl
Aug 21, 2021
af0f068
Apply suggestions from code review
storopoli Aug 22, 2021
ebb2f68
replaced realiability to Cronbach's alpha and expanded tests
Aug 22, 2021
0a5e830
Apply suggestions from code review
nalimilan Aug 24, 2021
ce3368a
Fix formula
nalimilan Aug 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/src/scalarstats.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,9 @@ modes
summarystats
describe
```

## Reliability Measures

```@docs
cronbachalpha
```
7 changes: 6 additions & 1 deletion src/StatsBase.jl
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,11 @@ export
standardize,
AbstractDataTransform, # the type to represent a abstract data transformation
ZScoreTransform, # the type to represent a z-score data transformation
UnitRangeTransform # the type to represent a 0-1 data transformation
UnitRangeTransform, # the type to represent a 0-1 data transformation

# Reliability
nalimilan marked this conversation as resolved.
Show resolved Hide resolved
CronbachAlpha, # the type to represent Cronbach's alpha scores
cronbachalpha # function to compute Cronbach's alpha scores

# source files

Expand All @@ -232,6 +236,7 @@ include("partialcor.jl")
include("empirical.jl")
include("hist.jl")
include("pairwise.jl")
include("reliability.jl")
include("misc.jl")

include("sampling.jl")
Expand Down
74 changes: 74 additions & 0 deletions src/reliability.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
struct CronbachAlpha{T <: Real}
alpha::T
dropped::Vector{T}
end

function Base.show(io::IO, x::CronbachAlpha)
@printf(io, "Cronbach's alpha for all items: %.4f\n", x.alpha)
isempty(x.dropped) && return
println(io, "\nCronbach's alpha if an item is dropped:")
for (idx, val) in enumerate(x.dropped)
@printf(io, "item %i: %.4f\n", idx, val)
end
end

"""
cronbachalpha(covmatrix::AbstractMatrix{<:Real})

Calculate Cronbach's alpha (1951) from a covariance matrix `covmatrix` according to
the [formula](https://en.wikipedia.org/wiki/Cronbach%27s_alpha):

```math
\\rho = \\frac{k}{k-1} (1 - \\frac{\\sum^k_{i=1} \\sigma^2_i}{\\sigma^2_X})
```

where ``k`` is the number of items, i.e. columns; ``\\sigma_i`` denotes item variance;
and ``\\sigma^2_i`` consists of item variances and inter-item covariances.

Returns a `CronbachAlpha` object that holds:

* `alpha`: the Cronbach's alpha score for all items, i.e. columns, in `covmatrix`; and
* `dropped`: a vector giving Cronbach's alpha scores if a specific item,
i.e. column, is dropped from `covmatrix`.

# Example
```jldoctest
julia> using StatsBase

julia> cov_X = [10 6 6 6;
6 11 6 6;
6 6 12 6;
6 6 6 13];

julia> cronbachalpha(cov_X)
Cronbach's alpha for all items: 0.8136

Cronbach's alpha if an item is dropped:
item 1: 0.7500
item 2: 0.7606
item 3: 0.7714
item 4: 0.7836
nalimilan marked this conversation as resolved.
Show resolved Hide resolved
```
"""
function cronbachalpha(covmatrix::AbstractMatrix{<:Real})
isposdef(covmatrix) || throw(ArgumentError("Covariance matrix must be positive definite."))
k = size(covmatrix, 2)
k > 1 || throw(ArgumentError("Covariance matrix must have more than one column."))
v = vec(sum(covmatrix, dims=1))
σ = sum(v)
for i in axes(v, 1)
v[i] -= covmatrix[i, i]
end
σ_diag = sum(i -> covmatrix[i, i], 1:k)

alpha = k * (1 - σ_diag / σ) / (k - 1)
if k > 2
dropped = typeof(alpha)[(k - 1) * (1 - (σ_diag - covmatrix[i, i]) / (σ - 2*v[i] - covmatrix[i, i])) / (k - 2)
for i in 1:k]
else
# if k = 2 do not produce dropped; this has to be also
# correctly handled in show
dropped = Vector{typeof(alpha)}()
end
return CronbachAlpha(alpha, dropped)
end
79 changes: 79 additions & 0 deletions test/reliability.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using StatsBase
using LinearAlgebra, Random, Test

@testset "StatsBase.CronbachAlpha" begin
nalimilan marked this conversation as resolved.
Show resolved Hide resolved
# basic vanilla test
cov_X = [10 6 6 6;
6 11 6 6;
6 6 12 6;
6 6 6 13]
cronbach_X = cronbachalpha(cov_X)
@test cronbach_X isa CronbachAlpha{Float64}
@test cronbach_X.alpha ≈ 0.8135593220338981
@test cronbach_X.dropped ≈
[0.75, 0.7605633802816901, 0.7714285714285715, 0.782608695652174]

# testing Rational
cov_rational = cov_X .// 1
cronbach_rational = cronbachalpha(cov_rational)
@test cronbach_rational isa CronbachAlpha{Rational{Int}}
@test cronbach_rational.alpha == 48 // 59
@test cronbach_rational.dropped ==
[3 // 4, 54 // 71, 27 // 35, 18 // 23]

# testing BigFloat
cov_bigfloat = BigFloat.(cov_X)
cronbach_bigfloat = cronbachalpha(cov_bigfloat)
@test cronbach_bigfloat isa CronbachAlpha{BigFloat}
@test cronbach_bigfloat.alpha ≈ 0.8135593220338981
@test cronbach_bigfloat.dropped ≈
[0.75, 0.7605633802816901, 0.7714285714285715, 0.782608695652174]

# testing corner cases
@test_throws MethodError cronbachalpha([1.0, 2.0])
cov_k2 = [10 6;
6 11]
cronbach_k2 = cronbachalpha(cov_k2)
@test cronbach_k2.alpha ≈ 0.7272727272727273
@test isempty(cronbach_k2.dropped)

# testing when Matrix is not positive-definite
cov_not_pos = [-1 1;
-1 1]
@test_throws ArgumentError cronbachalpha(cov_not_pos)

# testing with a zero
cov_zero = [1 2;
0 1]
@test_throws ArgumentError cronbachalpha(cov_not_pos)

# testing with one column
cov_k1 = reshape([1, 2], 2, 1)
@test_throws ArgumentError cronbachalpha(cov_k1)

# testing with Missing
cov_missing = [1 2;
missing 1]
@test_throws MethodError cronbachalpha(cov_missing)


# testing Base.show
storopoli marked this conversation as resolved.
Show resolved Hide resolved
cronbach_X = cronbachalpha(cov_X)
io = IOBuffer()
show(io, cronbach_X)
str = String(take!(io))
@test str == """
Cronbach's alpha for all items: 0.8136\n
Cronbach's alpha if an item is dropped:
nalimilan marked this conversation as resolved.
Show resolved Hide resolved
item 1: 0.7500
item 2: 0.7606
item 3: 0.7714
item 4: 0.7826
"""
# for two columns
io = IOBuffer()
show(io, cronbach_k2)
str = String(take!(io))
@test str == "Cronbach's alpha for all items: 0.7273\n"

end # @testset "StatsBase.CronbachAlpha"
nalimilan marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ tests = ["ambiguous",
"signalcorr",
"misc",
"pairwise",
"reliability",
"robust",
"sampling",
"wsampling",
Expand Down