Узнайте, как очищать неструктурированные CSV-файлы, обобщать данные и визуализировать основные тенденции в продажах в электронной коммерции.
Делиться

Привет всем! Добро пожаловать в начало масштабного путешествия в мир данных, которое я называю «EDA в открытом доступе». Те, кто меня знает, подтвердят, что лучший способ чему-либо научиться — это решить реальную задачу и поделиться всем сложным процессом, включая ошибки, победы и всё остальное. Если вы хотите повысить свой уровень владения Pandas и навыками анализа данных, эта серия для вас.
Мы будем выступать в роли аналитиков данных для вымышленной, средней по размеру компании электронной коммерции, которую я назову NovaShop . Они передали нам необработанный, неряшливый CSV-файл с данными о продажах и задали простой вопрос: «Как у нас дела?»
Цель первой части — заложить фундаментальные основы: мы очистим этот сложный набор данных по электронной коммерции, изучим его базовую структуру и освоим основные навыки разведочного анализа данных (EDA) в Pandas, которые ежедневно использует каждый специалист по анализу данных. Эта серия построена таким образом, чтобы вы прошли путь от новичка (часть 1) до продвинутого аналитика данных (часть 3), поэтому смело начинайте с любого уровня.
Прежде чем перейти к коду, давайте определим нашу основную мотивацию. Для NovaShop нам нужно ответить на несколько простых, но важных вопросов: Какие товары приносят наибольший доход? В каких странах наблюдается наибольший объем продаж? Давайте выясним.
Обзор набора данных: анализ данных о продажах
Для начала анализа данных по NovaShop мы будем использовать набор данных UCI Online Retail Dataset . Это отличный, очень реалистичный, неоптимизированный набор данных, который охватывает все транзакции британской онлайн-компании, не имеющей магазинов, в период с конца 2010 по конец 2011 года.
Данный набор данных распространяется под лицензией Creative Commons Attribution 4.0 International (CC BY 4.0).
Это позволяет обмениваться наборами данных и адаптировать их для любых целей при условии указания соответствующего авторства.
Набор данных содержит более полумиллиона строк и полон аномалий, встречающихся в реальном мире — пропущенные значения, отрицательные числа и непоследовательное форматирование текста. Это именно то, что нам нужно!
Вот восемь ключевых столбцов, с которыми мы будем работать, и что они нам говорят с точки зрения бизнеса:
- Номер счета: Это Номер счета-фактуры. Уникальный 6-значный номер, присваиваемый каждой транзакции. Если код начинается с буквы «C», это означает аннулирование (возврат).
- Код товара/артикул : уникальный 5-значный код, присваиваемый каждому отдельному товару.
- Описание: Название предмета. Часто требует исправления (лишние пробелы, несоответствие регистра).
- Количество: число приобретенных товаров. Сколько единиц товара было в каждой транзакции? Может быть отрицательным для случаев возврата.
- InvoiceDate: Дата и время транзакции. Это необходимо для последующего анализа временных рядов.
- Цена за единицу товара: Цена товара в фунтах стерлингов (GBP). Цена одного товара. Иногда может быть равна 0 или быть отрицательной из-за ошибок/бесплатных товаров.
- Идентификатор клиента (CustomerID): уникальный 5-значный номер, присваиваемый каждому зарегистрированному клиенту. Важно отметить, что он часто отсутствует, а это значит, что у нас много транзакций от гостей.
- Страна: Название страны, в которой проживает клиент. Это отлично подойдет для сегментации международных продаж.
Давайте быстро взглянем на первые несколько строк, чтобы понять, с чем мы имеем дело. Это результат выполнения функции df.head():
Давайте импортируем набор данных в Pandas и посмотрим, сколько строк мы имеем дело.
import pandas as pd import numpy as np df = pd.read_csv('Online Retail.csv') df.shape
Выход:
(541909, 8)
Это довольно много строк. Возможно, мне придётся немного разделить строки.
Загрузка и сегментация данных: работа с большими объемами.
В первой части мы возьмем случайную выборку в 10% от полного набора данных. Это позволит нам получить гораздо более управляемый размер — около 54 000 строк — при сохранении характеристик исходных данных. Это распространенный и практичный метод при работе с большими данными или при прототипировании.
# Загрузка и нарезка данных FILE_PATH = 'Online Retail.csv' SAMPLE_FRACTION = 0.1 # Мы выберем 10% данных full_df = pd.read_csv(FILE_PATH, encoding='unicode_escape') # Выбираем случайную выборку в 10% для более быстрой обработки в Части 1 df = full_df.sample(frac=SAMPLE_FRACTION, random_state=42).reset_index(drop=True)
Теперь давайте еще раз посмотрим на форму, используя df.shape.
Выход:
(54191, 8)
Отлично! Теперь можем начать.
Первичный анализ данных: беремся за дело.
Первым делом мне нужно выяснить, что именно мы будем убирать. Мне нужно ответить на следующие вопросы.
- Что содержит этот набор данных?
- Какие существуют типы данных и какие значения ненулевые?
- Как выглядят эти цифры?
Визуальная проверка: df.head() и df.tail()
Проанализировав первую и последние несколько строк, я смог подтвердить несколько вещей:
- Отмены и возвраты: Я заметил, что некоторые значения InvoiceNo начинаются с буквы 'C' , а соответствующее количество отрицательное. Это подтверждает, что возвраты включены в анализ, и для анализа доходов мне нужно будет их отделить или исключить.
- Отсутствие данных: В столбце CustomerID я визуально увидел значения NaN, что подтверждает большое количество транзакций с гостями.
- Несогласованный текст: Я проверил столбец «Описание» на наличие пробелов. Судя по небольшому образцу, который я получил, точно сказать не могу. Но не помешает исправить их при очистке данных. Возможно, я также сохраню единообразие регистра букв. Всегда рекомендуется удалять все начальные и конечные пробелы из всех строковых столбцов, чтобы предотвратить неочевидные ошибки при группировке.
- Я также заметил некоторые несоответствия в написании заглавных букв в столбце «Страна». Я обнаружил страну с названием EIRE. Вероятно, это означает Ирландию, возможно, нужно это исправить.
Какие существуют типы данных и значения, отличные от Null? (.info())
Следующий важный шаг — проверка структуры с помощью метода `.info()`. Это позволяет определить тип данных каждого столбца и, что наиболее важно, узнать, сколько ненулевых (не пропущенных) значений у нас есть.
Основные выводы
- Отсутствующие значения: После анализа 10% выборки я обнаружил огромный пробел в CustomerID. Примерно 25% идентификаторов клиентов отсутствуют. Я также заметил несколько сотен отсутствующих значений в Description, которые мне нужно будет исправить.
- Типы данных: Столбец InvoiceDate по-прежнему указан как объект (строка). Необходимо преобразовать его в корректный объект Pandas datetime. Столбец CustomerID также в настоящее время имеет тип float , вероятно, потому что содержит значения NaN! Это небольшая деталь, которую мне нужно будет помнить, если я когда-нибудь захочу использовать его как настоящий целочисленный идентификатор.
Как выглядят эти числа? (.describe())
Далее я использовал метод `.describe()` для получения краткой статистической сводки по всем числовым столбцам (Quantity и UnitPrice).
- Количество (Quantity) : Минимальное количество составляет -2472 . Это подтверждает наличие возвратов, но масштаб этих минимальных значений указывает на экстремальный выброс. Для базового анализа продаж мне, возможно, потребуется отфильтровать эти отрицательные числа, а также, возможно, экстремальные положительные и отрицательные выбросы.
- Цена за единицу (UnitPrice) : Минимальная цена равна 0. Это означает, что некоторые товары были предоставлены бесплатно или являются временными значениями. Поскольку в обычных продажах цена товара должна быть положительной, для точного расчета выручки всегда рекомендуется отфильтровывать строки, где UnitPrice равен нулю или меньше.
На основании проведенных экспресс-проверок. Эти данные далеки от идеала. У нас огромное количество пропущенных значений, некорректные типы данных, крайне проблематичные отрицательные/нулевые значения в основных числовых столбцах, а также текстовые несоответствия, которые необходимо устранить.
Обработка пропущенных значений
Теперь, когда мы знаем, где отсутствуют данные, мы можем начать обсуждать стратегии обработки нулевых значений. Меня больше всего интересуют поля Description и CustomerID.
Отсутствующие описания
Описание указывает на то, какой товар был продан, поэтому его отсутствие делает транзакцию бессмысленной. В нашей выборке менее 1% строк имеют отсутствующие описания. Полное удаление этих строк имеет смысл, поскольку они бесполезны для анализа на уровне товара.
# Удаляем строки, в которых отсутствует описание df.dropna(subset=['Description'], inplace=True) # Проверка на наличие нулевых значений df['Description'].isnull().sum()
Выход:
np.int64(0)
Отлично! Все нулевые значения исчезли.
Отсутствуют идентификаторы клиентов
Отсутствие идентификаторов клиентов (около 25% нашей выборки) — гораздо более серьезная проблема. Если я удалю все эти идентификаторы, я потеряю почти четверть данных о продажах, что приведет к опасному искажению представления NovaShop о ее общей выручке.
Для простого анализа доходов и продукции (цель части 1) мне на самом деле не нужен CustomerID. Мы можем продолжить анализ по всем строкам даже без ID. ID нужен только в том случае, если мы планируем сегментацию клиентов (например, RFM-анализ), о чем я расскажу в части 2!
Преобразование типов данных и удаление дубликатов.
Теперь, когда мы разобрались с отсутствующими описаниями, следующие две неотложные задачи — это устранение полностью дублирующихся строк и исправление нашего важного столбца InvoiceDate.
Исправление типа даты счета-фактуры
Помните, как метод `.info()` отображал `InvoiceDate` как строку (объект)? Нам нужно немедленно это исправить, чтобы Pandas знал, как сортировать и агрегировать наши данные в хронологическом порядке.
# Преобразование InvoiceDate из объекта (строки) в дату и время df['InvoiceDate'] = pd.to_datetime(df['InvoiceDate'])
Удаление дубликатов
Если две строки идентичны во всех столбцах, они являются дубликатами и искусственно завышают наши показатели продаж. Давайте проверим их и удалим.
# Удаление дубликатов num_duplicates = df.duplicated().sum() print(f»Найдено {num_duplicates} полностью дублирующихся строк.»)
Выход:
Обнаружено 62 полностью повторяющиеся строки.
Давайте их бросим
# Удаляем дубликаты, сохраняя первый экземпляр df.drop_duplicates(inplace=True) print(f»Размер DataFrame после удаления дубликатов: {len(df)} строк.»)
Выход:
Размер датафрейма после удаления дубликатов: 53980 строк.
Отфильтровывание возвратов и ошибок
Наш метод `.describe()` выявил несколько серьезных проблемных моментов: отрицательные количества (возвраты) и нулевые/отрицательные цены за единицу (ошибки/бесплатные товары). Для первой части нам нужно рассчитать чистую выручку от продаж , поэтому мы должны отфильтровать этот шум.
Обработка отрицательных объемов и цен
Мы будем хранить только те транзакции, в которых:
- Количество строго больше нуля (без учета возвратов и отмен).
- Значение UnitPrice строго больше нуля (для отсеивания ошибок и бесплатных товаров).
# Фильтр: Оставить только транзакции, где Quantity положительное (т. е. продажи, а не возвраты) df = df[df['Quantity'] > 0] # Фильтр: Оставить только транзакции, где UnitPrice положительное (т. е. не бесплатно или ошибка) df = df[df['UnitPrice'] > 0] print(f»Размер DataFrame после фильтрации: {len(df)} строк.»)
Выход
Размер датафрейма после фильтрации: 52933 строки.
Очистка текстовых столбцов
Наконец, давайте разберемся с проблемами стандартизации текста, которые мы обнаружили визуально, такими как код страны EIRE и любые потенциальные скрытые пробелы в поле «Описание».
- Удаление пробелов: Мы используем .str.strip() для удаления начальных и конечных пробелов как в описании, так и в названии страны.
- Стандартизация страны: Мы вручную сопоставляем несоответствия, такие как «EIRE» с «Ireland».
# Очистка текстовых столбцов # Очистка столбцов «Описание» и «Страна» df['Description'] = df['Description'].str.strip() df['Country'] = df['Country'].str.strip() # Обработка несоответствий в названиях конкретных стран # EIRE — распространенное несоответствие в этом наборе данных для Ирландии df['Country'].replace('EIRE', 'Ireland', inplace=True) print(“Текстовые столбцы очищены и стандартизированы.”)
Разработка функциональных возможностей и первоначальный анализ
Теперь данные очищены. Наконец-то мы можем начать задавать интересные вопросы. Самый важный показатель для любого набора данных о продажах — это выручка. Поскольку в наших исходных данных есть только «Количество» и «Цена за единицу», нам нужно самостоятельно сформировать столбец «Выручка» .
Разработка функциональных возможностей: создание колонки доходов
Выручка от сделки — это просто количество проданных товаров, умноженное на цену каждого товара.
df['Revenue'] = df['Quantity'] * df['UnitPrice']
Первые результаты: Какие страны обеспечивают наибольший доход?
Давайте используем наши очищенные данные, чтобы ответить на вопрос NovaShop: «В каких странах сосредоточены наши продажи?»
Мы воспользуемся мощной трехэтапной комбинацией:
- Сгруппируйте по столбцу «Страна».
- Суммируйте выручку в каждой группе.
- Отсортируйте результаты от наибольшего дохода к наименьшему.
# Группировка по странам, суммирование выручки и сортировка по 10 лучшим странам top_countries = df.groupby('Country')['Revenue'].sum().sort_values(ascending=False).head(10) print(“n — — Топ-10 стран по выручке (GBP) — -”) print(top_countries)
Выход:
— Топ-10 стран по доходам (фунты стерлингов) — Страна Великобритания 941268.661 Нидерланды 27435.830 Ирландия 26066.000 Франция 23645.330 Германия 22389.510 Австралия 12429.990 Испания 5600.900 Швейцария 5483.890 Гонконг 3597.850 Бельгия 3593.510 Название: Доход, тип данных: float64
Результаты показывают типичное доминирование Великобритании . Это ожидаемо, поскольку компания базируется в Великобритании. Следующие несколько стран дают нам краткий план того, на чем должна быть сосредоточена международная деятельность NovaShop.
Заключение
Мы это сделали. Мы взяли исходный набор данных, содержащий полмиллиона строк, разделили его на части для удобства управления, поработали с пропущенными значениями, исправили типы данных, отфильтровали ошибки и рассчитали наш первый ключевой бизнес-показатель. Самая сложная, фундаментальная работа завершена.
Вот краткий обзор того, чего мы достигли в первой части:
- Проверка данных: Мы использовали методы .head(), .info() и .describe() для выявления важных проблем, таких как отрицательные значения цен/количества, отсутствующие идентификаторы клиентов и некорректный формат даты и времени.
- Очистка данных: Мы систематически удаляли значения NULL и дубликаты, преобразовывали InvoiceDate в корректный объект типа datetime и отфильтровывали транзакции, не связанные с продажами (возвраты и бесплатные товары).
- Разработка функционала: Мы создали важнейший столбец «Доходы».
- Первые результаты: Мы составили список 10 стран с наибольшим доходом для NovaShop, предоставив им первые полезные данные из исходного набора.
Очищенный набор данных теперь готов для более сложного анализа. Во второй части мы углубимся в анализ продукции и разложение временных рядов. Мы выясним, какие товары действительно приносят прибыль, и проанализируем, как меняется объем продаж по часам и по месяцам.
Я с нетерпением жду продолжения! Если вам понравилась эта статья, пожалуйста, дайте мне знать на любом из этих каналов. Ваши отзывы будут для меня очень важны.
Середина
Твиттер
YouTube
Источник: towardsdatascience.com























