class: inverse, bottom background-image: url(https://images.unsplash.com/photo-1588591795084-1770cb3be374?q=80&w=2070&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D) background-size: cover ## .large[R Medicine 2026: ggregions for building intuitive coding interfaces for anatomical visualizations] ## .small[] #### .tiny[Dr. Evangeline Reynolds | 2026-04-04 | DU, Image credit: Ross Sneddon, Upsplash] ??? Title slide --- class: inverse # Motivations for ggregions > ### I am told there are people who do not care for maps, and I find it hard to believe. - Robert Louis Stevenson -- > ### I hate when I work with spatial data that so much of my mindshare goes to thinking about 'how do I wrangle this this map and what on earth is going on' versus the actual meaning behind what I'm trying to map. - Emily Riederer -- > ### 'My teacher, Mr. Jayson, says a map is a picture of someplace from above. It's like flying over that spot in an airplane.' - 'Lisa' in Loreen Leedy's in 'Mappying Penny's World' --- class: inverse ## Maps are some of the most compelling and intuitive data visualizations, -- and they are some of the first visualizations people will come across in their lives. -- ## And there is great support for mapping in R and in the data visualization library {ggplot2} which supports many R packages. -- ## But, producing a simple map, like a choropleth, can feel much harder than producing other plots, like a scatterplot for example in ggplot2. --- count: false ## A scatter plot is produced by: ``` r *ggplot(cars) # data ``` --- count: false ## A scatter plot is produced by: ``` r ggplot(cars) + # data * aes(x = speed, y = dist) # position and other visual channel mapping ``` --- count: false ## A scatter plot is produced by: ``` r ggplot(cars) + # data aes(x = speed, y = dist) + # position and other visual channel mapping * geom_point() # layer ``` <style> .panel1-scatter-auto { color: black; width: 99%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel2-scatter-auto { color: black; width: NA%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel3-scatter-auto { color: black; width: NA%; hight: 33%; float: left; padding-left: 1%; font-size: 80% } </style> ??? ## In other words, you might say, 'used this data, and points, and the positions are defined by x and y' --- <!-- #### ggregions posits that by investing in methods to easily create new geo-specific ggplot2 layer extensions (geom_*s), producing maps with ggplot2 can become as intuitive as creating other plots. About specifying a map, you might say, 'use this data, draw regions, position and color fill according to var1 and var2.' --> <!-- The proposed regions mapping might look something like this: --> ## ggregions proposed API ``` r ggplot(mapping_data) + # data * aes(region_id = var1_region_id, # position mapping fill = var2) + # and other visual channel mapping * geom_region() # layer ``` -- > ## [ggregions-generated] APIs feels like they actually puts the "question" at the center. - Emily Riederer --- # What {ggregions} delivers -- ## Effortlessly write new region-specific geom_* functions. <!-- The {ggregions} allows API creators to quickly create easy-to-use ggplot2 layer extensions (e.g. geom_*()s) that adhere to the ggplot2 'grammar'. --> --- ## Example with geographic regions: US states ### Step 1. prepare data ``` r library(dplyr) # Step 1. prep some of their geo reference data us_states_ref <- usmapdata::us_map() |> select( state_name = full, # state_name will positional aes like x or y state_abbr = abbr, fips, geometry = geom, # one column with geometry is required ) # state_abbr will also be optional positional aes like x or y ``` --- ### Step 2. write user-facing functions with ggregion 'write' utilities <!-- Then, authors will be able use `write_geom_region_locale()` to create a regions-specific ggplot2 layer, for example `geom_us_state()` below, which can be made available in their geo packages. --> ``` r # Step 2. use `write_geom_region_locale` library(ggregions) geom_us_state <- write_geom_region_locale(us_states_ref) stamp_us_state <- write_stamp_region_locale(us_states_ref) geom_us_state_text <- write_geom_region_text_locale(us_states_ref) stamp_us_state_text <- write_stamp_region_text_locale(us_states_ref) ``` --- ### Step 2b. (optional) Inspect user-facing function ``` r geom_us_state ``` ``` function (mapping = aes(), data = NULL, stat = StatRegion, position = "identity", na.rm = FALSE, show.legend = NA, inherit.aes = TRUE, ref_data = us_states_ref, ...) { c(layer_sf(geom = GeomSf, data = data, mapping = mapping, stat = stat, position = position, show.legend = show.legend, inherit.aes = inherit.aes, params = rlang::list2(na.rm = na.rm, ref_data = ref_data, ...)), coord_sf(crs = st_crs_mod(ref_data))) } <environment: namespace:ggregions> ``` --- #### Steb 2bb (for the truely curious) Inspect StatRegions compute ``` r ggregions:::StatRegion$compute_panel ``` ``` <ggproto method> <Wrapper function> function (...) compute_panel(...) <Inner function (f)> function (data, scales, ref_data, keep = NULL, drop = NULL, stamp = F) { ref_data$id <- ref_data[1][[1]] if (!is.null(keep)) { ref_data <- dplyr::filter(ref_data, id %in% keep) } if (!is.null(drop)) { ref_data <- dplyr::filter(ref_data, !(id %in% drop)) } ref_data <- ggplot2::StatSfCoordinates$compute_group(ggplot2::StatSf$compute_panel(ref_data, coord = ggplot2::CoordSf), coord = ggplot2::CoordSf) if (!stamp) { dplyr::inner_join(ref_data, data) } else { ref_data } } ``` <!-- ## Deliver intuitive, newcomer-welcoming spatial viz experience --> <!-- By including the newly specified `geom_` function in their geo data package (perhaps instead or in addition to a convenience wrapper that are typical of geo packages) the package *users* will be able to use the` data + aes + geom` formulation for defining their plots. Instead of positional aesthetics like x, y or geometry, a region identifier like `state_name` or `fips` can be used. Internally, the regions `geom_*` will translate between place name or other identifier to the necessary borders (`sfc`s). --> <!-- Below is some tabular data with geographic region identifiers, but no boundaries: --> <!-- Even though there is no boundary information, analysts can map this data with the familiar data + aesthetic mapping + layer syntax that makes ggplot2 easy to read, write, and reason about. --> --- count: false .panel1-usmapdata-auto[ ``` r *library(ggplot2) ``` ] .panel2-usmapdata-auto[ ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) *library(usmapdata) ``` ] .panel2-usmapdata-auto[ ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) *us_income ``` ] .panel2-usmapdata-auto[ ``` # A tibble: 52 × 5 GEOID NAME variable income moe <chr> <chr> <chr> <dbl> <dbl> 1 01 Alabama income 24476 136 2 02 Alaska income 32940 508 3 04 Arizona income 27517 148 4 05 Arkansas income 23789 165 5 06 California income 29454 109 6 08 Colorado income 32401 109 7 09 Connecticut income 35326 195 8 10 Delaware income 31560 247 9 11 District of Columbia income 43198 681 10 12 Florida income 25952 70 # ℹ 42 more rows ``` ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> * ggplot() ``` ] .panel2-usmapdata-auto[ <!-- --> ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> ggplot() + * aes(state_name = NAME) ``` ] .panel2-usmapdata-auto[ <!-- --> ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> ggplot() + aes(state_name = NAME) + * geom_us_state() ``` ] .panel2-usmapdata-auto[ <!-- --> ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> ggplot() + aes(state_name = NAME) + geom_us_state() + * aes(fill = income) ``` ] .panel2-usmapdata-auto[ <!-- --> ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> ggplot() + aes(state_name = NAME) + geom_us_state() + aes(fill = income) + * scale_fill_viridis_c(option = "magma") ``` ] .panel2-usmapdata-auto[ <!-- --> ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> ggplot() + aes(state_name = NAME) + geom_us_state() + aes(fill = income) + scale_fill_viridis_c(option = "magma") + * geom_us_state_text(color = "white") ``` ] .panel2-usmapdata-auto[ <!-- --> ] --- count: false .panel1-usmapdata-auto[ ``` r library(ggplot2) library(usmapdata) us_income |> ggplot() + aes(state_name = NAME) + geom_us_state() + aes(fill = income) + scale_fill_viridis_c(option = "magma") + geom_us_state_text(color = "white") + * aes(label = after_stat(state_abbr)) ``` ] .panel2-usmapdata-auto[ <!-- --> ] <style> .panel1-usmapdata-auto { color: black; width: 38.6060606060606%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel2-usmapdata-auto { color: black; width: 59.3939393939394%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel3-usmapdata-auto { color: black; width: NA%; hight: 33%; float: left; padding-left: 1%; font-size: 80% } </style> <!-- Packages that might take avantage of ggregion's functionality include --> <!-- [afrimapr](https://afrimapr.github.io/afrimapr.website/), [geobr](https://ipeagit.github.io/geobr/), [cancensus](https://mountainmath.github.io/cancensus/index.html), [chilemapas](https://pacha.dev/chilemapas/), [RCzechia](https://github.com/jlacko/RCzechia), [geofi](https://ropengov.github.io/geofi/) --> <!-- , [geokz](https://github.com/arodionoff/geokz), [mapsPERU](https://github.com/musajajorge/mapsPERU) and [geoidep](https://geografo.pe/geoidep/index.html), [mapSpain](https://github.com/rOpenSpain/mapSpain/), [geographr](https://github.com/britishredcrosssociety/geographr), [geouy](https://github.com/RichDeto/geouy), [tigris](https://github.com/walkerke/tigris), --> <!-- and [rgeoboundaries](https://github.com/wmgeolab/rgeoboundaries). --> <!-- Since these geo packages have many users, the impacts of `{ggregions}` could be significant. --> --- class: inverse, center, middle # Anatomical regions --- ### ggregions with teethr::dental_arcade_mapping https://github.com/bbartholdy/teethr --- ### data preparation ``` r library(tidyverse) library(teethr) teeth_ref_data <- dental_arcade_mapping |> as_tibble() |> left_join(tooth_notation |> * select(tooth = text, fdi = FDI, standard = standards) ) ``` -- ### ggplot2 'layer' preparation ``` r library(ggregions) geom_tooth <- ggregions::write_geom_region_locale(teeth_ref_data) geom_tooth_text <- write_geom_region_text_locale(teeth_ref_data) stamp_tooth <- write_stamp_region_locale(teeth_ref_data) stamp_tooth_text <- write_stamp_region_text_locale(teeth_ref_data) ``` --- count: false ### yields user-friendly API .panel1-arcade-auto[ ``` r *caries_ratios ``` ] .panel2-arcade-auto[ ``` # A tibble: 32 × 4 tooth n count ratio <chr> <int> <dbl> <dbl> 1 URI1 35 4 0.114 2 URI2 31 4 0.129 3 URC1 35 7 0.2 4 URP1 34 3 0.0882 5 URP2 23 5 0.217 6 URM1 32 7 0.219 7 URM2 25 7 0.28 8 URM3 22 9 0.409 9 ULI1 28 2 0.0714 10 ULI2 26 2 0.0769 # ℹ 22 more rows ``` ] --- count: false ### yields user-friendly API .panel1-arcade-auto[ ``` r caries_ratios |> * ggplot() ``` ] .panel2-arcade-auto[ <!-- --> ] --- count: false ### yields user-friendly API .panel1-arcade-auto[ ``` r caries_ratios |> ggplot() + * aes(tooth = tooth, * fill = ratio) ``` ] .panel2-arcade-auto[ <!-- --> ] --- count: false ### yields user-friendly API .panel1-arcade-auto[ ``` r caries_ratios |> ggplot() + aes(tooth = tooth, fill = ratio) + * geom_tooth(alpha = .5) ``` ] .panel2-arcade-auto[ <!-- --> ] --- count: false ### yields user-friendly API .panel1-arcade-auto[ ``` r caries_ratios |> ggplot() + aes(tooth = tooth, fill = ratio) + geom_tooth(alpha = .5) + * stamp_tooth_text(size = 2) ``` ] .panel2-arcade-auto[ <!-- --> ] --- count: false ### yields user-friendly API .panel1-arcade-auto[ ``` r caries_ratios |> ggplot() + aes(tooth = tooth, fill = ratio) + geom_tooth(alpha = .5) + stamp_tooth_text(size = 2) + * scale_fill_viridis_c() ``` ] .panel2-arcade-auto[ <!-- --> ] <style> .panel1-arcade-auto { color: black; width: 38.6060606060606%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel2-arcade-auto { color: black; width: 59.3939393939394%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel3-arcade-auto { color: black; width: NA%; hight: 33%; float: left; padding-left: 1%; font-size: 80% } </style> --- <img src="r-medicine-ggregions_files/figure-html/unnamed-chunk-10-1.png" width="65%" /> --- ## ggregions X gganatogram::hgFemale_list ### Step 1: Data preparation ``` r female_sf <- gganatogram::hgFemale_list[c(1:156, 180:195)] |> # return to this!! bind_rows() |> remove_missing() |> to_sf_routine() |> rename(organ = id) |> select(-group) ``` -- ### Step 2: User-facing function preparation ``` r stamp_organ <- ggregions::write_stamp_region_locale(female_sf) geom_organ <- ggregions::write_geom_region_locale(female_sf) stamp_organ_text <- ggregions::write_stamp_region_text_locale(female_sf) geom_organ_text <- ggregions::write_geom_region_text_locale(female_sf) ``` --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r *tribble(~my_organ, ~color, * "stomach", "cadetblue", * "brain", "pink3", * "colon", "darkseagreen4", * "lung", "plum", * "heart", "coral") ``` ] .panel2-gganatogram-auto[ ``` # A tibble: 5 × 2 my_organ color <chr> <chr> 1 stomach cadetblue 2 brain pink3 3 colon darkseagreen4 4 lung plum 5 heart coral ``` ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> *ggplot() ``` ] .panel2-gganatogram-auto[ <!-- --> ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> ggplot() + * stamp_organ(alpha = .2) ``` ] .panel2-gganatogram-auto[ <!-- --> ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> ggplot() + stamp_organ(alpha = .2) + * aes(organ = my_organ) ``` ] .panel2-gganatogram-auto[ <!-- --> ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> ggplot() + stamp_organ(alpha = .2) + aes(organ = my_organ) + * geom_organ() ``` ] .panel2-gganatogram-auto[ <!-- --> ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> ggplot() + stamp_organ(alpha = .2) + aes(organ = my_organ) + geom_organ() + * aes(fill = I(color)) ``` ] .panel2-gganatogram-auto[ <!-- --> ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> ggplot() + stamp_organ(alpha = .2) + aes(organ = my_organ) + geom_organ() + aes(fill = I(color)) + * geom_organ_text() ``` ] .panel2-gganatogram-auto[ <!-- --> ] --- count: false Enjoy! .panel1-gganatogram-auto[ ``` r tribble(~my_organ, ~color, "stomach", "cadetblue", "brain", "pink3", "colon", "darkseagreen4", "lung", "plum", "heart", "coral") |> ggplot() + stamp_organ(alpha = .2) + aes(organ = my_organ) + geom_organ() + aes(fill = I(color)) + geom_organ_text() + * ggplyr:::data_slice(5) ``` ] .panel2-gganatogram-auto[ <!-- --> ] <style> .panel1-gganatogram-auto { color: black; width: 38.6060606060606%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel2-gganatogram-auto { color: black; width: 59.3939393939394%; hight: 32%; float: left; padding-left: 1%; font-size: 80% } .panel3-gganatogram-auto { color: black; width: NA%; hight: 33%; float: left; padding-left: 1%; font-size: 80% } </style> --- class: inverse, bottom background-image: url(https://encrypted-tbn3.gstatic.com/shopping?q=tbn:ANd9GcTSNaV2hzHW71ovNcoPji1OZMZBz_EJQxwCRClzMeX-QmKTiP5OpNcMvuIVE7fmgObCuOsrcRLUMUxKG2P2kZSIkrSf8Puhpg) background-size: contain # Thank you! github: ## EvaMaeRey/ggregions --- ## Unresolved questions for this project: #### 1. Perhaps an *alternative to sf* would make sense for anatomical regions - we don't really anticipate *'crs'* to be specified. Would allowing another preparation/Stat make sense - polygon-based? Or just more attention to converting polygons to sf (relates to #3). -- #### 2. The gganatogram objects bring up the fact that regions *overplot* - the heart is covered by the lungs. So should there be a way to control the ordering (And Even if there is no regional overlap, overplotting can happen if we have the region more than once in the dataset) -- #### 3. ggregions' focus is on gg-type, friendly API, *but how much focus should be on preparing reference data.* The teethr and colon case might help here. -- #### 4. ggregions allows you to 'try-on' the ggregions framework (positional aesthetics that are symantic, 'texas' can be the answer to 'where'?). But, *what if you don't want to take the ggregions dependency?* Should there be more instruction regarding this? Licensing considerations? -- #### 5. More relevant to the geographic case, it might be useful to allow different resolution of the regions reference data. How important is addressing this a this point? -- #### 6. Can this project strengthen the R ecosystem? Motivation to get into packaging? How do we make that clearer, and the pathway easier? <!-- adjust font size in this css code chunk, currently 80 --> <style type="text/css"> .remark-code{line-height: 1.5; font-size: 70%} @media print { .has-continuation { display: block; } } code.r.hljs.remark-code{ position: relative; overflow-x: hidden; } code.r.hljs.remark-code:hover{ overflow-x:visible; width: 500px; border-style: solid; } </style>