Modifying keys and values in nested dictionaries in python - python

I'm working with a nested dictionary and I'm trying to figure out how to modify certain nested and non-nested keys/values effectively. In short, I'm trying to do the following:
take a nested value and switch it with a non-nested key
rename a nested key.
Here's a basic example of a dictionary that I'm working with:
pet_dictionary = {'Buford':{'color':'white', 'weight': 95, 'age':'3',
'breed':'bulldog'},
'Henley':{'color':'blue', 'weight': 70, 'age':'2',
'breed':'bulldog'},
'Emi':{'color':'lilac', 'weight': 65, 'age':'1',
'breed':'bulldog'},
}
I want to take the non-nested key, which is name of each dog (i.e. Buford, Henley, Emi), switch it with nested value for the age (i.e. 3, 2, 1), and then change the nested key name from 'age' to 'name.' So the output should look like this:
pet_dictionary = {'3':{'color':'white', 'weight': 95, 'name':'Buford',
'breed':'bulldog'},
'2':{'color':'blue', 'weight': 70, 'name':'Henley',
'breed':'bulldog'},
'1':{'color':'lilac', 'weight': 65, 'name':'Emi',
'breed':'bulldog'},
}
I understand how to do this manually one-by-one, but I'm not sure what the best approach is for making all of these changes in a more elegant/optimal way.

While iterating your dictionary, you can cleanly build a new dictionary in three steps:
# Preserves original dict
d = {}
for k, v in pet_dictionary.items():
key = v["age"] # 1. grab the new key
d[key] = {"name": k} # 2. add new "name" item
d[key].update({k_:v_ for k_, v_ in v.items() if k_!="age"}) # 3. update the new dict
d

This might help
pet_dictionary = {'Buford':{'color':'white', 'weight': 95, 'age':'3',
'breed':'bulldog'},
'Henley':{'color':'blue', 'weight': 70, 'age':'2',
'breed':'bulldog'},
'Emi':{'color':'lilac', 'weight': 65, 'age':'1',
'breed':'bulldog'},
}
d = {}
for k,v in pet_dictionary.items():
d[v['age']] = pet_dictionary[k]
d[v['age']].update({"name": k})
del d[v['age']]['age']
print d
Output:
{'1': {'color': 'lilac', 'breed': 'bulldog', 'name': 'Emi', 'weight': 65}, '3': {'color': 'white', 'breed': 'bulldog', 'name': 'Buford', 'weight': 95}, '2': {'color': 'blue', 'breed': 'bulldog', 'name': 'Henley', 'weight': 70}}

This is a situation where pythons iterables come to shine
p = {'Buford':{'color':'white', 'weight': 95, 'age':'3',
'breed':'bulldog'},
'Henley':{'color':'blue', 'weight': 70, 'age':'2',
'breed':'bulldog'},
'Emi':{'color':'lilac', 'weight': 65, 'age':'1',
'breed':'bulldog'},
}
new_dictionary = {p[i]['age']:{'color':p[i]['color'],'weight':p[i]['weight'],
'name':i,'breed':p[i]['breed']} for i in p}
Output:
{'3': {'color': 'white', 'weight': 95, 'name': 'Buford', 'breed': 'bulldog'},
'2': {'color': 'blue', 'weight': 70, 'name': 'Henley', 'breed': 'bulldog'},
'1': {'color': 'lilac', 'weight': 65, 'name': 'Emi', 'breed': 'bulldog'}}

With a couple of comprehensions, you can do that transformation like:
Code:
new_dict = {
info['age']: {k: v for k, v in list(info.items()) + [('name', name)]
if k != 'age'}
for name, info in pet_dictionary.items()
}
Test Code:
pet_dictionary = {
'Buford': {'color': 'white', 'weight': 95, 'age': '3', 'breed': 'bulldog'},
'Henley': {'color': 'blue', 'weight': 70, 'age': '2', 'breed': 'bulldog'},
'Emi': {'color': 'lilac', 'weight': 65, 'age': '1', 'breed': 'bulldog'},
}
new_dict = {
info['age']: {k: v for k, v in list(info.items()) + [('name', name)]
if k != 'age'}
for name, info in pet_dictionary.items()
}
for dog in new_dict.items():
print(dog)
Results:
('3', {'color': 'white', 'weight': 95, 'breed': 'bulldog', 'name': 'Buford'})
('2', {'color': 'blue', 'weight': 70, 'breed': 'bulldog', 'name': 'Henley'})
('1', {'color': 'lilac', 'weight': 65, 'breed': 'bulldog', 'name': 'Emi'})

