> 🎤 **Corey Schafer** — Кори Шефер — популярный технический видеоблогер и преподаватель, специализирующийся на глубоком разборе библиотек Python, включая Pandas и Django.

## Мастерство управления структурами данных: как эффективно добавлять и удалять строки и столбцы в Pandas

### ⚡ Зачем читать
- **Экономия времени:** Вы научитесь трансформировать огромные массивы данных за секунды, избавляясь от ручного редактирования CSV-файлов.
- **Чистота кода:** Узнаете, почему использование квадратных скобок вместо точечной нотации спасает от критических ошибок в проектах.
- **Профессиональный подход:** Освоите технику работы с `inplace=True` и условным удалением строк, что критически важно для подготовки качественных датасетов для Machine Learning.

### 🗺 Карта навыков
| Навык | Инструмент | Уровень сложности | Применение |
| :--- | :--- | :--- | :--- |
| Создание колонок | `df['col'] = ...` | Базовый | Инженерия признаков (Feature Engineering) |
| Удаление колонок | `df.drop(columns=[...])` | Базовый | Очистка данных (Data Cleaning) |
| Разделение строк | `.str.split(expand=True)` | Средний | Нормализация данных |
| Добавление строк | `.append()` | Средний | Слияние датасетов |
| Фильтрация строк | `df.drop(index=...)` | Продвинутый | Удаление выбросов и дубликатов |

## 1. Искусство создания и объединения колонок

В процессе анализа данных часто возникает ситуация, когда исходная информация разрознена. Например, у вас есть отдельные столбцы с именами и фамилиями сотрудников, но для отчетов требуется полный формат «Имя Фамилия». Кори Шефер демонстрирует, что в Pandas объединение данных — это элегантная и быстрая операция, не требующая сложных циклов.

Представим, что у нас есть DataFrame `df` с колонками `first_name` (например, «Tony») и `last_name` («Stark»). Чтобы создать новый столбец `full_name`, мы просто складываем существующие ряды, добавив между ними строковый пробел: `df['full_name'] = df['first_name'] + ' ' + df['last_name']`. Важно помнить критический совет спикера: используйте только квадратные скобки. Если вы напишете `df.full_name = ...`, Python воспримет это как попытку создать атрибут объекта, что приведет к ошибке или непредсказуемому поведению. 

После того как новые данные созданы, их можно использовать для глубокого анализа. Если вы хотите вычислить, скажем, годовой доход на основе ежемесячных выплат, вы можете применить математические операции аналогичным образом. Этот подход позволяет не просто добавлять «статичные» колонки, а внедрять логику расчетов прямо в структуру DataFrame. При добавлении колонок Pandas автоматически выравнивает данные по индексу, что исключает риск смещения значений при объединении.

> "I do want to point out that you can't use the dot notation when assigning a column like this we have to use the brackets like we did here in order to make these assignments because if you use dot notation then Python is going to think that you're trying to assign an attribute onto the data frame object and not a column."

Помимо простой конкатенации, метод `.str.split()` с аргументом `expand=True` позволяет совершить обратную операцию. Если у вас есть столбец `full_name` с данными вроде «Steve Rogers», вы можете мгновенно превратить его в две отдельные колонки `first` и `last`. Аргумент `expand=True` здесь играет ключевую роль: он преобразует результат сплита из списка в полноценные столбцы DataFrame. Без него вы получили бы просто серию списков, что неудобно для дальнейшей работы.

✅ **Сделайте сейчас:** Создайте DataFrame с колонками `City` и `Country`. Используя операцию сложения строк, создайте новый столбец `Location`, объединив их через запятую. Затем попробуйте удалить исходные столбцы методом `.drop()` с аргументом `columns=['City', 'Country']`.

## 2. Управление жизненным циклом данных: удаление и добавление строк

