avatarAsjad Naqvi

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

11996

Abstract

js-operator">=</span><span class="hljs-string">"Congo DRC"</span> replace country<span class="hljs-operator">=</span><span class="hljs-string">"Russia"</span> if country<span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-string">"Russian Federation"</span> replace country<span class="hljs-operator">=</span><span class="hljs-string">"Palestine"</span> if country<span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-string">"Palestinian Territory"</span></pre></div><div id="58fc"><pre>*** <span class="hljs-built_in">merge</span> <span class="hljs-keyword">with</span> <span class="hljs-keyword">the</span> policies database </pre></div><div id="dec8"><pre><span class="hljs-built_in">merge</span> <span class="hljs-number">1</span>:m country using ../master/COVID_policies2 <span class="hljs-built_in">drop</span> <span class="hljs-keyword">if</span> <span class="hljs-variable">_m</span>==<span class="hljs-number">2</span> <span class="hljs-built_in">drop</span> <span class="hljs-variable">_m</span></pre></div><p id="3156">Note that a bunch of small island states won’t match. They can be dealt with by merging them with another, more extensive map file. This is unfortunately a standard problem for these small islands as they tend to disappear in the sea of large countries. They are also hardly visible on global maps.</p><p id="49d5">In the next step, we generate a basic map for the last date we have in the dataset and plot the HCI:</p><div id="9496"><pre>summ <span class="hljs-type">date</span> spmap Index <span class="hljs-keyword">using</span> world_shp <span class="hljs-keyword">if</span> <span class="hljs-type">date</span>==r(max)<span class="hljs-comment">', ///</span> id(_ID) clmethod(<span class="hljs-keyword">custom</span>) clbreaks(<span class="hljs-number">0</span>(<span class="hljs-number">10</span>)<span class="hljs-number">100</span>) fcolor(Blues2)</pre></div><div id="9b39"><pre>graph <span class="hljs-built_in">export</span> ../graphs/animation/map1.png, replace wid(2000)</pre></div><p id="8d75">which gives us this default image:</p><figure id="b8dd"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*0hpwSBqJ6vw78O4sMGSmbA.png"><figcaption></figcaption></figure><p id="fc7a">The next code block, fine tunes this map by using a custom color scheme defined by the <code>colorpalette</code> package (introduced in <a href="https://readmedium.com/covid-19-visualizations-with-stata-part-2-customizing-color-schemes-206af77d00ce">Guide 2</a>):</p><div id="32bf"><pre>local <span class="hljs-built_in">date</span>: <span class="hljs-keyword">display</span> %tdd_m_yy <span class="hljs-built_in">date</span>(c(current_date), <span class="hljs-string">"DMY"</span>) <span class="hljs-keyword">display</span> <span class="hljs-string">"date'"</span></pre></div><div id="0a52"><pre>colorpalette matplotlib viridis, n(<span class="hljs-number">10</span>) <span class="hljs-built_in">reverse</span> nograph <span class="hljs-keyword">local</span> colors r(p)'</pre></div><div id="3415"><pre><span class="hljs-attribute">summ</span> date <span class="hljs-attribute">spmap</span> Index using world_shp if date==r(max)', /// <span class="hljs-attribute">id</span>(_ID) /// <span class="hljs-attribute">clmethod</span>(custom) clbreaks(<span class="hljs-number">0</span>(<span class="hljs-number">10</span>)<span class="hljs-number">100</span>) fcolor(<span class="hljs-string">"colors'"</span>) /// <span class="hljs-attribute">ocolor</span>(white ..) osize(*<span class="hljs-number">0</span>.<span class="hljs-number">15</span> ..) /// <span class="hljs-attribute">ndocolor</span>(black) ndfcolor(gs14) ndlabel(<span class="hljs-string">"No data"</span>) ndsize(*<span class="hljs-number">0</span>.<span class="hljs-number">15</span> ..) /// <span class="hljs-attribute">legend</span>(pos(<span class="hljs-number">7</span>) size(*<span class="hljs-number">0</span>.<span class="hljs-number">8</span>)) legstyle(<span class="hljs-number">2</span>) /// <span class="hljs-attribute">title</span>(<span class="hljs-string">"COVID-19 Policy Stringency Index (date')"</span>, size(medium)) /// <span class="hljs-attribute">note</span>(<span class="hljs-string">"Data source: Oxford COVID-19 Government Response Tracker. Mercator projection used. Antartica dropped from maps."</span> , size(*<span class="hljs-number">0</span>.<span class="hljs-number">5</span>)) </pre></div><div id="5ec9"><pre>graph <span class="hljs-built_in">export</span> ../graphs/animation/map2.png, replace wid(2000)</pre></div><p id="ca43">In the code above, we auto generate the date and add it to the title. We also define the cut-offs in steps of ten, customize boundaries, no data zones, line widths etc. and get the following map:</p><figure id="5989"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*RyMpotGwo3v4rB6IdbsYyw.png"><figcaption></figcaption></figure><p id="9563">In the graph above, the green shades in the middle HCI range are hard to differentiate, while the two yellowish tones, that show the bottom two categories, stand out a lot. The HCI range roughly between 20–50 is considered fairly weak on the aggregate level (individual dimensions of the index may still vary). It is only above 50 and 60 where one can consider policies to be strong, and above scores above 70 or 80 are considered to be very strong, well thought-out responses.</p><p id="382f">To highlight this point on the map, we can use a <i>power shift</i> to move the color tones around. This can be achieved through a manual interpolation of colors as follows:</p><div id="f016"><pre>colorpalette: matplotlib viridis, <span class="hljs-built_in">ipolate</span>(<span class="hljs-number">10</span>) <span class="hljs-built_in">name</span>(<span class="hljs-string">"Even"</span>) / <span class="hljs-comment">///</span> matplotlib viridis, <span class="hljs-built_in">ipolate</span>(<span class="hljs-number">10</span>, <span class="hljs-built_in">power</span>(<span class="hljs-number">0.5</span>)) <span class="hljs-built_in">name</span>(<span class="hljs-string">"Shift left"</span>) / <span class="hljs-comment">///</span> matplotlib viridis, <span class="hljs-built_in">ipolate</span>(<span class="hljs-number">10</span>, <span class="hljs-built_in">power</span>(<span class="hljs-number">1.6</span>)) <span class="hljs-built_in">name</span>(<span class="hljs-string">"Shift right"</span>) </pre></div><div id="a83c"><pre>graph <span class="hljs-built_in">export</span> ../graphs/animation/colorpalette.png, replace wid(2000)</pre></div><figure id="39b5"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*8Wv-EUGxv15rgD12iGVhQw.png"><figcaption></figcaption></figure><p id="2784">Here we see how the color interpolation changes relative to the evenly distributed color scheme. For our purposes, we will use the color bands labeled “Shift right” and redraw the map:</p><div id="28a0"><pre><span class="hljs-keyword">local</span> <span class="hljs-type">date</span>: display %tdd_m_yy <span class="hljs-type">date</span>(c(<span class="hljs-built_in">current_date</span>), "DMY") display "`date'"

