Статьи

Пропуски в данных — не приговор: Полное руководство по визуализации и анализу с missingno в Python

Работа с данными

Исследование и подготовка данных — это фундамент, на котором строятся любые проекты в Data Science. Новички часто привыкают к "стерильным" учебным датасетам, где все гладко и чисто. Реальность же больше похожа на археологические раскопки: данные почти всегда неполные, с ошибками, неточностями и, конечно, пропусками.

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

В зависимости от источника данных пропуски могут иметь разные обозначения. Наиболее часто используется NaN (Not a Number), но также можно встретить «NA», «None», «-999», «0», « », «-», «?» и другие варианты.

Если в датафрейме отсутствуют данные и они обозначены не как NaN, то их необходимо преобразовать в NaN. Например, можно использовать следующий код:

df = df.replace('', np.nan)

Первая реакция любого, кто работает с Pandas, — вызвать df.isna().sum(). Мы получаем аккуратную табличку с количеством пропусков по каждому столбцу и чувствуем, что контролируем ситуацию. Но это иллюзия. Знать сколько данных пропущено — это лишь первый шаг. Гораздо важнее понять, как они пропущены. Есть ли в этих пропусках система? Связаны ли они между собой? Являются ли они случайным шумом или сигналом о серьезных проблемах в данных?

Простой подсчет NaN не ответит на эти вопросы. Чтобы перейти от поверхностного взгляда к глубокому пониманию, нам нужен специализированный инструмент. И здесь на сцену выходит missingno.

Почему стандартных средств Pandas недостаточно?

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

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

import pandas as pd
import numpy as np

# Загружаем данные
df = pd.read_csv('https://github.com/obulygin/content/raw/refs/heads/main/xeek_data/xeek_train_subset.csv')
df

1. Метод .info()

Это наш первый взгляд на здоровье датафрейма.

df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 133198 entries, 0 to 133197
Data columns (total 22 columns):
 #   Column       Non-Null Count   Dtype  
---  ------       --------------   -----  
 0   WELL         133198 non-null  object 
 1   DEPTH_MD     133198 non-null  float64
 2   X_LOC        125805 non-null  float64
 3   Y_LOC        125805 non-null  float64
 4   Z_LOC        125805 non-null  float64
 5   GROUP        133198 non-null  object 
 6   FORMATION    111632 non-null  object 
 7   CALI         133006 non-null  float64
 8   RSHA         62039 non-null   float64
 9   RMED         125556 non-null  float64
 10  RDEP         125805 non-null  float64
 11  RHOB         108053 non-null  float64
 12  GR           133198 non-null  float64
 13  NPHI         91725 non-null   float64
 14  PEF          100840 non-null  float64
 15  DTC          132635 non-null  float64
 16  SP           93680 non-null   float64
 17  ROP          130454 non-null  float64
 18  DTS          12184 non-null   float64
 19  DCAL         56200 non-null   float64
 20  DRHO         105539 non-null  float64
 21  LITHOFACIES  133198 non-null  int64  
dtypes: float64(18), int64(1), object(3)
memory usage: 22.4+ MB

Вывод .info() уже дает нам важную подсказку. Общее число записей (RangeIndex) — 133,198. Но Non-Null Count у многих столбцов (X_LOC, FORMATION, CALI и т.д.) заметно меньше. Это прямое указание на наличие пропусков.

2. Метод .isna().sum()

Самый прямой способ подсчитать пропуски:

df.isna().sum()
WELL                0
DEPTH_MD            0
X_LOC            7393
Y_LOC            7393
Z_LOC            7393
GROUP               0
FORMATION       21566
CALI              192
RSHA            71159
RMED             7642
RDEP             7393
RHOB            25145
GR                  0
NPHI            41473
PEF             32358
DTC               563
SP              39518
ROP              2744
DTS            121014
DCAL            76998
DRHO            27659
LITHOFACIES         0
dtype: int64

Метод isna() возвращает логический массив с True (если значение является пропуском) и False - если нет. А sum() суммирует элементы в этом массиве (где True будут единицами, а False — нулями), так мы и получим количество пропусков. Теперь у нас есть точные цифры. Мы видим, что в некоторых столбцах (X_LOC, Y_LOC, Z_LOC, RDEP) одинаковое количество пропусков — 7393. Это уже наводит на мысль о неслучайности. Столбец DTS пропущен более чем в ста тысячх строк — он почти пустой. DCAL, RSHA тоже выглядят плохо.

В чем же проблема?