With pandas,
import pandas as pd
pet_dictionary = {'Buford':{'color':'white', 'weight': 95, 'age':'3',
'breed':'bulldog'},
'Henley':{'color':'blue', 'weight': 70, 'age':'2',
'breed':'bulldog'},
'Emi':{'color':'lilac', 'weight': 65, 'age':'1',
'breed':'bulldog'},
}
pd.DataFrame.from_dict(pet_dictionary, orient='index') \
.reset_index() \
.rename(columns={'index': 'name'}) \
.set_index('age') \
.to_dict('index')

def flip(k, v):
v1 = dict(v)
v1.update(name=k)
return v1.pop('age'), v1
pet_dictionary2 = dict([flip(k, v) for k, v in pet_dictionary.items()])
# import pprint as pp; pp.pprint(pet_dictionary2)
# {'1': {'breed': 'bulldog', 'color': 'lilac', 'name': 'Emi', 'weight': 65},
# '2': {'breed': 'bulldog', 'color': 'blue', 'name': 'Henley', 'weight': 70},
# '3': {'breed': 'bulldog', 'color': 'white', 'name': 'Buford', 'weight': 95}}
If it is ok to change the previous dictionary, then you can do:
def flip(k, v):
v.update(name=k)
return v.pop('age'), v

Doing this in few lines, without any additional libs, but mutating original dictionary:
pet_dictionary = {
nested.pop('age'): nested.update({'name': name}) or nested
for name, nested in pet_dictionary.items()
}
And with additional import, but without mutating pet_dictionary:
import copy
new_pet_dict = {
nested.pop('age'): nested.update({'name': name}) or nested
for name, nested in copy.deepcopy(pet_dictionary).items()
}
...which leaves original pet_dictionary untouched.
Info
Initially, I published different answer, where key in new dict where created using .pop method, and nested dict using {**nested, 'name': name} but it didn't work. It would be much cleaner solution, but AFAIK, interpreter reads code from right to left and... that's obviously wouldn't work using this approach.
How does this work then? It looks little tricky, especially line:
nested.update({'name': name}) or nested
But let's have a closer look. We need nested to be updated with name key, but that returns None and mutates object. So left part of this or will be always None and we would like to have dict object in our dict comprehension. Here comes short circuit evaluation in Python, which always returns second operand if first is falsy.
None-mutating example uses deepcopy and mutates ad-hoc copy, not original dictionary.

