Привет, Хабр!
Аналитики данных часто сталкиваются с грязными данными, которые могут существенно замедлить процесс анализа. Грязны данные – это пропущенные значения, дубликаты, неконсистентные данные. Пропущенные значения заставляют нас гадать, что же было замыслено нашим коллегой; дубликаты вводят в заблуждение, умножая одно и то же на количество их копий, а неконсистентные данные заставляют нас сомневаться в каждой цифре.
Очищать грязные данные можно c Pandas. Рассмотрим основные методы.
Пропущенные значения
Пропущенные значения могут возникать по разным причинам. Вне зависимости от причины, сначала их нужно обнаружить.
isnull()
в Pandas возвращает DataFrame или Series с булевыми значениями, где True
указывает на пропущенное значение. Аналогично, notnull()
возвращает True
для элементов, где значение присутствует:
# удаляем строки, где есть хотя бы одно пропущенное значение
cleaned_df = df.dropna()
# заполняем все пропущенные значения нулями
filled_df = df.fillna(0)
print("Очищенный DataFrame:\n", cleaned_df)
prit("DataFrame с заполненными пропусками:\n", filled_df)
Код выведет таблицу, где True
обозначает пропущенные значения.
- Что с ними делать дальше? Один из подходов – удаление строк или столбцов с пропусками, но это может привести к потере какой-то информации. Другой подход – заполнение пропусков.
Для заполнения пропусков используем метод fillna()
:
# заполняем пропуски средним значением по столбцам
df.fillna(df.mean(), inplace=True)
print("df с заполненными пропусками средним:\n", df)
Для категориальных данных может быть разумно заполнить пропуски наиболее часто встречающимся значением:
# заполнение пропусков в зависимости от условий
df['A'].fillna(value=df['A'].mean(), inplace=True)
df['B'].fillna(value=df['B'].median(), inplace=True)
# кастомная функция для заполнения
def custom_fill(series):
return series.fillna(series.mean())
df.apply(custom_fill)
Удаление с помощью dropna()
По умолчанию dropna()
удаляет строки, которые содержат хотя бы одно пропущенное значение:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'Name': ['ivan', 'artem', np.nan, 'diana', 'eva'],
'Age': [25, np.nan, np.nan, 27, 28],
'City': ['New York', 'Los Angeles', 'Chicago', np.nan, 'Miami']
})
# удаляем строки с пропущенными значениями
df_cleaned = df.dropna()
print(df_cleaned)
dropna()
удаляет все строки, где есть хотя бы одно NaN
.
Чтобы удалить столбцы, содержащие пропущенные значения можно юзать параметр axis=1
:
df_cleaned_columns = df.dropna(axis=1)
print(df_cleaned_columns)
Можно удалить строки, где все значения пропущены с помощью параметра how='all'
:
# добавляем строку, полностью состоящую из NaN
df.loc[5] = [np.nan, np.nan, np.nan]
# удаляем строки, где все значения являются NaN
df_cleaned_all = df.dropna(how='all')
print(df_cleaned_all)
Можно указать столбцы, для которых должна быть применена проверка на NaN
, используя параметр subset
.
# удаляем строки, где пропущены значения в определенных столбцах
df_cleaned_subset = df.dropna(subset=['Name', 'City'])
print(df_cleaned_subset)
В этом случае будут удалены только те строки, в которых отсутствуют значения в столбцах Name или City
thresh
позволяет указать минимальное количество непропущенных значений, при котором строка или столбец сохраняется:
# удаляем строки, где менее двух непропущенных значений
df_cleaned_thresh = df.dropna(thresh=2)
print(df_cleaned_thresh)
Кастомные варианты
Часто юзаются для числовых данных, чтобы минимизировать влияние пропусков на распределение данных.
Ср. значение:
import pandas as pd
import numpy as np
df = pd.DataFrame({
'A': [1, 2, np.nan, 4, 5],
'B': [np.nan, 2, 3, np.nan, 5],
'C': [1, 2, 3, 4, np.nan]
})
# заполняем пропущенные значения в столбце A средним значением по этому столбцу
df['A'].fillna(df['A'].mean(), inplace=True)
Медиана
Медиана может быть предпочтительнее среднего значения в случаях, когда данные содержат выбросы, которые могут исказить среднее:
# фулим пропущенные значения в столбце B медианой
df['B'].fillna(df['B'].median(), inplace=True)
Мода
Для категориальных данных или данных с дискретными значениями можно использовать моду:
# C - категориальный столбец, заполняем пропущенные значения модой
df['C'].fillna(df['C'].mode()[0], inplace=True)
Можно применять произвольные функции для более сложных заполнений. Например, можно заполнить пропуски значениями, основанными на других столбцах данных:
# Заполняем пропущенные значения в столбце A, используя кастомную логику
df['A'].fillna(df.apply(lambda row: row['B'] if pd.isnull(row['A']) else row['A'], axis=1), inplace=True)
Когда заполнение должно учитывать группировку по какому-либо признаку, можно использовать apply()
или transform()
совместно с groupby()
:
# допустим, есть столбец группы, и мы хотим заполнить пропуски средним значением внутри каждой группы
df['group'] = ['X', 'X', 'Y', 'Y', 'Z']
df['A'] = df.groupby('group')['A'].transform(lambda x: x.fillna(x.mean()))
А что насчет дупликатов?
Дубликаты могут могут быть как полностью идентичными записями, так и частичными дубликатами, когда совпадают только некоторые поля. В любом случае, дубликаты вносят шум в данные, увеличивают их объем и могут привести к неверным аналитическим выводам.
drop_duplicates()
позволяет не только удалять полные дубликаты строк, но и предоставляет настройки для работы с частичными дубликатами, удалим полные дупликаты:
import pandas as pd
df = pd.DataFrame({
'A': [1, 2, 2, 3, 3, 3],
'B': ['a', 'b', 'b', 'c', 'c', 'c'],
'C': [100, 200, 200, 300, 300, 300]
})
# удаляем дубликаты
df_unique = df.drop_duplicates()
print(df_unique)
Код удалит все полные дубликаты строк, оставив уникальные комбинации значений по всем столбцам.
Часто нужно удалять дубликаты, основываясь не на всех столбцах, а только на некоторых. Для этого в drop_duplicates()
есть параметр subset
:
# удаляем дубликаты, основываясь только на столбцах 'A' и 'B'
df_unique_subset = df.drop_duplicates(subset=['A', 'B'])
print(df_unique_subset)
keep
позволяет контролировать, какие дубликаты будут удалены: первый, последний или все:
# Удаляем все дубликаты, кроме последнего вхождения
df_keep_last = df.drop_duplicates(keep='last')
print(df_keep_last)
Можно использовать комбинацию методов Pandas для предварительной обработки данных перед удалением дубликатов. Например, можно привести все строки к нижнему регистру, удалить пробелы, преобразовать даты в единый формат и т.д:
# преобразуем все строки в нижний регистр и удаляем пробелы
df['B'] = df['B'].str.lower().str.replace(' ', '')
# теперь удаляем дубликаты
df_preprocessed = df.drop_duplicates()
print(df_preprocessed)
Категориальные данные
get_dummies()
используется для преобразования категориальных переменных в фиктивные/индикаторные переменные, кстати в ml юзается довольно часто:
import pandas as pd
df = pd.DataFrame({
'Color': ['Red', 'Green', 'Blue', 'Green']
})
# преобразуем категориальные данные в индикаторные переменные
dummies = pd.get_dummies(df['Color'])
print(dummies)
В результате каждое уникальное значение в столбце Color
превращается в отдельный столбец с индикаторными переменными (0 или 1), указывающими на присутствие или отсутствие данной категории в каждой строке.
factorize()
используется для получения числового представления категориальных данных, присваивая каждой уникальной категории целочисленный идентификатор:
# получаем числовое представление категориальных данных
codes, uniques = pd.factorize(df['Color'])
print(codes) # массив кодов
print(uniques) # массив уникальных значений
str
-методы позволяют выполнять такие операции, как преобразование регистра, поиск и замена подстрок, разбиение строк и т.п:
df_text = pd.DataFrame({
'Text': ['This is a test.', 'Hello, World!', 'Otus!']
})
# преобразуем все тексты в нижний регистр
df_text['Text_lower'] = df_text['Text'].str.lower()
# удаляем знаки пунктуации
df_text['Text_clean'] = df_text['Text'].str.replace('[^\w\s]', '', regex=True)
print(df_text)
Иногда можно использовать str.replace()
, str.findall().
Преобразование текстовых данных в числовые часто требуется для аналитических и ml моделей. Например, после использования factorize()
для категориальных данных можно обратно преобразовать числовые коды в текстовые категории, используя массив uniques
:
# преобразование кодов обратно в текстовые категории
df['Color_restored'] = codes
df['Color_restored'] = df['Color_restored'].map(lambda x: uniques[x])
print(df)
Какие методы очистки знаете вы?
В преддверии старта курса Аналитик данных хочу пригласить вас на бесплатные вебинары про подготовку данных в Pandas, а также про SQL в реалиях начинающего дата-аналитика.