# Rename subkeys of nested dictionary dynamically

My nested (ordered) dictionary looks like this:

OrderedDict([
('1', OrderedDict([('1994_fte_count', '4'), ('1995_fte_count', '2'), ('1996_fte_count', '5'), ('1997_fte_count', '7')])),
('2', OrderedDict([('1994_fte_count', '5'), ('1995_fte_count', '22'), ('1996_fte_count', '6'), ('1997_fte_count', '3')])),
('3', OrderedDict([('1994_fte_count', '6'), ('1995_fte_count', '7'), ('1996_fte_count', '7'), ('1997_fte_count', '81')]))
])


The subkeys ("1994_fte_count", "1995_fte_count", etc.) all range up until 2000. I want to replace the year with the difference to a given year. This shall happen dynamically for a lot of years. For example, I start with 1995, and "1995_fte_count" shall be renamed "lag0_fte_count", while "1994_fte_count" shall be renamed "lag1_fte_count". I read the file in a several times, each time with a different benchmark year.

I currently use the following nested loop:

year = "1996" # comes as string
lag_length = 2
year_range = range(int(year)-lag_length, int(year))
replacement = ["lag" + str(i) for i in reversed(range(0,lag_length))]
for ID in my_dict.keys():
for subkey in my_dict[ID].keys():
for i in range(0, len(year_range)):
acutal_year = str(year_range[i])
if subkey.startswith(acutal_year):
newkey = subkey.replace(acutal_year, replacement[i])
my_dict[ID][newkey] = my_dict[ID].pop(subkey)


Given the nature of the task (simple text replace), I do think that there must be a shorter, more readable and faster way than to iterate over all the subkeys multiple times. Who knows how to?

Clarity probably trumps speed, but then again, it's not quite clear why you need this format and what is further going to happen to that dictionary.

You can definitely get rid of the innermost loop by computing the replacement index on the fly instead of testing all possibilities:

from collections import OrderedDict

year = int("1996") # comes as string
lag_length = 2
replacement = ["lag%s_fte_count" % (i - 1) for i in range(lag_length, 0, -1)]

for ID, value in my_dict.iteritems():
for subkey in value.keys():
year_string = subkey[:4]

if not year_string.isdigit():
continue

parsed_year = int(year_string)

if not year - lag_length <= parsed_year < year:
continue

value[replacement[lag_length + parsed_year - year]] = value.pop(subkey)


I also used iteritems to get both values at the same time; for value.keys() that's sadly not possible while iterating. The year input should also be parsed immediately as it'll always be used in the integer form.

Next I'm assuming the keys start with the four-character year, so then the year can be parsed and compared with the target range and if it's in the range, the key is renamed by calculating the correct index.

Furthermore you could remove the isdigit call if the format allows for it and replacement could be removed by always generating the string immediately, e.g. like this:

value["lag%s_fte_count" % (year - parsed_year - 1)] = value.pop(subkey)

• You are absolutely right, readability/clarity is better. The purpose of all of this is to aggregate over different items while always using the same relative lag: Items with benchmark year 1997 become comparable with items with benchmark year 2010. One last question: How exactly would you get rid of replacement? – MERose Jun 5 '15 at 18:14
• @MERose see edit / last line. – ferada Jun 8 '15 at 10:41
• You should have accepted my edits, because the above code is wrong, as it is. First, it should be ["lag%s" % i for i in range(lag_length, 0, -1)], because I am not interested in lag0. Second, the new key's name is corresponds to the old one (which is say 1994_fte_count), except for the year string (1994), which is replaced by say lag1, making lag1_fte_count. Your code just makes lag1. – MERose Jun 9 '15 at 16:18
• @MERose I fixed the keys to include the _fte_count suffix. It generates the same indexes as your initial one, so I'm not quite sure what you mean. Edit: If you really mean with year == 1995 it should generate ...('1', OrderedDict([('1996_fte_count', '5'), ('1997_fte_count', '7'), ('lag1_fte_count', '4'), ('lag0_fte_count', '2')]))... then that change is of course trivial as well. – ferada Jun 9 '15 at 16:49