Python: вычитание между списками словарей
У меня есть 2 списка, содержащие словари следующим образом:
listone = [{'unit1': {'test1': 10}},
{'unit1': {'test2': 45'},
{'unit2': {'test1': 78'},
{'unit2': {'test2': 2'}}]
listtwo = [{'unit1': {'test1': 56}},
{'unit1': {'test2': 34'},
{'unit2': {'test1': 23'},
{'unit2': {'test2': 5'}}]
У меня также есть все имена устройств и имена тестов в отдельных списках:
units = ['unit1', 'unit2']
testnames = ['test1,'test2']
Как я могу найти дельту для каждого тестового значения, то есть val of (test2
- test1
), чтобы я мог, наконец, расположить данные следующим образом:
unit1, test1, delta
unit1, test2, delta
unit2, test1, delta
unit2, test2, delta
Пока у меня есть такие:
def delta(array1, array2):
temp = []
temp2 = []
tmp = []
tmp2 = []
delta = []
for unit in units:
for mkey in array1:
for skey in mkey:
if skey == unit:
temp.append(mkey[skey])
floater(temp) #floats all the values
for i in testnames:
for u in temp:
tmp.append(u[i])
tmp = filter(None, tmp2)
for mkey in array2:
for skey in mkey:
if skey == unit:
temp.append(mkey[skey])
floater(temp2)
for i in testnames:
for u in temp2:
tmp2.append(u[i])
tmp2 = filter(None, tmp2)
delta = [tmp2 - tmp for tmp2, tmp in zip(tmp2, tmp)]
print delta
delta(listone,listtwo)
К сожалению, код дает Keyerror
,:(Помогите, пожалуйста. Спасибо.
4 ответа
Подобный, но немного более инкапсулированный:
from collections import defaultdict
listone = [
{'unit1': {'test1': 10}},
{'unit1': {'test2': 45}},
{'unit2': {'test1': 78}},
{'unit2': {'test2': 2}}
]
listtwo = [
{'unit1': {'test1': 56}},
{'unit1': {'test2': 34}},
{'unit2': {'test1': 23}},
{'unit2': {'test2': 5}}
]
def dictify(lst):
res = defaultdict(lambda: defaultdict(int))
for entry in lst:
for unit,testentry in entry.iteritems():
for test,val in testentry.iteritems():
res[unit][test] = val
return res
# returns dict['unitX']['testY'] = val
def genDeltas(dictA, dictB):
units = dictA.keys()
units.sort()
tests = dictA[units[0]].keys()
tests.sort()
for unit in units:
_A = dictA[unit]
_B = dictB[unit]
for test in tests:
yield unit,test,(_B[test]-_A[test])
for unit,test,delta in genDeltas(dictify(listone),dictify(listtwo)):
print "{0}, {1}, {2}".format(unit,test,delta)
Изменить: чтобы найти средние значения поля:
class Avg(object):
def __init__(self, total=0.0, num=0):
super(Avg,self).__init__()
self.total = total
self.num = num
def add(self, value):
self.total += value
self.num += 1
def value(self):
return self.total / self.num
def avgBy(data, field=0):
res = defaultdict(Avg)
for unit,testdict in data.iteritems():
for test,val in testdict.iteritems():
res[(unit,test)[field]].add(val)
return {item:avg.value() for item,avg in res.iteritems()}
dictone = dictify(listone)
avg_by_unit = avgBy(dictone, 0)
print(avg_by_unit)
avg_by_test = avgBy(dictone, 1)
print(avg_by_test)
Возможно преобразовать ваши данные в другую, более удобную структуру данных. Например, вместо listone
было бы проще работать с одним диктом, подобным этому:
{('unit1', 'test1'): 10,
('unit2', 'test1'): 78,
('unit2', 'test2'): 2,
('unit1', 'test2'): 45}
Так дано,
import itertools
units = ['unit1', 'unit2']
testnames = ['test1','test2']
listone = [{'unit1': {'test1': 10}},
{'unit1': {'test2': 45}},
{'unit2': {'test1': 78}},
{'unit2': {'test2': 2}}]
listtwo = [{'unit1': {'test1': 56}},
{'unit1': {'test2': 34}},
{'unit2': {'test1': 23}},
{'unit2': {'test2': 5}}]
Здесь мы конвертируем listone
а также listtwo
к списку диктов:
dicts=[{},{}]
for i,alist in enumerate([listone,listtwo]):
for item in alist:
for unit,testdict in item.iteritems():
for testname,value in testdict.iteritems():
dicts[i][unit,testname]=value
Теперь найти deltas
это просто:
for unit,testname in itertools.product(units,testnames):
delta=dicts[1][unit,testname]-dicts[0][unit,testname]
print('{u}, {t}, {d}'.format(u=unit,t=testname,d=delta))
доходность
unit1, test1, 46
unit1, test2, -11
unit2, test1, -55
unit2, test2, 3
Я думаю, что это проще сделать со словарями словарей. Здесь я определяю их поэтапно, так как я предполагаю, что вы собираете результаты некоторого процесса тестирования, но вы также можете сделать это в одной строке.
listOne = {}
listOne['unit1'] = {}
listOne['unit2'] = {}
listOne['unit1']['test1']=10
listOne['unit1']['test2']=45
listOne['unit2']['test1'] = 78
listOne['unit2']['test2'] = 2
listTwo = {}
listTwo['unit1'] = {}
listTwo['unit2'] = {}
listTwo['unit1']['test1']=56
listTwo['unit1']['test2']=34
listTwo['unit2']['test1'] = 23
listTwo['unit2']['test2'] = 5
units = ['unit1', 'unit2']
testnames = ['test1','test2']
deltas = {}
# collect the deltas
for unit in units :
deltas[unit] = {}
for test in testnames :
deltas[unit][test] = listTwo[unit][test] -listOne[unit][test]
# print put the results
for unit in units :
for test in testnames :
print unit, ', ', test, ', ', deltas[unit][test]
Это дает
unit1 , test1 , 46
unit1 , test2 , -11
unit2 , test1 , -55
unit2 , test2 , 3
Это решит вашу текущую проблему:
listone = [{'unit1': {'test1': 10}},
{'unit1': {'test2': 45}},
{'unit2': {'test1': 78}},
{'unit2': {'test2': 2}}]
listtwo = [{'unit1': {'test1': 56}},
{'unit1': {'test2': 34}},
{'unit2': {'test1': 23}},
{'unit2': {'test2': 5}}]
units = ['unit1', 'unit2']
testnames = ['test1', 'test2']
# Iterate over all units
for unit in units:
# Iterate over all tests
for test in testnames:
# Find the rows corresponding to our current unit/test
list1Row = [i for i,d in enumerate(listone) if d.keys()[0] == unit and d.values()[0].keys()[0] == test]
list2Row = [i for i,d in enumerate(listtwo) if d.keys()[0] == unit and d.values()[0].keys()[0] == test]
# Check to make sure there was exactly one match.
# This is another weakness of your data structure.
if (len(list1Row) == 1) and (len(list2Row) == 1):
list1Row = list1Row[0]
list2Row = list2Row[0]
delta = listtwo[list2Row].values()[0].values()[0] - listone[list1Row].values()[0].values()[0]
print unit, test, delta
Однако, как рекомендовал предыдущий постер, вы должны рассмотреть другую структуру данных. Я бы предложил что-то вроде одного словаря с ключом (unit, test) и значением списка.