Подтвердить что ты не робот

Обнаружение географических кластеров

У меня есть R data.frame, содержащий долготу, широту, которая охватывает всю карту США. Когда X число записей находится в пределах небольшого географического региона, скажем, на несколько градусов долготы и на несколько градусов, я хочу, чтобы это удалось обнаружить, а затем моя программа вернет координаты для географического ограничивающего прямоугольника. Есть ли Python или R CRAN-пакет, который уже делает это? Если нет, как бы мне узнать, как это узнать?

4b9b3361

Ответ 1

Мне удалось совместить Joran-ответ с комментарием Dan H. Это пример вывода: cluster map

Код python испускает функции для R: map() и rect(). Эта карта примера США была создана с помощью:

map('state', plot = TRUE, fill = FALSE, col = palette())

а затем вы можете применить rect() соответственно с помощью в интерпретаторе R GUI (см. ниже).

import math
from collections import defaultdict

to_rad = math.pi / 180.0   # convert lat or lng to radians
fname = "site.tsv"        # file format: LAT\tLONG
threshhold_dist=50         # adjust to your needs
threshhold_locations=15    # minimum # of locations needed in a cluster

def dist(lat1,lng1,lat2,lng2):
    global to_rad
    earth_radius_km = 6371

    dLat = (lat2-lat1) * to_rad
    dLon = (lng2-lng1) * to_rad
    lat1_rad = lat1 * to_rad
    lat2_rad = lat2 * to_rad

    a = math.sin(dLat/2) * math.sin(dLat/2) + math.sin(dLon/2) * math.sin(dLon/2) * math.cos(lat1_rad) * math.cos(lat2_rad)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)); 
    dist = earth_radius_km * c
    return dist

def bounding_box(src, neighbors):
    neighbors.append(src)
    # nw = NorthWest se=SouthEast
    nw_lat = -360
    nw_lng = 360
    se_lat = 360
    se_lng = -360

    for (y,x) in neighbors:
        if y > nw_lat: nw_lat = y
        if x > se_lng: se_lng = x

        if y < se_lat: se_lat = y
        if x < nw_lng: nw_lng = x

    # add some padding
    pad = 0.5
    nw_lat += pad
    nw_lng -= pad
    se_lat -= pad
    se_lng += pad

    # sutiable for r map() function
    return (se_lat,nw_lat,nw_lng,se_lng)

def sitesDist(site1,site2): 
    #just a helper to shorted list comprehension below 
    return dist(site1[0],site1[1], site2[0], site2[1])

def load_site_data():
    global fname
    sites = defaultdict(tuple)

    data = open(fname,encoding="latin-1")
    data.readline() # skip header
    for line in data:
        line = line[:-1]
        slots = line.split("\t")
        lat = float(slots[0])
        lng = float(slots[1])
        lat_rad = lat * math.pi / 180.0
        lng_rad = lng * math.pi / 180.0
        sites[(lat,lng)] = (lat,lng) #(lat_rad,lng_rad)
    return sites

def main():
    sites_dict = {}
    sites = load_site_data()
    for site in sites: 
        #for each site put it in a dictionary with its value being an array of neighbors 
        sites_dict[site] = [x for x in sites if x != site and sitesDist(site,x) < threshhold_dist] 

    results = {}
    for site in sites: 
        j = len(sites_dict[site])
        if j >= threshhold_locations:
            coord = bounding_box( site, sites_dict[site] )
            results[coord] = coord

    for bbox in results:
        yx="ylim=c(%s,%s), xlim=c(%s,%s)" % (results[bbox]) #(se_lat,nw_lat,nw_lng,se_lng)
        print('map("county", plot=T, fill=T, col=palette(), %s)' % yx)
        rect='rect(%s,%s, %s,%s, col=c("red"))' % (results[bbox][2], results[bbox][0], results[bbox][3], results[bbox][2])
        print(rect)
        print("")

main()

Вот пример файла TSV (site.tsv)

LAT     LONG
36.3312 -94.1334
36.6828 -121.791
37.2307 -121.96
37.3857 -122.026
37.3857 -122.026
37.3857 -122.026
37.3895 -97.644
37.3992 -122.139
37.3992 -122.139
37.402  -122.078
37.402  -122.078
37.402  -122.078
37.402  -122.078
37.402  -122.078
37.48   -122.144
37.48   -122.144
37.55   126.967

С моим набором данных выводится вывод моего python script, показанный на карте США. Я изменил цвета для ясности.