Мы знаем что и сколько. Но мы не видим картину целиком.

  • Где именно в датафрейме расположены эти пропуски? В начале, в конце, равномерно?
  • Если в строке пропущено значение X_LOC, пропущено ли там и Y_LOC? (Судя по одинаковому числу — да, но это лишь гипотеза).
  • Связаны ли пропуски в RHOB с пропусками в CALI?
  • Являются ли пропуски в DTS свойством определенных скважин или они случайны?

Ответы на эти и другие вопросы определяют стратегию работы с данными. Простое удаление строк (dropna()) может уничтожить ценную информацию, а наивная замена средним — исказить распределение и связи между признаками.

Нам нужен инструмент, который превратит эту сухую таблицу чисел в наглядную карту. И этот инструмент — missingno.

Визуальный аудит с missingno: от графиков к инсайтам

missingno — это не просто библиотека для красивых графиков. Это философия визуального аудита данных. Она предоставляет четыре мощных инструмента, каждый из которых отвечает на свой класс вопросов о структуре пропусков.

Установка стандартная:

pip install missingno

А теперь давайте проведем настоящее расследование, используя наш датасет.

msno.bar(): Быстрая оценка полноты

Столбчатая диаграмма — это аналог isna().sum(), но в графическом виде. Она показывает, какая доля данных присутствует в каждом столбце.

import missingno as msno
import matplotlib.pyplot as plt

msno.bar(df)
plt.show() 

Как читать этот график:

  • Ось Y (слева): Полнота данных от 0.0 (полностью пустой столбец) до 1.0 (полностью заполненный).
  • Ось X: Названия столбцов датафрейма.
  • Цифры над столбцами: Абсолютное количество НЕпропущенных значений в каждом столбце.

[!TIP] Когда использовать msno.bar()? Это ваш первый шаг. Используйте его сразу после загрузки данных, чтобы получить "общую температуру по больнице" и мгновенно выявить самые проблемные столбцы, которые потребуют пристального внимания.

Этот график полезен, но он все еще не показывает расположение пропусков. Чтобы заглянуть внутрь структуры датафрейма, нам нужен следующий инструмент.

msno.matrix(): рентген вашего датафрейма

Матричный график — это, пожалуй, самый информативный инструмент в арсенале missingno. Он позволяет буквально заглянуть внутрь датафрейма и увидеть точное расположение каждого пропущенного значения.

msno.matrix(df)
plt.show()

Как читать этот график:

  • Каждая строка на графике соответствует строке в вашем датафрейме.
  • Столбцы — это признаки.
  • Сплошные черные области — это данные, которые у нас есть.
  • Белые полосы и точки — это пропуски (NaN).
  • Спарклайн (sparkline) справа — это мини-график, который визуализирует полноту данных по строкам. Он показывает общую картину "здоровья" данных от начала до конца датафрейма. Линия на самом дне означает полностью заполненную строку. Чем выше поднимается линия, тем больше пропусков в соответствующем диапазоне строк.

Какие выводы можно сделать:

  1. Структура пропусков: Мы отчетливо видим, что пропуски — не случайный шум. В некоторых столбцах они образуют огромные сплошные белые блоки. Это говорит о том, что данные отсутствуют не точечно, а целыми сегментами. Вероятно, для определенных скважин или на определенных глубинах эти измерения просто не проводились.
  2. Локализация: Спарклайн справа неровный. Есть пики и впадины. Это значит, что есть целые группы строк, где данных не хватает больше, чем в других. Если бы данные были упорядочены по времени или по скважинам, это был бы мощнейший инсайт.
  3. Визуальное подтверждение корреляций: Присмотритесь к столбцам X_LOC, Y_LOC, Z_LOC и RDEP. Белые горизонтальные черточки в них появляются и исчезают синхронно. Это визуальное подтверждение нашей гипотезы, сделанной на основе isna().sum(): если пропущено одно из этих значений, скорее всего, пропущены и остальные.

[!INFO] Почему матрица так важна? Матричный график переводит наш анализ с уровня "сколько" на уровень "где и как". Он незаменим для временных рядов и любых упорядоченных данных (например, геологических разрезов по глубине), так как позволяет увидеть, являются ли пропуски изолированными событиями или системными сбоями на протяжении определенных периодов/участков.

msno.heatmap(): Поиск скрытых связей

Мы уже подозреваем, что пропуски в некоторых столбцах связаны. Тепловая карта создана, чтобы измерить эту связь количественно. Она вычисляет корреляцию отсутствия данных (nullity correlation) между столбцами.

msno.heatmap(df)
plt.show()