Update for Python 3.8:
Only if mutating original dict is acceptable (credits to #pylang for noticing it), there is neat syntax for playing with dictionaries:
new = {nested.pop('age'): {**nested, 'name': name} for name, nested in pet_dictionary.items()}

Here's a two liner:
##add new value, which was formerly the key
{k: v.update({'name':k}) for k,v in pet_dictionary.items()}
##reset keys to value of 'age'
new_pet = {v.get('age'): v for k,v in pet_dictionary.items()}

Related

How can I collect key-value pairs of dictionaries into one large dictionary in Python?

I have a dictionary in the following format:
data = {
'Bob': {
'age': 12,
'weight': 150,
'eye_color': 'blue'
},
'Jim': {
'favorite_food': 'cherries',
'sport': 'baseball',
'hobby': 'running'
},
'Tom': {
'strength': 'average',
'endurance': 'high',
'heart_rate': 'low'
}
}
What is the most Pythonic way to concatenate all of the dictionaries within dict into a new dictionary so that I would end up with something like the following:
new_dict = {
'age': 12,
'weight': 150,
'eye_color': 'blue',
'favorite_food': 'cherries',
'sport': 'baseball',
'hobby': 'running',
'strength': 'average',
'endurance': 'high',
'heart_rate': 'low'
}
You can use functools.reduce() to build up the result, unioning one dictionary at a time:
from functools import reduce
data = {
'Bob' : { 'age': 12, 'weight': 150, 'eye_color': 'blue' },
'Jim' : { 'favorite_food': 'cherries', 'sport': 'baseball', 'hobby': 'running' },
'Tom' : { 'strength': 'average', 'endurance': 'high', 'hear_rate': 'low' }
}
result = reduce(lambda x, y: dict(**x, **y), data.values(), {})
print(result)
This outputs:
{'age': 12, 'weight': 150, 'eye_color': 'blue', 'favorite_food': 'cherries',
'sport': 'baseball', 'hobby': 'running', 'strength': 'average',
'endurance': 'high', 'hear_rate': 'low'}
On Python 3.9 or higher, you can use lambda x: x | y, operator.or_, or dict.__or__ instead of lambda x: dict(**x, **y) if you're on Python 3.9 or higher. The latter two are from a suggestion by Mad Physicist.
One option is to use a dictionary comprehension with a nested generator expression:
new_dict = {k: v for d in data.values() for k, v in d.items()}
Another way that's subtly different to to use collections.ChainMap:
new_dict = collections. ChainMap(*data.values())
In this case, new_dict will not be a dict, but will quack like one just fine. Lookup will be a bit slower, but construction will be faster.

Iterating through a nested dictionary to check if a particular value is all numerical letters

the following is a subset of a really large nested dictionary that I have:
{
'1': {'Name': 'Katherine Watson',
'Age': '1',
'Height': '150'},
'2': {'Name': 'Emilia Li',
'Age': '56',
'Height': '175'},
'3': {'Name': 'Dorothy Johnson',
'Age': '29',
'Height': '162'},
'4': {'Name': 'Alexandar Knight',
'Age': '14',
'Height': '164r'}
}
I'm having trouble figuring out how to write a function that will iterate through the specific key ('Height'), which then returns the corresponding value if it's all numerical numbers or None otherwise.
E.g. the dictionary with ID'1' should return '150' for the height. But the dictionary with ID'4' should return None for the height.
Here's a code I've written but it only returns '150' instead of iterating through all the IDs and returning '150' '175' '162' 'None'.
data = {
'1': {'Name': 'Katherine Watson',
'Age': '1',
'Height': '150'},
'2': {'Name': 'Emilia Li',
'Age': '56',
'Height': '175'},
'3': {'Name': 'Dorothy Johnson',
'Age': '29',
'Height': '162'},
'4': {'Name': 'Alexandar Knight',
'Age': '14',
'Height': '164r'}
}
def person_height(height):
for some_id, info in data.items():
if info['Height'].isnumeric():
return info['Height']
else:
return None
Use isdigit
data = {
'1': {'Name': 'Katherine Watson',
'Age': '1',
'Height': '150'},
'2': {'Name': 'Emilia Li',
'Age': '56',
'Height': '175'},
'3': {'Name': 'Dorothy Johnson',
'Age': '29',
'Height': '162'},
'4': {'Name': 'Alexandar Knight',
'Age': '14',
'Height': '164r'}
}
def person_height(height):
if height.isdigit():
return height
for some_id, info in data.items():
print("\nID:", some_id)
print("Height:", person_height(info['Height']))
Output:
ID: 1
Height: 150
ID: 2
Height: 175
ID: 3
Height: 162
ID: 4
Height: None
You could also do this with a list comprehension.
def get_heights(data):
return [int(person['Height'])
if person['Height'].isdigit()
else None
for person in data.values()]
print(get_heights(data))
Running it with your sample data outputs:
[150, 175, 162, None]
Since you're not using the IDs, you can use .values() instead of .items(). And in your code, you named the argument height but then refer to data in the function body. This means that it doesn't matter what you supply as the argument; the code only works because it's referring back to the globally defined variable, which happens to have the same name.
I've also converted the heights to integers, even though you didn't specifically request that.
Your code is fine actually but return will break the loop immediately and return the first result only so just turn your return to print() will do the work.
Another way is save the result to a list first and read them later:
data = {
'1': {'Name': 'Katherine Watson',
'Age': '1',
'Height': '150'},
'2': {'Name': 'Emilia Li',
'Age': '56',
'Height': '175'},
'3': {'Name': 'Dorothy Johnson',
'Age': '29',
'Height': '162'},
'4': {'Name': 'Alexandar Knight',
'Age': '14',
'Height': '164r'}
}
def person_height(data):
height_list = []
for some_id, info in data.items():
if info['Height'].isnumeric():
height_list.append(info['Height'])
else:
height_list.append(None)
return height_list
for height in person_height(data):
print(height)
Output:
150
175
162
None
In order to store your results in a "clean" dictionary, you will need as many nested loops as nested dictionaries you have. So:
def check_height(your_dict):
new_clean_dict = {}
for a, b in your_dict.items():
for e, f in b.items():
if e == 'Height' and f.isdigit():
new_clean_dict[a] = {'Height': f}
else:
new_clean_dict[a] = {'Height': None}
return new_clean_dict
This will produce a new dictionary with the same root key and with a value for each key that is a nested dictionary with only the key Height.
In order to get the results:
new_clean_dict = check_height(your_dict)

Replacing a specific item of a dict in a list of dicts? [duplicate]

This question already has answers here:
Find the index of a dict within a list, by matching the dict's value
(12 answers)
Closed 3 years ago.
Say I have the following list of dicts:
dicts = [
{'name': "Tom", 'age': 20, 'height': 1.8},
{'name': "Isa", 'age': 31, 'height': 1.5},
... ]
I'd like to replace the age of a given person with a given value.
def replace_age(person, age):
dicts[?]['age'] = age
replace_age("Tom", 45)
Assuming that name is unique, what's the most elegant way to go about this?
In a golden world: dicts[name=person]['age'] = age
Not a duplicate of Find the index of a dict within a list, by matching the dict's value: I'm looking to change the value, not get the index. And Tom is a pretty common name.
this is a variant:
def replace_age(person, age):
try:
dct = next(item for item in dicts if item["name"] == person)
except StopIteration:
# person not found
# here you could print a message or raise an error...
return
dct["age"] = age
this will only work if the names are unique. if they are not only the first occurrence will be replaced.
since the name is unique you can change your data structure where you keep your data to achieve your task efficiently:
efficient_dict = {e['name']: {'age' : e.get('age'), 'height': e.get('height')} for e in dicts}
def replace_age(person, age):
if person in efficient_dict:
efficient_dict[person]['age'] = age
Here's my version
dictionaries = [
{'name': "Tom", 'age': 20, 'height': 1.8},
{'name': "Isa", 'age': 31, 'height': 1.5}
]
def change_dict_person_age(dictionaries, person, age):
for dictionary in dictionaries:
if dictionary['name'] == person:
dictionary['age'] = age
# Uncomment the following line if you want to stop at the 1st
# match. Leave it as is if you want to modify all occurrences.
#break
change_dict_person_age(dictionaries, "Tom", 40)
print(dictionaries)
#[{'name': 'Tom', 'age': 40, 'height': 1.8}, {'name': 'Isa', 'age': 31, 'height': 1.5}]
I also wrote a more generic version for broader user:
dictionaries = [
{'name': "Tom", 'age': 20, 'height': 1.8},
{'name': "Isa", 'age': 31, 'height': 1.5}
]
def change_dict(dictionaries, key_to_check, value_to_match, key_to_change, value_to_change):
for dictionary in dictionaries:
if dictionary[key_to_check] == value_to_match:
dictionary[key_to_change] = value_to_change
# Uncomment the following line if you want to stop at the 1st
# match. Leave it as is if you want to modify all occurrences.
#break
change_dict(dictionaries, "name", "Tom", "age", 50)
print(dictionaries)
#[{'name': 'Tom', 'age': 50, 'height': 1.8}, {'name': 'Isa', 'age': 31, 'height': 1.5}]

How to create a dictionary with new KEY with data from list? [duplicate]

This question already has answers here:
Convert [key1,val1,key2,val2] to a dict?
(12 answers)
Closed 6 years ago.
I have the following list which I want to convert into a dictionary.
newData = ['John', 4.52, 'Jane', 5.19, 'Ram', 4.09, 'Hari', 2.97, 'Sita', 3.58, 'Gita', 4.1]
I want to create a dictionary like this:
newDict = [{'name': 'John', 'Height': 4.52},
{'name': 'Jane', 'Height': 5.19},
{'name': 'Ram', 'Height': 4.09},
{'name': 'Hari', 'Height': 2.97},
{'name': 'Sita', 'Height': 3.58},
{'name': 'Gita', 'Height': 4.1}]
What will be the easiest method to do this?
Enjoy:
newData = ['John', 4.52, 'Jane', 5.19, 'Ram', 4.09, 'Hari', 2.97, 'Sita', 3.58, 'Gita', 4.1]
newDict = [
{"name": name, "height": height}
for name, height in zip(newData[::2], newData[1::2])
]
print(newDict)
Here is quick list expansion you can do:
newDict = [{ 'name': newData[x], 'Height': newData[x + 1] } for x in range(0, len(newData), 2) ]
The trick is to use the step parameter with range, which gives you every other element.
newDict = []
for i in range(0,len(newList),2):
newDict.append(
{'name':newList[i],
'hieght':newList[i+1]}
)

Loop in dict python

I have already looked around but have not found any help for this.
This is my dict:
{'id': 1, 'name': 'Studio Pierrot'}
{'id': 29, 'name': 'VAP'}
{'id': 102, 'name': 'FUNimation Entertainment'}
{'id': 148, 'name': 'Hakusensha'}
{'id': 238, 'name': 'AT-X'}
{'id': 751, 'name': 'Marvelous AQL'}
{'id': 1211, 'name': 'Tokyo MX'}
aproducers = an.info['Producers'][0]['name']
for key in aproducers:
print key
The output is like:
S
t
u
d
i
o
...
I want to output just Studio Pierrot,VAP,FUNimation Entertainment...
You’re looping over a string, the single name value of the first producer. You need to loop over the producers instead:
for producer in an.info['Producers']:
print producer['name']
I suggest you to use the methods keys() values() items() and to use nested dicts
for the last question you can just use:
listproducer = []
for producer in an.info['Producers']:
listproducer.append( producer['name'] )

Categories