Analysis of the data on Presidents of Peru

We will use the list of Presidents of Peru from a Wikipedia page, to play a bit with some cool R packages (XML, dplyr, lubridate, ggplot2, and googleVis), which will be used to extract and clean up the data, and later make some summaries and plots.


For this experiment, we will need the following libraries

  • XML: to parse and extract a table from an HTML page
  • dplyr: to do some data manipulation
  • lubridate: to do some date operations
  • ggplot2: to generate a nice boxplot
  • googleVis: to make some interactive tables and plots (I am using the development version from github)

If you don't have them installed, then you might want to run:

install.packages(c("XML", "dplyr", "lubridate", "ggplot2"))
# pre-requisites for the development version of googleVis
install.packages(c("devtools","RJSONIO", "knitr", "shiny", "httpuv"))

Getting and mangling the data

First, let's read the data from the third HTML table in Wikipedia's page: "List of Presidents of Peru"

src <- ""
doc = htmlParse(src, encoding = "UTF-8")
tables <- readHTMLTable(doc)
# the table we need is the third one
t3 <- tables[[3]]

Then, we ought to fix some weirdness in the data, and will save it to a CSV just in case we want to do some more processing in the future. As we are keeping the original column names from the HTML table, some code is a bit more cumbersome (because we need to use backticks).

# We do not need column #2, which contains an image
# also, let's reorder the columns
t3 <- t3[,c(3,6,7,4,5)]

