В Evotrex мы создаем IoT-решения для интеллектуальных складов и используем Go для поддержки нескольких наших ключевых приложений и микросервисов. И хотя мы наслаждались преимуществами того, что большинство наших сервисов на одном языке, была одна область, где мы все еще оглядывались на определенные инструменты и плакали. И это была визуализация данных.
Создание объемных выходных данных (таких как трехмерные векторные поля со многими важными статистическими свойствами) означает, что у нас должны быть инструменты для их проверки. Имея в команде опытных программистов на Python, часто бывает непросто решить, хотим ли мы оставаться на плаву для конкретного инструмента анализа данных.
Вот тогда-то и появился гонум/сюжет. Gonum уже позволил нам воспроизвести некоторые из наших числовых расчетов из NumPy и специализированных утилит C++ в Go, однако нам отчаянно нужен был правильный способ визуализации наших наборов данных. Поначалу не очевидно, что gonum/plot поддерживает отображение 2D-данных, кроме плоттера Bubbles, который демонстрируется в вики: в коде есть еще два плоттера: Heatmap и Field. В этом первом посте я приведу пример того, как использовать первый.
Структура данных
Наша цель — визуализировать двумерный массив, который в данном случае будет иметь одно значение float64, связанное с каждой ячейкой. В следующем примере я буду использовать var dataset [][]float64
в качестве источника наших данных, которые мы хотим построить.
Как и во всех плоттерах gonum/plot, нам нужно хранить наши данные в структуре, которая удовлетворяет ожидаемому интерфейсу, чтобы плоттер мог их визуализировать. Базовой структурой данных для плоттера/тепловой карты является интерфейс GridXYZ
, который имеет следующие методы:
Dims() int
: возвращает количество столбцов и строк нашего двумерного массива.Z(c, r int) float64
: возвращает фактическое значение, хранящееся в позициях индексовc
иr
, например.dataset[c][r]
X(c int) float64
: возвращает значение, которое мы хотим использовать для оси X в столбцеc
.Y(r int) float64
: возвращает значение, которое мы хотим использовать для оси Y в строкеr
.
Хотя Z(c, r int) float64
, вероятно, очевидно, стоит потратить минуту, чтобы понять, что делают функции X
и Y
. В типичном сценарии вы рисуете данные, где координаты X и Y представляют количество, например. это данные ширины и высоты. Это зависит от вас, кто определяет этот набор данных, чтобы придать смысл целочисленным данным строки и столбца, что делается с помощью этих методов.
В дополнение к набору данных для построения необходимо определить цветовой диапазон, чтобы визуализировать диапазон значений. Пакет gonum.org/v1/plot/palette
обеспечивает это очень хорошо, и вы можете найти полный набор палитр на выбор здесь: https://godoc.org/gonum.org/v1/plot/palette/brewer и здесь: https:/ /godoc.org/gonum.org/v1/plot/palette/moreland.
Хранение реальных данных
Давайте построим тепловую карту для сетки, которая представляет собой скалярное значение на физической сетке 0,5 м x 0,5 м, как карта высот. Сначала мы определяем нашу структуру хранения данных, которая будет соответствовать интерфейсу GridXYZ
.
type plottable struct {
grid [][]float64
N int
M int
resolution float64
minX float64
minY float64
}
Как видите, из-за физической интерпретации базовой сетки мы должны включить эту структуру с соответствующими параметрами:
- Разрешение нашей сетки, в данном случае 0,5 (в этом примере мы предполагаем одинаковое разрешение в обоих направлениях X и Y)
minX
иminY
представляют, какие физические координаты должна принимать ячейка 0, 0- и
N
иM
для хранения информации о ширине и высоте, перемещенных сюда в структуру в основном для удобства.
Реализация интерфейса
Наша структура plottable должна реализовывать интерфейс GridXYZ
, чтобы мы могли в конечном итоге передать его в конструктор NewHeatMap()
:
func (p plottable) Dims() (c, r int) { return p.N, p.M } func (p plottable) X(c int) float64 { return p.minX + float64(c)*p.resolution } func (p plottable) Y(r int) float64 { return p.minY + float64(r)*p.resolution } func (p plottable) Z(c, r int) float64 { return p.grid[c][r] }
Таким образом, индексы X и Y теперь связаны с правильными значимыми значениями на нашем графике.
Заговор
Окончательный сюжет теперь прост. Создайте экземпляр структуры plottable
, заполните поля grid
, N
и M
(или используйте len()
) и вызовите плоттер как обычно. Если наш исходный набор данных был var dataset [][]float64
, то следующий код создаст хорошую тепловую карту, которая начинается с (-0.5; 42.0)
и имеет разрешение 0,5:
plotData := plottable{ grid: dataset, N: cols, M: rows, minX: -0.5, minY: 42.0, resolution: 0.5, } pal := moreland.SmoothBlueRed().Palette(255) hm := plotter.NewHeatMap(plotData, pal)
Наслаждаться!