# Advice on rank-hot (thermometer) encoding of categorical variables

**URL:** <https://discourse.julialang.org/t/advice-on-rank-hot-thermometer-encoding-of-categorical-variables/87007>\
**Category:** General Usage\
**Tags:** question\
**Created:** [September 9, 2022, 5:54pm UTC](https://discourse.julialang.org/t/advice-on-rank-hot-thermometer-encoding-of-categorical-variables/87007 "2022-09-09T17:54:33Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![opera\_malenky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opera_malenky/32/8213_2.png) [@opera\_malenky](https://discourse.julialang.org/u/opera_malenky)\
**Post date:** [September 9, 2022, 5:54pm UTC](https://discourse.julialang.org/t/advice-on-rank-hot-thermometer-encoding-of-categorical-variables/87007/1 "2022-09-09T17:54:34Z")

</div>

As the title says, I was interested in trying to play around with rank-hot encoding for ordinal data (sometimes also called [thermometer](https://github.com/kmedian/thermoencoder) encoding).

It’s like one-hot, but instead of a single 1 in the position associated with a particular ordinal rank, all rank indicators up to and below the observed rank are coded as 1’s, while all rank indicators above the observed rank are coded as 0’s.

To give an example, the vector

```julia
[1, 3, 2, 5]

```

should lead to the matrix (assuming observations are row-wise):

```julia
1 0 0 0 0
1 1 1 0 0 
1 1 0 0 0 
1 1 1 1 1

```

I took a stab at making a function that uses CategoricalArrays; it works, but I thought I’d post and see if anyone has suggestions for a better approach.

```julia
using CategoricalArrays

v = ["Group A", "Group A", "Group B", "Group C", "Group C", "Group D"]
cv = categorical(v) # Convert string array to categorical array

function rankhot(x)

    # Get number of levels of ordinal variable
    max_level = maximum(levelcode.(x)) 

    # Number of ordinal observations in vector x
    n = length(x) 

    # Create an N x L matrix (observations x number of levels)
    # to store results
    y = Matrix{Float64}(undef, n, max_level) 

    for i in 1:max_level
        for j in 1:n

            # Check that the current level number is less than or 
            # equal to the level number of the current observation
            # If it is, assign 1, else 0
            y[j, i] = (i <= levelcode(x[j])) ? 1. : 0.
        end
    end

    return y

end

rankhot(cv)

```

This works fine, but I was curious if anyone had suggestions for improvements - for example, something that uses (and outputs) a DataFrame (which I am planning to try and use more in the future), maybe a simpler function (map or comprehensions?).

Frankly, I had initially planned to take one of the one-hot encoding implementations out there and adapt it, but I admit I found following most of their code a bit difficult to understand (though I admit I didn’t spend a huge amount of time trying). 🙂 Maybe later.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [September 9, 2022, 6:31pm UTC](https://discourse.julialang.org/t/advice-on-rank-hot-thermometer-encoding-of-categorical-variables/87007/2 "2022-09-09T18:31:41Z")

</div>

```julia
using Compat

julia> rankhot(k, n) = [Int(i≤k) for i in 1:n]

julia> stack(rankhot.([1,2,3,4,5,4,3,2,1], 5); dims=1)
9×5 Matrix{Int64}:
 1 0 0 0 0
 1 1 0 0 0
 1 1 1 0 0
 1 1 1 1 0
 1 1 1 1 1
 1 1 1 1 0
 1 1 1 0 0
 1 1 0 0 0
 1 0 0 0 0

```

---

<div class="post-metadata">

**Author:** ![opera\_malenky](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/opera_malenky/32/8213_2.png) [@opera\_malenky](https://discourse.julialang.org/u/opera_malenky)\
**Post date:** [September 9, 2022, 7:08pm UTC](https://discourse.julialang.org/t/advice-on-rank-hot-thermometer-encoding-of-categorical-variables/87007/3 "2022-09-09T19:08:14Z")

</div>

Nice! Thanks.

I had also attempted something using comprehensions (since it seemed like it should be an elegant approach) that looked very similar:

```julia
rankhot2(x) = reshape([i <= levelcode(x[j]) for i=1:5 for j=1:length(x)], length(x), 5)
rankhot2(cv)

```

Which is very slightly slower than yours.

The original code in the OP actually is rather fast by comparison to both, but since this would likely be a one-time operation on the data before doing any analyses, the speed is not really all that relevant. And it’s nice to see other ways to do this.

---

<div class="post-metadata">

**Author:** ![jar1](https://avatars.discourse-cdn.com/v4/letter/j/c0e974/32.png) [@jar1](https://discourse.julialang.org/u/jar1)\
**Post date:** [September 9, 2022, 7:38pm UTC](https://discourse.julialang.org/t/advice-on-rank-hot-thermometer-encoding-of-categorical-variables/87007/4 "2022-09-09T19:38:54Z")

</div>

```julia

julia> XS = [1,2,3,4,5,4,3,2,1]

julia> N = maximum(XS);

julia> [Int(i≤x) for x in XS, i in 1:N]

```

or

```julia

julia> map(Base.splat(≥), Iterators.product(XS, 1:N))
9×5 Matrix{Bool}:
 1 0 0 0 0
 1 1 0 0 0
 1 1 1 0 0
 1 1 1 1 0
 1 1 1 1 1
 1 1 1 1 0
 1 1 1 0 0
 1 1 0 0 0
 1 0 0 0 0

```
