class: center, middle, inverse, title-slide .title[ # Tables and Geographic Data ] .author[ ### Maithreyi Gopalan ] .date[ ### Week 8 ] --- layout: true <script> feather.replace() </script> <div class="slides-footer"> <span> <a class = "footer-icon-link" href = "https://github.com/maithgopalan/c2-dataviz-2026/raw/main/static/slides/w8.pdf"> <i class = "footer-icon" data-feather="download"></i> </a> <a class = "footer-icon-link" href = "https://dataviz-win2026.netlify.app/slides/w8.html"> <i class = "footer-icon" data-feather="link"></i> </a> <a class = "footer-icon-link" href = "https://github.com/maithgopalan/c2-dataviz-2026"> <i class = "footer-icon" data-feather="github"></i> </a> </span> </div> --- # Agenda * Refresher: Flexdashboards and Quarto Blog Deployment * Tables + [{gt}](https://gt.rstudio.com/) + [{kableExtra}](https://haozhu233.github.io/kableExtra/) + [{reactable}](https://glin.github.io/reactable/index.html) * Geographic data + Vector/raster data + Producing basic maps + Geospatial ecosystem --- class: inverse-red middle # Refresher ## Flexdashboards and Quarto Blog Deployment --- # What is a Flexdashboard? * R Markdown format for building dashboard layouts — no Shiny required for static versions * Install: `install.packages("flexdashboard")` * New file: **File → New File → R Markdown → From Template → Flex Dashboard** * Knit to HTML — deploy like any other HTML file ```yaml --- title: "My Dashboard" output: flexdashboard::flex_dashboard: orientation: columns vertical_layout: fill --- ``` --- # Flexdashboard Layout Basics Columns and rows are defined with level 2 headers and dashes: ```md Column {data-width=650} ------------------------------------- ### Chart A < r code > Column {data-width=350} ------------------------------------- ### Chart B < r code > ### Chart C < r code > ``` * `###` creates a new panel — panels split space evenly within a column * `{data-width=650}` sets column width (no spaces around `=`!) --- # Rows Instead of Columns ```yaml output: flexdashboard::flex_dashboard: orientation: rows ``` ```md Row {data-height=500} ------------------------------------- ### Chart A Row {data-height=300} ------------------------------------- ### Chart B ### Chart C ``` --- # Multiple Pages ```md # Page 1 Column {data-width=650} ------------------------------------- ### Chart A # Page 2 Column ------------------------------------- ### Chart B ``` Level 1 headers (`#`) create new pages in the nav bar. --- # Tabsets ```md Column {.tabset data-width=650} ------------------------------------- ### Chart 1 < r code > ### Chart 2 < r code > ### Data Table < r code > ``` No comma between multiple column arguments: * ✅ `Column {.tabset data-width=650}` * ❌ `Column {.tabset, data-width=650}` --- # Sidebar ```md Sidebar {.sidebar} ===================================== Describe the dashboard here. Supports **markdown**, [links](http://example.com), etc. ``` Use `====` (not `----`) to keep the sidebar across multiple pages. --- # Adding Interactivity For `plotly` and `reactable` — no Shiny needed: ``` r library(plotly) p <- ggplot(mpg, aes(displ, cty)) + geom_point() + geom_smooth() ggplotly(p) ``` For full Shiny interactivity, add `runtime: shiny` to YAML and use `render*` functions. --- # Deploying a Flexdashboard Since it renders to HTML, deploy anywhere that hosts HTML: * **GitHub Pages** — push the `.html` file to your repo, enable Pages * **Netlify** — drag and drop the HTML file at [netlify.com/drop](https://app.netlify.com/drop) * **shinyapps.io** — required if using `runtime: shiny` For GitHub Pages, simplest approach: ```bash # Keep your dashboard in the root or docs/ folder # Ensure that its renamed to index.html # Enable Pages in repo Settings → Pages → /root or /docs ``` --- class: inverse-blue middle # Quarto Blog ## Quick Deployment Refresher --- # Quarto Blog: The Key Files ``` my-blog/ ├── _quarto.yml # site settings — theme, navbar, output-dir ├── index.qmd # listing page (your home page) ├── about.qmd # about/bio page ├── posts/ # one subfolder per post │ └── my-post/ │ └── index.qmd ├── styles.css # CSS overrides └── docs/ # built site — this gets pushed to GitHub ``` `output-dir: docs` in `_quarto.yml` is what makes GitHub Pages work. --- # `_quarto.yml` Essentials ```yaml project: type: website output-dir: docs website: title: "My Blog" site-url: https://USERNAME.github.io/my-blog/ navbar: right: - about.qmd - icon: github href: https://github.com/USERNAME format: html: theme: flatly css: styles.css toc: true execute: freeze: auto warning: false ``` --- # Writing a Post Create `posts/my-post/index.qmd`: ```yaml --- title: "My Post Title" author: "Maithreyi Gopalan" date: "2026-02-18" categories: [R, education policy] image: "thumbnail.png" draft: false --- ``` Write content below the `---`. Use `#|` for chunk options: ```r ``` --- # Render and Preview ```bash # Live preview with hot reload quarto preview # Build the full site to /docs quarto render ``` `freeze: auto` means posts only re-run when source changes — commit `_freeze/` to GitHub! --- # Deploying to GitHub Pages 1. Run `quarto render` — builds site into `docs/` 2. Commit everything including `docs/`: ```bash git add . git commit -m "Build site" git push ``` 3. On GitHub: **Settings → Pages → Branch: main, Folder: /docs → Save** Your site goes live at `https://USERNAME.github.io/REPO/` --- # Troubleshooting Deployment | Problem | Fix | |---|---| | Posts missing from listing | Check `draft: false` and post is in `posts/` | | Site not updating | Confirm `docs/` is committed, not in `.gitignore` | | YAML errors | Remove `#` inline comments from YAML blocks | | Push rejected | Use a PAT token, not your GitHub password | | Fonts not loading | `@import` must be first line of `styles.css` | --- class: inverse-blue middle # Tables <style type="text/css"> table { font-size: 1rem; } .rt-table { font-size: 0.9rem; } .rt-pagination { font-size: 0.9rem; } .rt-search { font-size: 0.9rem; } </style> --- class: inverse-green background-image: url(https://github.com/rstudio/gt/raw/master/man/figures/logo.svg?sanitize=TRUE) background-size: contain --- # {gt} Overview * Pipe-oriented — works naturally with `%>%` * Beautiful tables with minimal code * Spanner heads and grouping built in * Renders to HTML and PDF seamlessly -- Probably the best package for static tables. [{kableExtra}](https://haozhu233.github.io/kableExtra/) is also excellent — fewer people know `gt`, which is why we cover it here. --- # Install ``` r install.packages("gt") ``` --- # The hard part * Getting your data into the right shape for a table * Use your `pivot_*` skills ``` r library(fivethirtyeight) flying ``` ``` ## # A tibble: 1,040 × 27 ## respondent_id gender age height children_under_18 household_income education location frequency recline_frequency recline_obligation recline_rude ## <dbl> <chr> <ord> <ord> <lgl> <ord> <ord> <chr> <ord> <ord> <lgl> <ord> ## 1 3436139758 <NA> <NA> <NA> NA <NA> <NA> <NA> Once a y… <NA> NA <NA> ## 2 3434278696 Male 30-44 "6'3\"" TRUE <NA> Graduate degree Pacific Once a y… About half the t… TRUE Somewhat ## 3 3434275578 Male 30-44 "5'8\"" FALSE $100,000 - $149,999 Bachelor degree Pacific Once a y… Usually TRUE No ## 4 3434268208 Male 30-44 "5'11\"" FALSE $0 - $24,999 Bachelor degree Pacific Once a y… Always FALSE No ## 5 3434250245 Male 30-44 "5'7\"" FALSE $50,000 - $99,999 Bachelor degree Pacific Once a m… About half the t… FALSE No ## 6 3434245875 Male 30-44 "5'9\"" TRUE $25,000 - $49,999 Graduate degree East No… Once a y… Usually TRUE No ## 7 3434235351 Male 30-44 "6'2\"" TRUE <NA> Some college or… Pacific Once a m… Once in a while FALSE Somewhat ## 8 3434218031 Male 30-44 "6'0\"" TRUE $0 - $24,999 Bachelor degree New Eng… Once a y… Once in a while TRUE No ## 9 3434213681 <NA> <NA> "6'0\"" TRUE <NA> <NA> <NA> Once a y… Once in a while TRUE No ## 10 3434172894 Male 30-44 "5'6\"" FALSE $0 - $24,999 Bachelor degree Pacific Once a y… Never TRUE Very ## # ℹ 1,030 more rows ## # ℹ 15 more variables: recline_eliminate <lgl>, switch_seats_friends <ord>, switch_seats_family <ord>, wake_up_bathroom <ord>, wake_up_walk <ord>, baby <ord>, ## # unruly_child <ord>, two_arm_rests <chr>, middle_arm_rest <chr>, shade <chr>, unsold_seat <ord>, talk_stranger <ord>, get_up <ord>, electronics <lgl>, ## # smoked <lgl> ``` --- ``` r flying %>% count(gender, age, recline_frequency) ``` ``` ## # A tibble: 53 × 4 ## gender age recline_frequency n ## <chr> <ord> <ord> <int> ## 1 Female 18-29 Never 24 ## 2 Female 18-29 Once in a while 36 ## 3 Female 18-29 About half the time 10 ## 4 Female 18-29 Usually 13 ## 5 Female 18-29 Always 10 ## 6 Female 18-29 <NA> 19 ## 7 Female 30-44 Never 21 ## 8 Female 30-44 Once in a while 25 ## 9 Female 30-44 About half the time 22 ## 10 Female 30-44 Usually 22 ## # ℹ 43 more rows ``` --- ``` r smry <- flying %>% count(gender, age, recline_frequency) %>% drop_na(age, recline_frequency) %>% pivot_wider( names_from = "age", values_from = "n" ) smry ``` ``` ## # A tibble: 10 × 6 ## gender recline_frequency `18-29` `30-44` `45-60` `> 60` ## <chr> <ord> <int> <int> <int> <int> ## 1 Female Never 24 21 19 23 ## 2 Female Once in a while 36 25 30 36 ## 3 Female About half the time 10 22 18 17 ## 4 Female Usually 13 22 26 28 ## 5 Female Always 10 21 29 12 ## 6 Male Never 24 17 20 18 ## 7 Male Once in a while 19 39 40 29 ## 8 Male About half the time 11 11 16 11 ## 9 Male Usually 14 30 15 27 ## 10 Male Always 11 14 21 14 ``` --- # Turn into a table ``` r library(gt) smry %>% gt() ``` -- ## Note: these may look slightly different on slides The way they render locally is how they'll appear in a standard R Markdown or Quarto file. --- class: middle
gender
recline_frequency
18-29
30-44
45-60
> 60
Female
Never
24
21
19
23
Female
Once in a while
36
25
30
36
Female
About half the time
10
22
18
17
Female
Usually
13
22
26
28
Female
Always
10
21
29
12
Male
Never
24
17
20
18
Male
Once in a while
19
39
40
29
Male
About half the time
11
11
16
11
Male
Usually
14
30
15
27
Male
Always
11
14
21
14
--- # Add gender as a grouping variable ``` r smry %>% * group_by(gender) %>% gt() ``` --- class: middle
recline_frequency
18-29
30-44
45-60
> 60
Female
Never
24
21
19
23
Once in a while
36
25
30
36
About half the time
10
22
18
17
Usually
13
22
26
28
Always
10
21
29
12
Male
Never
24
17
20
18
Once in a while
19
39
40
29
About half the time
11
11
16
11
Usually
14
30
15
27
Always
11
14
21
14
--- # Add a spanner head ``` r smry %>% group_by(gender) %>% gt() %>% tab_spanner( label = "Age Range", * columns = c(`18-29`, `30-44`, `45-60`, `> 60`) ) ``` --- class: middle
recline_frequency
Age Range
18-29
30-44
45-60
> 60
Female
Never
24
21
19
23
Once in a while
36
25
30
36
About half the time
10
22
18
17
Usually
13
22
26
28
Always
10
21
29
12
Male
Never
24
17
20
18
Once in a while
19
39
40
29
About half the time
11
11
16
11
Usually
14
30
15
27
Always
11
14
21
14
--- # Change column names ``` r smry %>% group_by(gender) %>% gt() %>% tab_spanner( label = "Age Range", columns = c(`18-29`, `30-44`, `45-60`, `> 60`) ) %>% * cols_label(recline_frequency = "Recline") ``` --- class: middle
Recline
Age Range
18-29
30-44
45-60
> 60
Female
Never
24
21
19
23
Once in a while
36
25
30
36
About half the time
10
22
18
17
Usually
13
22
26
28
Always
10
21
29
12
Male
Never
24
17
20
18
Once in a while
19
39
40
29
About half the time
11
11
16
11
Usually
14
30
15
27
Always
11
14
21
14
--- # Align columns ``` r smry %>% group_by(gender) %>% gt() %>% tab_spanner( label = "Age Range", columns = c(`18-29`, `30-44`, `45-60`, `> 60`) ) %>% cols_label(recline_frequency = "Recline") %>% * cols_align(align = "left", * columns = recline_frequency) ``` --- class: middle
Recline
Age Range
18-29
30-44
45-60
> 60
Female
Never
24
21
19
23
Once in a while
36
25
30
36
About half the time
10
22
18
17
Usually
13
22
26
28
Always
10
21
29
12
Male
Never
24
17
20
18
Once in a while
19
39
40
29
About half the time
11
11
16
11
Usually
14
30
15
27
Always
11
14
21
14
--- # Add a title ``` r smry %>% group_by(gender) %>% gt() %>% tab_spanner( label = "Age Range", columns = c(`18-29`, `30-44`, `45-60`, `> 60`) ) %>% cols_label(recline_frequency = "Recline") %>% cols_align(align = "left", columns = recline_frequency) %>% * tab_header( * title = "Airline Passengers", * subtitle = "Leg space is limited, what do you do?" * ) ``` --- class: middle
Airline Passengers
Leg space is limited, what do you do?
Recline
Age Range
18-29
30-44
45-60
> 60
Female
Never
24
21
19
23
Once in a while
36
25
30
36
About half the time
10
22
18
17
Usually
13
22
26
28
Always
10
21
29
12
Male
Never
24
17
20
18
Once in a while
19
39
40
29
About half the time
11
11
16
11
Usually
14
30
15
27
Always
11
14
21
14
--- # Format columns ``` r smry %>% * mutate(across(c(`18-29`, `30-44`, `45-60`, `> 60`), * ~.x / 100)) %>% group_by(gender) %>% gt() %>% tab_spanner( label = "Age Range", columns = c(`18-29`, `30-44`, `45-60`, `> 60`) ) %>% * fmt_percent( * columns = c(`18-29`, `30-44`, `45-60`, `> 60`), * decimals = 0 * ) %>% cols_label(recline_frequency = "Recline") %>% cols_align(align = "left", columns = recline_frequency) %>% tab_header( title = "Airline Passengers", subtitle = "Leg space is limited, what do you do?" ) ``` --- class: middle
Airline Passengers
Leg space is limited, what do you do?
Recline
Age Range
18-29
30-44
45-60
> 60
Female
Never
24%
21%
19%
23%
Once in a while
36%
25%
30%
36%
About half the time
10%
22%
18%
17%
Usually
13%
22%
26%
28%
Always
10%
21%
29%
12%
Male
Never
24%
17%
20%
18%
Once in a while
19%
39%
40%
29%
About half the time
11%
11%
16%
11%
Usually
14%
30%
15%
27%
Always
11%
14%
21%
14%
--- # Add a source note ``` r ... %>% * tab_source_note( * source_note = md("Data from [fivethirtyeight](https://fivethirtyeight.com/features/airplane-etiquette-recline-seat/)") * ) ``` --- class: middle
Airline Passengers
Leg space is limited, what do you do?
Recline
Age Range
18-29
30-44
45-60
> 60
Female
Never
24%
21%
19%
23%
Once in a while
36%
25%
30%
36%
About half the time
10%
22%
18%
17%
Usually
13%
22%
26%
28%
Always
10%
21%
29%
12%
Male
Never
24%
17%
20%
18%
Once in a while
19%
39%
40%
29%
About half the time
11%
11%
16%
11%
Usually
14%
30%
15%
27%
Always
11%
14%
21%
14%
Data from
fivethirtyeight
--- # Color cells ``` r ... %>% * data_color( * columns = c(`18-29`, `30-44`, `45-60`, `> 60`), * fn = scales::col_numeric( * palette = c("#FFFFFF", "#FF0000"), * domain = NULL * ) * ) %>% ... ``` --- class: middle
Airline Passengers
Leg space is limited, what do you do?
Recline
Age Range
18-29
30-44
45-60
> 60
Female
Never
24%
21%
19%
23%
Once in a while
36%
25%
30%
36%
About half the time
10%
22%
18%
17%
Usually
13%
22%
26%
28%
Always
10%
21%
29%
12%
Male
Never
24%
17%
20%
18%
Once in a while
19%
39%
40%
29%
About half the time
11%
11%
16%
11%
Usually
14%
30%
15%
27%
Always
11%
14%
21%
14%
Data from
fivethirtyeight
--- # What else? * Lots more it can do — see the [website](https://gt.rstudio.com) -- [Thomas Mock](https://twitter.com/thomas_mock) does great work with tables and often shares tutorials (e.g., [embedding custom features](https://themockup.blog/posts/2020-10-31-embedding-custom-features-in-gt-tables/), [functions and themes](https://themockup.blog/posts/2020-09-26-functions-and-themes-for-gt-tables/), [10 table rules](https://themockup.blog/posts/2020-09-04-10-table-rules-in-r/)). --- class: inverse-red center middle # A few other table options --- # kableExtra ### A few quick examples Make sure to specify `results = "asis"` in your chunk options. .pull-left[ ``` r library(knitr) library(kableExtra) dt <- mtcars[1:5, 1:6] kable(dt) %>% kable_styling("striped") %>% column_spec(5:7, bold = TRUE) ``` ] .pull-right[ <table class="table table-striped" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;"> </th> <th style="text-align:right;"> mpg </th> <th style="text-align:right;"> cyl </th> <th style="text-align:right;"> disp </th> <th style="text-align:right;"> hp </th> <th style="text-align:right;"> drat </th> <th style="text-align:right;"> wt </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> Mazda RX4 </td> <td style="text-align:right;"> 21.0 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 160 </td> <td style="text-align:right;font-weight: bold;"> 110 </td> <td style="text-align:right;font-weight: bold;"> 3.90 </td> <td style="text-align:right;font-weight: bold;"> 2.620 </td> </tr> <tr> <td style="text-align:left;"> Mazda RX4 Wag </td> <td style="text-align:right;"> 21.0 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 160 </td> <td style="text-align:right;font-weight: bold;"> 110 </td> <td style="text-align:right;font-weight: bold;"> 3.90 </td> <td style="text-align:right;font-weight: bold;"> 2.875 </td> </tr> <tr> <td style="text-align:left;"> Datsun 710 </td> <td style="text-align:right;"> 22.8 </td> <td style="text-align:right;"> 4 </td> <td style="text-align:right;"> 108 </td> <td style="text-align:right;font-weight: bold;"> 93 </td> <td style="text-align:right;font-weight: bold;"> 3.85 </td> <td style="text-align:right;font-weight: bold;"> 2.320 </td> </tr> <tr> <td style="text-align:left;"> Hornet 4 Drive </td> <td style="text-align:right;"> 21.4 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 258 </td> <td style="text-align:right;font-weight: bold;"> 110 </td> <td style="text-align:right;font-weight: bold;"> 3.08 </td> <td style="text-align:right;font-weight: bold;"> 3.215 </td> </tr> <tr> <td style="text-align:left;"> Hornet Sportabout </td> <td style="text-align:right;"> 18.7 </td> <td style="text-align:right;"> 8 </td> <td style="text-align:right;"> 360 </td> <td style="text-align:right;font-weight: bold;"> 175 </td> <td style="text-align:right;font-weight: bold;"> 3.15 </td> <td style="text-align:right;font-weight: bold;"> 3.440 </td> </tr> </tbody> </table> ] --- ``` r kable(dt) %>% kable_styling("striped") %>% column_spec(5:7, bold = TRUE) %>% row_spec(c(2, 4), bold = TRUE, color = "#EFF3F7", background = "#71B0DE") ``` <table class="table table-striped" style="margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;"> </th> <th style="text-align:right;"> mpg </th> <th style="text-align:right;"> cyl </th> <th style="text-align:right;"> disp </th> <th style="text-align:right;"> hp </th> <th style="text-align:right;"> drat </th> <th style="text-align:right;"> wt </th> </tr> </thead> <tbody> <tr> <td style="text-align:left;"> Mazda RX4 </td> <td style="text-align:right;"> 21.0 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 160 </td> <td style="text-align:right;font-weight: bold;"> 110 </td> <td style="text-align:right;font-weight: bold;"> 3.90 </td> <td style="text-align:right;font-weight: bold;"> 2.620 </td> </tr> <tr> <td style="text-align:left;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> Mazda RX4 Wag </td> <td style="text-align:right;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 21.0 </td> <td style="text-align:right;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 6 </td> <td style="text-align:right;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 160 </td> <td style="text-align:right;font-weight: bold;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 110 </td> <td style="text-align:right;font-weight: bold;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 3.90 </td> <td style="text-align:right;font-weight: bold;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 2.875 </td> </tr> <tr> <td style="text-align:left;"> Datsun 710 </td> <td style="text-align:right;"> 22.8 </td> <td style="text-align:right;"> 4 </td> <td style="text-align:right;"> 108 </td> <td style="text-align:right;font-weight: bold;"> 93 </td> <td style="text-align:right;font-weight: bold;"> 3.85 </td> <td style="text-align:right;font-weight: bold;"> 2.320 </td> </tr> <tr> <td style="text-align:left;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> Hornet 4 Drive </td> <td style="text-align:right;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 21.4 </td> <td style="text-align:right;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 6 </td> <td style="text-align:right;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 258 </td> <td style="text-align:right;font-weight: bold;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 110 </td> <td style="text-align:right;font-weight: bold;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 3.08 </td> <td style="text-align:right;font-weight: bold;font-weight: bold;color: rgba(239, 243, 247, 255) !important;background-color: rgba(113, 176, 222, 255) !important;"> 3.215 </td> </tr> <tr> <td style="text-align:left;"> Hornet Sportabout </td> <td style="text-align:right;"> 18.7 </td> <td style="text-align:right;"> 8 </td> <td style="text-align:right;"> 360 </td> <td style="text-align:right;font-weight: bold;"> 175 </td> <td style="text-align:right;font-weight: bold;"> 3.15 </td> <td style="text-align:right;font-weight: bold;"> 3.440 </td> </tr> </tbody> </table> --- ``` r kable(dt) %>% kable_styling("striped", full_width = FALSE) %>% pack_rows( "Group 1", 1, 3, label_row_css = "background-color: #666; color: #fff;" ) %>% pack_rows( "Group 2", 4, 5, label_row_css = "background-color: #666; color: #fff;" ) ``` <table class="table table-striped" style="width: auto !important; margin-left: auto; margin-right: auto;"> <thead> <tr> <th style="text-align:left;"> </th> <th style="text-align:right;"> mpg </th> <th style="text-align:right;"> cyl </th> <th style="text-align:right;"> disp </th> <th style="text-align:right;"> hp </th> <th style="text-align:right;"> drat </th> <th style="text-align:right;"> wt </th> </tr> </thead> <tbody> <tr grouplength="3"><td colspan="7" style="background-color: #666; color: #fff;"><strong>Group 1</strong></td></tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Mazda RX4 </td> <td style="text-align:right;"> 21.0 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 160 </td> <td style="text-align:right;"> 110 </td> <td style="text-align:right;"> 3.90 </td> <td style="text-align:right;"> 2.620 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Mazda RX4 Wag </td> <td style="text-align:right;"> 21.0 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 160 </td> <td style="text-align:right;"> 110 </td> <td style="text-align:right;"> 3.90 </td> <td style="text-align:right;"> 2.875 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Datsun 710 </td> <td style="text-align:right;"> 22.8 </td> <td style="text-align:right;"> 4 </td> <td style="text-align:right;"> 108 </td> <td style="text-align:right;"> 93 </td> <td style="text-align:right;"> 3.85 </td> <td style="text-align:right;"> 2.320 </td> </tr> <tr grouplength="2"><td colspan="7" style="background-color: #666; color: #fff;"><strong>Group 2</strong></td></tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Hornet 4 Drive </td> <td style="text-align:right;"> 21.4 </td> <td style="text-align:right;"> 6 </td> <td style="text-align:right;"> 258 </td> <td style="text-align:right;"> 110 </td> <td style="text-align:right;"> 3.08 </td> <td style="text-align:right;"> 3.215 </td> </tr> <tr> <td style="text-align:left;padding-left: 2em;" indentlevel="1"> Hornet Sportabout </td> <td style="text-align:right;"> 18.7 </td> <td style="text-align:right;"> 8 </td> <td style="text-align:right;"> 360 </td> <td style="text-align:right;"> 175 </td> <td style="text-align:right;"> 3.15 </td> <td style="text-align:right;"> 3.440 </td> </tr> </tbody> </table> --- # kableExtra wrapup Many other options — see the documentation. Works well for both PDF and HTML. -- What about Microsoft Word? Use [{flextable}](https://davidgohel.github.io/flextable/index.html) — it's specifically designed for Word output. --- # Many others * [huxtable](https://hughjonesd.github.io/huxtable/) * [formattable](https://renkun-ken.github.io/formattable/) * [DT](https://rstudio.github.io/DT/) * [rhandsontable](https://jrowen.github.io/rhandsontable/) -- ### Particularly helpful for modeling output * [modelsummary](https://github.com/vincentarelbundock/modelsummary) * [stargazer](https://www.jakeruss.com/cheatsheets/stargazer/) * [pixiedust](https://github.com/nutterb/pixiedust) -- ### For descriptives * [gtsummary](https://github.com/ddsjoberg/gtsummary) --- class: inverse-red middle # reactable My favorite for interactive tables --- [](https://glin.github.io/reactable/index.html) Works great with [**shiny**](https://shiny.rstudio.com) too --- # Penguins data ``` r library(palmerpenguins) library(reactable) reactable(penguins) ``` --- class: middle <iframe src="widgets/rt_basic.html" width="100%" height="450px" frameborder="0"></iframe> --- # Rename columns ``` r penguins %>% reactable( columns = list( bill_length_mm = colDef(name = "Bill Length (mm)"), bill_depth_mm = colDef(name = "Bill Depth (mm)") ) ) ``` --- class: middle <iframe src="widgets/rt_rename.html" width="100%" height="450px" frameborder="0"></iframe> --- # Or use a function ``` r penguins %>% reactable( defaultColDef = colDef( header = function(x) str_to_title(gsub("_", " ", x)) ) ) ``` --- class: middle <iframe src="widgets/rt_function.html" width="100%" height="450px" frameborder="0"></iframe> --- # Add filter, search, pagination ``` r reactable(penguins, filterable = TRUE) reactable(penguins, searchable = TRUE) reactable(penguins, defaultPageSize = 3) reactable(penguins, defaultPageSize = 3, paginationType = "jump") ``` --- class: middle <iframe src="widgets/rt_filter.html" width="100%" height="450px" frameborder="0"></iframe> --- # Grouping and aggregation ``` r penguins %>% reactable( groupBy = c("species", "island"), columns = list( bill_length_mm = colDef(aggregate = "mean", format = colFormat(digits = 2)) ) ) ``` --- class: middle <iframe src="widgets/rt_grouping.html" width="100%" height="450px" frameborder="0"></iframe> --- # Sparklines ``` r library(sparkline) table_data <- penguins %>% group_by(species) %>% summarize(bill_length = list(bill_length_mm)) %>% mutate(boxplot = NA, sparkline = NA) table_data %>% reactable( columns = list( bill_length = colDef(cell = function(value) { sparkline(value, type = "bar") }), boxplot = colDef(cell = function(value, index) { sparkline(table_data$bill_length[[index]], type = "box") }), sparkline = colDef(cell = function(value, index) { sparkline(table_data$bill_length[[index]]) }) ) ) ``` --- class: middle <iframe src="widgets/rt_sparkline.html" width="100%" height="450px" frameborder="0"></iframe> --- # Lots more! The goal today isn't to teach you everything — it's to show you what's possible. Check out the [documentation](https://glin.github.io/reactable/index.html) for more. -- Also check out [{reactablefmtr}](https://kcuilla.github.io/reactablefmtr/index.html) for easier use and amazing visual extensions! --- class: inverse-blue middle # Handling Data for Deployment ## Flexdashboards and Quarto Blogs --- # The Core Question When you embed a widget or dashboard in a Quarto blog — where does the **data** live? Four options, each with tradeoffs: * Self-contained HTML * Committed data folder * Inline base64 encoding * Remote data source The right choice depends on **data size** and **sensitivity** --- # Option 1: Self-Contained HTML Add one line to your flexdashboard YAML: ````yaml output: flexdashboard::flex_dashboard: self_contained: true ```` * Bundles data, widgets, and CSS into a **single HTML file** * Just commit and push that one file — no broken paths * ✅ Best for: small datasets, maximum simplicity * ❌ Avoid if: file size gets large (>10MB) --- # Option 2: Commit a Data Folder Keep data alongside your built site: ```` docs/ ├── my-dashboard.html └── data/ └── my_data.csv ```` Read with a relative path in your Rmd: ````r df <- read_csv("data/my_data.csv") ```` * Commit everything including `data/` and push to GitHub * ✅ Best for: medium datasets you want to reuse across posts * ❌ Avoid if: data is sensitive or changes frequently --- # Option 3: Remote Data Source Pull live from an API, Google Sheets, or database at render time: ````r library(googlesheets4) df <- read_sheet("your-sheet-id") # or from tidycensus, an API, etc. df <- get_acs(geography = "tract", ...) ```` * Nothing sensitive ever touches GitHub * Data stays up to date without re-rendering * ✅ Best for: sensitive, large, or frequently updated data * Add `data/` to `.gitignore` if caching locally --- # Embedding Widgets: Quarto Blog In a Quarto blog post, widgets work **natively** — no extra steps: ````r
```` * `quarto render` automatically bundles widget dependencies into `docs/` * Works for: `reactable`, `plotly`, `leaflet`, `mapview`, `DT`, and more * Self-contained per post — just push `docs/` as usual ```` docs/ ├── index.html └── posts/ └── my-post/ └── index.html ← widget dependencies bundled in here ```` --- # Embedding Widgets: Flexdashboard In a flexdashboard, widgets also embed **inline by default** when knitting: ````r ### My Interactive Table ``` r library(reactable) reactable(penguins, searchable = TRUE, filterable = TRUE) ``` ```` * No `saveWidget()` needed — knitting produces one self-contained HTML * Works for all htmlwidgets: `plotly`, `leaflet`, `reactable`, `DT`, etc. * For Shiny-powered widgets, add `runtime: shiny` to YAML and deploy to shinyapps.io instead --- # The Exception: Xaringan Slides Xaringan does **not** support HTML widgets natively — widgets render blank: ````r # This will NOT show in xaringan slides reactable(penguins) ```` The fix — save each widget and embed via `<iframe>`: ````r # Save widget to file saveWidget(reactable(penguins), "widgets/rt_basic.html", selfcontained = TRUE) ```` Then in the slide: ````html <iframe src="widgets/rt_basic.html" width="100%" height="450px" frameborder="0"></iframe> ```` Commit the `widgets/` folder alongside your slides HTML — they must travel together. --- # Which Option Should You Use? | Situation | Approach | |---|---| | Quarto blog post | Use widget directly — Quarto handles it | | Flexdashboard | Use widget directly — embeds on knit | | Xaringan slides | `saveWidget()` + `<iframe>` + commit `widgets/` | | Small data, want simplicity | `self_contained: true` in YAML | | Medium data, reused across posts | Commit `data/` folder | | Sensitive or large data | Remote source + `.gitignore` | --- class: inverse-blue # Data Viz in the wild Cheyna and Ramtin * Kyla and Isha on deck for next week --- class: inverse-blue center middle # Geographic data --- # First — a disclaimer * We're *only* talking about **visualizing** geographic data, not analyzing it -- * There's SO MUCH we won't get to -- * Today is an intro — hopefully you'll feel comfortable with the basics --- # Learning objectives * Know the difference between vector and raster data * Be able to produce basic maps using a variety of tools * Be able to obtain different types of geographic data from a few different places * Be able to produce basic interactive maps * Understand the basics of the R geospatial ecosystem -- ### Today is partially about content and partially about exposure --- # Where to learn more ### [Geocomputation with R](https://geocompr.robinlovelace.net) .center[ <img src="https://geocompr.robinlovelace.net/images/cover.png" width = "260px"> ] --- class: inverse-orange center middle  # Vector versus raster data .footnote[Image from Zev Ross] --- # Vector data * Points, lines, and polygons * Can easily include non-spatial attributes (e.g., population within a polygon) -- * Come in the form of shapefiles (`.shp`), GeoJSON, or frequently in R packages -- ### This is what we'll focus on today Tends to be most relevant for social science research questions --- # Raster data .pull-left[ * Divides space into a grid * Each square (pixel) gets a value ] .pull-right[  ] -- Common formats include images, satellite data, and remote sensing data. -- Can be useful in social science to show things like population density or temperature grids. --- # Example  .footnote[[source](https://www.nytimes.com/interactive/2021/02/16/us/winter-storm-texas-power-outage-map.html)] --- # The #rspatial ecosystem * [{sf}](https://r-spatial.github.io/sf/index.html) — simple features, the standard for vector data * [{terra}](https://rspatial.org/terra/) — modern replacement for {raster} * [{ggplot2}](https://ggplot2.tidyverse.org) — `geom_sf()` for plotting sf objects * [{tmap}](https://github.com/mtennekes/tmap) — thematic map package * [{mapview}](https://r-spatial.github.io/mapview/index.html) — quick interactive maps -- ### Goal today Take you through a basic tour of each of these. --- # Some specific challenges with geospatial data * Coordinate reference systems and projections * List columns (specifically when working with {sf} objects) * Different geometry types (lines, points, polygons) * Vector versus raster --- # Working with spatial data Two main options: * `spatialDataFrame` (from the [{sp}](https://cran.r-project.org/web/packages/sp/sp.pdf) package) — older, less common now * **sf data frame** (simple features) — the modern standard, works natively with tidyverse Use `sf::st_as_sf()` to convert `{sp}` to `{sf}` if needed. --- # {tigris} ``` r library(tigris) library(sf) *options(tigris_class = "sf") roads_laneco <- roads("OR", "Lane") roads_laneco ``` ``` ## Simple feature collection with 20433 features and 4 fields ## Geometry type: LINESTRING ## Dimension: XY ## Bounding box: xmin: -124.1536 ymin: 43.4376 xmax: -121.8078 ymax: 44.29001 ## Geodetic CRS: NAD83 ## # A tibble: 20,433 × 5 ## LINEARID FULLNAME RTTYP MTFCC geometry ## <chr> <chr> <chr> <chr> <LINESTRING [°]> ## 1 1102152610459 W Lone Oak Lp M S1640 (-123.1256 44.10108, -123.1262 44.10109, -123.1263 44.1011, -123.1263 44.10112, -123.1... ## 2 1102217699747 Sheldon Village Lp M S1640 (-123.0742 44.07891, -123.073 44.07891, -123.0729 44.0789, -123.0729 44.0788, -123.072... ## 3 110458663505 Cottage Hts Lp M S1400 (-123.0522 43.7893, -123.0522 43.7894, -123.0521 43.78949, -123.0521 43.7895, -123.052... ## 4 1102152615811 Village Plz Lp M S1640 (-123.1051 44.08716, -123.1058 44.08736, -123.1063 44.08755, -123.1068 44.08778) ## 5 110458661289 River Pointe Lp M S1400 (-123.0864 44.10306, -123.0852 44.10305, -123.0851 44.10305, -123.085 44.10303, -123.0... ## 6 1102217699746 Sheldon Village Lp M S1640 (-123.0723 44.07875, -123.0723 44.07904, -123.0723 44.07937, -123.0723 44.07941, -123.... ## 7 110458664549 Village Plz Lp M S1400 (-123.1053 44.08658, -123.1052 44.08687, -123.1052 44.08698, -123.1051 44.08716, -123.... ## 8 1102223141058 Carpenter Byp M S1400 (-123.3368 43.78013, -123.3367 43.78008) ## 9 1106092829172 State Hwy 126 Bus S S1200 (-123.0502 44.04391, -123.0502 44.04396, -123.0515 44.0439, -123.0521 44.04388, -123.0... ## 10 1104493105112 State Hwy 126 Bus S S1200 (-122.996 44.04587, -122.9959 44.04586, -122.9957 44.04586, -122.9957 44.04586, -122.9... ## # ℹ 20,423 more rows ``` --- # I/O Write to disk: ``` r write_sf(roads_laneco, here::here("data", "roads_lane.shp")) ``` -- Read back in: ``` r roads_laneco <- read_sf(here::here("data", "roads_lane.shp")) ``` --- # {sf} works with ggplot Use `ggplot2::geom_sf()` ``` r ggplot(roads_laneco) + geom_sf(color = "gray60") ``` <!-- --> --- # Add water features ``` r lakes <- area_water("OR", "Lane") streams <- linear_water("OR", "Lane") ggplot() + geom_sf(data = lakes, fill = "#518FB5") + geom_sf(data = streams, color = "#518FB5") + geom_sf(data = roads_laneco, color = "gray60") ``` Note — these functions are all from [{tigris}](https://github.com/walkerke/tigris). --- <!-- --> --- # Quick aside: `osmdata` * Specifically for street-level data from OpenStreetMap ``` r bb <- osmdata::getbb("Eugene") bb ``` ``` ## min max ## x -123.20876 -123.03059 ## y 43.98753 44.13232 ``` --- ``` r ggplot() + geom_sf(data = lakes, fill = "#518FB5") + geom_sf(data = streams, color = "#518FB5", linewidth = 1.2) + geom_sf(data = roads_laneco, color = "gray60") + coord_sf(xlim = bb[1, ], ylim = bb[2, ]) ``` --- class: center middle <!-- --> --- # Fully with osmdata ``` r library(osmdata) library(colorspace) bb <- getbb("Eugene") roads <- bb %>% opq() %>% add_osm_feature("highway") %>% osmdata_sf() water <- bb %>% opq() %>% add_osm_feature("water") %>% osmdata_sf() ``` --- # Plot with osmdata ``` r ggplot() + geom_sf(data = water$osm_multipolygons, fill = "#518FB5", color = darken("#518FB5")) + geom_sf(data = water$osm_polygons, fill = "#518FB5", color = darken("#518FB5")) + geom_sf(data = water$osm_lines, color = darken("#518FB5")) + geom_sf(data = roads$osm_lines, color = "gray40", linewidth = 0.2) + coord_sf(xlim = bb[1, ], ylim = bb[2, ], expand = FALSE) + labs(caption = "Eugene, OR") ``` --- class: center <!-- --> --- # Getting census data with {tidycensus} ### Note You need a Census API key — register at [api.census.gov/data/key_signup.html](https://api.census.gov/data/key_signup.html) Then set it with: ``` r library(tidycensus) census_api_key("YOUR API KEY", install = TRUE) ``` Or add `CENSUS_API_KEY = "YOUR KEY"` to `.Renviron` via `usethis::edit_r_environ()` --- # Getting the data ``` r library(tidycensus) # Find variable code: # v <- load_variables(2022, "acs5"); View(v) census_vals <- get_acs( geography = "tract", state = "OR", variables = c(med_income = "B06011_001", ed_attain = "B15003_001"), year = 2022, geometry = TRUE ) ``` ``` ## | | | 0% | |=== | 2% | |===== | 3% | |======= | 5% | |=========== | 7% | |============= | 9% | |============== | 9% | |================ | 11% | |================== | 12% | |==================== | 13% | |====================== | 15% | |======================= | 15% | |========================= | 17% | |=========================== | 18% | |============================= | 19% | |=============================== | 20% | |================================= | 22% | |=================================== | 23% | |===================================== | 25% | |======================================== | 26% | |========================================== | 27% | |============================================ | 29% | |============================================== | 30% | |================================================ | 32% | |================================================== | 33% | |===================================================== | 35% | |======================================================= | 36% | |========================================================= | 37% | |=========================================================== | 39% | |============================================================= | 40% | |=============================================================== | 42% | |================================================================= | 43% | |==================================================================== | 45% | |====================================================================== | 46% | |======================================================================== | 47% | |========================================================================== | 49% | |============================================================================ | 50% | |============================================================================== | 52% | |================================================================================= | 53% | |=================================================================================== | 54% | |===================================================================================== | 56% | |======================================================================================= | 57% | |========================================================================================= | 59% | |=========================================================================================== | 60% | |============================================================================================== | 62% | |================================================================================================ | 63% | |================================================================================================== | 64% | |==================================================================================================== | 66% | |====================================================================================================== | 67% | |======================================================================================================== | 69% | |=========================================================================================================== | 70% | |============================================================================================================= | 72% | |=============================================================================================================== | 73% | |================================================================================================================= | 74% | |=================================================================================================================== | 76% | |===================================================================================================================== | 77% | |======================================================================================================================== | 79% | |========================================================================================================================== | 80% | |============================================================================================================================ | 81% | |============================================================================================================================== | 83% | |================================================================================================================================ | 84% | |================================================================================================================================== | 86% | |==================================================================================================================================== | 87% | |======================================================================================================================================= | 89% | |========================================================================================================================================= | 90% | |=========================================================================================================================================== | 91% | |============================================================================================================================================= | 93% | |=============================================================================================================================================== | 94% | |================================================================================================================================================= | 96% | |==================================================================================================================================================== | 97% | |====================================================================================================================================================== | 99% | |========================================================================================================================================================| 100% ``` --- # Look at the data ``` r census_vals ``` ``` ## Simple feature collection with 2002 features and 5 fields (with 14 geometries empty) ## Geometry type: MULTIPOLYGON ## Dimension: XY ## Bounding box: xmin: -124.5662 ymin: 41.99179 xmax: -116.4635 ymax: 46.29083 ## Geodetic CRS: NAD83 ## First 10 features: ## GEOID NAME variable estimate moe geometry ## 1 41001950100 Census Tract 9501; Baker County; Oregon med_income 30087 7312 MULTIPOLYGON (((-118.5194 4... ## 2 41001950100 Census Tract 9501; Baker County; Oregon ed_attain 2138 199 MULTIPOLYGON (((-118.5194 4... ## 3 41067031912 Census Tract 319.12; Washington County; Oregon med_income 66173 10025 MULTIPOLYGON (((-122.8003 4... ## 4 41067031912 Census Tract 319.12; Washington County; Oregon ed_attain 3031 448 MULTIPOLYGON (((-122.8003 4... ## 5 41067031100 Census Tract 311; Washington County; Oregon med_income 33125 4389 MULTIPOLYGON (((-122.8058 4... ## 6 41067031100 Census Tract 311; Washington County; Oregon ed_attain 2239 312 MULTIPOLYGON (((-122.8058 4... ## 7 41003010300 Census Tract 103; Benton County; Oregon med_income 39126 2752 MULTIPOLYGON (((-123.8167 4... ## 8 41003010300 Census Tract 103; Benton County; Oregon ed_attain 2690 343 MULTIPOLYGON (((-123.8167 4... ## 9 41029002600 Census Tract 26; Jackson County; Oregon med_income 30905 4519 MULTIPOLYGON (((-122.7616 4... ## 10 41029002600 Census Tract 26; Jackson County; Oregon ed_attain 2047 206 MULTIPOLYGON (((-122.7616 4... ``` --- # Remove missing geometry rows ``` r census_vals <- census_vals[!st_is_empty(census_vals$geometry), , drop = FALSE] ``` --- # Plot it ``` r library(colorspace) ggplot(census_vals) + geom_sf(aes(fill = estimate, color = estimate)) + facet_wrap(~variable) + guides(color = "none") + scale_fill_continuous_diverging("Blue-Red 3", rev = TRUE) + scale_color_continuous_diverging("Blue-Red 3", rev = TRUE) ``` --- # Hmm... <!-- --> --- # Try again — set midpoint ``` r income <- filter(census_vals, variable == "med_income") income_plot <- ggplot(income) + geom_sf(aes(fill = estimate, color = estimate)) + facet_wrap(~variable) + guides(color = "none") + scale_fill_continuous_diverging( "Blue-Red 3", rev = TRUE, * mid = mean(income$estimate, na.rm = TRUE) ) + scale_color_continuous_diverging( "Blue-Red 3", rev = TRUE, * mid = mean(income$estimate, na.rm = TRUE) ) + theme(legend.position = "bottom", legend.key.width = unit(2, "cm")) ``` --- ``` r income_plot ``` <!-- --> --- # Same thing for education ``` r ed <- filter(census_vals, variable == "ed_attain") ed_plot <- ggplot(ed) + geom_sf(aes(fill = estimate, color = estimate)) + facet_wrap(~variable) + guides(color = "none") + scale_fill_continuous_diverging( "Blue-Red 3", rev = TRUE, mid = mean(ed$estimate, na.rm = TRUE) ) + scale_color_continuous_diverging( "Blue-Red 3", rev = TRUE, mid = mean(ed$estimate, na.rm = TRUE) ) + theme(legend.position = "bottom", legend.key.width = unit(2, "cm")) ``` --- ``` r ed_plot ``` <!-- --> --- # Put them together ``` r gridExtra::grid.arrange(income_plot, ed_plot, ncol = 2) ``` <!-- --> --- class: inverse-blue middle # Bivariate color scales --- background-image: url(https://timogrossenbacher.ch/wp-content/uploads/2019/04/bm-thematic-bivariate-map-with-legend-1-2.png) background-size: cover --- # How? * Break continuous variables into categorical (quartile) bins * Assign each combination of bins a color * Make sure the color combinations are perceptually meaningful --  .footnote[gif from [Joshua Stevens](https://www.joshuastevens.net/cartography/make-a-bivariate-choropleth-map/)] --- # Move to wide format ``` r wider <- get_acs( geography = "tract", state = "OR", variables = c(med_income = "B06011_001", ed_attain = "B15003_001"), year = 2022, geometry = TRUE, * output = "wide" ) wider <- wider[!st_is_empty(wider$geometry), , drop = FALSE] ``` --- # Find quartiles ``` r ed_quartiles <- quantile( wider$ed_attainE, probs = seq(0, 1, length.out = 4), na.rm = TRUE ) inc_quartiles <- quantile( wider$med_incomeE, probs = seq(0, 1, length.out = 4), na.rm = TRUE ) ``` --- # Create cut variable ``` r wider <- wider %>% mutate(cat_ed = cut(ed_attainE, ed_quartiles), cat_inc = cut(med_incomeE, inc_quartiles)) wider %>% select(starts_with("cat")) ``` ``` ## Simple feature collection with 994 features and 2 fields ## Geometry type: MULTIPOLYGON ## Dimension: XY ## Bounding box: xmin: -124.5662 ymin: 41.99179 xmax: -116.4635 ymax: 46.29083 ## Geodetic CRS: NAD83 ## First 10 features: ## cat_ed cat_inc geometry ## 1 (0,2.45e+03] (6.53e+03,3.34e+04] MULTIPOLYGON (((-118.5194 4... ## 2 (2.45e+03,3.42e+03] (4.26e+04,1.07e+05] MULTIPOLYGON (((-122.8003 4... ## 3 (0,2.45e+03] (6.53e+03,3.34e+04] MULTIPOLYGON (((-122.8058 4... ## 4 (2.45e+03,3.42e+03] (3.34e+04,4.26e+04] MULTIPOLYGON (((-123.8167 4... ## 5 (0,2.45e+03] (6.53e+03,3.34e+04] MULTIPOLYGON (((-122.7616 4... ## 6 (0,2.45e+03] (6.53e+03,3.34e+04] MULTIPOLYGON (((-122.8811 4... ## 7 (2.45e+03,3.42e+03] (3.34e+04,4.26e+04] MULTIPOLYGON (((-122.8458 4... ## 8 (2.45e+03,3.42e+03] (6.53e+03,3.34e+04] MULTIPOLYGON (((-117.0657 4... ## 9 (3.42e+03,6.64e+03] (3.34e+04,4.26e+04] MULTIPOLYGON (((-123.0464 4... ## 10 (3.42e+03,6.64e+03] (3.34e+04,4.26e+04] MULTIPOLYGON (((-122.906 44... ``` --- # Set palette ``` r pal <- st_drop_geometry(wider) %>% count(cat_ed, cat_inc) %>% arrange(cat_ed, cat_inc) %>% drop_na(cat_ed, cat_inc) %>% mutate(pal = c("#F3F3F3", "#C3F1D5", "#8BE3AF", "#EBC5DD", "#C3C5D5", "#8BC5AF", "#E7A3D1", "#C3A3D1", "#8BA3AE")) pal ``` ``` ## cat_ed cat_inc n pal ## 1 (0,2.45e+03] (6.53e+03,3.34e+04] 141 #F3F3F3 ## 2 (0,2.45e+03] (3.34e+04,4.26e+04] 87 #C3F1D5 ## 3 (0,2.45e+03] (4.26e+04,1.07e+05] 100 #8BE3AF ## 4 (2.45e+03,3.42e+03] (6.53e+03,3.34e+04] 114 #EBC5DD ## 5 (2.45e+03,3.42e+03] (3.34e+04,4.26e+04] 110 #C3C5D5 ## 6 (2.45e+03,3.42e+03] (4.26e+04,1.07e+05] 107 #8BC5AF ## 7 (3.42e+03,6.64e+03] (6.53e+03,3.34e+04] 75 #E7A3D1 ## 8 (3.42e+03,6.64e+03] (3.34e+04,4.26e+04] 133 #C3A3D1 ## 9 (3.42e+03,6.64e+03] (4.26e+04,1.07e+05] 123 #8BA3AE ``` --- # Join and plot ``` r bivar_map <- left_join(wider, pal) %>% ggplot() + geom_sf(aes(fill = pal, color = pal)) + guides(fill = "none", color = "none") + scale_fill_identity() + scale_color_identity() ``` --- class: center middle <!-- --> --- # Add a legend ``` r leg <- ggplot(pal, aes(cat_ed, cat_inc)) + geom_tile(aes(fill = pal)) + scale_fill_identity() + coord_fixed() + labs(x = expression("Higher education" %->% ""), y = expression("Higher income" %->% "")) + theme(axis.text = element_blank(), axis.title = element_text(size = 12)) leg ``` <!-- --> --- # Put together ``` r library(cowplot) ggdraw() + draw_plot(bivar_map + theme_void(), 0.1, 0.1, 1, 1) + draw_plot(leg, -0.05, 0, 0.3, 0.3) ``` Coordinates are mostly guess/check depending on aspect ratio. --- <!-- --> --- class: inverse-orange middle # {tmap} ### Back to just one variable I mostly use `ggplot()`, but {tmap} is really powerful and has a clean syntax. --- # Education map with [{tmap}](https://cran.r-project.org/web/packages/tmap/vignettes/tmap-getstarted.html) ``` r library(tmap) tm_shape(wider) + tm_polygons("ed_attainE") + tm_layout(legend.outside = TRUE) ``` <!-- --> --- # Facet ``` r tm_shape(census_vals) + tm_polygons("estimate") + tm_facets("variable") + tm_layout(legend.outside = TRUE) ``` <!-- --> --- # Change colors ``` r tm_shape(wider) + tm_polygons("ed_attainE", * palette = "magma", * border.col = "gray90", * lwd = 0.1) + tm_layout(legend.outside = TRUE) ``` <!-- --> --- # Continuous legend ``` r tm_shape(wider) + tm_polygons("ed_attainE", style = "cont") + tm_layout(legend.outside = TRUE) ``` <!-- --> --- # Add text labels ``` r cnty <- get_acs( geography = "county", state = "OR", variables = c(ed_attain = "B15003_001"), year = 2022, geometry = TRUE ) ``` ``` ## | | | 0% | |= | 0% | |= | 1% | |== | 1% | |== | 2% | |=== | 2% | |==== | 2% | |==== | 3% | |===== | 3% | |===== | 4% | |====== | 4% | |======= | 4% | |======= | 5% | |======== | 5% | |========= | 6% | |========== | 6% | |========== | 7% | |=========== | 7% | |============ | 8% | |============= | 8% | |============= | 9% | |============== | 9% | |=============== | 10% | |================ | 10% | |================ | 11% | |================= | 11% | |================= | 12% | |================== | 12% | |=================== | 12% | |=================== | 13% | |==================== | 13% | |===================== | 14% | |====================== | 14% | |====================== | 15% | |======================= | 15% | |======================== | 16% | |========================= | 16% | |========================= | 17% | |========================== | 17% | |=========================== | 18% | |============================ | 18% | |============================ | 19% | |============================= | 19% | |============================== | 20% | |=============================== | 20% | |=============================== | 21% | |================================ | 21% | |================================= | 21% | |================================= | 22% | |================================== | 22% | |================================== | 23% | |=================================== | 23% | |==================================== | 23% | |==================================== | 24% | |===================================== | 24% | |===================================== | 25% | |====================================== | 25% | |======================================= | 25% | |======================================= | 26% | |======================================== | 26% | |======================================== | 27% | |========================================= | 27% | |========================================== | 27% | |========================================== | 28% | |=========================================== | 28% | |============================================ | 29% | |============================================= | 29% | |============================================= | 30% | |============================================== | 30% | |=============================================== | 31% | |================================================ | 31% | |================================================ | 32% | |================================================= | 32% | |================================================== | 33% | |=================================================== | 33% | |=================================================== | 34% | |==================================================== | 34% | |==================================================== | 35% | |===================================================== | 35% | |====================================================== | 35% | |====================================================== | 36% | |======================================================= | 36% | |======================================================== | 37% | |========================================================= | 37% | |========================================================= | 38% | |========================================================== | 38% | |=========================================================== | 39% | |============================================================ | 39% | |============================================================ | 40% | |============================================================= | 40% | |============================================================== | 41% | |=============================================================== | 41% | |=============================================================== | 42% | |================================================================ | 42% | |================================================================= | 43% | |================================================================== | 43% | |================================================================== | 44% | |=================================================================== | 44% | |==================================================================== | 45% | |===================================================================== | 45% | |===================================================================== | 46% | |====================================================================== | 46% | |======================================================================= | 46% | |======================================================================= | 47% | |======================================================================== | 47% | |======================================================================== | 48% | |========================================================================= | 48% | |========================================================================== | 48% | |========================================================================== | 49% | |=========================================================================== | 49% | |=========================================================================== | 50% | |============================================================================ | 50% | |============================================================================= | 50% | |============================================================================= | 51% | |============================================================================== | 51% | |============================================================================== | 52% | |=============================================================================== | 52% | |================================================================================ | 52% | |================================================================================ | 53% | |================================================================================= | 53% | |================================================================================== | 54% | |=================================================================================== | 54% | |=================================================================================== | 55% | |==================================================================================== | 55% | |==================================================================================== | 56% | |===================================================================================== | 56% | |====================================================================================== | 56% | |====================================================================================== | 57% | |======================================================================================= | 57% | |======================================================================================= | 58% | |======================================================================================== | 58% | |========================================================================================= | 58% | |========================================================================================= | 59% | |========================================================================================== | 59% | |========================================================================================== | 60% | |=========================================================================================== | 60% | |============================================================================================ | 60% | |============================================================================================ | 61% | |============================================================================================= | 61% | |============================================================================================== | 62% | |=============================================================================================== | 62% | |=============================================================================================== | 63% | |================================================================================================ | 63% | |================================================================================================= | 64% | |================================================================================================== | 64% | |================================================================================================== | 65% | |=================================================================================================== | 65% | |==================================================================================================== | 66% | |===================================================================================================== | 66% | |===================================================================================================== | 67% | |====================================================================================================== | 67% | |======================================================================================================= | 68% | |======================================================================================================== | 68% | |======================================================================================================== | 69% | |========================================================================================================= | 69% | |========================================================================================================== | 69% | |========================================================================================================== | 70% | |=========================================================================================================== | 70% | |=========================================================================================================== | 71% | |============================================================================================================ | 71% | |============================================================================================================= | 71% | |============================================================================================================= | 72% | |============================================================================================================== | 72% | |============================================================================================================== | 73% | |=============================================================================================================== | 73% | |================================================================================================================ | 73% | |================================================================================================================ | 74% | |================================================================================================================= | 74% | |================================================================================================================= | 75% | |================================================================================================================== | 75% | |=================================================================================================================== | 75% | |=================================================================================================================== | 76% | |==================================================================================================================== | 76% | |==================================================================================================================== | 77% | |===================================================================================================================== | 77% | |====================================================================================================================== | 77% | |====================================================================================================================== | 78% | |======================================================================================================================= | 78% | |======================================================================================================================= | 79% | |======================================================================================================================== | 79% | |========================================================================================================================= | 79% | |========================================================================================================================= | 80% | |========================================================================================================================== | 80% | |========================================================================================================================== | 81% | |=========================================================================================================================== | 81% | |============================================================================================================================ | 81% | |============================================================================================================================ | 82% | |============================================================================================================================= | 82% | |============================================================================================================================= | 83% | |============================================================================================================================== | 83% | |=============================================================================================================================== | 83% | |=============================================================================================================================== | 84% | |================================================================================================================================ | 84% | |================================================================================================================================ | 85% | |================================================================================================================================= | 85% | |================================================================================================================================== | 85% | |================================================================================================================================== | 86% | |=================================================================================================================================== | 86% | |==================================================================================================================================== | 87% | |===================================================================================================================================== | 87% | |===================================================================================================================================== | 88% | |====================================================================================================================================== | 88% | |======================================================================================================================================= | 89% | |======================================================================================================================================== | 89% | |======================================================================================================================================== | 90% | |========================================================================================================================================= | 90% | |========================================================================================================================================== | 91% | |=========================================================================================================================================== | 91% | |=========================================================================================================================================== | 92% | |============================================================================================================================================ | 92% | |============================================================================================================================================= | 93% | |============================================================================================================================================== | 93% | |============================================================================================================================================== | 94% | |=============================================================================================================================================== | 94% | |================================================================================================================================================ | 94% | |================================================================================================================================================ | 95% | |================================================================================================================================================= | 95% | |================================================================================================================================================= | 96% | |================================================================================================================================================== | 96% | |=================================================================================================================================================== | 96% | |=================================================================================================================================================== | 97% | |==================================================================================================================================================== | 97% | |==================================================================================================================================================== | 98% | |===================================================================================================================================================== | 98% | |====================================================================================================================================================== | 98% | |====================================================================================================================================================== | 99% | |======================================================================================================================================================= | 99% | |======================================================================================================================================================= | 100% | |========================================================================================================================================================| 100% ``` --- # Estimate polygon centroid ``` r centroids <- st_centroid(cnty) ``` -- ``` r centroids <- centroids %>% mutate(county = str_replace_all(NAME, " County, Oregon", "")) ``` --- # Plot with labels ``` r tm_shape(cnty) + tm_polygons("estimate", style = "cont") + tm_shape(centroids) + tm_text("county", size = 0.5) + tm_layout(legend.outside = TRUE) ``` <!-- --> --- # Create interactive maps Just run `tmap_mode("view")` then reuse the same code: ``` r tmap_mode("view") tm_shape(cnty) + tm_polygons("estimate") + tm_shape(centroids) + tm_text("county", size = 0.5) ``` ---
--- # mapview Really quick easy interactive maps — great for exploratory work. ``` r library(mapview) mapview(cnty) ```
--- ``` r mapview(cnty, zcol = "estimate") ```
--- ``` r mapview(cnty, zcol = "estimate", popup = leafpop::popupTable(cnty, zcol = c("NAME", "estimate"))) ```
--- class: inverse-blue center middle # A few other things of note --- # statebins ``` r library(statebins) statebins(states, state_col = "NAME", value_col = "estimate") + theme_void() ``` <!-- --> --- # Cartograms ``` r library(cartogram) or_county_pop <- get_acs( geography = "county", state = "OR", variables = "B01003_001", year = 2022, geometry = TRUE ) or_county_pop <- st_transform(or_county_pop, crs = 2992) carto_counties <- cartogram_cont(or_county_pop, "estimate") ``` --- # Compare .pull-left[ ``` r ggplot(or_county_pop) + geom_sf(fill = "#BCD8EB") ``` <!-- --> ] .pull-right[ ``` r ggplot(carto_counties) + geom_sf(fill = "#D5FFFA") ``` <!-- --> ] --- # US cartogram by population ``` r state_pop <- get_acs( geography = "state", variables = "B01003_001", year = 2022, geometry = TRUE ) state_pop <- st_transform(state_pop, crs = 2163) carto_states <- cartogram_cont(state_pop, "estimate") ``` --- ``` r ggplot(carto_states) + geom_sf() ``` <!-- --> --- # Last note You may or may not like cartograms. -- Just be careful not to lie with maps.  --- class: inverse-green # Next time: Shiny App+ Some final presentations!