# Работа с данными: подготовка данных |![ Скетчноут от [(@sketchthedocs)](https://sketchthedocs.dev) ](../../sketchnotes/08-DataPreparation.png)| |:---:| |Подготовка данных - _Скетчноут от [@nitya](https://twitter.com/nitya)_ | ## [Тест перед лекцией](https://purple-hill-04aebfb03.1.azurestaticapps.net/quiz/14) В зависимости от источника, необработанные данные могут содержать несоответствия, которые создают сложности для анализа и моделирования. Другими словами, такие данные можно назвать "грязными", и их необходимо очистить. Этот урок посвящен техникам очистки и преобразования данных для решения проблем, связанных с отсутствующими, неточными или неполными данными. Темы, рассмотренные в этом уроке, будут использовать Python и библиотеку Pandas и будут [продемонстрированы в ноутбуке](notebook.ipynb) в этом каталоге. ## Важность очистки данных - **Удобство использования и повторного использования**: Когда данные правильно организованы и нормализованы, их легче искать, использовать и делиться ими с другими. - **Согласованность**: В области науки о данных часто приходится работать с несколькими наборами данных, которые необходимо объединить. Убедившись, что каждый отдельный набор данных имеет общую стандартизацию, можно гарантировать, что данные останутся полезными после их объединения в один набор. - **Точность моделей**: Очищенные данные повышают точность моделей, которые на них основаны. ## Общие цели и стратегии очистки - **Исследование набора данных**: Исследование данных, которое будет рассмотрено в [позднем уроке](https://github.com/microsoft/Data-Science-For-Beginners/tree/main/4-Data-Science-Lifecycle/15-analyzing), помогает выявить данные, которые нуждаются в очистке. Визуальное наблюдение за значениями в наборе данных позволяет установить ожидания относительно его структуры или определить проблемы, которые можно решить. Исследование может включать базовые запросы, визуализации и выборки. - **Форматирование**: В зависимости от источника данные могут иметь несоответствия в представлении. Это может создавать проблемы при поиске и отображении значений, когда они видны в наборе данных, но неправильно представлены в визуализациях или результатах запросов. Общие проблемы форматирования включают устранение пробелов, корректировку дат и типов данных. Решение таких проблем обычно лежит на пользователях данных. Например, стандарты представления дат и чисел могут различаться в разных странах. - **Дублирование**: Данные с несколькими повторениями могут приводить к неточным результатам и обычно должны быть удалены. Это часто случается при объединении двух или более наборов данных. Однако бывают случаи, когда дублирование содержит дополнительные сведения, которые могут быть полезны и должны быть сохранены. - **Отсутствующие данные**: Отсутствующие данные могут вызывать неточности, а также слабые или предвзятые результаты. Иногда их можно восстановить путем "перезагрузки" данных, заполнения пропущенных значений с помощью вычислений и кода, например, на Python, или просто удаления значения и соответствующих данных. Причины отсутствия данных могут быть различными, и действия по их восстановлению зависят от того, как и почему они были утрачены. ## Изучение информации о DataFrame > **Цель обучения:** К концу этого раздела вы должны уметь находить общую информацию о данных, хранящихся в pandas DataFrame. После загрузки данных в pandas они, скорее всего, будут представлены в виде DataFrame (см. предыдущий [урок](https://github.com/microsoft/Data-Science-For-Beginners/tree/main/2-Working-With-Data/07-python#dataframe) для подробного обзора). Однако если ваш DataFrame содержит 60,000 строк и 400 столбцов, как начать разбираться с такими данными? К счастью, [pandas](https://pandas.pydata.org/) предоставляет удобные инструменты для быстрого просмотра общей информации о DataFrame, а также первых и последних строк. Для изучения этой функциональности мы импортируем библиотеку Python scikit-learn и используем известный набор данных: **набор данных Iris**. ```python import pandas as pd from sklearn.datasets import load_iris iris = load_iris() iris_df = pd.DataFrame(data=iris['data'], columns=iris['feature_names']) ``` | |длина чашелистика (см)|ширина чашелистика (см)|длина лепестка (см)|ширина лепестка (см)| |----------------------------------------|-----------------------|-----------------------|-------------------|-------------------| |0 |5.1 |3.5 |1.4 |0.2 | |1 |4.9 |3.0 |1.4 |0.2 | |2 |4.7 |3.2 |1.3 |0.2 | |3 |4.6 |3.1 |1.5 |0.2 | |4 |5.0 |3.6 |1.4 |0.2 | - **DataFrame.info**: Для начала метод `info()` используется для вывода сводки содержимого, присутствующего в `DataFrame`. Давайте посмотрим на этот набор данных: ```python iris_df.info() ``` ``` RangeIndex: 150 entries, 0 to 149 Data columns (total 4 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 sepal length (cm) 150 non-null float64 1 sepal width (cm) 150 non-null float64 2 petal length (cm) 150 non-null float64 3 petal width (cm) 150 non-null float64 dtypes: float64(4) memory usage: 4.8 KB ``` Из этого мы узнаем, что набор данных *Iris* содержит 150 записей в четырех столбцах без пропущенных значений. Все данные хранятся в виде 64-битных чисел с плавающей точкой. - **DataFrame.head()**: Далее, чтобы проверить фактическое содержимое `DataFrame`, мы используем метод `head()`. Давайте посмотрим, как выглядят первые несколько строк нашего `iris_df`: ```python iris_df.head() ``` ``` sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) 0 5.1 3.5 1.4 0.2 1 4.9 3.0 1.4 0.2 2 4.7 3.2 1.3 0.2 3 4.6 3.1 1.5 0.2 4 5.0 3.6 1.4 0.2 ``` - **DataFrame.tail()**: Напротив, чтобы проверить последние несколько строк `DataFrame`, мы используем метод `tail()`: ```python iris_df.tail() ``` ``` sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) 145 6.7 3.0 5.2 2.3 146 6.3 2.5 5.0 1.9 147 6.5 3.0 5.2 2.0 148 6.2 3.4 5.4 2.3 149 5.9 3.0 5.1 1.8 ``` > **Вывод:** Даже просто взглянув на метаданные о содержимом DataFrame или на первые и последние значения, можно сразу получить представление о размере, форме и содержании данных, с которыми вы работаете. ## Работа с отсутствующими данными > **Цель обучения:** К концу этого раздела вы должны знать, как заменять или удалять пропущенные значения из DataFrame. Чаще всего наборы данных, которые вы хотите использовать (или вынуждены использовать), содержат пропущенные значения. То, как вы справляетесь с отсутствующими данными, имеет тонкие компромиссы, которые могут повлиять на ваш окончательный анализ и реальные результаты. Pandas обрабатывает пропущенные значения двумя способами. Первый вы уже видели в предыдущих разделах: `NaN`, или Not a Number. Это специальное значение, являющееся частью спецификации IEEE для чисел с плавающей точкой, и оно используется только для указания отсутствующих значений с плавающей точкой. Для пропущенных значений, отличных от чисел с плавающей точкой, pandas использует объект Python `None`. Хотя может показаться запутанным, что вы сталкиваетесь с двумя различными типами значений, которые говорят практически одно и то же, есть веские программные причины для такого выбора, и на практике это позволяет pandas предложить хороший компромисс для большинства случаев. Тем не менее, и `None`, и `NaN` имеют ограничения, которые необходимо учитывать в отношении их использования. Узнайте больше о `NaN` и `None` из [ноутбука](https://github.com/microsoft/Data-Science-For-Beginners/blob/main/4-Data-Science-Lifecycle/15-analyzing/notebook.ipynb)! - **Обнаружение пропущенных значений**: В `pandas` методы `isnull()` и `notnull()` являются основными для обнаружения пропущенных данных. Оба возвращают булевы маски для ваших данных. Мы будем использовать `numpy` для значений `NaN`: ```python import numpy as np example1 = pd.Series([0, np.nan, '', None]) example1.isnull() ``` ``` 0 False 1 True 2 False 3 True dtype: bool ``` Внимательно посмотрите на вывод. Вас что-то удивило? Хотя `0` является арифметическим нулем, он, тем не менее, является вполне допустимым целым числом, и pandas рассматривает его как таковое. `''` немного сложнее. Хотя мы использовали его в разделе 1 для представления пустого строкового значения, он, тем не менее, является строковым объектом, а не представлением null с точки зрения pandas. Теперь давайте перевернем это и используем эти методы так, как вы будете использовать их на практике. Вы можете использовать булевы маски непосредственно как индекс ``Series`` или ``DataFrame``, что может быть полезно при работе с отдельными пропущенными (или присутствующими) значениями. > **Вывод:** Методы `isnull()` и `notnull()` дают схожие результаты при использовании в `DataFrame`: они показывают результаты и индекс этих результатов, что значительно поможет вам в работе с данными. - **Удаление пропущенных значений**: Помимо идентификации пропущенных значений, pandas предоставляет удобный способ удаления null-значений из `Series` и `DataFrame`. (Особенно для больших наборов данных часто более целесообразно просто удалить пропущенные значения [NA] из анализа, чем обрабатывать их другими способами.) Чтобы увидеть это в действии, вернемся к `example1`: ```python example1 = example1.dropna() example1 ``` ``` 0 0 2 dtype: object ``` Обратите внимание, что это должно выглядеть как ваш вывод из `example3[example3.notnull()]`. Разница здесь в том, что вместо индексации по маскированным значениям `dropna` удалил эти пропущенные значения из `Series` `example1`. Поскольку `DataFrame` имеет две измерения, он предоставляет больше возможностей для удаления данных. ```python example2 = pd.DataFrame([[1, np.nan, 7], [2, 5, 8], [np.nan, 6, 9]]) example2 ``` | | 0 | 1 | 2 | |------|---|---|---| |0 |1.0|NaN|7 | |1 |2.0|5.0|8 | |2 |NaN|6.0|9 | (Вы заметили, что pandas преобразовал два столбца в числа с плавающей точкой, чтобы учесть `NaN`?) Вы не можете удалить одно значение из `DataFrame`, поэтому вам придется удалять целые строки или столбцы. В зависимости от того, что вы делаете, вы можете захотеть сделать одно или другое, и pandas предоставляет вам варианты для обоих. Поскольку в науке о данных столбцы обычно представляют переменные, а строки — наблюдения, вы, скорее всего, будете удалять строки данных; настройка по умолчанию для `dropna()` — удаление всех строк, содержащих любые null-значения: ```python example2.dropna() ``` ``` 0 1 2 1 2.0 5.0 8 ``` Если необходимо, вы можете удалить значения NA из столбцов. Используйте `axis=1`, чтобы сделать это: ```python example2.dropna(axis='columns') ``` ``` 2 0 7 1 8 2 9 ``` Обратите внимание, что это может удалить много данных, которые вы, возможно, захотите сохранить, особенно в небольших наборах данных. Что если вы хотите удалить только строки или столбцы, содержащие несколько или даже все null-значения? Вы можете указать эти настройки в `dropna` с помощью параметров `how` и `thresh`. По умолчанию `how='any'` (если вы хотите проверить это самостоятельно или увидеть, какие еще параметры есть у метода, выполните `example4.dropna?` в ячейке кода). Вы можете, например, указать `how='all'`, чтобы удалять только строки или столбцы, содержащие все null-значения. Давайте расширим наш пример `DataFrame`, чтобы увидеть это в действии. ```python example2[3] = np.nan example2 ``` | |0 |1 |2 |3 | |------|---|---|---|---| |0 |1.0|NaN|7 |NaN| |1 |2.0|5.0|8 |NaN| |2 |NaN|6.0|9 |NaN| Параметр `thresh` дает вам более тонкий контроль: вы устанавливаете количество *не-null* значений, которые строка или столбец должны иметь, чтобы быть сохраненными: ```python example2.dropna(axis='rows', thresh=3) ``` ``` 0 1 2 3 1 2.0 5.0 8 NaN ``` Здесь первая и последняя строки были удалены, так как они содержат только два не-null значения. - **Заполнение null-значений**: В зависимости от вашего набора данных иногда имеет смысл заполнить null-значения допустимыми, а не удалять их. Вы могли бы использовать `isnull` для этого на месте, но это может быть трудоемким, особенно если у вас много значений для заполнения. Поскольку это такая распространенная задача в науке о данных, pandas предоставляет `fillna`, который возвращает копию `Series` или `DataFrame` с заменой пропущенных значений на выбранные вами. Давайте создадим еще один пример `Series`, чтобы увидеть, как это работает на практике. ```python example3 = pd.Series([1, np.nan, 2, None, 3], index=list('abcde')) example3 ``` ``` a 1.0 b NaN c 2.0 d NaN e 3.0 dtype: float64 ``` Вы можете заполнить все пропущенные значения одним значением, например, `0`: ```python example3.fillna(0) ``` ``` a 1.0 b 0.0 c 2.0 d 0.0 e 3.0 dtype: float64 ``` Вы можете **заполнить вперед** null-значения, используя последнее допустимое значение для заполнения null: ```python example3.fillna(method='ffill') ``` ``` a 1.0 b 1.0 c 2.0 d 2.0 e 3.0 dtype: float64 ``` Вы также можете **заполнить назад**, чтобы распространить следующее допустимое значение назад для заполнения null: ```python example3.fillna(method='bfill') ``` ``` a 1.0 b 2.0 c 2.0 d 3.0 e 3.0 dtype: float64 ``` Как вы могли догадаться, это работает так же с `DataFrame`, но вы также можете указать `axis`, вдоль которого нужно заполнить null-значения. Возьмем снова ранее использованный `example2`: ```python example2.fillna(method='ffill', axis=1) ``` ``` 0 1 2 3 0 1.0 1.0 7.0 7.0 1 2.0 5.0 8.0 8.0 2 NaN 6.0 9.0 9.0 ``` Обратите внимание, что если предыдущее значение недоступно для заполнения вперед, null-значение остается. > **Основная мысль:** Существует множество способов работы с пропущенными значениями в ваших наборах данных. Конкретная стратегия (удаление, замена или способ замены) должна определяться особенностями данных. Чем больше вы работаете с наборами данных, тем лучше вы будете понимать, как справляться с пропущенными значениями. ## Удаление дублированных данных > **Цель обучения:** К концу этого раздела вы должны уверенно определять и удалять дублированные значения из DataFrame. Помимо пропущенных данных, в реальных наборах данных вы часто будете сталкиваться с дублированными данными. К счастью, `pandas` предоставляет простой способ обнаружения и удаления дублированных записей. - **Определение дубликатов: `duplicated`**: Вы можете легко обнаружить дублированные значения с помощью метода `duplicated` в pandas, который возвращает булеву маску, указывающую, является ли запись в `DataFrame` дубликатом более ранней записи. Давайте создадим еще один пример `DataFrame`, чтобы увидеть это в действии. ```python example4 = pd.DataFrame({'letters': ['A','B'] * 2 + ['B'], 'numbers': [1, 2, 1, 3, 3]}) example4 ``` | |letters|numbers| |------|-------|-------| |0 |A |1 | |1 |B |2 | |2 |A |1 | |3 |B |3 | |4 |B |3 | ```python example4.duplicated() ``` ``` 0 False 1 False 2 True 3 False 4 True dtype: bool ``` - **Удаление дубликатов: `drop_duplicates`:** просто возвращает копию данных, для которых все значения `duplicated` равны `False`: ```python example4.drop_duplicates() ``` ``` letters numbers 0 A 1 1 B 2 3 B 3 ``` Оба метода, `duplicated` и `drop_duplicates`, по умолчанию учитывают все столбцы, но вы можете указать, чтобы они проверяли только определенный набор столбцов в вашем `DataFrame`: ```python example4.drop_duplicates(['letters']) ``` ``` letters numbers 0 A 1 1 B 2 ``` > **Основная мысль:** Удаление дублированных данных — это важная часть практически каждого проекта в области анализа данных. Дублированные данные могут исказить результаты вашего анализа и привести к неточным выводам! ## 🚀 Задание Все обсуждаемые материалы предоставлены в формате [Jupyter Notebook](https://github.com/microsoft/Data-Science-For-Beginners/blob/main/2-Working-With-Data/08-data-preparation/notebook.ipynb). Кроме того, после каждого раздела есть упражнения — попробуйте их выполнить! ## [Тест после лекции](https://purple-hill-04aebfb03.1.azurestaticapps.net/quiz/15) ## Обзор и самостоятельное изучение Существует множество способов изучения и подходов к подготовке данных для анализа и моделирования, а очистка данных — это важный этап, который требует практического подхода. Попробуйте эти задания на Kaggle, чтобы изучить техники, которые не были рассмотрены в этом уроке. - [Data Cleaning Challenge: Parsing Dates](https://www.kaggle.com/rtatman/data-cleaning-challenge-parsing-dates/) - [Data Cleaning Challenge: Scale and Normalize Data](https://www.kaggle.com/rtatman/data-cleaning-challenge-scale-and-normalize-data) ## Задание [Оценка данных из формы](assignment.md) --- **Отказ от ответственности**: Этот документ был переведен с использованием сервиса автоматического перевода [Co-op Translator](https://github.com/Azure/co-op-translator). Несмотря на наши усилия обеспечить точность, автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его исходном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникшие в результате использования данного перевода.