billylanchantin

billylanchantin

How should I undo a one-hot encoded variable?

I have a dataset with categorical variables, e.g.:

alias Explorer.DataFrame, as: DF

df = DF.new(%{
  age: [1, 5, 3],
  animal: ["dog", "cat", "dog"],       # categorical
  color: ["brown", "black", "brindle"] # categorical
})

I currently have the variables one-hot encoded:

df_one_hot = DF.new(%{
  age: [1, 5, 3],
  animal_cat_1_of_2: [1, 0, 1], # animal == "dog"
  animal_cat_2_of_2: [0, 1, 0], # animal == "cat"
  color_cat_1_of_3: [1, 0, 0], # color == "brown"
  color_cat_2_of_3: [0, 1, 0], # color == "black"
  color_cat_3_of_3: [0, 0, 1], # color == "brindle"
})

Neural nets generally do well with that encoding. Tree-based models, however, may benefit from the original encoding or ordinal encoding. So while experimenting with different models, I found myself needing to undo the one-hot encoding.

I came up with a solution which I’ll post below. But I wanted to see how others would approach the problem. This is a somewhat computationally-intensive operation, and I worry that I’m not taking full advantage of Explorer. In particular, this looked like it may be a job for across, but I couldn’t make it work.

Marked As Solved

josevalim

josevalim

Creator of Elixir

If you can hardcode the fields, then you can do:

require Explorer.DataFrame, as: DF

DF.mutate(df_one_hot,
  animal: cond do
    animal_cat_1_of_2 == 1 -> "dog"
    animal_cat_2_of_2 == 1 -> "cat"
  end,
  color: cond do
    color_cat_1_of_3 == 1 -> "brown"
    color_cat_2_of_3 == 1 -> "black"
    color_cat_3_of_3 == 1 -> "brindle"
  end
)

If you cannot, then you can port your approach to mutate_with. mutate_with gives you access to the columns and allow you to dynamically build a query based on the field. Then use Series.select to build the cond. Scroll down to find the answer (I added some padding in case you want to try it out by yourself before seeing the solution):


















categorical_cols =
  df_one_hot.names
  |> Enum.map(&Regex.run(~r/(.+)_cat_(\d+)_of_\d+/, &1))
  |> Enum.reject(&is_nil/1)
  |> Enum.group_by(
    fn [_, group, _] -> group end,
    fn [col, _, num] -> {col, String.to_integer(num)} end
  )
  |> Map.new(fn {group, col_num_pairs} -> {group, col_num_pairs} end)

DF.mutate_with(df_one_hot, fn df ->
  Enum.map(categorical_cols, fn {group, col_to_num} ->
    expr = Enum.reduce(col_to_num, -1, fn {col_name, num}, acc ->
      equal = Explorer.Series.equal(df[col_name], 1)
      Explorer.Series.select(equal, num, acc)
    end)

    {group, expr}
  end)
end)

Last Post!

billylanchantin

billylanchantin

Thanks! That made a huge improvement.

Quick benchmark on a subset of the data (cols: 203, rows: 65,917):

{billy_us, billy_df} = :timer.tc(fn ->
  # ...
end)

{jose_us, jose_df} = :timer.tc(fn ->
  # ...
  |> DF.discard(#... to make the comparison fair
end)

billy_df.names == jose_df.names  #=> true
[billy: billy_us, jose: jose_us] #=> [billy: 26438, jose: 4680]

What’s more, your solution works on lazy frames. I needed to call DF.collect my dataframe first.

This particular dataset had ~80 columns one-hot columns. Hardcoding is do-able, but I’d prefer to avoid it if possible.

Where Next?

Popular in Questions Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
bsollish-terakeet
Credo is smart enough to check for (something like) this: assert length(the_list) == 0 with this response: Checking if an enum is empt...
New
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
senggen
Erlang/OTP 25 [erts-13.2.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] 15:22:35.803 [error] gen_event {lager_file_backend...
New

We're in Beta

About us Mission Statement