colorpalette matplotlib viridis, ipolate(<span class="hljs-number">10</span>, power(<span class="hljs-number">1.6</span>)) <span class="hljs-keyword">reverse</span> nograph <span class="hljs-keyword">local</span> colors r(p)<span class="hljs-string">'</span></pre></div><div id="dbcc"><pre><span class="hljs-attribute">summ</span> date <span class="hljs-attribute">spmap</span> Index using world_shp if date==r(max)', /// <span class="hljs-attribute">id</span>(_ID) /// <span class="hljs-attribute">clmethod</span>(custom) clbreaks(<span class="hljs-number">0</span>(<span class="hljs-number">10</span>)<span class="hljs-number">100</span>) fcolor(<span class="hljs-string">"colors'"</span>) /// <span class="hljs-attribute">ocolor</span>(white ..) osize(*<span class="hljs-number">0</span>.<span class="hljs-number">15</span> ..) /// <span class="hljs-attribute">ndocolor</span>(black) ndfcolor(gs14) ndlabel(<span class="hljs-string">"No data"</span>) ndsize(*<span class="hljs-number">0</span>.<span class="hljs-number">15</span> ..) /// <span class="hljs-attribute">legend</span>(pos(<span class="hljs-number">7</span>) size(*<span class="hljs-number">0</span>.<span class="hljs-number">8</span>)) legstyle(<span class="hljs-number">2</span>) /// <span class="hljs-attribute">title</span>(<span class="hljs-string">"COVID-19 Health Containment Index (date')"</span>, size(medium)) /// <span class="hljs-attribute">note</span>(<span class="hljs-string">"Data source: Oxford COVID-19 Government Response Tracker. Mercator projection used. Antartica dropped from maps."</span> , size(<span class="hljs-number">0</span>.<span class="hljs-number">5</span>))</pre></div><div id="04dd"><pre>graph <span class="hljs-built_in">export</span> ../graphs/animation/map3.png, replace wid(2000)</pre></div><p id="b2cb">which gives us this image:</p><figure id="d464"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*qDDcZHQm6SiU3_vC9I6jYw.png"><figcaption></figcaption></figure><p id="1cff">Here the policies are a bit more differentiated than the map with evenly distributed colors and the middle policy range countries are easier to identify.</p><p id="5ff1">Now that we have the color template in place, we need to generate the maps for each date. But before we loop over all the dates, we need to make sure that the dates are in a format such that they can be added in the title as well. This can be achieved in several different ways, but for this guide, we will do it using the custom <code>labmask</code> package:</p><div id="d8bf"><pre><span class="hljs-keyword">ssc</span> install labmask, <span class="hljs-keyword">replace</span> <span class="hljs-comment">// install the custom package</span></pre></div><div id="9b0e"><pre>gen sdate = <span class="hljs-built_in">string</span>(<span class="hljs-built_in">date</span>, <span class="hljs-string">"%tdd_m_yy"</span>) <span class="hljs-comment">// define the date format</span> labmask <span class="hljs-built_in">date</span>, values(sdate) <span class="hljs-comment">// mask the values</span></pre></div><p id="97a6">Here we generate a string variable called <i>sdate</i>, which has the dates in the correct display format. We then take this string variable and mask it on the date variable as value labels. This literally takes the string in each row and converts it to a label for a date variable:</p><figure id="db04"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*4Ggjcb0lQOUcXG3dDNUHXg.png"><figcaption></figcaption></figure><p id="ad5d">We can now use this information to generate maps for each day using the following loop:</p><div id="9bf3"><pre>** <span class="hljs-built_in">get</span> the <span class="hljs-built_in">color</span> scheme <span class="hljs-keyword">in</span> place colorpalette matplotlib viridis, ipolate(<span class="hljs-number">10</span>, power(<span class="hljs-number">1.6</span>)) <span class="hljs-built_in">reverse</span> nograph <span class="hljs-built_in">local</span> colors r(p)'</pre></div><div id="b0a5"><pre>* here we recover <span class="hljs-keyword">the</span> total <span class="hljs-built_in">number</span> <span class="hljs-keyword">of</span> dates levelsof <span class="hljs-built_in">date</span>, <span class="hljs-built_in">local</span>(dates)</pre></div><div id="60b8"><pre>* main <span class="hljs-keyword">loop</span> block <span class="hljs-keyword">foreach</span> x <span class="hljs-keyword">of</span> <span class="hljs-keyword">local</span> dates {</pre></div><div id="209e"><pre><span class="hljs-keyword">local</span> t : <span class="hljs-keyword">label</span> date <span class="hljs-symbol">x'</span> <span class="hljs-comment">// pick the date label in a local t</span> <span class="hljs-

