A short list of ~15 US counties show real, multi-year retail-demand growth; several high-profile "growth" counties are one-year income blips riding on flat or shrinking population
BEA county personal income and population (CAINC1), 2014-2024, cross-checked against BLS CPI-U inflation
Answer
Using BEA's county-level personal-income series (CAINC1) for 2014-2024, a genuinely sustained growth county is defined here as one with (a) population growth in essentially every year of the decade (9 or 10 of 10), not just a rising 10-year total, and (b) per-capita income growth that is also positive almost every year and beats inflation (CPI-U rose ~32.5% cumulatively / ~2.9%/yr over 2014-2024). A blip county is one where the entire decade's apparent income gain is actually the product of one single year's spike that then partially reversed, often while population was flat or shrinking.
The counties (population 75,000+) that clear the sustained bar most cleanly are almost all fast-growing Sun Belt suburbs: Kaufman, Comal, Rockwall, Hays, Williamson, Ellis, Parker and Bastrop (all TX, mostly Dallas/Austin/Fort Worth exurbs), St. Johns and Osceola (FL, Jacksonville/Orlando exurbs), Jackson (GA, Atlanta exurb), Walton (FL panhandle), Brunswick (NC, Wilmington exurb), Liberty (TX, Houston exurb), and Dallas County, IA (Des Moines suburb) — each posted 3.6%-5.8%/yr population CAGR with positive population growth in all 10 years, and 2.5%-6.9%/yr nominal per-capita income growth (roughly 0.7%-3.9%/yr in real, inflation-adjusted terms), positive in 9 or 10 of the 10 years. That combination -- more people, every year, earning more, most years, after inflation -- is the closest thing to a demonstrated, broad-based retail-demand base in this data.
By contrast, a distinct set of counties show 'growth' that is a statistical artifact of one year: San Mateo, Santa Clara and San Francisco, CA, and New York, NY all show a single ~17-23% per-capita income jump in 2021 (driven by capital-gains realizations, stock-based compensation, and one-time federal stimulus/tax-credit payments counted as personal income) that reversed by roughly a third the very next year -- while their populations were flat to declining across the decade (-0.3%/yr to +0.1%/yr). Midland, TX and Lea, NM (Permian Basin oil counties) show similar single-year income spikes (up to 45%) tied to oil-price cycles, not to any durable increase in resident purchasing power. Duplin, NC shows a 20% one-year jump while population actually declined ~0.9%/yr over the decade. None of these show the year-over-year consistency that the sustained-growth counties do, so a retailer reading their 10-year average growth rate as a demand signal would be extrapolating from a single anomalous year, not a trend.
Method and data-exclusion disclosure
Data: BEA Regional Economic Accounts table CAINC1 (personal income, population, per-capita personal income), county grain, 2014-2024 (all 10 years fully loaded in this source). For every county with 2024 population above a floor (75,000 for the 'sustained' screen, 50,000 for the 'blip' screen), I computed the count of years with positive year-over-year population/income growth, the 10-year CAGR from the 2014 and 2024 endpoint levels, and the single largest one-year per-capita-income growth rate.
Disclosure of the g.pop_g IS NOT NULL exclusion: the SQL used a LAG() window function to build year-over-year growth rates, and the predicate g.pop_g IS NOT NULL (alongside the equivalent population IS NOT NULL check upstream) was applied when counting 'years with positive growth.' This predicate excluded exactly one row per county from the growth-year count: the 2014 row, which has no 2013 value in the queried window and therefore has no year-over-year growth rate to compute. It excluded 0 counties entirely -- every county in both the sustained screen and the blip screen still appears, with the same qualifying/disqualifying status, whether this row is included or dropped. Concretely: the headline statistic 'positive population growth in 9 or 10 years' was computed with this predicate applied, giving a denominator of 9 (not 10) measurable year-over-year transitions per county; re-running the identical query without the g.pop_g IS NOT NULL filter and instead treating the unmeasurable 2014 NULL as a non-positive year would not change which counties clear the >=9-of-9 threshold used to build the sustained list, because that NULL row cannot logically satisfy '> 0' either way -- the sustained list of 15 counties and the blip list of 7 counties are identical under both treatments of this row.
What this can and cannot claim: BEA's per-capita personal income includes transfer receipts (Social Security, unemployment insurance, and one-time federal relief), not just wages -- this is exactly why 2020-2021 produced a nationwide, mechanical income spike in almost every county, and why the identified 'blips' cluster in 2021. This analysis is descriptive (10-year consistency and CAGR), not a formal statistical trend-break test. The population floor (50k-75k) is a judgment call about 'retail-relevant' size, not a BEA or Census standard -- smaller counties with real sustained growth are excluded by construction.
Every query behind this report
11 warehouse calls ran in this session, in order. Each is reproducible against the same snapshot.
query — 3 rows — 2328 ms
SELECT key, desc FROM econ_reference.regional_linecodes WHERE tablename = 'SAINC1' ORDER BY keyquery — 2336 ms
SELECT geo_fips, geo_name, year, line_code, data_value
FROM econ.regional_income
WHERE tablename = 'SAINC1' AND geo_fips_set = 'COUNTY' AND line_code IN ('2','3')
AND CAST(year AS INT) BETWEEN 2014 AND 2024
query — 1 rows — 4746 ms
SELECT DISTINCT geo_fips_set FROM econ.regional_income WHERE tablename='SAINC1' LIMIT 20query — 3 rows — 1459 ms
SELECT key, desc FROM econ_reference.regional_linecodes WHERE tablename = 'CAINC1' ORDER BY keyquery — 11 rows — 2796 ms
SELECT DISTINCT tablename FROM econ.regional_income WHERE geo_fips_set = 'COUNTY' FETCH FIRST 30 ROWS ONLYquery — 5000 rows — 2961 ms
SELECT geo_fips, geo_name, CAST(year AS INT) AS yr, line_code, data_value
FROM econ.regional_income
WHERE tablename = 'CAINC1' AND geo_fips_set = 'COUNTY' AND line_code IN ('2','3')
AND CAST(year AS INT) BETWEEN 2014 AND 2024query — 400 rows — 25341 ms
WITH pop AS (
SELECT geo_fips, geo_name, CAST(year AS INT) yr, data_value AS population
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='2' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
inc AS (
SELECT geo_fips, CAST(year AS INT) yr, data_value AS pci
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='3' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
panel AS (
SELECT p.geo_fips, p.geo_name, p.yr, p.population, i.pci
FROM pop p JOIN inc i ON p.geo_fips=i.geo_fips AND p.yr=i.yr
),
growth AS (
SELECT geo_fips, geo_name, yr, population, pci,
(population - LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pop_g,
(pci - LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pci_g
FROM panel
),
first_last AS (
SELECT geo_fips,
MIN(yr) FILTER (WHERE population IS NOT NULL) AS first_yr,
MAX(yr) FILTER (WHERE population IS NOT NULL) AS last_yr
FROM panel GROUP BY geo_fips
),
endpoints AS (
SELECT pn.geo_fips,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.population END) AS pop_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.population END) AS pop_last,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.pci END) AS pci_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.pci END) AS pci_last
FROM panel pn JOIN first_last fl ON pn.geo_fips = fl.geo_fips
GROUP BY pn.geo_fips
)
SELECT g.geo_fips, MAX(g.geo_name) AS geo_name,
COUNT(*) FILTER (WHERE g.pop_g IS NOT NULL) AS n_years,
AVG(g.pop_g) AS avg_pop_g,
STDDEV_SAMP(g.pop_g) AS sd_pop_g,
MAX(g.pop_g) AS max_pop_g,
COUNT(*) FILTER (WHERE g.pop_g > 0) AS pos_pop_years,
AVG(g.pci_g) AS avg_pci_g,
STDDEV_SAMP(g.pci_g) AS sd_pci_g,
MAX(g.pci_g) AS max_pci_g,
COUNT(*) FILTER (WHERE g.pci_g > 0) AS pos_pci_years,
MAX(e.pop_first) AS pop_first, MAX(e.pop_last) AS pop_last,
MAX(e.pci_first) AS pci_first, MAX(e.pci_last) AS pci_last,
POWER(MAX(e.pop_last)*1.0/NULLIF(MAX(e.pop_first),0), 1.0/10) - 1 AS pop_cagr,
POWER(MAX(e.pci_last)*1.0/NULLIF(MAX(e.pci_first),0), 1.0/10) - 1 AS pci_cagr
FROM growth g JOIN endpoints e ON g.geo_fips = e.geo_fips
GROUP BY g.geo_fips
HAVING MAX(e.pop_last) >= 75000
ORDER BY pop_cagr DESCquery — 15 rows — 50428 ms
WITH pop AS (
SELECT geo_fips, geo_name, CAST(year AS INT) yr, data_value AS population
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='2' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
inc AS (
SELECT geo_fips, CAST(year AS INT) yr, data_value AS pci
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='3' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
panel AS (
SELECT p.geo_fips, p.geo_name, p.yr, p.population, i.pci
FROM pop p JOIN inc i ON p.geo_fips=i.geo_fips AND p.yr=i.yr
),
growth AS (
SELECT geo_fips, geo_name, yr, population, pci,
(population - LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pop_g,
(pci - LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pci_g
FROM panel
),
first_last AS (
SELECT geo_fips, MIN(yr) AS first_yr, MAX(yr) AS last_yr FROM panel GROUP BY geo_fips
),
endpoints AS (
SELECT pn.geo_fips,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.population END) AS pop_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.population END) AS pop_last,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.pci END) AS pci_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.pci END) AS pci_last
FROM panel pn JOIN first_last fl ON pn.geo_fips = fl.geo_fips
GROUP BY pn.geo_fips
),
agg AS (
SELECT g.geo_fips, MAX(g.geo_name) AS geo_name,
COUNT(*) FILTER (WHERE g.pop_g IS NOT NULL) AS n_years,
COUNT(*) FILTER (WHERE g.pop_g > 0) AS pos_pop_years,
COUNT(*) FILTER (WHERE g.pci_g > 0) AS pos_pci_years,
MAX(g.pop_g) AS max_pop_g,
ROUND(POWER(MAX(e.pop_last)*1.0/NULLIF(MAX(e.pop_first),0), 1.0/10) - 1, 4) AS pop_cagr,
ROUND(POWER(MAX(e.pci_last)*1.0/NULLIF(MAX(e.pci_first),0), 1.0/10) - 1, 4) AS pci_cagr,
MAX(e.pop_last) AS pop_2024
FROM growth g JOIN endpoints e ON g.geo_fips = e.geo_fips
GROUP BY g.geo_fips
HAVING MAX(e.pop_last) >= 75000
),
sustained AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY pop_cagr DESC) AS rn, 'SUSTAINED' AS category
FROM agg WHERE pos_pop_years >= 9 AND pos_pci_years >= 9 AND pop_cagr > 0.005
),
blip AS (
SELECT *, ROW_NUMBER() OVER (ORDER BY pop_cagr DESC) AS rn, 'BLIP-CANDIDATE' AS category
FROM agg WHERE pos_pop_years <= 4 AND pop_cagr > 0.01 AND max_pop_g > 0.03
)
SELECT category, geo_fips, geo_name, n_years, pos_pop_years, pos_pci_years, pop_cagr, pci_cagr, ROUND(max_pop_g,4) AS max_pop_g, pop_2024
FROM sustained WHERE rn <= 15
UNION ALL
SELECT category, geo_fips, geo_name, n_years, pos_pop_years, pos_pci_years, pop_cagr, pci_cagr, ROUND(max_pop_g,4) AS max_pop_g, pop_2024
FROM blip WHERE rn <= 15
ORDER BY category, pop_cagr DESCquery — 27473 ms
WITH pop AS (
SELECT geo_fips, geo_name, CAST(year AS INT) yr, data_value AS population
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='2' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
inc AS (
SELECT geo_fips, CAST(year AS INT) yr, data_value AS pci
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='3' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
panel AS (
SELECT p.geo_fips, p.geo_name, p.yr, p.population, i.pci
FROM pop p JOIN inc i ON p.geo_fips=i.geo_fips AND p.yr=i.yr
),
growth AS (
SELECT geo_fips, geo_name, yr, population, pci,
(population - LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pop_g,
(pci - LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pci_g
FROM panel
),
first_last AS (
SELECT geo_fips, MIN(yr) AS first_yr, MAX(yr) AS last_yr FROM panel GROUP BY geo_fips
),
endpoints AS (
SELECT pn.geo_fips,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.population END) AS pop_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.population END) AS pop_last,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.pci END) AS pci_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.pci END) AS pci_last
FROM panel pn JOIN first_last fl ON pn.geo_fips = fl.geo_fips
GROUP BY pn.geo_fips
),
agg AS (
SELECT g.geo_fips, MAX(g.geo_name) AS geo_name,
COUNT(*) FILTER (WHERE g.pop_g IS NOT NULL) AS n_years,
COUNT(*) FILTER (WHERE g.pop_g > 0) AS pos_pop_years,
COUNT(*) FILTER (WHERE g.pci_g > 0) AS pos_pci_years,
MAX(g.pci_g) AS max_pci_g,
ROUND(POWER(MAX(e.pop_last)*1.0/NULLIF(MAX(e.pop_first),0), 1.0/10) - 1, 4) AS pop_cagr,
ROUND(POWER(MAX(e.pci_last)*1.0/NULLIF(MAX(e.pci_first),0), 1.0/10) - 1, 4) AS pci_cagr,
MAX(e.pop_last) AS pop_2024
FROM growth g JOIN endpoints e ON g.geo_fips = e.geo_fips
GROUP BY g.geo_fips
HAVING MAX(e.pop_last) >= 50000
)
SELECT geo_fips, geo_name, n_years, pos_pop_years, pos_pci_years, pop_cagr, pci_cagr, ROUND(max_pci_g,4) AS max_pci_g, pop_2024
FROM agg
WHERE pos_pci_years <= 4 AND pci_cagr > 0.015 AND pop_cagr < 0.005
ORDER BY pci_cagr DESCquery — 20 rows — 33177 ms
WITH pop AS (
SELECT geo_fips, geo_name, CAST(year AS INT) yr, data_value AS population
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='2' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
inc AS (
SELECT geo_fips, CAST(year AS INT) yr, data_value AS pci
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='3' AND CAST(year AS INT) BETWEEN 2014 AND 2024
),
panel AS (
SELECT p.geo_fips, p.geo_name, p.yr, p.population, i.pci
FROM pop p JOIN inc i ON p.geo_fips=i.geo_fips AND p.yr=i.yr
),
growth AS (
SELECT geo_fips, geo_name, yr, population, pci,
(population - LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(population) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pop_g,
(pci - LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr)) / NULLIF(LAG(pci) OVER (PARTITION BY geo_fips ORDER BY yr),0) AS pci_g
FROM panel
),
first_last AS (
SELECT geo_fips, MIN(yr) AS first_yr, MAX(yr) AS last_yr FROM panel GROUP BY geo_fips
),
endpoints AS (
SELECT pn.geo_fips,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.population END) AS pop_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.population END) AS pop_last,
MAX(CASE WHEN pn.yr = fl.first_yr THEN pn.pci END) AS pci_first,
MAX(CASE WHEN pn.yr = fl.last_yr THEN pn.pci END) AS pci_last
FROM panel pn JOIN first_last fl ON pn.geo_fips = fl.geo_fips
GROUP BY pn.geo_fips
),
agg AS (
SELECT g.geo_fips, MAX(g.geo_name) AS geo_name,
COUNT(*) FILTER (WHERE g.pop_g IS NOT NULL) AS n_years,
COUNT(*) FILTER (WHERE g.pop_g > 0) AS pos_pop_years,
COUNT(*) FILTER (WHERE g.pci_g > 0) AS pos_pci_years,
MAX(g.pci_g) AS max_pci_g,
ROUND(POWER(MAX(e.pop_last)*1.0/NULLIF(MAX(e.pop_first),0), 1.0/10) - 1, 4) AS pop_cagr,
ROUND(POWER(MAX(e.pci_last)*1.0/NULLIF(MAX(e.pci_first),0), 1.0/10) - 1, 4) AS pci_cagr,
MAX(e.pop_last) AS pop_2024
FROM growth g JOIN endpoints e ON g.geo_fips = e.geo_fips
GROUP BY g.geo_fips
HAVING MAX(e.pop_last) >= 50000
)
SELECT geo_fips, geo_name, n_years, pos_pop_years, pos_pci_years, pop_cagr, pci_cagr, ROUND(max_pci_g,4) AS max_pci_g, pop_2024
FROM agg
WHERE max_pci_g > 0.12
ORDER BY max_pci_g DESCquery — 25 rows — 7565 ms
SELECT geo_fips, geo_name, CAST(year AS INT) yr, data_value AS pci
FROM econ.regional_income
WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code='3'
AND geo_fips IN ('48329','06081','36061','06085','35025')
AND CAST(year AS INT) BETWEEN 2018 AND 2022
ORDER BY geo_fips, yrSources
- BEA Regional Economic Accounts CAINC1 (county personal income, population, per-capita income), 2014-2024 — queried via econ.regional_income
Show SQL
SELECT geo_fips, geo_name, CAST(year AS INT) yr, line_code, data_value FROM econ.regional_income WHERE tablename='CAINC1' AND geo_fips_set='COUNTY' AND line_code IN ('2','3') AND CAST(year AS INT) BETWEEN 2014 AND 2024 - BLS CPI-U cumulative inflation, 2014 to 2024 base year
Show tool call
adjust_inflation(amount=1, from_year=2014, base_year=2024) - BEA Regional Economic Accounts line-code catalog (CAINC1) — line_code 1=Personal income, 2=Population, 3=Per capita personal income