Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

R circlize - plot margins and plotting regions

I'm trying to replicate the graphs of this website https://gjabel.wordpress.com/2014/03/28/circular-migration-flow-plots-in-r/ using the r library "circlize". Unfortunately I have two problems:

First of all I got a warning for all plotting regions (i.e. Note: 1 point is out of plotting region in sector 'Mexico', track '1'). I thought the problem was that in circos.text and circos.axis, since I was using them along with direction, which is a deprecated function. But also using facing instead of direction, the problem persists. So I guess I didn't understand the meaning of this warning. Do you have some hints so to help me?

Moreover, in my plot, links were very far from segments. Thus I tried to reduce the track.margin, which solve the problem, but now the names of the segments are outside the margins, and I cannot visualize them. Is there a better way to solve this problem?

This is what I wrote so far (which is almost entirely taken from this site https://github.com/null2/globalmigration)

library("circlize")
library("plyr")
library("migest")
#load data
m<-read.table(system.file("science", "country_custom.txt", package = "migest"), skip=2, stringsAsFactors=FALSE)
#1)a data.frame to store information on each segment of the circle to be plotted
df1<-m[,1:3]
names(df1)<-c("order","rgb","region")
df1$region<-gsub("\\.", "\n", df1$region)
#2) a matrix containing the flow data (in this example only 28 countries)
m<-m[,-(1:3)]/1e05
m<-as.matrix(m)
dimnames(m)<-list(orig=df1$region,dest=df1$region)
#sort order of data.frame and matrix for plotting in circos    
df1<-arrange(df1, order) #reordering a data frame by its columns
df1$region <- factor(df1$region, levels=df1$region)
m<-m[levels(df1$region),levels(df1$region)]
#define ranges of circos sectors and their colors (both of the sectors and the links)
#determine the length of segments on the outside of the plot.
df1$xmin <- 0
df1$xmax <- rowSums(m)+colSums(m) #inflows+outflows
#set the colour names for segments and flows
n<-nrow(df1)
df1 <- cbind(df1, matrix(as.numeric(unlist(strsplit(df1$rgb,","))),nrow=n, byrow=TRUE) )
names(df1)[ncol(df1)-2:0]<-c("r","g","b")
df1$rcol<-rgb(df1$r, df1$g, df1$b, max = 255)
df1$lcol<-rgb(df1$r, df1$g, df1$b, alpha=200, max = 255)
##plot sectors
windows()
par(mar=rep(0,4))
circos.clear()
#1)basic circos graphic parameters
circos.par(cell.padding=c(0,0,0,0), track.margin=c(0,0.01), start.degree = 90, gap.degree =4)
#2)sector details
circos.initialize(factors = df1$region, xlim = cbind(df1$xmin, df1$xmax))
#3)plot sectors
circos.trackPlotRegion(ylim = c(0, 1), factors = df1$region, track.height=0.1,
       panel.fun = function(x, y) {
         #select details of current sector
         name = get.cell.meta.data("sector.index")
         i = get.cell.meta.data("sector.numeric.index")
         xlim = get.cell.meta.data("xlim")
         ylim = get.cell.meta.data("ylim")

         #text direction (dd) and adjusmtents (aa)
         theta = circlize(mean(xlim), 1.3)[1, 1] %% 360
         dd <- ifelse(theta < 90 || theta > 270, "vertical_right", "vertical_left")
         aa = c(1, 0.5)
         if(theta < 90 || theta > 270)  aa =c(0, 0.5)

         #plot country labels
         circos.text(x=mean(xlim), y=1.7, labels=name, direction = dd,
         cex=0.6,adj = aa)
         #circos.text(x=mean(xlim), y=2, labels=name, facing = "bending",cex=0.6)             

         #plot main sector
         circos.rect(xleft=xlim[1], ybottom=ylim[1], xright=xlim[2], ytop=ylim[2], 
         col = df1$rcol[i], border=df1$rcol[i])

         #blank in part of main sector
         circos.rect(xleft=xlim[1], ybottom=ylim[1], xright=xlim[2]-rowSums(m)[i], ytop=ylim[1]+0.3, 
         col = "white", border = "white")

         #white line all the way around
         circos.rect(xleft=xlim[1], ybottom=0.3, xright=xlim[2], ytop=0.32, col = "white", border = "white")

         #plot axis
         #NOTE: Ticks indicate the number of migrants in 100s.
         circos.axis(labels.cex=0.6, direction = "outside", major.at=seq(from=0,to=floor(df1$xmax)[i],by=5), 
         minor.ticks=1, labels.away.percentage = 0.15)
       })

Thank you very much for your help.

EDIT: I add a second part of the script, since it seems that it is necessary to solve the second issue.

#plot links
#create a new dataframe containing the long form of the matrix m
#add sum values to df1, marking the x-position of the first links out (sum1) and in (sum2). Updated for further links in loop below.
df1$sum1 <- colSums(m) #outflows
df1$sum2 <- numeric(n)
#create a data.frame of the flow matrix sorted by flow size, to allow largest flow plotted first
df2<-cbind(as.data.frame(m),orig=rownames(m),  stringsAsFactors=FALSE)
#long matrix
df2<-reshape(df2, idvar="orig", varying=list(1:n), direction="long", timevar="dest", time=rownames(m),  v.names = "m") 
df2<-arrange(df2,desc(m))
#keep only the largest flows to avoid clutter
df2<-subset(df2, m>quantile(m,0.925))
#plot links
for(k in 1:nrow(df2)){
require("circlize")
#i,j reference of flow matrix
#note: you are selecting the states in region1 according to the edgelist in region2
i<-match(df2$orig[k],df1$region)
j<-match(df2$dest[k],df1$region)
#plot link
#sector.index1=sender
#point1=size of the base of the link at the origin
#We set the origin segment to start at the current sum of outflows from the sender country (df1$sum1[i])    
#We set the end of the segment outflow equal to the total outflows from the sender country, plus the flow from the edge we considered
circos.link(sector.index1=df1$region[i], point1=c(df1$sum1[i], df1$sum1[i] + abs(m[i, j])),
sector.index2=df1$region[j], point2=c(df1$sum2[j], df1$sum2[j] + abs(m[i, j])),
col = df1$lcol[i]) #, top.ratio==0.66, top.ratio.low==0.67)
#note: The height and thickness of the link at its mid-point is determined by the top.ratio and the top.ratio.low argument
#update sum1 and sum2 for use when plotting the next link
df1$sum1[i] = df1$sum1[i] + abs(m[i, j])
df1$sum2[j] = df1$sum2[j] + abs(m[i, j])
}
like image 245
sezonzo Avatar asked Jan 10 '15 01:01

sezonzo


3 Answers

You may check chordDiagram() in current version of circlize. chordDiagram() is a flexible function which makes such Chord Diagram (i.e. a circular plot with links inside). This function was introduced in recent versions of circlize and you don't need too much code to self-define a Chord Diagram. There is also a vignette shipped with the package which gives you detailed introduction of how to make a simple or complex Chord Diagram.

A quick demonstration of reproducing the figure you are interested is as follows:

mat = matrix(runif(36), 6)
rownames(mat) = letters[1:6]
colnames(mat) = letters[1:6]

library(circlize)
circos.par(gap.degree = 8)
chordDiagram(mat, grid.col = 1:6, directional = TRUE, annotationTrack = "grid",
  preAllocateTracks = list(list(track.height = 0.05),
                           list(track.height = 0.05)))
circos.trackPlotRegion(track.index = 1, panel.fun = function(x, y) {
  xlim = get.cell.meta.data("xlim")
  ylim = get.cell.meta.data("ylim")
  sector.index = get.cell.meta.data("sector.index")
  circos.text(mean(xlim), mean(ylim), sector.index, facing = "inside", niceFacing = TRUE)
}, bg.border = NA)
circos.trackPlotRegion(track.index = 2, panel.fun = function(x, y) {
  circos.axis("bottom", major.tick.percentage = 0.2, labels.cex = 0.4)
}, bg.border = NA)
circos.clear()

enter image description here

like image 103
Zuguang Gu Avatar answered Oct 31 '22 18:10

Zuguang Gu


The first problem concerning the notes messages is not really a problem. You can still get the plot you want regardless of the warning. You can subdue the message using

circos.par(points.overflow.warning=FALSE)

The second problem is a result of a recent update to the circlize package. You need to change the track margins from their initial settings by adding

circos.par(track.margin=c(0,0)) 

after circos.trackPlotRegion command and before the links are drawn via the circos.link function in the for loop.

Sorry for the problems. I have been meaning to update the demo file in the migest package for a while now but sort of forgot about it over the holidays.

like image 33
guyabel Avatar answered Oct 31 '22 18:10

guyabel


As the author explained here

"points.overflow.warning" Since each cell is in fact not a real plotting region but only an ordinary rectangle, it does not eliminate points that are plotted out of the region. So if some points are out of the plotting region, circlize would continue drawing the points and printing warnings. In some cases, draw something out of the plotting region is useful, such as draw some legend or text. Set this value to FALSE to turn off the warnings.

by canvas.xlim and canvas.ylim(described in the same page above), you can set up your canvas in order to avoid, or just ignore the warning.

like image 32
rankthefirst Avatar answered Oct 31 '22 19:10

rankthefirst