Options

keyword">display</span> <span class="hljs-string">"t'"</span></pre></div><div id="b5e6"><pre><span class="hljs-bullet">* </span>main graph block:</pre></div><div id="14c0"><pre><span class="hljs-attribute">spmap</span> Index using world_shp if date==x', /// <span class="hljs-attribute">id</span>(ID) /// <span class="hljs-attribute">clmethod</span>(custom) clbreaks(<span class="hljs-number">0</span>(<span class="hljs-number">10</span>)<span class="hljs-number">100</span>) fcolor(<span class="hljs-string">"colors'"</span>) /// <span class="hljs-attribute">ocolor</span>(white ..) osize(*<span class="hljs-number">0</span>.<span class="hljs-number">15</span> ..) /// <span class="hljs-attribute">ndocolor</span>(black) ndfcolor(gs14) ndlabel(<span class="hljs-string">"No data"</span>) ndsize(*<span class="hljs-number">0</span>.<span class="hljs-number">15</span> ..) /// <span class="hljs-attribute">legend</span>(pos(<span class="hljs-number">7</span>) size(*<span class="hljs-number">0</span>.<span class="hljs-number">8</span>)) legstyle(<span class="hljs-number">2</span>) /// <span class="hljs-attribute">title</span>(<span class="hljs-string">"COVID-19 Health Containment Index (t')"</span>, size(medium)) /// </pre></div><div id="fb74"><pre>* export <span class="hljs-keyword">each</span> map <span class="hljs-keyword">as</span> <span class="hljs-keyword">a</span> fixed name + <span class="hljs-built_in">date</span> graph export <span class="hljs-string">"../graphs/guide6/HCI`x'.png"</span>, <span class="hljs-built_in">replace</span> wid(<span class="hljs-number">2000</span>)

} <span class="hljs-comment"> // end of loop</span></pre></div><p id="f6ef">Here we are doing exactly what we did in the previous step, except the <code>if </code>condition now says to pick the value of the date defined by the loop variable <code>x'</code> . We are also using the value label of the date variable, stored in the local <code>t'</code> for the map title.</p><p id="f9fe">Once the loop is finished, a large set of files containing the maps will be generated in the <i>guide6 </i>folder. They all have a standardized name format: <i>HCI_<date>.png</i>.</p><figure id="ea10"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*jvgqYev9iBoMtssIW6Li4g.png"><figcaption></figcaption></figure><h1 id="7303">Task 3: Making a video</h1><p id="d5b7">In the next step, we need to combine the images to make a video. For this step, download the open-source software <a href="https://ffmpeg.org/download.html">FFmpeg</a> and click on the relevant operating system icon. The logos are marked below in red, since here the tendency is to click on the large green source code button:</p><figure id="4f5e"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*DH9ffZ2RE24W5Y4D_ZBntg.png"><figcaption></figcaption></figure><p id="9105">The documentation of FFmpeg is given <a href="https://ffmpeg.org/ffmpeg.html">here</a> including all the commands that be used. FFmpeg is a syntax based software, without a GUI, so understanding all the options and possibilities also requires a good understanding of the commands available.</p><p id="cf45">For Windows, clicking on the link above downloads a zipped file. This file can be extracted anywhere, for example, in some software folder, or even in the COVID19 folder defined above. I just copied it on my D: drive. What is really important is that you know the path of this folder for the next step:</p><figure id="93f2"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*EoECS9xy_O74Mt1l4fsEfQ.png"><figcaption></figcaption></figure><p id="19fc">In Stata, we need define two globals to making the scripting easier. One for the directory path of FFmpeg which has to refer to the bin folder:</p><div id="8e9a"><pre><span class="hljs-keyword">global</span> ffmpegdir <span class="hljs-string">"D:/Software/ffmpeg/bin"</span> </pre></div><div id="bf57"><pre><span class="hljs-keyword">global</span> videodir <span class="hljs-string">"D:/Programs/Dropbox/Dropbox/PROJECT COVID - MEDIUM/graphs/guide6"</span></pre></div><p id="7786">The second global says where the video should be kept. Here I just keep the video folder same as the map images folder.</p><p id="faf3">We can now use the following syntax to generate the video:</p><div id="50d4"><pre>! <span class="hljs-string">"ffmpegdir/ffmpeg.exe"</span> -<span class="hljs-keyword">y</span> -framerate <span class="hljs-number">10</span> -start_number <span class="hljs-number">21915</span> -i <span class="hljs-string">"videodir/HCI_%02d.png"</span> -c:v libx264 -crf <span class="hljs-number">15</span> -r <span class="hljs-number">30</span> <span class="hljs-string">"videodir/Video.mp4"</span></pre></div><p id="977b">In this code, several FFmpeg options are used. While we won’t discuss all of these, the key ones are as follows:</p><p id="6fc0"><code>!</code> tells Stata to load the Windows command shell</p><p id="8069"><code>-y</code> is the default DOS command for replacing files (if you learned programming in the 1980s, this would be obvious :))</p><p id="1e0f"><code>-framerate 10</code> says how many frames per second. If this is reduced then the video is slower.</p><p id="24ca"><code>-start_number 21915</code> says, which is the file number to start with. This should be the number given at the end of the first file.</p><p id="8a71"><code>-i</code> says to read all the files which match the criteria <code>HCI_%02d.png</code></p><p id="4ce1">The remaining options are codecs that generates the video. For example <code>-crf</code> is constant rate factor. Here a <i>lower </i>crf value will give a <i>higher </i>quality. 0 is for lossless animation, but it will also produce a larger file. Usually values in the range of 5–15 are good options.</p><p id="d48a"><code>-r</code> is the output framerate option, which is different from the <code>-framerate</code> command defined earlier. Both can be used to control the speed of the animation.</p><p id="8d81">If the above syntax is correctly specified, and both FFmmpeg and image paths are correct, then a screen should pop up which should show an output like this:</p><figure id="2ee1"><img src="https://cdn-images-1.readmedium.com/v2/resize:fit:800/1*huG5xFid5JTychH-3pkZ-w.png"><figcaption></figcaption></figure><p id="5c76">If something is wrong with the syntax, then nothing will happen. At the end of the execution, a file named <i>video.mp4</i> will be created in the specified folder. Here I will go an additional step and filter it again:</p><div id="dcba"><pre>! <span class="hljs-string">"<span class="hljs-variable">ffmpegdir</span>/ffmpeg.exe"</span> -y -i <span class="hljs-string">"<span class="hljs-variable">videodir</span>/Video.mp4"</span> -filter <span class="hljs-string">"minterpolate='mi_mode=blend:fps=120'"</span> <span class="hljs-string">"<span class="hljs-variable">videodir</span>/Video_smooth.mp4"</span></pre></div><p id="939e">The step above, creates a smooth transition between frames so it looks less jerky. Again this is something to experiment with, but here we get the final <i>video_smooth.mp4</i> file which looks like this:</p> <figure id="1b22"> <div> <div> <img class="ratio" src="http://placehold.it/16x9"> <iframe class="" src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fwww.youtube.com%2Fembed%2FcVB29RiFkb4%3Ffeature%3Doembed&amp;display_name=YouTube&amp;url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DcVB29RiFkb4&amp;image=https%3A%2F%2Fi.ytimg.com%2Fvi%2FcVB29RiFkb4%2Fhqdefault.jpg&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=youtube" allowfullscreen="" frameborder="0" height="480" width="640"> </div> </div> </figure></iframe></div></div></figure><p id="57fe">The steps explained above, can be applied to any Stata animation, and not just maps. As long as there is a good control over colors, axes, legends, file naming conventions etc., anything can be animated. For other examples, you can also visit my <a href="https://www.youtube.com/playlist?list=PLlcwCrK2uh-lURzUanJ1s-R2eeg5WNr7v">YouTube</a> channel to check out various sample Stata animations.</p><p id="5b7e">NOTE: the guide was updated in Dec 2020 to fix some links and make the code a bit more efficient. Therefore the figures were updated but the video file on YouTube remains the same.</p><h1 id="1f17">Exercise</h1><p id="0411">Try generating a map with another policy variable, try other color schemes, and experiment with different FFmpeg options. Try and also animate a line graph or distributions over time.</p><h1 id="4813">Other Stata guides</h1><p id="6518"><a href="https://readmedium.com/covid-19-data-visualization-with-stata-part-1-an-introduction-to-data-setup-and-customized-6b879a1e8647">Part 1: An introduction to data setup and customized graphs</a></p><p id="8f22"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-2-customizing-color-schemes-206af77d00ce">Part 2: Customizing colors schemes</a></p><p id="6eee"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-3-heat-plots-e2ef5ac1160b">Part 3: Heat plots</a></p><p id="29d5"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-4-maps-fbd4fe2642f6">Part 4: Maps</a></p><p id="b441"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-5-stacked-area-graphs-ed976d025365">Part 5: Stacked area graphs</a></p><p id="c22c"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-6-animations-f9d2b09985c2">Part 6: Animations</a></p><p id="6de3"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-7-doubling-time-graphs-1-58b7687cbdc0">Part 7: Doubling time graphs I</a></p><p id="0b15"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-8-joy-plots-ridge-line-plots-dbe022e7264d">Part 8: Ridge-line plots (Joy plots)</a></p><p id="bd22"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-9-customized-bar-graphs-dde096567837">Part 9: Customized bar graphs</a></p><p id="fc22"><a href="https://readmedium.com/covid-19-visualizations-with-stata-part-10-stream-graphs-9d55db12318a">Part 10: Stream graphs</a></p><p id="c8e6">If you enjoy these guides and find them useful, then please like and follow <a href="https://medium.com/the-stata-guide">The Stata Guide</a>. Also, please share your visualizations if you use these guides!</p><h1 id="f351">About the author</h1><p id="9535">I am an economist by profession and I have been using Stata since 2003. I am currently based in Vienna, Austria where I work at the <a href="https://www.wu.ac.at/en/ecolecon/institute">Vienna University of Economics and Business (WU)</a> and at the <a href="https://iiasa.ac.at/">International Institute for Applied Systems Analysis (IIASA)</a>. You can find my research work on <a href="https://www.researchgate.net/profile/Asjad_Naqvi">ResearchGate</a> and <a href="https://scholar.google.com/citations?user=oWGGVpYAAAAJ&amp;hl=en">Google Scholar</a>, and Stata code repository on <a href="https://github.com/asjadnaqvi">GitHub</a>. You can follow my COVID-19 related Stata visualizations on my <a href="https://twitter.com/AsjadNaqvi">Twitter</a>. I am also featured on the Stata <a href="https://www.stata.com/covid19/">COVID-19 webpage</a> in the visualization and graphics section.</p><p id="9e75">You can connect with me via <a href="https://medium.com/@asjadnaqvi">Medium</a>, <a href="https://twitter.com/AsjadNaqvi">Twitter</a>, <a href="https://www.linkedin.com/in/asjad-naqvi-phd-9a539512/">LinkedIn </a>or simply via email: [email protected].</p><p id="0cbe">My Medium blog for Stata stuff here: <a href="https://medium.com/the-stata-guide">The Stata Guide</a> where new awesome content is released regularly. Clap, and/or follow if you like these guides!</p></article></body>