rect(-74.989,39.7667, -73.0419,41.5209, col=c("red"))
rect(-123.005,36.8144, -121.392,38.3672, col=c("green"))
rect(-78.2422,38.2474, -76.3,39.9282, col=c("blue"))

Дополнение к 2013-05-01 для Yacob


Эти две строки дают вам всю цель...

map("county", plot=T )
rect(-122.644,36.7307, -121.46,37.98, col=c("red"))

Если вы хотите ограничить часть карты, вы можете использовать ylim и xlim

map("county", plot=T, ylim=c(36.7307,37.98), xlim=c(-122.644,-121.46))
# or for more coloring, but choose one or the other map("country") commands
map("county", plot=T, fill=T, col=palette(), ylim=c(36.7307,37.98), xlim=c(-122.644,-121.46))
rect(-122.644,36.7307, -121.46,37.98, col=c("red"))

Вы хотите использовать карту мира...

map("world", plot=T )

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

threshhold_dist is the size of the bounding box, ie: the geographical area
theshhold_location is the number of lat/lng points needed with in
    the bounding box in order for it to be considered a cluster.

Вот полный пример. Файл TSV находится на сайте pastebin.com. Я также включил изображение, сгенерированное из R, которое содержит вывод всех команд rect().

# pyclusters.py
# May-02-2013
# -John Taylor

# latlng.tsv is located at http://pastebin.com/cyvEdx3V
# use the "RAW Paste Data" to preserve the tab characters

import math
from collections import defaultdict

# See also: http://www.geomidpoint.com/example.html
# See also: http://www.movable-type.co.uk/scripts/latlong.html

to_rad = math.pi / 180.0  # convert lat or lng to radians
fname = "latlng.tsv"      # file format: LAT\tLONG
threshhold_dist=20        # adjust to your needs
threshhold_locations=20   # minimum # of locations needed in a cluster
earth_radius_km = 6371

def coord2cart(lat,lng):
    x = math.cos(lat) * math.cos(lng)
    y = math.cos(lat) * math.sin(lng)
    z = math.sin(lat)
    return (x,y,z)

def cart2corrd(x,y,z):
    lon = math.atan2(y,x)
    hyp = math.sqrt(x*x + y*y)
    lat = math.atan2(z,hyp)
    return(lat,lng)

def dist(lat1,lng1,lat2,lng2):
    global to_rad, earth_radius_km

    dLat = (lat2-lat1) * to_rad
    dLon = (lng2-lng1) * to_rad
    lat1_rad = lat1 * to_rad
    lat2_rad = lat2 * to_rad

    a = math.sin(dLat/2) * math.sin(dLat/2) + math.sin(dLon/2) * math.sin(dLon/2) * math.cos(lat1_rad) * math.cos(lat2_rad)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)); 
    dist = earth_radius_km * c
    return dist

def bounding_box(src, neighbors):
    neighbors.append(src)
    # nw = NorthWest se=SouthEast
    nw_lat = -360
    nw_lng = 360
    se_lat = 360
    se_lng = -360

    for (y,x) in neighbors:
        if y > nw_lat: nw_lat = y
        if x > se_lng: se_lng = x

        if y < se_lat: se_lat = y
        if x < nw_lng: nw_lng = x

    # add some padding
    pad = 0.5
    nw_lat += pad
    nw_lng -= pad
    se_lat -= pad
    se_lng += pad

    #print("answer:")
    #print("nw lat,lng : %s %s" % (nw_lat,nw_lng))
    #print("se lat,lng : %s %s" % (se_lat,se_lng))

    # sutiable for r map() function
    return (se_lat,nw_lat,nw_lng,se_lng)

def sitesDist(site1,site2): 
    # just a helper to shorted list comprehensioin below 
    return dist(site1[0],site1[1], site2[0], site2[1])

def load_site_data():
    global fname
    sites = defaultdict(tuple)

    data = open(fname,encoding="latin-1")
    data.readline() # skip header
    for line in data:
        line = line[:-1]
        slots = line.split("\t")
        lat = float(slots[0])
        lng = float(slots[1])
        lat_rad = lat * math.pi / 180.0
        lng_rad = lng * math.pi / 180.0
        sites[(lat,lng)] = (lat,lng) #(lat_rad,lng_rad)
    return sites

