Архив рубрики ~Лента новостей~

3 приема Pandas для очистки и подготовки данных

3 приема Pandas для очистки и подготовки данных
3 приема Pandas для очистки и подготовки данных

В этой статье мы рассмотрим три основных приема Pandas для эффективной очистки и подготовки данных: декларативное связывание методов, оптимизация памяти и скорости с помощью категориальных переменных и векторизованных строковых методов доступа, а также заполнение пропущенных данных с учетом групп с помощью метода `.transform()`.

3 приема Pandas для очистки и подготовки данных

# Введение

По оценкам, очистка и подготовка данных занимают до 80% рабочего времени специалиста по анализу данных. Поскольку Pandas является стандартной библиотекой для обработки данных в Python, эффективность ваших операций напрямую определяет, как быстро вы сможете перейти от необработанных, «загрязненных» наборов данных к готовым к моделированию признакам. И есть веская причина увеличить время, затрачиваемое на очистку и подготовку данных: это напрямую означает больше времени, которое можно потратить на моделирование, анализ и передачу результатов.

Однако многие разработчики пишут код на Pandas, имитирующий стандартные циклические структуры Python или использующий императивные обновления, изменяющие состояние. Эти подходы страдают от ряда проблем: они могут вызывать сбивающее с толку предупреждение SettingWithCopyWarning, увеличивать использование оперативной памяти из-за избыточного копирования и снижать скорость выполнения, избегая векторизации.

Для создания конвейеров обработки данных производственного уровня необходимо перейти от базового синтаксиса к идиоматическим шаблонам проектирования Pandas. В этой статье мы рассмотрим три важных приема Pandas для эффективной очистки и подготовки данных:

  1. декларативная цепочка методов
  2. Оптимизация памяти и скорости с помощью категориальных и векторизованных строковых методов доступа
  3. групповая импутация с использованием метода .transform()

# 1. Декларативная цепочка методов с использованием .assign(), .query() и .pipe()

При подготовке данных обычно выполняется последовательность модификаций: очистка строковых значений, создание новых математических столбцов, фильтрация выбросов, переименование полей и так далее.

Наивный подход предполагает последовательную запись этих операций, изменяя DataFrame на месте или многократно переназначая его одной и той же переменной. Это не только затрудняет чтение и отладку кода, но и часто приводит к появлению печально известного предупреждения SettingWithCopyWarning при изменении срезов DataFrame. Это предупреждение означает, что Pandas не может гарантировать, изменяете ли вы копию или исходный буфер массива в памяти.

Обернув конвейер очистки данных в скобки, вы можете последовательно связывать методы Pandas. Использование метода `.assign()` для объявления новых столбцов, метода `.query()` для фильтрации строк и метода `.pipe()` для применения пользовательских функций делает ваши операции линейными, читаемыми и защищенными от побочных эффектов.

Этот императивный стиль изменяет DataFrame поэтапно, что сопряжено с риском появления предупреждающих сигналов и затрудняет изолирование промежуточных этапов:

import pandas as pd import numpy as np # Пример исходных данных о продажах data = { 'sale_date': ['2026-01-01', '2026-01-02', 'invalid_date', '2026-01-04'], 'item_code': [' PROD_A ', ' PROD_B', 'PROD_C ', ' PROD_D '], 'price': [100.0, 250.0, -99.0, 150.0], 'quantity': [2, 1, 5, 3] } df = pd.DataFrame(data) # Простая многошаговая очистка df['sale_date'] = pd.to_datetime(df['sale_date'], errors='coerce') df['item_code'] = df['item_code'].str.strip() df['total_revenue'] = df['price'] * df['quantity'] # Фильтрация некорректных дат и недействительных цен df = df[df['sale_date'].notna()] df = df[df['price'] > 0] # Переименование столбцов для обеспечения согласованности df.rename(columns={'item_code': 'product_id'}, inplace=True) print(df)

Здесь мы преобразуем ту же самую логику в единый, целостный, сквозной конвейер. Для обработки пользовательских аномалий мы используем специальную вспомогательную функцию с помощью метода `.pipe()`:

