Семестр 3 · Модуль 2 · Неделя 4
Series и DataFrame. Введение в Pandas
Зачем Pandas · Series vs ndarray · DataFrame · загрузка CSV · первичный EDA ·
loc/iloc и фильтры
2 ч теории. Презентация — 30 мин мини-лекция, далее live-coding
и задачи семинара.
Прежде чем начать: вы уже умеете считать векторы в NumPy.
Сегодня добавим к числам имена — и получим таблицы, с которыми
работают все downstream-курсы.
Зачем вам Pandas на 2–3 курсе
- Почти все downstream-курсы начинаются строкой
import pandas as pd:
Анализ данных, Математическая статистика, ML, NLP, Базы данных.
- Реальные данные — это таблицы: выгрузки из БД, CSV с продажами,
ответы опросов, логи. Pandas — стандарт работы с ними.
- Один вызов
df.info() заменяет 20 строк ручного осмотра — а это и есть
начало анализа (EDA).
- Навык переносится: тот же
groupby встретится в SQL (модуль 4) и в
отчётах на работе.
Что чаще всего получает data scientist от заказчика: готовый чистый
файл или «сырую» таблицу, которую надо осмотреть и почистить?
Мостик: список → ndarray → Series → DataFrame
От известного к новому, на контрасте. Все четыре структуры «похожи», но добавляют по одной возможности:
| Структура | Индексация | «+» | Тип элементов |
| Список (С1) | по позиции | конкатенация | любой |
| ndarray (модуль 1) | по позиции | поэлементно | один dtype |
| Series (новое) | по метке и позиции | поэлементно, метки сохраняются | один dtype |
| DataFrame (новое) | loc/iloc по строкам, колонки по имени | поэлементно по значениям | разный по колонкам |
Главное, что добавляет Pandas к NumPy, — метки: у чисел
появляются имена, и результат операций их сохраняет.
Что даст [1,2,3] + [4,5,6]? А np.array([1,2,3]) + np.array([4,5,6])?
Series — одномерный массив с метками
Series = ndarray + index (метки элементов). Создание из списка, словаря, ndarray:
import pandas as pd
s = pd.Series([10, 20, 30], index=['a', 'b', 'c'])
s.index, s.values, s.dtype
(Index(['a', 'b', 'c'], dtype='object'), array([10, 20, 30]), dtype('int64'))
Из словаря: pd.Series({'a': 10, 'b': 20}) — ключи станут метками.
У Series есть имя: s.name = 'оценки'.
Чем pd.Series([1,2,3]) без index отличается от np.array([1,2,3])?
Доступ по метке и по позиции
s['b'] # по метке → 20
s.iloc[1] # по позиции → 20
s.loc['b'] # по метке, осознанно → 20
Векторные операции — как в NumPy, но метки сохраняются:
s * 2
a 20
b 40
c 60
dtype: int64
Что вернёт s.loc[['a','c']] — число, Series или что-то ещё?
DataFrame — двумерная таблица
DataFrame — «Series, собранные в таблицу»: строки (index) × столбцы (columns).
У колонок могут быть разные типы — это главное отличие от ndarray.
pd.DataFrame({ # dict of lists
'name': ['Аня', 'Боря', 'Вера'],
'age': [20, 21, 19],
})
name age
0 Аня 20
1 Боря 21
2 Вера 19
Атрибуты: df.shape, df.columns,
df.index, df.dtypes, df.values.
Создание из list of dicts, ndarray, Series — те же варианты.
Сколько строк и столбцов в таблице? Почему df.values здесь — object-массив?
Загрузка данных: pd.read_csv
Самый частый первый шаг любого анализа:
df = pd.read_csv('titanic.csv')
df.shape
(40, 8)
Ключевые параметры: sep (разделитель, для CSV по умолчанию ,),
encoding, header, index_col, usecols,
nrows (первые N строк), parse_dates, na_values.
# продвинутые, когда нужны:
df = pd.read_csv('sales.csv', parse_dates=['date'], na_values=['-', 'N/A'])
Какой параметр нужен, если файл содержит заголовки колонок в первой строке? А если разделитель — точка с запятой?
Кодировка: cp1251 — источник «кракозябр»
Русскоязычные CSV из Excel/1С часто сохранены в cp1251, а не utf-8.
Неверная кодировка даёт мусор вместо текста:
df = pd.read_csv('titanic.csv') # файл в cp1251
df.head()
# PassengerId Name ...
# 0 1 Ðнна Ðванов ...
Решение — указать кодировку при чтении. Если текст всё равно «кривой» — проверь
encoding='cp1251'.
df = pd.read_csv('titanic.csv', encoding='cp1251')
Что увидит студент, открывший cp1251-файл как utf-8? Как это диагностировать?
Первичный осмотр: head, info, describe
df[['PassengerId', 'Name',
'Pclass', 'Survived']].head(3)
df.info()
PassengerId Name Pclass Survived
0 1 Анна Иванов 3 0
1 2 Мария Петров 2 0
2 3 Пётр Сидоров 3 1
Data columns (total 8 columns): # показаны 3 из 8 строк
# Column Non-Null Count Dtype
0 PassengerId 40 non-null int64
1 Name 40 non-null str
4 Age 38 non-null float64 # ← 2 пропуска!
7 Survived 40 non-null int64
Что в info() сразу выдаёт проблему с данными (пропуски)? Какой колонки это касается?
describe() и value_counts()
df.describe()
df['Survived'].value_counts(normalize=True)
PassengerId Pclass Age Fare Survived
count 40.00 40.00 38.00 40.00 40.00
mean 20.50 2.28 28.15 131.02 0.38
std 11.69 0.88 10.30 72.68 0.49
min 1.00 1.00 9.30 10.29 0.00
25% 10.75 1.00 18.95 73.03 0.00
50% 20.50 3.00 27.20 128.20 0.00
75% 30.25 3.00 34.67 195.74 1.00
max 40.00 3.00 49.20 248.13 1.00
Survived
0 0.625
1 0.375
Name: proportion, dtype: float64
Какие колонки НЕ попадают в describe() и почему? Что вернёт unique() по колонке Embarked?
Выбор колонок: df['col'] и df[['a','b']]
df['Age'] # Series — одна колонка
df[['Name', 'Age']] # DataFrame — несколько
0 34.8
1 19.1
2 25.5
Name: Age, dtype: float64
Важное следствие: df['Age'].mean() — это среднее по колонке,
а df[['Age']] — таблица из одной колонки.
Какой тип у df['Age'] и у df[['Age']]? Почему это важно для .mean()?
loc vs iloc — по меткам или по позициям
# loc: метки, граница ВКЛЮЧИТЕЛЬНО
df.loc[0:5, 'Name':'Age']
# iloc: позиции, граница ИСКЛЮЧИТЕЛЬНО
df.iloc[5:15, [0, 2]]
Name Pclass Sex Age
0 Анна Иванов 3 male 34.8
1 Мария Петров 2 male 19.1
2 Пётр Сидоров 3 male 25.5
3 Ольга Козлов 3 male NaN
4 Ольга Новиков 3 male 25.7
Почему loc[0:5] включает строку 5, а iloc[0:5] — нет?
Фильтрация строк: df[условие]
df[df['Survived'] == 1] # все выжившие
df[df['Age'] > 30] # старше 30
df[df['Age'].isna()] # без возраста
df[(df['Survived'] == 1) & (df['Age'] > 30)] # & — И, со скобками!
Булева маска — как в NumPy (модуль 1), только фильтрует строки.
df.loc[df['Survived'] == 1, ['Name', 'Age']] # строки И колонки сразу
Что вернёт df[df['Pclass'] == 1]? Как записать условие «И», а как «ИЛИ»?
set_index / reset_index и смена индекса
df2 = df.set_index('PassengerId') # сделать колонку индексом
df2.loc[3] # выборка по метке-номеру
df2 = df2.reset_index() # вернуть индекс как колонку
Индекс — «адресная книга» строк. loc выбирает по нему,
iloc — по позиции независимо от меток.
После set_index('PassengerId') — что вернёт df2.loc[3]?
Chained indexing — главный риск недели
# ОПАСНО: два вызова подряд
df['Age'][df['Age'] > 30] = 99
# SettingWithCopyWarning + запись "в никуда"
# ПРАВИЛЬНО: один атомарный вызов
df.loc[df['Age'] > 30, 'Age'] = 99
df['Age'] может вернуть копию; запись в копию не меняет
исходную таблицу и порождает предупреждение. Правило: один вызов
df.loc[...].
Почему df['a']['b'] = 5 может «не записать» значение?
Как правильно записать в подмножество?
Типичные ошибки недели 4
- Chained indexing
df['a']['b'] = ... — запись «в никуда» + Warning.
- Путаница
loc/iloc: метки vs позиции, включительность границ.
- Числовая колонка стала
object после чтения (примеси, NaN) — не проверили dtypes.
- Забытая кодировка
cp1251 — «кракозябры» вместо текста.
df['col'] возвращает копию при записи — записывать через df.loc[rows, 'col'] = ....
Студент сделал df['Salary'].mean() и получил NaN. В чём может быть дело?
Вопросы для проверки понимания
- Чем
loc отличается от iloc и когда какая граница включительна?
- Как выбрать 3 колонки и отфильтровать строки одним выражением?
- Что выведет
describe() и какие колонки в него попадут?
- Почему
df['a']['b'] = 5 может не записать значение?
Ответьте себе письменно за 2 минуты — это и есть мини-самооценка перед семинаром.
Переход: на семинаре вы загрузите Titanic, осмотрите его и научитесь
выбирать данные без chained indexing.
Что дальше
- Семинар недели 4: загрузка Titanic, первичный EDA, фильтры,
loc/iloc — базовый, стандартный и продвинутый уровни.
- ДЗ недели 4: EDA-ноутбук по своему датасету с 3–4 вопросами и
числовыми ответами.
- Неделя 5: очистка данных — пропуски, дубликаты, типы. Без неё
не работает ни один анализ.
Готовы? Откройте семинар-4.md и загрузите titanic.csv.