Circular Migration Flow Plots in R

Please see this blog post on updated version of circular plots for migration flows, based on global estimates for 2010-15.

A article of mine was published in Science today. It introduces estimates for bilateral global migration flows between all countries. The underlying methodology is based on the conditional maximisation routine in my Demographic Research paper. However, I tweaked the demographic accounting which ensures the net migration in the estimated migration flow tables matches very closely to the net migration figures from the United Nations.

My co-author, Nikola Sander, developed some circular plots for the paper based on circos in perl. A couple of months back, after the paper was already in the submission process, I figured out how to replicate these plots in R using the circlize package. Zuguang Gu, the circlize package developer was very helpful, responding quickly (and with examples) to my emails.

To demonstrate, I have put two demo files in my migest R package. For the estimates of flows by regions, users can hopefully replicate the plots (so long as the circlize and plyr packages are installed) using:

demo(cfplot_reg, package = "migest", ask = FALSE)

It should result in the following plot:
The basic idea of the plot is to show simultaneously the relative size of estimated flows between regions. The origins and destinations of migrants are represented by the circle’s segments, where nearby regions are positioned close to each other. The size of the estimated flow is indicated by the width of the link at its bases and can be read using the tick marks (in millions) on the outside of the circle’s segments. The direction of the flow is encoded both by the origin colour and by the gap between link and circle segment at the destination.

You can save the PDF version of the plot (which looks much better than what comes up in my R graphics device) using:

dev.copy2pdf(file = "cfplot_reg.pdf", height=10, width=10)

If you want to view the R script:"demo/cfplot_reg.R", package = "migest"))

In Section 5 of our Vienna Institute of Demography Working Paper I provide a more detailed breakdown for the R code in the demo files.

A similar demo with slight alterations to the labelling is also available for a plot of the largest country to country flows:

demo(cfplot_nat, package = "migest", ask = FALSE)


If you are interested in the estimates, you can fully explore in the interactive website (made using d3.js) at There is also a link on the website to download all the data. Ramon Bauer has a nice blog post explaining the d3 version.

Publication Details:

Abel, G.J. and Sander, N. (2014). Quantifying Global International Migration Flows. Science. 343 (6178), 1520–1522.

Widely available data on the number of people living outside of their country of birth do not adequately capture contemporary intensities and patterns of global migration flows. We present data on bilateral flows between 196 countries from 1990 through 2010 that provide a comprehensive view of international migration flows. Our data suggest a stable intensity of global 5-year migration flows at ~0.6% of world population since 1995. In addition, the results aid the interpretation of trends and patterns of migration flows to and from individual countries by placing them in a regional or global context. We estimate the largest movements to occur between South and West Asia, from Latin to North America, and within Africa.

