I am plotting a gpd map with an overlay of scatterplot and circle patches. However, when I run the code, the output only displays the scatterplot and circle patches without the border maps. There is nothing wrong with the map as I tried running it separately and it is showing the right output. Please see the image description below for how the plot looks like.
Here is the code:
def plot_dbscan(points, dbscan, title, pt_sizer=1, plot_circles=False):
# Index noise and clusters out of the dbscan points
noise = points[dbscan.labels_ == -1]
clusters = points[dbscan.labels_ != -1]
# Plot country border
fig, ax = plt.subplots(1, figsize=(12,8))
map_1_prj.plot(ax=ax, fc='None', ec='k', linewidth=1.5)
# Allow relative point size adjustment with pt_sizer argument
sns.scatterplot(x=noise[:,0], y=noise[:,1], ax=ax, alpha=1, s=2*pt_sizer, color='gray')
sns.scatterplot(x=clusters[:,0], y=clusters[:,1], ax=ax, s=4*pt_sizer, color='red')
# Option to plot a minimum bounding circle around each cluster
if plot_circles:
for label in np.unique(dbscan.labels_):
if label != -1:
cluster_points = points[dbscan.labels_ == label]
# Get minimum bounding circle using pointpats.centrography.minimum_bounding_circle()
(center_x, center_y), radius = minimum_bounding_circle(cluster_points)
# Create matplotlib patch
circle_patch = mpatches.Circle((center_x, center_y), radius=radius, fc='None', ec='yellow', linewidth=2)
ax.add_patch(circle_patch)
ax.axis('equal')
# Limit bounds of plot to earthquake data
ax.set_xlim(gdf_prj.total_bounds[0], gdf_prj.total_bounds[2])
ax.set_ylim(gdf_prj.total_bounds[1], gdf_prj.total_bounds[3])
# Manually prepare legend items
Ph_border = mlines.Line2D([], [], color='k', linewidth=1.5, label='Philippine Border')
noise_l = mlines.Line2D([], [], marker='.', linewidth=0, markersize=4,
color='gray', label='Noise')
if plot_circles:
# Draw yellow circle around red point for legend
mec = 'yellow'
else:
mec = 'None'
clusters_l = mlines.Line2D([], [], marker='.', linewidth=0,
markersize=12, color='red', markeredgecolor=mec,
label='DBSCAN Clusters')
# Define legend
plt.legend(handles=[Ph_border, noise_l, clusters_l])
plt.title(title)
plt.show()
Related
I'm having trouble displaying multiple 3d plots with rgl's mfrow3d and misc3d's contour3d.
In particular, plotting a new subplot will result in all previous subplots being deleted. Here is a simple example:
library(rgl)
library(misc3d)
# setup rgl subplots
mfrow3d(1,2)
# step into first subplot
next3d()
# Draw a ball
f <- function(x, y, z)x^2+y^2+z^2
x <- seq(-2,2,len=20)
contour3d(f,4,x,x,x)
# advance to next subplot
next3d()
# Ball with one corner removed.
contour3d(f,4,x,x,x,
mask = function(x,y,z) x > 0 | y > 0 | z > 0,
screen = list(x = 290, y = -20),
color = "red", color2 = "white")
# the first subplot is removed
In the first call to contour3d, the first ball draws fine on the left. However, after the second call to contour3d, the second plot is drawn on the right, but the first plot is deleted.
What am I missing here? My hunch is that I'm missing an argument to contour3d, as mfrow3d works fine with other *3d plotting functions, but not with with contour3d.
Like base graphics, rgl graphics come in two types: low level (things like drawing points, lines, etc.) and high level (like plot3d or persp3d). By default high level plots first advance to the next frame (by calling next3d()), while low level plots add to the current one.
The misc3d::contour3d function draws everything using low-level commands, but it assumes it has control of the full window, so instead of calling next3d() to advance to the next frame, it calls clear3d() which clears the whole window.
To work around this, you can call next3d() yourself (only after the first plot, you don't need it before the first one), and then tell contour3d() to add to the scene. That is, change your code like this:
library(rgl)
library(misc3d)
# setup rgl subplots
mfrow3d(1,2)
# Draw a ball
f <- function(x, y, z)x^2+y^2+z^2
x <- seq(-2,2,len=20)
contour3d(f,4,x,x,x)
# advance to next subplot
next3d()
# Ball with one corner removed.
contour3d(f,4,x,x,x,
mask = function(x,y,z) x > 0 | y > 0 | z > 0,
screen = list(x = 290, y = -20),
color = "red", color2 = "white", add = TRUE)
I have a grid and I want to produce a map out of this grid with some map elements (scale, north arrow, etc). I have no problem drawing the grid and the coloring I need, but the additional map elements won't show on the map. I tried putting first=TRUE to the sp.layout argument according to the sp manual, but still no success.
I reproduced the issue with the integrated meuse dataset, so you may just copy&paste that code. I use those package versions: lattice_0.20-33 and sp_1.2-0
library(sp)
library(lattice) # required for trellis.par.set():
trellis.par.set(sp.theme()) # sets color ramp to bpy.colors()
alphaChannelSupported = function() {
!is.na(match(names(dev.cur()), c("pdf")))
}
data(meuse)
coordinates(meuse)=~x+y
data(meuse.riv)
library(gstat, pos = match(paste("package", "sp", sep=":"), search()) + 1)
data(meuse.grid)
coordinates(meuse.grid) = ~x+y
gridded(meuse.grid) = TRUE
v.uk = variogram(log(zinc)~sqrt(dist), meuse)
uk.model = fit.variogram(v.uk, vgm(1, "Exp", 300, 1))
meuse[["ff"]] = factor(meuse[["ffreq"]])
meuse.grid[["ff"]] = factor(meuse.grid[["ffreq"]])
zn.uk = krige(log(zinc)~sqrt(dist), meuse, meuse.grid, model = uk.model)
zn.uk[["se"]] = sqrt(zn.uk[["var1.var"]])
meuse.sr = SpatialPolygons(list(Polygons(list(Polygon(meuse.riv)),"meuse.riv")))
rv = list("sp.polygons", meuse.sr, fill = "lightblue")
sampling = list("sp.points", meuse.riv, color = "black")
scale = list("SpatialPolygonsRescale", layout.scale.bar(),
offset = c(180500,329800), scale = 500, fill=c("transparent","black"), which = 4)
text1 = list("sp.text", c(180500,329900), "0", cex = .5, which = 4)
text2 = list("sp.text", c(181000,329900), "500 m", cex = .5, which = 4)
arrow = list("SpatialPolygonsRescale", layout.north.arrow(),
offset = c(181300,329800),
scale = 400, which = 4)
library(RColorBrewer)
library(lattice)
trellis.par.set(sp.theme())
precip.pal <- colorRampPalette(brewer.pal(7, name="Blues"))
spplot(zn.uk, "var1.pred",
sp.layout = list(rv, sampling, scale, text1, text2),
main = "log(zinc); universal kriging standard errors",
col.regions=precip.pal,
contour=TRUE,
col='black',
pretty=TRUE,
scales=list(draw = TRUE),
labels=TRUE)
And that's how it looks...all naked:
So my questions:
Where is the scale bar, north arrow, etc hiding? Did I miss something? Every example I could find on the internet looks similar to that. On my own dataset I can see the scale bar and north arrow being drawn at first, but as soon as the grid is rendered, it superimposes the additional map elements (except for the scale text, that is shown on the map - not the bar and north arrow for some reason I don't seem to comprehend).
The error message appearing on the map just shows when I try to add the sampling locations sampling = list("sp.points", meuse.riv, color = "black"). Without this entry, the map shows without error, but also without additional map elements. How can I show the sampling points on the map (e.g. in circles whose size depends on the absolute value of this sampling point)?
This bothered me for many, many hours by now and I can't find any solution to this. In Bivand et al's textbook (2013) "Applied Spatial Data Analysis with R" I could read the following entry:
The order of items in the sp.layout argument matters; in principle objects
are drawn in the order they appear. By default, when the object of spplot has
points or lines, sp.layout items are drawn before the points to allow grids
and polygons drawn as a background. For grids and polygons, sp.layout
items are drawn afterwards (so the item will not be overdrawn by the grid
and/or polygon). For grids, adding a list element first = TRUE ensures that
the item is drawn before the grid is drawn (e.g. when filled polygons are added). Transparency may help when combining layers; it is available for the
PDF device and several other devices.
Function sp.theme returns a lattice theme that can be useful for plots
made by spplot; use trellis.par.set(sp.theme()) after a device is opened
or changed to make this effective.
However, also with this additional information I wasn't able to solve this problem. Glad for any hint!
The elements you miss are being drawn in panel four, which does not exist, so are not being drawn. Try removing the which = 4.
meuse.riv in your example is a matrix, which causes the error message, but should be a SpatialPoints object, so create sampling by:
sampling = list("sp.points", SpatialPoints(meuse.riv), color = "black")
When working from examples, my advice is to choose examples as close as possible to what you need, and only change one thing at a time.
I am graphing several n-edge polygons on the same plot. Let say:
1/ n=3: draw polygon with 3 edges, color it with "pink"
2/ n=6: draw polygon with 6 edges, will color with "grey". At this point, I see that the first polygon in step 1 is overlapped by this one. In this case, I just want to keep the "pink" color of the first polygon and color the rest "un-overlapped" area of 2nd polygon with "grey" color.
I have tried some code as follow, but it always display "grey" polygon, instead of "pink" and "grey" areas.
BTW, I also walked around this problem by "draw the 6-edge polygon (n=6) first, and then draw 3-edges polygon (n=3)". By changing the drawing order from the biggest polygon to the smallest one, I can keep the color of the biggest and smallest polygons at the end. However, I would like to do the steps as I mentioned at the beginning of this questions so that i can see the plotting areas are increasing when n (number of edges) keeps increasing.
If you have any suggestions, please advice me. Thank you very much!
cat("\014")
rm(list=ls())
#############################
# first polygon
#n=3
xx3=c(0,-3,3);xx3
yy3=c(1,1,-2);yy3
#plot each intersection /vertex of polygon n=3
plot (xx3, yy3,type = "p", xlim=c(-8,8), ylim=c(-8,8),col="blue",xlab = "x", ylab = "y")
# display value of each point above
text(xx3, yy3, paste("(",round(xx3, 2),"," ,round(yy3, 2), ")"),
cex=0.8,family="mono", font=1, adj=1.5, pos=3)
#fill the shade area
polygon(xx3, yy3, col = "pink", border = "pink")
title("Plot n-edge polygon")
#############################
# RUN untill this point and stop.
#And then run following part, you will see the 1st polygon is overlapping region
#and is fully overwrited by the second polygon.
#############################
# Second polygon
#n=6
par(new=TRUE)
xx=c(0,-15/11,-15/4,-45/11,-3, 3);xx
yy=c(1,20/11,5/2,20/11,1,-2);yy
#plot each intersection /vertex of polygon n=6
points(xx, yy,type = "p", col="blue",xlab = "x", ylab = "y")
# display value of each point above
text(xx, yy, paste("(",round(xx, 2),"," ,round(yy, 2), ")"),
cex=0.8,family="mono", font=1, adj=1.5, pos=3)
#fill the shade area
polygon(xx, yy, col = "grey", border = "grey")
#draw x=0,y=0
abline(a=0,b=0,v=0)
One possibility is to compute the difference between the current polygon (bigger), and the previous one (smaller). I don't know if there is some easy way to compute geometries other than using sp (spatial objects) and rgeos.
Here some code that uses the packages sp and rgeos packages. The approach consists to compute the polygonal difference, by means of spatial objects, and plot it. This might not be the most elegant way, but at least it will do what you want.
require(sp)
require(rgeos)
#test data
xx3=c(0,-3,3);xx3
yy3=c(1,1,-2);yy3
xx=c(-5,-5,5,5);xx
yy=c(-5,5,5,-5);yy
#create a SpatialPolygons object for n = 3
sp3 <- as.data.frame(cbind(xx3,yy3))
sp3 <- rbind(sp3, sp3[1,])
coordinates(sp3) <- c("xx3","yy3")
p3 <- Polygon(sp3)
ps3 <- Polygons(list(p3),1)
sps3 <- SpatialPolygons(list(ps3))
#create a SpatialPolygons object for n = 6
sp <- as.data.frame(cbind(xx,yy))
sp <- rbind(sp, sp[1,])
coordinates(sp) <- c("xx","yy")
p <- Polygon(sp)
ps <- Polygons(list(p),1)
sps <- SpatialPolygons(list(ps))
#compute the difference (with rgeos)
#between the current polygon (bigger) and the previous one (smaller)
spsdiff <- gDifference(sps, sps3)
For plotting the difference, 2 ways:
#Plotting 1: based on sp-plot
#===========
plot(sps, border="transparent") #to set some bigger extent
plot(sps3, add=T, col = "pink")
plot(spsdiff, add=T, col = "grey")
#Plotting 2: use polygon and polypath base functions
#===========
#preparing data for using polypath (polygons with hole)
polys <- spsdiff#polygons[[1]]#Polygons
coords <- do.call("rbind", lapply(polys, function(x){ if(x#hole) x#coords }))
holes <- do.call("rbind", lapply(polys,function(x){ if(!x#hole) rbind(rep(NA,2),x#coords) }))
poly.coords <- rbind(coords,holes)
#plot it
plot(xx, yy, col = "transparent")
polygon(xx3, yy3, col = "pink")
polypath(poly.coords[,1],poly.coords[,2],col="grey", rule="evenodd")
If you have to repeat this, you can re-use this code within a loop to iteratively plot the polygon differences.
Note: rgeos requires you to install the GEOS library on your machine
Im drawing a knn-classification plot in R using plot to plot the samples and contour to plot the lines that classify the plane.
Here is my code:
k<-1
datax<-rbind(matrix(rnorm(30,-1,5.25),15,2),matrix(rnorm(36,1,5.25),18,2))
datay<-rbind(matrix(1,15,1),matrix(0,18,1))
plot(datax[,1], datax[,2],pch = datay+1,axes=FALSE,ann=FALSE)
box()
n <- 1000
xp <- seq(length=n, from = min(datax[,1]), to = max(datax[,1]))
yp <- seq(length=n,from = min(datax[,2]) ,to = max(datax[,2]))
gr <- expand.grid(xp, yp)
library(class)
z <- as.numeric(knn(datax, gr, datay,k))-1
zM <- matrix(z, n, n, byrow = FALSE)
contour(xp, yp, zM, xlab="x",ylab="",nlevels = 1 ,lwd=2, add=TRUE, drawlabels =FALSE)
My question is: How can i color the enclosed areas in the plot? I tried filled.contour but there is no add parameter. I simply want the area where the classifier is = 0 white and where it classifies = 1 in blue. How should i do this?
thanks
Instead of contour, you can use contourLines to keep the coordinates of the edges of the contour lines and plot them with polygon.
plot(datax[,1], datax[,2],axes=FALSE,ann=FALSE, type="n")
box()
cL <- contourLines(xp, yp, zM,nlevels = 1)
lapply(cL,function(x)polygon(x$x,x$y,col="red"))
points(datax[,1], datax[,2],pch = datay+1)
However it is not perfect with contour lines that reach the edges of the plot (see the left lower corner of the second plot), so it will need some hand-made tuning:
Edit: In the case of nested contour lines, I don't think there is an easy way to deal with it but here is one way:
library(splancs)
ord <- sapply(lapply(cL,function(x)datay[inout(datax,cbind(x$x,x$y))]),
median) #Check what values are present in the polygon and
#take the most common one
plot(datax[,1], datax[,2],axes=FALSE,ann=FALSE, type="n")
box()
lapply(cL[ord==1],function(x)polygon(x$x,x$y,col="blue"))
lapply(cL[ord==0],function(x)polygon(x$x,x$y,col="white"))
points(datax[,1], datax[,2],pch = datay+1)
2nd Edit: There is of course also the possibility of using function image in your case:
image(xp, yp, zM, col=c("transparent","blue"))
points(datax[,1], datax[,2],pch = datay+1)
I want to achieve the following outcomes:
Rescale the size of the bubbles such that the largest bubble has a
diameter of 1 (on whichever has the more compressed scale of the x
and y axes).
Rescale the size of the bubbles such that the smallest bubble has a diameter of 1 mm
Have a legend with the first and last points the minimum non-zero
frequency and the maximum frequency.
The best I have been able to do is as follows, but I need a more general solution where the value of maxSize is computed rather than hard-coded. If I was doing it in the traditional R plots I would use par("pin") to work out the size of plot area and work backwards, but I cannot figure out how to access this information with ggplot2. Any suggestions?
library(ggplot2)
agData = data.frame(
class=rep(1:7,3),
drv = rep(1:3,rep(7,3)),
freq = as.numeric(xtabs(~class+drv,data = mpg))
)
agData = agData[agData$freq != 0,]
rng = range(agData$freq)
mn = rng[1]
mx = rng[2]
minimumArea = mx - mn
maxSize = 20
minSize = max(1,maxSize * sqrt(mn/mx))
qplot(class,drv,data = agData, size = freq) + theme_bw() +
scale_area(range = c(minSize,maxSize),
breaks = seq(mn,mx,minimumArea/4), limits = rng)
Here is what it looks like so far:
When no ggplot, lattice or other highlevel package seems to do the job without hours of fine tuning I always revert to the base graphics. The following code gets you what you want, and after it I have another example based on how I would have plotted it.
Note however that I have set the maximum radius to 1 cm, but just divide size.range/2 to get diameter instead. I just thought radius gave me nicer plots, and you'll probably want to adjust things anyways.
size.range <- c(.1, 1) # Min and max radius of circles, in cm
# Calculate the relative radius of each circle
radii <- sqrt(agData$freq)
radii <- diff(size.range)*(radii - min(radii))/diff(range(radii)) + size.range[1]
# Plot in two panels
mar0 <- par("mar")
layout(t(1:2), widths=c(4,1))
# Panel 1: The circles
par(mar=c(mar0[1:3],.5))
symbols(agData$class, agData$drv, radii, inches=size.range[2]/cm(1), bg="black")
# Panel 2: The legend
par(mar=c(mar0[1],.5,mar0[3:4]))
symbols(c(0,0), 1:2, size.range, xlim=c(-4, 4), ylim=c(-2,4),
inches=1/cm(1), bg="black", axes=FALSE, xlab="", ylab="")
text(0, 3, "Freq")
text(c(2,0), 1:2, range(agData$freq), col=c("black", "white"))
# Reset par settings
par(mar=mar0)
Now follows my suggestion. The largest circle has a radius of 1 cm and area of the circles are proportional to agData$freq, without forcing a size of the smallest circle. Personally I think this is easier to read (both code and figure) and looks nicer.
with(agData, symbols(class, drv, sqrt(freq),
inches=size.range[2]/cm(1), bg="black"))
with(agData, text(class, drv, freq, col="white"))