loess: локально взвешенная регрессия

Назначение loess

Преобразование loess в Vega-Lite используется для построения сглаженной линии тренда на основе метода локально взвешенной регрессии (Locally Estimated Scatterplot Smoothing). Метод позволяет выявлять закономерности в шумных данных без необходимости задавать глобальную математическую модель.

В отличие от линейной регрессии, где строится одна общая функция для всего набора данных, LOESS вычисляет локальные аппроксимации для небольших участков данных и затем объединяет их в плавную кривую.

Типичные сценарии применения:

  • анализ временных рядов;
  • сглаживание шумных измерений;
  • визуализация трендов;
  • исследование нелинейных зависимостей;
  • предварительный анализ данных перед построением модели.

Принцип работы LOESS

Алгоритм выполняет несколько этапов:

  1. Для каждой точки выбирается локальное окно соседних значений.

  2. Точкам внутри окна назначаются веса:

    • ближние точки получают больший вес;
    • дальние — меньший.
  3. На локальном участке строится регрессия.

  4. Полученные локальные оценки объединяются в непрерывную кривую.

Главная особенность метода — отсутствие жёстко заданной формы функции.

LOESS способен описывать:

  • изгибы;
  • локальные колебания;
  • нелинейные зависимости;
  • плавные переходы.

Базовый синтаксис loess

Трансформация подключается через массив transform.

{
  transform: [
    {
      loess: "y",
      on: "x"
    }
  ]
}

Основные параметры

Параметр Описание
loess Поле зависимой переменной
on Поле независимой переменной
bandwidth Размер локального окна
groupby Группировка данных
as Имена выходных полей

Простейший пример

vegaEmbed("#view", {
  data: {
    values: [
      {x: 1, y: 5},
      {x: 2, y: 9},
      {x: 3, y: 7},
      {x: 4, y: 10},
      {x: 5, y: 15},
      {x: 6, y: 14},
      {x: 7, y: 18}
    ]
  },

  layer: [
    {
      mark: "point",

      encoding: {
        x: {field: "x", type: "quantitative"},
        y: {field: "y", type: "quantitative"}
      }
    },

    {
      transform: [
        {
          loess: "y",
          on: "x"
        }
      ],

      mark: {
        type: "line",
        color: "red"
      },

      encoding: {
        x: {field: "x", type: "quantitative"},
        y: {field: "y", type: "quantitative"}
      }
    }
  ]
});

Что происходит в примере

Слой point отображает исходные точки.

Второй слой:

  • применяет LOESS-преобразование;
  • вычисляет сглаженную зависимость;
  • рисует линию тренда.

Параметр bandwidth

Роль bandwidth

bandwidth определяет размер локального окна, используемого для вычисления регрессии.

{
  loess: "y",
  on: "x",
  bandwidth: 0.3
}

Значение находится в диапазоне:

0 < bandwidth <= 1

Малый bandwidth

Небольшое значение:

bandwidth: 0.1

Особенности:

  • высокая чувствительность;
  • сильная адаптация к локальным колебаниям;
  • возможное переобучение;
  • линия повторяет шум.

Большой bandwidth

bandwidth: 0.9

Особенности:

  • сильное сглаживание;
  • подавление шума;
  • потеря мелких деталей;
  • более общий тренд.

Сравнение разных значений bandwidth

vegaEmbed("#view", {
  data: {
    url: "data/cars.json"
  },

  layer: [
    {
      mark: "point",

      encoding: {
        x: {
          field: "Horsepower",
          type: "quantitative"
        },

        y: {
          field: "Miles_per_Gallon",
          type: "quantitative"
        }
      }
    },

    {
      transform: [
        {
          loess: "Miles_per_Gallon",
          on: "Horsepower",
          bandwidth: 0.2
        }
      ],

      mark: {
        type: "line",
        color: "red"
      },

      encoding: {
        x: {
          field: "Horsepower",
          type: "quantitative"
        },

        y: {
          field: "Miles_per_Gallon",
          type: "quantitative"
        }
      }
    },

    {
      transform: [
        {
          loess: "Miles_per_Gallon",
          on: "Horsepower",
          bandwidth: 0.8
        }
      ],

      mark: {
        type: "line",
        color: "blue"
      },

      encoding: {
        x: {
          field: "Horsepower",
          type: "quantitative"
        },

        y: {
          field: "Miles_per_Gallon",
          type: "quantitative"
        }
      }
    }
  ]
});

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

LOESS может вычисляться отдельно для разных категорий.

Пример группировки

