Семестр 3 · Модуль 2 · Неделя 4

Series и DataFrame. Введение в Pandas

Зачем Pandas · Series vs ndarray · DataFrame · загрузка CSV · первичный EDA · loc/iloc и фильтры

2 ч теории. Презентация — 30 мин мини-лекция, далее live-coding и задачи семинара.

Прежде чем начать: вы уже умеете считать векторы в NumPy. Сегодня добавим к числам имена — и получим таблицы, с которыми работают все downstream-курсы.

Зачем вам Pandas на 2–3 курсе

Что чаще всего получает 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

  1. Chained indexing df['a']['b'] = ... — запись «в никуда» + Warning.
  2. Путаница loc/iloc: метки vs позиции, включительность границ.
  3. Числовая колонка стала object после чтения (примеси, NaN) — не проверили dtypes.
  4. Забытая кодировка cp1251 — «кракозябры» вместо текста.
  5. df['col'] возвращает копию при записи — записывать через df.loc[rows, 'col'] = ....
Студент сделал df['Salary'].mean() и получил NaN. В чём может быть дело?

Вопросы для проверки понимания

  1. Чем loc отличается от iloc и когда какая граница включительна?
  2. Как выбрать 3 колонки и отфильтровать строки одним выражением?
  3. Что выведет describe() и какие колонки в него попадут?
  4. Почему df['a']['b'] = 5 может не записать значение?

Ответьте себе письменно за 2 минуты — это и есть мини-самооценка перед семинаром.

Переход: на семинаре вы загрузите Titanic, осмотрите его и научитесь выбирать данные без chained indexing.

Что дальше

Готовы? Откройте семинар-4.md и загрузите titanic.csv.