Обработка пропущенных и некорректных значений

Библиотека Nivo опирается на декларативную модель описания данных, где визуализация строится поверх массива объектов. Любые пропуски, некорректные значения или аномалии в данных не «исправляются» автоматически, а интерпретируются в зависимости от типа графика и используемых шкал.

Ключевая особенность поведения заключается в том, что разные компоненты Nivo по-разному реагируют на значения null, undefined, NaN, отсутствующие ключи и строки вместо чисел. Это требует явного контроля качества данных на уровне подготовки.


Типы некорректных значений и их влияние

null и undefined

В большинстве компонентов Nivo:

  • null интерпретируется как отсутствие точки данных
  • undefined приводит к пропуску значения при вычислениях
  • визуально такие точки либо разрывают линию, либо не отображаются вовсе

Пример:

const data = [
  { x: 0, y: 10 },
  { x: 1, y: null },
  { x: 2, y: 25 }
]

В линейных графиках это приведёт к разрыву линии, если не задана стратегия интерполяции.


NaN и строковые значения

NaN считается невалидным числом и ведёт к исключению точки из визуализации.

Строковые значения:

{ x: 1, y: "20" }

могут приводить к неявным преобразованиям или игнорированию, в зависимости от внутреннего парсинга шкал d3.

Рекомендуется нормализовать данные до передачи в компонент:

const clean = data.map(d => ({
  ...d,
  y: typeof d.y === 'string' ? Number(d.y) : d.y
}))

Поведение в линейных графиках

Компонент ResponsiveLine особенно чувствителен к пропускам.

Разрывы линий

По умолчанию:

  • null разрывает линию
  • пропущенные значения не интерполируются
<ResponsiveLine
  data={data}
  enableArea={false}
/>

Интерполяция пропусков

Для управления поведением используется стратегия defined:

const data = [
  {
    id: "series1",
    data: [
      { x: 0, y: 10 },
      { x: 1, y: null },
      { x: 2, y: 20 }
    ]
  }
]

Можно предварительно фильтровать:

const filtered = data.map(series => ({
  ...series,
  data: series.data.filter(d => d.y != null)
}))

Столбчатые диаграммы и пропуски

В ResponsiveBar отсутствие ключа или null приводит к:

  • отсутствию столбца
  • сдвигу визуальной структуры при динамических данных
{ country: "A", value: null }

Такие случаи требуют явного заполнения:

const normalized = data.map(d => ({
  ...d,
  value: d.value ?? 0
}))

или фильтрации категорий:

const filtered = data.filter(d => d.value != null)

Круговые диаграммы

В ResponsivePie пропуски обрабатываются строже:

  • NaN и null полностью исключаются
  • сумма долей пересчитывается автоматически
const data = [
  { id: "A", value: 10 },
  { id: "B", value: null }
]

Результат: сегмент B не отображается.

Важный аспект нормализации

При динамических данных рекомендуется:

const safe = data
  .filter(d => typeof d.value === "number" && !isNaN(d.value))

Точечные графики

В ResponsiveScatterPlot пропуски влияют на распределение точек:

  • точки с NaN исключаются
  • отсутствие координаты x или y приводит к пропуску объекта целиком
{ x: null, y: 10 } // не отображается

Частичная потеря данных

При частично заполненных координатах важно нормализовать структуру:

const safePoints = data.map(p => ({
  x: p.x ?? 0,
  y: p.y ?? 0
}))

или полностью исключать некорректные:

const safePoints = data.filter(p =>
  typeof p.x === "number" && typeof p.y === "number"
)

Тепловые карты

В ResponsiveHeatMap пропуски особенно критичны, так как каждая ячейка должна иметь числовое значение.

Поведение при отсутствии значений

  • отсутствующие ключи отображаются как пустые ячейки
  • null может интерпретироваться как отсутствие цвета
  • NaN приводит к пропуску рендера ячейки
{
  id: "row1",
  data: [
    { x: "A", y: null },
    { x: "B", y: 20 }
  ]
}

Нормализация

Типичный подход:

const normalized = data.map(row => ({
  ...row,
  data: row.data.map(cell => ({
    ...cell,
    y: typeof cell.y === "number" ? cell.y : 0
  }))
}))

Стратегии подготовки данных

Фильтрация некорректных значений

Базовый подход:

const sanitize = d => typeof d === "number" && !isNaN(d)

Использование:

const clean = data.filter(d => sanitize(d.value))

Заполнение значений (imputation)

Используется, когда потеря данных недопустима:

  • нулями
  • средним значением
  • предыдущим известным значением
const filled = data.map((d, i, arr) => ({
  ...d,
  value: d.value ?? (arr[i - 1]?.value ?? 0)
}))

Линейная интерполяция

Для временных рядов:

const interpolate = (a, b) => (a + b) / 2

Кастомные accessors и защита от ошибок

Nivo позволяет использовать функции доступа к данным:

value: d => d.metrics?.value

При отсутствии защиты это приводит к:

  • undefined
  • нарушению шкал
  • отсутствию рендеринга

Безопасный accessor

value: d => Number(d.metrics?.value ?? 0)

Tooltip и отображение некорректных значений

Tooltip-компоненты часто вызываются даже для частично некорректных данных.

Риски

  • отображение undefined
  • NaN в пользовательском интерфейсе
  • неконсистентные подписи

Защита

tooltip: ({ point }) => {
  const value = Number.isFinite(point.data.y)
    ? point.data.y
    : "нет данных"

  return (
    <div>
      Значение: {value}
    </div>
  )
}

Работа со шкалами и некорректными значениями

Шкалы d3, используемые внутри Nivo:

  • игнорируют NaN
  • могут расширять domain при наличии undefined
  • ломают вычисление min/max при строках

Пример проблемы

domain={[0, "20"]} // некорректно

Решение

const domain = [
  Math.min(...data.map(d => d.y || 0)),
  Math.max(...data.map(d => d.y || 0))
]

Типовые ошибки и их причины

Пустой график

Причины:

  • все значения null
  • фильтрация удалила все точки
  • неправильный accessor

Сдвиг категорий

Причины:

  • пропущенные ключи в bar chart
  • неравномерная структура объектов

Разрыв линий

Причины:

  • null в series
  • отсутствие стратегии обработки defined
  • неконсистентные временные ряды

Практика устойчивых данных

Надёжная схема подготовки:

const prepare = data =>
  data
    .filter(d => d != null)
    .map(d => ({
      ...d,
      value: Number.isFinite(d.value) ? d.value : 0
    }))

Стандартизация структуры перед передачей в Nivo

Ключевой принцип — график не должен получать «сырые» данные. Любая визуализация должна опираться на предсказуемую структуру, где:

  • все числовые поля валидны
  • отсутствующие значения явно обработаны
  • категории согласованы по всем сериям
  • временные ряды имеют непрерывную шкалу или явно обозначенные разрывы