Удаление и добавление записей — это хлеб насущный специалиста по работе с данными. Часто мы сталкиваемся с тем, что в DataFrame нужно добавить новые наблюдения (например, данные о новом клиенте) или, наоборот, избавиться от «шумных» записей. Кори Шефер детально разбирает метод `.append()`, который, несмотря на свою простоту, требует внимания к деталям, особенно в части индексов.

При добавлении одной записи (например, словарь `{'first_name': 'Tony'}`) к существующему набору, вы столкнетесь с ошибкой индексации, если не используете `ignore_index=True`. Аргумент `ignore_index=True` сообщает Pandas: «не пытайся найти существующий индекс для этой строки, просто создай новый с нуля». Это критически важно, так как при добавлении строк из разных источников или при разном порядке колонок, индексы могут конфликтовать. Если вы добавляете новый DataFrame `df2` к `df1`, следите за аргументом `sort=False`. Это предотвращает появление предупреждений о сортировке колонок и делает код более предсказуемым.

Удаление строк — это процесс фильтрации. Мы используем метод `.drop()`, передавая в него `index` тех записей, которые нам не нужны. Однако «высший пилотаж» — это удаление на основе условий. Вместо того чтобы вручную искать ID строки, вы можете создать фильтр: `filt = (df['last_name'] == 'Doe')`, а затем передать `df.drop(index=df[filt].index)`. Это делает ваш код динамичным: он будет работать корректно, даже если данные в таблице изменятся. Кори подчеркивает: всегда выносите условия фильтрации в отдельные переменные. Это не только делает код чище, но и облегчает отладку.

> "I don't really like all of this being bunched up together because I think that it's hard to read and you always want your code to be easy to read by other developers so I would pull the conditional out into its own variable and instead I would say something like this I would say filt for filter is equal to and then I'll just grab our conditional here."

Помните: методы `.drop()` и `.append()` возвращают копию объекта, а не меняют исходный DataFrame, если не указан параметр `inplace=True`. Впрочем, стоит учитывать, что для `.append()` этот параметр недоступен, поэтому для сохранения изменений всегда используйте конструкцию `df = df.append(...)`. Это важная дисциплина, которая предотвращает потерю данных при выполнении длинных скриптов обработки данных.

✅ **Сделайте сейчас:** Создайте второй DataFrame с данными одного пользователя. Присоедините его к основному DataFrame с использованием `ignore_index=True`. Затем найдите все строки, где значение в колонке `Email` равно `NaN`, и удалите их с помощью фильтрации по индексу.

---

## 3. Продвинутые техники фильтрации и удаления строк по сложным условиям

В профессиональной разработке и анализе данных мы редко удаляем строки «вручную», зная их конкретный индекс. Реальные датасеты содержат тысячи и миллионы записей, поэтому удаление должно основываться на логических критериях. Кори Шефер демонстрирует, как превратить процесс очистки данных в элегантную фильтрацию. Основная идея заключается в создании промежуточной переменной-фильтра, которая делает код самодокументируемым и легко читаемым для коллег.

Допустим, наша задача — избавиться от всех записей, где фамилия сотрудника совпадает с определенным значением, например, «Doe». Вместо того чтобы вызывать `df.drop()` и пытаться вставить сложную логику внутрь скобок, мы создаем переменную `filt = (df['last_name'] == 'Doe')`. Это создает серию булевых значений (True/False). Затем мы вызываем `df.drop(index=df[filt].index)`. Метод `.index` здесь критически важен: он извлекает метки всех строк, удовлетворяющих условию, которые затем передаются в `drop` для удаления. Такая связка позволяет динамически изменять датасет в зависимости от входящих данных.

Кори делает важный акцент на чистоте кода. Новички часто стремятся «запихнуть» всё выражение в одну строку: `df.drop(index=df[df['last_name'] == 'Doe'].index)`. Хотя это работает, такой подход быстро превращается в «спагетти-код», который крайне сложно отлаживать. Разделение логики на определение фильтра и его применение — это стандарт индустрии. Это позволяет не только легко заменить условие, но и, например, сначала просмотреть, какие именно строки попали под удаление, просто вызвав `df[filt]` перед выполнением операции `drop`.