COVID-19 visualizations with Stata Part 6: Animations

In this guide we will learn how to make animations in Stata. Here, we will use the Oxford COVID-19 Government Response Tracker (OxCGRT) dataset (introduced in Guide 3 and Guide 4), and we will go through all the steps to create a video that shows the global evolution of the Health Containment Index:

This guide will also introduce new elements of Stata like formatting locals, label masks, and fine-tuning custom color schemes.

A related guide on animations is also given on the official Stata blog here which provides a good introduction on this topic.

Preamble: Set up Stata

Like all previous guides, this guide assumes a basic knowledge of Stata. This guide deals with advanced usage of locals, loops, and code structures that require some experience and familiarity with Stata programming. If you are using this guide for the first time, and are new to Stata, then Guide 1 and Guide 2 are highly recommended, followed by the next set of guides which are in increasing order of difficulty.

This guide uses the following folder structure for the work-flow management:

Within the graphs folder, I also create an additional sub-folder called guide6, to store the figures generated here. For details on how to organize your files, please see Guide 1.

In order to make the graphs exactly as they are shown here, several additional item are required:

  • Install the cleanplots theme for a clean look for your figures (more on themes in Guide 2):
net install cleanplots, from("https://tdmize.github.io/data/cleanplots")
set scheme cleanplots, perm
net install palettes, replace from("https://raw.githubusercontent.com/benjann/palettes/master/")
net install colrspace, replace from("https://raw.githubusercontent.com/benjann/colrspace/master/")
  • Set default graph font to Arial Narrow (see the Font guide on customizing fonts)
