morpc 0.3.2__tar.gz → 0.3.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {morpc-0.3.2 → morpc-0.3.4}/PKG-INFO +1 -1
- morpc-0.3.4/docs/05-morpc-geos-demo.ipynb +411 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/07-morpc-census-demo.ipynb +0 -8
- {morpc-0.3.2 → morpc-0.3.4}/morpc/__init__.py +1 -1
- {morpc-0.3.2 → morpc-0.3.4}/morpc/census/census.py +43 -4
- {morpc-0.3.2 → morpc-0.3.4}/morpc/frictionless/frictionless.py +19 -5
- {morpc-0.3.2 → morpc-0.3.4}/morpc/morpc.py +316 -106
- {morpc-0.3.2 → morpc-0.3.4}/morpc.egg-info/PKG-INFO +1 -1
- morpc-0.3.2/docs/05-morpc-geos-demo.ipynb +0 -898
- {morpc-0.3.2 → morpc-0.3.4}/.gitattributes +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/.github/workflows/deploy.yml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/.github/workflows/python-publish.yml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/.gitignore +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/README.md +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/.gitignore +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/.ipynb_checkpoints/index-checkpoint.md +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/.ipynb_checkpoints/morpc-color-demo-checkpoint.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/.ipynb_checkpoints/myst-checkpoint.yml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/01-morpc-py-demos.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/02-morpc-countylookup-demo.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/03-morpc-varlookup-demo.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/04-morpc-restapi-demo.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/06-morpc-frictionless-demo.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/08-morpc-plot-demo.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/09-morpc-color-demo.ipynb +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/assets/HORIZONTAL_LOGOS_PRIMARY_COLOR_V2.png +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/index.md +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/myst.yml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/MORPC MPO Boundary.gpkg +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/Screenshot 2025-06-03 080403.png +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/dataChartToExcelOutput.xlsx +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/plot_df.csv +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/plot_df.resource.yaml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/plot_df.schema.yaml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/rest_resource.json +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/temp_df.csv +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/temp_df.resource.yaml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/docs/temp_data/temp_df.schema.yaml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/census/__init__.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/color/.ipynb_checkpoints/color-checkpoint.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/color/.ipynb_checkpoints/morpc_colors-checkpoint.json +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/color/__init__.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/color/color.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/color/morpc_colors.json +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/color/palette.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/frictionless/__init__.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/plot/__init__.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/plot/plot.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/rest_api/__init__.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc/rest_api/rest_api.py +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc.egg-info/SOURCES.txt +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc.egg-info/dependency_links.txt +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc.egg-info/requires.txt +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/morpc.egg-info/top_level.txt +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/pyproject.toml +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/release_new_package.md +0 -0
- {morpc-0.3.2 → morpc-0.3.4}/setup.cfg +0 -0
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
{
|
|
2
|
+
"cells": [
|
|
3
|
+
{
|
|
4
|
+
"cell_type": "markdown",
|
|
5
|
+
"id": "2e7c0e95-72d3-4b78-85c1-7679c8b50d75",
|
|
6
|
+
"metadata": {},
|
|
7
|
+
"source": [
|
|
8
|
+
"# Spatial Data Tools"
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"cell_type": "markdown",
|
|
13
|
+
"id": "8f0a9134-295d-4a94-b91c-1f152350634d",
|
|
14
|
+
"metadata": {
|
|
15
|
+
"editable": true,
|
|
16
|
+
"slideshow": {
|
|
17
|
+
"slide_type": ""
|
|
18
|
+
},
|
|
19
|
+
"tags": []
|
|
20
|
+
},
|
|
21
|
+
"source": [
|
|
22
|
+
"## Load spatial data"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"cell_type": "markdown",
|
|
27
|
+
"id": "63ec4df4-eec2-475d-936a-e93db94f52a3",
|
|
28
|
+
"metadata": {},
|
|
29
|
+
"source": [
|
|
30
|
+
"Often we want to make a copy of some input data and work with the copy, for example to protect the original data or to create an archival copy of it so that we can replicate the process later. With tabular data this is simple, but with spatial data it can be tricky. Shapefiles actually consist of up to six files, so it is necessary to copy them all. Geodatabases may contain many layers in addition to the one we care about. The `load_spatial_data()` function simplifies the process of reading the data and (optionally) making an archival copy. It has three parameters:\n",
|
|
31
|
+
" - `sourcePath` - The path to the geospatial data. It may be a file path or URL. In the case of a Shapefile, this should point to the .shp file or a zipped file that contains all of the Shapefile components. You can point to other zipped contents as well, but see caveats below.\n",
|
|
32
|
+
" - `layerName` (required for GPKG and GDB, optional for SHP) - The name of the layer that you wish to extract from a GeoPackage or File Geodatabase. Not required for Shapefiles, but may be specified for use in the archival copy (see below)\n",
|
|
33
|
+
" - `driverName` (required for zipped data or data with non-standard file extension) - which [GDAL driver](https://gdal.org/drivers/vector/index.html) to use to read the file. Script will attempt to infer this from the file extension, but you must specify it if the data is zipped, if the file extension is non-standard, or if the extension cannot be determined from the path (e.g. if the path is an API query)\n",
|
|
34
|
+
" - `archiveDir` (optional) - The path to the directory where a copy of a data should be archived. If this is specified, the data will be archived in this location as a GeoPackage. The function will determine the file name and layer name from the specified parameters, using generic values if necessary.\n",
|
|
35
|
+
" - `archiveFileName` (optional) - If `archiveDir` is specified, you may use this to specify the name of the archival GeoPackage. Omit the extension. If this is unspecified, the function will assign the file name automatically using a generic value if necessary.\n",
|
|
36
|
+
" \n",
|
|
37
|
+
"The following example loads data from the MORPC Mid-Ohio Open Data website, however you can also load data from a local path or network drive."
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"cell_type": "code",
|
|
42
|
+
"execution_count": 1,
|
|
43
|
+
"id": "344971d0-83be-4410-b2d9-ee1ae90250cf",
|
|
44
|
+
"metadata": {
|
|
45
|
+
"tags": []
|
|
46
|
+
},
|
|
47
|
+
"outputs": [],
|
|
48
|
+
"source": [
|
|
49
|
+
"import geopandas as gpd\n",
|
|
50
|
+
"import morpc\n",
|
|
51
|
+
"import os"
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"cell_type": "code",
|
|
56
|
+
"execution_count": 7,
|
|
57
|
+
"id": "b8c36fa4-b7dd-43d4-9574-7a640d62a22b",
|
|
58
|
+
"metadata": {},
|
|
59
|
+
"outputs": [
|
|
60
|
+
{
|
|
61
|
+
"name": "stdout",
|
|
62
|
+
"output_type": "stream",
|
|
63
|
+
"text": [
|
|
64
|
+
"morpc.load_spatial_data | INFO | Loading spatial data from location: https://www2.census.gov/geo/tiger/TIGER2024/METDIV/tl_2024_us_metdiv.zip\n",
|
|
65
|
+
"morpc.load_spatial_data | INFO | Attempting to load data from Census FTP site. Using wget to archive file.\n",
|
|
66
|
+
"morpc.load_spatial_data | WARNING | Data from Census FTP must be temp saved. Using ./temp_data.\n",
|
|
67
|
+
"morpc.load_spatial_data | INFO | Using driver Census Shapefile as specified by user.\n",
|
|
68
|
+
"morpc.load_spatial_data | INFO | Reading spatial data...\n",
|
|
69
|
+
"morpc.load_spatial_data | INFO | File name is unspecified. Will infer file name from source path.\n",
|
|
70
|
+
"morpc.load_spatial_data | INFO | Using automatically-selected file name: tl_2024_us_metdiv\n",
|
|
71
|
+
"morpc.load_spatial_data | INFO | Layer name is unspecified. Using automatically-selected layer name: tl_2024_us_metdiv\n",
|
|
72
|
+
"morpc.load_spatial_data | INFO | Creating archival copy of geospatial layer at ./temp_data\\tl_2024_us_metdiv.gpkg, layer tl_2024_us_metdiv\n"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"name": "stderr",
|
|
77
|
+
"output_type": "stream",
|
|
78
|
+
"text": [
|
|
79
|
+
"C:\\Users\\jinskeep\\morpc_venv\\Lib\\site-packages\\pyogrio\\raw.py:198: RuntimeWarning: driver ESRI Shapefile does not support open option DRIVER\n"
|
|
80
|
+
]
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"source": [
|
|
84
|
+
"url = 'https://www2.census.gov/geo/tiger/TIGER2024/METDIV/tl_2024_us_metdiv.zip'\n",
|
|
85
|
+
"gdf = morpc.load_spatial_data(url, archiveDir='./temp_data')"
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"cell_type": "code",
|
|
90
|
+
"execution_count": null,
|
|
91
|
+
"id": "3573a45b-048b-4223-9c99-92219ab4db59",
|
|
92
|
+
"metadata": {
|
|
93
|
+
"tags": []
|
|
94
|
+
},
|
|
95
|
+
"outputs": [],
|
|
96
|
+
"source": [
|
|
97
|
+
"# Create a directory to store the archival data (for demonstration purposes only)\n",
|
|
98
|
+
"if not os.path.exists(\"./temp_data\"):\n",
|
|
99
|
+
" os.makedirs(\"./temp_data\")\n",
|
|
100
|
+
"\n",
|
|
101
|
+
"# Load the data and create an archival copy\n",
|
|
102
|
+
"gdf = morpc.load_spatial_data(\n",
|
|
103
|
+
" sourcePath=\"https://opendata.arcgis.com/api/v3/datasets/e42b50fbd17a47739c2a7695778c498e_17/downloads/data?format=shp&spatialRefId=3735&where=1%3D1\", \n",
|
|
104
|
+
" layerName=\"MORPC MPO Boundary\",\n",
|
|
105
|
+
" driverName=\"ESRI Shapefile\",\n",
|
|
106
|
+
" archiveDir=\"./temp_data\"\n",
|
|
107
|
+
")"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"cell_type": "markdown",
|
|
112
|
+
"id": "534d5386-9d8c-490e-941b-92c0b67ec65a",
|
|
113
|
+
"metadata": {},
|
|
114
|
+
"source": [
|
|
115
|
+
"Let's take a look at the data and make sure it loaded correctly."
|
|
116
|
+
]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"cell_type": "code",
|
|
120
|
+
"execution_count": null,
|
|
121
|
+
"id": "f8ee189b-6326-4038-b193-2e3184a09ac6",
|
|
122
|
+
"metadata": {
|
|
123
|
+
"tags": []
|
|
124
|
+
},
|
|
125
|
+
"outputs": [],
|
|
126
|
+
"source": [
|
|
127
|
+
"gdf.drop(columns=\"Updated\").explore() ## avoid datetime column JSON error"
|
|
128
|
+
]
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"cell_type": "markdown",
|
|
132
|
+
"id": "ba2a1e26-6d2f-469d-a71c-4a096bfeffc5",
|
|
133
|
+
"metadata": {},
|
|
134
|
+
"source": [
|
|
135
|
+
"Now let's read the archival copy and make sure it looks the same. We'll use the `load_spatial_data()` function again, but this time we won't make an archival copy."
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"cell_type": "code",
|
|
140
|
+
"execution_count": null,
|
|
141
|
+
"id": "d2cc1a3f-9f78-4044-86de-6f3a6502b6de",
|
|
142
|
+
"metadata": {
|
|
143
|
+
"tags": []
|
|
144
|
+
},
|
|
145
|
+
"outputs": [],
|
|
146
|
+
"source": [
|
|
147
|
+
"gdfArchive = morpc.load_spatial_data(\"./temp_data/MORPC MPO Boundary.gpkg\", layerName=\"MORPC MPO Boundary\")"
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"cell_type": "markdown",
|
|
152
|
+
"id": "4ea6c47d-30fc-4e72-96b6-338666130bdd",
|
|
153
|
+
"metadata": {
|
|
154
|
+
"editable": true,
|
|
155
|
+
"slideshow": {
|
|
156
|
+
"slide_type": ""
|
|
157
|
+
},
|
|
158
|
+
"tags": []
|
|
159
|
+
},
|
|
160
|
+
"source": [
|
|
161
|
+
"# Assign geographic identifiers"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"cell_type": "markdown",
|
|
166
|
+
"id": "29c482fe-76bc-4ae1-84cf-48e60dad52be",
|
|
167
|
+
"metadata": {},
|
|
168
|
+
"source": [
|
|
169
|
+
"Sometimes we have a set of locations and we would like to know what geography (county, zipcode, etc.) they fall in. The `assign_geo_identifiers()` function takes a set of georeference points and a list of geography levels and determines for each level which area each point falls in. The function takes two parameters:\n",
|
|
170
|
+
" - `points` - a GeoPandas GeoDataFrame consisting of the points of interest\n",
|
|
171
|
+
" - `geographies` - A Python list of one or more strings in which each element corresponds to a geography level. You can specify as many levels as you want from the following list, however note that the function must download the polygons and perform the analysis for each level so if you specify many levels it may take a long time.\n",
|
|
172
|
+
" - \"county\" - County (Census TIGER)\n",
|
|
173
|
+
" - \"tract\" - *Not currently implemented*\n",
|
|
174
|
+
" - \"blockgroup\" - *Not currently implemented*\n",
|
|
175
|
+
" - \"block\" - *Not currently implemented*\n",
|
|
176
|
+
" - \"zcta\" - *Not currently implemented*\n",
|
|
177
|
+
" - \"place\" - Census place (Census TIGER)\n",
|
|
178
|
+
" - \"placecombo\" - *Not currently implemented*\n",
|
|
179
|
+
" - \"juris\" - *Not currently implemented*\n",
|
|
180
|
+
" - \"region15County\" - *Not currently implemented*\n",
|
|
181
|
+
" - \"region10County\" - *Not currently implemented*\n",
|
|
182
|
+
" - \"regionCORPO\" - *Not currently implemented*\n",
|
|
183
|
+
" - \"regionMPO\" - *Not currently implemented*\n",
|
|
184
|
+
"\n",
|
|
185
|
+
"**NOTE:** Many of the geography levels are not currently implemented. They are being implemented as they are needed. If you need one that has not yet been implemented, please contact Adam Porr (or implement it yourself)."
|
|
186
|
+
]
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
"cell_type": "markdown",
|
|
190
|
+
"id": "cad973e5-cf05-4b46-8d18-a3fddd07e93f",
|
|
191
|
+
"metadata": {},
|
|
192
|
+
"source": [
|
|
193
|
+
"In the following example, we will assign labels for the \"county\" and \"place\" geography levels to libraries in MORPC's Points of Interest layer. First we'll download just the library locations from Mid-Ohio Open Data using the ArcGIS REST API."
|
|
194
|
+
]
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
"cell_type": "code",
|
|
198
|
+
"execution_count": null,
|
|
199
|
+
"id": "2ee41a03-52bc-4e14-843a-54f70d73982a",
|
|
200
|
+
"metadata": {
|
|
201
|
+
"editable": true,
|
|
202
|
+
"slideshow": {
|
|
203
|
+
"slide_type": ""
|
|
204
|
+
},
|
|
205
|
+
"tags": []
|
|
206
|
+
},
|
|
207
|
+
"outputs": [],
|
|
208
|
+
"source": [
|
|
209
|
+
"url = \"https://services1.arcgis.com/EjjnBtwS9ivTGI8x/arcgis/rest/services/Points_of_Interest/FeatureServer/0/query?outFields=*&where=%22type%22=%27Library%27&f=geojson\"\n",
|
|
210
|
+
"librariesRaw = gpd.read_file(url)"
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"cell_type": "markdown",
|
|
215
|
+
"id": "1db0a93b-5f7a-4a6a-ade1-d25b33477da8",
|
|
216
|
+
"metadata": {},
|
|
217
|
+
"source": [
|
|
218
|
+
"The data incudes a bunch of fields that we don't need. For clarity, extract only the relevant fields."
|
|
219
|
+
]
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
"cell_type": "code",
|
|
223
|
+
"execution_count": null,
|
|
224
|
+
"id": "61f3f15d-072d-486f-abf3-6fbdd4f71fda",
|
|
225
|
+
"metadata": {
|
|
226
|
+
"tags": []
|
|
227
|
+
},
|
|
228
|
+
"outputs": [],
|
|
229
|
+
"source": [
|
|
230
|
+
"libraries = librariesRaw.copy().filter(items=['NAME', 'ADDRESS','geometry'], axis=\"columns\")"
|
|
231
|
+
]
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"cell_type": "code",
|
|
235
|
+
"execution_count": null,
|
|
236
|
+
"id": "c6d75af5-6020-4e04-8245-298ddcf7ebb0",
|
|
237
|
+
"metadata": {
|
|
238
|
+
"tags": []
|
|
239
|
+
},
|
|
240
|
+
"outputs": [],
|
|
241
|
+
"source": [
|
|
242
|
+
"libraries.head()"
|
|
243
|
+
]
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"cell_type": "markdown",
|
|
247
|
+
"id": "776f3718-24d5-4e56-b51b-89b796d9794b",
|
|
248
|
+
"metadata": {},
|
|
249
|
+
"source": [
|
|
250
|
+
"Let's take a look at the library locations."
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"cell_type": "code",
|
|
255
|
+
"execution_count": null,
|
|
256
|
+
"id": "5f1c2dee-3fbb-4515-b04a-844efcab875d",
|
|
257
|
+
"metadata": {
|
|
258
|
+
"tags": []
|
|
259
|
+
},
|
|
260
|
+
"outputs": [],
|
|
261
|
+
"source": [
|
|
262
|
+
"libraries.explore(style_kwds={\"radius\":4})"
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
"cell_type": "markdown",
|
|
267
|
+
"id": "8ade8d86-39df-4f3f-8856-08c03d1f18a9",
|
|
268
|
+
"metadata": {},
|
|
269
|
+
"source": [
|
|
270
|
+
"Use the `assign_geo_identifiers()` function to iterate through the requested geography levels (in this case \"county\" and \"place\"), labeling each point with the identifier of the geography in each level where the point is located."
|
|
271
|
+
]
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
"cell_type": "markdown",
|
|
275
|
+
"id": "a37eb22f-3cbd-4681-8715-a331b1a16703",
|
|
276
|
+
"metadata": {},
|
|
277
|
+
"source": [
|
|
278
|
+
"## Assign Geographic Identifiers"
|
|
279
|
+
]
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"cell_type": "markdown",
|
|
283
|
+
"id": "9ef7876e-8f9d-4539-99b1-02056e7d8251",
|
|
284
|
+
"metadata": {},
|
|
285
|
+
"source": [
|
|
286
|
+
"This fuction is broken due to changes at the Census which prevents loading TigerLINE files from FTP site."
|
|
287
|
+
]
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
"cell_type": "code",
|
|
291
|
+
"execution_count": null,
|
|
292
|
+
"id": "58025778-8cdc-4e25-9cd9-45895f165673",
|
|
293
|
+
"metadata": {
|
|
294
|
+
"editable": true,
|
|
295
|
+
"slideshow": {
|
|
296
|
+
"slide_type": ""
|
|
297
|
+
},
|
|
298
|
+
"tags": []
|
|
299
|
+
},
|
|
300
|
+
"outputs": [],
|
|
301
|
+
"source": [
|
|
302
|
+
"librariesEnriched = morpc.assign_geo_identifiers(libraries, [\"county\",\"place\"])"
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"cell_type": "markdown",
|
|
307
|
+
"id": "af64d50e-779e-404c-92af-978205aa7f61",
|
|
308
|
+
"metadata": {
|
|
309
|
+
"editable": true,
|
|
310
|
+
"slideshow": {
|
|
311
|
+
"slide_type": ""
|
|
312
|
+
},
|
|
313
|
+
"tags": []
|
|
314
|
+
},
|
|
315
|
+
"source": [
|
|
316
|
+
"Note that two columns have been added to the dataframe, one that contains the identifier for the county the library is located in and one that contains the identifier for the place. "
|
|
317
|
+
]
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
"cell_type": "code",
|
|
321
|
+
"execution_count": null,
|
|
322
|
+
"id": "c4a8debd-a9e8-4973-8d44-57012206449b",
|
|
323
|
+
"metadata": {
|
|
324
|
+
"editable": true,
|
|
325
|
+
"slideshow": {
|
|
326
|
+
"slide_type": ""
|
|
327
|
+
},
|
|
328
|
+
"tags": []
|
|
329
|
+
},
|
|
330
|
+
"outputs": [],
|
|
331
|
+
"source": [
|
|
332
|
+
"librariesEnriched.head()"
|
|
333
|
+
]
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
"cell_type": "markdown",
|
|
337
|
+
"id": "ffb89cbd-81f4-44f9-93da-f3a656e44323",
|
|
338
|
+
"metadata": {
|
|
339
|
+
"editable": true,
|
|
340
|
+
"slideshow": {
|
|
341
|
+
"slide_type": ""
|
|
342
|
+
},
|
|
343
|
+
"tags": []
|
|
344
|
+
},
|
|
345
|
+
"source": [
|
|
346
|
+
"Let's take a look at libraries, symbolizing each according to the county where it is located."
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
"cell_type": "code",
|
|
351
|
+
"execution_count": null,
|
|
352
|
+
"id": "576aeba6-9978-4b1b-b846-46d4db0d8087",
|
|
353
|
+
"metadata": {
|
|
354
|
+
"tags": []
|
|
355
|
+
},
|
|
356
|
+
"outputs": [],
|
|
357
|
+
"source": [
|
|
358
|
+
"librariesEnriched.explore(column=\"id_county\", style_kwds={\"radius\":4})"
|
|
359
|
+
]
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"cell_type": "markdown",
|
|
363
|
+
"id": "614c48cc-36cc-44be-8211-a96ec3c7577d",
|
|
364
|
+
"metadata": {},
|
|
365
|
+
"source": [
|
|
366
|
+
"Let's take another look, this time symbolizing each library according to the place where it is located. The legend has been suppressed because there are too many unique values, but you can hover over each point to see the place identifier that has been assigned to it."
|
|
367
|
+
]
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
"cell_type": "code",
|
|
371
|
+
"execution_count": null,
|
|
372
|
+
"id": "45ffcdad-dfa2-4e8d-b684-ffeb6fb1b12d",
|
|
373
|
+
"metadata": {
|
|
374
|
+
"tags": []
|
|
375
|
+
},
|
|
376
|
+
"outputs": [],
|
|
377
|
+
"source": [
|
|
378
|
+
"librariesEnriched.explore(column=\"id_place\", style_kwds={\"radius\":4}, legend=True)"
|
|
379
|
+
]
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"cell_type": "code",
|
|
383
|
+
"execution_count": null,
|
|
384
|
+
"id": "dcf66a7e-9f88-48c0-ad7c-e2ebf2876809",
|
|
385
|
+
"metadata": {},
|
|
386
|
+
"outputs": [],
|
|
387
|
+
"source": []
|
|
388
|
+
}
|
|
389
|
+
],
|
|
390
|
+
"metadata": {
|
|
391
|
+
"kernelspec": {
|
|
392
|
+
"display_name": "Python 3 (ipykernel)",
|
|
393
|
+
"language": "python",
|
|
394
|
+
"name": "python3"
|
|
395
|
+
},
|
|
396
|
+
"language_info": {
|
|
397
|
+
"codemirror_mode": {
|
|
398
|
+
"name": "ipython",
|
|
399
|
+
"version": 3
|
|
400
|
+
},
|
|
401
|
+
"file_extension": ".py",
|
|
402
|
+
"mimetype": "text/x-python",
|
|
403
|
+
"name": "python",
|
|
404
|
+
"nbconvert_exporter": "python",
|
|
405
|
+
"pygments_lexer": "ipython3",
|
|
406
|
+
"version": "3.12.10"
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
"nbformat": 4,
|
|
410
|
+
"nbformat_minor": 5
|
|
411
|
+
}
|
|
@@ -614,14 +614,6 @@
|
|
|
614
614
|
"source": [
|
|
615
615
|
"dim_table.loc[dim_table['Variable type'] == 'Estimate'].head()"
|
|
616
616
|
]
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
"cell_type": "code",
|
|
620
|
-
"execution_count": null,
|
|
621
|
-
"id": "730020a9-0f68-4441-a20b-1c2f1abaff74",
|
|
622
|
-
"metadata": {},
|
|
623
|
-
"outputs": [],
|
|
624
|
-
"source": []
|
|
625
617
|
}
|
|
626
618
|
],
|
|
627
619
|
"metadata": {
|
|
@@ -10,14 +10,20 @@ ACS_ID_FIELDS = {
|
|
|
10
10
|
{"name":"SUMLEVEL", "type":"string", "description":"Code representing the geographic summary level for the data"},
|
|
11
11
|
{"name":"STATE","type":"string","description":"Unique identifier for state in which geography is located"},
|
|
12
12
|
{"name":"COUNTY","type":"string","description":"Unique identifier for county in which geography is located"},
|
|
13
|
-
{"name":"TRACT","type":"string","description":"Unique identifier for tract in which geography is located"}
|
|
13
|
+
{"name":"TRACT","type":"string","description":"Unique identifier for tract in which geography is located"}
|
|
14
14
|
],
|
|
15
15
|
"tract": [
|
|
16
16
|
{"name":"GEO_ID", "type":"string", "description":"Unique identifier for geography"},
|
|
17
17
|
{"name":"SUMLEVEL", "type":"string", "description":"Code representing the geographic summary level for the data"},
|
|
18
18
|
{"name":"STATE","type":"string","description":"Unique identifier for state in which geography is located"},
|
|
19
19
|
{"name":"COUNTY","type":"string","description":"Unique identifier for county in which geography is located"}
|
|
20
|
-
],
|
|
20
|
+
],
|
|
21
|
+
"county subdivision": [
|
|
22
|
+
{"name":"GEO_ID", "type":"string", "description":"Unique identifier for geography"},
|
|
23
|
+
{"name":"SUMLEVEL", "type":"string", "description":"Code representing the geographic summary level for the data"},
|
|
24
|
+
{"name":"STATE","type":"string","description":"Unique identifier for state in which geography is located"},
|
|
25
|
+
{"name":"COUNTY","type":"string","description":"Unique identifier for county in which geography is located"}
|
|
26
|
+
],
|
|
21
27
|
"county": [
|
|
22
28
|
{"name":"GEO_ID", "type":"string", "description":"Unique identifier for geography"},
|
|
23
29
|
{"name":"SUMLEVEL", "type":"string", "description":"Code representing the geographic summary level for the data"},
|
|
@@ -146,13 +152,13 @@ ACS_AGEGROUP_SORT_ORDER = {
|
|
|
146
152
|
'80 to 84 years': 17,
|
|
147
153
|
'85 years and over': 18
|
|
148
154
|
}
|
|
149
|
-
|
|
155
|
+
|
|
150
156
|
def api_get(url, params, varBatchSize=20, verbose=True):
|
|
151
157
|
"""
|
|
152
158
|
api_get() is a low-level wrapper for Census API requests that returns the results as a pandas dataframe. If necessary, it
|
|
153
159
|
splits the request into several smaller requests to bypass the 50-variable limit imposed by the API. The resulting dataframe
|
|
154
160
|
is indexed by GEOID (regardless of whether it was requested) and omits other fields that are not requested but which are returned
|
|
155
|
-
automatically with each API request (e.g. "state", "county")
|
|
161
|
+
automatically with each API request (e.g. "state", "county")
|
|
156
162
|
|
|
157
163
|
Parameters
|
|
158
164
|
----------
|
|
@@ -278,6 +284,39 @@ def api_get(url, params, varBatchSize=20, verbose=True):
|
|
|
278
284
|
# |--------------|----------------|---------------------|-------------------------|
|
|
279
285
|
# | B25127_004E | Owner occupied | Built 2020 or later | 1, detached or attached |
|
|
280
286
|
#
|
|
287
|
+
|
|
288
|
+
def acs_variables_by_group(groupNumber, acsYear, acsSurvey):
|
|
289
|
+
"""
|
|
290
|
+
Get a list of all variables that are in a census variable group.
|
|
291
|
+
|
|
292
|
+
Parameters
|
|
293
|
+
----------
|
|
294
|
+
groupNumber : str
|
|
295
|
+
The group number to search for within the variables table. ie. B11001
|
|
296
|
+
|
|
297
|
+
acsYear : str
|
|
298
|
+
The year of the survey. ie. 2023
|
|
299
|
+
|
|
300
|
+
acsSurvey : str
|
|
301
|
+
The acs survey to get variables for. ie. 1 or 5
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
dict
|
|
306
|
+
A dict of the variables in the group and related fields.
|
|
307
|
+
"""
|
|
308
|
+
import requests
|
|
309
|
+
import json
|
|
310
|
+
|
|
311
|
+
r = requests.get(f'https://api.census.gov/data/{acsYear}/acs/acs{acsSurvey}/variables.json')
|
|
312
|
+
json = r.json()
|
|
313
|
+
|
|
314
|
+
variables = {}
|
|
315
|
+
for variable in json['variables']:
|
|
316
|
+
if json['variables'][variable]['group'] == groupNumber:
|
|
317
|
+
variables[variable] = json['variables'][variable]
|
|
318
|
+
return variables
|
|
319
|
+
|
|
281
320
|
def acs_label_to_dimensions(labelSeries, dimensionNames=None):
|
|
282
321
|
"""
|
|
283
322
|
acs_label_to_dimensions(labelSeries, dimensionNames=None)
|
|
@@ -36,7 +36,7 @@ def name_to_desc_map(schema):
|
|
|
36
36
|
|
|
37
37
|
# Given a dataframe and the Frictionless Schema object (see load_schema), recast each of the fields in the
|
|
38
38
|
# dataframe to the data type specified in the schema.
|
|
39
|
-
def cast_field_types(df, schema, forceInteger=False, handleMissingFields="error", verbose=True):
|
|
39
|
+
def cast_field_types(df, schema, forceInteger=False, forceInt64=False, handleMissingFields="error", verbose=True):
|
|
40
40
|
import frictionless
|
|
41
41
|
import pandas as pd
|
|
42
42
|
import shapely
|
|
@@ -64,8 +64,13 @@ def cast_field_types(df, schema, forceInteger=False, handleMissingFields="error"
|
|
|
64
64
|
# the field must be cast as "Int64" instead.
|
|
65
65
|
if((fieldType == "int") or (fieldType == "integer")):
|
|
66
66
|
try:
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
if(forceInt64 == True):
|
|
68
|
+
# Cast all integer fields as Int64 whether this is necessary or not. This is useful when trying to merge
|
|
69
|
+
# dataframes with mixed int32 and Int64 values.
|
|
70
|
+
outDF[fieldName] = outDF[fieldName].astype("Int64")
|
|
71
|
+
else:
|
|
72
|
+
# Try to cast the field as an "int". This will fail if nulls are present.
|
|
73
|
+
outDF[fieldName] = outDF[fieldName].astype("int")
|
|
69
74
|
except:
|
|
70
75
|
try:
|
|
71
76
|
# Try to cast as "Int64", which supports nulls. This will fail if the fractional part is non-zero.
|
|
@@ -472,7 +477,7 @@ def validate_resource(resourcePath, verbose=True):
|
|
|
472
477
|
print(results)
|
|
473
478
|
return False
|
|
474
479
|
|
|
475
|
-
def load_data(resourcePath, archiveDir=None, validate=False, verbose=True):
|
|
480
|
+
def load_data(resourcePath, archiveDir=None, validate=False, forceInteger=False, forceInt64=False, verbose=True):
|
|
476
481
|
"""Often we want to make a copy of some input data and work with the copy, for example to protect
|
|
477
482
|
the original data or to create an archival copy of it so that we can replicate the process later.
|
|
478
483
|
The `load_data()` function simplifies the process of reading the data and
|
|
@@ -488,6 +493,15 @@ def load_data(resourcePath, archiveDir=None, validate=False, verbose=True):
|
|
|
488
493
|
validate : bool
|
|
489
494
|
Optional. If True, the resource file, schema file, and data file will be validated. If archiveDir is
|
|
490
495
|
specified, the copies of the files will be validated. If not, the original files will be validated.
|
|
496
|
+
Defaults to False.
|
|
497
|
+
forceInteger : bool
|
|
498
|
+
Optional. If True, then try harder to cast integer fields. This may involve rounding the values to the ones places.
|
|
499
|
+
Defaults to False.
|
|
500
|
+
forceInt64 : bool
|
|
501
|
+
Optional. If True, then cast all integer fields as Int64 regardless of whether this is necessary. This is useful
|
|
502
|
+
when trying to merge dataframes which would otherwise have mixed int32 and Int64 fields. Defaults to False.
|
|
503
|
+
verbose : bool
|
|
504
|
+
Optional. If False, then most output will be suppressed. Defaults to True.
|
|
491
505
|
|
|
492
506
|
Returns
|
|
493
507
|
-------
|
|
@@ -559,7 +573,7 @@ def load_data(resourcePath, archiveDir=None, validate=False, verbose=True):
|
|
|
559
573
|
print("morpc.load_data | ERROR | Unknown data file extension: {}".format(dataFileExtension))
|
|
560
574
|
raise RuntimeError
|
|
561
575
|
|
|
562
|
-
df = cast_field_types(df, resource.schema, verbose=verbose)
|
|
576
|
+
df = cast_field_types(df, resource.schema, forceInteger=forceInteger, forceInt64=forceInt64, verbose=verbose)
|
|
563
577
|
|
|
564
578
|
return df, resource, resource.schema
|
|
565
579
|
|