Множества в Python — set для аналитика

Коротко

Set (множество) — неупорядоченная коллекция уникальных элементов. Главное свойство — автоматическое удаление дубликатов и быстрая проверка вхождения за O(1). Для аналитика set полезен при работе с уникальными значениями: уникальные пользователи, пересечение аудиторий, разность списков. На собеседованиях множества встречаются в задачах на уникальность, сравнение коллекций и оптимизацию.

Создание множества

# Фигурные скобки
colors = {'red', 'green', 'blue'}

# Из списка — удалит дубликаты
numbers = set([1, 2, 3, 2, 1])
print(numbers)  # {1, 2, 3}

# Пустое множество — только через set()
empty = set()           # правильно
not_a_set = {}          # это dict, не set!

# Из строки — по символам
chars = set('hello')
print(chars)  # {'h', 'e', 'l', 'o'}

# Генератор множества (set comprehension)
squares = {x**2 for x in range(10)}
print(squares)  # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}

Важно: {} создаёт пустой dict, а не set. Для пустого множества — set().

Уникальные значения из списка

Самое частое применение в аналитике — быстрое получение уникальных значений:

user_ids = [101, 202, 101, 303, 202, 404, 101]
unique_users = set(user_ids)
print(unique_users)       # {101, 202, 303, 404}
print(len(unique_users))  # 4

# Количество уникальных значений в одну строку
n_unique = len(set(user_ids))

В pandas аналог — df['col'].nunique() или df['col'].unique(), но для чистого Python set — самый быстрый способ.

Добавление и удаление элементов

s = {1, 2, 3}

# Добавить элемент
s.add(4)         # {1, 2, 3, 4}
s.add(2)         # {1, 2, 3, 4} — дубликат игнорируется

# Удалить элемент
s.remove(3)      # {1, 2, 4} — KeyError если нет
s.discard(99)    # ничего не произойдёт, без ошибки
s.pop()          # удалит произвольный элемент

# Очистить
s.clear()        # set()

remove() выбросит KeyError, если элемента нет. discard() — безопасная альтернатива, удалит если есть, ничего не сделает если нет.

Операции над множествами

Множества поддерживают математические операции — объединение, пересечение, разность, симметрическая разность.

a = {1, 2, 3, 4, 5}
b = {4, 5, 6, 7, 8}

# Объединение — все элементы из обоих
a | b              # {1, 2, 3, 4, 5, 6, 7, 8}
a.union(b)         # то же самое

# Пересечение — общие элементы
a & b              # {4, 5}
a.intersection(b)  # то же самое

# Разность — в a, но не в b
a - b              # {1, 2, 3}
a.difference(b)    # то же самое

# Симметрическая разность — в a или в b, но не в обоих
a ^ b              # {1, 2, 3, 6, 7, 8}
a.symmetric_difference(b)
Операция Оператор Метод Результат
Объединение a | b a.union(b) Всё из a и b
Пересечение a & b a.intersection(b) Общие элементы
Разность a - b a.difference(b) В a, но не в b
Симм. разность a ^ b a.symmetric_difference(b) Уникальные для каждого

Проверка вхождения и подмножества

s = {1, 2, 3, 4, 5}

# Проверка вхождения — O(1)
3 in s       # True
99 in s      # False
99 not in s  # True

# Подмножество
{1, 2}.issubset(s)         # True — {1,2} ⊂ s
{1, 2} <= s                # True

# Надмножество
s.issuperset({1, 2})       # True — s ⊃ {1,2}
s >= {1, 2}                # True

# Непересекающиеся
{1, 2}.isdisjoint({3, 4})  # True — нет общих
{1, 2}.isdisjoint({2, 3})  # False — есть общий: 2

Проверка x in set работает за O(1) благодаря хеш-таблице. Для списка проверка x in list — O(n). Если нужно много проверок вхождения, конвертируйте список в set.

Аналитические примеры

Пересечение аудиторий

campaign_a_users = {101, 202, 303, 404, 505}
campaign_b_users = {303, 404, 606, 707}

# Кто видел обе кампании
overlap = campaign_a_users & campaign_b_users
print(overlap)  # {303, 404}

# Кто видел только кампанию A
only_a = campaign_a_users - campaign_b_users
print(only_a)   # {101, 202, 505}

# Общий охват
total_reach = campaign_a_users | campaign_b_users
print(len(total_reach))  # 7

