Skip to content

Commit 476ad50

Browse files
Merge pull request #279 from SANDAG/feature-employment-industry-xref
[PULL REQUEST] Block to MGRA Employment Industry Cross Reference
2 parents f768497 + b3b0492 commit 476ad50

4 files changed

Lines changed: 211 additions & 123 deletions

File tree

python/employment.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,13 @@ def _aggregate_lodes_to_mgra(
9797
This function allocates jobs from Census blocks to MGRAs using distributions from
9898
the California Employment Development Department (EDD) point-level dataset. Blocks
9999
with no EDD data available use a simple land area intersection to allocate jobs to
100-
MGRAs.
100+
MGRAs. The allocation first attempts to allocate within industry codes using EDD
101+
data, then falls back to using EDD data without considering industry codes, and
102+
finally falls back to using the land area intersection.
101103
102104
Args:
103105
combined_data: LODES data with columns: year, block, industry_code, jobs
104-
xref: Crosswalk with columns: block, mgra, pct_edd, pct_area, edd_flag
106+
xref: Crosswalk with columns: block, mgra, pct_industry, pct_edd, pct_area, flag
105107
year: The year for which to aggregate data
106108
107109
Returns:
@@ -111,14 +113,12 @@ def _aggregate_lodes_to_mgra(
111113
# Get MGRA data from SQL
112114
with utils.ESTIMATES_ENGINE.connect() as con:
113115
mgra_data = pd.read_sql_query(
114-
sql=sql.text(
115-
"""
116+
sql=sql.text("""
116117
SELECT DISTINCT [mgra]
117118
FROM [inputs].[mgra]
118119
WHERE run_id = :run_id
119120
ORDER BY [mgra]
120-
"""
121-
),
121+
"""),
122122
con=con,
123123
params={"run_id": utils.RUN_ID},
124124
)
@@ -129,10 +129,14 @@ def _aggregate_lodes_to_mgra(
129129
mgra_data.merge(pd.DataFrame({"industry_code": unique_industries}), how="cross")
130130
.assign(year=year)
131131
.merge(
132-
combined_data.merge(xref, on="block", how="inner")
132+
combined_data.merge(xref, on=["block", "industry_code"], how="inner")
133133
.assign(
134134
value=lambda df: df["jobs"]
135-
* np.where(df["edd_flag"] == 1, df["pct_edd"], df["pct_area"])
135+
* np.where(
136+
df["flag"] == "pct_industry",
137+
df["pct_industry"],
138+
np.where(df["flag"] == "pct_edd", df["pct_edd"], df["pct_area"]),
139+
)
136140
)
137141
.groupby(["year", "mgra", "industry_code"], as_index=False)["value"]
138142
.sum(),
@@ -170,7 +174,14 @@ def _distribute_self_emp_to_mgra(
170174
"""
171175
# Check that required columns are present
172176
required_b24080_cols = {"year", "geography", "industry_code", "value"}
173-
required_xref_cols = {"geography", "mgra", "flag", "pct_18_64", "pct_pop", "pct_split"}
177+
required_xref_cols = {
178+
"geography",
179+
"mgra",
180+
"flag",
181+
"pct_18_64",
182+
"pct_pop",
183+
"pct_split",
184+
}
174185
if not required_b24080_cols.issubset(b24080.columns):
175186
raise ValueError(
176187
f"B24080 DataFrame is missing required columns: {required_b24080_cols - set(b24080.columns)}"
@@ -179,7 +190,7 @@ def _distribute_self_emp_to_mgra(
179190
raise ValueError(
180191
f"xref DataFrame is missing required columns: {required_xref_cols - set(xref.columns)}"
181192
)
182-
193+
183194
# Check that flag column only contains expected values
184195
expected_flags = {"pct_18_64", "pct_pop", "pct_split"}
185196
if not set(xref["flag"].unique()).issubset(expected_flags):
@@ -214,8 +225,7 @@ def _distribute_self_emp_to_mgra(
214225

215226
# Sum weighted values to the MGRA level
216227
merged = (
217-
merged
218-
.groupby(["year", "mgra", "industry_code"])["weighted_value"]
228+
merged.groupby(["year", "mgra", "industry_code"])["weighted_value"]
219229
.sum()
220230
.reset_index()
221231
.assign(run_id=utils.RUN_ID)
@@ -347,11 +357,11 @@ def _validate_jobs_inputs(jobs_inputs: dict[str, pd.DataFrame]) -> None:
347357
null={},
348358
)
349359
# No row count validation performed as xref is many-to-many
360+
# NULLs are allowed in the result set
350361
tests.validate_data(
351362
"xref_block_to_mgra",
352363
jobs_inputs["xref_block_to_mgra"],
353364
negative={},
354-
null={},
355365
)
356366
# No row count validation performed as xref is many-to-many
357367
tests.validate_data(
@@ -374,7 +384,6 @@ def _validate_jobs_inputs(jobs_inputs: dict[str, pd.DataFrame]) -> None:
374384
negative={},
375385
null={},
376386
)
377-
378387

379388

380389
def _create_jobs_output(

sql/employment/get_naics72_split.sql

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ Point-level data is gotten from the confidential EDD dataset, assigned to
77
Notes:
88
1) This query assumes the connection is to the GIS server.
99
2) Data prior to year 2017 is not present in the EDD view and must be
10-
queried directly from the source database table.
10+
queried directly from the source database table. Note there is no 2016
11+
data available.
1112
3) If no split is present for a block, the regional percentage split is
1213
substituted. All 2020 Census blocks are represented, except three
1314
water-only slivers see: https://github.com/SANDAG/Estimates-Program/issues/193
@@ -94,7 +95,7 @@ BEGIN
9495
[emp_valid] > 0
9596
AND [emp_total] > 0
9697
END
97-
ELSE IF @year = 2016 OR @year BETWEEN 2010 AND 2014
98+
ELSE IF @year BETWEEN 2010 AND 2013
9899
BEGIN
99100
INSERT INTO [#edd]
100101
SELECT
@@ -123,7 +124,7 @@ BEGIN
123124
AND [businesses].[emp_id] = [employment].[emp_id]
124125
WHERE LEFT([code], 3) IN ('721','722')
125126
END
126-
ELSE IF @year = 2015
127+
ELSE IF @year BETWEEN 2014 AND 2016
127128
BEGIN
128129
INSERT INTO [#edd]
129130
SELECT
@@ -176,6 +177,8 @@ BEGIN
176177
-- Calculate % split of NAICS 72 into 721 and 722 for 2020 Census Blocks -
177178
SELECT
178179
[GEOID20] AS [block],
180+
-- Note these CASE statements default to regional average for the block
181+
-- Using the Window functions with OVER() to calculate regional averages
179182
CASE
180183
WHEN [72] = 0 THEN SUM([721]) OVER() / SUM([72]) OVER()
181184
ELSE [721] / [72]

0 commit comments

Comments
 (0)