Skip to content

samherniman/indoor_co2_visuals

Repository files navigation

Monthly indoorCO2map.com summary March 2026

There is a well documented relationship between indoor levels of CO2 and the amount of ventilation in indoor environments. Buildings with high indoor levels of CO2 have poor ventilation and are therefore more likely to be vectors of airborne diseases (like COVID-19, Measles, and Flu) and to trap indoor pollutants.

Measuring CO2 inside is a cheap way of measuring the air quality in indoor environments. When we breathe, we exhale CO2 and it gets trapped inside the room we are in. If the building has good ventilation it will leave quickly. If it has bad ventilation, it stays in the room and builds up.

If there is bad ventilation, then smoke from cooking can build up and that’s bad for you. Same thing for VOCs from perfumes, as well as gas leaks, radon, and mold spores. At high concentrations in artificial environments, they contribute to all sorts of things: cancer1, Alzheimer’s2–4, Parkinson’s3, childhood asthma5–9, childhood lung problems10,11, and heart conditions12. Bad ventilation also contributes to a much higher risk of respiratory infections. If someone who is sick breathes in a badly ventilated room, the infectious aerosols will float around in the room until someone breathes them in. In a well ventilated space, they are dispersed very quickly and the risk of infection is much lower. Having an open window in a classroom (or having an air filter), for instance, reduces school absences significantly.

CO2 levels outside are typically around 420 parts per million (ppm), so if we measure the CO2 in a room and it is higher than that, you know its not ventilating much. Anywhere from 400 - 600 ppm are considered well ventilated. Every indoor environment is going to trap some CO2 and that’s okay. Levels between 600 ppm and 1000 ppm may need some improvement. Anything above 1000 ppm is generally considered bad and should certainly be improved in some manner.

Indoor CO2-Map is a community science project to monitor indoor CO2 levels in non-residential buildings and transit systems around the world. Since April 2024 volunteers have brought CO2 monitors into cafes, shops, schools, trains, and all sorts of other places to monitor CO2 levels in them and upload them to a public database.

The following is a monthly summary of how this project is going.

Buildings

This month there were 828 measurements of 639 unique buildings.

cr-ccplot?

The most measured building was Rewe, a supermarket in Kassel, Germany (min: 738, mean: 818, max: 877), which was measured 12 times.

There were measurements in 17 separate countries.

cr-histco2plot?

Here is a graph that shows the distribution of all the CO2 measurements this month. The dashed red line shows the median which was 783 ppm. There are many measurements that we would consider good CO2 levels, however, you’ll notice that about 25 percent are over 1000 ppm, which really should be addressed.

cr-buildingtypes?

This graph shows the distribution of the most common building types in the month of March. The dark bar in the middle of each box and whisker plot shows the median value for each category. The rest of the lines show the range of the distribution. Most of the values fall within each box. If you want more information about how to interpret this graph, watch this video.

As is common, supermarkets tend to have higher CO2 values than other types of buildings. I’ve converted those CO2 values to the percentage of rebreathed air, which specifies how much of each breath you take has already been exhaled by someone else.

Here is a graph of all the recordings that happened this month shown by the grey curves. I’ve highlighted the highest one.cr-allcurves?

The building with the highest measured CO2 levels was The Black Sheep in Wien, Austria with a median CO2 value of 7080.5 ppm. While this is incredibly high, it is important to realize that this is an outlier. The majority of measurements are much lower than this. There is a boxplot to the right of the graph which shows where the majority of measurements fall.

The building with the lowest measured CO2 levels was Cineplex Germering in Germering, Germany with a median CO2 value of 425.5 ppm. There were some measurements that were even lower than this, but we have removed them from this analysis. Generally outdoor CO2 levels don’t go below 410 ppm, therefore we have removed any datapoints that are below 400 ppm. If your CO2 monitor consistently shows levels below 410 ppm while you are inside or outside, it is likely that your monitor needs recalibrating.

Here is a chart showing the 27 measurements that had a median CO2 value under 500. Keep in mind that some of these are potentially miscalibrated sensors or erroneous recordings where the sensor was outside. However, it is important to celebrate the places that do in fact have well ventilated spaces.

