# Association of Associations : how to permute level 1 and level 2 keys?

I have a ragged Association of Association, say :

assoc = Association[
"1" -> Association["a" -> "x", "b" -> "y"],
"2" -> Association[            "b" -> "z", "c" -> "k"]
]

I would like to transform it into a Association where level 1 and level 2 keys are reversed, that is to say :

Association[
"a" -> Association["1" -> "x"],
"b" -> Association["1" -> "y", "2" -> "z"],
"c" -> Association[            "2" -> "k"]
]

My solution is :

keysExplodedList = Reap[MapIndexed[Sow[Reverse[#2] -> #1] &, assoc, {2}]][[2, 1]]
groupedLevel1 = GroupBy[#[[1, 1]] &] @ keysExplodedList
groupedLevel2 = GroupBy[#[[1, 2]] &] /@ groupedLevel1
result = Map[#[[1, 2]] &, groupedLevel2, {2}]

<|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|>

Is there something more elegant ?

• @WReach I have Mathematica 10.0.1 Commented Jun 21, 2015 at 14:55
• My solution could be more concisely rewritten as : (Reap[MapIndexed[Sow[#2 -> #1] &, assoc, {2}]][[2, 1]] // GroupBy[{#[[1, 2]] &, #[[1, 1]] &}])[[All, All, 1, 2]] Commented Jun 22, 2015 at 16:16

Mathematica 10.1 almost supports this operation directly:

assoc // Query[Transpose]

(*
<| "a" -> <|"1" -> "x", "2" -> Missing["KeyAbsent", "a"]|>,
"b" -> <|"1" -> "y", "2" -> "z"|>,
"c" -> <|"1" -> Missing["KeyAbsent", "c"], "2" -> "k"|>
|>
*)

All that remains is to delete the unwanted Missing elements:

assoc // Query[Transpose] // DeleteMissing[#, 2]&

(*
<| "a" -> <|"1" -> "x"|>,
"b" -> <|"1" -> "y", "2" -> "z"|>,
"c" -> <|"2" -> "k"|>
|>
*)

We can see that Query uses the undocumented function GeneralUtilitiesAssociationTranspose to do the heavy-lifting:

Query[Transpose] // Normal

(* GeneralUtilitiesAssociationTranspose *)

assoc // GeneralUtilitiesAssociationTranspose

(*
<| "a" -> <|"1" -> "x", "2" -> Missing["KeyAbsent", "a"]|>,
"b" -> <|"1" -> "y", "2" -> "z"|>,
"c" -> <|"1" -> Missing["KeyAbsent", "c"], "2" -> "k"|>
|>
*)

An Imperative Solution

The words "elegant" and "imperative" rarely appear together these days, but an imperative solution can express the transposition directly:

Module[{r = <| |>}
, Do[r = Merge[{r, <| j -> <| i -> assoc[[i, j]] |> |>}, Association]
, {i, Keys[assoc]}
, {j, Keys[assoc[[i]]]}
]
; r
]

(*
<| "a" -> <|"1" -> "x"|>,
"b" -> <|"1" -> "y", "2" -> "z"|>,
"c" -> <|"2" -> "k"|>
|>
*)

A ScanIndexed operator would come in handy here (the undocumented one in GeneralUtilities is not, well, general enough).

• A little less typing: DeleteMissing[Query[Transpose] @ assoc, 2] Commented Jun 21, 2015 at 15:25
• @m_goldberg Never thought of DeleteMissing for the DeleteCases... updated and thanks. Commented Jun 21, 2015 at 15:49
• //Query[Transpose] is very interesting if Transpose can be used in its general form : Transpose[#,{n1,n2,n3...}]& (when there are more than 2 levels of Association). I suppose it is the case, but it is not documented. Commented Jun 22, 2015 at 18:33
• @andre Alas, the general form of Transpose does not presently work on associations -- whether specified directly or after being interpreted by Query. Commented Jun 22, 2015 at 18:43

Not as nice as what WReach posted but this is what I came up with:

GroupBy[
Join @@ Thread /@ Normal //@ assoc,
{First@*Last, First}
][[All, All, 1, 2, 2]]
<|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|>

Here is another version, using KeyValueMap (needs 10.1):

regroup =
Composition[
Map[Association],
GroupBy[First -> Last],
Flatten,
Normal,
KeyValueMap[Function[{k, v}, Map[k -> # &, v]]]
];

So that

regroup @ assoc

(* <|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|> *)

Just for completeness:

assoc // Query[ Transpose /* Map[ DeleteMissing ] ]

<|"a" -> <|"1" -> "x"|>, "b" -> <|"1" -> "y", "2" -> "z"|>, "c" -> <|"2" -> "k"|>|>

DeleteMissing /@ GeneralUtilities`AssociationTranspose[assoc]

<|a-><|1->x|>,b-><|1->y,2->z|>,c-><|2->k|>|>

I have not noted WReach have mentioned this method here.But I think this is a bug behavior in function Transpose when it process Association.And I have report it to Wolfram Research before this.And there is workaround to avoid this bug

DeleteMissing /@ Normal[Transpose[Dataset[assoc]]]

<|a-><|1->x|>,b-><|1->y,2->z|>,c-><|2->k|>|>