Географическая тепловая карта настраиваемого свойства в R с помощью ggmap

Наша цель - создать что-то вроде http://rentheatmap.com/sanfrancisco.html.

Я получил карту с ggmap и могу наносить точки поверх нее.

library('ggmap')
map <- get_map(location=c(lon=20.46667, lat=44.81667), zoom=12, maptype='roadmap', color='bw')
positions <- data.frame(lon=rnorm(100, mean=20.46667, sd=0.05), lat=rnorm(100, mean=44.81667, sd=0.05), price=rnorm(10, mean=1000, sd=300))
ggmap(map) + geom_point(data=positions, mapping=aes(lon, lat)) + stat_density2d(data=positions, mapping=aes(x=lon, y=lat, fill=..level..), geom="polygon", alpha=0.3)

stat_de density2d на карте

Это хорошее изображение, основанное на плотности. Кто-нибудь знает, как сделать что-то, что выглядит так же, но использует свойство position $ для построения контуров и масштабирования?

Я внимательно просмотрел stackoverflow.com и не нашел решения.

ИЗМЕНИТЬ 1

positions$price_cuts <- cut(positions$price, breaks=5)
ggmap(map) + stat_density2d(data=positions, mapping=aes(x=lon, y=lat, fill=price_cuts), alpha=0.3, geom="polygon")

Результаты в пяти независимых графиках stat_de density: введите описание изображения здесь

ИЗМЕНИТЬ 2 (из hrbrmstr)

positions <- data.frame(lon=rnorm(10000, mean=20.46667, sd=0.05), lat=rnorm(10000, mean=44.81667, sd=0.05), price=rnorm(10, mean=1000, sd=300))
positions$price <- ((20.46667 - positions$lon) ^ 2 + (44.81667 - positions$lat) ^ 2) ^ 0.5 * 10000
positions <- data.frame(lon=rnorm(10000, mean=20.46667, sd=0.05), lat=rnorm(10000, mean=44.81667, sd=0.05))
positions$price <- ((20.46667 - positions$lon) ^ 2 + (44.81667 - positions$lat) ^ 2) ^ 0.5 * 10000
positions <- subset(positions, price < 1000)
positions$price_cuts <- cut(positions$price, breaks=5)
ggmap(map) + geom_hex(data=positions, aes(fill=price_cuts), alpha=0.3)

Результат в: введите описание изображения здесь

Это также создает достойную картину на реальных данных. Это пока лучший результат. Дополнительные предложения приветствуются.

РЕДАКТИРОВАТЬ 3: Вот тестовые данные и результаты описанного выше метода:

https://raw.githubusercontent.com/artem-fedosov/share/master/kernel_smoothing_ggplot.csv

test<-read.csv('test.csv')
ggplot(data=test, aes(lon, lat, fill=price_cuts)) + stat_bin2d(, alpha=0.7) + geom_point() + scale_fill_brewer(palette="Blues")

введите описание изображения здесь

Я считаю, что должен быть какой-то метод, который использует не ядро ​​плотности для вычисления правильных многоугольников. Вроде бы функция должна быть в ggplot из коробки, но я не могу ее найти.

РЕДАКТИРОВАТЬ 4: Я ценю ваше время и усилия, чтобы найти правильное решение этого, казалось бы, не слишком сложного вопроса. Я проголосовал за оба ваших ответа как за хорошее приближение к цели.

Я обнаружил одну проблему: данные с кружками слишком искусственны, и подходы не так хорошо работают с данными мира чтения.

Подход Пола дал мне сюжет: введите описание изображения здесь

Кажется, что он фиксирует шаблоны данных, что здорово.

Подход jazzurro дал мне следующий сюжет: введите описание изображения здесь

У него тоже есть шаблоны. Однако оба графика не кажутся такими красивыми, как график stat_de density2d по умолчанию. Я все равно подожду пару дней, чтобы посмотреть, придет ли какое-то другое решение. Если нет, я награжу jazzurro, так как это будет результат, который я буду использовать.

Существует открытая версия необходимого кода на python + google_maps. Может быть, здесь кто-то найдет вдохновение: https://github.com/jeffkaufman/apartment_prices