> "I don't really like all of this being bunched up together because I think that it's hard to read and you always want your code to be easy to read by other developers so I would pull the conditional out into its own variable and instead I would say something like this I would say filt for filter is equal to and then I'll just grab our conditional here."

Помимо этого, метод `.drop()` можно использовать для удаления строк по их меткам (labels). Если вы знаете ID строки, передача его в `index` работает мгновенно. Важно понимать, что Pandas в данном случае оперирует индексами (индексами строк), а не порядковыми номерами, если только они не совпадают. Всегда проверяйте текущий индекс перед удалением, чтобы избежать случайного удаления не тех данных. Если вы хотите сделать удаление постоянным, не забывайте про `inplace=True`, иначе метод просто вернет «срез» без нужных строк, а ваш исходный `df` останется нетронутым.

✅ **Сделайте сейчас:** Создайте датафрейм с данными о десяти сотрудниках (имя, фамилия, отдел). Сформируйте фильтр для поиска сотрудников из отдела 'Sales'. Выведите их на экран, убедитесь в правильности выбора, а затем удалите эти записи из основного датафрейма с помощью переменной `filt` и метода `.drop()`.

## 4. Архитектурный подход к слиянию DataFrame и управлению индексами

Когда мы работаем с данными, поступающими из разных источников, рано или поздно приходится объединять несколько DataFrame в один. Метод `.append()` в Pandas был долгое время классическим инструментом для этого, и хотя в современных версиях библиотеки предпочтение отдается методу `pd.concat()`, понимание логики, которую заложил в обучение Кори Шефер, критически важно для понимания того, как работают индексы.

Главная проблема при объединении — это конфликты индексов и разный порядок колонок. Если вы просто попытаетесь прибавить один DataFrame к другому, Pandas может выдать предупреждение (или даже ошибку в старых версиях), если индексы дублируются или столбцы идут в разном порядке. Аргумент `ignore_index=True` здесь — ваш лучший друг. Он отбрасывает старые индексы присоединяемого DataFrame и перестраивает их последовательно, начиная с нуля (или продолжая текущий индекс основного объекта). Это гарантирует, что у каждой строки будет уникальный идентификатор, что предотвращает ошибки при дальнейшей фильтрации.

При работе с колонками также важно учитывать параметр `sort=False`. При слиянии Pandas по умолчанию пытается привести столбцы к единому алфавитному порядку, чтобы они «совпали». Если вам это не нужно, отключение сортировки ускоряет процесс и избавляет от лишних предупреждений. Помните: Pandas — это объектно-ориентированная библиотека. Если вы присваиваете результат операции переменной, вы создаете новый объект в памяти. Это безопасный подход, позволяющий избежать случайной порчи данных. Если же вы хотите сэкономить память при работе с гигантскими таблицами, всегда взвешивайте, стоит ли делать копии или можно эффективно изменять структуру "на лету".

> "These have conflicting indexes and they also have columns that are not in the same order so again we're going to want to ignore the indexes when appending these so that they are assigned indexes properly."

Для профессионала важно понимать не только «как» написать код, но и «почему» он работает именно так. При добавлении строк, если в новом DataFrame отсутствуют какие-то колонки, которые есть в основном, Pandas автоматически заполнит их значениями `NaN` (Not a Number). Это «правило пустоты» позволяет объединять разнородные данные без поломки структуры. Всегда следите за типами данных в новых колонках после объединения: иногда появление `NaN` заставляет Pandas автоматически перевести целочисленный тип колонки (int) в тип с плавающей точкой (float), чтобы вместить значения `NaN`. Это может быть неожиданным побочным эффектом, который важно учитывать при дальнейшем анализе и построении моделей машинного обучения.

---

## 5. Методы трансформации строк: от строкового разделения к автоматизации колонок

