2012-02-07 3 views
4

Я новичок в python и изучаю, как правильно поступать.python: создание гистограммы из словаря

У меня есть список словарей d. Каждый словарь представляет пользователей и содержит информацию, такую ​​как user_id, age и т. Д. Этот список d может содержать несколько словарей, которые представляют одного и того же пользователя (но с немного другой информацией, которая не имеет значения для моих целей). Я хочу создать гистограмму, показывающую, сколько пользователей находится в d с заданным возрастом. Как это сделать эффективным способом?

Редактировать: Я хочу подчеркнуть, что мне нужно устранить дубликаты в списке.

ответ

3

Ну, классический подход к решению этой проблемы можно было бы создать defaultdict:

import collections 
histogram = collections.defaultdict(int) 

Тогда итерации по словарях в списке, и (с помощью d_list вместо d как имя списка словарей),

for d in d_list: 
    histogram[d['age']] += 1 

Но вы указали дополнительную информацию, которая меня смущает. Вы сказали, что несколько dicts могут представлять одного и того же пользователя. Вы хотите удалить эти дубликаты из гистограммы? Если это ваш вопрос, один из подходов заключался бы в том, чтобы хранить пользователей в типе user_records, используя (firstname, lastname) кортежей в качестве ключей. Затем последовательные словари, представляющие одного и того же пользователя, будут разбивать друг друга, и только одна запись на пользователя будет сохранена. Затем перебирайте значения в , чтобы словарь (возможно, с использованием user_records.itervalues()).

Этот общий подход может быть изменен для использования любых значений в каждой записи, которые наилучшим образом идентифицируют уникальных пользователей. Если значение user_id уникально для каждого пользователя, используйте его как ключ вместо (firstname, lastname). Но ваш вопрос предложил (мне), что user_id не обязательно будет одинаковым для двух пользователей, которые являются одинаковыми.

После того как вы исключенные дубликаты, хотя, есть также ярлык, если вы используете Python> = 2.7:

histogram = collections.Counter(d['age'] for d in user_records.itervalues()) 

Некоторые примеры кода ... что у нас есть record_list:

>>> record_list 
[{'lastname': 'Mann', 'age': 23, 'firstname': 'Joe'}, 
{'lastname': 'Moore', 'age': 23, 'firstname': 'Alex'}, 
{'lastname': 'Sault', 'age': 33, 'firstname': 'Marie'}, 
{'lastname': 'Mann', 'age': 23, 'firstname': 'Joe'}] 
>>> user_ages = dict(((d['firstname'], d['lastname']), d['age']) for d in record_list) 
>>> user_ages 
{('Joe', 'Mann'): 23, ('Alex', 'Moore'): 23, ('Marie', 'Sault'): 33} 

Как вы можете видеть, у record_list есть дубликат, но user_ages dict нет. Теперь получение количества возрастов так же просто, как запуск значений через Counter.

>>> collections.Counter(user_ages.itervalues()) 
Counter({23: 2, 33: 1}) 

То же самое можно сделать с помощью любой строки или неизменного объекта, который может служить в качестве уникального идентификатора конкретного пользователя.

+0

Не могли бы вы развернуть это: «Один из подходов заключался бы в том, чтобы хранить пользователей в типе user_records с использованием (firstname, lastname) кортежей в качестве ключей. Затем последовательные словари, представляющие одного и того же пользователя, разбивали бы друг друга, и только одна запись на пользователя быть сохраненным ». – ashim

+0

Обратите внимание, что вы на самом деле имеете в виду «Python> = 2.7». :) – Dougal

+0

Кортежи строк могут быть ключом в словаре, и любой данный ключ может появляться в словаре только один раз, поэтому любые дубликаты будут автоматически удалены. Я отправлю код примера. – senderle

-2

Попытка улучшить ответ @ senderle, надеюсь, я лучше понял проблему.

Я предполагаю, что список содержит словари, где ключи являются идентификаторами пользователей и данные являются объектами, которые имеют age свойство:

import collections 
# Merge all dictionaries to one uid->age mapping (I'm sure there's a shorter way) 
all_ages={} 
for d1 in d: 
    for uid,data in d1.iteritems(): 
     all_ages[uid]=data.age 
# Count distinct users per age 
histogram = collections.defaultdict(int) 
for uid,age in all_ages.iteritems(): 
    histogram[age]+=1 
+0

делает эту строку 'для uid, данные в d1' работают, если в словаре есть больше сопоставлений – ashim

+0

Ответ @capoluca ugoren на самом деле не имеет большого смысла. Строка 'для uid, данные в d1' фактически пересекает * ключи *' d1' и ожидает, что они будут 2-мя кортежами, что почти наверняка не то, что вы хотите сделать. Он мог бы зацикливаться на 'd1.iteritems()', но этот формат данных по-прежнему невозможен (хотя это имеет смысл для цикла 'all_ages'). Кроме того, у Python нет '++' (вместо этого используйте '+ = 1'). Ответ отправителя является одним и тем же базовым подходом, но на самом деле имеет смысл. – Dougal

+0

@ Dougal, я признаю, что я получил синтаксис неправильно (и не проверял его). Использование 'iteritems' для обеих итераций и' + = 1' исправит его. Отличие от ответа senderle заключается в том, что я принимаю несколько пользователей в каждом словаре, в то время как он предполагает, что каждый словарь является пользователем. – ugoren

2

Вы можете использовать itertools.groupby группу в списках всех словарей, у которых имеются того же возраста и после этого просто рассчитать длину этих списков.

Например:

import itertools 

l = [{'user_id': 1, 'age': 20}, 
    {'user_id': 2, 'age': 21}, 
    {'user_id': 3, 'age': 21}, 
    {'user_id': 4, 'age': 20}, 
    {'user_id': 5, 'age': 21}, 
    {'user_id': 6, 'age': 21}, 
    ] 

def get_age(d): 
    return d.get('age') 

print [(age, len(list(group))) 
     for age, group in itertools.groupby(sorted(l, key=get_age), 
              key=get_age)] 

Пример вывода:

[(20, 2), (21, 5)] 

Примечание: Как было отмечено @Dougal, список должен быть sorted. В противном случае itertools.groupby не будет работать должным образом.

+2

Обратите внимание, что 'groupby' предполагает, что список сортируется по соответствующему атрибуту, поэтому вам может потребоваться выполнить' itertools.groupby (sorted (l, key = key_func), key = key_func) 'where' key_func = lambda d: d [ 'age'] '(или' operator.itemgetter ('age') '). – Dougal

+1

@Dougal Вы совершенно правы, я обновил свой ответ, чтобы сделать это ясно. Кроме того, я использовал функцию вместо 'operator.itemgetter' на всякий случай, чтобы избежать исключения KeyError. Спасибо за ваш комментарий. – jcollado

+0

Самое большое разочарование в группе - это то, что если это «глобальная» группа, вам нужно сначала ее отсортировать. В вашем примере, если user_id: 4 изменен на 20 лет, результат будет «[(20, 1), (21, 2), (20, 1), (21, 2)]« Вам нужно будет сортировать с тем же key, а затем group by .... get's будет подвержен ошибкам. –

Смежные вопросы