COVID-19 visualizations with Stata Part 9: Customized bar graphs
By: Asjad Naqvi
Tags: Stata, Covid 19, Automation, Bar Graph, Colors

The choice of vertical or horizontal bars is a matter of taste. If space is limited, then horizontal bars give the space to stretch the data across the page width.

One can also generate stacked bar graphs:

graph bar (sum) new_cases*, over(month) percentages stack ///

ytitle(Percentage) ylabel(, format(%12.0fc))

which gives us this figure:

Stacked graphs sum up to 100% and are useful to look at the distribution and the variable shares but don’t say anything about the total.

Next we need to deal with two elements; (a) colors and (b) legend labels.

Let’s deal with the colors first. As shown in the previous guides, the colorpalette package by Ben Jann, opens up endless possibilities to utilize colors. Below we plot the graph using the default Stata s2 color scheme:

colorpalette s2, n(5) nograph graph bar (sum) new_cases*, over(month) stack ///

ytitle(Cases) ylabel(, format(%12.0fc)) ///

bar(1, fcolor("`r(p1)'") lcolor(black) lwidth(vvthin)) ///

bar(2, fcolor("`r(p2)'") lcolor(black) lwidth(vvthin)) ///

bar(3, fcolor("`r(p3)'") lcolor(black) lwidth(vvthin)) ///

bar(4, fcolor("`r(p4)'") lcolor(black) lwidth(vvthin)) ///

bar(5, fcolor("`r(p5)'") lcolor(black) lwidth(vvthin))

which gives us this familiar looking figure:

Notice how the colors are passed on to the graph using the locals generated from the colorpalette package. These are discussed in detail in Guide 2 and the Color guide posts.

We can also use other color palettes defined by the colorpalette package. For example, here is the matplotlib viridis color scheme:

colorpalette viridis, n(5) nograph graph bar (sum) new_cases*, over(month) stack ///

ytitle(Cases) ylabel(, format(%12.0fc)) ///

bar(1, fcolor("`r(p1)'") lcolor(black) lwidth(vthin)) ///

bar(2, fcolor("`r(p2)'") lcolor(black) lwidth(vthin)) ///

bar(3, fcolor("`r(p3)'") lcolor(black) lwidth(vthin)) ///

bar(4, fcolor("`r(p4)'") lcolor(black) lwidth(vthin)) ///

bar(5, fcolor("`r(p5)'") lcolor(black) lwidth(vthin))

which gives us this image:

or we go fully custom and define our own color scheme using RGB values. For example, here is a color scheme that we previous used in the Color guide post, where we extracted the color from this page:

colorpalette ///

"253 253 150" ///

"255 197 1" ///

"255 152 1" ///

" 3 125 80" ///

" 2 75 48" ///

, n(5) nograph graph bar (sum) new_cases*, over(month) stack ///

ytitle(Cases) ylabel(, format(%12.0fc)) ///

bar(1, fcolor("`r(p1)'") lcolor(black) lwidth(vvthin)) ///

bar(2, fcolor("`r(p2)'") lcolor(black) lwidth(vvthin)) ///

bar(3, fcolor("`r(p3)'") lcolor(black) lwidth(vvthin)) ///

bar(4, fcolor("`r(p4)'") lcolor(black) lwidth(vvthin)) ///

bar(5, fcolor("`r(p5)'") lcolor(black) lwidth(vvthin))

Here we see a bar graph that also looks a bit more appealing with the graduated colors:

Once we have the colors in place, the next step is to automate the legend labels.

Automate all the elements

This process of generating an automated legend for bar graphs is not trivial. Since these are multiple locals involved, the code block is provided below which has to run in one go, and the explanations are given underneath:

ds new_cases*

local items : word count `r(varlist)' display `item'

local colors = `items' + 1 colorpalette ///

"253 253 150" ///

"255 197 1" ///

"255 152 1" ///

" 3 125 80" ///

" 2 75 48" ///

, n(`colors') nograph foreach x of numlist 1/`items' {



*** here the code for bar colors

local barcolor `barcolor' bar(`x', fcolor("`r(p`x')'")

lcolor(black) lwidth(*0.4)) `///'





*** here the code for legend



local mylab : var lab new_cases`x'

local legend `legend' lab(`x' "`mylab'") }





graph bar (sum) new_cases*, ///

over(month, axis(lcolor(none))) percentages stack ///