<style>#itfdtqefsv table { font-family: system-ui, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } #itfdtqefsv thead, #itfdtqefsv tbody, #itfdtqefsv tfoot, #itfdtqefsv tr, #itfdtqefsv td, #itfdtqefsv th { border-style: none; } #itfdtqefsv p { margin: 0; padding: 0; } #itfdtqefsv .gt_table { display: table; border-collapse: collapse; line-height: normal; margin-left: auto; margin-right: auto; color: #333333; font-size: 16px; font-weight: normal; font-style: normal; background-color: #FFFFFF; width: auto; border-top-style: solid; border-top-width: 2px; border-top-color: #A8A8A8; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #A8A8A8; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; } #itfdtqefsv .gt_caption { padding-top: 4px; padding-bottom: 4px; } #itfdtqefsv .gt_title { color: #333333; font-size: 125%; font-weight: initial; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; border-bottom-color: #FFFFFF; border-bottom-width: 0; } #itfdtqefsv .gt_subtitle { color: #333333; font-size: 85%; font-weight: initial; padding-top: 3px; padding-bottom: 5px; padding-left: 5px; padding-right: 5px; border-top-color: #FFFFFF; border-top-width: 0; } #itfdtqefsv .gt_heading { background-color: #FFFFFF; text-align: center; border-bottom-color: #FFFFFF; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #itfdtqefsv .gt_bottom_border { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #itfdtqefsv .gt_col_headings { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; } #itfdtqefsv .gt_col_heading { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 6px; padding-left: 5px; padding-right: 5px; overflow-x: hidden; } #itfdtqefsv .gt_column_spanner_outer { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: normal; text-transform: inherit; padding-top: 0; padding-bottom: 0; padding-left: 4px; padding-right: 4px; } #itfdtqefsv .gt_column_spanner_outer:first-child { padding-left: 0; } #itfdtqefsv .gt_column_spanner_outer:last-child { padding-right: 0; } #itfdtqefsv .gt_column_spanner { border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: bottom; padding-top: 5px; padding-bottom: 5px; overflow-x: hidden; display: inline-block; width: 100%; } #itfdtqefsv .gt_spanner_row { border-bottom-style: hidden; } #itfdtqefsv .gt_group_heading { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; text-align: left; } #itfdtqefsv .gt_empty_group_heading { padding: 0.5px; color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; vertical-align: middle; } #itfdtqefsv .gt_from_md > :first-child { margin-top: 0; } #itfdtqefsv .gt_from_md > :last-child { margin-bottom: 0; } #itfdtqefsv .gt_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; margin: 10px; border-top-style: solid; border-top-width: 0px; border-top-color: #D3D3D3; border-left-style: none; border-left-width: 1px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 1px; border-right-color: #D3D3D3; vertical-align: middle; overflow-x: hidden; } #itfdtqefsv .gt_stub { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; } #itfdtqefsv .gt_stub_row_group { color: #333333; background-color: #FFFFFF; font-size: 100%; font-weight: initial; text-transform: inherit; border-right-style: solid; border-right-width: 2px; border-right-color: #D3D3D3; padding-left: 5px; padding-right: 5px; vertical-align: top; } #itfdtqefsv .gt_row_group_first td { border-top-width: 2px; } #itfdtqefsv .gt_row_group_first th { border-top-width: 2px; } #itfdtqefsv .gt_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #itfdtqefsv .gt_first_summary_row { border-top-style: solid; border-top-color: #D3D3D3; } #itfdtqefsv .gt_first_summary_row.thick { border-top-width: 2px; } #itfdtqefsv .gt_last_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #itfdtqefsv .gt_grand_summary_row { color: #333333; background-color: #FFFFFF; text-transform: inherit; padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; } #itfdtqefsv .gt_first_grand_summary_row { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-top-style: double; border-top-width: 6px; border-top-color: #D3D3D3; } #itfdtqefsv .gt_last_grand_summary_row_top { padding-top: 8px; padding-bottom: 8px; padding-left: 5px; padding-right: 5px; border-bottom-style: double; border-bottom-width: 6px; border-bottom-color: #D3D3D3; } #itfdtqefsv .gt_striped { background-color: rgba(128, 128, 128, 0.05); } #itfdtqefsv .gt_table_body { border-top-style: solid; border-top-width: 2px; border-top-color: #D3D3D3; border-bottom-style: solid; border-bottom-width: 2px; border-bottom-color: #D3D3D3; } #itfdtqefsv .gt_footnotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #itfdtqefsv .gt_footnote { margin: 0px; font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #itfdtqefsv .gt_sourcenotes { color: #333333; background-color: #FFFFFF; border-bottom-style: none; border-bottom-width: 2px; border-bottom-color: #D3D3D3; border-left-style: none; border-left-width: 2px; border-left-color: #D3D3D3; border-right-style: none; border-right-width: 2px; border-right-color: #D3D3D3; } #itfdtqefsv .gt_sourcenote { font-size: 90%; padding-top: 4px; padding-bottom: 4px; padding-left: 5px; padding-right: 5px; } #itfdtqefsv .gt_left { text-align: left; } #itfdtqefsv .gt_center { text-align: center; } #itfdtqefsv .gt_right { text-align: right; font-variant-numeric: tabular-nums; } #itfdtqefsv .gt_font_normal { font-weight: normal; } #itfdtqefsv .gt_font_bold { font-weight: bold; } #itfdtqefsv .gt_font_italic { font-style: italic; } #itfdtqefsv .gt_super { font-size: 65%; } #itfdtqefsv .gt_footnote_marks { font-size: 75%; vertical-align: 0.4em; position: initial; } #itfdtqefsv .gt_asterisk { font-size: 100%; vertical-align: 0; } #itfdtqefsv .gt_indent_1 { text-indent: 5px; } #itfdtqefsv .gt_indent_2 { text-indent: 10px; } #itfdtqefsv .gt_indent_3 { text-indent: 15px; } #itfdtqefsv .gt_indent_4 { text-indent: 20px; } #itfdtqefsv .gt_indent_5 { text-indent: 25px; } #itfdtqefsv .katex-display { display: inline-flex !important; margin-bottom: 0.75em !important; } #itfdtqefsv div.Reactable > div.rt-table > div.rt-thead > div.rt-tr.rt-tr-group-header > div.rt-th-group:after { height: 0px !important; } </style>
Measurements under 500 ppm
Name CO2 ppm Building type Location
El galeon 447.0 Mall Adeje, Spain
Bodegas Tajinaste 479.0 Wine La Orotava, Spain
Habit Coffee 487.0 Cafe Victoria, Canada
Supercor Exprés 483.0 Convenience Puerto de la Cruz,
MC Döner 497.0 Fast food Leipzig, Germany
Wen's Nudeln 455.0 Fast food Wien, Austria
Noodle King 475.0 Fast food Wien, Austria
Noodle King 492.0 Fast food Wien, Austria
Praxis 486.0 Doityourself Utrecht, Netherlands
Puurr aan de Vliet 474.0 Restaurant Leidschendam, Netherlands
Praxis 493.0 Doityourself Utrecht, Netherlands
Edo Nudeln 497.0 Fast food Wien, Austria
XXXLutz 496.0 Furniture Wien, Austria
Cineplex Germering 478.0 Cinema Germering, Germany
Cineplex Germering 485.0 Cinema Germering, Germany
Cineplex Germering 454.0 Cinema Germering, Germany
Cineplex Germering 425.5 Cinema Germering, Germany
Pastabilities 477.0 Restaurant City of Syracuse, United States
Binder Optik 497.0 Optician Leonberg, Germany
Ozan Market 497.0 Convenience Leidschendam, Netherlands
Kinepolis 465.0 Cinema Leidschendam, Netherlands
Zeughauskino 497.0 Cinema Berlin, Germany
David Rodriguez 461.0 Bar Puerto de la Cruz,
Einstein Kaffee 491.0 Cafe Berlin, Germany
Morley's Chicken 497.0 Fast food Greater London, United Kingdom
OBI 478.0 Doityourself Wien, Austria
McDonald's 498.0 Fast food Wien, Austria

Trends over time

The following are charts that are updated every month, but they reflect all data collected so far from the indoorco2 monitoring project (since April 2024). Over time, we should be able to see yearly trends where CO2 levels are higher in the Winter when shopkeepers close their windows to keep things warm and then lower CO2 levels when shopkeepers open their windows in the Summer.

cr-metweekall?

We can start to see trends like in this graph which shows CO2 against the week of the year. There are two relevant points you should know about the X axis, Week of the year (meteorological), before moving on.

  1. Datapoints are aggregated into weeks regardless of the year they are collected in, so some weeks were measured in 2024, 2025 and 2026 but they would all show up in the same week number.

  2. This accounts for the hemisphere in which the recording was collected. Since Winter in the Southern Hemisphere is June through August, while Winter in the Northern Hemisphere is December through February, we have adjusted the week numbers so that they line up meterologically. Essentially, a measurement collected in the Northern Hemisphere on the first of January would show up as week 1, however, a measurement collected in the Southern Hemisphere on the first of January would show up as week 27.

cr-metweektype?

If we split the graph by the most popular building types, we can start to see some interesting trends. Supermarkets remain relatively high throughout the year with little variation while fast food, and chemists have quite a strong dip in CO2 levels during the Summer. This may be because most supermarkets keep their doors closed throughout the year and they tend to have larger buildings; conversely, chemists and fast food restaurants tend to be small to medium sized buildings which means that they can be very easily ventilated if they leave their front door open in the Summer. Restaurants have a very interesting trend here, the strong upward trend of the model at the end of the year is probably due to not enough measurements of restaurants yet rather that there being any meaningful conclusions. Over time we should hopefully see more stable trends show up.

Here’s a histogram showing how many measurements have been recorded each week since the start of the project. Over the last 12 months there have been 11020 building measurements which is 918 per month or 212 per week.cr-allhist?

Transit

This month there were 302 measurements of 129 unique transit lines. The most measured transit line was subway U6 in the U-Bahn Wien transit network in Wien, Austria (min: 457, mean: 660, max: 1298), which was measured 18 times.

When we look at the distribution of CO2 measurements by the transit type this month we can see some patterns. Trains often have higher CO2 values than buses, subways and trams because they usually travel for longer distances between stations. This causes trains to rely more heavily on mechanical ventilation than buses, subways, and trams which open their doors at stations more frequently.cr-transitmonthbox?

This trend can also be seen when we look at the distribution of each transit type on all the data from 2024, 2025 and 2026.cr-transitallbox?

That’s all for this month! Check back soon for more updates.

If this was useful to you, please consider supporting me so I can make more things like this. I would be incredibly grateful.

<script src="https://liberapay.com/samherniman/widgets/button.js"></script>

Donate using Liberapay

Some news

Recently Aurel Wünsch and I gave a talk about this project at Fluconf 2026. Check out the recording here, and the companion website here.

<iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/60YwSH9g3Bg?si=WsgFugFTD8ozkfF6" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen> </iframe>

I was also interviewed for a podcast. You can listen to the recording here.

<iframe width="100%" height="166" scrolling="no" frameborder="no" allow="autoplay" src="https://w.soundcloud.com/player/?url=https%3A//api.soundcloud.com/tracks/soundcloud%253Atracks%253A2258686259&color=%23ff5500&auto_play=false&hide_related=false&show_comments=true&show_user=true&show_reposts=false&show_teaser=true"> </iframe>

Some thanks

This work would not be possible without the hard work of all the contributors to OpenStreetMap and indoorco2map. If you would like to contribute to either of these projects, please visit their websites. You can contribute to the indoorco2map by downloading the Android app or iOS app and connecting it to any one of the following CO2 sensors: Aranet4, Airvalent, AirSpot and Inkbird IAM-T1. You can also donate by contributing to the indoorCO2map gofundme.
I would also like to thank Aurel Wünsch who tirelessly works on the project as well as the other contributors to the project ahunt, da5nsy, paul-hammant, and samherniman.

Finally, many thanks go to the teams who work on the following software, which I used heavily.

We used R v. 4.4.313 and the following R packages: autocruller v. 0.0.0.900014, dbscan v. 1.2.415,16, duckplyr v. 1.1.3.900717, gganimate v. 1.0.1118, ggrepel v. 0.9.619, glue v. 1.8.020, gt v. 1.2.021, h3 v. 3.7.222, here v. 1.0.223, magick v. 2.9.024, mapview v. 2.11.425, osmdata v. 0.3.026, pak v. 0.9.227, patchwork v. 1.3.228, rmarkdown v. 2.3029–31, rnaturalearth v. 1.2.032, rnaturalearthhires v. 1.0.0.900033, scales v. 1.4.034, scico v. 1.5.035, sf v. 1.0.2436,37, tidygeocoder v. 1.0.638, tidyplots v. 0.4.039, tidyverse v. 2.0.040.

All figures in this report are licensed under CC BY-SA 4.0. Please feel free to use and remix them and let me know if you do. I love to see my work being used elsewhere!

2. Fu, P. & Yung, K. K. L. Air pollution and alzheimer’s disease: A systematic review and meta-analysis. Journal of Alzheimer’s Disease 77, 701–714 (2020).