Новые и ушедшие пользователи

users_march = {1, 2, 3, 4, 5}
users_april = {3, 4, 5, 6, 7}

new_users = users_april - users_march       # {6, 7}
churned_users = users_march - users_april    # {1, 2}
retained_users = users_march & users_april   # {3, 4, 5}

retention_rate = len(retained_users) / len(users_march)
print(f'Retention: {retention_rate:.0%}')    # Retention: 60%

Быстрая фильтрация по списку ID

all_orders = [
    {'user_id': 1, 'amount': 500},
    {'user_id': 2, 'amount': 300},
    {'user_id': 3, 'amount': 800},
    {'user_id': 4, 'amount': 150},
]

premium_ids = {1, 3}  # set для быстрой проверки

premium_orders = [
    o for o in all_orders if o['user_id'] in premium_ids
]

Проверка in premium_ids — O(1). Если бы premium_ids был списком — O(n) на каждую проверку.

Frozenset — неизменяемое множество

fs = frozenset([1, 2, 3])
# fs.add(4)  # AttributeError — нельзя изменить

# Frozenset можно использовать как ключ dict или элемент другого set
d = {frozenset({1, 2}): 'group_a'}

Frozenset — аналог tuple для множеств. Хешируемый, можно использовать как ключ словаря или элемент другого множества.

Set vs list — когда что

Критерий set list
Порядок Не гарантирован Сохраняется
Дубликаты Нет Да
Проверка in O(1) O(n)
Индексация [i] Нельзя Можно
Изменяемый Да Да

Используйте set, когда: нужны уникальные значения, частые проверки вхождения, операции над множествами. Используйте list, когда: важен порядок, нужна индексация, допускаются дубликаты.

Типичные ошибки

Пустое множество через {}. {} — это dict, не set. Для пустого множества: set().

Мутабельные элементы. Set хранит только хешируемые (неизменяемые) элементы. Списки и словари нельзя добавить в set — будет TypeError. Используйте tuple вместо list, frozenset вместо set.

s = set()
# s.add([1, 2])     # TypeError: unhashable type: 'list'
s.add((1, 2))       # OK — tuple хешируемый

Порядок не гарантирован. {3, 1, 2} может вывестись как {1, 2, 3}, но рассчитывать на порядок нельзя. Если нужен отсортированный уникальный список: sorted(set(data)).

Вопросы с собеседований

-- Чем set отличается от list? -- Set — неупорядоченная коллекция уникальных элементов. Проверка вхождения O(1) вместо O(n). Нет индексации, нет дубликатов, порядок не гарантирован.

-- Как быстро убрать дубликаты из списка? -- list(set(data)) — но порядок потеряется. Если нужен порядок: list(dict.fromkeys(data)) — в Python 3.7+ dict сохраняет порядок вставки.

-- Что можно хранить в set? -- Только хешируемые (immutable) объекты: числа, строки, tuple, frozenset, bool, None. Нельзя: list, dict, set.

-- Как найти общие элементы двух списков за O(n)? -- Преобразовать оба в set и взять пересечение: set(a) & set(b). Без set — вложенный цикл O(n²).

-- Что такое frozenset? -- Неизменяемый аналог set. Можно использовать как ключ словаря или элемент другого множества, потому что frozenset хешируемый.


Потренируйтесь решать задачи — откройте тренажёр с 1500+ вопросами для подготовки к собеседованиям аналитиков.

FAQ

Когда set быстрее list?

При проверке вхождения (x in collection) — всегда. Set использует хеш-таблицу: проверка за O(1). List ищет перебором: O(n). Если проверяете вхождение в цикле — конвертируйте list в set заранее.

Можно ли сортировать set?

Напрямую нельзя — у set нет метода sort(). Но sorted(my_set) вернёт отсортированный список. Если нужна и уникальность, и порядок — используйте dict.fromkeys() или sorted(set(data)).

Set в pandas — зачем?

В pandas множества полезны для фильтрации: df[df['user_id'].isin(set_of_ids)]. Метод isin() внутри использует set для быстрой проверки. Для подсчёта уникальных значений — nunique() и unique().

Как тренироваться

Set — базовая структура данных Python, которая часто появляется на собеседованиях аналитиков. В тренажёре Карьерник есть вопросы по структурам данных Python. Больше вопросов — в разделе с примерами.