{
  transform: [
    {
      loess: "sales",
      on: "month",
      groupby: ["region"]
    }
  ]
}

Каждая группа:

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

Полный пример с группировкой

vegaEmbed("#view", {
  data: {
    values: [
      {month: 1, sales: 10, region: "East"},
      {month: 2, sales: 12, region: "East"},
      {month: 3, sales: 18, region: "East"},

      {month: 1, sales: 8, region: "West"},
      {month: 2, sales: 15, region: "West"},
      {month: 3, sales: 20, region: "West"}
    ]
  },

  transform: [
    {
      loess: "sales",
      on: "month",
      groupby: ["region"]
    }
  ],

  mark: "line",

  encoding: {
    x: {
      field: "month",
      type: "quantitative"
    },

    y: {
      field: "sales",
      type: "quantitative"
    },

    color: {
      field: "region",
      type: "nominal"
    }
  }
});

Параметр as

По умолчанию Vega-Lite создаёт выходные поля с теми же именами.

Можно переопределить их:

{
  loess: "y",
  on: "x",
  as: ["smoothX", "smoothY"]
}

После трансформации будут созданы:

Поле Назначение
smoothX сглаженное значение X
smoothY сглаженное значение Y

Пример с as

vegaEmbed("#view", {
  data: {
    values: [
      {x: 1, y: 2},
      {x: 2, y: 4},
      {x: 3, y: 3},
      {x: 4, y: 7}
    ]
  },

  transform: [
    {
      loess: "y",
      on: "x",
      as: ["lx", "ly"]
    }
  ],

  mark: "line",

  encoding: {
    x: {
      field: "lx",
      type: "quantitative"
    },

    y: {
      field: "ly",
      type: "quantitative"
    }
  }
});

Комбинирование с layer

Наиболее распространённый сценарий — отображение:

  • исходных данных;
  • сглаженной линии одновременно.
layer: [
  {
    mark: "point"
  },

  {
    transform: [
      {
        loess: "y",
        on: "x"
      }
    ],

    mark: "line"
  }
]

Использование с временными рядами

LOESS особенно эффективен для анализа времени.

vegaEmbed("#view", {
  data: {
    url: "data/seattle-weather.csv"
  },

  layer: [
    {
      mark: "point",

      encoding: {
        x: {
          field: "date",
          type: "temporal"
        },

        y: {
          field: "temp_max",
          type: "quantitative"
        }
      }
    },

    {
      transform: [
        {
          loess: "temp_max",
          on: "date",
          bandwidth: 0.3
        }
      ],

      mark: {
        type: "line",
        color: "red",
        size: 3
      },

      encoding: {
        x: {
          field: "date",
          type: "temporal"
        },

        y: {
          field: "temp_max",
          type: "quantitative"
        }
      }
    }
  ]
});

Отличие LOESS от линейной регрессии

Линейная регрессия

Предполагает наличие общей функции:

genui{“math_block_widget_always_prefetch_v2”:{“content”:“y=ax+b”}}

Особенности:

  • одна модель для всех данных;
  • плохо работает с изгибами;
  • хорошо интерпретируется.

LOESS

Не требует единой функции.

Особенности:

  • локальные вычисления;
  • адаптация к форме данных;
  • высокая гибкость;
  • слабая интерпретируемость.

Отличие от скользящего среднего

Скользящее среднее:

  • использует фиксированное окно;
  • вычисляет обычное усреднение;
  • не строит локальную регрессию.

LOESS:

  • использует взвешивание;
  • строит локальную модель;
  • создаёт более естественное сглаживание.

Производительность

LOESS является вычислительно затратным методом.

Особенно при:

  • больших наборах данных;
  • малом bandwidth;
  • множестве групп;
  • интерактивной визуализации.

Оптимизация

Уменьшение объёма данных

transform: [
  {
    sample: 1000
  },

  {
    loess: "y",
    on: "x"
  }
]

Предварительная агрегация

transform: [
  {
    aggregate: [
      {
        op: "mean",
        field: "value",
        as: "avg"
      }
    ],

    groupby: ["date"]
  },

  {
    loess: "avg",
    on: "date"
  }
]

Типичные ошибки

Слишком маленький bandwidth

bandwidth: 0.01

Результат:

  • резкие колебания;
  • повторение шума;
  • нестабильная линия.

Слишком большое сглаживание

bandwidth: 1

Результат:

  • потеря локальных закономерностей;
  • чрезмерно усреднённая кривая.

Недостаток данных

