У меня есть список кортежей:
[('Player1', 'A', 1, 100),
('Player1', 'B', 15, 100),
('Player2', 'A', 7, 100),
('Player2', 'B', 65, 100),
('Global Total', None, 88, 100)]
Который я хочу преобразовать в диктант в следующем формате:
{
'Player1': {
'A': [1, 12.5],
'B': [15, 18.75],
'Total': [16, 18.18]
},
'Player2': {
'A': [7, 87.5],
'B': [65, 81.25],
'Total': [72, 81.81]
},
'Global Total': {
'A': [8, 100],
'B': [80, 100]
}
}
Таким образом, каждый элемент Player имеет свое общее значение местный и процентное соотношение в соответствии с его общим значением Глобальный.
В настоящее время я делаю это так:
fixed_vals = {}
for name, status, qtd, prct in data_set: # This is the list of tuples var
if name in fixed_vals:
fixed_vals[name].update({status: [qtd, prct]})
else:
fixed_vals[name] = {status: [qtd, prct]}
fixed_vals['Global Total']['Total'] = fixed_vals['Global Total'].pop(None)
total_a = 0
for k, v in fixed_vals.items():
if k != 'Global Total':
total_a += v['A'][0]
fixed_vals['Global Total']['A'] = [
total_a, total_a * 100 / fixed_vals['Global Total']['Total'][0]
]
fixed_vals['Global Total']['B'] = [
fixed_vals['Global Total']['Total'][0] - total_a,
fixed_vals['Global Total']['Total'][0] - fixed_vals['Global Total']['A'][1]
]
for player, vals in fixed_vals.items():
if player != 'Global Total':
vals['A'][1] = vals['A'][0] * 100 / fixed_vals['Global Total']['A'][0]
vals['B'][1] = fixed_vals['Global Total']['A'][1] - vals['B'][1]
проблема в том, что это не очень гибко, так как мне нужно сделать что-то похожее на это, но с почти 12 категориями (A, B, ...)
Есть ли лучший подход к этому? Может, с пандами это банально?
Отредактируйте для пояснения:
Для каждого игрока нет повторяющихся категорий, все они имеют одинаковую последовательность (у некоторых может быть 0, но категория уникальна)
В желаемом dict, какие из этих элементов являются переменными, а какие - строками? Я не могу проверить ваш опубликованный код, так как вы не предоставили Минимальный, полный, проверяемый пример. Мы не сможем эффективно помочь вам, пока вы не опубликуете свой код MCVE и точно не опишете проблему. Мы сможем вставить ваш опубликованный код в текстовый файл и воспроизвести описанную вами проблему.
@ Приготовься :)
Ваш код вылетает на sum(v[1][0] for ... с ключевой ошибкой на 1, поскольку такого ключа нет.
как рассчитывается Player1 ['Total] [1]?
@Prune Мне очень жаль, я считаю, что это должно исправить.
Это все еще не удается. Я задолбался. Надеюсь, ответ groupby решит вашу проблему.
Объясните, пожалуйста, как рассчитываются вторые позиции в каждом списке?
@pylang Второй элемент - это процент игрока, относящегося к этой категории.






одним из решений было бы использовать groupby для группировки очков подряд одного и того же игрока.
tup = [('Player1', 'A', 1, 100),('Player1', 'B', 15, 100),('Player2', 'A', 7, 100), ('Player2', 'B', 65, 100), ('Global Total', None, 88, 100)]`
затем импортируйте нашу группу
from itertools import groupby
result = dict((name,dict((x[1],x[2:]) for x in values)) for name,values in groupby(tup,lambda x:x[0]))
тогда просто идите и обновите все итоги
for key in result:
if key == "Global Total": continue # skip this one ...
# sum up our player scores
result[key]['total'] = [sum(col) for col in zip(*result[key].values())]
# you can print the results too
print result
# {'Player2': {'A': (7, 100), 'total': [72, 200], 'B': (65, 100)}, 'Player1': {'A': (1, 100), 'total': [16, 200], 'B': (15, 100)}, 'Global Total': {'total': [88, 100], None: (88, 100)}}
ПРИМЕЧАНИЕ Это решение !ТРЕБУЕТ!, что все результаты player1 сгруппированы вместе в вашем кортеже, а все результаты player2 сгруппированы и т. д.
Результат этой функции не дает правильных процентов.
ааааааааааааааааааааааааааааааааудудато хват в проблеме, я неправильно прочитал формулировку проблемы и не хочу ее переделывать ... Я скоро удалю это решение
A) Разбейте код на управляемые части:
from collections import defaultdict
result = defaultdict(dict)
for (cat, sub, num, percent) in input_list:
result[cat][sub] = [num, percent]
Теперь у нас есть подсказка с подсчетом игроков, но единственные действительные проценты являются общими, а глобальных подсчетов у нас нет.
from collections import Counter
def build_global(dct):
keys = Counter()
for key in dct:
if key == "Global Total":
continue
for sub_key in dct[key]:
keys[sub_key] += dct[key][sub_key][0]
for key in keys:
dct["Global Total"][key] = [keys[key], 100]
build_global(result) теперь дает действительные глобальные подсчеты для каждого события.
Ну наконец то:
def calc_percent(dct):
totals = dct["Global Total"]
for key in dct:
local_total = 0
if key == "Global Total":
continue
for sub_key in dct[key]:
local_total += dct[key][sub_key][0]
dct[key][sub_key][1] = (dct[key][sub_key][0]/float(totals[sub_key][0])) * 100
dct[key]['Total'] = [local_total, (local_total/float(dct['Global Total'][None][0])) * 100]
calc_percent(result) проходит и строит проценты.
результат тогда:
defaultdict(<type 'dict'>,
{'Player2': {'A': [7, 87.5], 'B': [65, 81.25], 'Total': [72, 81.81818181818183]},
'Player1': {'A': [1, 12.5], 'B': [15, 18.75], 'Total': [16, 18.181818181818183]},
'Global Total': {'A': [8, 100], None: [88, 100], 'B': [80, 100]}})
Если вам нужен точно, как указано, вы можете удалить запись None из глобального итога и dict(result), чтобы преобразовать defaultdict в обычный dict.
Кажется, всех привлекает решение только для диктовки, но почему бы не попробовать преобразовать его в pandas?
import pandas as pd
# given
tuple_list = [('Player1', 'A', 1, 100),
('Player1', 'B', 15, 100),
('Player2', 'A', 7, 100),
('Player2', 'B', 65, 100),
('Global Total', None, 88, 100)]
# make a dataframe
df = pd.DataFrame(tuple_list , columns = ['player', 'game','score', 'pct'])
del df['pct']
df = df[df.player!='Global Total']
df = df.pivot(index='player', columns='game', values='score')
df.columns.name=''
df.index.name=''
# just a check
assert df.to_dict() == {'A': {'Player1': 1, 'Player2': 7},
'B': {'Player1': 15, 'Player2': 65}}
# A B
#player
#Player1 1 15
#Player2 7 65
print('Obtained dataset:\n', df)
По сути, все, что вам нужно, это фрейм данных 'df', а остальное вы можете вычислить и добавить позже, нет необходимости сохранять его в словаре.
Ниже обновлено по запросу OP:
# the sum across columns is this - this was the 'Grand Total' in the dicts
# A 8
# B 80
sum_col = df.sum(axis=0)
# lets calculate the share of each player score:
shares = df / df.sum(axis=0) * 100
assert shares.transpose().to_dict() == {'Player1': {'A': 12.5, 'B': 18.75},
'Player2': {'A': 87.5, 'B': 81.25}}
# in 'shares' the columns add to 100%:
# A B
#player
#Player1 12.50 18.75
#Player2 87.50 81.25
# lets mix up a dataframe close to original dictionary structure
mixed_df = pd.concat([df.A, shares.A, df.B, shares.B], axis=1)
totals = mixed_df.sum(axis=0)
totals.name = 'Total'
mixed_df = mixed_df.append(totals.transpose())
mixed_df.columns = ['A', 'A_pct', 'B', 'B_pct']
print('\nProducing some statistics\n', mixed_df)
Не возражаете поставить дополнительные шаги? Мне не удается добиться результата.
Шаги, чтобы получить итоги?
Да, пожалуйста. Я не могу добавить столбец к строкам и понятия не имею, как добавить процентный столбец между столбцами и т. д.
Есть ли способ динамически объединять столбцы? Я подумываю адаптировать это к набору данных с большим количеством столбцов. Что-то вроде pd.concat([df.a, mixed.a, ..., df.t, mixed.t], index=1)
Возможно это, @EvgenyPogrebnyak: pd.concat([val for sub in zip([df[col] for col in df.columns.values.tolist()], [mixed_df[col] for col in mixed_df.columns.values.tolist()]) for val in sub], axis=1) ??
Я бы подумал так: import itertools; cols = itertools.chain.from_iterable([(df[a], shares[b]) for a, b in zip(df.columns, shares.columns)]) ; mixed_df = pd.concat(cols, axis=1), но, может быть, есть что-нибудь получше / поменьше. Вам также придется изменить новые имена столбцов, поэтому, вероятно, заслуживает отдельного конкретного вопроса с включенными первыми попытками.
Используя инструмент переназначения из more_itertools в Python 3.6+:
Дано
import copy as cp
import collections as ct
import more_itertools as mit
data = [
("Player1", "A", 1, 100),
("Player1", "B", 15, 100),
("Player2", "A", 7, 100),
("Player2", "B", 65, 100),
('Global Total', None, 88, 100)
]
# Discard the last entry
data = data[:-1]
# Key functions
kfunc = lambda tup: tup[0]
vfunc = lambda tup: tup[1:]
rfunc = lambda x: {item[0]: [item[1]] for item in x}
Код
# Step 1
remapped = mit.map_reduce(data, kfunc, vfunc, rfunc)
# Step 2
intermediate = ct.defaultdict(list)
for d in remapped.values():
for k, v in d.items():
intermediate[k].extend(v)
# Step 3
remapped["Global Total"] = {k: [sum(v)] for k, v in intermediate.items()}
final = cp.deepcopy(remapped)
for name, d in remapped.items():
for lbl, v in d.items():
stat = (v[0]/remapped["Global Total"][lbl][0]) * 100
final[name][lbl].append(stat)
Подробности
Шаг 1 - создайте новый дикт из групп remapped.
Это делается путем определения ключевых функций, которые определяют, как обрабатывать ключи и значения. Функция сокращения обрабатывает значения в подкаталоги. См. Также документы для получения более подробной информации о more_itertools.map_reduce.
>>> remapped
defaultdict(None,
{'Player1': {'A': [1], 'B': [15]},
'Player2': {'A': [7], 'B': [65]}})
Шаг 2 - создайте диктатор intermediate для поиска
>>> intermediate
defaultdict(list, {'A': [1, 7], 'B': [15, 65]})
Шаг 3 - соберите final dict из последних словарей
>>> final
defaultdict(None,
{'Player1': {'A': [1, 12.5], 'B': [15, 18.75]},
'Player2': {'A': [7, 87.5], 'B': [65, 81.25]},
'Global Total': {'A': [8, 100.0], 'B': [80, 100.0]}})
что, если player1 имеет 2 входа для A? игроки всегда последовательно увеличиваются? (т.е. PLAYER1 идет в списке кортежей ПЕРЕД PLAYER2, а PLAYER 5 идет после PLAYER4?) Всегда ли одни и те же 12 категорий (
A..L)?