import geopandas as gpd
import requests
import pandas as pd
Data Sources¶
Land area data: U.S. Census Bureau's TIGER/Line Shapefiles
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.
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 |
# Import TIGER data
county = gpd.read_file('tl_2023_us_county/tl_2023_us_county.shp')
county.head()
| 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,... |
# 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
# 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
# Get full county FIPS
nynj_area['fips'] = nynj_area['STATEFP'] + nynj_area['COUNTYFP']
nynj_area.head(2)
| STATEFP | COUNTYFP | NAME | ALAND | fips | |
|---|---|---|---|---|---|
| 46 | 36 | 101 | Steuben | 3601398422 | 36101 |
| 110 | 34 | 037 | Sussex | 1342876715 | 34037 |
# 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()
| 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 |
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
# Change the datatype of total_pop
nynj_pop = nynj_pop.astype({'totalpopulation': 'int64'})
nynj_pop['totalpopulation'].dtype
dtype('int64')
# 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
# Rearrange columns
nynj = nynj[['fips', 'countyname', 'stateabbr', 'totalpopulation', 'ALAND']]
nynj.rename(columns={'countyname':'county', 'stateabbr':'state'}, inplace=True)
nynj.head(2)
| fips | county | state | totalpopulation | ALAND | |
|---|---|---|---|---|---|
| 0 | 34035 | Somerset | NJ | 348842 | 781832066 |
| 1 | 34025 | Monmouth | NJ | 642799 | 1212642647 |
# 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
# 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
# Arrange columns
nynj = nynj[['fips', 'county', 'state', 'pop_density']]
nynj.head(3)
| 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 |
# 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)
| 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 |
# 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¶
# Get TIGER data
ct_town = gpd.read_file('tl_2023_09_cousub/tl_2023_09_cousub.shp')
ct_town.head(2)
| 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)
# Get full 2022 town FIPS
ct_town['town_fips_2022'] = ct_town['STATEFP'] + ct_town['COUNTYFP'] + ct_town['COUSUBFP']
ct_town.head(2)
| 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 |
# 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
# 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.
# 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
# Cheeck the towns that don't have matching county
town_county_area[town_county_area['county_name'].isna()]
| 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.
# 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
# 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()
| 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 |
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¶
# 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()
| 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.
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
# 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.
# 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
# Check the census tracts that don't have population data
tract_county_pop[tract_county_pop['totalpopulation'].isna()]
| 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.
# 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
# Convert totalpopulation to integer
tract_county_pop['totalpopulation'] = tract_county_pop['totalpopulation'].astype('int64')
tract_county_pop['totalpopulation'].dtype
dtype('int64')
# 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()
| 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 |
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¶
ct = pd.merge(ct_county_pop, ct_county_area, how='left', on='county_fips_2020')
ct.head(3)
| 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 |
# 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)
| 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 |
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
# 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)
| 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¶
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
appended_df.head()
| 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 |
appended_df.tail()
| 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 |
appended_df.to_csv('../pop_density.csv', index=False)