# convert to dates the start and end term columns
fix_date <- function(x) {
    return(as.Date(strptime(x, format="%B %e, %Y")))
t3[c("Inaugurated","Left office")] <- lapply(t3[c("Inaugurated","Left office")], fix_date)

# cleanup random stuff in []s in a couple of columns
t3$`Form of entry` <- sub("\\[7\\]", "", gsub("\n", " - ", t3$`Form of entry`))
t3$President <- gsub("\\[.+\\]", "", as.character(t3$President))

# add the regular end of term (5 yrs) for the current president
last <- nrow(t3)
if ([last,]$`Left office`)) {
  tmp <- t3[last,]$Inaugurated
  year(tmp) <- year(tmp) + 5  # normal presidential term: 5 years
  t3[last,]$`Left office` <- tmp

Displaying the data as a sortable and paginated table

Let's look at the data we got after scraping Wikipedia and mangling values around. We'll make an interactive table using the gvisTable function from the googleVis package.

We want to paginate the table, because Peru has had 97 people that held the Presidency at one point or another. The table is a bit wide, so it will look nicer.

opts <- list(width=800, height=330, showRowNumber=TRUE, page="enable")
presidents_table <- gvisTable(t3, options=opts)
print(presidents_table, "chart")

Creating a timeline chart

Now, let's visualize the succession of presidents using a timeline chart as implemented in googleVis, coloring each timespan by the what original data calls "Form of entry", which is how a particular person got into the Presidency. There are 3 records that do not have a given value for the aforementioned field, so we will recode those as "Unknown".

This chart is also a bit wide, because the data spans over 190 years.

t3[t3[, 2] == "", 2] <- "Unkown"
presidents_timeline <- gvisTimeline(
    t3, rowlabel="President", start="Inaugurated", end="Left office",
    barlabel="Form of entry", options=list(height=500, width=800))
print(presidents_timeline, "chart")

You might have noticed that at some points in Peru's history we had more than one President, and at other times they seem to change rapidly or to swing back and forth among a number of recurring characters. Such was our lot back then, but we have had better luck for some decades now.

Understanding how they got into power

We will make cummulative frequency chart, by using dplyr to manipulate and summarize the data and googleVis to plot it. We could've used table() along with other base functions, but dplyr's syntax is cleaner and more readable.

# group by "Form of entry", get the counts per group, and sort the
# data frame in descending order of counts
t3_summary <- t3 %>% group_by("Form of entry") %>%
    summarise(count=n()) %>%
    arrange(-count, `Form of entry`) %>%
    mutate(`Cummulative frequency`=round(100*cumsum(count)/sum(count),2))

# make the cummulative frequency chart and print it
t3_summary_chart <- gvisLineChart(
    t3_summary, xvar="Form of entry", yvar="Cummulative frequency",
    options=list(height=400, width=800, pointSize=5,
                 title="How peruvian presidents got into office",
                 vAxis="{title:'Cummulative frequency (%)'}",
                 hAxis="{title:'Mode of attaining office'}",
print(t3_summary_chart, "chart")

In this chart we can plainly see that the first 4 modes of attaining office ("Direct Elections", "Coup d'état", "Interim caretaker", and "Elected by Congress"), comprise the majority (a bit over 81%) of all the ways that the office of President have ever been attained in Peru.

Length of time in office

If we wanted to know the distribution of the lengths of time in office for all presidents, we can do some simple data exploration and create a histogram, with the the median and mean overlayed on it:

# length of time in office
lio_days <- as.numeric(t3$`Left office` - t3$Inaugurated + 1)  # in days
lio_yrs <- lio_days / 365.25   # in years
lio_yrs_mean <- round(mean(lio_yrs),2)
lio_yrs_median <- round(median(lio_yrs),2)
hist(lio_yrs, main="Distribution of time in office", xlab="Time span (years)")
abline(v=lio_yrs_mean, col="red", lwd=2, lty="dashed")
abline(v=lio_yrs_median, col="blue", lwd=2, lty="dashed")
text(x=c(lio_yrs_mean+.1, lio_yrs_median+.1), y=c(25,45),
     labels=c(paste0("Mean=", lio_yrs_mean), paste0("Median=", lio_yrs_median)),
     pos=4, col=c("red", "blue"))

plot of chunk unnamed-chunk-4

We can see a typical right-skewed distribution, with a great majority of short lengths of term in office (as little as 2 days), and some exceptionally long ones (as much as ~11.15 years). So in this case, the mean (2.04 years) is not very informative, and the median (0.96 years) looks suspiciosly short.

Let's look at these time spans groupíng them by the way each one attained the office.

t3$len_office <- lio_yrs
# generate a grouping variable based on the form of entry
t3$group <- gsub("^([^-]+) -(.*)", "\\1", t3[,"Form of entry"], fixed=FALSE)
# combine the forms of entry with counts less than 10
tg <- table(t3$group)
otherlvl <- names(tg[tg < 10])
t3[t3$group %in% otherlvl,]$group <- "Other"
# reorder the grouping column by increasing count
tg <- table(t3$group)
t3$group <- factor(as.character(t3$group), levels=names(tg[order(tg)]))
# Make boxplots for each grouping factor
t3_plot <- ggplot(t3, aes(group, len_office)) +
    geom_hline(yintercept=5, colour="gray", linetype="longdash") +
    geom_boxplot(aes(colour=group)) +
    ggtitle("Distributions of Peruvian President's terms in office") +
    coord_flip() + ylab("Length in office (years)") + xlab("How office was attained") +
    theme_bw() + theme(legend.position="none")

plot of chunk lengthoffice

In this chart we have added a reference line, the official time span for a President's term in office in Peru: 5 years. It would seem that if you got into office by "Direct Elections" you have a better chance to reach you usual term (median ~ 4 years), but if you got by another route (let's say by "Coup d'état") you are more likely to be there for a short time.

In the table below, we can see a set of summary statistics per group, which indicate a distinctive difference between them.

t3_grouped <- t3 %>% group_by(group) %>%
    summarise(n=n(), avg=mean(len_office), sd=sd(len_office), median=median(len_office),
              min=min(len_office), max=max(len_office), iqr=IQR(len_office)) %>%
t3_grouped[-1] <- sapply(t3_grouped[-1], function (x) { round(x,3) })
t3_grouped_table <- gvisTable(t3_grouped,
                              options=list(width=800, height=200))
print(t3_grouped_table, "chart")

In fact, using a Kruskal-Walis rank sum test, seems to indicate that the groups are indeed different (p < 0.001).

kruskal.test(len_office ~ group, data=t3)
    Kruskal-Wallis rank sum test

data:  len_office by group
Kruskal-Wallis chi-squared = 38.46, df = 4, p-value = 8.997e-08

There might be a moral in this data, but policital conclusions run the risk of degenerating in random rants, so I'll skip that.

Reproducibility information

The source code for this document is available at

This post was originally published in RPubs at This version contains syntax changes in current versions of dplyr where the "piping" operator is now %>%

R version 3.1.1 (2014-07-10)
Platform: x86_64-pc-linux-gnu (64-bit)

 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C
 [3] LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8
 [7] LC_PAPER=en_US.UTF-8       LC_NAME=C
 [9] LC_ADDRESS=C               LC_TELEPHONE=C

attached base packages:
[1] methods   stats     graphics  grDevices utils     datasets  base

other attached packages:
[1] googleVis_0.5.2 ggplot2_1.0.0   lubridate_1.3.3 dplyr_0.2
[5] XML_3.98-1.1    knitr_1.6

loaded via a namespace (and not attached):
 [1] assertthat_0.1   colorspace_1.2-2 digest_0.6.4     evaluate_0.5.5
 [5] formatR_0.10     grid_3.1.1       gtable_0.1.2     labeling_0.2
 [9] magrittr_1.0.1   MASS_7.3-33      memoise_0.2.1    munsell_0.4.2
[13] parallel_3.1.1   plyr_1.8.1       proto_0.3-10     Rcpp_0.11.2
[17] reshape2_1.4     RJSONIO_1.2-0.2  scales_0.2.4     stringr_0.6.2
[21] tools_3.1.1
Go Top