Python 有效地分组到dict中

Python 有效地分组到dict中,python,pandas,Python,Pandas,我有一个元组列表: [('Player1', 'A', 1, 100), ('Player1', 'B', 15, 100), ('Player2', 'A', 7, 100), ('Player2', 'B', 65, 100), ('Global Total', None, 88, 100)] 我希望将其转换为以下格式的dict: { 'Player1': { 'A': [1, 12.5], 'B': [15, 18.75],

我有一个元组列表:

[('Player1', 'A', 1, 100),
('Player1', 'B', 15, 100),
('Player2', 'A', 7, 100),
('Player2', 'B', 65, 100),
('Global Total', None, 88, 100)]
我希望将其转换为以下格式的dict:

{
  '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]
         }
}
所以每个玩家都有自己的本地总值,以及根据其全球总值计算的百分比

目前,我是这样做的:

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,但类别是唯一的)

一个解决方案是使用groupby对来自同一玩家的连续玩家分数进行分组

tup = [('Player1', 'A', 1, 100),('Player1', 'B', 15, 100),('Player2', 'A', 7, 100),    ('Player2', 'B', 65, 100),    ('Global Total', None, 88, 100)]`
然后导入我们的groupby

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]
现在我们有了一个玩家数量的dict,但唯一有效的百分比是总数,我们没有全局计数

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\u 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]}})
defaultdict(,
{'Player2':{'A':[7,87.5],'B':[65,81.25],'Total':[72,81.8183]},
‘Player1’:{‘A’:[1,12.5],‘B’:[15,18.75],‘Total’:[16,18.18183]},
'全球总计':{'A':[8100],无:[88100],'B':[80100]})

如果您确实需要它,您可以删除全局合计中的
None
条目和
dict(result)
以将
defaultdict
转换为普通的
dict

每个人似乎都喜欢只使用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)

在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-构建重新映射的
组的新目录

这是通过定义指示如何处理键和值的键函数来实现的。reduce函数将值处理为子字典。另见

步骤2-为查找建立一个
中间
dict

>>> intermediate
defaultdict(list, {'A': [1, 7], 'B': [15, 65]})
步骤3-从后面的词典中构建一个
最终的
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有两个条目作为一个项目呢?玩家总是依次增加吗?(即元组列表中PLAYER1位于PLAYER2之前,player5位于PLAYER4之后?)是否总是有相同的12个类别(
A..L
)?在所需的dict中,哪些项目是变量,哪些是字符串?我无法对您发布的代码进行测试,因为您未能提供。在您发布MCVE代码并准确描述问题之前,我们无法有效地帮助您。我们应该能够将您发布的代码粘贴到文本文件中,并重现您描述的问题。@Prune在这里:)您的代码在
sum(v[1][0]处崩溃因为…
1
上有一个键错误,因为没有这样的键。它仍然失败。我完成了。我希望
groupby
的答案能解决您的问题。此函数的输出不能生成正确的百分比。啊,是的……我误读了问题陈述,对重做它没有兴趣……我会尽快删除此解决方案介意添加额外的步骤吗?我无法获得结果。获取总数的步骤是什么?是的,请。我无法将列添加到行中,并且不知道如何在列之间添加百分比列,等等。是否有方法以友好方式将列合并?我正在考虑将其调整为包含更多列的数据集。有一些类似于
pd.concat([df.a,mixed.a,…,df.t,mixed.t],index=1)
我认为:
import itertools;cols=itertools.chain.from_iterable([(df[a],shares[b]),对于zip中的a,b(df.columns,shares.columns)];mixed_df=pd.concat(cols,axis=1)
,但可能有更好/更小的内容。您还必须更改新的列名,因此可能需要一个单独的特定问题,包括首次尝试。
>>> 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]}})