Enhanced Association Tools Now Available 
in the Wolfram Function Repository

March 10, 2021 — Bob Sandheinrich, Development Manager, Document & Media Systems

Association has become one of the most commonly used symbols for developers working with any kind of data since it was introduced in Version 10 of the Wolfram Language in 2014. While there are many built-in tools for working with an Association, developers also made many tools themselves as they modernized their code. Now many of those tools have found their way into the Wolfram Function Repository. Here I’ll highlight some of my favorites and show how they compare to built-in Wolfram Language functions.

Constructing

An Association stores key-value data. There are many Wolfram Language functions for creating an Association, including AssociationMap, AssociationThread, Counts and GroupBy. The Function Repository also includes several functions for creating new associations.

You can use Association directly on a list of rules to convert it, but it only works at the top level. ToAssociations converts lists of rules deep in the expression as well:

Association@{"Beatles"->{"Drums"->"Ringo","Guitar"->"George"}}

@{"Beatles"->{"Drums"->"Ringo","Guitar"->"George"}}

AssociationMap creates an Association by mapping a single function over a list, using elements of the list as keys and the outputs as values. AssociationThrough does the opposite. It maps several functions over a single value:

AssociationMap[Sqrt,{1,4,9,16}]

[{Sqrt,FactorInteger,Exp},16]

SparseArray is an efficient way to store sparse numeric arrays. The values are indexed numerically. SparseAssociation generalized that concept to Association so that values are indexed with keys:

rareanimalsightings=[{{"April","Black Bear"}->1,{"July","Bald Eagle"}->4,{"August","Bald Eagle"}->1,{"November","Moose"}->1},Automatic]

It works like an Association:

rareanimalsightings["April"]["Black Bear"]

And it gives zero as the value for keys that were not explicitly given:

rareanimalsightings["July"]["Black Bear"]

Because those values are not stored in the SparseAssociation, it is smaller when there are many default values:

sparseAssoc=[RandomInteger[1,{100,200}],{Range[100],Range[200]},0]

ByteCount[sparseAssoc]

ByteCount@Normal[sparseAssoc]

Modifying Associations

Associations can be modified using many standard Wolfram Language symbols like Map, KeyMap, MapAt and Set. However, the number of functions that data scientists want for manipulating their data is infinite, so they have created some of their own. Here are some that have been published in the Function Repository.

While MapAt can apply a single function to the values of specific keys in an Association, MapAtKey can apply different functions to different keys:

MapAt[CubeRoot,<|"Odds"->{-27,-8,-1},"Evens"->{16,9,4}|>,"Odds"]

[{"Odds"->CubeRoot,"Evens"->Sqrt},<|"Odds"->{-27,-8,-1},"Evens"->{16,9,4}|>]

KeyCombine is a combination of Merge and KeyMap that allows you to combine elements of an Association based on their keys:

data=<|(Entity["Chicago", Entity["City",{"Chicago","Illinois","UnitedStates"}], "Entity[\"City\", {\"Chicago\", \"Illinois\", \"UnitedStates\"}]", "city"])->"Windy City",(Entity["Los Angeles", Entity["City",{"LosAngeles","California","UnitedStates"}], "Entity[\"City\", {\"LosAngeles\", \"California\", \"UnitedStates\"}]", "city"])->"City of Angels",(Entity["New Orleans", Entity["City",{"NewOrleans","Louisiana","UnitedStates"}], "Entity[\"City\", {\"NewOrleans\", \"Louisiana\", \"UnitedStates\"}]", "city"])->"The Big Easy"|>;

Using KeyMap causes values to be lost:

KeyMap[#["TimeZone"]&,data]

KeyCombine preserves all values in a list:

[#["TimeZone"]&,data]

Manually editing the content of an Association in a notebook can be challenging. AssociationEditor provides a convenient GUI form for editing content. I modified Bob’s value in the following example and used the print button to print out the updated Association:

[<|"Rick"->159,"Paco"->90,"Bob"->90,"Michael"->74|>]

Nested Associations

In an Association, the keys can be any expression, including lists. A side effect of that feature is that a list usually cannot be used to specify a location deep inside a nested Association. Several Function Repository functions have been published specifically to help work with nested associations.

NestedLookup treats lists as an index deep within nested associations:

[<|"Bob"-><|"Age"->37,"Location"-><|"City"->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),"Country"->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])|>|>|>,{"Bob","Location","City"}]

It also handles missing values at any level:

[<|"Bob"-><|"Age"->37,"Location"-><|"City"->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),"Country"->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])|>|>|>,{"Bob","Weight"},Missing["None of your business"]]

NestedAssociate adds or modifies values deep in a nested Association:

[<|"Bob"-><|"Age"->37,"Location"-><|"City"->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),"Country"->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])|>|>|>,{"Bob","Location","State"}->(Entity["Missouri, United States", Entity["AdministrativeDivision",{"Missouri","UnitedStates"}], "Entity[\"AdministrativeDivision\", {\"Missouri\", \"UnitedStates\"}]", "administrative division"])]

NestedKeyDrop removes key-value pairs from deep in a nested Association:

[<|"Bob"-><|"Age"->37,"Location"-><|"City"->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),"Country"->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])|>|>|>,{"Bob","Location","City"}]

AssociationMapAt maps a function deep in a nested Association:

[GeoGraphics[#["Polygon"]]&,<|"Bob"-><|"Age"->37,"Location"-><|"City"->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),"Country"->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])|>|>|>,{"Bob","Location","City"}]

AssociationKeyFlatten converts a nested Association into a flat Association:

[<|"Bob"-><|"Age"->37,"Location"-><|"City"->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),"Country"->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])|>|>|>]

AssociationKeyDeflatten does the opposite operation*. It creates a nested association from a flat Association with lists as keys:

[\[LeftAssociation]{"Bob","Age"}->37,{"Bob","Location","City"}->(Entity["Saint Louis", Entity["City",{"SaintLouis","Missouri","UnitedStates"}], "Entity[\"City\", {\"SaintLouis\", \"Missouri\", \"UnitedStates\"}]", "city"]),{"Bob","Location","Country"}->(Entity["United States", Entity["Country","UnitedStates"], "Entity[\"Country\", \"UnitedStates\"]", "country"])\[RightAssociation]]

* We thought the opposite of “flatten” might be “sharpen,” but we are reserving the name AssociationSharpen for this extreme data science function deployed as a resource function in my cloud account.

Modernization

The function Counts can be thought of as a modernization of the older function Tally:

Tally[{1,2,3,4,1,2,3,1,2,1}]

Counts[{1,2,3,4,1,2,3,1,2,1}]

Similarly, other functions that predate Association have been modernized in the Function Repository. Now there are two ways to Reap what you Sow:

Reap[Sow[(Entity["wheat", Entity["Plant","Genus:Triticum"], "Entity[\"Plant\", \"Genus:Triticum\"]", "plant"]),"Seeds"];"Fuel the tractor";Sow[(Entity["corn", Entity["Plant","Subspecies:ZeaMaysMays"], "Entity[\"Plant\", \"Subspecies:ZeaMaysMays\"]", "plant"]),"Seeds"];Sow["Division","Politics"];"Sleep"]

[Sow[(Entity["wheat", Entity["Plant","Genus:Triticum"], "Entity[\"Plant\", \"Genus:Triticum\"]", "plant"]),"Seeds"];"Fuel the tractor";Sow[(Entity["corn", Entity["Plant","Subspecies:ZeaMaysMays"], "Entity[\"Plant\", \"Subspecies:ZeaMaysMays\"]", "plant"]),"Seeds"];Sow["Division","Politics"];"Sleep"]

BinCounts splits data into bins and gives you the number of items in each bin, but does not return the actual bins. BinCountAssociation uses the keys of an Association to include that information in the results:

BinCounts[{1,3,2,1,4,5,6,2},{0,10,1}]

[{1,3,2,1,4,5,6,2},{0,10,1}]

Interoperability

A final, important aspect of the Wolfram Language and the Wolfram Function Repository is that there is consistency and interoperability among all the functions. Using a function created by one developer in the Function Repository does not mean that you have to build converters or translators to use other functions in the repository or in the Wolfram Language. To illustrate, here is a big pointless blob of Association-based operations using most of the functions discussed previously (and more) together seamlessly. While the result is meaningless, the immediate compatibility warms the heart:

@[@[[Mod[#,3]&,#]&,[{"Sown"->},@Table[Sow[i->[,[Values@[{Sqrt,Exp},i]]],"Factors"],{i,1,40}]],{"Sown","Factors"}],{{"Sown","Factors",1},"Result"}]

The functions mentioned here are only some of the Association tools in the Function Repository, which in turn are only a small piece of the consistently growing collection in the full repository. Every week, new functions are added that both expand the Wolfram Language and fill in some of its gaps. There is no longer any need to wait for Wolfram Language version releases to see the latest and greatest new functions: you can get them any time you want right here.

Explore more user contributions like the functions mentioned here or submit your own computational creations at the Wolfram Function Repository.