D3 против Scipy (реализация диаграммы Вороного)

Фон

Я работаю с набором из 8000 географических точек, содержащихся в CSV-файле. С одной стороны я создаю визуализацию диаграмм Вороного, построенных по этим точкам - это делается с помощью библиотеки D3. С другой стороны, я вычисляю эти диаграммы Вороного в Python, используя Scipy.

Моя логика работы проста — я работаю со своими данными на стороне Python, делаю тепловые карты, анализирую и так далее, а затем визуализирую эффекты с помощью D3. Но сегодня я случайно обнаружил, что диаграммы Вороного, сделанные Scipy и D3, отличаются. Я заметил это после использования geojson.io для построения GeoJsons of Voronois, сделанных на Python, просто чтобы посмотреть, смогу ли я все там визуализировать.

Как я уже сказал, Вороные были разные - у некоторых из них были другие углы, а у некоторых даже были дополнительные вершины.

Вопрос:

Почему это происходит? Почему диаграммы Вороного, рассчитанные этими двумя библиотеками (D3 и Scipy), различаются?

Дополнительное описание

Как это делается на стороне D3: на примере Криса Зеттера http://chriszetter.com/blog/2014/06/15/building-a-voronoi-map-with-d3-and-leaflet/ Я перевожу широту и долготу в пользовательскую проекцию, чтобы визуализировать ее на карте mapbox.

var voronoi = d3.geom.voronoi()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.clipExtent([[N_W.x , N_W.y],[S_E.x, S_E.y]])

Я создаю Voronoi на основе точек, которые видны в пределах границы карты + некоторые отступы (filteredPoints)

  filteredPoints = points.filter(function(d) {
  var latlng = new L.LatLng(d.latitude, d.longitude);
  if (!drawLimit.contains(latlng)) { return false };
  // this translates points from coordinates to pixels
  var point = map.latLngToLayerPoint(latlng);
  key = point.toString();
  if (existing.has(key)) { return false };
  existing.add(key);
  d.x = point.x;
  d.y = point.y;
  return true;
  });

voronoi(filteredPoints).forEach(function(d) { d.point.cell = d});

Как это делается на стороне Python: я использую scipy.spatial.Voronoi.

from scipy.spatial import Voronoi

def create_voronois():
    points = numpy.array(points_list)
    vor = Voronoi(points)

Где «points_list» — это список моих 8000 географических точек.

РЕДАКТИРОВАТЬ:

Скриншот из моей визуализации - черные рамки сделаны Voronois с помощью D3, белые сделаны scipy.spatial.Voronoi. Как мы видим, scipy ошибается. Кто-нибудь сравнивал эти 2 библиотеки раньше?

http://imgur.com/b1ndx0F

Код для запуска. Он печатает GeoJson с плохо рассчитанным Вороным.

import numpy
from scipy.spatial import Voronoi
from geojson import FeatureCollection, Feature, Polygon

points = [
[22.7433333333000,  53.4869444444000],
[23.2530555556000,  53.5683333333000],
[23.1066666667000,  53.7200000000000],
[22.8452777778000,  53.7758333333000],
[23.0952777778000,  53.4413888889000],
[23.4152777778000,  53.5233333333000],
[22.9175000000000,  53.5322222222000],
[22.7197222222000   ,53.7322222222000],
[22.9586111111000,  53.4594444444000],
[23.3425000000000,  53.6541666667000],
[23.0900000000000,  53.5777777778000],
[23.2283333333000,  53.4713888889000],
[23.3488888889000,  53.5072222222000],
[23.3647222222000   ,53.6447222222000]]

def create_voronois(points_list):
    points = numpy.array(points_list)
    vor = Voronoi(points)
    point_voronoi_list = []
    feature_list = []
    for region in range(len(vor.regions) - 1):
        vertice_list = []
        for x in vor.regions[region]:
            vertice = vor.vertices[x]
            vertice = (vertice[1], vertice[0])
            vertice_list.append(vertice)
            polygon = Polygon([vertice_list])
            feature = Feature(geometry=polygon, properties={})
            feature_list.append(feature)

    feature_collection = FeatureCollection(feature_list)
    print feature_collection

create_voronois(points)

person grael    schedule 23.09.2015    source источник
comment
Библиотеки, вероятно, имеют разные алгоритмы/реализации.   -  person Lars Kotthoff    schedule 23.09.2015
comment
@LarsKotthoff В этом проблема - диаграмма Вороного из целевого набора точек должна каждый раз быть одинаковой (en.wikipedia .org/wiki/Voronoi_diagram), из чего следует, что либо я допустил здесь какую-то ошибку в коде, либо одна из библиотек неверна (будь то разные предположения или что-то еще).   -  person grael    schedule 23.09.2015
comment
Учитывая выходные данные, должно быть тривиально увидеть, какой из них неверен - в другом регионе посмотрите, к какой точке он на самом деле ближе всего.   -  person Lars Kotthoff    schedule 23.09.2015
comment
@grael: Вы уверены, что числовые значения входных точек одинаковы в обоих случаях? Вы упоминаете некоторые преобразования. Они одинаковы для D3 и scipy?   -  person Warren Weckesser    schedule 23.09.2015
comment
@WarrenWeckesser Да, как вы можете видеть на картинке, на которую я дал ссылку в конце моего сообщения, точки находятся в одном месте, обе библиотеки получают один и тот же их набор, но возвращают разные ячейки Вороного.   -  person grael    schedule 23.09.2015
comment
Если вы можете, было бы полезно, если бы вы могли использовать свои данные для создания автономного воспроизводимого примера (то есть чего-то, что мы можем скопировать и запустить), для которого код scipy генерирует неправильную диаграмму Вороного.   -  person Warren Weckesser    schedule 23.09.2015
comment
@WarrenWeckesser Добавлен код в раздел «Правка».   -  person grael    schedule 23.09.2015
comment
@grael Отлично, спасибо!   -  person Warren Weckesser    schedule 23.09.2015


