class: center, middle, title-slide .title[ # Visualizing Amounts ] .author[ ### Luke Tierney ] .institute[ ### University of Iowa ] .date[ ### 2023-05-05 ] --- layout: true <link rel="stylesheet" href="stat4580.css" type="text/css" /> <!-- title based on Wilke's chapter --> ## Visualization and Comparison --- .pull-left[ A visualization of a single value without some comparative context is rarely useful <img src="amounts_files/figure-html/unnamed-chunk-2-1.png" style="display: block; margin: auto;" /> ] -- .pull-right[ Useful visualizations almost always involve comparisons. {{content}} ] -- * position of a value on an axis {{content}} -- * relative position of two values {{content}} -- * relative magnitudes of two values --- A simple and common setting: Visualizing a measurement for each of a set of categories. -- .pull-left[ Some of the more common visualizations: {{content}} ] -- * Dot Charts (also called Cleveland Dot Charts) {{content}} -- * Bar Charts * Grouped Bar Charts (Clustered Bar Charts) * Stacked Bar Charts * Diverging Bar Charts {{content}} -- * Heat Maps {{content}} -- * Bubble Charts -- .pull-right[ Some less frequently used visualizations: {{content}} ] -- * Waterfall Charts * Polar Area Charts (Coxcomb Charts) * Dumbbell Charts {{content}} -- Some questions to keep in mind: {{content}} -- * What comparisons and other assessments do these visualizations support? {{content}} -- * How do these visualizations scale to larger data sets? --- layout: true ## Dot Plots --- ### Basics One of the simplest visualizations of a single numerical variable with a modest number of observations and lables for the observations is a _dot plot_, or _Cleveland dot plot_: .pull-left.small-code[ .hide-code[ ```r library(dplyr) library(ggplot2) library(gapminder) le_am_2007 <- filter(gapminder, year == 2007, continent == "Americas") thm <- theme_minimal() + theme(text = element_text(size = 16)) ggplot(le_am_2007, aes(y = country, x = lifeExp)) + geom_point(color = "deepskyblue3", size = 2) + labs(x = "Life Expectancy (years)", y = NULL) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-3-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ This visualization {{content}} ] -- * shows the overall distribution of the data, and {{content}} -- * makes it easy to locate the life expectancy of a particular country. --- Unless there is a natural order to the categories (e.g. months of the year or days of the week) it is usually better to reorder to make the plot increasing or decreasing: .pull-left.small-code[ .hide-code[ ```r ggplot(le_am_2007, aes(y = reorder(country, lifeExp), x = lifeExp)) + geom_point(color = "deepskyblue3", size = 2) + labs(x = "Life Expectancy (years)", y = NULL) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-5-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ * Locating a particular country is a little more difficult. {{content}} ] -- * But the shape of the distribution is more apparent. {{content}} -- * Approximate median and quartiles can be read off easily. --- .pull-left[ Dot plots are particularly appropriate for interval data. {{content}} ] -- * they often do not show the origin; {{content}} -- * they focus the viewer's attention on differences. {{content}} -- Dot plots are often very useful for group summaries like totals or averages. {{content}} -- For the `barley` data, total yield within each site, adding up across all varieties and both years, can be computed as ```r b_tot_site <- group_by(barley, site) %>% summarize(yield = sum(yield)) ``` -- .pull-right.small-code[ The totals can then be visualized in a dot plot: .hide-code[ ```r ggplot(b_tot_site, aes(x = yield, y = site)) + geom_point(color = "deepskyblue3", size = 2.5) + labs(x = "Total Yield (bushels/acre)", y = NULL) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-7-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ ### Larger Data Sets For larger data sets, like the `citytemps` data with 140 observations, over-plotting of labels becomes a problem: ] -- .pull-right.small-code[ .hide-code[ ```r ggplot(citytemps, aes(x = temp, y = reorder(city, temp))) + geom_point(color = "deepskyblue3", size = 0.5) + labs(x = "Temperature (degrees F)", y = NULL) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-8-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Reducing to 30 or 40, e.g. by taking a sample or a meaningful subset, can help: ] .pull-right.small-code[ .hide-code[ ```r ct1 <- filter(citytemps, temp < 32) %>% sample_n(10) ct2 <- filter(citytemps, temp >= 32) %>% sample_n(20) ctsamp <- bind_rows(ct1, ct2) ggplot(ctsamp, aes(x = temp, y = reorder(city, temp))) + geom_point(color = "deepskyblue3", size = 2) + labs(x = "Temperature (degrees F)", y = NULL) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-9-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ ### Some Variations {{content}} ] -- The size of the dots can be used to encode an additional numeric variable. {{content}} -- This view uses area to encode population size: -- .pull-right.small-code[ .hide-code[ ```r ggplot(le_am_2007, aes(y = reorder(country, lifeExp), x = lifeExp, size = pop / 1000000)) + geom_point(col = "deepskyblue3") + labs(x = "Life Expectancy (years)", y = NULL) + scale_size_area("Population\n(Millions)", max_size = 8) + thm + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-10-1.png" style="display: block; margin: auto;" /> ] {{content}} ] -- This is sometimes called a _bubble chart_. --- .pull-left[ Repeated measures, such as values for 2002 and 2007, can be shown and distinguished by color, shape, or both. {{content}} ] -- Using color: <img src="amounts_files/figure-html/gapminder-dotplot-two-year-1.png" style="display: block; margin: auto;" /> -- .pull-right.small-code[ .hide-code[ ```r ## filter down to data for 2002 and 2007 ## for the Americas le2 <- filter(gapminder, year >= 2002, continent == "Americas") ## make a factor Year to get a discrete color ## palette, not a continuous one le2 <- mutate(le2, Year = factor(year)) ggplot(le2, aes(y = reorder(country, lifeExp), x = lifeExp, color = Year)) + geom_point() + labs(x = "Life Expectancy (years)", y = NULL) + thm + theme(legend.position = "top") ``` ] * All countries show some improvement in life expectancy. {{content}} ] -- * The small improvement for Jamaica _pops out_ a bit. --- Using shape for the `barley` data: .hide-code[ .small-code[ ```r b_tot <- group_by(barley, site, year) %>% summarize(yield = sum(yield), .groups = "drop") ggplot(b_tot, aes(x = yield, y = site, shape = year)) + geom_point(color = "deepskyblue3", size = 3) + labs(x = "Total Yield (bushels/acre)", y = NULL) + thm + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-11-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ For repeated two values per class it can help to connect the two dots. {{content}} ] -- This visually emphasizes the relative sizes of differences. {{content}} -- The result is sometimes called a _dumbbell chart_. {{content}} -- For the barley yields data: -- .pull-right.small-code[ .hide-code[ ```r ggplot(b_tot, aes(x = yield, y = site)) + geom_line(aes(group = site), linewidth = 2, color = "grey") + geom_point(aes(color = year), size = 4) + labs(x = "Total Yield (bushels/acre)", y = NULL) + thm + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-12-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ For the Gapminder life expectancy data: ] .pull-right.small-code[ .hide-code[ ```r ggplot(le2, aes(y = reorder(country, lifeExp), x = lifeExp, color = Year)) + geom_line(aes(group = country), linewidth = 1.5, color = "grey") + geom_point(size = 2.5) + labs(x = "Life Expectancy (years)", y = NULL) + thm + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-14-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ ### Variations in Appearance The dot plots introduced by W. S. Cleveland in his 1993 book _Visualizing Data_ use only horizontal grid lines. {{content}} ] -- This also corresponds to the dot plots provided by base and lattice graphics and to the dot plot obtained using the Wall Street Journal theme from the `ggthemes`package: -- .pull-right.small-code[ .hide-code[ ```r ggplot(b_tot_site, aes(x = yield, y = site)) + geom_point(size = 2) + labs(x = "Total Yield (bushels/acre)", y = NULL) + ggthemes::theme_wsj() ``` <img src="amounts_files/figure-html/unnamed-chunk-15-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left.small-code[ A theme to closely match the style used by Cleveland can be defined as <!-- # http://www.win-vector.com/blog/2013/02/revisiting-clevelands-the-elements-of-graphing-data-in-ggplot2/ --> ```r theme_dotplotx <- function() { theme( ## remove the vertical grid lines panel.grid.major.x = element_blank(), panel.grid.minor.x = element_blank(), ## explicitly set the horizontal lines ## (or they will disappear too) panel.grid.major.y = element_line(color = "black", linetype = 3), axis.text.y = element_text(size = rel(1.2)), ## use a white backgrounsd panel.background = element_rect(fill = "white", color = NA), panel.border = element_rect(fill = NA, color = "grey20"), ## increase text size text = element_text(size = 16)) } ``` ] -- .pull-right.small-code[ This produces .hide-code[ ```r ggplot(b_tot_site, aes(x = yield, y = site)) + geom_point(size = 2) + labs(x = "Total Yield (bushels/acre)", y = NULL) + theme_dotplotx() ``` <img src="amounts_files/figure-html/unnamed-chunk-17-1.png" style="display: block; margin: auto;" /> ] ] --- layout: true ## Bar Charts --- ### Basics A basic bar chart: .pull-left.small-code[ .hide-code[ ```r p <- ggplot(le_am_2007) + geom_col(aes(y = lifeExp, x = reorder(country, lifeExp)), fill = "deepskyblue3") + labs(y = "Life Expectancy (years)", x = NULL) + scale_y_continuous( expand = expansion(mult = c(0, .1))) + thm p ``` <img src="amounts_files/figure-html/unnamed-chunk-18-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ The labels are a mess. {{content}} ] -- * One option is to write labels at an angle, but this makes them hard to read. {{content}} -- * Flipping the plot is usually a better option. --- .pull-left[ To make labels readable we can flip the plot: ] .pull-right.small-code[ .hide-code[ ```r ## Redoing the plot plot with `x` and `y` aesthetics reversed did not ## work in the past but does work in current ggplot2 versions. ## But as we already have the previous plot we can just use coord_flip(), p + coord_flip() ``` <img src="amounts_files/figure-html/unnamed-chunk-19-1.png" style="display: block; margin: auto;" /> ] ] --- ### Some Notes Bar charts seem to be used much more than dot plots in the popular media, but they are less widely applicable. -- Research on visual perception has shown that viewers of bar charts subconsciously, or _pre-attentively_, focus on relative lengths of bars. -- This means that for a bar chart to be appropriate and work well: -- * Ratio comparisons have to make sense for you data. -- * The relative bar lengths have to accurately reflect that data ratio. -- This in turn implies that bar charts should _always_ have a zero base line. -- You may need to intervene with your software's defaults to make this happen. --- Creating a bar chart with a non-zero base line is possible in `ggplot` but not easy: .pull-left.small-code[ .hide-code[ ```r baseline <- 60 ticks <- c(0, 10, 20, 30) ggplot(le_am_2007, aes(x = lifeExp - baseline, y = reorder(country, lifeExp))) + geom_col(fill = "deepskyblue3") + labs(x = "Life Expectancy (years)", y = NULL) + scale_x_continuous( breaks = ticks, labels = ticks + baseline, expand = expansion(mult = c(0, .1))) + thm + labs(title = "A Bad Bar Chart") ``` <img src="amounts_files/figure-html/unnamed-chunk-20-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ * The visual impression is that the value for Bolivia is five times higher than the value for Haiti. {{content}} ] -- * This is _not_ an accurate representation of the data. --- Bar charts always push the viewer to ratio comparisons, whether they are meaningful or not. -- Using a non-zero baseline can therefore [mislead the viewer](https://www.storytellingwithdata.com/blog/2012/09/bar-charts-must-have-zero-baseline). -- Some news organizations seem particularly prone to taking advantage of/falling prey to this issue. -- .pull-left[ <img src="../img/bushtaxbar.jpeg" width="70%" style="display: block; margin: auto;" /> ] .pull-right[ <img src="../img/obamabar.jpeg" width="70%" style="display: block; margin: auto;" /> ] -- A recent [blog post](https://www.statschat.org.nz/2020/02/02/graphs-dont-matter/) discusses a court case in New Zealand about a misleading bar chart with a non-zero baseline. -- Another recent [blog post](https://www.storytellingwithdata.com/blog/2020/2/19/what-is-a-bar-chart) may be helpful. --- ### Data With Both Positive and Negative Values Bar charts can be used for data containing both positive and negative values: .small-code[ .hide-code[ ```r ctsampC <- mutate(ctsamp, temp = round((temp - 32) / 1.8)) p1 <- ggplot(ctsampC, aes(y = city, x = temp)) + geom_point(color = "deepskyblue3") + geom_vline(xintercept = 0, lty = 2) + labs(x = "Temperature (degrees C)", y = NULL) + thm p2 <- ggplot(ctsampC, aes(x = temp, y = city)) + geom_col(fill = "deepskyblue3") + labs(x = "Temperature (degrees C)", y = NULL) + thm library(patchwork) p1 + p2 ``` <img src="amounts_files/figure-html/unnamed-chunk-23-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left.width-30[ This puts a strong emphasis on the base line {{content}} ] -- This can be useful if the baseline is meaningful, such as {{content}} -- * [an average level](https://www.ncei.noaa.gov/access/monitoring/climate-at-a-glance/time-series/global) {{content}} -- * zero degrees Celcius -- .pull-right.width-60.small-code[ Zero degrees Fahrenheit is not meaningful: .hide-code[ ```r p1 <- ggplot(ctsamp, aes(y = city, x = temp)) + geom_point(color = "deepskyblue3") + geom_vline(xintercept = 0, lty = 2) + labs(x = "Temperature (degrees F)", y = NULL) + thm p2 <- ggplot(ctsamp, aes(x = temp, y = city)) + geom_col(fill = "deepskyblue3") + labs(x = "Temperature (degrees F)", y = NULL) + thm p1 + p2 ``` <img src="amounts_files/figure-html/unnamed-chunk-24-1.png" style="display: block; margin: auto;" /> ] ] --- ### Grouped Bar Charts A _grouped bar chart_ can be used to show two measurements, e.g. two times. .pull-left.small-code[ .hide-code[ ```r ggplot(le2) + geom_col(aes(x = lifeExp, y = reorder(country, lifeExp), fill = Year), position = "dodge") + labs(x = "Life Expectancy (years)", y = NULL) + scale_x_continuous( expand = expansion(mult = c(0, .1))) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-25-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ This visualization would be more effective for {{content}} ] -- * fewer countries and more years {{content}} -- * with more variation in ratios within and between categories. {{content}} -- In this case the dot plot is a much better choice. --- For the `barley` data looking at total yields for each of the sites and the two years: .small-code[ .hide-code[ ```r ggplot(b_tot) + geom_col(aes(x = yield, y = site, fill = year), position = "dodge") + labs(x = "Total Yield (bushels/acre)", y = NULL) + scale_x_continuous( expand = expansion(mult = c(0, .1))) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-26-1.png" style="display: block; margin: auto;" /> ] ] --- ### Stacked Bar Charts A _stacked bar chart_ is appropriate when adding the values within a category to form a total makes sense. -- .pull-left.small-code[ For the `barley` data: .hide-code[ ```r ggplot(b_tot) + geom_col(aes(x = yield, y = site, fill = year), position = "stack") + labs(x = "Total Yield (bushels/acre)", y = NULL) + scale_x_continuous( expand = expansion(mult = c(0, .1))) + thm ``` <img src="amounts_files/figure-html/unnamed-chunk-27-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ * The combined bars show the totals. {{content}} ] -- * The bar segments show the contribution of each year within the sites. {{content}} -- * Comparing 1931 yields across sites is easy; comparing 1932 values is harder. {{content}} -- A stacked bar chart would make no sense for the two-year life expectancy data. --- ### Category Order Ordering of categories can change the visual effectiveness of bar charts. -- #### Supermarket Sales A small data set on multi-national supermarket chain sales: .hide-code[ ```r chains <- read.csv(textConnection("Chain, Total, Foreign Walmart, 22, 5 Costco, 4.5, 2 Tesco, 3, 0.5 Carrefour, 2.5, 2")) chains <- mutate(chains, Home = Total - Foreign) tbl <- select(chains, Chain, Home, Foreign, Total) kbl <- knitr::kable(tbl, format = "html") kableExtra::kable_styling(kbl, full_width = FALSE) ``` <table class="table" style="width: auto !important; margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;"> Chain </th> <th style="text-align:right;"> Home </th> <th style="text-align:right;"> Foreign </th> <th style="text-align:right;"> Total </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> Walmart </td> <td style="text-align:right;"> 17.0 </td> <td style="text-align:right;"> 5.0 </td> <td style="text-align:right;"> 22.0 </td> </tr> <tr> <td style="text-align:left;"> Costco </td> <td style="text-align:right;"> 2.5 </td> <td style="text-align:right;"> 2.0 </td> <td style="text-align:right;"> 4.5 </td> </tr> <tr> <td style="text-align:left;"> Tesco </td> <td style="text-align:right;"> 2.5 </td> <td style="text-align:right;"> 0.5 </td> <td style="text-align:right;"> 3.0 </td> </tr> <tr> <td style="text-align:left;"> Carrefour </td> <td style="text-align:right;"> 0.5 </td> <td style="text-align:right;"> 2.0 </td> <td style="text-align:right;"> 2.5 </td> </tr> </tbody> </table> ] <!-- https://junkcharts.typepad.com/junk_charts/2020/01/taking-small-steps-to-bring-out-the-message.html --> --- The default alphabetical ordering of the chains in a bar chart is not ideal: .pull-left.small-code[ .hide-code[ ```r library(tidyr) chains <- mutate(chains, Total = NULL) lchains <- pivot_longer(chains, -Chain, names_to = "Type", values_to = "Sales") p <- ggplot(lchains, aes(x = Sales, y = Chain, fill = Type)) + geom_col() + ylab(NULL) + scale_x_continuous( expand = expansion(mult = c(0, .1))) + scale_fill_brewer( palette = "Paired", guide = guide_legend(title = NULL, reverse = TRUE)) + thm + theme(legend.position = "top") p ``` <img src="amounts_files/figure-html/unnamed-chunk-29-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left.small-code[ Reordering by total sales produces a better result: .hide-code[ ```r lchain_s <- mutate(lchains, Chain = reorder(Chain, Sales, FUN = sum)) p %+% lchain_s ``` <img src="amounts_files/figure-html/unnamed-chunk-30-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ With this ordering, comparing the first category of sales, `Home`, among chains is easier than comparing the `Foreign` sales as the `Home` values have a common baseline. ] --- If the goal of the visualization is to emphasize the foreign sales then reversing the order would be better. .pull-left.width-55.small-code[ .hide-code[ ```r lchain_st <- mutate(lchain_s, Type = factor(Type, levels = c("Home", "Foreign"))) p %+% lchain_st + scale_fill_brewer(palette = "Paired", guide = guide_legend(title = NULL, reverse = TRUE), direction = -1) ``` <img src="amounts_files/figure-html/unnamed-chunk-31-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right.width-40[ This also makes it easy to see that Walmart's international sales alone exceed the total sales of each of the other chains. ] --- A _filled bar chart_ can be used to help compare the proportions of total sales from foreign stores. .pull-left.width-55.small-code[ .hide-code[ ```r levs <- levels(with(chains, reorder(Chain, Foreign / (Foreign + Home)))) mutate(lchain_st, Chain = factor(Chain, levs)) %>% ggplot(aes(x = Sales, y = Chain, fill = Type)) + geom_col(position = "fill") + labs(x = "Proportion of Sales", y = NULL) + scale_x_continuous( expand = expansion(mult = c(0, .1))) + scale_fill_brewer(palette = "Paired", guide = guide_legend(title = NULL, reverse = TRUE), direction = -1) + thm + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-32-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ #### 2016 Election Results Another example for filled bar charts is provided by 2016 presidential election results by state. This is produced by default settings. The values for Clinton are easy to compare, as they are on a common baseline. The values for Other are also on a common baseline, but the numbers for Trump are not. ] .pull-right.small-code[ .hide-code[ ```r p <- ggplot(geofacet::election, aes(x = votes, y = state, fill = candidate)) + geom_col(position = "fill") + scale_x_continuous(expand = c(0, 0)) + xlab(NULL) + geom_vline(xintercept = 0.5, linetype = 2) p ``` <img src="amounts_files/figure-html/pres2016-dflt-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Reordering the categories puts both Clinton and Trump on common baselines and also matches the common color use: ] .pull-right.small-code[ .hide-code[ ```r ## move 'Other' to middle, align Clinton/Trump with colors elect <- mutate(geofacet::election, candidate = factor(candidate, c("Trump", "Other", "Clinton"))) p %+% elect ``` <img src="amounts_files/figure-html/unnamed-chunk-33-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Different orderings of the states can be used to achieve different goals. Ordering by Clinton's percentage makes it easier to see where she had an outright majority. ] .pull-right.small-code[ .hide-code[ ```r elect_wide <- pivot_wider(select(elect, -votes), names_from = candidate, values_from = pct) ## ordered by Clinton pct slevs <- arrange(elect_wide, Clinton) %>% pull(state) p %+% mutate(elect, state = factor(state, slevs)) ``` <img src="amounts_files/figure-html/unnamed-chunk-34-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Another possibility is to order by winning margin between the two major candidates. ] .pull-right.small-code[ .hide-code[ ```r ## ordered by winning margin slevs <- arrange(elect_wide, Clinton - Trump) %>% pull(state) p %+% mutate(elect, state = factor(state, slevs)) ``` <img src="amounts_files/figure-html/unnamed-chunk-35-1.png" style="display: block; margin: auto;" /> ] ] --- layout: true ## Faceting --- Faceting can be used with dot plots and bar charts as well: .pull-left.small-code[ .hide-code[ ```r ggplot(barley) + geom_point(aes(x = yield, y = variety, color = year)) + facet_wrap(~ site) + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-36-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ These plots show lower yields for 1932 than for 1931 for all sites except Morris. {{content}} ] -- * Cleveland suggest this may indicate a data entry error for Morris. {{content}} -- * A [paper from 2014](https://blog.revolutionanalytics.com/2014/07/theres-no-mistake-in-the-barley-data.html) suggests there may be no error. --- layout: true ## Heat Maps --- .pull-left[ Heat maps encode a numeric variable as the color of a tile placed at a particular position in a grid. {{content}} ] -- Heat maps are also called _matrix charts_ or _image plots_. {{content}} -- Heat maps can be effective in cases where a line plot contains too much over-potting, such as -- .pull-right.small-code[ .hide-code[ ```r gma <- filter(gapminder, continent == "Americas") ggplot(gma) + geom_line(aes(x = year, y = lifeExp, color = country)) + theme(legend.position = "top", legend.title = element_blank()) ``` <img src="amounts_files/figure-html/unnamed-chunk-37-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left.small-code[ A heat map of these data: .hide-code[ ```r ggplot(gma) + geom_tile(aes(x = year, y = reorder(country, lifeExp), fill = lifeExp)) + scale_x_continuous(expand = c(0, 0)) + scale_fill_viridis_c() + labs(y = NULL, fill = "Life Expectancy (years)") + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-38-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right[ Again it is useful to consider reordering of categories when there is no natural order. {{content}} ] -- This heat map orders the countries by average life expectancy. --- layout: true ## Bubble Charts --- A form of chart often seen in the popular press is the _bubble chart_. -- The bubble chart uses ares of circles to represent magnitudes. -- Position in the plane is usually not fully used or not used at all for mapping attributes. -- In on-line publications further information on each of the bubbles is often provided through interactions, such as a mouse-over tooltip. -- Other charts forms are almost always better for encoding the magnitude information. --- It is also easy to get the encoding wrong ([Corrected version](../img/shrinking-banks-correct.jpg)): <img src="../img/shrinking-banks-orig.jpg" width="60%" style="display: block; margin: auto;" /> <!-- http://www.perceptualedge.com/blog/?p=1532 ![](http://www.perceptualedge.com/blog/wp-content/uploads/2013/03/show-me-bubbles.png) http://junkcharts.typepad.com/junk_charts/2009/01/popping-the-bubble-so-to-speak.html https://flipchartfairytales.files.wordpress.com/2009/01/banks20market20cap20correct1.jpg --> --- `ggplot` bubble charts for the average `yield` values from the `barley` data and for the 2007 population sizes for the gapminder data: <img src="amounts_files/figure-html/unnamed-chunk-40-1.png" style="display: block; margin: auto;" /> --- layout: true ## Waterfall Charts --- Also called _cascade charts_. -- These usually show the decomposition of a total into positive and negative contributions from various sources. -- <!-- Fig 6.7 from Andy Kirk's _Data Visualization, 2nd Ed._ --> New Zealand net immigration by region: <img src="../img/6.7.Waterfall.png" width="70%" style="display: block; margin: auto;" /> --- Internet subscribers by month ([blog post with `ggplot` code](https://web.archive.org/web/20190622211938/http://anhhoangduc.com/blog/create-waterfall-chart-with-ggplot2/)). <img src="../img/waterfall.png" width="65%" style="display: block; margin: auto;" /> --- layout: true ## Polar Area Charts --- .pull-left[ Florence Nightingale's famous chart showing the effect of the sanitation improvements in March/April 1855: <!-- https://understandinguncertainty.org/files/Coxcombs.jpg --> <img src="../img/Coxcombs.jpg" width="95%" style="display: block; margin: auto;" /> ] -- .pull-right[ This chart has been called a _polar area diagrams_ or a _Coxcomb Chart_. {{content}} ] -- It can be viewed as * a bar chart with the bars positioned in front of each other, {{content}} -- * transformed to polar coordinates, {{content}} -- * with magnitude mapped to area (i.e. square root of magnitude mapped to radius). {{content}} -- The data are available in the `HistData` package as data frame `Nightingale`. --- .pull-left.small-code[ Some rearrangement of the data is needed to get it into a form amenable for plotting. .hide-code[ ```r library(forcats) data(Nightingale, package = "HistData") Night <- mutate(Nightingale, Period = ifelse(Date < as.Date("1855-04-01"), "Before", "After"), Period = factor(Period, c("Before", "After")), Month = factor(Month, month.abb)) %>% select(Date, Month, Year, Period, Disease, Wounds, Other) %>% pivot_longer(5 : 7, names_to = "Cause", values_to = "Deaths") ## Rearrange the Month levels to start with April. Night3 <- mutate(Night, Month = fct_shift(Month, 3)) ``` ] {{content}} ] -- A standard stacked bar chart of the data: -- .pull-right.small-code[ .hide-code[ ```r ggplot(Night3, aes(x = Month, y = Deaths, fill = Cause)) + geom_col() + facet_wrap(~ Period) + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-45-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ A grouped bar chart of the data: ] .pull-right.small-code[ .hide-code[ ```r ggplot(Night3, aes(x = Month, y = Deaths, fill = Cause)) + geom_col(position = "dodge") + facet_wrap(~ Period) + theme(legend.position = "top") ``` <img src="amounts_files/figure-html/unnamed-chunk-46-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Using `position = "identity"` the bars will be placed in front of each other rather than side by side or stacked. ] -- .pull-right.small-code[ .hide-code[ ```r p <- ggplot(Night3, aes(x = Month, y = Deaths, fill = Cause)) + geom_col(position = "identity", ## bars in front of each other width = 1, ## remove space between bars color = "black", ## bar border color linewidth = 0.1) + ## bar border thickness facet_wrap(~ Period) + theme(legend.position = "top") p ``` <img src="amounts_files/figure-html/unnamed-chunk-47-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-left[ Problem: Larger bars may be placed in front of smaller ones. ] --- .pull-left[ Reordering the `Deaths` values from largest to smallest ensures that larger bars do not cover smaller ones. ] .pull-right.small-code[ .hide-code[ ```r p <- ggplot(arrange(Night3, desc(Deaths)), aes(x = Month, y = Deaths, fill = Cause)) + geom_col(position = "identity", ## bars in front of each other width = 1, ## remove space between bars color = "black", ## bar border color linewidth = 0.1) + ## bar border thickness facet_wrap(~ Period) + theme(legend.position = "top") p ``` <img src="amounts_files/figure-html/unnamed-chunk-48-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Changing to polar coordinates and mapping square roots of `Deaths` to radius produces the basic polar area chart: ] .pull-right.small-code[ .hide-code[ ```r p %+% (arrange(Night3, desc(Deaths)) %>% mutate(Deaths = sqrt(Deaths))) + coord_polar() ``` <img src="amounts_files/figure-html/unnamed-chunk-49-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left[ Some adjustments bring the result closer to the original: ] .pull-right.small-code[ .hide-code[ ```r p %+% (arrange(Night, desc(Deaths)) %>% mutate(Month = fct_shift(Month, 6), Period = factor(Period, c("After", "Before")), Deaths = sqrt(Deaths))) + coord_polar() + scale_fill_manual( values = c(Wounds = "pink", Other = "darkgray", Disease = "lightblue")) + theme(axis.title = element_blank(), axis.text.y = element_blank(), axis.ticks = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), panel.border = element_blank()) ``` <img src="amounts_files/figure-html/unnamed-chunk-50-1.png" style="display: block; margin: auto;" /> ] ] --- layout: true ## Base and Lattice Graphics --- .pull-left.small-code[ Base graphics provides the `dotchart()` function for creating dot plots: .hide-code[ ```r with(le_am_2007, dotchart(sort(lifeExp), labels = country[order(lifeExp)])) ``` <img src="amounts_files/figure-html/unnamed-chunk-51-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left.small-code[ The `lattice` package provides `dotplot()`: .hide-code[ ```r library(lattice) dotplot(reorder(country, lifeExp) ~ lifeExp, data = le_am_2007) ``` <img src="amounts_files/figure-html/unnamed-chunk-52-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right.small-code[ Most lattice plots support a `group` argument that is usually mapped to color: .hide-code[ ```r dotplot(reorder(country, lifeExp) ~ lifeExp, group = year, data = le2, auto.key = TRUE) ``` <img src="amounts_files/figure-html/unnamed-chunk-53-1.png" style="display: block; margin: auto;" /> ] ] --- .pull-left.small-code[ Creating a basic bar chart in `lattice` uses the `barchart()` function. .hide-code[ ```r ## By default lattice creates a bad bar chart with a ## non-zero base line for these data. Fix that by ## specifying xlim = c(0, 85). barchart(reorder(country, lifeExp) ~ lifeExp, data = le_am_2007, xlim = c(0, 85)) ``` <img src="amounts_files/figure-html/unnamed-chunk-54-1.png" style="display: block; margin: auto;" /> ] ] -- .pull-right.small-code[ Base graphics also provides a bar chart with the `barplot()` function. .hide-code[ ```r par(mar = c(5, 5, 4, 2) + 0.1) with(le_am_2007, barplot(sort(lifeExp), horiz = TRUE, names.arg = country[order(lifeExp)], las = 1, cex.names = 0.7, cex.axis = 0.7)) ``` <img src="amounts_files/figure-html/unnamed-chunk-55-1.png" style="display: block; margin: auto;" /> ] ] --- layout: false ## Reading Chapter [_Visualizing amounts_](https://clauswilke.com/dataviz/visualizing-amounts.html) in [_Fundamentals of Data Visualization_](https://clauswilke.com/dataviz/). --- layout: false ## Interactive Tutorial An interactive [`learnr`](https://rstudio.github.io/learnr/) tutorial for these notes is [available](../tutorials/amounts.Rmd). You can run the tutorial with ```r STAT4580::runTutorial("amounts") ``` You can install the current version of the `STAT4580` package with ```r remotes::install_gitlab("luke-tierney/STAT4580") ``` You may need to install the `remotes` package from CRAN first. --- layout: true ## Exercises --- 1) A plot similar to this was featured in a CNN news story several years ago: .pull-left[ <img src="amounts_files/figure-html/unnamed-chunk-58-1.png" style="display: block; margin: auto;" /> ] .pull-right[ Which of the following is approximately correct: * About the same number of democrats as republicans agreed with the court. * About 15% more democrats than republicans agreed with the court. * About tree times as many democrats than republicans agreed with the court. * About two times as many democrats than republicans agreed with the court. ] --- 2) Consider the stacked bar chart produced by the following code: ```r library(tidyverse) mpg2 <- mutate(mpg, class = fct_rev(fct_infreq(class)), cyl = factor(cyl)) p <- ggplot(mpg2, aes(y = class, fill = cyl)) + geom_bar() ``` Which of these modifications makes it easiest to compare the count of 4-cylinder models within the different classes? * a. `p %+% mutate(mpg2, cyl = factor(cyl, c(4, 5, 6, 8)))` * b. `p %+% mutate(mpg2, cyl = factor(cyl, c(8, 6, 5, 4)))` * c. `p %+% mutate(mpg2, cyl = factor(cyl, c(5, 6, 4, 8)))` * d. `p %+% mutate(mpg2, cyl = factor(cyl, c(4, 6, 8, 5)))` --- 3) The bar chart produced by the following code has `x` axis labels that could be improved: ```r library(gapminder) library(dplyr) library(ggplot2) library(scales) p <- filter(gapminder, year == 2007) %>% group_by(continent) %>% summarize(avgGdpPercap = mean(gdpPercap)) %>% ggplot(aes(x = avgGdpPercap, y = continent)) + geom_col() + labs(x = "Average GDP Per Capita", y = NULL) + theme_minimal() + theme(text = element_text(size = 16)) ``` There are a number of different options. Which of the following does **not** provide improved `x` axis labels? * a. `p + scale_x_continuous(labels = label_comma())` * b. `p + scale_x_continuous(labels = label_dollar())` * c. `p + scale_x_continuous(labels = unit_format(scale = 1/1000, unit = "K", prefix = "$"))` * d. `p + scale_x_continuous(labels = c("$10,000", "$20,000", "$30,000"))` --- 4) A stacked bar chart is appropriate if the combined bar heights of the stacked bars have a reasonable interpretation. Consider the following two plots: <img src="amounts_files/figure-html/unnamed-chunk-61-1.png" style="display: block; margin: auto;" /> Which of the following statements is true: * a. P1 is an appropriate use of a stacked bar chart but P2 is not. * b. P2 is an appropriate use of a stacked bar chart but P1 is not. * c. Neither P1 nor P2 is an appropriate use of a stacked bar chart. * d. Both P1 and P2 are appropriate uses of a stacked bar chart.
//adapted from Emi Tanaka's gist at //https://gist.github.com/emitanaka/eaa258bb8471c041797ff377704c8505