Часто в реальных задачах анализа данных мы сталкиваемся с «грязными» строковыми данными, где несколько логических единиц информации объединены в одну ячейку. Классический пример из практики Кори Шефера — колонка `full_name`, содержащая имя и фамилию через пробел. Чтобы сделать эти данные пригодными для анализа или построения моделей, нам необходимо провести операцию декомпозиции. Для этих целей в Pandas используется мощный строковый аксессор `.str` в сочетании с методом `.split()`. Однако одной лишь разбивки недостаточно: по умолчанию `split` вернет список, что неудобно для дальнейшей обработки. Здесь на помощь приходит аргумент `expand=True`, который трансформирует результат разбиения в отдельные колонки датафрейма.

Рассмотрим на примере: если у нас есть колонка `full_name` со значениями вроде «Tony Stark» или «Steve Rogers», вызов `df['full_name'].str.split(' ', expand=True)` вернет новый объект DataFrame с двумя колонками, где в первой будут имена, а во второй — фамилии. Это принципиально меняет структуру данных. Далее мы можем легко присвоить эти результаты обратно в существующий DataFrame, обратившись к нему через список имен колонок: `df[['first', 'last']] = df['full_name'].str.split(' ', expand=True)`. Такой подход позволяет за одну операцию создать новые признаки, что критически важно при подготовке данных для машинного обучения или продвинутой визуализации.

Кори Шефер подчеркивает важность чистоты операций. Когда мы используем `.split()`, мы не просто меняем тип данных — мы меняем размерность пространства признаков. Новички часто забывают про параметр `expand=True`, из-за чего получают серию списков, с которыми невозможно выполнять арифметические или логические операции. Всегда проверяйте результат через `head()`, чтобы убедиться, что структура колонок соответствует ожидаемой. Также важно помнить: если количество пробелов в строках варьируется, `split` может вести себя иначе, поэтому всегда стоит предварительно исследовать данные на наличие аномалий.

> "The result of that split method is that we get the first name and the last name in a list... if we want to assign these to two different columns then we need to expand this list so that they're actually in two different columns so to do this in pandas we can use the expand argument."

Помимо разделения, метод `.apply()` является отличной альтернативой для более сложных трансформаций, например, математических операций над колонками (скажем, конвертация валют или расчет индексов). Однако работа со строками через `.str` оптимизирована под векторизованные операции, поэтому она работает значительно быстрее, чем `apply` с использованием лямбда-функций. Всегда отдавайте предпочтение встроенным строковым методам, если задача может быть ими решена.

✅ **Сделайте сейчас:** Создайте DataFrame с колонкой `date_time` (например, "2023-01-01 10:00"). Используйте метод `.str.split()` с пробелом в качестве разделителя и аргументом `expand=True`, чтобы разделить дату и время на две отдельные колонки. Переименуйте их в `date` и `time` и выведите обновленный DataFrame.

## 6. Принципы безопасности при манипуляции данными: атрибуты vs индексы

При работе с Pandas важно понимать разницу между доступом к данным через атрибуты (dot notation) и через индексы (square brackets). Хотя новичкам часто кажется, что `df.column_name` и `df['column_name']` — это одно и то же, это не так. Использование точечной нотации ограничено: вы не можете создать новый столбец с помощью `df.new_col = ...`, так как Python интерпретирует это как добавление атрибута к объекту, что часто ведет к конфликтам или ошибкам. Кори Шефер категоричен: для операций создания, удаления и выбора данных всегда используйте квадратные скобки. Это стандарт, гарантирующий, что Pandas будет работать именно с колонками вашего датафрейма, а не с метаданными или методами самого объекта.

Особое внимание стоит уделить методу `.drop()`. При удалении колонок мы передаем параметр `columns=`, а при удалении строк — `index=`. Это разграничение зон ответственности делает код предсказуемым. Если вы случайно передадите название колонки в аргумент `index`, Pandas выдаст ошибку, так как не найдет соответствующую метку среди индексов строк. Ошибка — это хорошо, ведь она предотвращает silent fail, когда программа продолжает работать с неверными данными. Всегда используйте параметр `inplace=True` только после того, как вы убедились в правильности выбора данных, предварительно выведя результат операции на экран.