import pandas as pd import numpy as np data = { 'sale_date': ['2026-01-01', '2026-01-02', 'invalid_date', '2026-01-04'], 'item_code': [' PROD_A ', ' PROD_B', 'PROD_C ', ' PROD_D '], 'price': [100.0, 250.0, -99.0, 150.0], 'quantity': [2, 1, 5, 3] } df_raw = pd.DataFrame(data) # Пользовательский модульный шаг очистки def clean_item_codes(df): df['item_code'] = df['item_code'].str.strip() return df # Конвейер цепочки методов cleaned_df = ( df_raw .copy() # Предотвращает изменение исходных необработанных данных .assign( sale_date=lambda d: pd.to_datetime(d['sale_date'], errors='coerce'), total_revenue=lambda d: d['price'] * d['quantity'] ) .pipe(clean_item_codes) .query(«sale_date.notna() and price > 0») .rename(columns={'item_code': 'product_id'}) ) print(cleaned_df)

Выход:

дата_продажи идентификатор_продукта цена количество общая_выручка 0 2026-01-01 PROD_A 100.0 2 200.0 1 2026-01-02 PROD_B 250.0 1 250.0 3 2026-01-04 PROD_D 150.0 3 450.0

Заключив выражение в скобки ( … ), Python позволяет создавать многострочные цепочки без использования обратных косых черт.

  • Метод `.assign()` принимает именованные аргументы, в которых лямбда-функции получают текущее состояние DataFrame (d), что позволяет создавать или изменять несколько столбцов последовательно.
  • Метод `.pipe()` передает промежуточный DataFrame во внешнюю функцию. Это отделяет многократно используемую логику очистки от основной цепочки.
  • Метод `.query()` принимает логическое выражение в виде строки. Он более удобен, чем вложенные скобки (`df[(df[a] > 0) & (df[b].notna())]`), и работает быстрее благодаря быстрому вычислителю числовых выражений NumPy, `NumExpr` .

Этот функциональный шаблон позволяет избежать предупреждения SettingWithCopyWarning, поскольку он никогда не изменяет промежуточные срезы.

# 2. Оптимизация памяти и скорости с помощью категориальных и векторизованных строковых методов

По умолчанию Pandas присваивает столбцам, содержащим текст, универсальный тип данных «объект». Столбец типа «объект» хранит указатели Python на строки, разбросанные по памяти кучи, а не непрерывные, упакованные значения. Для больших наборов данных со строками низкой кардинальности (столбцы с повторяющимися категориями, такими как флаги статуса, названия городов или пол) это по умолчанию приводит к очевидному увеличению потребления памяти.

Кроме того, разработчики часто вносят пользовательские изменения в строки, передавая лямбда-выражения Python в метод `.apply()`. Это заставляет Pandas последовательно перебирать каждую строку, что замедляет работу интерпретатора Python.

Мы можем оптимизировать как использование оперативной памяти, так и время выполнения, следующим образом:

  1. Преобразование строковых столбцов с низкой кардинальностью в собственный тип данных категорий.
  2. Замена медленных циклов `.apply()` на оптимизированные векторизованные строковые методы с помощью аксессора `.str`.

Давайте смоделируем очистку большого набора данных (1 000 000 строк), сохранив текст в виде столбцов-объектов и удалив пробелы с помощью метода `.apply()`:

import pandas as pd import numpy as np import time # Создание фиктивного набора данных с 1 миллионом строк строковых данных с низкой кардинальностью n_rows = 1000000 categories = [' PENDING ', ' COMPLETED ', ' FAILED ', ' SHIPPED '] df = pd.DataFrame({ 'status': np.random.choice(categories, size=n_rows), 'val': np.random.rand(n_rows) }) # Оценка использования памяти перед очисткой mem_before = df['status'].memory_usage(deep=True) / (1024 ** 2) start_time = time.time() # Наивная очистка: медленные циклы применения Python df['status'] = df['status'].apply(lambda x: x.strip().upper()) duration_apply = time.time() — start_time mem_after = df['status'].memory_usage(deep=True) / (1024 ** 2) print(f»Очистка выполнена за: {duration_apply:.4f} секунд») print(f»Использование памяти столбцом Status: {mem_after:.2f} МБ (изначально {mem_before:.2f} МБ)»)