LOESS плохо работает:

  • на очень маленьких выборках;
  • при редких точках;
  • при больших разрывах между значениями.

Практический пример: анализ продаж

vegaEmbed("#view", {
  data: {
    values: [
      {day: 1, sales: 120},
      {day: 2, sales: 135},
      {day: 3, sales: 128},
      {day: 4, sales: 170},
      {day: 5, sales: 180},
      {day: 6, sales: 175},
      {day: 7, sales: 220},
      {day: 8, sales: 210},
      {day: 9, sales: 230}
    ]
  },

  layer: [
    {
      mark: {
        type: "line",
        color: "#999"
      },

      encoding: {
        x: {
          field: "day",
          type: "quantitative"
        },

        y: {
          field: "sales",
          type: "quantitative"
        }
      }
    },

    {
      transform: [
        {
          loess: "sales",
          on: "day",
          bandwidth: 0.4
        }
      ],

      mark: {
        type: "line",
        color: "red",
        size: 4
      },

      encoding: {
        x: {
          field: "day",
          type: "quantitative"
        },

        y: {
          field: "sales",
          type: "quantitative"
        }
      }
    }
  ]
});

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

В чистом Vega трансформация выглядит иначе.

{
  "type": "loess",
  "x": "x",
  "y": "y",
  "bandwidth": 0.3
}

Vega-Lite и Vega: различия

Vega-Lite

Преимущества:

  • компактный синтаксис;
  • декларативный стиль;
  • быстрое создание визуализаций.

Vega

Преимущества:

  • полный контроль;
  • низкоуровневая настройка;
  • сложные пайплайны данных.

Когда использовать LOESS

Метод особенно полезен при:

  • отсутствии линейности;
  • шумных измерениях;
  • исследовательском анализе;
  • временных рядах;
  • научных данных;
  • биостатистике;
  • финансовой аналитике.

Когда LOESS нежелателен

Метод плохо подходит для:

  • огромных потоковых данных;
  • строгих математических моделей;
  • задач интерпретации коэффициентов;
  • прогнозирования вне диапазона данных.

Визуальные рекомендации

Отображение исходных точек

Без точек сглаженная линия может вводить в заблуждение.

Правильный подход:

layer: [
  { mark: "point" },
  { mark: "line" }
]

Выделение линии тренда

mark: {
  type: "line",
  size: 4,
  color: "red"
}

Использование прозрачности

mark: {
  type: "point",
  opacity: 0.3
}

Это помогает:

  • уменьшить визуальный шум;
  • показать плотность данных;
  • выделить сглаженную кривую.

Комбинирование с интерактивностью

LOESS можно сочетать с:

  • фильтрацией;
  • масштабированием;
  • выделением диапазонов;
  • параметрами (params);
  • селекторами (selection).

Пример с фильтрацией

transform: [
  {
    filter: "datum.value > 0"
  },

  {
    loess: "value",
    on: "date"
  }
]

Порядок трансформаций

Порядок имеет значение.

transform: [
  { filter: "datum.y != null" },
  { calculate: "datum.y * 2", as: "newY" },
  { loess: "newY", on: "x" }
]

LOESS будет работать уже с преобразованными данными.


Совместимость с другими transform

LOESS часто комбинируется с:

Transform Назначение
filter очистка данных
calculate вычисление полей
aggregate агрегация
window оконные вычисления
sample уменьшение выборки
lookup объединение таблиц

Отладка LOESS

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

  • типы данных;
  • наличие null;
  • сортировку по оси X;
  • размер выборки;
  • значение bandwidth.

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

Правильный вариант:

x: {
  field: "date",
  type: "temporal"
}

Неправильный:

x: {
  field: "date",
  type: "nominal"
}

Проверка пропусков

transform: [
  {
    filter: "datum.y != null"
  },

  {
    loess: "y",
    on: "x"
  }
]

Итоговая структура типичной визуализации

vegaEmbed("#view", {

  data: {
    url: "data.csv"
  },

  layer: [

    {
      mark: "point",

      encoding: {
        x: {
          field: "x",
          type: "quantitative"
        },

        y: {
          field: "y",
          type: "quantitative"
        }
      }
    },

    {
      transform: [
        {
          loess: "y",
          on: "x",
          bandwidth: 0.4
        }
      ],

      mark: {
        type: "line",
        color: "red",
        size: 3
      },

      encoding: {
        x: {
          field: "x",
          type: "quantitative"
        },

        y: {
          field: "y",
          type: "quantitative"
        }
      }
    }
  ]
});