Say I have the following data:
({:year 2023, :month 4, :type TypeA, :cat CatA, :amount 62.5}
{:year 2023, :month 4, :type TypeB, :cat CatB, :amount 45.25}
{:year 2023, :month 4, :type TypeC, :cat CatA, :amount 40.0}
{:year 2023, :month 5, :type TypeA, :cat CatA, :amount 52.75}
{:year 2023, :month 5, :type TypeC, :cat CatA, :amount 14.0})
I would like to transform this by type or by cat into the following:
({:year 2023, :month 4, :typeA 62.5, :typeB 42.25, :typeC 40.0}
{:year 2023, :month 5, :typeA 52.75, :typeB 0, :typeC 14.0}}
or the same by cat:
({:year 2023, :month 4, :catA 102.5, :catB 42.25}
{:year 2023, :month 5, :catA 52.75, :catB 14.0}}
I sorta tried it with distinct and nested maps but have not found a valid solution.
BONUS: The types and cats should be flexible and it is not safe to reference them by name hard-coded.
The result seems to be close enough.
(let [data '({:year 2023, :month 4, :type TypeA, :cat CatA, :amount 62.5}
{:year 2023, :month 4, :type TypeB, :cat CatB, :amount 45.25}
{:year 2023, :month 4, :type TypeC, :cat CatA, :amount 40.0}
{:year 2023, :month 5, :type TypeA, :cat CatA, :amount 52.75}
{:year 2023, :month 5, :type TypeC, :cat CatA, :amount 14.0})
key-fields [:year :month]
k-field :cat
;k-field :type
v-field :amount]
(->> data
(group-by #(select-keys % key-fields))
(mapv (fn [[k ms]]
(reduce (fn [k m]
(let [;; Not sure if you need to make the first character lower-case here.
f (keyword (k-field m))]
(assoc k f (v-field m))))
k ms)))))
It doesn't add zeros for absent values, but I'd argue that it's usually best not to add them. Instead, the clients of this code can decide what to do with them.