graph set window fontface "Arial Narrow"

This guide has been written in version 16.1 and should work with version 14 and onwards. Earlier versions might need some modification for implementing custom colors.

  • This guide also requires the installation of FFmpeg, an open-source video-editing software. So make sure you have write permissions on the computer you are using.

Task 1: Get the data in order

The Oxford COVID-19 policy dataset was introduced and discussed extensively in Guide 3 including how to pull data from a Github repository. Therefore we will dive straight into setting up the data:

*** get the data
clear
insheet using "https://raw.githubusercontent.com/OxCGRT/covid-policy-tracker/master/data/OxCGRT_latest.csv", clear
*** new data has been added for USA and UK for regions so we drop the regions:
drop if regionname!=""
*** keep only the variables we need
keep date countryname containmenthealthindexfordisplay
ren containmenthealthindexfordisplay Index   // also used as a label
ren countryname country
duplicates list country date
*** fix the date
tostring date, gen(date2)    // string the date variable
drop date
gen date = date(date2,"YMD")
format date %tdDD-Mon-yyyy
drop date2
drop if date < 21915    // 1st January
order country date
sort country date
compress
save ./master/COVID_policies2.dta, replace

Here we keep the variables we need, fix the dates, and save the dataset in the master folder. Since we already generated a COVID_policies.dta dataset for the heatplots shown in Guide 3, therefore I label this dataset as COVID_policies2.dta.