Ответы (1)


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

Приведенные ниже сценарии показывают, что, если вы сохраняете относительное расстояние между входными точками, функция Вороного scipy и d3.geom.voronoi генерируют одну и ту же диаграмму.

Вот скрипт, который использует код Вороного scipy:

import numpy
from scipy.spatial import Voronoi, voronoi_plot_2d
import matplotlib.pyplot as plt


points = [
[22.7433333333000,  53.4869444444000],
[23.2530555556000,  53.5683333333000],
[23.1066666667000,  53.7200000000000],
[22.8452777778000,  53.7758333333000],
[23.0952777778000,  53.4413888889000],
[23.4152777778000,  53.5233333333000],
[22.9175000000000,  53.5322222222000],
[22.7197222222000,  53.7322222222000],
[22.9586111111000,  53.4594444444000],
[23.3425000000000,  53.6541666667000],
[23.0900000000000,  53.5777777778000],
[23.2283333333000,  53.4713888889000],
[23.3488888889000,  53.5072222222000],
[23.3647222222000,  53.6447222222000]]


vor = Voronoi(points)

voronoi_plot_2d(vor)
plt.axis('equal')
plt.xlim(22.65, 23.50)
plt.ylim(53.35, 53.85)
plt.show()

Он генерирует этот сюжет:

сюжет

Теперь вот программа javascript, которая использует d3.geom.voronoi:

<html>
<head>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js"></script>
</head>
<body>
  <div id="chart">
  </div>
  <script type="text/javascript">

  // This code is a hacked up version of http://bl.ocks.org/njvack/1405439

  var w = 800,
      h = 400;

  var data = [
    [22.7433333333000,  53.4869444444000],
    [23.2530555556000,  53.5683333333000],
    [23.1066666667000,  53.7200000000000],
    [22.8452777778000,  53.7758333333000],
    [23.0952777778000,  53.4413888889000],
    [23.4152777778000,  53.5233333333000],
    [22.9175000000000,  53.5322222222000],
    [22.7197222222000,  53.7322222222000],
    [22.9586111111000,  53.4594444444000],
    [23.3425000000000,  53.6541666667000],
    [23.0900000000000,  53.5777777778000],
    [23.2283333333000,  53.4713888889000],
    [23.3488888889000,  53.5072222222000],
    [23.3647222222000,  53.6447222222000]
  ];

  // Translate and scale the points.  The same scaling factor (2*h) must be used
  // on x and y to preserve the relative distances among the points.
  // The y coordinates are also flipped.
  var vertices = data.map(function(point) {return [2*h*(point[0]-22.5), h - 2*h*(point[1]-53.4)]})

  var svg = d3.select("#chart")
    .append("svg:svg")
      .attr("width", w)
      .attr("height", h);
  var paths, points;

  points = svg.append("svg:g").attr("id", "points");
  paths = svg.append("svg:g").attr("id", "point-paths");

  paths.selectAll("path")
      .data(d3.geom.voronoi(vertices))
    .enter().append("svg:path")
      .attr("d", function(d) { return "M" + d.join(",") + "Z"; })
      .attr("id", function(d,i) { 
        return "path-"+i; })
      .attr("clip-path", function(d,i) { return "url(#clip-"+i+")"; })
      .style("fill", d3.rgb(230, 230, 230))
      .style('fill-opacity', 0.4)
      .style("stroke", d3.rgb(50,50,50));

  points.selectAll("circle")
      .data(vertices)
    .enter().append("svg:circle")
      .attr("id", function(d, i) { 
        return "point-"+i; })
      .attr("transform", function(d) { return "translate(" + d + ")"; })
      .attr("r", 2)
      .attr('stroke', d3.rgb(0, 50, 200));

  </script>
</body>
</html>

Он генерирует:

вороной скриншот

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

person Warren Weckesser    schedule 23.09.2015
comment
Спасибо, что сделали мой код простым. К сожалению, это не решает мою проблему. На данный момент у меня больше нет доступа к базе данных точек, но завтра я дам вам скриншот того, как выглядят эти ячейки, сделанные D3 (ячейки, построенные с использованием одного и того же набора точек), и вы заметите, как они отличаются по форме. и углы. - person grael; 23.09.2015
comment
Я подозреваю, что проблема связана с некоторым преобразованием или проекцией данных до или после вычисления диаграммы Вороного. Код scipy здесь ничего не знает о широте и долготе. Точки — это просто координаты на плоскости (x, y), а метрика — стандартная евклидова метрика. - person Warren Weckesser; 23.09.2015
comment
Может быть, здесь имеет значение кривизна Земли? Meaby одна библиотека это учитывает, а другая просто вычисляет ячейки, как если бы Земля была плоской? - person grael; 23.09.2015
comment
...вычисляет ячейки, как если бы Земля была плоской Это то, что делает Вороной из scipy. Ваш код выполняет var point = map.latLngToLayerPoint(latlng);, что означает, что вы преобразовываете данные перед вычислением диаграммы Вороного. - person Warren Weckesser; 23.09.2015
comment
Для этих точек я создал визуализацию в D3 - imgur.com/qnk0A3P. Как видите, формы и углы разные. - person grael; 24.09.2015