class: center, middle, inverse, title-slide # STATS 220 ## Data visualisationπ --- .pull-left[ <br> > The greatest value of a picture is when it forces us to notice what we never expected to see. > <br> -- [John W. Tukey](https://en.wikipedia.org/wiki/John_Tukey) ] .pull-right[ <br> .center[<img src="img/eda.jpg" height="100%">] ] ??? * John Tukey, one of the most well-known stat * invent boxplot, stem-and-leaf (pencil and paper) * coined the term EDA --- ## numbers vs plots .pull-left[ ```r dino ``` ``` #> # A tibble: 142 x 2 #> x y #> <dbl> <dbl> #> 1 55.4 97.2 #> 2 51.5 96.0 #> 3 46.2 94.5 #> 4 42.8 91.4 #> 5 40.8 88.3 #> 6 38.7 84.9 #> # β¦ with 136 more rows ``` ] -- .pull-right[ <img src="figure/plots-1.png" width="480" style="display: block; margin: auto;" /> ] ??? * humans digest info quicker through eyes than reading tables. -> ytb, tt * numbers on their own don't make sense to us. --- ## numbers vs plots <img src="https://github.com/stephlocke/lazyCDN/blob/master/DinoSequential.gif?raw=true" width="100%"> .footnote[image credit: Steph Locke] ??? * simple stats cannot reveal full pic. * all xy data tbl share the same mean/sd/corr, but different str in data... --- ## Why data visualisation?π > A picture is worth a thousand words. -- Henrik Ibsen 1. Data visualisation communicates information much quicker than numerical tables. 2. Data visualisation can reveal unexpected structures in data; it is not surprising that data visualisation is one of the key tools in exploratory data analysis. 3. Data plot is usually more eye-catching even if you lose accuracy of the information. --- class: middle inverse ## Charts π₯ Graphics ??? * When we talk about data vis, we use plots, charts, graphics interchangablely * But when comes to stats, there's difference bt. * What do we mean by graphics, how to make graphics --- ## A toy example <br> .pull-left[ ```r sci_tbl ``` ``` #> # A tibble: 4 x 2 #> dept count #> <chr> <int> #> 1 Physics 12 #> 2 Mathematics 8 #> 3 Statistics 20 #> 4 Computer Science 23 ``` ] .pull-right[ * `dept`: discrete/categorical * `count`: quantitative/numeric <hr> What types of plots can we make? 1. bar plot for counts 2. pie chart for proportions ] ??? --- ## Named charts .pull-left[ * Bar plot ```r barplot(as.matrix(sci_tbl$count), legend = sci_tbl$dept) ``` <img src="figure/base-barplot-1.png" width="480" style="display: block; margin: auto;" /> ] .pull-right[ * Pie chart ```r pie(sci_tbl$count, labels = sci_tbl$dept) ``` <img src="figure/base-pieplot-1.png" width="480" style="display: block; margin: auto;" /> ] ??? * default r functions * one-off functions * What's the fundamental difference bt bar and pie --- ## Seems convenient, but ... <br> .pull-left[ .x[ * a limited set of named charts * single purpose functions * inconsistent inputs ] ] .pull-right[ ```r barplot(as.matrix(sci_tbl$count), legend = sci_tbl$dept) ``` ```r pie(sci_tbl$count, labels = sci_tbl$dept) ``` ] ??? --- .pull-left[ <br> <br> .center[<img src="img/gg.jpg" width="300px">] ] .pull-right[ > Grammar makes language expressive. A language consisting of words and no grammar (statement = word) expresses only as many ideas as there are words. By specifying how words are combined in statements, a grammar expands a languageβs scope. ] ??? * a book blew my mind, changed the view to look at stat graphics. * we can easily run out of names * we can generate many types of new graphics by combine components following the grammar. * this books lays the theoretical foundation to {ggplot2}, {tab}, {vega-lite} --- class: inverse background-image: url(img/named-gg.png) background-size: cover .footnote[image credit: Thomas Lin Pederson] ??? decomposed to --- class: inverse middle ### The *grammar of graphics* takes us beyond a limited set of .blue[charts (words)] to an almost unlimited world of .blue[graphical forms (statements)]. <hr> ### *{ggplot2}* provides a cohesive system for declaratively creating elegant graphics, based on The Grammar of Graphics. ??? * extends gg, and layered gg. * provides a cohesive and declarative grammar to create graphics --- .pull-left[ ```r library(ggplot2) ggplot(data = sci_tbl) + geom_bar( aes(x = "", y = count, fill = dept), stat = "identity" ) ``` <img src="figure/gg-col-1.png" width="480" style="display: block; margin: auto;" /> ] .pull-right[ ```r ggplot(data = sci_tbl) + geom_bar( aes(x = "", y = count, fill = dept), stat = "identity" ) + * coord_polar(theta = "y") ``` <img src="figure/gg-pie-1.png" width="480" style="display: block; margin: auto;" /> ] ??? * Back to our question, the difference * The difference is plotting bars on the polar coord --- ## A graphing template ```r ggplot(data = <DATA>, mapping = aes(<MAPPINGS>)) + layer(geom = <GEOM>, stat = <STAT>, position = <POSITION>) + layer(geom = <GEOM>, stat = <STAT>, position = <POSITION>) ``` 1. `data`: tibble/data.frame. 2. `mapping`: .brown[aes]thetic mappings between data variables and visual elements, via `aes()`. 3. `layer()`: a graphical layer is a combination of data, stat and geom with a potential position adjustment. * `geom`: geometric elements to render each data observation. * `stat`: statistical transformations applied to the data prior to plotting. * `position`: position adjustment, such as "identity", "stack", "dodge" etc. ??? * geom: points, bars, lines, text * stat: "identity", leave as is, boxplot, five numbers * `+`: layer + layer * When you think about a graphic to make: + which geom to represent the data + any stats to be used --- ## Layers: a bar chart π ```r ggplot(data = sci_tbl, mapping = aes(x = dept, y = count)) + layer(geom = "bar", stat = "identity", position = "identity") ``` <img src="figure/gg-layer-1.png" width="480" style="display: block; margin: auto;" /> --- ## Aesthetic mapping: positional ```r *p <- ggplot(sci_tbl, aes(x = dept, y = count)) p ``` <img src="figure/aes-map-1.png" width="480" style="display: block; margin: auto;" /> ??? * `ggplot()` initialise the plot * save a ggplot obj to a symbol --- ## Geoms .small[(a shorthand to `layer()`)] .pull-left[ ```r p + geom_bar(stat = "identity") ``` <img src="figure/gg-bar-1.png" width="480" style="display: block; margin: auto;" /> ] .pull-right[ ```r p + geom_col() ``` * `stat = "identity"` leaves data as is. * `geom_col()` is a shortcut to `geom_bar(stat = "identity")`. **Generally, we use `geom_*()` instead of `layer()` in practice.** ] ??? * auto complete for `geom_*()` --- ## Geoms .pull-left[ .small[ ```r p + geom_point() ``` <img src="figure/gg-point-1.png" width="480" style="display: block; margin: auto;" /> ] ] .pull-right[ .small[ ```r p + geom_segment(aes(xend = dept, y = 0, yend = count)) ``` <img src="figure/gg-seg-1.png" width="480" style="display: block; margin: auto;" /> ] ] ??? * We don't have to stick with bar * we can use points/vertical lines * `geom_segment()`: more aes --- ## Composite geoms: lollipop π = points + segments ```r p + geom_point() + geom_segment(aes(xend = dept, y = 0, yend = count)) ``` <img src="figure/gg-pop-1.png" width="480" style="display: block; margin: auto;" /> --- ## Geom [catalogue](https://ggplot2.tidyverse.org/reference/index.html#section-layers)
.footnote[source code: Emi Tanaka] --- ## Stats .pull-left[ * Aggregated (pre-computed) ```r sci_tbl ``` ``` #> # A tibble: 4 x 2 #> dept count #> <chr> <int> #> 1 Physics 12 #> 2 Mathematics 8 #> 3 Statistics 20 #> 4 Computer Science 23 ``` ] .pull-right[ * Disaggregated ```r sci_tbl0 ``` ``` #> # A tibble: 63 x 1 #> dept #> <chr> #> 1 Physics #> 2 Physics #> 3 Physics #> 4 Physics #> 5 Physics #> 6 Physics #> # β¦ with 57 more rows ``` ] --- ## Stats .pull-left[ .small[ ```r ggplot(sci_tbl, aes(x = dept, y = count)) + geom_bar(stat = "identity") ``` <img src="figure/gg-bar-asis-1.png" width="480" style="display: block; margin: auto;" /> ] ] .pull-right[ .small[ ```r ggplot(sci_tbl0, aes(x = dept)) + geom_bar(stat = "count") ``` <img src="figure/gg-bar-c-1.png" width="480" style="display: block; margin: auto;" /> ] ] --- ## Aesthetic mapping: visual .pull-left[ ```r p + geom_col(aes(colour = dept)) ``` <img src="figure/gg-bar-col-1.png" width="480" style="display: block; margin: auto;" /> ] .pull-right[ ```r p + geom_col(aes(fill = dept)) ``` <img src="figure/gg-bar-fill-1.png" width="480" style="display: block; margin: auto;" /> ] --- ## Mapping .brown[variables] / Setting .brown[constants] .pull-left[ ```r p + geom_col(aes(fill = dept)) ``` <img src="figure/gg-bar-fill2-1.png" width="480" style="display: block; margin: auto;" /> ] .pull-right[ ```r p + geom_col(fill = "#756bb1") ``` <img src="figure/gg-bar-fill-str-1.png" width="480" style="display: block; margin: auto;" /> ] ??? * bar -> rect -> 2d + stroke + fill + auto legend --- ## Mapping .brown[variables] + Setting .brown[constants] ```r p + geom_col(aes(fill = dept), colour = "#000000") ``` <img src="figure/gg-bar-both-1.png" width="600" style="display: block; margin: auto;" /> --- ## Visual aesthetics .pull-left[ * `colour`/`color`, `fill`: + named colours, e.g. `"red"` + RGB specification, e.g. `"#756bb1"` * `alpha`: opacity between 0 and 1 * `shape`: * an integer between 0 and 25 * a single string, e.g. `"triangle open"` * `linetype`: * an integer between 0 and 6 * a single string, e.g. `"dashed"` * `size`, `radius`: a numerical value (in millimetres) ] .pull-right[ <br> <br> <img src="https://ggplot2.tidyverse.org/reference/scale_shape-6.png" width="100%"> ] --- ## Your turn Describe a bubble chart in terms of grammar of graphics. <img src="figure/bubble-chart-1.png" width="600" style="display: block; margin: auto;" /> ??? gg: grammar of graphics {ggplot2}: the second version --- ## Coords .pull-left[ * Coordinate systems + `coord_cartesian()` (default) + ~~`coord_flip()`~~ (deprecated; now you can simply swap `x` and `y`) + `coord_map()` + `coord_polar()` ] .pull-right[ ```r p + geom_col(aes(fill = dept)) + * coord_polar(theta = "y") ``` <img src="figure/gg-arc-1.png" width="600" style="display: block; margin: auto;" /> ] ??? live demo: * `ggplot()` * `ggplot(data)` * `ggplot(data, aes())` * inherit aes + layers * swap x and y --- ## Themes: modify the look .pull-left[ * [Built-in ggplot themes](https://ggplot2.tidyverse.org/reference/ggtheme.html) + `theme_grey()`/`theme_gray()` + `theme_bw()`, `theme_linedraw()` + `theme_light()`, `theme_dark()` + `theme_minimal()`, `theme_classic()` + `theme_void()` ] .pull-right[ ```r p + geom_col(aes(fill = dept)) + * theme_bw() ``` <img src="figure/gg-theme-bw-1.png" width="600" style="display: block; margin: auto;" /> ] ??? * start with p11 --- ## Themes: modify the look .pull-left[ * Many R packages provide themes. + [{ggthemes}](https://github.com/jrnold/ggthemes) + [{ggthemr}](https://github.com/Mikata-Project/ggthemr) + [{hrbrthemes}](https://cinc.rud.is/web/packages/hrbrthemes/) + [{ggtech}](https://github.com/ricardo-bion/ggtech) ] .pull-right[ ```r library(ggthemes) p + geom_col(aes(fill = dept)) + * theme_economist() ``` <img src="figure/ggthemes-1.png" width="480" style="display: block; margin: auto;" /> ] ??? * to be able to modify the theme is the 1st step to make pub-ready plot * in an organisation, use a uniform theme across * if you want to make your first R package, contributing a theme would be a good start. --- ## Modify the look of *texts* with `element_text()` <img src="img/ggplot-theme-text-annotation.png" width="100%"> .footnote[image credit: Emi Tanaka] ??? * tons of args in `themes()` for fine tune * I'll quickly go through these args, but dive more deeply in week 7 for effective data vis. --- ## Modify the look of *texts* with `element_text()` ```r p + geom_col(aes(fill = dept)) + theme(axis.text.x = element_text(angle = 30, vjust = 0.1)) ``` <img src="figure/gg-theme-1.png" width="720" style="display: block; margin: auto;" /> --- ## Modify the look of *lines* with `element_line()` .center[<img src="img/ggplot-annotated-line-marks.png" width="80%">] .footnote[image credit: Emi Tanaka] --- ## Modify the look of *regions* with `element_rect()` .center[<img src="img/ggplot-annotated-rect-marks.png" width="60%">] .footnote[image credit: Emi Tanaka] --- class: middle inverse ## Small multiples .small[(or trellis/faceting plots)] ### π the idea of conditioning on the values taken on by one or more of the variables in a data set ??? --- .left-column[ ## Facets ] .right-column[ `mpg` data available from {ggplot2} ```r mpg ``` ``` #> # A tibble: 234 x 11 #> manufacturer model displ year cyl trans drv cty #> <chr> <chr> <dbl> <int> <int> <chr> <chr> <int> #> 1 audi a4 1.8 1999 4 auto(l5) f 18 #> 2 audi a4 1.8 1999 4 manual(mβ¦ f 21 #> 3 audi a4 2 2008 4 manual(mβ¦ f 20 #> 4 audi a4 2 2008 4 auto(av) f 21 #> 5 audi a4 2.8 1999 6 auto(l5) f 16 #> 6 audi a4 2.8 1999 6 manual(mβ¦ f 18 #> # β¦ with 228 more rows, and 3 more variables: hwy <int>, #> # fl <chr>, class <chr> ``` ] --- .left-column[ ## Facets ] .right-column[ ```r p_mpg <- ggplot(mpg, aes(displ, cty)) + geom_point(aes(colour = drv)) p_mpg ``` <img src="figure/gg-mpg-1.png" width="600" style="display: block; margin: auto;" /> ] --- .left-column[ ## Facets ### - `facet_grid()` ] .right-column[ ```r p_mpg + facet_grid(rows = vars(drv)) # facet_grid(~ drv) ``` <img src="figure/gg-facet-rows-1.png" width="600" style="display: block; margin: auto;" /> ] ??? * looking at conditional distribution * `grid` -> 2d matrix layout --- .left-column[ ## Facets ### - `facet_grid()` ] .right-column[ ```r p_mpg + facet_grid(cols = vars(drv)) # facet_grid(drv ~ .) ``` <img src="figure/gg-facet-cols-1.png" width="720" style="display: block; margin: auto;" /> ] --- .left-column[ ## Facets ### - `facet_grid()` ] .right-column[ ```r p_mpg + facet_grid(rows = vars(drv), cols = vars(cyl)) # facet_grid(cyl ~ drv) ``` <img src="figure/gg-facet-grid-1.png" width="840" style="display: block; margin: auto;" /> ] --- .left-column[ ## Facets ### - `facet_grid()` ### - `facet_wrap()` ] .right-column[ ```r p_mpg + facet_wrap(vars(drv, cyl), ncol = 3) # facet_wrap(~ drv + cyl, ncol = 3) ``` <img src="figure/gg-facet-wrap-1.png" width="720" style="display: block; margin: auto;" /> ] --- class: middle center background-image: url(img/data-vis-log.png) background-size: contain # .large[.orange[Exploratory data visualisation]] .footnote[image credit: Emi Tanaka] --- .left-column[ ## case study ### - import ] .right-column[ ```r movies <- as_tibble(jsonlite::read_json( "https://vega.github.io/vega-editor/app/data/movies.json", simplifyVector = TRUE)) movies ``` ``` #> # A tibble: 3,201 x 16 #> Title US_Gross Worldwide_Gross US_DVD_Sales #> <chr> <int> <dbl> <int> #> 1 The Land Girls 146083 146083 NA #> 2 First Love, Last Riβ¦ 10876 10876 NA #> 3 I Married a Strangeβ¦ 203134 203134 NA #> 4 Let's Talk About Sex 373615 373615 NA #> 5 Slam 1009819 1087521 NA #> 6 Mississippi Mermaid 24551 2624551 NA #> # β¦ with 3,195 more rows, and 12 more variables: #> # Production_Budget <int>, Release_Date <chr>, #> # MPAA_Rating <chr>, Running_Time_min <int>, #> # Distributor <chr>, Source <chr>, Major_Genre <chr>, #> # Creative_Type <chr>, Director <chr>, #> # Rotten_Tomatoes_Rating <int>, IMDB_Rating <dbl>, #> # IMDB_Votes <int> ``` ] --- .left-column[ ## case study ### - import ### - skim ] .right-column[ .small[ ```r skimr::skim(movies) ``` ``` #> ββ Data Summary ββββββββββββββββββββββββ #> Values #> Name movies #> Number of rows 3201 #> Number of columns 16 #> _______________________ #> Column type frequency: #> character 8 #> numeric 8 #> ________________________ #> Group variables None #> #> ββ Variable type: character ββββββββββββββββββββββββββββββββββββββββββββββββββββ #> skim_variable n_missing complete_rate min max empty n_unique whitespace #> 1 Title 1 1.00 1 66 0 3176 0 #> 2 Release_Date 7 0.998 8 11 0 1603 0 #> 3 MPAA_Rating 605 0.811 1 9 0 7 0 #> 4 Distributor 232 0.928 3 33 0 174 0 #> 5 Source 365 0.886 6 29 0 18 0 #> 6 Major_Genre 275 0.914 5 19 0 12 0 #> 7 Creative_Type 446 0.861 7 23 0 9 0 #> 8 Director 1331 0.584 7 27 0 550 0 #> #> ββ Variable type: numeric ββββββββββββββββββββββββββββββββββββββββββββββββββββββ #> skim_variable n_missing complete_rate mean sd #> 1 US_Gross 7 0.998 44002085. 62555311. #> 2 Worldwide_Gross 7 0.998 85343400. 149947343. #> 3 US_DVD_Sales 2637 0.176 34901547. 45895122. #> 4 Production_Budget 1 1.00 31069171. 35585913. #> 5 Running_Time_min 1992 0.378 110. 20.2 #> 6 Rotten_Tomatoes_Rating 880 0.725 54.3 28.1 #> 7 IMDB_Rating 213 0.933 6.28 1.25 #> 8 IMDB_Votes 213 0.933 29909. 44938. #> p0 p25 p50 p75 p100 hist #> 1 0 5493221. 22019466. 56091762. 760167650 βββββ #> 2 0 8031285. 31168926. 97283797 2767891499 βββββ #> 3 618454 9906211. 20331558. 37794216. 352582053 βββββ #> 4 218 6575000 20000000 42000000 300000000 βββββ #> 5 46 95 107 121 222 βββββ #> 6 1 30 55 80 100 β ββββ #> 7 1.4 5.6 6.4 7.2 9.2 βββ ββ #> 8 18 4828. 15106 35810. 519541 βββββ ``` ] ] --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ * Data analysis starts with questions (a.k.a. curiosity). .center[ <img src="https://rstudio-education.github.io/tidyverse-cookbook/images/data-science-workflow.png" width="100%"> ] ] ??? * eda: wandering around a city as a tourist: sometimes have destinations, sometimes no. * once you started off with one question, more questions on the way. * There's no best single plot for the data. * Make as many plots as possible, as quick as possible to get most facets of the data * end up with nothing * for internal use, don't need to polish --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ <i class="far fa-question-circle"></i> Are movies ratings consistent b/t IMDB & Rotten Tomatoes .small[ ```r ggplot(movies, aes(x = IMDB_Rating, y = Rotten_Tomatoes_Rating)) + geom_point(size = 0.5, alpha = 0.5) + geom_smooth(method = "gam") + theme(aspect.ratio = 1) ``` <img src="figure/movies-gam-1.png" width="420" style="display: block; margin: auto;" /> ] ] --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ <i class="far fa-question-circle"></i> Are movies ratings consistent b/t IMDB & Rotten Tomatoes .small[ ```r ggplot(movies, aes(x = IMDB_Rating, y = Rotten_Tomatoes_Rating)) + geom_hex() + theme(aspect.ratio = 1) ``` <img src="figure/movies-hex-1.png" width="480" style="display: block; margin: auto;" /> ] ] ??? * live demo: for rendering speed --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ <i class="far fa-question-circle"></i> The popularity of major genre .small[ ```r ggplot(movies, aes(y = Major_Genre)) + geom_bar() ``` <img src="figure/movies-bar-1.png" width="600" style="display: block; margin: auto;" /> ] ] --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ <i class="far fa-question-circle"></i> The likeness of major genre .small[ ```r ggplot(movies) + geom_boxplot(aes(x = IMDB_Rating, y = Major_Genre)) ``` <img src="figure/movies-boxplot-1.png" width="600" style="display: block; margin: auto;" /> ] ] --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ <i class="far fa-question-circle"></i> The likeness of major genre .small[ ```r ggplot(movies) + geom_density(aes(x = IMDB_Rating, fill = Major_Genre)) ``` <img src="figure/movies-density-1.png" width="720" style="display: block; margin: auto;" /> ] ] ??? * overlapping * could use facet --- .left-column[ ## case study ### - import ### - skim ### - vis ] .right-column[ <i class="far fa-question-circle"></i> The likeness of major genre .small[ ```r library(ggridges) ggplot(movies, aes(x = IMDB_Rating, y = Major_Genre)) + geom_density_ridges(aes(fill = Major_Genre)) ``` <img src="figure/movies-ridges-1.png" width="840" style="display: block; margin: auto;" /> ] ] --- ## {ggplot2}-ext π¦ > {ggplot2} now has an official extension mechanism. This means that others can now easily create their own stats, geoms and positions, and provide them in other packages. This should allow the ggplot2 community to flourish, even as less development work happens in ggplot2 itself. β‘οΈ <https://exts.ggplot2.tidyverse.org/gallery/> ??? * {ggplot2} has been around for more than 10 yrs. * {ggplot2} is extensible --- .pull-left[ [<img src="https://gganimate.com/reference/figures/logo.png" width="80px">](https://gganimate.com) ```r library(gganimate) ggplot(mtcars, aes(factor(cyl), mpg)) + geom_boxplot() + # Here comes the gganimate code transition_states( gear, transition_length = 2, state_length = 1 ) + enter_fade() + exit_shrink() + ease_aes('sine-in-out') ``` ] .pull-right[ <br> <br> <br> <img src="https://gganimate.com/reference/figures/README-unnamed-chunk-2-1.gif" width="100%"> ] --- .left-column[ [<img src="https://github.com/slowkow/ggrepel/raw/master/man/figures/logo.svg" width="80px">](https://ggrepel.slowkow.com/index.html) ] .right-column[ <br> <br> <br> <img src="https://ggrepel.slowkow.com/articles/examples_files/figure-html/comparison-1.png" width="100%"> ] --- ## The R Graph Gallery .center[[<img src="img/r-graph-gallery.png" width="80%">](https://www.r-graph-gallery.com)] ??? * good resources for d3.js --- .left-column[ ## To be continued ... ] .right-column[ <blockquote class="twitter-tweet"><p lang="en" dir="ltr">NEW: the Thursday 19 March update of our coronavirus mortality trajectories tracker<br><br>β’ Italy now has more Covid-19 deaths than Chinaβs total<br>β’ UK remains on a steeper mortality curve than Italy, while Britain remains far from lockdown<br><br>Live version here: <a href="https://t.co/VcSZISFxzF">https://t.co/VcSZISFxzF</a> <a href="https://t.co/QvByzSj6QX">pic.twitter.com/QvByzSj6QX</a></p>— John Burn-Murdoch (@jburnmurdoch) <a href="https://twitter.com/jburnmurdoch/status/1240723388336877569?ref_src=twsrc%5Etfw">March 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> ] ??? * a tragic yr cos of covid-19 * a blast yr for data vis * log scale --- ## Reading .pull-left[ .center[[<img src="https://d33wubrfki0l68.cloudfront.net/b88ef926a004b0fce72b2526b0b5c4413666a4cb/24a30/cover.png" height="520px">](https://r4ds.had.co.nz)] ] .pull-right[ * [Data visualisation](https://r4ds.had.co.nz/data-visualisation.html) * [{ggplot2} cheatsheet](https://github.com/rstudio/cheatsheets/raw/master/data-visualization-2.1.pdf) ]