4. Olloquequi, J. et al. From inhalation to neurodegeneration: Air pollution as a modifiable risk factor for alzheimer’s disease. International Journal of Molecular Sciences 25, 6928 (2024).

5. Akar-Ghibril, N. & Phipatanakul, W. The indoor environment and childhood asthma. Curr Allergy Asthma Rep 20, 43 (2020).

7. Pan, R. et al. Interactions between climate factors and air quality index for improved childhood asthma self-management. Science of The Total Environment 723, 137804 (2020).

8. Breysse, P. N. et al. Indoor air pollution and asthma in children. Proc Am Thorac Soc 7, 102–106 (2010).

9. Hulin, M., Caillaud, D. & Annesi-Maesano, I. Indoor air pollution and childhood asthma: Variations between urban and rural areas. Indoor Air 20, 502–514 (2010).

10. Maung, T. Z., Bishop, J. E., Holt, E., Turner, A. M. & Pfrang, C. Indoor air pollution and the health of vulnerable groups: A systematic review focused on particulate matter (PM), volatile organic compounds (VOCs) and their effects on children and people with pre-existing lung disease. International Journal of Environmental Research and Public Health 19, 8752 (2022).

11. Kurmi, O. P., Lam, K. B. H. & Ayres, J. G. Indoor air pollution and the lung in low- and medium-income countries. European Respiratory Journal 40, 239–254 (2012).

12. Uzoigwe, J. C., Prum, T., Bresnahan, E. & Garelnabi, M. The emerging role of outdoor and indoor air pollution in cardiovascular disease. N Am J Med Sci 5, 445–453 (2013).

13. R Core Team. R: A Language and Environment for Statistical Computing. (R Foundation for Statistical Computing, Vienna, Austria, 2025).

15. Hahsler, M., Piekenbrock, M. & Doran, D. dbscan: Fast density-based clustering with R. Journal of Statistical Software 91, 1–30 (2019).

17. Mühleisen, H. & Müller, K. duckplyr: A ‘DuckDB’-Backed Version of ‘dplyr.

18. Pedersen, T. L. & Robinson, D. gganimate: A Grammar of Animated Graphics. (2025).

20. Hester, J. & Bryan, J. glue: Interpreted String Literals. (2024).

21. Iannone, R. et al. gt: Easily Create Presentation-Ready Display Tables. (2025).

22. Kuethe, S. H3: R Bindings for H3. (2022).

23. Müller, K. here: A Simpler Way to Find Your Files. (2025).

25. Appelhans, T., Detsch, F., Reudenbach, C. & Woellauer, S. mapview: Interactive Viewing of Spatial Data in r. (2025).

26. Mark Padgham, Bob Rudis, Robin Lovelace & Maëlle Salmon. Osmdata. Journal of Open Source Software 2, 305 (2017).

27. Csárdi, G. & Hester, J. pak: Another Approach to Package Installation. (2025).

28. Pedersen, T. L. patchwork: The Composer of Plots. (2025).

29. Xie, Y., Allaire, J. J. & Grolemund, G. R Markdown: The Definitive Guide. (Chapman; Hall/CRC, Boca Raton, Florida, 2018).

30. Xie, Y., Dervieux, C. & Riederer, E. R Markdown Cookbook. (Chapman; Hall/CRC, Boca Raton, Florida, 2020).

31. Allaire, J. et al. rmarkdown: Dynamic Documents for r. (2025).

32. Massicotte, P. & South, A. rnaturalearth: World Map Data from Natural Earth. (2026).

34. Wickham, H., Pedersen, T. L. & Seidel, D. scales: Scale Functions for Visualization. (2025).

35. Pedersen, T. L. & Crameri, F. scico: Colour Palettes Based on the Scientific Colour-Maps. (2023).

36. Pebesma, E. Simple Features for R: Standardized Support for Spatial Vector Data. The R Journal 10, 439–446 (2018).

37. Pebesma, E. & Bivand, R. Spatial Data Science: With applications in R. (Chapman and Hall/CRC, 2023). doi:10.1201/9780429459016.

38. Cambon, J., Hernangómez, D., Belanger, C. & Possenriede, D. tidygeocoder: An r package for geocoding. Journal of Open Source Software 6, 3544 (2021).

39. Engler, J. B. Tidyplots empowers life scientists with easy code-based data visualization. iMeta e70018 (2025) doi:10.1002/imt2.70018.

40. Wickham, H. et al. Welcome to the tidyverse. Journal of Open Source Software 4, 1686 (2019).