`barcolor' ///

blabel(bar, size(vsmall) position(center) format(%12.0fc)) ///

legend(`legend' rows(1) size(small) pos(6))

The first two line pick the number of variables that have the name new_cases* . The macro local items : word count `r(varlist)' counts the number of variables. In theory we have as many variables as we want. The local items is used to define the number of colors which are just the number of items + 1. This is to avoid very dark shades that occur at the end of the color schemes. This step is not really necessary but is more of a fine-tuning of the graph. The colorpalette command generates the required number of colors using the colors local.

We then iteratively generate the graph elements using a loop. Since we have already the number of variables in the local items , the foreach command is used to loop over the variables. The loop generates two things. First, are the bar colors. Each bar is given a value according to the item position on the colorpalette scheme. Here we also define the line color. To break the line, we use the three forward slashes /// . Since this is a non-standard application of loops and line breaks, the forward slashes are given in quotations to prevent the code from thinking that a line break in the code exists here. This had to be discovered with some code experimentation.

The second part of the code looks complex but this is what it does: it picks the variable label from the variable name, stores it in a macro, and from this generates an entry that looks like this lab(1 "Africa") lab(2 "Asia") and so on. This is an alternative way of defining an appendix label and gives a nice symmetry and order to the legend generation. Note that this step could also have been done manually since we only have five continents but here the aim is to learn how to automate these steps. For example, one can split the continents to generate new categories. For example, North America can be split into USA, and the rest of North America, or Asia can be split into India, China, and the rest of Asia etc. This will be discussed in a subsequent guide.

From the code above, we get this graph below:

were the percentages are also labeled in the center of the graph. Note that with very small bars, the numbers can get squished a bit but here there is little option to individually customize bars. One can theoretically program the bar graphs completely using the standard twoway graph commands for a full control over all the elements, but this we will leave for another guide.

Below, we repeat the code to generate a horizontal bar graph:

ds new_cases*

local items : word count `r(varlist)' display `item'

local colors = `items' + 1 colorpalette ///

"253 253 150" ///

"255 197 1" ///

"255 152 1" ///

" 3 125 80" ///

" 2 75 48" ///

, n(`colors') nograph foreach x of numlist 1/`items' {



*** here the code for bar colors

local barcolor `barcolor' bar(`x', fcolor("`r(p`x')'")

lcolor(black) lwidth(*0.3)) `///'





*** here the code for legend

local mylab : var lab new_cases`x'

local legend `legend' lab(`x' "`mylab'")

} graph hbar (sum) new_cases*, ///

over(month, axis(lcolor(none))) stack ///

`barcolor' ///

ylabel(, format(%12.0fc)) ///

legend(`legend' rows(1) size(small) pos(6))

which looks like this:

We can also customize the fonts to generate a graph that has a bit more information and looks balanced in terms of fonts and colors. Here we define the final piece of code for our main figure:

graph set window fontface "Courier New" ds new_cases*

local items : word count `r(varlist)' display `item'

local colors = `items' + 1 colorpalette ///

"253 253 150" ///

"255 197 1" ///

"255 152 1" ///

" 3 125 80" ///

" 2 75 48" ///

, n(`colors') nograph foreach x of numlist 1/`items' {





*** here the code for bar colors

local barcolor `barcolor' bar(`x', fcolor("`r(p`x')'")

lcolor(black) lwidth(*0.1)) `///'





*** here the code for legend

local mylab : var lab new_cases`x'

local legend `legend' lab(`x' "`mylab'")

}

*** the final graph we want: graph bar (mean) new_cases*, ///

over(month, label(labsize(small)) axis(lcolor(none))) bargap(-20) ///

`barcolor' ///

ytitle(Cases) ylabel(, format(%12.0fc)) ///

blabel(bar, size(1.7) orientation(vertical) margin(vsmall) format(%12.0fc)) ///

legend(`legend' col(1) size(vsmall) pos(11) ring(0) region(fcolor(none))) ///

title("{fontface Arial Bold: Monthly distribution of COVID-19 cases by Continent}") ///

xsize(2) ysize(1)

where we set the font to “Courier New”, which is a monospaced font. In monospaced fonts all the letters have the same width. This classic font also gives an old typewriter feel. The header is also customized and uses Arial Bold, an easy-to-read sans-Serif font. We also label the bars and make them overlap a bit to give it a finishing touch. And from all of the code above, we get this graph below: