In [1]:
import geopandas as gpd
import requests
import pandas as pd

Data Sources¶

Land area data: U.S. Census Bureau's TIGER/Line Shapefiles

  • NY & NJ county data
  • CT town/county subdivision data

Population data: CDC PLACES data was used as it contains county-level and tract-level population figures with no minimum population restriction. ACS 1-year estimates, on the other hand, cut off entirely for any county with fewer than 65,000 residents.

  • NY & NJ county data
  • CT census tract data

Crosswalk file for mapping towns and census tracts in CT to counties: 2022tractcrosswalk.csv

Note: TIGER tract-level data doesn't provide tract FIPS, CDC PLACES doesn't have town-level data. Therefore, I had to use 2 different geo levels for CT data and use 2022tractcrosswalk.csv to map both tracts and towns to counties.

Gather Data for NY, NJ¶

Variables from TIGER/Line Shapefile

Variable Code Description
STATEFP State FIPS code (2 digits)
COUNTYFP County FIPS code (3 digits)
NAME County name
ALAND Land area in square meters

Variables from CDC PLACES

Variable Code Description
stateabbr State abbreviation
countyname County name
countyfips Full county FIPS code including state FIPS (5 digits)
totalpopulation Total population
In [2]:
# Import TIGER data
county = gpd.read_file('tl_2023_us_county/tl_2023_us_county.shp')
county.head()
Out[2]:
STATEFP COUNTYFP COUNTYNS GEOID GEOIDFQ NAME NAMELSAD LSAD CLASSFP MTFCC CSAFP CBSAFP METDIVFP FUNCSTAT ALAND AWATER INTPTLAT INTPTLON geometry
0 31 039 00835841 31039 0500000US31039 Cuming Cuming County 06 H1 G4020 NaN NaN NaN A 1477563029 10772508 +41.9158651 -096.7885168 POLYGON ((-96.55516 41.91587, -96.55515 41.914...
1 53 069 01513275 53069 0500000US53069 Wahkiakum Wahkiakum County 06 H1 G4020 NaN NaN NaN A 680980771 61564427 +46.2946377 -123.4244583 POLYGON ((-123.72755 46.2645, -123.72756 46.26...
2 35 011 00933054 35011 0500000US35011 De Baca De Baca County 06 H1 G4020 NaN NaN NaN A 6016818946 29090018 +34.3592729 -104.3686961 POLYGON ((-104.89337 34.08894, -104.89337 34.0...
3 31 109 00835876 31109 0500000US31109 Lancaster Lancaster County 06 H1 G4020 339 30700 NaN A 2169269688 22850324 +40.7835474 -096.6886584 POLYGON ((-96.68493 40.5233, -96.69219 40.5231...
4 31 129 00835886 31129 0500000US31129 Nuckolls Nuckolls County 06 H1 G4020 NaN NaN NaN A 1489645187 1718484 +40.1764918 -098.0468422 POLYGON ((-98.2737 40.1184, -98.27374 40.1224,...
In [3]:
# Keep only important columns
county_area = county[['STATEFP', 'COUNTYFP', 'NAME', 'ALAND']]
county_area.info()
<class 'pandas.DataFrame'>
RangeIndex: 3235 entries, 0 to 3234
Data columns (total 4 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   STATEFP   3235 non-null   str  
 1   COUNTYFP  3235 non-null   str  
 2   NAME      3235 non-null   str  
 3   ALAND     3235 non-null   int64
dtypes: int64(1), str(3)
memory usage: 101.2 KB
In [4]:
# Filter for NY & NJ
nynj_area = county_area[county_area['STATEFP'].isin(['34', '36'])]
nynj_area.info()
<class 'pandas.DataFrame'>
Index: 83 entries, 46 to 3205
Data columns (total 4 columns):
 #   Column    Non-Null Count  Dtype
---  ------    --------------  -----
 0   STATEFP   83 non-null     str  
 1   COUNTYFP  83 non-null     str  
 2   NAME      83 non-null     str  
 3   ALAND     83 non-null     int64
dtypes: int64(1), str(3)
memory usage: 3.2 KB
In [5]:
# Get full county FIPS
nynj_area['fips'] = nynj_area['STATEFP'] + nynj_area['COUNTYFP']
nynj_area.head(2)
Out[5]:
STATEFP COUNTYFP NAME ALAND fips
46 36 101 Steuben 3601398422 36101
110 34 037 Sussex 1342876715 34037
In [6]:
# Get population data for NY, NJ from CDC PLACES
response = requests.get('https://data.cdc.gov/resource/i46a-9kgh.json?$query=SELECT%0A%20%20%60stateabbr%60%20AS%20%60stateabbr%60%2C%0A%20%20%60countyname%60%20AS%20%60countyname%60%2C%0A%20%20%60countyfips%60%20AS%20%60countyfips%60%2C%0A%20%20%60totalpopulation%60%20AS%20%60totalpopulation%60%0AWHERE%20caseless_one_of(%60stateabbr%60%2C%20%22NY%22%2C%20%22NJ%22)')
content = response.json()

nynj_pop = pd.DataFrame(content)
nynj_pop.head()
Out[6]:
stateabbr countyname countyfips totalpopulation
0 NJ Somerset 34035 348842
1 NJ Monmouth 34025 642799
2 NJ Essex 34013 851117
3 NJ Salem 34033 65338
4 NJ Burlington 34005 469167
In [7]:
nynj_pop.info()
<class 'pandas.DataFrame'>
RangeIndex: 83 entries, 0 to 82
Data columns (total 4 columns):
 #   Column           Non-Null Count  Dtype
---  ------           --------------  -----
 0   stateabbr        83 non-null     str  
 1   countyname       83 non-null     str  
 2   countyfips       83 non-null     str  
 3   totalpopulation  83 non-null     str  
dtypes: str(4)
memory usage: 2.7 KB
In [8]:
# Change the datatype of total_pop
nynj_pop = nynj_pop.astype({'totalpopulation': 'int64'})
nynj_pop['totalpopulation'].dtype
Out[8]:
dtype('int64')
In [9]:
# Merge nynj_pop and nynj_area
nynj = pd.merge(nynj_pop, nynj_area, how='left', left_on='countyfips', right_on='fips')
nynj.info()
<class 'pandas.DataFrame'>
RangeIndex: 83 entries, 0 to 82
Data columns (total 9 columns):
 #   Column           Non-Null Count  Dtype
---  ------           --------------  -----
 0   stateabbr        83 non-null     str  
 1   countyname       83 non-null     str  
 2   countyfips       83 non-null     str  
 3   totalpopulation  83 non-null     int64
 4   STATEFP          83 non-null     str  
 5   COUNTYFP         83 non-null     str  
 6   NAME             83 non-null     str  
 7   ALAND            83 non-null     int64
 8   fips             83 non-null     str  
dtypes: int64(2), str(7)
memory usage: 6.0 KB
In [10]:
# Rearrange columns
nynj = nynj[['fips', 'countyname', 'stateabbr', 'totalpopulation', 'ALAND']]
nynj.rename(columns={'countyname':'county', 'stateabbr':'state'}, inplace=True)
nynj.head(2)
Out[10]:
fips county state totalpopulation ALAND
0 34035 Somerset NJ 348842 781832066
1 34025 Monmouth NJ 642799 1212642647
In [11]:
# Convert square meters to square miles
nynj['Area'] = nynj['ALAND'] / 2589988.11
# Calculate population density
nynj['pop_density'] = nynj['totalpopulation'] / nynj['Area']
nynj.info()
<class 'pandas.DataFrame'>
RangeIndex: 83 entries, 0 to 82
Data columns (total 7 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   fips             83 non-null     str    
 1   county           83 non-null     str    
 2   state            83 non-null     str    
 3   totalpopulation  83 non-null     int64  
 4   ALAND            83 non-null     int64  
 5   Area             83 non-null     float64
 6   pop_density      83 non-null     float64
dtypes: float64(2), int64(2), str(3)
memory usage: 4.7 KB
In [12]:
# Calculate population density
nynj['pop_density'] = (nynj['totalpopulation'] / nynj['Area']).round(2)
nynj.info()
<class 'pandas.DataFrame'>
RangeIndex: 83 entries, 0 to 82
Data columns (total 7 columns):
 #   Column           Non-Null Count  Dtype  
---  ------           --------------  -----  
 0   fips             83 non-null     str    
 1   county           83 non-null     str    
 2   state            83 non-null     str    
 3   totalpopulation  83 non-null     int64  
 4   ALAND            83 non-null     int64  
 5   Area             83 non-null     float64
 6   pop_density      83 non-null     float64
dtypes: float64(2), int64(2), str(3)
memory usage: 4.7 KB
In [13]:
# Arrange columns
nynj = nynj[['fips', 'county', 'state', 'pop_density']]
nynj.head(3)
Out[13]:
fips county state pop_density
0 34035 Somerset NJ 1155.61
1 34025 Monmouth NJ 1372.90
2 34013 Essex NJ 6750.48

Gather Data for Connecticut¶

Variables from TIGER/Line Shapefile

Variable Code Description
STATEFP State FIPS code (2 digits)
COUNTYFP Planning region FIPS code (3 digits)
COUSUBFP Town FIPS code (5 digits)
NAME Town name
ALAND land area in square meters

Variables from CDC PLACES

Variable Code Description
stateabbr State abbreviation
tractfips Full tract FIPS code including state FIPS and planning region FIPS (11 digits)
totalpopulation Total population
In [14]:
# Import the crosswalk file
crosswalk = pd.read_csv('../../reference/ct_crosswalk/2022tractcrosswalk.csv', dtype={'Tract_fips_2022':str, 'county_fips_2020':str, 'town_fips_2022':str})
crosswalk.head(2)
Out[14]:
tract_fips_2020 Tract_fips_2022 tract_name town_name town_fips_2020 town_fips_2022 county_name county_fips_2020 ce_name_2022 ce_fips_2022 school_district_code school_district_name zip5_zcta2020 PUMA2020code PUMA2020name
0 9013528100 09110528100 5281.0 Andover 901301080 0911001080 Tolland 09013 Capitol Planning Region 9110 208 Region 8 6232 20203 Capitol East
1 9009125200 09140125200 1252.0 Ansonia 900901220 0914001220 New Haven 09009 Naugatuck Valley Planning Region 9140 2 Ansonia 6401 20703 Naugatuck Valley South
In [15]:
# Keep only important columns in the crosswalk
crosswalk = crosswalk[['Tract_fips_2022', 'county_fips_2020', 'county_name', 'town_fips_2022']]
crosswalk.info()
<class 'pandas.DataFrame'>
RangeIndex: 879 entries, 0 to 878
Data columns (total 4 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   Tract_fips_2022   879 non-null    str  
 1   county_fips_2020  879 non-null    str  
 2   county_name       879 non-null    str  
 3   town_fips_2022    879 non-null    str  
dtypes: str(4)
memory usage: 27.6 KB

Get CT Area Data at Town Level and Aggregate to County Level¶

In [16]:
# Get TIGER data
ct_town = gpd.read_file('tl_2023_09_cousub/tl_2023_09_cousub.shp')
ct_town.head(2)
Out[16]:
STATEFP COUNTYFP COUSUBFP COUSUBNS GEOID GEOIDFQ NAME NAMELSAD LSAD CLASSFP MTFCC FUNCSTAT ALAND AWATER INTPTLAT INTPTLON geometry
0 09 140 46940 00213459 0914046940 0600000US0914046940 Middlebury Middlebury town 43 T1 G4040 A 45988511 1785060 +41.5246912 -073.1230162 POLYGON ((-73.16468 41.55709, -73.16366 41.557...
1 09 170 47535 00213462 0917047535 0600000US0917047535 Milford Milford town 43 T5 G4040 C 57444044 10216676 +41.2250861 -073.0611101 POLYGON ((-73.12245 41.1829, -73.12195 41.1851...

The county FIPS in ct_town are the new FIPS for planning regions. When combined with the state and subcounty FIPS, we can get the full new 2022 FIPS for towns: XX (State) + YYY (Region) + ZZZZZ (Town Code)

In [17]:
# Get full 2022 town FIPS
ct_town['town_fips_2022'] =  ct_town['STATEFP'] + ct_town['COUNTYFP'] + ct_town['COUSUBFP']
ct_town.head(2)
Out[17]:
STATEFP COUNTYFP COUSUBFP COUSUBNS GEOID GEOIDFQ NAME NAMELSAD LSAD CLASSFP MTFCC FUNCSTAT ALAND AWATER INTPTLAT INTPTLON geometry town_fips_2022
0 09 140 46940 00213459 0914046940 0600000US0914046940 Middlebury Middlebury town 43 T1 G4040 A 45988511 1785060 +41.5246912 -073.1230162 POLYGON ((-73.16468 41.55709, -73.16366 41.557... 0914046940
1 09 170 47535 00213462 0917047535 0600000US0917047535 Milford Milford town 43 T5 G4040 C 57444044 10216676 +41.2250861 -073.0611101 POLYGON ((-73.12245 41.1829, -73.12195 41.1851... 0917047535
In [18]:
# Get only the important columns
ct_town_area = ct_town[['town_fips_2022', 'ALAND']]
ct_town_area.info()
<class 'pandas.DataFrame'>
RangeIndex: 174 entries, 0 to 173
Data columns (total 2 columns):
 #   Column          Non-Null Count  Dtype
---  ------          --------------  -----
 0   town_fips_2022  174 non-null    str  
 1   ALAND           174 non-null    int64
dtypes: int64(1), str(1)
memory usage: 2.8 KB
In [19]:
# Get town-county crosswalk
town_cnty_crosswalk = crosswalk[['county_fips_2020', 'county_name', 'town_fips_2022']].drop_duplicates(subset='town_fips_2022', keep='first')
town_cnty_crosswalk.info()
<class 'pandas.DataFrame'>
Index: 169 entries, 0 to 877
Data columns (total 3 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   county_fips_2020  169 non-null    str  
 1   county_name       169 non-null    str  
 2   town_fips_2022    169 non-null    str  
dtypes: str(3)
memory usage: 5.3 KB

Since ct_town_area has more unique rows than town_cnty_crosswalk, I'll merge left on ct_town_area and check which town doesn't have a matching county from the crosswalk.

In [20]:
# Merge ct_town_area & town_cnty_crosswalk
town_county_area = pd.merge(ct_town_area, town_cnty_crosswalk, on='town_fips_2022', how='left')
town_county_area.info()
<class 'pandas.DataFrame'>
RangeIndex: 174 entries, 0 to 173
Data columns (total 4 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   town_fips_2022    174 non-null    str  
 1   ALAND             174 non-null    int64
 2   county_fips_2020  169 non-null    str  
 3   county_name       169 non-null    str  
dtypes: int64(1), str(3)
memory usage: 5.6 KB
In [21]:
# Cheeck the towns that don't have matching county
town_county_area[town_county_area['county_name'].isna()]
Out[21]:
town_fips_2022 ALAND county_fips_2020 county_name
6 0917000000 0 NaN NaN
50 0918000000 0 NaN NaN
77 0913000000 0 NaN NaN
149 0919000000 0 NaN NaN
173 0912000000 0 NaN NaN

These are exactly 5 of the water-based subdivisions in Connecticut's geographic tracking files. The Census Bureau wraps administrative boundaries around coastal states to account for territorial coastal waters and large sounds. We just need to remove these towns.

In [22]:
# Remove water-based subdivisions
town_county_area = town_county_area[~town_county_area['county_name'].isna()]
town_county_area.info()
<class 'pandas.DataFrame'>
Index: 169 entries, 0 to 172
Data columns (total 4 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   town_fips_2022    169 non-null    str  
 1   ALAND             169 non-null    int64
 2   county_fips_2020  169 non-null    str  
 3   county_name       169 non-null    str  
dtypes: int64(1), str(3)
memory usage: 6.6 KB
In [23]:
# Aggregate land area
ct_county_area = town_county_area.drop(columns='town_fips_2022').groupby(['county_fips_2020','county_name']).sum().reset_index()
ct_county_area.head()
Out[23]:
county_fips_2020 county_name ALAND
0 09001 Fairfield 1618680913
1 09003 Hartford 1903490898
2 09005 Litchfield 2384146612
3 09007 Middlesex 956493758
4 09009 New Haven 1565147930
In [24]:
ct_county_area.info()
<class 'pandas.DataFrame'>
RangeIndex: 8 entries, 0 to 7
Data columns (total 3 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   county_fips_2020  8 non-null      str  
 1   county_name       8 non-null      str  
 2   ALAND             8 non-null      int64
dtypes: int64(1), str(2)
memory usage: 324.0 bytes

Get CT Population Data at Tract Level and Aggregate to County Level¶

In [25]:
# Get population data for CT from CDC PLACES
response = requests.get('https://data.cdc.gov/resource/yjkw-uj5s.json?$query=SELECT%0A%20%20%60stateabbr%60%20AS%20%60stateabbr%60%2C%0A%20%20%60tractfips%60%20AS%20%60tractfips%60%2C%0A%20%20%60totalpopulation%60%20AS%20%60totalpopulation%60%0AWHERE%20caseless_one_of(%60stateabbr%60%2C%20%22CT%22)')
content = response.json()

ct_tract_pop = pd.DataFrame(content)
ct_tract_pop.head()
Out[25]:
stateabbr tractfips totalpopulation
0 CT 09110400101 2928
1 CT 09110400102 4299
2 CT 09110400200 5997
3 CT 09110400300 6951
4 CT 09110415300 2521

tractfips in ct_tract_pop are new FIPS codes for census tracts after 2022. Old tract FIPS and new tract FIPS have the same last 5 digits. We can merge ct_area with ct_pop based on these last 5 digits of tract FIPS.

In [26]:
ct_tract_pop.info()
<class 'pandas.DataFrame'>
RangeIndex: 876 entries, 0 to 875
Data columns (total 3 columns):
 #   Column           Non-Null Count  Dtype
---  ------           --------------  -----
 0   stateabbr        876 non-null    str  
 1   tractfips        876 non-null    str  
 2   totalpopulation  876 non-null    str  
dtypes: str(3)
memory usage: 20.7 KB
In [27]:
# Get tract-county crosswalk
tract_cnty_crosswalk = crosswalk[['Tract_fips_2022', 'county_fips_2020', 'county_name', ]]
tract_cnty_crosswalk.info()
<class 'pandas.DataFrame'>
RangeIndex: 879 entries, 0 to 878
Data columns (total 3 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   Tract_fips_2022   879 non-null    str  
 1   county_fips_2020  879 non-null    str  
 2   county_name       879 non-null    str  
dtypes: str(3)
memory usage: 20.7 KB

Since tract_cnty_crosswalk has more unique rows than ct_town_pop, I'll merge left on tract_cnty_crosswalk and check which tract in the crosswalk doesn't have population data from CDC PLACES.

In [28]:
# Merge tract_cnty_crosswalk with ct_tract_pop
tract_county_pop = pd.merge(tract_cnty_crosswalk, ct_tract_pop, left_on='Tract_fips_2022', right_on='tractfips', how='left')
tract_county_pop.info()
<class 'pandas.DataFrame'>
RangeIndex: 879 entries, 0 to 878
Data columns (total 6 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   Tract_fips_2022   879 non-null    str  
 1   county_fips_2020  879 non-null    str  
 2   county_name       879 non-null    str  
 3   stateabbr         876 non-null    str  
 4   tractfips         876 non-null    str  
 5   totalpopulation   876 non-null    str  
dtypes: str(6)
memory usage: 41.3 KB
In [29]:
# Check the census tracts that don't have population data
tract_county_pop[tract_county_pop['totalpopulation'].isna()]
Out[29]:
Tract_fips_2022 county_fips_2020 county_name stateabbr tractfips totalpopulation
151 09110980001 09003 Hartford NaN NaN NaN
709 09110980003 09003 Hartford NaN NaN NaN
866 09110980002 09003 Hartford NaN NaN NaN

The Census Bureau creates these unique 9800-series boundaries specifically to isolate areas with zero permanent residential populations. Since these tracks have a residential adult population of exactly zero, they would carry a population weight of 0. We can just remove them from aggregation as they contribute nothing to the county-level totals.

In [30]:
# Remove tracts with no data
tract_county_pop = tract_county_pop[~tract_county_pop['totalpopulation'].isna()]
tract_county_pop.info()
<class 'pandas.DataFrame'>
Index: 876 entries, 0 to 878
Data columns (total 6 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   Tract_fips_2022   876 non-null    str  
 1   county_fips_2020  876 non-null    str  
 2   county_name       876 non-null    str  
 3   stateabbr         876 non-null    str  
 4   tractfips         876 non-null    str  
 5   totalpopulation   876 non-null    str  
dtypes: str(6)
memory usage: 47.9 KB
In [31]:
# Convert totalpopulation to integer
tract_county_pop['totalpopulation'] = tract_county_pop['totalpopulation'].astype('int64')
tract_county_pop['totalpopulation'].dtype
Out[31]:
dtype('int64')
In [32]:
# Aggregate population
ct_county_pop = tract_county_pop.drop(columns=['Tract_fips_2022','stateabbr','tractfips']).groupby(['county_fips_2020','county_name']).sum().reset_index()
ct_county_pop.head()
Out[32]:
county_fips_2020 county_name totalpopulation
0 09001 Fairfield 957419
1 09003 Hartford 899498
2 09005 Litchfield 185186
3 09007 Middlesex 164245
4 09009 New Haven 864835
In [33]:
ct_county_pop.info()
<class 'pandas.DataFrame'>
RangeIndex: 8 entries, 0 to 7
Data columns (total 3 columns):
 #   Column            Non-Null Count  Dtype
---  ------            --------------  -----
 0   county_fips_2020  8 non-null      str  
 1   county_name       8 non-null      str  
 2   totalpopulation   8 non-null      int64
dtypes: int64(1), str(2)
memory usage: 324.0 bytes

Calculate CT Population Density¶

In [34]:
ct = pd.merge(ct_county_pop, ct_county_area, how='left', on='county_fips_2020')
ct.head(3)
Out[34]:
county_fips_2020 county_name_x totalpopulation county_name_y ALAND
0 09001 Fairfield 957419 Fairfield 1618680913
1 09003 Hartford 899498 Hartford 1903490898
2 09005 Litchfield 185186 Litchfield 2384146612
In [35]:
# Convert square meters to square miles
ct['Area'] = ct['ALAND'] / 2589988.11
# Calculate population density
ct['pop_density'] = (ct['totalpopulation'] / ct['Area']).round(2)
ct.head(3)
Out[35]:
county_fips_2020 county_name_x totalpopulation county_name_y ALAND Area pop_density
0 09001 Fairfield 957419 Fairfield 1618680913 624.976195 1531.93
1 09003 Hartford 899498 Hartford 1903490898 734.941945 1223.90
2 09005 Litchfield 185186 Litchfield 2384146612 920.524153 201.17
In [36]:
ct.info()
<class 'pandas.DataFrame'>
RangeIndex: 8 entries, 0 to 7
Data columns (total 7 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   county_fips_2020  8 non-null      str    
 1   county_name_x     8 non-null      str    
 2   totalpopulation   8 non-null      int64  
 3   county_name_y     8 non-null      str    
 4   ALAND             8 non-null      int64  
 5   Area              8 non-null      float64
 6   pop_density       8 non-null      float64
dtypes: float64(2), int64(2), str(3)
memory usage: 580.0 bytes
In [37]:
# Rearrange columns
ct['fips'] = ct['county_fips_2020']
ct['county'] = ct['county_name_x']
ct['state'] = 'CT'
ct = ct[['fips', 'county', 'state', 'pop_density']]
ct.head(3)
Out[37]:
fips county state pop_density
0 09001 Fairfield CT 1531.93
1 09003 Hartford CT 1223.90
2 09005 Litchfield CT 201.17

Combine NY, NJ, CT¶

In [38]:
appended_df = pd.concat ([nynj, ct])
appended_df.info()
<class 'pandas.DataFrame'>
Index: 91 entries, 0 to 7
Data columns (total 4 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   fips         91 non-null     str    
 1   county       91 non-null     str    
 2   state        91 non-null     str    
 3   pop_density  91 non-null     float64
dtypes: float64(1), str(3)
memory usage: 3.6 KB
In [39]:
appended_df.head()
Out[39]:
fips county state pop_density
0 34035 Somerset NJ 1155.61
1 34025 Monmouth NJ 1372.90
2 34013 Essex NJ 6750.48
3 34033 Salem NJ 196.88
4 34005 Burlington NJ 586.98
In [40]:
appended_df.tail()
Out[40]:
fips county state pop_density
3 09007 Middlesex CT 444.74
4 09009 New Haven CT 1431.12
5 09011 New London CT 403.80
6 09013 Tolland CT 365.02
7 09015 Windham CT 226.97
In [41]:
appended_df.to_csv('../pop_density.csv', index=False)
In [ ]: