Dictionaries in Python. One of the most important data... | by Ankit Gupta | Towards Data Science

In this article, I will talk about dictionaries. This is the second article in the series named “Data Structures in Python”. The first part of this series was about lists.
Dictionaries are important data structures in Python that use keys for indexing. They are an unordered sequence of items (key-value pairs), which means the order is not preserved. The keys are immutable. Just like lists, the values of dictionaries can hold heterogeneous data i.e., integers, floats, strings, NaN, Booleans, lists, arrays, and even nested dictionaries.
This article will provide you a clear understanding and enable you to work proficiently with Python dictionaries.
The following topics are covered in this article:
Creating a dictionary and adding elements
Accessing dictionary elements
Removing dictionary elements
Adding/inserting new elements
Merging/concatenating dictionaries
Modifying a dictionary
Sorting a dictionary
Dictionary comprehension
Alternative ways to create dictionaries
Copying a dictionary
Renaming existing keys
Nested dictionaries
Checking if a key exists in a dictionary
Like lists are initialized using square brackets ([ ]), dictionaries are initialized using curly brackets ({ }). An empty dictionary has, of course, zero length.
dic_a = {} # An empty dictionarytype(dic_a)>>> dictlen(dic_a)>>> 0
A dictionary has two characteristic features: keys and values. Each key has a corresponding value. Both, the key and the value, can be of type string, float, integer, NaN, etc. Adding elements to a dictionary means adding a key-value pair. A dictionary is composed of one or more key-value pairs.
Let us add some elements to our empty dictionary. The following is one way to do so. Here, 'A' is the key and 'Apple' is its value. You can add as many elements as you like.
# Adding the first elementdic_a['A'] = 'Apple'print (dic_a)>>> {'A': 'Apple'}# Adding the second elementdic_a['B'] = 'Ball'print (dic_a)>>> {'A': 'Apple', 'B': 'Ball'}
Note: Python being case-sensitive, 'A' and 'a' act as two different keys.
dic_a['a'] = 'apple'print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'a': 'apple'}
If you find the above method of adding elements one-by-one tiresome, you can also initialize the dictionary at once by specifying all the key-value pairs.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
So far your dictionary had strings as keys and values. A dictionary can also store data of mixed types. The following is a valid Python dictionary.
dic_b = {1: 'Ace', 'B': 123, np.nan: 99.9, 'D': np.nan, 'E': np.inf}
You should though use meaningful names for the keys as they denote the indices of dictionaries. In particular, avoid using floats and np.nan as keys.
Having created our dictionaries, let us see how we can access their elements.
You can access the keys and values using the functions dict.keys() and dict.values(), respectively. You can also access both keys and values in the form of tuples using the items() function.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.keys()>>> dict_keys(['A', 'B', 'C'])dic_a.values()>>> dict_values(['Apple', 'Ball', 'Cat'])dic_a.items()>>> dict_items([('A', 'Apple'), ('B', 'Ball'), ('C', 'Cat')])
Alternatively, you can also use a “for” loop to access/print them one at a time.
# Printing keysfor key in dic_a.keys():    print (key, end=' ')>>> A B C############################## Printing valuesfor key in dic_a.values():    print (key, end=' ')>>> Apple Ball Cat
You can avoid two “for” loops and access the keys and values using items(). The “for” loop will iterate through the key-value pairs returned by items(). Here, the key and value are arbitrary variable names.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}for key, value in dic_a.items():    print (key, value)>>> A Apple    B Ball    C Cat
The dictionary items cannot be accessed using list-like indexing.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a[0]>>> ----> 1 dic_a[0]    KeyError: 0
You need to use the keys to access the corresponding values from a dictionary.
# Accessing the value "Apple"dic_a['A']>>> 'Apple'# Accessing the value "Cat"dic_a['C']>>> 'Cat'
You will get an error if the key does not exist in the dictionary.
dic_a['Z']>>> KeyError Traceback (most recent call last)----> 1 dic_a['Z']KeyError: 'Z'
If you want to avoid such key errors in case of non-existent keys, you can use the get() function. This returns None when the key does not exist. You can also use a custom message to be returned.
print (dic_a.get('Z'))>>> None# Custom return messageprint (dic_a.get('Z', 'Key does not exist'))>>> Key does not exist
If you want to access dictionary elements (either keys or values) using indices, you need to first convert them into lists.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}list(dic_a.keys())[0]>>> 'A'list(dic_a.keys())[-1]>>> 'C'list(dic_a.values())[0]>>> 'Apple'list(dic_a.values())[-1]>>> 'Cat'
Deleting elements from a dictionary means deleting a key-value pair together.
You can delete the dictionary elements using the del keyword and the key whose value you want to delete. The deletion is in-place, which means you do not need to re-assign the value of the dictionary after deletion.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}# Deleting the key-value pair of 'A': 'Apple'del dic_a['A']print (dic_a)>>> {'B': 'Ball', 'C': 'Cat'}# Deleting the key-value pair of 'C': 'Cat'del dic_a['C']print (dic_a)>>> {'B': 'Ball'}
You can also use the “pop( )” function to delete elements. It returns the value being popped (deleted) and the dictionary is modified in-place.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.pop('A')>>> 'Apple' print (dic_a)# {'B': 'Ball', 'C': 'Cat'}
In both the above-described methods, you will get a KeyError if the key to be deleted does not exist in the dictionary. In the case of “pop( )”, you can specify the error message to show up if the key does not exist.
key_to_delete = 'E'dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.pop(key_to_delete, f'Key {key_to_delete} does not exist.')>>> 'Key E does not exist.'
There is no direct way but you can use a “for” loop as shown below.
to_delete = ['A', 'C']dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}for key in to_delete:    del dic_a[key]    print (dic_a)>>> {'B': 'Ball'}
You can add one element at a time to an already existing dictionary as shown below.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a['D'] = 'Dog'print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}dic_a['E'] = 'Egg'print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog', 'E': 'Egg'}
If the key you are adding already exists, the existing value will be overwritten.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a['A'] = 'Adam' # key 'A' already exists with the value 'Apple'print (dic_a)>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat'}
You can also use the update() function for adding a new key-value pair by passing the pair as an argument.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a.update({'D': 'Dog'})print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
The update() function also allows you to add multiple key-value pairs simultaneously to an existing dictionary.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_b = {'D':'Dog', 'E':'Egg'}dic_a.update(dic_b)print(dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog', 'E': 'Egg'}
You can merge two or more dictionaries using the unpacking operator (**) starting Python 3.5 onwards.
dic_a = {'A': 'Apple', 'B': 'Ball'}dic_b = {'C': 'Cat', 'D': 'Dog'}dic_merged = {**dic_a, **dic_b}print (dic_merged)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
If you don’t want to create a new dictionary but just want to add dic_bto the existing dic_a, you can simply update the first dictionary as shown earlier.
dic_a = {'A': 'Apple', 'B': 'Ball'}dic_b = {'C': 'Cat', 'D': 'Dog'}dic_a.update(dic_b)print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
One of the characteristics of Python dictionaries is that they cannot have duplicate keys i.e., a key cannot appear twice. So, what happens if you concatenate two or more dictionaries that have one or more common keys.
The answer is that the key-value pair in the last merged dictionary (in the order of merging) will survive. In the following example, the key'A' exists in all three dictionaries, and, therefore, the final dictionary took the value from the last merged dictionary (dic_c).
dic_a = {'A': 'Apple', 'B': 'Ball'}dic_b = {'C': 'Cat', 'A': 'Apricot'}dic_c = {'A': 'Adam', 'E': 'Egg'}dic_merged = {**dic_a, **dic_b, **dic_c}print (dic_merged)>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat', 'E': 'Egg'}
I just said that the dictionaries cannot have duplicate keys. Strictly speaking, you can define a dictionary with duplicate keys, but, when you print it, only the last duplicate key will be printed. A shown below, only unique keys are returned, and for the duplicated key (‘A’ here), only the last value is returned.
dic_a = {'A': 'Apple', 'B': 'Ball', 'A': 'Apricot', 'A': 'Assault'}print (dic_a)>>> {'A': 'Assault', 'B': 'Ball'}
From Python 3.9 onwards, you can use the | operator to concatenate two or more dictionaries.
dic_a = {'A': 'Apple', 'B': 'Ball'}dic_b = {'C': 'Cat', 'D': 'Dog'}dic_c = dic_a | dic_b>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}# Concatenating more than 2 dictionariesdic_d = dic_a | dic_b | dic_c
If you want to change the value of 'A' from 'Apple' to 'Apricot', you can use a simple assignment.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_a['A'] = 'Apricot'print (dic_a)>>> {'A': 'Apricot', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
The order is not maintained in a dictionary. You can sort a dictionary using either the keys or the values using the sorted() function.
If the keys are strings (alphabets), they will be sorted alphabetically. In a dictionary, we have two main elements: keys and values. Hence, while sorting with respect to the keys, we use the first element i.e. keys, and, therefore, the index used in the lambda function is “[0]”. You can read this post for more information on the lambda functions.
dic_a = {'B': 100, 'C': 10, 'D': 90, 'A': 40}sorted(dic_a.items(), key=lambda x: x[0])>>> [('A', 40), ('B', 100), ('C', 10), ('D', 90)]
The sorting is not in-place. As shown below, if you now print the dictionary, it remains unordered, as initialized originally. You will have to reassign it after sorting.
# The dictionary remains unordered if you print itprint (dic_a)>>> {'B': 100, 'C': 10, 'D': 90, 'A': 40}
If you want to sort in reverse order, specify the keyword reverse=True.
sorted(dic_a.items(), key=lambda x: x[0], reverse=True)>>> [('D', 90), ('C', 10), ('B', 100), ('A', 40)]
To sort a dictionary based on its values, you need to use the index “[1]” inside the lambda function.
dic_a = {'B': 100, 'C': 10, 'D': 90, 'A': 40}sorted(dic_a.items(), key=lambda x: x[1])>>> [('C', 10), ('A', 40), ('D', 90), ('B', 100)]
It is a very helpful method for creating dictionaries dynamically. Suppose you want to create a dictionary where the key is an integer and the value is its square. A dictionary comprehension would look like the following.
dic_c = {i: i**2 for i in range(5)}print (dic_c)>>> {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
If you want your keys to be strings, you can use “f-strings”.
dic_c = {f'{i}': i**2 for i in range(5)}print (dic_c)>>> {'0': 0, '1': 1, '2': 4, '3': 9, '4': 16}
Suppose you have two lists and you want to create a dictionary out of them. The simplest way is to use the dict() constructor.
names = ['Sam', 'Adam', 'Tom', 'Harry']marks = [90, 85, 55, 70]dic_grades = dict(zip(names, marks))print (dic_grades)>>> {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
You can also zip the two lists together and create the dictionary using “dictionary comprehension” as shown earlier.
dic_grades = {k:v for k, v in zip(names, marks)}print (dic_grades)>>> {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}
You can also pass a list of key-value pairs separated by commas to the dict() construct and it will return a dictionary.
dic_a = dict([('A', 'Apple'), ('B', 'Ball'), ('C', 'Cat')])print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
If your keys are strings, you can use even a simpler initialization using only the variables as the keys.
dic_a = dict(A='Apple', B='Ball', C='Cat')print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
I will explain this point using a simple example. There are more subtleties involved in the copying mechanism of dictionaries, and, I would recommend the reader to refer to this Stack Overflow post for a detailed explanation.
When you simply reassign an existing dictionary (parent dictionary) to a new dictionary, both point to the same object (“reference assignment”).
Consider the following example where you reassign dic_a to dic_b.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_b = dic_a # Simple reassignment (Reference assignment)
Now, if you modify dic_b(for e.g. adding a new element), you will notice that the change will also be reflected in dic_a.
dic_b['D'] = 'Dog'print (dic_b)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}print (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}
A shallow copy is created using the copy() function. In a shallow copy, the two dictionaries act as two independent objects, with their contents still sharing the same reference. If you add a new key-value pair in the new dictionary (shallow copy), it will not show up in the parent dictionary.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_b = dic_a.copy()dic_b['D'] = 'Dog'# New, shallow copy, has the new key-value pairprint (dic_b)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}# The parent dictionary does not have the new key-value pairprint (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
Now, if the contents in the parent dictionary (“dic_a”) will change depends on the type of the value. For instance, in the following, the contents are simple strings that are immutable. So changing the value in “dic_b” for a given key ('A'in this case) will not change the value of the key 'A' in “dic_a”.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}dic_b = dic_a.copy()# Replace an existing key with a new value in the shallow copydic_b['A'] = 'Adam'print (dic_b)>>> {'A': 'Adam', 'B': 'Ball', 'C': 'Cat'}# Strings are immutable so 'Apple' doesn't change to 'Adam' in dic_aprint (dic_a)>>> {'A': 'Apple', 'B': 'Ball', 'C': 'Cat'}
However, if the value of the key 'A' in “dic_a” is a list, then changing its value in the “dic_b” will reflect the changes in “dic_a” (parent dictionary), because lists are mutable.
dic_a = {'A': ['Apple'], 'B': 'Ball', 'C': 'Cat'}# Make a shallow copydic_b = dic_a.copy()dic_b['A'][0] = 'Adam'print (dic_b)>>> {'A': ['Adam'], 'B': 'Ball', 'C': 'Coal'}# Lists are mutable so the changes get reflected in dic_a tooprint (dic_a)>>> {'A': ['Adam'], 'B': 'Ball', 'C': 'Cat'}
Suppose you want to replace the key “Adam” by “Alex”. You can use the pop function because it deletes the passed key (“Adam” here) and returns the deleted value (85 here). So you kill two birds with a single shot. Use the returned (deleted) value to assign the value to the new key (“Alex” here). There can be more complicated cases where the key is a tuple. Such cases are out of the scope of this article.
dic_a = {'Sam': 90, 'Adam': 85, 'Tom': 55, 'Harry': 70}dic_a['Alex'] = dic_a.pop('Adam')print (dic_a)>>> {'Sam': 90, 'Tom': 55, 'Harry': 70, 'Alex': 85}
A nested dictionary has one or more dictionaries within a dictionary. The following is the simplest example of a nested dictionary with two layers of nesting. Here, the outer dictionary (layer 1) has only one key-value pair. However, the value is now a dictionary itself.
dic_a = {'A': {'B': 'Ball'}}dic_a['A']>>> {'B': 'Ball'}type(dic_a['A'])>>> dict
If you want to further access the key-value pair of the inner dictionary (layer 2), you now need to use dic_a['A'] as the dictionary.
dic_a['A']['B']>>> 'Ball'
Let us add an additional layer of the nested dictionary. Now, dic_a['A'] is a nested dictionary in itself, unlike the simplest nested dictionary above.
dic_a = {'A': {'B': {'C': 'Cat'}}}# Layer 1dic_a['A']>>> {'B': {'C': 'Cat'}}# Layer 2dic_a['A']['B']>>> {'C': 'Cat'}# Layer 3dic_a['A']['B']['C']>>> 'Cat'
You can find if a particular key exists in a dictionary using the in operator.
dic_a = {'A': 'Apple', 'B': 'Ball', 'C': 'Cat', 'D': 'Dog'}'A' in dic_a# True'E' in dic_a# False
In the above code, you do not need to use “in dic_a.keys( )” because “in dic_a” already looks up in the keys.
This brings me to the end of this article. You can access the first part of this series on “Data Structures in Python” here.