Как читать этот график:

  • Это симметричная матрица, где каждый столбец сравнивается с каждым.
  • Значения варьируются от -1 до 1.
    • Близко к 1 (светлые тона): Сильная положительная корреляция. Если значение отсутствует в одном столбце, оно с высокой вероятностью отсутствует и в другом.
    • Близко к -1: Сильная отрицательная корреляция. Если значение отсутствует в одном столбце, оно с высокой вероятностью присутствует в другом. (Встречается редко, но указывает на интересные взаимоисключающие процессы сбора данных).
    • Близко к 0 (темные тона): Отсутствие корреляции. Пропуски в этих столбцах живут своей жизнью.
    • Значение <1: Если вы видите ячейку со значением, которое не равно 1, но меньше него (например, 0.8), это означает, что не все, но многие пропуски совпадают.

Какие выводы можно сделать:

  • Подтвержденные кластеры: Мы видим яркий квадрат в области столбцов RDEP, RMED, X_LOC, Y_LOC, Z_LOC. Корреляция между ними очень высока. Это означает, что данные в этих столбцах почти всегда пропадают вместе. Вероятно, они собирались в рамках одного процесса или одним набором инструментов.
  • Изолированные проблемы: Посмотрите на столбцы DTS и RSHA. Они не показывают сильной корреляции с другими кластерами. Их пропуски — это отдельная история. При этом между собой они тоже не коррелируют, что интересно.
  • Отсутствие связей: Большинство других пар столбцов имеют корреляцию около нуля. Это говорит о том, что причины пропусков в FORMATION и RHOB, например, скорее всего, никак не связаны.

msno.dendrogram(): Карта родства пропусков

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

msno.dendrogram(df)
plt.show()

Как читать этот график:

  • Древовидный граф строится снизу вверх.
  • Столбцы, которые объединяются на низком уровне (ближе к 0), имеют очень схожую структуру пропусков. Горизонтальная линия, соединяющая их, показывает, на каком уровне "непохожести" они были сгруппированы.
  • Чем выше по дереву происходит объединение, тем менее похожи паттерны пропусков у столбцов (или групп столбцов).

Какие выводы можно сделать:

  • Клан "идеальных": мы видим плотную группу (LITHOFACIES, GR, GROUP, WELL, DEPTH_MD), объединенную на уровне 0. Это наши полностью заполненные столбцы. Их паттерн пропусков идентичен — пропусков нет.
  • Клан "геопозиции": дендрограмма объединяет X_LOC, Y_LOC, Z_LOC и RDEP на очень низком уровне, подтверждая их тесную связь. RMED присоединяется к этой группе чуть выше, что говорит о сильной, но не идеальной связи — именно то, что мы видели на тепловой карте.
  • Клан "одиночек": мы видим, что DTS, RSHA и CALI присоединяются к общему дереву очень высоко. Это значит, что их паттерны пропусков уникальны и не похожи ни друг на друга, ни на другие столбцы. Они — "дальние родственники" для остального набора данных.

Заключение: Что делать с этими знаниями?

Мы прошли путь от простого подсчета NaN до глубокого структурного анализа. Библиотека missingno не заполняет пропуски за вас. Она делает нечто более важное — дает вам исчерпывающую информацию для принятия осознанного решения.

На основе нашего анализа можно наметить конкретный план действий:

  1. Столбец DTS: Пропущено более 85% данных. Пытаться восстановить его — крайне рискованная затея, которая может привнести больше шума, чем сигнала. Наиболее разумная стратегия — удалить этот столбец.
  2. Столбец RSHA: Пропущено почти 50% данных. Ситуация пограничная. Можно рассмотреть его удаление или попытаться восстановить значения, но только для тех моделей, которые устойчивы к подобным манипуляциям.
  3. Кластер X_LOC, Y_LOC, Z_LOC, RDEP, RMED: Эти столбцы теряют данные синхронно. Это значит, что если мы решим удалить строки с пропусками (dropna), мы должны делать это с учетом всего кластера. Удаление строк, где пропущен X_LOC, почти наверняка приведет к удалению тех же строк, где пропущены Y_LOC и Z_LOC.
  4. Столбцы с умеренным количеством пропусков (FORMATION, CALI, RHOB): Их пропуски не сильно коррелируют друг с другом. Здесь можно применять более тонкие методы импутации (заполнения): для каждого столбца подбирать свою стратегию (например, заполнение модой для категориального FORMATION и медианой/средним или даже модельным предсказанием для числовых CALI и RHOB).

Простой вызов df.isna().sum() никогда не дал бы нам такой глубины понимания. Визуальный анализ с missingno превращает проблему пропусков из технической неприятности в интересный исследовательский квест, по итогам которого вы знаете о своих данных гораздо больше. А хорошее знание данных — это и есть ключ к построению качественных моделей.