34 thoughts on “Circular Migration Flow Plots in R”

  1. Hello Guy,

    Congratulations on the publication. I really like this visualization. I’ve installed migest and taken a look at the demos; however, I’m unclear how I might create a plot like this from my data. If I have a square connectivity or migration matrix, can I use your migest package to create (and tweak) a plot such as the ones you’ve shown here?


    1. Hi Dan. Take a look at Section 5 of the Vienna Institute of Demography Working Paper (the link is in the post above, below the first plot). There I explain all the lines of the R code in the demo files, based on the original 10 by 10 origin-destination flow table read from a .txt file. Let me know if there are anything that remains unclear? We are going to submit the paper to journal fairly soon, so any feedback would be great.

  2. Hi Dan,
    a friend of mine posted your fantastic migration globe chart in Facebook and I was wondering if you could send me the file by any chance. I would love to print it out and put it up on my wall (in high quality, like a poster). I know its not what you had in mind but I already have a “future trend map” as wall decoration. It s a powerful visualization. Thanks. Cheers from Germany

      1. Hi Sabby.

        There is a poster based on my estimates, which includes a larger plot of country to country flows. The plot itself was done in Circos (rather than R) by my colleague. At the bottom of webpage you can find a link to the PDF (we call it a data sheet) and also a details on how anyone can request a hard copy, which I believe will post to you for free!

        If you still want to create a you own print of the R version of the plot, you can do so by running the demo script and printing the graphics device to a PDF as I show in the post above, but use much bigger arguments for the height and width, something like dev.copy2pdf(file = “cfplot_reg.pdf”, height=40, width=40) . This will give you a 40 inch square plot saved in you current R working directory.

        Hope this helps?


  3. Dear Guy,
    Just used your idea, and made a graph on myself! Incredible graph, it has a lot of explanatory power. Simply love it.

  4. Hi Guy,
    That’s a beautiful graph. I would like make a similar graph using our data, however, I received this error message “Detect some gaps are too large”. You also mentioned this error in one of your reports. Do you recall how you fixed it? Thanks so much.

  5. Hi Anthony. I guess have a fiddle with the circos.par, in particular the argument? In the plots above I had circos.par(cell.padding=c(0,0,0,0), track.margin=c(0,0.1), = 90, =4).

  6. Hi Guy,

    I have tried the parameters as you suggested, but still does not work. Do you think because some values are very small as compare to others (e.g.; 1 versus 1812)?. I made a few changes to my data as in matrix 2 by adding a few zeros after a 1 or 2 and it works. Is there any way to work around with this a large range? I would like to plot this beautiful graph using my real data (matrix 1). Any help is much appreciated.



    #matrix 1
    #level0 <- c(1, 8, 39, 14, 2)
    #level1 <- c(1, 19, 153, 93, 1)
    #level2 <- c(2, 19, 274, 46, 13)
    #level3 <- c(0, 8, 152, 1812, 465)
    #level4 <- c(0, 2, 1, 164, 226)

    #matrix 2
    #level0 <- c(100,8,39,14,200)
    #level1 <- c(100,190, 153,93,100)
    #level2 <- c(200,19,274,646,130)
    #level3 <- c(200,800,152,1812,465)
    #level4 <- c(200,200,100,164,226)

    #build matrix 2
    a <- list(c(100,8,39,14,200),c(100,19, 153,93,100), c(200,19,274,646,13), c(200,8,152,1812,465),c(200,200,100,164,226))
    mat <-, a)
    mat <-, a)
    mat <- rbind(level0,rbind(level1,rbind(level2,rbind(level3,level4))))
    #mat = matrix(sample(1:100, 25, replace = TRUE), 5, 5)
    rownames(mat) = c("level 0", "level 1", "level 2", "level 3", "level 4")
    colnames(mat) = c("Level0", "Level1", "Level2", "Level3", "Level4")
    rn = rownames(mat)
    cn = colnames(mat)

    factors = c(rn, rev(cn))
    factors = factor(factors, levels = factors)
    col_sum = apply(mat, 2, sum)
    row_sum = apply(mat, 1, sum)
    xlim = cbind(rep(0, 10), c(row_sum, col_sum))

    par(mar = c(1, 1, 1, 1))
    circos.par(cell.padding = c(0, 0, 0, 0), clock.wise = FALSE, track.margin=c(0,0.1), = 4, =90)
    circos.initialize(factors = factors, xlim = xlim
    , sector.width = c(row_sum/sum(row_sum), col_sum/sum(col_sum)))
    circos.trackPlotRegion(factors = factors, ylim = c(0, 1), bg.border = NA,
    # bg.col = c("red", "orange", "yellow", "green", "blue", rep("grey", 5)), track.height = 0.05,
    bg.col = c(c("red", "orange", "yellow", "green", "blue"),
    c("blue", "green", "yellow", "orange", "red")), track.height = 0.05, = function(x, y) { ="sector.index")
    xlim ="xlim")
    circos.text(mean(xlim), 3,, adj = c(0.5, 0))
    circos.axis(labels.cex=0.8, direction="outside", labels.away.percentage=0.5)
    if( %in% rn) {
    for(i in seq_len(ncol(mat))) {
    circos.lines(rep(sum(mat[, seq_len(i)]), 2), c(0, 1),
    col = "white")
    } else if( %in% cn) {
    for(i in seq_len(nrow(mat))) {
    circos.lines(rep(sum(mat[ seq_len(i),]), 2), c(0, 1),
    col = "white")
    col = c("#FF000020", "#FFA50020", "#FFFF0020", "#00FF0020", "#0000FF20")
    for(i in seq_len(nrow(mat))) {
    for(j in seq_len(ncol(mat))) {[i], c(sum(mat[i, seq_len(j-1)]), sum(mat[i, seq_len(j)])),
    cn[j], c(sum(mat[seq_len(i-1), j]), sum(mat[seq_len(i), j])),
    col = col[i], border = "white")

    1. Hi Anthony. Do you want to post your question/code on stackoverflow, send us the link and I will answer there. I have managed to a plot with your data, but it won’t display very well in the wordpress comments section. Guy

  7. Hi gjabel! I love this graph. I am trying to replicating this with my data which is a country level trade data. To get an idea of how your code works, I tried your code posted at . But this seems to cause an error which I am unable to resolve.
    Error in match.arg(facing) :
    ‘arg’ should be one of “inside”, “outside”, “reverse.clockwise”, “clockwise”, “downward”, “bending”

    I get this after I run the ‘plot sectors’ snippet. Googling on this didn’t help. Any idea why this is happening?

    1. Hi Vanathi. They makers of the circlize package updated some of their functions, so for the moment the demo won’t work…. i have updated the files in a new version (1.6) of the migest package which should be on CRAN in the next few days. If you can’t wait, the discussions in the comments section my answer on the stackoverflow question that you point to explain the same (two) changes I just made to get the demo scripts working error free. You can copy and paste the code in the R scripts of the demo file directory of the current migest (1.5) version you have installed, add in the edits, and then run the script as you would any normal R code. Hope this helps. Guy

  8. Hey Guy, this is probably the best example i’ve come across that allows someone to simply replicate a customised chord chart using circlize. I am wondering whether you’ve had any experience with a rectangular matrix (e.g 8×10) instead of square matrix. Further to this is it possible to put the ‘circos’ in some kind of object/variable that can be printed later similar to a ggplot?

    1. Thanks Dan. I imagine a rectangular matrix should be pretty straight forward, if your row and columns are part of the same set of regions (its just a square matrix with a couple of rows containing zeros). I am not sure about the ggplot style object. I guess not, but probably best to contact the circlize developers. I have found they are really helpful with their responses.

  9. Hi Guy,
    thank you very much for your evaluable effort to share your work.
    I’m trying to replicate your plots using the script available at, but unfortunately there is something going wrong.
    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 I was using “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.
    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.
    Would you be so kind to give me some hints? I posted the same question in stackoverflow ( so that it may be easier to communicate.
    Again thank you very much

  10. Hi gjabel
    Thanks for such a wonderful visualistion. I am also trying to perform similar kind of study although its on SNPs sharing .. I have 14 samples and i know the shared section among all the possible combination with in them. Can you library be used to visualise this kind of study as Venn Diagram are not feasible to understand this situation.Please reply ASAP

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s