def main():
    color_list = ( "red", "blue", "green", "yellow", "orange", "brown", "pink", "purple" )
    color_idx = 0
    sites_dict = {}
    sites = load_site_data()
    for site in sites: 
        #for each site put it in a dictionarry with its value being an array of neighbors 
        sites_dict[site] = [x for x in sites if x != site and sitesDist(site,x) < threshhold_dist] 

    print("")
    print('map("state", plot=T)') # or use: county instead of state
    print("")


    results = {}
    for site in sites: 
        j = len(sites_dict[site])
        if j >= threshhold_locations:
            coord = bounding_box( site, sites_dict[site] )
            results[coord] = coord

    for bbox in results:
        yx="ylim=c(%s,%s), xlim=c(%s,%s)" % (results[bbox]) #(se_lat,nw_lat,nw_lng,se_lng)

        # important!
        # if you want an individual map for each cluster, uncomment this line
        #print('map("county", plot=T, fill=T, col=palette(), %s)' % yx)
        if len(color_list) == color_idx:
            color_idx = 0
        rect='rect(%s,%s, %s,%s, col=c("%s"))' % (results[bbox][2], results[bbox][0], results[bbox][3], results[bbox][1], color_list[color_idx])
        color_idx += 1
        print(rect)
    print("")


main()

pyclusters.py / R image result

Ответ 2

Я делаю это на регулярной основе, сначала создавая матрицу расстояний, а затем запуская кластеризацию на ней. Вот мой код.

library(geosphere)
library(cluster)
clusteramounts <- 10
distance.matrix <- (distm(points.to.group[,c("lon","lat")]))
clustersx <- as.hclust(agnes(distance.matrix, diss = T))
points.to.group$group <- cutree(clustersx, k=clusteramounts)

Я не уверен, полностью ли он решает вашу проблему. Вы можете протестировать с разными k, а также, возможно, сделать второй запуск кластеризации некоторых из первых кластеров в случае, если они слишком большие, например, если у вас есть одна точка в Миннесоте и тысяча в Калифорнии. Когда у вас есть point.to.group $group, вы можете получить ограничивающие поля, набрав max и min lat lon для каждой группы.

Если вы хотите, чтобы X было 20, и у вас есть 18 очков в Нью-Йорке и 22 в Далласе, вы должны решить, хотите ли вы одну маленькую и одну действительно большую коробку (по 20 очков каждый), если лучше иметь поле Далласа включает 22 очка, или если вы хотите разбить 22 балла в Далласе на две группы. Кластеризация на основе расстояния может быть хорошей в некоторых из этих случаев. Но это, конечно, зависит от того, почему вы хотите группировать очки.

/Chris

Ответ 3

Несколько идей:

  • Ad-hoc и примерный: "2-D гистограмма". Создайте произвольные "прямоугольные" ящики ширины по вашему выбору, присвойте каждому бин ID. Размещение точки в бункере означает "связывать точку с идентификатором ячейки". После каждого добавления в корзину задайте ячейке, сколько у нее очков. Даунсайд: неверно "видит" кластер точек, которые прокладывают границу бункера; и: бункеры "постоянной продольной ширины" фактически (пространственно) меньше при движении на север.
  • Используйте библиотеку "Shapely" для Python. Следуйте этому примеру с запасом для "точек буферизации" и выполните каскадное объединение буферов. Ищите глобусы над определенной областью или "содержат" определенное количество исходных точек. Обратите внимание, что Shapely не является по своей сути "геосъемкой", поэтому вам придется добавлять исправления, если они вам нужны.
  • Используйте истинную БД с пространственной обработкой. MySQL, Oracle, Postgres (с PostGIS), MSSQL все (я думаю) имеют типы данных "Геометрия" и "География", и вы можете делать пространственные запросы на них (из ваших сценариев Python).

Каждый из них имеет разные затраты в долларах и времени (в кривой обучения)... и в разной степени геопространственной точности. Вы должны выбрать то, что подходит вашему бюджету и/или требованиям.

Ответ 4

Если вы используете стройное, вы можете расширить функцию cluster_points для возврата ограничивающей рамки кластера с помощью свойства .bounds в форме упорядоченной геометрии, например:

clusterlist.append(cluster, (poly.buffer(-b)).bounds)

Ответ 5

может быть что-то вроде

def dist(lat1,lon1,lat2,lon2):
    #just return normal x,y dist
    return sqrt((lat1-lat2)**2+(lon1-lon2)**2)

def sitesDist(site1,site2):
    #just a helper to shorted list comprehensioin below
    return dist(site1.lat,site1.lon,site2.lat,site2.lon)
sites_dict = {}
threshhold_dist=5 #example dist
for site in sites:
    #for each site put it in a dictionarry with its value being an array of neighbors
    sites_dict[site] = [x for x in sites if x != site and sitesDist(site,x) < threshhold_dist]
print "\n".join(sites_dict)