Аналитика, машинное обучение и наука о данных для начинающих / Analytics, machine learning, and data science for beginners. Просто о сложных технологиях
🐍 Python для аналитики | 👨💻 SQL | 📊 Визуализация | ➕ Математика | 🦾 Машинное обучение (machine learning, ML) | Наука о данных (Data Science)
Канал носит исключительно информационный и ознакомительный характер. Автор делится личным опытом и субъективным мнением о программировании. Материалы не являются официальной образовательной программой
ПодписчикиСколько подписчиков у канала сейчас. Волна — число подписчиков в каждой точке периода. Изменение — к началу периода.252▲ 6,8 %+16 за период
ПросмотрыСколько сейчас просмотров у постов, вышедших за период. Это не просмотры за день: сколько посмотрели вчера, MAX не сообщает. Процент — изменение в среднем за день: нынешний период ещё идёт (сегодня — неполный день), поэтому его сравниваем с прошлым по темпу, а не по сумме. «Было» — прошлый такой же период целиком.52▼ 71,5 %было 195
РеакцииСколько реакций у постов, вышедших за период. Процент — изменение в среднем за день: нынешний период ещё идёт (сегодня — неполный день), поэтому его сравниваем с прошлым по темпу, а не по сумме. «Было» — прошлый такой же период целиком.1▼ 78,6 %было 5
УпоминанияСколько раз канал упомянули другие каналы: ссылки и репосты, без рекламных постов. Чем крупнее круг, тем больше упоминаний в этой точке периода. Процент — изменение в среднем за день: нынешний период ещё идёт (сегодня — неполный день), поэтому его сравниваем с прошлым по темпу, а не по сумме. «Было» — прошлый такой же период целиком.61▼ 18,5 %было 76по подписке
РепостыСколько раз другие каналы сделали репост постов этого канала (без рекламных постов). Репосты входят и в число упоминаний. Процент — изменение в среднем за день: нынешний период ещё идёт (сегодня — неполный день), поэтому его сравниваем с прошлым по темпу, а не по сумме. «Было» — прошлый такой же период целиком.28▲ 13,7 %было 25после входа
Средний охват постаСколько просмотров в среднем набирает один пост. Считаем по постам старше двух суток: у свежих просмотры ещё растут. Точки, где есть свежие посты, — пунктиром и полыми кружками: охват всех постов на сейчас, он ещё вырастет.52▼ 73,3 %было 195
ERRКакая доля подписчиков видит средний пост: средний охват ÷ подписчики × 100 %. Линия — ERR в каждой точке периода, пунктир — ERR прошлого такого же периода. Точки с постами моложе двух суток — полые кружки на пунктирном конце линии.20,6 %▼ 62,0 п. п.пунктир — было 82,6 %
Вовлечённость ERКак часто читатели реагируют на посты: реакции ÷ просмотры × 100 %. Засечка на полосе — прошлый период.1,92 %▼ 0,6 п. п.
было 2,56 %
Постов в деньСколько публикаций в среднем выходит за день. Клетка — точка периода: чем темнее, тем больше постов в день. Процент — изменение в среднем за день: нынешний период ещё идёт (сегодня — неполный день), поэтому его сравниваем с прошлым по темпу, а не по сумме. «Было» — прошлый такой же период целиком.0,1▲ 7,0 %было 0,1
Доля рекламыКакая доля публикаций за период похожа на рекламу: маркировка erid, рекламные ссылки и другие признаки. Шкала — рекламная нагрузка по порогам индекса качества: до 10 % — низкая, до 25 % — умеренная, до 50 % — высокая, дальше — очень высокая. Рост доли отмечается красным.5,7 %▲ 3,5 п. п.
ДинамикаСверху — число подписчиков на конец каждой точки периода, снизу — прирост за точку: зелёный — подписчиков стало больше, красный — меньше. Над графиком прироста — самый большой прирост и самая большая убыль. Сколько подписалось и отписалось по отдельности, MAX не сообщает.
Рост аудитории
+16подписчиков за период
+5 самый большой прирост · 25 окт 2026
Оценка каналаОценка канала от 0 до 100 из пяти частей: аудитория, честность роста, рекламная нагрузка, доверие и достоверность расчёта. Полоса — сколько баллов часть набрала из своего максимума. Индекс пересчитывается каждую ночь; каналы сравниваются с похожими — той же категории и размера.
Индекс качества пока не рассчитан. Он появляется у каналов от 500 подписчиков, которые публикуют посты хотя бы раз в неделю, после недели наблюдения.
ОхватыСколько сейчас просмотров у постов, вышедших в каждой точке периода (а не просмотры за день). Подписан рекорд; точка с сегодняшним днём бледнее — день ещё идёт. Средний охват поста — по постам старше двух суток.
Просмотры и охват
52просмотров за период▼ 71,5 %
Средний охват поста — 52 просмотров (посты старше двух суток).
ВовлечённостьER — реакции, делённые на просмотры, в процентах — у постов каждой точки периода. Пунктир — ER за весь период. Изменение — в процентных пунктах к прошлому такому же периоду.
Вовлечённость подписчиков
1,92 %ER = реакции ÷ просмотры▼ 0,6 п. п.
ER каналав среднем за период
ЦитируемостьИндекс цитируемости (ИЦ) — насколько часто и какими каналами упоминают этот канал за последние 30 дней, без рекламных постов. Уровень — место среди похожих каналов. Линия — ИЦ в каждой точке периода (в неделе и месяце — средний за её дни).
Индекс цитируемости пока не рассчитан: он появится после ночного расчёта, когда канал упомянут другие каналы.
РитмСколько постов в среднем выходит в каждый час каждого дня недели, время московское: чем темнее клетка, тем больше публикаций. «Лучшее время» — часы, в которые у постов старше двух суток самый высокий средний охват.
Когда и сколько публикует
ПнВтСрЧтПтСбВс
036912151821
Лучшее время:09:0018:00часы, в которые у постов самый высокий средний охват
ФорматыИз чего состоит лента. У каждого поста один формат — по старшинству: репост, видео, фото, ссылка, текст. Полоса и проценты — доля постов формата; справа — средний охват одного поста формата (по постам старше двух суток), полоска — сравнение с самым просматриваемым форматом.
ПубликацииКаждая точка — пост за период: по горизонтали — дата выхода, по вертикали — просмотры, размер точки — реакции, цвет — формат. Так видно хиты, провалы и то, как меняется охват. У крупных каналов показаны 100 самых просматриваемых постов и равномерная выборка остальных — не больше 1 500 точек.
Каждый пост — точка
Выше — больше просмотров, крупнее — больше реакций. Нажмите на точку, чтобы открыть пост.
ВидеоФотоДругоеразмер — реакцииПриближение — кнопками «+» и «−», щипком или Ctrl + колесо мыши; приближенный график сдвигается перетаскиванием
СводкаИтоги выбранного периода: сколько вышло публикаций, сколько у них сейчас просмотров и реакций и средний охват поста. Просмотры — текущие у постов, вышедших в эти дни, а не просмотры за день. Изменение — к такому же отрезку перед периодом, если за него есть статистика.
Итоги периода
Нажмите на показатель — ниже он появится по точкам периода
Просмотрысегодня — день ещё идётТочки с постами моложе двух суток — бледные с пунктиром: их просмотры ещё растут.
РекламаКакая доля публикаций за период похожа на рекламу: маркировка erid, рекламные ссылки и другие признаки. Детектор осторожный, поэтому настоящая доля обычно выше. Шкала — рекламная нагрузка по порогам индекса качества. Ниже — средний охват рекламного и обычного поста и кого рекламирует канал (сайты, на которые ведут ссылки из рекламы).
Что работаетПосты периода с наибольшим числом просмотров. Полоса — просмотры относительно первого места. Превью — картинка поста, у поста без картинки — аватар канала. Просмотры — сколько у поста сейчас.
РитмСколько постов вышло в каждый день периода (бледные столбики) и среднее за 7 дней (линия): видно, разгоняется канал или затихает. Сегодняшний день ещё идёт, поэтому среднее для него считается по вчерашний день.
Публикации по дням
Бледные столбики — дни, линия — среднее за 7 дней: видно, разгоняется канал или затихает
Публикации за деньСреднее за 7 дней
Данные обновлены в 13:04 по Москве. Просмотры — текущие у постов, вышедших в эти дни; средний охват — по постам старше двух суток.
Как считается индекс качества
- по одному признаку → groupby - перекрёстно (2+ признака) → pivot_table - несколько метрик в одной сводной → pivot_table(values=[...])
🔧 Синтаксис
pd.pivot_table(df, index='строки', columns='столбцы', # опционально values='данные', aggfunc='mean', # по умолчанию mean fill_value=0, # замена всех NaN margins=True, # итоги sort=True)
- Числовой столбец None → NaN (единый формат для вычислений). - Строковый столбец None остаётся None (NaN не существует для строк). - Смешанный → None (без чисел) / NaN (с числами). - Тип столбца определяется автоматически. - fill_value=0 заменяет любые NaN в итоговой таблице (включая mean, median). - Для гарантии замены в pandas < 1.3.0 (особенно при нескольких агрегациях) можно явно дописать .fillna(0):
result = pd.pivot_table(...).fillna(0)
🆚 pivot_table vs groupby
groupby: - не строит матрицу ❌ - длинная таблица - только вниз - возвращает Series или DF
pivot_table: - строит матрицу ✅ - широкая таблица - вниз и вправо - возвращает всегда DF
✅ Чек-лист
- groupby('col') — по одному столбцу - groupby(['col1','col2']) — по двум - .groupby(...)['col'].sum() → Series - .groupby(...)[['col']].sum() → DF - pivot_table() — перекрёстная группировка - groupby(..., dropna=False) — не исключать пропуски
💬 Далее: Объединение таблиц — merge, concat, join.
Pandas, как и SQL, реализует парадигму Разделение‑Применение‑Объединение / Split‑Apply‑Combine: - разбиваем данные на группы - применяем к каждой группе функцию (медиану и т.д.) - собираем результаты в новый DataFrame
Это логика мышления, а не жёсткий порядок операций.
🆚️ Агрегации в SQL и Pandas
SQL: - Быстрая агрегация внутри базы без перелива данных - GROUP BY + COUNT, SUM, AVG, MIN, MAX, STRING_AGG, ARRAY_AGG - Оконные функции (ROW_NUMBER, LAG, SUM OVER) — агрегация с сохранением строк
df — объект DataFrame .groupby() — метод группировки ['cost'] — выбор данных .sum() — метод агрегации
🐍 Типы скобок в Python
( ) — вызов функции/метода [ ] — доступ к элементу
👥 Группировка
groupby() только группирует данные, не считает их. Результат появляется при добавлении агрегации: sum(), mean() и т.д.
df.groupby('ch')[['cost', 'revenue']].sum()
Результат — DataFrame: потеряли 40k выручки 🤯
cost revenue ch ok 500.0 0.0 tg 0.0 30000.0 vk 5000.0 10000.0 ya 5000.0 30000.0
Почему? `0.0`, а не `0`: если есть None → Pandas автоматически приводит всю колонку к типу float64
нет строки с NaN: по умолчанию строки с None в ключе группировки исключаются из результата groupby(dropna=True) dropna=False — чтобы вернуть, см. ниже.
0️⃣ NaN или 0?
Зависит от: 1) метода агрегации 2) объекта агрегации
1️⃣ Методы sum и count возвращают число, все остальные — NaN:
• .sum() → пустая сумма = 0 • .mean() → нечего усреднять = NaN • .count() → ноль штук = 0 • .min() → не из чего выбирать = NaN • .max() → NaN • .std() → NaN
2️⃣ NaN в ключе группировки — другие правила
- NaN в значениях (revenue, cost) → правила агрегации (см. таб. выше) - NaN в ключе группировки (ch) → зависит от dropna: `dropna=True` (по умолчанию): → строки с None в ключе исключаются `dropna=False`: → None становится отдельной группой, внутри — те же правила агрегации
В Pandas большинство методов по умолчанию не изменяют исходный DataFrame, а возвращают новый изменённый объект.
Если нужно изменить исходный, используется параметр inplace=True.
☝️ inplace=True не используется в цепочках методов (method chaining), потому что он возвращает None, а не DataFrame, и цепочка обрывается. Поэтому в стиле df.method1().method2() всегда работают методы без inplace, а результат присваивается переменной или передаётся дальше.
# ❌ Так не работает – inplace вернёт None df.fillna(0, inplace=True).dropna() # Ошибка!
🍩 Что делать с пропусками?
.dropna() и .fillna() — два основных метода для работы с пропущенными значениями (NaN, None, NaT).
Для замены других некорректных значений (например, 0 или пустая строка) используйте .replace() (см.ниже).
Выбор зависит от количества пропусков: • Удалить (.dropna()) — когда данные с пропусками не нужны для анализа (теряем объём, но избавляемся от проблем) • Заполнить (.fillna()) — когда пропусков мало и разумная замена (среднее, медиана, 0 и т.п.) не сильно исказит данные
df.dropna() # удалить строки с любым NaN df.dropna(how='all') # удалить строки, где все значения NaN
df.fillna(0) # заполнить константой df.fillna(df['cost'].mean()) # заполнить средним по столбцу df.fillna(df.median()) # каждый столбец заполняет свои пропуски собственной медианой этого же столбца
🔄 Преобразование типов данных
Изменить тип столбца можно с помощью .astype():
df['price'] = df['price'].astype('float32') df['date'] = pd.to_datetime(df['date']) # работа с датами
Перезапись происходит через прямое переприсваивание:
# Повышаем цены на 10% df['price'] = df['price'] * 1.1 # без методов .update() или .replace()
📌 Замена значений: .apply(), .map(), .replace()
Метод .apply() — для функции любой сложности (своей, lambda, встроенной) на Series и DataFrame
Лямбда — компактная функция в одну строку (одно выражение). Для простых действий это удобнее, чем создавать отдельную функцию: lambda x: x * 1.1 # возьми x, верни x * 1.1
# лямбда-функция: пересчёт цены в доллары df['price_usd'] = df['price_eur'].apply(lambda x: x * 1.1)
# своя функция (когда логика сложнее) def discount(price): return price * 0.9 if price > 1000 else price
df['price_disc'] = df['price'].apply(discount)
# применение к строке (axis=1) — для больших данных может быть медленнее векторизованных операций df['full_info'] = df.apply(lambda row: f"{row['name']} - {row['price']}", axis=1)
Метод .map() — простое преобразование элементов или замена значений по словарю (только Series)
# Замена кодов на названия городов df['city'] = df['city_code'].map({1: 'Москва', 2: 'СПб', 3: 'Казань'}) # Если кода нет в словаре → NaN
Метод .replace() — глобальная замена конкретных значений (Series и DataFrame)
# Замена 'Msk' на 'Москва', 'Spb' на 'СПб' (во всём DataFrame) df['city'] = df['city'].replace({'Msk': 'Москва', 'Spb': 'СПб'})
Короткое правило: • одна колонка → .map() • весь DataFrame → .replace()
Обязательная база: • by (столбец / список столбцов) — порядок задаёт приоритет • ascending (направление) - True — по возрастанию - False — по убыванию
# ❌ краткая форма (для нескольких столбцов без by= — ошибка) df.sort_values('cost', ascending=False)
# ✅ явная форма — рекомендуется df.sort_values(by='cost', ascending=False)
Опционально: • inplace — изменить исходный DataFrame (True) или вернуть копию (False, по умолч.) • ignore_index — сбросить индекс и создать новый последовательный (0…n-1) • key — векторизованная функция, применяемая к значениям перед сортировкой • na_position — куда помещать NaN, None: 'first' (в начало) или 'last' (в конец, по умолч.)
❗️Частая ловушка: inplace и присваивание
`inplace=True` возвращает `None`, не пытайся поймать его в переменную.
df_sorted = df.sort_index(ascending=False) • Полезно, если индекс — временная метка (быстрый просмотр последних записей) • Сортировка колонок по именам: df.sort_index(axis=1)
🔥 Быстрая сортировка топ‑N
• nlargest() — N наибольших значений (от большего) • nsmallest() — N наименьших значений (от меньшего)
На больших данных работает быстрее, чем sort_values().head(n): - sort_values() сортирует все строки - nlargest() использует алгоритм — O(n log N), где N — малое число
• Категории — для своего порядка сортировки (не лексикографического): - преобразовать столбец к типу CategoricalDtype с ordered=True - указать порядок categories (например: 'low' < 'medium' < 'high') - далее обычный sort_values()
from pandas.api.types import CategoricalDtype df['priority'] = df['priority'].astype( CategoricalDtype(categories=['low', 'medium', 'high'], ordered=True) ) df.sort_values('priority') # low → medium → high
✅ После сортировки: reset_index()
Если не использовать ignore_index=True, после сортировки старый индекс «едет». Чтобы сохранить данные (например, ID пользователя) — применить reset_index():
df_sorted = df.sort_values('cost').reset_index() # старый индекс стал колонкой index, добавили новый индекс
⚠️ ignore_index=True отбрасывает старый индекс, восстановить его нельзя. Не смешивайте оба подхода.
👩🎓 Выбор алгоритма сортировки: kind
• 'quicksort' (по умолчанию) — быстрый, но нестабильный (равные элементы могут поменять порядок) • 'mergesort' / 'stable' — стабильный (сохраняет исходный порядок равных ключей) • 'heapsort' — нестабильный
🌪 Фильтрация
Логика: оставляем строки, где условие истинно.
Базовое условие
df[df['cost'] > 1000] # сравнение со скаляром
Сложное условие (И / ИЛИ)
- & — оба условия истинны (И) - | — хотя бы одно истинно (ИЛИ) - Каждое условие обязательно в своих круглых скобках – иначе Pandas не поймёт порядок операций
df[(df['cost'] > 1000) & (df['channel'] != 'ok')] # И df[(df['channel'] == 'ya') | (df['channel'] == 'vk')] # ИЛИ
Отрицание условия — тильда ~
df[~(df['cost'] > 1000)] # строки, где cost <= 1000 df[~df['channel'].isin(['ya', 'vk'])] # все каналы, кроме ya и vk
isin() — фильтр по списку значений
Заменяет цепочку условий == через | (работает как «ИЛИ»). Короткая, удобная запись, особенно когда элементов много.
df[df['channel'].isin(['ya', 'vk'])] # оставить ya или vk
query() — альтернатива для читаемых сложных условий
df.query('cost > 1000 and channel != "ok"') df.query('channel in ["ya", "vk"]')
📏 Pandas: меняем размер таблицы — добавляем и удаляем данные
Сегодня разбираем, как менять размер таблицы: добавлять и удалять строки и столбцы.
🤝 Фиксированная длина
Series и DataFrame в Pandas имеют фиксированную длину. Нельзя просто взять и добавить новую строку к существующему объекту — нужно создавать новый объект, объединяя старый с новыми данными.
Почему? Pandas внутри использует неизменяемые массивы NumPy — менять размер без копирования нельзя. Это жертва ради скорости и памяти.
➕ Добавление строк
Чтобы добавить одну или несколько строк, используют функцию pd.concat(). Старый метод .append() устарел.
- ignore_index=True переиндексирует результат (0,1,2…), иначе индекс останется старым.
➕ Добавление столбцов
Добавить новый столбец проще — он не меняет длину таблицы. Просто добавляем столбец с новым именем.
Новый столбец всегда будет той же длины, что и DataFrame. ⚠️ Длина списка должна совпадать с числом строк (иначе ошибка).
На практике такая задача бывает редко, чаще создаём столбцы с константой, векторизованными вычислениями и сложной функцией .apply() — Pandas автоматически выдаст результат той же длины.
# + столбец с константой df['порог_миллионника'] = 1.0
# + столбец, вычисленный из существующих df['миллионник'] = df['нас.млн'] >= 1.0 # True для городов-миллионников (Москва, СПб, Нск), False для Саратова
⚠️ При добавлении столбца Pandas автоматически выравнивает данные по индексу, если присваивается Series.
❌ Три способа удалить данные и один — отфильтровать
1. Метод drop — гибкое удаление
- имеет параметр axis: удаляет строки (axis=0) или столбцы (axis=1) - по умолчанию возвращает новый DataFrame (новую копию), не меняя исходный - чтобы изменить исходный DataFrame → используйте inplace=True - чтобы сохранить новый DataFrame → присвойте результат переменной
⚠️ inplace=True изменяет исходный объект и возвращает None 🤯
# без присваивания — результат пропадёт df.drop('город', axis=1) # ⚠️ вернул новый DF, но никуда не записали → потеряли
# с присваиванием — когда исходные данные ещё пригодятся df2 = df.drop('город', axis=1) # исходный df остаётся нетронутым
# перезаписываем ту же переменную — когда исходный больше не нужен df = df.drop('город', axis=1)
2. Метод pop — вырезать столбец и вернуть его
- меняет исходный DataFrame сразу (без inplace) - возвращает удалённый столбец как Series (не DataFrame!) - удобен для отделения целевой переменной (y) от признаков (X)
y = df.pop('target') # target удаляется из df и сохраняется в y X = df # то, что осталось
После загрузки данных в DataFrame важно убедиться, что файл отображается правильно (не съехали столбцы, правильный разделитель, верная кодировка). Стандартное превью — достаточно неудобное и затратное по ресурсам. Здесь опишу базовые практики.
☄️ Методы для быстрого анализа
df.head() # первые 5 строк df.tail(3) # последние 3 строки df.shape # размер (строки, столбцы) df.info() # типы столбцов, количество не‑NaN, память df.columns # имена колонок (фичей)
🚩 Заголовки / header
Важно правильно указать, где находятся заголовки. Говорим системе:
- header=0 (по умолчанию) — первая строка становится именами колонок - header=None — в файле нет строки-заголовка (колонки получат имена 0,1,2...) - header=1 — заголовки лежат во второй строке, первая — служебная/мусорная - names=['a','b','c'] — передаём явно имена колонок - skiprows=[0,2,5] — пропустить строки с номерами 0,2,5 (мусорные) - skipfooter=2 — пропустить последние 2 строки (требует engine='python', потому что стандартный C-движок не умеет читать файл с конца):
NaN — «дырка» в данных, если не проверить: - базовые агрегаты выдадут NaN - модели машинного обучения выдадут ошибку - графики могут исказиться
df.isna().sum() # сколько NaN в каждом столбце (часто используют вместе/вместо с info) df.isna().sum().sum() # общее число NaN во всей таблице
🧷 Типы данных
Pandas различает только три основных типа: - Числовые (int64, float64) - Булевы (bool) - Объекты (object) — строки, смешанные типы, даты*, списки и т.д.
*Для работы с датами лучше использовать datetime64, но при первичном осмотре они тоже попадут в object, если не преобразовать.
Типы столбцов можно задать сразу при чтении (через `dtype`) или поменять после (через `astype()`): • dtype экономит память и время при загрузке больших файлов • astype даёт гибкость при постобработке
# Задать типы столбцов сразу при чтении — ускоряет чтение df = pd.read_csv('file.csv', dtype={'id': int, 'price': float})
# Посмотреть типы всех столбцов df.dtypes
# Поменять тип после загрузки (пример) df = pd.read_csv('file.csv') df['price'] = df['price'].astype(float)
📦 Доступ к столбцу
Предпочтительно через квадратные скобки.
df['cost'] # ✅ работает всегда df.cost # ⚠️ не работает с пробелами, спецсимволами, может совпасть с методом DataFrame
📊 Описательные статистики / descriptive statistics
Один из ключевых методов разведочного анализа данных (EDA).
1. Функция для числовых данных — describe():
- count – сколько непустых значений - mean – среднее арифметическое - std – стандартное отклонение (разброс данных) - min – минимум - 25%, 50%, 75% – квантили (границы, ниже которых лежат 25%, 50%, 75% данных) - max – максимум
df.describe()
2. Для текстовых (категориальных) столбцов — include=['object']:
df.describe(include=['object'])
Покажет: количество уникальных значений, самую частую категорию (top), её частоту (freq).
9️⃣ Подсчёт частот значений / frequency counts
Применяется к одному столбцу (Series), считает, сколько раз встречается каждое значение.
df['channel'].value_counts() # абсолютные значения # ya 99 # vk 1
`normalize=True` — доли (относительные частоты), помогает выявить дисбаланс данных:
df['channel'].value_counts(normalize=True) # доли # ya 0.99 # vk 0.01
Если одна категория встречается гораздо чаще других, модель будет плохо предсказывать редкие классы.
💬 Далее: Pandas. Меняем размер таблицы — добавляем и удаляем данные
pd.read_csv('data.csv') ↑ ↑ ↑ │ │ путь к файлу │ функция чтения (возвращает DataFrame) библиотека pandas # ( ) — вызов функции
📖 Распространённые форматы
📄 CSV (популярный формат) pd.read_csv() / df.to_csv() Универсальный, человекочитаемый, но медленный и большой
📊 Excel pd.read_excel() / df.to_excel() Много листов; для обмена с нетехническими специалистами
🌐 JSON pd.read_json() / df.to_json() API, логи
🥒 Pickle (только для временных файлов на своей машине) pd.read_pickle() / df.to_pickle() Бинарный, быстрый, сохраняет типы, но небезопасен (может выполнить код). Не открывать из непроверенных источников!
📦 Parquet (лучший выбор для больших данных) pd.read_parquet() / df.to_parquet() Современный столбцовый формат, сжатие до 10x, быстрый, безопасный; лучший для больших данных
🧑🎓 Пример CSV
CSV — всегда один «лист» (плоская таблица). Не поддерживает множественные листы.
⚠️ Проблемы: - sep — разделитель (, или ;), иначе всё склеится в один столбец - encoding — для кириллицы 'cp1251' или 'utf-8', иначе кракозябры - header=None — если в файле нет заголовка (столбцы назовутся по умолчанию: 0,1,2…) - index_col — какой столбец сделать индексом при открытии (обычно не трогаем)
➕ Полезно: - index=False – не создает лишний столбец с номерами строк при сохранении файла - encoding='utf-8-sig' – для совместимости с Excel
📊 Пример Excel (много листов / worksheets)
При загрузке указываем sheet_name (имя или номер). По умолчанию загрузится только первый лист. Загрузить все листы — sheet_name=None (получится словарь DataFrame'ов)
# Загрузить все листы в словарь all_sheets = pd.read_excel('file.xlsx', sheet_name=None)
# Сохранить df.to_excel('out.xlsx', index=False)
📥 Другие форматы (кратко)
# JSON df = pd.read_json('file.json') df.to_json('out.json', orient='records') # orient='records' — превращает таблицу в JSON-список, где каждый элемент — словарь: ключи — названия столбцов, значения — данные строки
# скопировать таблицу в буфер (Ctrl+C), затем выполнить: df = pd.read_clipboard() # Pandas сам считает данные из буфера и создаст DataFrame
⚡️ Чтение больших файлов кусками / чанками
Если файл > 1 ГБ и не влезает в память — его читают по кускам.
chunks = pd.read_csv('big_file.csv', chunksize=10000) for chunk in chunks: # цикл, который перебирает все куски (chunks) один за другим print(len(chunk)) # обрабатываем кусок
- chunksize=N – возвращает итератор, выдающий следующий кусок - Каждый кусок – обычный DataFrame, после обработки удаляется из памяти
Пример: накопить сумму без загрузки всех данных
total = 0 for chunk in pd.read_csv('big.csv', chunksize=100000): total += chunk['sales'].sum() print(total)
❌ Что нельзя с чанками?
Операции вроде merge или groupby без агрегации требуют всех данных сразу. Для них нужны Dask / Spark.
✅ Чек-лист
• Загрузить через pd.read_*() • Сохранить через df.to_*() с index=False • Чтение из буфера pd.read_clipboard() • Parquet — лучший формат • Большой файл читать чанками (chunksize)
Pandas и SQL сравнивают, т.к. они решают одну задачу — агрегацию данных. Хотя сам вопрос некорректный и возникает из-за непонимания экосистем и ролей:
• SQL — пром-агрегации (быстро, масштабируемо, вычисления прямо на стороне сервера БД (In-Database Processing)) • Pandas — исследования (удобно, гибко, но память ограничена (необходимо скачать данные на клиентскую машину))
🏡 Где живут данные
SQL: в БД (PostgreSQL, ClickHouse, etc.) Pandas: в оперативной памяти (на ноутбуке / сервере)
🥇 Кто что умеет лучше
SQL: считать колоссальные объемы Pandas: гибко трансформировать, писать свои функции
SQL: применить сложную python-функцию к каждой группе Pandas: обработать 100 млрд строк
🤔 Почему? - SQL не умеет выполнять произвольный python-код внутри GROUP BY - Pandas держит все данные в RAM
👔 Что делать?
SQL: выгрузить данные в Pandas → применить функцию → вернуть обратно Pandas: использовать SQL или изучить Spark
🧩 Что в голове у новичка?
• Каша из инструментов — зачем мне два похожих инструмента • Непонимание ролей — где заканчивается аналитика и начинается инженерия данных
Ниже — краткая карта.
⚔️ Pandas против SQL: не «или», а «когда»
Запрос к базе данных SQL: ✅ основной инструмент Pandas: ⚠️ можно, но нет pushdown (фильтрация в Python)
Работа с таблицами 5–50 млн строк SQL: ✅ да Pandas: ⚠️ тяжело (зависит от памяти и числа колонок)
Разведочный анализ (EDA) SQL: ❌ многословно Pandas: ✅ быстро, гибко
Машинное обучение (ML) SQL: ❌ нет (ограничен реляционной алгеброй (работа с таблицами); не приспособлен для математики ML) Pandas: ✅ да (единая экосистема для математики ML: PyTorch/TensorFlow (градиенты и тензоры), NumPy/Scikit-Learn (матрицы и линейная алгебра)
Продуктовый аналитик SQL: ✅ Pandas: ⚠️ Почему: SQL — 80%, Pandas — сложные преобразования или join не в базе
ML-инженер SQL: ⚠️ Pandas: ✅ Почему: SQL — выгрузка признаков (фичей), Pandas — конвейер (pipeline), но чаще Spark/Polars
🎯 Главный вывод
Оба инструмента делают одно и то же, но по-разному и в разных условиях. Аналитик и Data Scientist должны знать оба инструмента. Но на практике: - если данные в базе и нужна скорость → SQL - если данные в CSV/Excel или нужно ML → Pandas
В ML выбор инструмента диктуют: - объём данных - нужна ли кастомная функция на группе - где лежат данные (в SQL базе или в csv/parquet)
🐼 Первые шаги в Pandas: порядок действий
1. Загрузка данных (pd.read_csv, pd.read_excel) 2. Первичный осмотр (head, info, describe, isna) 3. Сортировка и фильтрация (sort_values, []) 4. Чистка данных (astype, rename, dropna, fillna, str-методы) 5. Агрегация и группировки (groupby, agg, transform) 6. Сводные таблицы (pivot_table, crosstab) 7. Объединение таблиц (merge, concat, join) 8. Визуализация (plot, matplotlib, seaborn)
Учимся ориентироваться в данных: выбирать строки и столбцы, понимать индексы и правильно к ним обращаться.
🔢 Индекс в Pandas
Индекс — это метки (label) строк, отдельная сущность, не входящая в состав колонок. Он служит для идентификации строк и быстрого доступа к ним.
Если индекс не задан явно, Pandas создаёт RangeIndex — стандартный числовой индекс от 0 до N-1 с шагом 1. Он работает по принципу range(), хранит только параметры start, stop, step и поэтому не занимает память. RangeIndex неизменяемый, но его можно переопределить, задав свой индекс.
Индекс может быть неуникальным (содержать повторяющиеся метки)
Альтернативы RangeIndex: - DatetimeIndex (для временных рядов) - MultiIndex (иерархический) - CategoricalIndex (для категориальных меток)
🎯 Польза индекса
1. Быстрый доступ по метке — df.loc['a'] вместо запоминания номера строки 2. Автоматическое выравнивание — при объединении данных Pandas сопоставляет строки по индексам 3. Срез по меткам — удобная выборка диапазонов (например, df.loc['2020':'2022']) 4. Смысловая нагрузка — индекс может быть id, датой и т.д.
📍 Доступ к данным: .loc и .iloc
В Pandas два основных метода выбора данных:
- `.loc[]` — выбор по меткам (именам) строк и столбцов Включает правую границу среза (закрытый интервал). Это удобно, когда метки — не числа (например, даты или буквы): хочется получить все строки от 'a' до 'c' включительно.
- `.iloc[]` — выбор по позициям (целым числам) Исключает правую границу (как в Python-срезах). Это естественно для позиционной индексации, где привычно, что [0:2] берёт элементы с индексами 0 и 1.
df = pd.DataFrame({'город': ['Москва', 'Казань', 'Сочи'], 'год': [2020, 2021, 2022]}, index=['a', 'b', 'c']) print(df) # город год # a Москва 2020 # b Казань 2021 # c Сочи 2022
Выбор по меткам — `.loc`
# Одна строка по метке print(df.loc['b']) # город Казань # год 2021
# Несколько строк по меткам print(df.loc[['a', 'c']]) # город год # a Москва 2020 # c Сочи 2022
# Срез по меткам (включая правую границу!) print(df.loc['a':'b']) # город год # a Москва 2020 # b Казань 2021
Выбор по позициям — `.iloc`
# Первая строка (позиция 0) print(df.iloc[0]) # город Москва # год 2020
# Несколько строк по позициям print(df.iloc[[0, 2]]) # город год # a Москва 2020 # c Сочи 2022
# Срез по позициям (правая граница не включается) print(df.iloc[0:2]) # город год # a Москва 2020 # b Казань 2021
Pandas — это надстройка над NumPy. Зачем? NumPy отлично работает с многомерными массивами, но имеет ограничения: - Массивы «безликие»: нет подписей у строк и столбцов - Плохо работает с разнотипными данными (числа + текст + даты) - Для анализа (группировки, сводных) нужно много кода
В аналитике данные имеют структуру (названия столбцов, метки строк) и, как правило, неоднородную: пропуски, разные типы данных и т.д.
🚀 Pandas добавляет главное — именованную структуру данных:
- У столбцов есть имена (например, «город») - У строк есть индексы (метки, например, id пользователя), не обязательно уникальные - Все операции (объединение, группировка, фильтрация) работают по именам, а не по позициям
Коротко: NumPy — про быстрые вычисления с числами. Pandas — про работу с поименованными данными.
🔄 ETL-конвейер на Pandas — основной сценарий работы аналитика
В своей работе аналитик постоянно делает микро-ETL:
1. Извлечь (Extract) — загрузить данные (CSV, Excel, SQL, API) 2. Преобразовать (Transform) — обработать: очистить, отфильтровать, сгруппировать, посчитать метрики 3. Загрузить (Load) — сохранить результат (в новый файл, базу данных, отчёт)
Pandas закрывает все три этапа в одном инструменте — быстрый «ручной» ETL. Большие промышленные ETL-пайплайны инженеры данных пишут на оркестраторах вроде Airflow, но внутри них всё равно часто вызывают Pandas-скрипты.
🧱 Две главные структуры Pandas
• Series — одномерный массив с метками (столбец) • DataFrame — двумерная таблица с именами строк и столбцов (таблица)
📈 Series
`pd.Series` — одномерный индексируемый массив. Индексы могут быть неуникальными (например, 'a', 'b', 'b'). Series можно создать из любого итерируемого объекта.
`pd.DataFrame` — двумерная таблица: и строки, и столбцы имеют имена. Создаётся из любого итерируемого объекта, но требующего двумерную структуру (список списков, словарь списков и т.д.).
Из списка списков (явно задаём названия столбцов): pd.DataFrame([[1, 2], [3, 4]], columns=['A', 'B']) # A B # 0 1 2 # 1 3 4
Из словаря списков (ключи → названия столбцов): data = {'имя': ['Аня', 'Боря'], 'возраст': [25, 30]} df = pd.DataFrame(data) print(df) # имя возраст # 0 Аня 25 # 1 Боря 30
print(df['имя']) # доступ по имени столбца print(df.index) # RangeIndex(start=0, stop=2)
Из списка словарей (каждый словарь — строка): rows = [{'город': 'Москва', 'год': 2020}, {'город': 'Казань', 'год': 2021}] pd.DataFrame(rows) # город год # 0 Москва 2020 # 1 Казань 2021
Из NumPy-массива: arr = np.array([[1, 2], [3, 4], [5, 6]]) pd.DataFrame(arr, columns=['A', 'B'], index=['x', 'y', 'z']) # A B # x 1 2 # y 3 4 # z 5 6
📐 Оси в Pandas
Оси в Pandas работают так же, как в NumPy:
- `axis=0` — операции вдоль строк (индексы) - `axis=1` — операции вдоль столбцов (названия)
df.mean(axis=0) # ср. по строкам (для каждого столбца) # A 1.5 # B 3.5
df.mean(axis=1) # ср. по столбцам (для каждой строки) # 0 2.0 # 1 3.0
⚠️ Правило фиксированной длины
У Series / DataFrame нельзя просто добавить строку (как в списке Python). Внутри Pandas использует массивы NumPy фиксированного размера — это даёт высокую скорость, но требует создания нового объекта при изменении.
Необходимые cookie нужны для работы и безопасности сайта: вход, защита форм, язык, тема и другие. Аналитические cookie Яндекс Метрики и рекламные cookie Рекламной сети Яндекса включаются только с вашего согласия. Нажимая «Принять все», вы даёте согласие на обработку данных cookie-файлов; подробнее — в Политике обработки персональных данных.