Task 2: The map

Maps were discussed extensively in Guide 4 including where to find the shapefiles and set them up in Stata. To summarize, the shapefile for the world can be downloaded here, and saved and unzipped in the GIS folder.

The map setup, including dropping Antarctica and fixing the map projection, is done as follows:

********* get the shapefiles in order in order
cd GIS
spshape2dta "World_Countries__Generalized_.shp", replace saving(world)
*** set up the GIS files
use world_shp, clear
merge m:1 _ID using world 
 drop if COUNTRY=="Antarctica"
 drop rec_header- _merge
 geo2xy _Y _X, proj (web_mercator) replace
  sort _ID
  save, replace

In the next step, we merge the map data with the policies dataset after fixing some country names:

**** let's merge the datasets
use world, clear
 ren COUNTRY country
 drop if country=="Antarctica"
  
  
*** fix the names 
replace country="Cote d'Ivoire" if country=="Côte d'Ivoire"
replace country="Kyrgyz Republic" if country=="Kyrgyzstan"
replace country="Slovak Republic" if country=="Slovakia"
replace country="Democratic Republic of Congo" if country=="Congo DRC"
replace country="Russia" if country=="Russian Federation"
replace country="Palestine" if country=="Palestinian Territory"
*** merge with the policies database 
merge 1:m country using ../master/COVID_policies2
drop if _m==2
drop _m

Note that a bunch of small island states won’t match. They can be dealt with by merging them with another, more extensive map file. This is unfortunately a standard problem for these small islands as they tend to disappear in the sea of large countries. They are also hardly visible on global maps.

In the next step, we generate a basic map for the last date we have in the dataset and plot the HCI:

summ date
spmap Index using world_shp if date==`r(max)', ///
 id(_ID) clmethod(custom) clbreaks(0(10)100) fcolor(Blues2)
graph export ../graphs/animation/map1.png, replace wid(2000)

which gives us this default image:

The next code block, fine tunes this map by using a custom color scheme defined by the colorpalette package (introduced in Guide 2):

local date: display %tdd_m_yy date(c(current_date), "DMY")
display "`date'"
colorpalette matplotlib viridis, n(10) reverse nograph
local colors `r(p)'
summ date 
spmap Index using world_shp if date==`r(max)', ///
id(_ID) ///
clmethod(custom) clbreaks(0(10)100)  fcolor("`colors'")  ///
ocolor(white ..) osize(*0.15 ..)  ///
ndocolor(black) ndfcolor(gs14) ndlabel("No data")  ndsize(*0.15 ..)  ///
legend(pos(7) size(*0.8)) legstyle(2)  /// 
title("COVID-19 Policy Stringency Index (`date')", size(medium)) ///
   note("Data source: Oxford COVID-19 Government Response Tracker. Mercator projection used. Antartica dropped from maps." , size(*0.5))
graph export ../graphs/animation/map2.png, replace wid(2000)

In the code above, we auto generate the date and add it to the title. We also define the cut-offs in steps of ten, customize boundaries, no data zones, line widths etc. and get the following map:

In the graph above, the green shades in the middle HCI range are hard to differentiate, while the two yellowish tones, that show the bottom two categories, stand out a lot. The HCI range roughly between 20–50 is considered fairly weak on the aggregate level (individual dimensions of the index may still vary). It is only above 50 and 60 where one can consider policies to be strong, and above scores above 70 or 80 are considered to be very strong, well thought-out responses.

To highlight this point on the map, we can use a power shift to move the color tones around. This can be achieved through a manual interpolation of colors as follows:

colorpalette: matplotlib viridis, ipolate(10) name("Even") / ///
matplotlib viridis, ipolate(10, power(0.5)) name("Shift left") / ///
matplotlib viridis, ipolate(10, power(1.6)) name("Shift right")  
graph export ../graphs/animation/colorpalette.png, replace wid(2000)

Here we see how the color interpolation changes relative to the evenly distributed color scheme. For our purposes, we will use the color bands labeled “Shift right” and redraw the map:

local date: display %tdd_m_yy date(c(current_date), "DMY")
display "`date'"     
     
colorpalette matplotlib viridis, ipolate(10, power(1.6)) reverse nograph
local colors `r(p)'
summ date 
spmap Index using world_shp if date==`r(max)', ///
id(_ID) ///
clmethod(custom) clbreaks(0(10)100)  fcolor("`colors'")  ///
ocolor(white ..) osize(*0.15 ..)  ///
ndocolor(black) ndfcolor(gs14) ndlabel("No data")  ndsize(*0.15 ..)  ///
legend(pos(7) size(*0.8)) legstyle(2)  /// 
title("COVID-19 Health Containment Index (`date')", size(medium)) ///
note("Data source: Oxford COVID-19 Government Response Tracker. Mercator projection used. Antartica dropped from maps." , size(*0.5))
graph export ../graphs/animation/map3.png, replace wid(2000)

which gives us this image:

Here the policies are a bit more differentiated than the map with evenly distributed colors and the middle policy range countries are easier to identify.

Now that we have the color template in place, we need to generate the maps for each date. But before we loop over all the dates, we need to make sure that the dates are in a format such that they can be added in the title as well. This can be achieved in several different ways, but for this guide, we will do it using the custom labmask package:

ssc install labmask, replace  // install the custom package
gen sdate = string(date, "%tdd_m_yy") // define the date format
labmask date, values(sdate)  // mask the values

Here we generate a string variable called sdate, which has the dates in the correct display format. We then take this string variable and mask it on the date variable as value labels. This literally takes the string in each row and converts it to a label for a date variable:

We can now use this information to generate maps for each day using the following loop:

*** get the color scheme in place
colorpalette matplotlib viridis, ipolate(10, power(1.6)) reverse nograph
local colors `r(p)'
* here we recover the total number of dates
levelsof date, local(dates)
* main loop block
foreach x of local dates {
local t : label date `x'   // pick the date label in a local t
 display "`t'"
* main graph block:
spmap Index using world_shp if date==`x', ///
id(_ID) ///
clmethod(custom) clbreaks(0(10)100)  fcolor("`colors'")  ///
ocolor(white ..) osize(*0.15 ..)  ///
ndocolor(black) ndfcolor(gs14) ndlabel("No data")  ndsize(*0.15 ..)  ///
legend(pos(7) size(*0.8)) legstyle(2)  /// 
title("COVID-19 Health Containment Index (`t')", size(medium)) ///
 
* export each map as a fixed name + date
graph export "../graphs/guide6/HCI_`x'.png", replace wid(2000)  
   
}  // end of loop

