defaultdict
— это подкласс словарей (dict
, см. предыдущий пост), поэтому он наследует большую часть своего поведения от dict
с дополнительными функциями. Чтобы понять, как эти функции делают его другим и более удобным в некоторых случаях, нам нужно столкнуться с некоторыми ошибками.
Если мы пытаемся подсчитать слова в документе, общий подход заключается в создании словаря, в котором словарь keys
содержит слова, а словарь values
содержит количество этих слов.
Попробуем сделать это с помощью обычного словаря.
Во-первых, для настройки мы возьмем список слов и split()
в отдельные слова. Я взял этот абзац из другого проекта, над которым я работаю, и искусственно добавил несколько лишних слов, чтобы определенные слова появлялись более одного раза (скоро станет ясно почему).
# paragraph
lines = ["This table highlights 538's new NBA statistic, RAPTOR, in addition to the more established Wins Above Replacement (WAR). An extra column, Playoff (P/O) War, is provided to highlight stars performers in the post-season, when the stakes are higher. The table is limited to the top-100 players who have played at least 1,000 minutes minutes the table Wins NBA NBA RAPTOR more players"]
# split paragraphy into individual words lines = " ".join(lines).split()
# list type(lines)
Теперь, когда у нас есть список lines
, мы создадим пустой dict
с именем word_counts
, и пусть каждое слово будет key
, а каждое value
будет количеством этого слова.
# empty list word_counts = {}
# loop through lines to count each word for word in lines: word_counts[word] += 1
# KeyError: 'This'
Мы получили KeyError
за самое первое слово в lines
(т. е. «Это»), потому что список пытался подсчитать несуществующий ключ. Мы научились обрабатывать исключения, поэтому можем использовать try
и except
.
Здесь мы перебираем lines
, и когда мы пытаемся подсчитать несуществующий ключ, как мы делали это раньше, мы сейчас ожидаем KeyError
и установим начальный счетчик равным 1, затем он может продолжить цикл и подсчитывать слово, которое теперь существует, чтобы его можно было увеличить.
# empty list word_counts = {}
# exception handling for word in lines: try: word_counts[word] += 1 except KeyError: word_counts[word] = 1
# call word_counts # abbreviated for space word_counts
{'This': 1, 'table': 3, 'highlights': 1, "538's": 1, 'new': 1, 'NBA': 3, 'statistic,': 1, 'RAPTOR,': 1, 'in': 2, 'addition': 1, 'to': 3, 'the': 5, 'more': 2, ... 'top-100': 1, 'players': 2, 'who': 1, 'have': 1, 'played': 1, 'at': 1, 'least': 1, '1,000': 1, 'minutes': 2, 'RAPTOR': 1}
Теперь есть и другие способы добиться вышеперечисленного:
# use conditional flow word_counts = {}
for word in lines: if word in word_counts: word_counts[word] += 1 else: word_counts[word] = 1
# use get for word in lines: previous_count = word_counts.get(word, 0) word_counts[word] = previous_count + 1
Здесь автор приводит доводы в пользу defaultdict
, утверждая, что два вышеупомянутых подхода неудобны. Мы вернемся на полный круг, чтобы попробовать наш первый подход, используя defaultdict
вместо традиционного dict
.
defaultdict
является подклассом dict
и должен быть импортирован из collections
:
from collections import defaultdict
word_counts = defaultdict(int)
for word in lines: word_counts[word] += 1
# we no longer get a KeyError # abbreviated for space defaultdict(int, {'This': 1, 'table': 3, 'highlights': 1, "538's": 1, 'new': 1, 'NBA': 3, 'statistic,': 1, 'RAPTOR,': 1, 'in': 2, 'addition': 1, 'to': 3, 'the': 5, 'more': 2, ... 'top-100': 1, 'players': 2, 'who': 1, 'have': 1, 'played': 1, 'at': 1, 'least': 1, '1,000': 1, 'minutes': 2, 'RAPTOR': 1})
В отличие от обычного словаря, когда defaultdict
пытается найти ключ, которого в нем нет, он автоматически добавит для него значение, используя аргумент, который мы предоставили при первом создании defaultdict
. Как вы видите выше, мы ввели int
в качестве аргумента, что позволяет автоматически добавлять целочисленное значение.
Если вы хотите, чтобы ваш defaultdict
имел values
значение lists
, вы можете передать list
в качестве аргумента. Затем, когда вы append
значение, оно автоматически содержится в list
.
dd_list = defaultdict(list) # defaultdict(list, {})
dd_list[2].append(1) # defaultdict(list, {2: [1]})
dd_list[4].append('string') # defaultdict(list, {2: [1], 4: ['string']})
Вы также можете передать dict
в defaultdict
, убедившись, что все добавляемые значения содержатся в dict
:
dd_dict = defaultdict(dict) # defaultdict(dict, {})
# match key-with-value dd_dict['first_name'] = 'lebron' # defaultdict(dict, {'first_name': 'lebron'}) dd_dict['last_name'] = 'james'
# match key with dictionary containing another key-value pair dd_dict['team']['city'] = 'Los Angeles'
# defaultdict(dict, # {'first_name': 'lebron', # 'last_name': 'james', # 'team': {'city': 'Los Angeles'}})
Применение: группировка с помощью defaultdict
Следующий пример взят из Real Python, фантастического ресурса для всего, что связано с Python.
Обычно используется defaultdict
для группировки элементов в последовательности или коллекции, устанавливая начальный параметр (он же .default_factory
) равным list
.
dep = [('Sales', 'John Doe'), ('Sales', 'Martin Smith'), ('Accounting', 'Jane Doe'), ('Marketing', 'Elizabeth Smith'), ('Marketing', 'Adam Doe')]
from collections import defaultdict
dep_dd = defaultdict(list)
for department, employee in dep: dep_dd[department].append(employee)
dep_dd #defaultdict(list, # {'Sales': ['John Doe', 'Martin Smith'], # 'Accounting': ['Jane Doe'], # 'Marketing': ['Elizabeth Smith', 'Adam Doe']})
Что произойдет, если у вас есть повторяющиеся записи? Мы немного забегаем вперед, чтобы использовать дубликаты дескрипторов set
и группировать только уникальные записи:
# departments with duplicate entries dep = [('Sales', 'John Doe'), ('Sales', 'Martin Smith'), ('Accounting', 'Jane Doe'), ('Marketing', 'Elizabeth Smith'), ('Marketing', 'Elizabeth Smith'), ('Marketing', 'Adam Doe'), ('Marketing', 'Adam Doe'), ('Marketing', 'Adam Doe')]
# use defaultdict with set dep_dd = defaultdict(set)
# set object has no attribute 'append' # so use 'add' to achieve the same effect for department, employee in dep: dep_dd[department].add(employee)
dep_dd #defaultdict(set, # {'Sales': {'John Doe', 'Martin Smith'}, # 'Accounting': {'Jane Doe'}, # 'Marketing': {'Adam Doe', 'Elizabeth Smith'}})
Применение: накопление с помощью defaultdict
Наконец, мы будем использовать defaultdict
для накопления значений:
incomes = [('Books', 1250.00), ('Books', 1300.00), ('Books', 1420.00), ('Tutorials', 560.00), ('Tutorials', 630.00), ('Tutorials', 750.00), ('Courses', 2500.00), ('Courses', 2430.00), ('Courses', 2750.00),]
# enter float as argument dd = defaultdict(float) # collections.defaultdict
for product, income in incomes: dd[product] += income
# defaultdict(float, {'Books': 3970.0, 'Tutorials': 1940.0, 'Courses': 7680.0})
for product, income in dd.items(): print(f"Total income for {product}: ${income:,.2f}")
# Total income for Books: $3,970.00 # Total income for Tutorials: $1,940.00 # Total income for Courses: $7,680.00
Я вижу, что defaultdict
и dictionaries
могут быть удобны для группировки, подсчета и накопления значений в столбце. Мы вернемся к этим основополагающим концепциям, как только приложения науки о данных станут более ясными.
Таким образом, dictionaries
и defaultdict
можно использовать для группировки элементов, накопления элементов и подсчета элементов. Оба могут быть использованы, даже если key
(еще) не существует, но его defaultdict
обрабатывает это более лаконично. А пока остановимся на этом и перейдем к следующей теме: счетчики.
***
Чтобы узнать больше о науке о данных, машинном обучении, R, Python, SQL и многом другом, найдите меня в Twitter.