Важный нюанс: концепция «view vs copy». Когда вы выбираете подмножество данных или удаляете часть строк, Pandas часто возвращает представление (view). Если вы планируете изменять эти данные, Pandas может выдать `SettingWithCopyWarning`. Это сигнал того, что вы работаете с копией, и изменения не будут применены к оригиналу. Чтобы избежать этого, используйте метод `.copy()` при создании промежуточных срезов. Понимание того, когда вы работаете с оригиналом, а когда с временной копией — это то, что отличает продвинутого аналитика от начинающего.

> "I do want to point out that you can't use the dot notation when assigning a column like this we have to use the brackets like we did here in order to make these assignments because if you use dot notation then Python is going to think that you're trying to assign an attribute onto the data frame object."

В заключение: автоматизация очистки через переменные-фильтры (`filt`), использование `inplace=True` для экономии памяти и избегание точечной нотации при записи — это «золотой стандарт» работы с данными. Эти навыки позволяют писать код, который работает стабильно даже на огромных наборах данных, где любая ошибка индексации может привести к потере часов вычислительного времени.

✅ **Сделайте сейчас:** Попробуйте создать новый столбец в своем DataFrame, используя точечную нотацию (`df.test = 1`), а затем попробуйте добавить столбец через квадратные скобки (`df['test'] = 1`). Сравните поведение Pandas. После этого удалите столбец `test` методом `.drop(columns=['test'])` и проверьте, остался ли он в DataFrame.

---

## 7. Работа с `append`: интеграция данных и стратегия `ignore_index`

Когда мы переходим от работы с единичными записями к объединению целых наборов данных, метод `.append()` становится основным инструментом «склейки». Представьте ситуацию: у вас есть основной DataFrame `df` с данными сотрудников, и из другого источника (например, API или CSV-файла) пришел новый DataFrame `df2` с данными новых коллег. Кори Шефер акцентирует внимание на том, что простое присоединение — это не всегда тривиальная задача, так как структура индексов может конфликтовать. Если оба датафрейма имеют индексы, начинающиеся с 0, Pandas при объединении сохранит их дубликаты, что создаст хаос при последующей индексации по методу `.loc`.

Использование аргумента `ignore_index=True` — это не просто «опция», это профессиональная гигиена данных. Установив его, вы приказываете Pandas отбросить старые индексы и перестроить их последовательно (0, 1, 2...). Это гарантирует уникальность каждой строки, что жизненно важно для предотвращения ошибок при фильтрации. При работе с методом `append` важно помнить: в отличие от `drop`, он не имеет аргумента `inplace=True`. Это означает, что метод возвращает *новый* объект DataFrame, а не изменяет старый. Вы должны явно переприсвоить его: `df = df.append(df2, ignore_index=True)`. Игнорирование этого правила — классическая ошибка новичков, после которой они удивляются, почему их данные «не сохранились».

> "These have conflicting indexes and they also have columns that are not in the same order so again we're going to want to ignore the indexes when appending these so that they are assigned indexes properly."

При добавлении данных с разной структурой колонок, Pandas проявляет гибкость: если в `df2` есть столбец, которого нет в `df`, он будет добавлен, а пустые ячейки в старых строках будут заполнены `NaN`. Это «правило пустоты» полезно, но требует осторожности. Если новые данные имеют другие типы (например, целые числа в `df` и строки в `df2`), Pandas может автоматически повысить тип колонки до `object`, что негативно скажется на производительности. Всегда проверяйте типы данных после «склейки» через `df.dtypes`.

✅ **Сделайте сейчас:** Создайте два небольших DataFrame: `df1` (столбцы 'A', 'B') и `df2` (столбцы 'B', 'C'). Объедините их с помощью `.append()` с параметром `ignore_index=True`. Изучите, как Pandas заполнил пропущенные значения `NaN` для отсутствующих колонок в каждом из наборов данных.

## 8. Искусство удаления строк: логическая фильтрация против индексации

Удаление строк — это операция, которая в руках аналитика становится инструментом очистки данных. Кори Шефер показывает два пути: удаление по конкретному индексу (например, `df.drop(4)`) и удаление на основе условий. Второй путь — это «высший пилотаж». Когда мы хотим удалить строки, где, например, «фамилия равна Doe», новички склонны пытаться сделать это через сложные циклы. Однако профессиональный подход заключается в использовании масок (фильтров) и передачи этих масок в метод `.drop()`.

Важный аспект: использование промежуточной переменной `filt`. Вместо того чтобы «засорять» вызов метода `drop` громоздким условием, лучше сначала определить `filt = (df['last_name'] == 'Doe')`, а затем передать `df.drop(index=df[filt].index)`. Это делает код читаемым, отлаживаемым и безопасным. Если вы совершили ошибку в условии, легко проверить переменную `filt` отдельно, не запуская при этом потенциально опасную операцию удаления. Кори подчеркивает: «Всегда делайте свой код понятным для других разработчиков». Читаемость кода в аналитике данных — это залог того, что вы не совершите фатальную ошибку при обработке огромных массивов информации.

> "I don't really like all of this being bunched up together because I think that it's hard to read and you always want your code to be easy to read by other developers so I would pull the conditional out into its own variable."

Понимание того, как работают индексы при удалении, также критично. Если вы удаляете строки, Pandas оставляет пропуски в нумерации индексов (например, 0, 1, 3, 4). Если вам критична непрерывная нумерация, после удаления используйте метод `.reset_index(drop=True)`. Помните, что удаление строк — это трансформация, которая меняет размерность данных. Если вы планируете продолжать работу с тем же датафреймом, убедитесь, что вы осознаете, как изменилось количество наблюдений. Потеря данных должна быть всегда осознанным действием, а не следствием опечатки в индексе.

✅ **Сделайте сейчас:** Сгенерируйте DataFrame из 10 строк. Создайте логическую маску, выбрав строки, где значение в колонке 'A' больше 5. Используйте эту маску, чтобы удалить соответствующие строки из DataFrame, а затем выполните `.reset_index(drop=True)`, чтобы обновить индексы.

## 🏋️ Практикум
1. Добавьте столбец `salary` к существующему DataFrame, заполнив его случайными числами.
2. Переименуйте столбцы в DataFrame, используя метод `.rename()`, чтобы избавиться от пробелов в названиях.
3. Объедините два DataFrame, один из которых имеет лишнюю колонку, и убедитесь, что `NaN` появились в правильных местах.
4. Удалите строку по индексу 5, предварительно сохранив копию датафрейма.
5. Создайте сложный фильтр (используя операторы `&` или `|`) и удалите все строки, соответствующие этому условию.
6. Рассчитайте среднее значение новой колонки после того, как вы добавили в неё данные.
7. Используйте `apply` для обработки строковых данных в колонке, чтобы преобразовать всё в верхний регистр, и сравните это с методом `.str.upper()`.

## 🔑 Итоги: 5 действий на сегодня
1. Используйте `[]` для создания колонок, забудьте про точечную нотацию (`df.col = ...`) для записи.
2. При `append` всегда используйте `ignore_index=True`, если не хотите получить дубликаты индексов.
3. При удалении сложных наборов строк выносите условия в переменную `filt` для повышения читаемости.
4. Используйте `expand=True` в `.str.split()` для мгновенной декомпозиции данных.
5. Всегда делайте `df = ...` при операциях без `inplace=True`, чтобы изменения сохранились.

## 💬 Цитаты для вдохновения
- "If you use dot notation then Python is going to think that you're trying to assign an attribute onto the data frame object and not a column."
- "You always want your code to be easy to read by other developers, so I would pull the conditional out into its own variable."