person Artem Fedosov    schedule 15.09.2014    source источник
comment
Вы пробовали что-то вроде positions$price_cuts <- cut(positions$price, breaks=5), а затем использовали price_cuts вместо ..level.. для заливки?   -  person hrbrmstr    schedule 15.09.2014
comment
Я пробовал. Он производит 5 независимых слоев с градиентной шкалой :(   -  person Artem Fedosov    schedule 15.09.2014
comment
geom_hex(data=positions, aes(fill=price_cuts), alpha=0.3) может приблизить вас к тому, что вы ищете (с некоторыми изменениями цвета)   -  person hrbrmstr    schedule 15.09.2014
comment
Выглядит очень красиво, очень близко к тому, что я хочу, и без сложных манипуляций с данными!   -  person Artem Fedosov    schedule 15.09.2014
comment
Я искал способ сделать многоугольник более гладким. Но пока не нашел хорошего решения. Как вы предположили, кажется, что python предлагает что-то для достижения цели.   -  person jazzurro    schedule 23.09.2014


Ответы (2)


Мне кажется, что карта в прикрепленной вами ссылке была создана с использованием интерполяции. Имея это в виду, я подумал, смогу ли я добиться такого же аскетизма, наложив интерполированный растр на ggmap.

library(ggmap)
library(akima) 
library(raster) 

## data set-up from question
map <- get_map(location=c(lon=20.46667, lat=44.81667), zoom=12, maptype='roadmap', color='bw')
positions <- data.frame(lon=rnorm(10000, mean=20.46667, sd=0.05), lat=rnorm(10000, mean=44.81667, sd=0.05), price=rnorm(10, mean=1000, sd=300))
positions$price <- ((20.46667 - positions$lon) ^ 2 + (44.81667 - positions$lat) ^ 2) ^ 0.5 * 10000
positions <- data.frame(lon=rnorm(10000, mean=20.46667, sd=0.05), lat=rnorm(10000, mean=44.81667, sd=0.05))
positions$price <- ((20.46667 - positions$lon) ^ 2 + (44.81667 - positions$lat) ^ 2) ^ 0.5 * 10000
positions <- subset(positions, price < 1000)

## interpolate values using akima package and convert to raster
r <- interp(positions$lon, positions$lat, positions$price, 
            xo=seq(min(positions$lon), max(positions$lon), length=100),
            yo=seq(min(positions$lat), max(positions$lat), length=100))
r <- cut(raster(r), breaks=5) 

## plot
ggmap(map) + inset_raster(r, extent(r)@xmin, extent(r)@xmax, extent(r)@ymin, extent(r)@ymax) +
  geom_point(data=positions, mapping=aes(lon, lat), alpha=0.2) 

https://i.stack.imgur.com/qzqfu.png

К сожалению, я не мог понять, как изменить цвет или альфа-канал с помощью inset_raster ... вероятно, из-за моего незнания ggmap.

ИЗМЕНИТЬ 1

Это очень интересная проблема, от которой я ломаю голову. Интерполяция выглядела не совсем так, как я думал, применительно к реальным данным; многоугольник приближается самостоятельно, и jazzurro, безусловно, выглядит намного лучше!

Интересно, почему растровый подход выглядит таким неровным, я еще раз взглянул на карту, которую вы прикрепили, и заметил очевидный буфер вокруг точек данных ... Я подумал, могу ли я использовать некоторые инструменты rgeos, чтобы попытаться воспроизвести эффект:

library(ggmap)
library(raster)
library(rgeos)
library(gplots)

## data set-up from question
dat <- read.csv("clipboard") # load real world data from your link
dat$price_cuts <- NULL
map <- get_map(location=c(lon=median(dat$lon), lat=median(dat$lat)), zoom=12, maptype='roadmap', color='bw')

## use rgeos to add buffer around points
coordinates(dat) <- c("lon","lat")
polys <- gBuffer(dat, byid=TRUE, width=0.005)

## calculate mean price in each circle
polys <- aggregate(dat, polys, FUN=mean)

## rasterize polygons
r <- raster(extent(polys), ncol=200, nrow=200) # define grid
r <- rasterize(polys, r, polys$price, fun=mean) 

## convert raster object to matrix, assign colors and plot
mat <- as.matrix(r)
colmat <- matrix(rich.colors(10, alpha=0.3)[cut(mat, 10)], nrow=nrow(mat), ncol=ncol(mat))
ggmap(map) + 
  inset_raster(colmat, extent(r)@xmin, extent(r)@xmax, extent(r)@ymin, extent(r)@ymax) +
  geom_point(data=data.frame(dat), mapping=aes(lon, lat), alpha=0.1, cex=0.1) 

введите описание изображения здесь

P.S. Я обнаружил, что матрицу цветов нужно отправить в inset_raster для настройки наложения

person Paul Regular    schedule 20.09.2014
comment
Очень красиво смотрится! r ‹- rasterize (polys, r, polys $ price, fun = mean) возвращает мне ошибку: Ошибка в rv [[ii]]: индекс за пределами - person Artem Fedosov; 23.09.2014
comment
Он работает без fun = mean (по умолчанию используется fun = 'last') и возвращает то же изображение, что и у вас. Как вы думаете, что может быть проблемой? - person Artem Fedosov; 23.09.2014
comment
А оно нам действительно нужно? Разве агрегация не означает вычислительную работу? - person Artem Fedosov; 23.09.2014
comment
Я понятия не имею, почему растеризация возвращает эту ошибку. Я обновил растровый пакет на своем конце и получил ту же ошибку. агрегация выполняет большую часть работы, но среднее значение полезно при растрировании, поскольку оно вычисляет среднюю цену в местах, где круги перекрываются. - person Paul Regular; 23.09.2014
comment
Когда я изначально работал над этим решением, я поместил кучу точек внутри каждого круга и применил растеризацию точек. Оказывается, растеризация по-прежнему принимает среднее значение пространственных точек ... Я задал вопрос, чтобы попытаться разобраться в проблеме растеризации полигонов: stackoverflow.com/questions/25994474/ - person Paul Regular; 23.09.2014

Вот мой подход. geom_hex подход хорош. Когда это вышло, мне это очень понравилось. Я все еще делаю. Поскольку вы спросили кое-что еще, я попробовал следующее. Думаю, мой результат похож на результат с stat_density2d. Но я мог избежать проблем, которые были у вас. По сути, я сам создал шейп-файл и нарисовал многоугольники. Я разбил данные по ценовым зонам (price_cuts) и нарисовал полигоны от края до центра зоны. Этот подход находится в линии EDIT 1 и 2. Я думаю, что до вашей конечной цели еще есть некоторое расстояние, если вы хотите нарисовать карту с большой площадью. Но я надеюсь, что это позволит вам двигаться вперед. Наконец, я хотел бы поблагодарить пару пользователей SO, которые задали отличные вопросы, связанные с полигонами. Без них я не смог бы прийти к такому ответу.

library(dplyr)
library(data.table)
library(ggmap)
library(sp)
library(rgdal)
library(ggplot2)
library(RColorBrewer)


### Data set by the OP
positions <- data.frame(lon=rnorm(10000, mean=20.46667, sd=0.05), lat=rnorm(10000,    mean=44.81667, sd=0.05))

positions$price <- ((20.46667 - positions$lon) ^ 2 + (44.81667 - positions$lat) ^ 2) ^ 0.5 * 10000

positions <- subset(positions, price < 1000)


### Data arrangement
positions$price_cuts <- cut(positions$price, breaks=5)
positions$price_cuts <- as.character(as.integer(positions$price_cuts))

### Create a copy for now
ana <- positions

### Step 1: Get a map
map <- get_map(location=c(lon=20.46667, lat=44.81667), zoom=11, maptype='roadmap', color='bw')

### Step 2: I need to create SpatialPolygonDataFrame using the original data.
### http://stackoverflow.com/questions/25606512/create-polygon-from-points-and-save-as-shapefile
### For each price zone, create a polygon, SpatialPolygonDataFrame, and convert it
### it data.frame for ggplot.

cats <- list()

for(i in unique(ana$price_cuts)){

foo <- ana %>%
       filter(price_cuts == i) %>%
       select(lon, lat)

    ch <- chull(foo)
    coords <- foo[c(ch, ch[1]), ]

    sp_poly <- SpatialPolygons(list(Polygons(list(Polygon(coords)), ID=1)))

    bob <- fortify(sp_poly)

    bob$area <- i

    cats[[i]] <- bob
}

cathy <- as.data.frame(rbindlist(cats))


### Step 3: Draw a map
### The key thing may be that you subet data for each price_cuts and draw
### polygons from outer side given the following link.
### This link was great. This is exactly what I was thinking.
### http://stackoverflow.com/questions/21748852/choropleth-map-in-ggplot-with-polygons-that-have-holes

ggmap(map) +
    geom_polygon(aes(x = long, y = lat, group = group, fill = as.numeric(area)),
                 alpha = .3,
                 data = subset(cathy, area == 5))+
    geom_polygon(aes(x = long, y = lat, group = group, fill = as.numeric(area)),
                 alpha = .3,
                 data =subset(cathy, area == 4))+
    geom_polygon(aes(x = long, y = lat, group = group, fill = as.numeric(area)),
                 alpha = .3,
                 data = subset(cathy, area == 3))+
    geom_polygon(aes(x = long, y = lat, group = group, fill = as.numeric(area)),
                 alpha = .3,
                 data = subset(cathy, area == 2))+
    geom_polygon(aes(x = long, y = lat, group = group, fill = as.numeric(area)),
                 alpha= .3,
                 data = subset(cathy, area == 1))+
    geom_point(data = ana, aes(x = lon, y = lat), size = 0.3) +                              
    scale_fill_gradientn(colours = brewer.pal(5,"Spectral")) +
    scale_x_continuous(limits = c(20.35, 20.58), expand = c(0, 0)) +
    scale_y_continuous(limits = c(44.71, 44.93), expand = c(0, 0)) +
    guides(fill = guide_legend(title = "Property price zone"))

введите описание изображения здесь

person jazzurro    schedule 19.09.2014