Here we are doing exactly what we did in the previous step, except the if condition now says to pick the value of the date defined by the loop variable `x' . We are also using the value label of the date variable, stored in the local `t' for the map title.

Once the loop is finished, a large set of files containing the maps will be generated in the guide6 folder. They all have a standardized name format: HCI_<date>.png.

Task 3: Making a video

In the next step, we need to combine the images to make a video. For this step, download the open-source software FFmpeg and click on the relevant operating system icon. The logos are marked below in red, since here the tendency is to click on the large green source code button:

The documentation of FFmpeg is given here including all the commands that be used. FFmpeg is a syntax based software, without a GUI, so understanding all the options and possibilities also requires a good understanding of the commands available.

For Windows, clicking on the link above downloads a zipped file. This file can be extracted anywhere, for example, in some software folder, or even in the COVID19 folder defined above. I just copied it on my D: drive. What is really important is that you know the path of this folder for the next step:

In Stata, we need define two globals to making the scripting easier. One for the directory path of FFmpeg which has to refer to the bin folder:

global ffmpegdir "D:/Software/ffmpeg/bin"  
global videodir  "D:/Programs/Dropbox/Dropbox/PROJECT COVID - MEDIUM/graphs/guide6"

The second global says where the video should be kept. Here I just keep the video folder same as the map images folder.

We can now use the following syntax to generate the video:

! "$ffmpegdir/ffmpeg.exe" -y -framerate 10 -start_number 21915 -i "$videodir/HCI_%02d.png" -c:v libx264 -crf 15 -r 30 "$videodir/Video.mp4"

In this code, several FFmpeg options are used. While we won’t discuss all of these, the key ones are as follows:

! tells Stata to load the Windows command shell

-y is the default DOS command for replacing files (if you learned programming in the 1980s, this would be obvious :))

-framerate 10 says how many frames per second. If this is reduced then the video is slower.

-start_number 21915 says, which is the file number to start with. This should be the number given at the end of the first file.

-i says to read all the files which match the criteria HCI_%02d.png

The remaining options are codecs that generates the video. For example -crf is constant rate factor. Here a lower crf value will give a higher quality. 0 is for lossless animation, but it will also produce a larger file. Usually values in the range of 5–15 are good options.

-r is the output framerate option, which is different from the -framerate command defined earlier. Both can be used to control the speed of the animation.

If the above syntax is correctly specified, and both FFmmpeg and image paths are correct, then a screen should pop up which should show an output like this:

If something is wrong with the syntax, then nothing will happen. At the end of the execution, a file named video.mp4 will be created in the specified folder. Here I will go an additional step and filter it again:

! "$ffmpegdir/ffmpeg.exe" -y -i "$videodir/Video.mp4" -filter "minterpolate='mi_mode=blend:fps=120'" "$videodir/Video_smooth.mp4"

The step above, creates a smooth transition between frames so it looks less jerky. Again this is something to experiment with, but here we get the final video_smooth.mp4 file which looks like this:

The steps explained above, can be applied to any Stata animation, and not just maps. As long as there is a good control over colors, axes, legends, file naming conventions etc., anything can be animated. For other examples, you can also visit my YouTube channel to check out various sample Stata animations.

NOTE: the guide was updated in Dec 2020 to fix some links and make the code a bit more efficient. Therefore the figures were updated but the video file on YouTube remains the same.

Exercise

Try generating a map with another policy variable, try other color schemes, and experiment with different FFmpeg options. Try and also animate a line graph or distributions over time.

Other Stata guides

Part 1: An introduction to data setup and customized graphs

Part 2: Customizing colors schemes

Part 3: Heat plots

Part 4: Maps

Part 5: Stacked area graphs

Part 6: Animations

Part 7: Doubling time graphs I

Part 8: Ridge-line plots (Joy plots)

Part 9: Customized bar graphs

Part 10: Stream graphs

If you enjoy these guides and find them useful, then please like and follow The Stata Guide. Also, please share your visualizations if you use these guides!

About the author

I am an economist by profession and I have been using Stata since 2003. I am currently based in Vienna, Austria where I work at the Vienna University of Economics and Business (WU) and at the International Institute for Applied Systems Analysis (IIASA). You can find my research work on ResearchGate and Google Scholar, and Stata code repository on GitHub. You can follow my COVID-19 related Stata visualizations on my Twitter. I am also featured on the Stata COVID-19 webpage in the visualization and graphics section.

You can connect with me via Medium, Twitter, LinkedIn or simply via email: [email protected].

My Medium blog for Stata stuff here: The Stata Guide where new awesome content is released regularly. Clap, and/or follow if you like these guides!

Stata
Animation
Color Scheme
Covid-19
Maps
Recommended from ReadMedium