Преобразовав столбец статуса в категорию в первую очередь и используя векторизованный аксессор .str, мы мгновенно ускоряем процесс и значительно экономим память:

import pandas as pd import numpy as np import time n_rows = 1000000 categories = [' PENDING ', ' COMPLETED ', ' FAILED ', ' SHIPPED '] df = pd.DataFrame({ 'status': np.random.choice(categories, size=n_rows), 'val': np.random.rand(n_rows) }) # Преобразование в тип данных категории df['status'] = df['status'].astype('category') # Оценка использования памяти mem_category = df['status'].memory_usage(deep=True) / (1024 ** 2) start_time = time.time() # Векторизованная очистка строк непосредственно по категориям df['status'] = df['status'].cat.rename_categories(lambda x: x.strip().upper()) duration_vectorized = time.time() — start_time print(f»Очистка категорий с помощью векторизации завершена за: {duration_vectorized:.4f} секунд») print(f»Использование памяти столбцом статуса категории: {mem_category:.2f} МБ») print(f»Ускорение: {duration_apply / duration_vectorized:.2f}x быстрее»)

Совокупный результат:

Очистка данных завершена за: 0,1213 секунды. Использование памяти столбца «Статус»: 53,64 МБ (первоначально 55,55 МБ). Векторизованная очистка категорий завершена за: 0,0003 секунды. Использование памяти столбца «Статус» категории: 0,95 МБ. Ускорение: в 407,83 раза быстрее.

Мы будем считать эти улучшения производительности успехом.

Когда столбец преобразуется в категорию, Pandas автоматически кодирует строки в целочисленные ключи (например, PENDING -> 0, COMPLETED -> 1).

  • Вместо хранения 1 000 000 строк, Pandas хранит 1 000 000 небольших целых чисел и крошечную карту из 4 фактических категорий строк. Это уменьшает объем используемой памяти с ~56 МБ до менее чем 1 МБ.
  • Очищая метки напрямую с помощью метода `.cat.rename_categories()`, Pandas выполняет строковые операции только над 4 уникальными категориями, вместо того чтобы перебирать 1 000 000 строк. Время выполнения сокращается практически до нуля.

Примечание: Если вы работаете с текстом высокой кардинальности (где значения редко повторяются), сохранение его в качестве категории не сэкономит память. В таких случаях следует избегать метода `.apply()` и использовать векторизованные строковые методы непосредственно для столбца объекта: `df['status'].str.strip().str.upper()`, который выполняется в скомпилированном C, а не в Python.

# 3. Групповая импутация и интерполяция с использованием функций groupby() и .transform()

Обработка пропущенных данных — это фундаментальный шаг в очистке данных. Во многих случаях замена пропущенных значений глобальным средним значением или константой вносит статистическую погрешность. Например, если вы заполняете пропущенную цену товара, использование глобальной средней цены всех товаров магазина будет неточным. Гораздо точнее будет заполнить пропущенные значения, используя среднюю цену конкретной категории товаров.

Наивный подход заключается в том, чтобы пройтись по категориям товаров в цикле, вычислить среднее значение группы, отфильтровать DataFrame, заполнить пропущенные значения и снова объединить группы. В качестве альтернативы, использование пользовательской функции внутри groupby().apply() запускает медленные циклы разделения-применения-объединения, которые плохо масштабируются.

Оптимальное решение — объединить метод groupby() с методом .transform().

Здесь мы имитируем заполнение пропущенных числовых значений цен (представленных NaN) с помощью цикла или пользовательской функции, передаваемой в метод `.apply()`:

import pandas as pd import numpy as np import time # Создание фиктивного каталога из 100 000 товаров, сгруппированных по категориям n_items = 100000 categories = [f»CAT_{i}» for i in range(100)] df = pd.DataFrame({ 'category': np.random.choice(categories, size=n_items), 'price': np.random.uniform(10.0, 500.0, size=n_items) }) # Введение 10% пропущенных цен (NaN) nan_mask = np.random.rand(n_items) < 0.1 df.loc[nan_mask, 'price'] = np.nan df_clunky = df.copy() start_time = time.time() # Разделение-применение-объединение с помощью apply() с пользовательской лямбда-функцией df_clunky['price'] = df_clunky.groupby('category')['price'].apply(lambda x: x.fillna(x.mean())).reset_index(level=0, drop=True) duration_clunky = time.time() - start_time print(f"Заполнение группы на основе применения заняло: {duration_clunky:.4f} секунд")

Используя метод `.transform()`, мы обходим пользовательские циклы лямбда-выражений и позволяем Pandas обрабатывать выравнивание индексов и векторизацию нативно:

import pandas as pd import numpy as np import time # Используем ту же настройку df_optimized = df.copy() start_time = time.time() # Оптимизированный подход с использованием преобразования group_means = df_optimized.groupby('category')['price'].transform('mean') df_optimized['price'] = df_optimized['price'].fillna(group_means) duration_opt = time.time() — start_time print(f»Групповая импутация на основе преобразования заняла: {duration_opt:.4f} секунд») print(f»Ускорение: {duration_clunky / duration_opt:.2f}x быстрее»)

Выход:

Групповая импутация на основе применения изменений заняла: 0,0224 секунды. Групповая импутация на основе преобразования заняла: 0,0032 секунды. Ускорение: в 7,04 раза быстрее.

Понимание принципа работы метода `.transform()` является ключом к написанию высокопроизводительного кода Pandas:

  • При выполнении команды `df.groupby('category')['price'].transform('mean')` Pandas вычисляет среднюю цену для каждой категории.
  • Вместо возврата уменьшенной сводной таблицы с группировкой, метод `.transform()` преобразует вычисленные значения обратно в размер и выравнивание исходного DataFrame. Он выводит серию точно такой же длины, как и исходный набор данных, где индекс i содержит среднее значение группы, к которой принадлежит строка i.
  • Затем мы можем использовать df['price'].fillna(group_means). Это заполнит пропущенные значения с помощью чистого, векторизованного, выровненного по индексу присваивания.

Этот шаблон очень универсален. Вы можете использовать его для стандартизации на уровне групп (например, вычитания средних значений групп) или для заполнения пропущенных значений для каждой группы с помощью: df.groupby('group')['val'].transform('ffill').

# Завершение

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

Давайте подведем итоги:

  • Методы цепочки заменяют ненадежные многострочные императивные изменения на читаемые декларативные последовательности обработки, которые полностью исключают предупреждение SettingWithCopyWarning.
  • Категориальное преобразование типов и векторизованные методы обработки строк оптимизируют структуру памяти и переносят преобразования строк на выполнение на скорости C, сокращая использование оперативной памяти до 98% при работе с данными низкой кардинальности.
  • Функция .transform(), учитывающая группировку данных , вычисляет статистику на уровне групп и выравнивает ее обратно в соответствии с исходными формами индекса, избегая медленных циклов пользовательской группировки.

Внедрение этих шаблонов в вашу повседневную работу сделает процессы проектирования признаков и очистки данных быстрыми, чистыми и легко поддерживаемыми.

Мэтью Мэйо ( @mattmayo13 ) имеет степень магистра компьютерных наук и диплом специалиста по анализу данных. Будучи главным редактором KDnuggets & Statology и внештатным редактором Machine Learning Mastery, Мэтью стремится сделать сложные концепции науки о данных доступными для всех. В сферу его профессиональных интересов входят обработка естественного языка, языковые модели, алгоритмы машинного обучения и изучение новых технологий искусственного интеллекта. Его движет стремление демократизировать знания в сообществе специалистов по науке о данных. Мэтью занимается программированием с 6 лет.

Источник: www.kdnuggets.com

Оцените материал:

Читайте также
Архив рубрики ~Обо всем~ На вашем iPhone 11 будет установлена iOS 27, но не будет работать Siri AI. Новости робототехники Новые искусственные глаза помогут роботам лучше видеть Архив рубрики ~Обо всем~ Компания OpenAI находится под следствием со стороны группы генеральных прокуроров штатов. Архив рубрики ~Обо всем~ Спутник научился самостоятельно находить объекты — вот что это значит. Архив рубрики ~Обо всем~ Сотрудник полиции графства Дербишир находится под следствием из-за «доказательных материалов», сгенерированных искусственным интеллектом. Архив рубрики ~Коротко из Telegram~ Владислав Бакальчук похвастался новыми цифрами роста маркетплейса М.Видео. По итогам… Архив рубрики ~Коротко из Telegram~ Бразилия внезапно ворвалась в число лидеров ИИ-гонки — представила Rio… Архив рубрики ~Коротко из Telegram~ Китайцы тихо выпустили GLM 5.2 — новую модель с открытым… Архив рубрики ~Коротко из Telegram~ MiniMax запустила Hub, локальную рабочую станцию, где ИИ агенты делают… Архив рубрики ~Коротко из Telegram~ Microsoft насторожилась из-за Claude Fable 5 У Microsoft возникла неприятная… Архив рубрики ~Коротко из Telegram~ Google нашла неприятную лазейку в споре с музыкантами Независимые музыканты… Архив рубрики ~Обо всем~ Платформа Asetek Emma V3 [Gen10] поддерживает установку на сокет Intel LGA1954 Архив рубрики ~Обо всем~ Компания Anthropic блокирует доступ всех пользователей к играм Fable 5 и Mythos 5. Архив рубрики ~Обо всем~ «Капсула времени: Как открытия XIX века преобразовали мир». Как у Чарльза Бэббиджа появилась идея вычислительной машины Архив рубрики ~Обо всем~ На вашем iPhone 11 будет установлена iOS 27, но не будет работать Siri AI. Новости робототехники Новые искусственные глаза помогут роботам лучше видеть Архив рубрики ~Обо всем~ Компания OpenAI находится под следствием со стороны группы генеральных прокуроров штатов. Архив рубрики ~Обо всем~ Спутник научился самостоятельно находить объекты — вот что это значит. Архив рубрики ~Обо всем~ Сотрудник полиции графства Дербишир находится под следствием из-за «доказательных материалов», сгенерированных искусственным интеллектом. Архив рубрики ~Коротко из Telegram~ Владислав Бакальчук похвастался новыми цифрами роста маркетплейса М.Видео. По итогам… Архив рубрики ~Коротко из Telegram~ Бразилия внезапно ворвалась в число лидеров ИИ-гонки — представила Rio… Архив рубрики ~Коротко из Telegram~ Китайцы тихо выпустили GLM 5.2 — новую модель с открытым… Архив рубрики ~Коротко из Telegram~ MiniMax запустила Hub, локальную рабочую станцию, где ИИ агенты делают… Архив рубрики ~Коротко из Telegram~ Microsoft насторожилась из-за Claude Fable 5 У Microsoft возникла неприятная… Архив рубрики ~Коротко из Telegram~ Google нашла неприятную лазейку в споре с музыкантами Независимые музыканты… Архив рубрики ~Обо всем~ Платформа Asetek Emma V3 [Gen10] поддерживает установку на сокет Intel LGA1954 Архив рубрики ~Обо всем~ Компания Anthropic блокирует доступ всех пользователей к играм Fable 5 и Mythos 5. Архив рубрики ~Обо всем~ «Капсула времени: Как открытия XIX века преобразовали мир». Как у Чарльза Бэббиджа появилась идея вычислительной машины

Оставить комментарий

Присоединяйтесь и подпишитесь на рассылку самых свежих новостей по Email

Получайте свежие новости и идеи на почту. Без спама — только самое интересное.

Нажимая «Подписаться», вы соглашаетесь с политикой конфиденциальности.