12
$\begingroup$

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 ?

$\endgroup$
  • $\begingroup$ @WReach I have Mathematica 10.0.1 $\endgroup$ – andre314 Jun 21 '15 at 14:55
  • $\begingroup$ 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]] $\endgroup$ – andre314 Jun 22 '15 at 16:16
15
$\begingroup$

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 GeneralUtilities`AssociationTranspose to do the heavy-lifting:

Query[Transpose] // Normal

(* GeneralUtilities`AssociationTranspose *)

assoc // GeneralUtilities`AssociationTranspose

(*
<| "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).

|improve this answer|||||
$\endgroup$
  • 2
    $\begingroup$ A little less typing: DeleteMissing[Query[Transpose] @ assoc, 2] $\endgroup$ – m_goldberg Jun 21 '15 at 15:25
  • $\begingroup$ @m_goldberg Never thought of DeleteMissing for the DeleteCases... updated and thanks. $\endgroup$ – WReach Jun 21 '15 at 15:49
  • $\begingroup$ //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. $\endgroup$ – andre314 Jun 22 '15 at 18:33
  • 1
    $\begingroup$ @andre Alas, the general form of Transpose does not presently work on associations -- whether specified directly or after being interpreted by Query. $\endgroup$ – WReach Jun 22 '15 at 18:43
4
$\begingroup$

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"|>|>
|improve this answer|||||
$\endgroup$
3
$\begingroup$

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"|>|> *)
|improve this answer|||||
$\endgroup$
1
$\begingroup$

Just for completeness:

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

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

|improve this answer|||||
$\endgroup$
1
$\begingroup$
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|>|>

|improve this answer|||||
$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.