<p><strong>Interested in more tips on working with Census data? <a href="http://eepurl.com/cPGKZD">Click here to join my email list!</a></strong></p>
<p>Last week, I published the development version of my new R package, <strong>tidycensus</strong>. You can read through the documentation and some examples at <a href="https://walkerke.github.io/tidycensus/">https://walkerke.github.io/tidycensus/</a>. I’m working on getting the package CRAN-ready with better error handling; in the meantime, I’m sharing a few examples to demonstrate its functionality.</p>
<p>If you are working on a national project that includes demographic data as a component, you might be interested in acquiring Census tract data for the entire United States. However, Census tract data are commonly available by state (with the exception of <a href="http://www.nhgis.org/">NHGIS</a>, which is a wonderful resource), meaning that an analyst would have to spend time piecing the data together.</p>
<p><strong>tidycensus</strong> solves this problem directly within R with help from the <strong>purrr</strong> package, a member of the tidyverse. In <strong>tidycensus</strong>, there is a built-in data frame named <code>fips_codes</code> that includes US state and county IDs; <strong>tidycensus</strong> uses this data frame to handle translations between state/county names and FIPS codes. However, this data frame can also be used to generate a vector of state codes to be fed to the <code>map_df</code> function in <strong>purrr</strong>. As such, this is all it takes to get a tibble of total population estimates for all US Census tracts from the 2011-2015 ACS:</p>
<pre><code>library(tidycensus)
library(purrr)
# Un-comment below and set your API key
# census_api_key(&quot;YOUR KEY GOES HERE&quot;)
us &lt;- unique(fips_codes$state)[1:51]

totalpop &lt;- map_df(us, function(x) {
  get_acs(geography = &quot;tract&quot;, variables = &quot;B01003_001&quot;, 
          state = x)
})

str(totalpop)</code></pre>
<pre><code>## Classes 'tbl_df', 'tbl' and 'data.frame':    73056 obs. of  5 variables:
##  $ GEOID   : chr  &quot;01001020100&quot; &quot;01001020200&quot; &quot;01001020300&quot; &quot;01001020400&quot; ...
##  $ NAME    : chr  &quot;Census Tract 201, Autauga County, Alabama&quot; &quot;Census Tract 202, Autauga County, Alabama&quot; &quot;Census Tract 203, Autauga County, Alabama&quot; &quot;Census Tract 204, Autauga County, Alabama&quot; ...
##  $ variable: chr  &quot;B01003_001&quot; &quot;B01003_001&quot; &quot;B01003_001&quot; &quot;B01003_001&quot; ...
##  $ estimate: num  1948 2156 2968 4423 10763 ...
##  $ moe     : num  203 268 404 493 624 478 436 281 1000 535 ...</code></pre>
<p>Get any ACS or decennial Census data in this way.</p>
<p>However - what if you also want tract geometry for mapping? This only requires a few small modifications. <code>map_df</code> in <strong>purrr</strong> uses the <code>bind_rows</code> function under the hood, which doesn’t work with simple features objects (yet). However, <strong>sf</strong> does have an <code>rbind</code> method that works for <code>sf</code> objects and can be fed to <strong>purrr</strong>’s <code>reduce</code> function.</p>
<pre><code>library(sf)
options(tigris_use_cache = TRUE)

totalpop_sf &lt;- reduce(
  map(us, function(x) {
    get_acs(geography = &quot;tract&quot;, variables = &quot;B01003_001&quot;, 
            state = x, geometry = TRUE)
  }), 
  rbind
)

str(totalpop_sf)</code></pre>
<pre><code>## Classes 'sf' and 'data.frame':   72843 obs. of  6 variables:
##  $ GEOID   : chr  &quot;01003010500&quot; &quot;01003011501&quot; &quot;01009050500&quot; &quot;01015981901&quot; ...
##  $ NAME    : chr  &quot;Census Tract 105, Baldwin County, Alabama&quot; &quot;Census Tract 115.01, Baldwin County, Alabama&quot; &quot;Census Tract 505, Blount County, Alabama&quot; &quot;Census Tract 9819.01, Calhoun County, Alabama&quot; ...
##  $ variable: chr  &quot;B01003_001&quot; &quot;B01003_001&quot; &quot;B01003_001&quot; &quot;B01003_001&quot; ...
##  $ estimate: num  5321 5771 7007 4 1607 ...
##  $ moe     : num  452 825 556 6 235 309 506 386 425 310 ...
##  $ geometry:sfc_GEOMETRY of length 72843; first list element: List of 1
##   ..$ :List of 1
##   .. ..$ : num [1:55, 1:2] -87.8 -87.8 -87.8 -87.8 -87.8 ...
##   ..- attr(*, &quot;class&quot;)= chr  &quot;XY&quot; &quot;MULTIPOLYGON&quot; &quot;sfg&quot;
##  - attr(*, &quot;agr&quot;)= Factor w/ 3 levels &quot;constant&quot;,&quot;aggregate&quot;,..: NA NA NA NA NA
##   ..- attr(*, &quot;names&quot;)= chr  &quot;GEOID&quot; &quot;NAME&quot; &quot;variable&quot; &quot;estimate&quot; ...
##  - attr(*, &quot;sf_column&quot;)= chr &quot;geometry&quot;</code></pre>
<p>By declaring <code>geometry = TRUE</code>, <strong>tidycensus</strong> fetches tract feature geometry using the <strong>tigris</strong> package and merges it to the ACS data automatically for you. I recommend using the caching feature in the <strong>tigris</strong> package if you plan to use this workflow multiple times. You might note the discrepancy in tracts between the geometry-enabled and regular data frames; this is due to the removal of some water-only tracts in the cartographic boundary shapefiles used by <strong>tidycensus</strong>.</p>