尚未收集仓库信息。
尚未检测到更新。GitHub 发布版本后会显示在这里。
How many people in your city could actually be your match? Census-based estimates for 387 U.S. metros.
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Dating Atlas estimates how many people in a U.S. metro could realistically be your match, and compares that number across all 387 metro areas. It is a single web page and one data file, with no server.
Live page: https://datingatlas.dev/
You pick a metro and say who you are and who you're looking for. The page then shows:
You can filter by politics (one or more of progressive, liberal, moderate, conservative, very conservative and not political, or any), faith (one or more of non-religious, atheist, agnostic, nothing in particular, Christian, Catholic, Protestant, Latter-day Saint, other Christian, Jewish, Muslim, Hindu, Buddhist and Pagan or Wiccan, or any) and diet (vegetarian, vegan, pescatarian, kosher, halal or any). Your age, height, education, work and kids plans change how interested people are likely to be. Two quick picks set the default profile (progressive, liberal or moderate, non-religious) or the opposite one (moderate, conservative or very conservative, Christian). A climate choice (warm winters, four seasons, or cold, snowy winters, from NOAA's 1991–2020 temperatures) narrows the map, the ranked list and the comparisons to metros with that climate, without changing the pool.
On a first visit, a three-step setup asks who you are, who you're looking for and where, then shows the result. The result shows the funnel, the map, the scatter plot and the key numbers in a 2×2 grid, with your search summarized in a bar you can open to edit. The ranked list and the methods open when you ask for them. The page saves your settings in your browser and in its address, so a copied link reopens the same result and skips the setup.
atlas/index.html the page's markup and the methods notes
atlas/style.css its styles
atlas/model.js the funnel model: constants, sources and math, no page code
atlas/charts.js the funnel shape, map and charts
atlas/app.js the form, saved settings and links, the text, and the first-visit setup
atlas/data.js every metro's data, as JS constants with one metro per line
data/ CSV extracts the data in atlas/data.js is built from
docs/ research notes and the screenshot above
scripts/ scripts that rebuild atlas/data.js (one per constant, below)
scripts/structure/ checks of how the funnel is built, against survey microdata
scripts/structure/out/ their results, as JSON
.github/workflows/ publishes atlas/ to GitHub Pages
There is no build step. Each data script rewrites one JS constant (ACS, PUMS, REL, ...) in atlas/data.js, which the page loads before its own scripts, and the page computes everything else in the browser. Per-metro data sits one metro per line, so a diff of a data update shows which metros changed.
The funnel runs once for each gender you're looking for and adds them up (pool() in atlas/model.js). Each step multiplies the count before it by a share. Steps marked local differ by metro; the rest use national rates.
| # | Funnel step | Data | Built by | In the page |
|---|---|---|---|---|
| 1 | Population | Census Bureau 2025 estimates for the 387 MSAs (2023 definitions), via Wikipedia's metro list. Local | data/metro_raw.csv → raw_to_json.py | RAW (pop) |
| 2 | Your genders | Fixed shares: women 50%, men 48.5%, nonbinary 1.5% | none | G |
| 3 | In your age range | ACS 2019–2023 B12002 totals by sex and age band. Local | pull_acs.R → acs_to_json.py | ACS.metros[...].wt/mt |
| 4 | Single, not living with a partner | ACS B12002 never married, divorced or widowed (local), times the share of singles not cohabiting by sex and age band from 2023 PUMS (national) | pull_acs.R → acs_to_json.py; pums_singles.py | ACS.metros[...].w/m, PUMS.free |
| 5 | Looking to date | Pew 2022: 20% of singles are in a committed relationship; Pew 2019 share looking, by sex and under/over 40, scaled to 2022; from 65, times 16/36 (Pew 2022: 16% of unpartnered adults 65+ looking against 36% at 50 to 64); optionally, only those open to the relationship you want (Pew 2019: committed only, casual only or either, by sex and under/over 40) | none (constants) | COMMITTED, LOOKING, LOOK_65, lookRate(), lookFit(), GOAL_ONLY, goalFit() |
| 6 | Has a degree (optional) | ACS B15001 degree share by sex and age band (local), times how often singles hold degrees against everyone (PUMS, national) | acs_to_json.py; pums_singles.py | ACS.metros[...].dw/dm, PUMS.deg, degParts() |
| 7 | Open to dating your gender | Gallup orientation rates | none | OPEN |
| 8 | Shares your politics and faith | 2024 Harris share by county summed to metros (tonmcg); PRRI county religion estimates and the 2020 U.S. Religion Census; politics groups, their degree shifts and their odds ratios with faith from the 2024 CES. |
Outside the funnel: rent and pay come from ACS B25031, B25113 and B20004 (housing_to_json.py → HOUSE), climate from NOAA's county temperatures, as 1991–2020 January and July averages (climate_to_json.py → CLIMATE), and the map from the 2023 Census Gazetteer and us-atlas (build_map.mjs → MAPXY, USMAP). docs/clicks-research.md covers the evidence behind the attraction and app defaults, and docs/methods.md lists every source with links (the page's "How the estimates work" section is the short version).
The funnel is a chain of multiplications: start with a head count, then keep only the share that passes each step. It runs once for each gender t you're looking for, in your metro, for your partner age range a, and the results are added up at the end.
All in one line:
\text{Total} = \sum_t \Big[\, 0.985 \sum_{\text{age} \in a} U_t(\text{age})\, C_t(\text{age}) \Big] \times 0.80 \times L_t \times G_t \times D_t \times O_t \times V_t \times F_t \times K_t \times y_{\text{you}} \times \min\!\big(y_t\, T_t,\ 0.95\big)
where
Step by step:
\begin{aligned}
\text{Singles}_t &= 0.985 \times \sum_{\text{age} \in a} \text{unmarried}_t(\text{age}) \times \text{not cohabiting}_t(\text{age}) \\
\text{Looking}_t &= \text{Singles}_t \times 0.80 \times \text{looking rate}_t \times \text{goal}_t \\
\text{Degree}_t &= \text{Looking}_t \times \text{degree share}_t \\
\text{Open}_t &= \text{Degree}_t \times \text{open to you}_t \\
\text{Fit}_t &= \text{Open}_t \times \text{values}_t \times \text{diet}_t \times \text{kids}_t \\
\text{Clicks}_t &= \text{Fit}_t \times \text{you say yes} \times \min\left(\text{they say yes} \times \text{your traits},\ 0.95\right) \\
\text{On apps}_t &= \text{Clicks}_t \times \text{app share}_t \\[4pt]
\text{Total} &= \sum_t \text{Clicks}_t
\end{aligned}
| Step | In plain English | Code |
|---|---|---|
| Singles | Never-married, divorced and widowed people at each age in your range, minus those living with a partner. 1.5% are set aside as nonbinary. | ACS, PUMS.free |
| Looking | Drop the 20% of singles already in a relationship, then keep those looking to date (lower from 40, lower again from 65). If you pick "serious" or "casual", also drop people who only want the other kind; "either" drops no one. | COMMITTED, LOOKING, goalFit() |
| Degree | Keep only degree holders. Skipped if the degree filter is off. | degParts() |
| Open | Keep those whose orientation includes your gender. | OPEN |
| Fit | Keep those who share your politics and faith, your diet, and are compatible on kids. | valRaw(), dietFit(), kidsFit() |
| Clicks | Keep the matches where you'd both say yes. Their yes is adjusted for your age, height, degree, job and kids, and capped at 95%. | ATTRACT, traitRel() |
| On apps | Of those, the share you could find on a dating app right now. Shown alongside, not added to the total. | APPS |
The parts that aren't plain multiplication:
singleYears() fits a monotone cubic (Fritsch–Carlson PCHIP) through the cumulative count at each band edge and differences it by year, so any age range gets a smooth count and every band keeps its exact total. A self-check asserts the totals add back up.degParts() weights each B15001 band's share by the singles at each age in your range, then scales it by PUMS.deg, the ratio of singles' degree rate to everyone's in that band.logit P = a + b·logit(metro Harris share) + c·woman (POL_FIT), normalized to sum to 1. Left and right then split by the national ratios (POL_SPLIT). Nonbinary people get CES's national shares for nonbinary singles (POL_NB). The degree filter scales each group by POL_DEG and renormalizes.NONE_SPLIT). Named faiths use PRRI's share directly; Pagan or Wiccan scales a national 0.3% by the metro's "other religion" share.POL_OR), so a pair can never exceed either share alone. Other faiths pair independently. The pairs don't overlap, so they add, capped at your politics' share. With the degree filter, the degree holders' odds ratios apply; thin ones are shrunk toward the all-singles ratio.interval() reruns the funnel 2,000 times with fixed seeded normal draws, so the range doesn't jitter as you move sliders. Each draw scales every factor by a mean-one lognormal or normal:
ACSE, from acs_moe_to_json.py), combined across age bands as a root sum of squaresUNC)The page shows the 10th and 90th percentiles. It also reruns each group of inputs alone and names the group with the largest log-variance as what the number "mostly depends on". The range covers the inputs, not how the funnel is built.
The main number is one moment. turnFit() estimates who joins the pool over a year, per single person not living with a partner: people who divorced or were widowed in the past 12 months (PUMS.div), people who moved in from outside the metro (the metro's B07201 share MOVE, scaled by age and sex with PUMS.mig), and people whose relationship ends (committed singles at 30% a year from Rhoades et al. 2011, cohabiting couples at 15% from the 2006 to 2010 NSFG). Each age counts by its own share of the pool, from the one-year-at-a-time funnels behind the age chart. interval() adds them to today's count and gives it a range, with the breakup rates varied by ±35%. It leaves out people who start looking, so it's a floor. The "If you're still looking later" chart reruns the funnel with your age and range moved up a year at a time, a cross-section rather than a forecast. Details are in docs/methods.md.
scripts/structure/ tests how the funnel is built, as opposed to its inputs. Results are in scripts/structure/out/ and written up in docs/structural-uncertainty.md. They use the same requirements.txt.
| Script | What it does |
|---|---|
ces_estimates.py | From the 2024 CES, the joint rates of degree, politics, faith and openness among single 25-to-35-year-olds, with bootstrap intervals. Source of DEG_OR. |
ces_politics.py | From the 2024 CES, the six politics groups among single 25-to-35-year-olds: how they vary with a metro's Harris share, how a degree shifts them, their odds ratios with no religion and Christian, and the atheist, agnostic and nothing-in-particular split, with bootstrap intervals. Source of the POL_* constants and NONE_SPLIT. |
ces_metro_form.py | Checks the shape of the progressive and secular formulas against CES respondents placed in their metros. |
secular_level.py | Checks the 40% secular-leaning base against CES and the GSS, and how it falls with age. |
pums_degree.py | Degree rates and cohabiting rates of singles against everyone, from 2023 PUMS with replicate-weight standard errors. |
nsfg_timing.py | From the 2022–23 NSFG, when childless singles who intend to have kids expect their first (within 2 years, 2 to 5, more than 5), by sex and age band, with effective sample sizes. Source of KID_WHEN. |
speed_dating.py | Tests whether mutual attraction is the product of the two yes rates (lift ≈ 1.03). |
harness.mjs | Loads the baseline page (baseline.html, the page at commit 73d4238, before step 2) in headless Chromium and runs its own pool() for every metro, with structural assumptions switched by a scenario object. |
sensitivity.mjs | Swaps each assumption for its data-based alternative across six searches and all metros. Writes sensitivity.json. |
step2.mjs | Built the earlier two-group politics-and-faith change, checks it against the harness, and writes step2-values-joint.patch. The six-group model has since replaced it. |
| Script | What it rebuilds |
|---|---|
scripts/raw_to_json.py | Embeds the metro list (RAW: name, population, degree share, Harris share) from data/metro_raw.csv, the one source for which metros exist and their order. Edit the CSV, then run it. |
scripts/pull_acs.R | Pulls ACS tables B12002, B15001 and B11012 for every metro with tidycensus, into data/metro_acs_2023.csv and data/acs_variables_2023.csv. Needs a Census API key. |
scripts/acs_to_json.py | Turns that ACS extract into the page's ACS object: single people by age band, degree shares by age band and single parents. |
scripts/pums_singles.py | Embeds PUMS from the 2023 ACS 1-year microdata: the share of singles not living with a partner, by sex and age band, how often singles hold degrees against everyone in each age band, and, for the yearly count, the share divorced or widowed in the past year and how often singles move from another state against everyone. |
scripts/mobility_to_json.py | Embeds each metro's share of residents who moved in from outside it in the past year (MOVE) from Table B07201 in the Census summary files, for the yearly count. Writes data/metro_mobility.csv. |
scripts/acs_moe_to_json.py | Embeds ACS standard errors (ACSE) for the uncertainty range, from the Census table-based summary files. No API key needed. |
scripts/religion_to_json.py | Embeds congregation membership and the unaffiliated share (REL), which feed the non-religious share. Writes data/metro_religion.csv. |
scripts/faith_to_json.py | Embeds PRRI's share for each faith group (FAITH), used by the faith filter. Writes data/metro_faith.csv. |
scripts/housing_to_json.py | Embeds rent and pay (HOUSE) from the Census summary files. Writes data/metro_housing.csv. |
scripts/climate_to_json.py | Embeds each metro's January and July average temperature (CLIMATE) from NOAA's county data, for the climate filter. Writes data/metro_climate.csv. |
The 2019 to 2023 data is already in data/ and atlas/data.js. To rebuild it, first install the Python packages the scripts use (pinned in requirements.txt to the versions that reproduce the committed data), plus R with tidycensus for pull_acs.R and Node for the map and the page check:
python3 -m venv .venv && . .venv/bin/activate && pip install -r requirements.txt
Then:
Rscript scripts/pull_acs.R # needs a Census API key
python3 scripts/raw_to_json.py # after editing data/metro_raw.csv
python3 scripts/acs_to_json.py data/metro_acs_2023.csv data/acs_variables_2023.csv
python3 scripts/pums_singles.py csv_pus.zip acsdt5y2023-b07201.dat
PYTHONPATH=scripts python3 scripts/acs_moe_to_json.py acsdt5y2023-b12002.dat acsdt5y2023-b15001.dat
python3 scripts/religion_to_json.py 2020_USRC_Summaries.xlsx prri_county.csv list1_2023.xlsx
PYTHONPATH=scripts python3 scripts/faith_to_json.py prri_county.csv list1_2023.xlsx
python3 scripts/housing_to_json.py acsdt5y2023-b25031.dat acsdt5y2023-b25113.dat acsdt5y2023-b20004.dat
python3 scripts/mobility_to_json.py acsdt5y2023-b07201.dat
PYTHONPATH=scripts python3 scripts/climate_to_json.py climdiv-tmpccy-v1.0.0-YYYYMMDD prri_county.csv list1_2023.xlsx
npm i d3-geo topojson-client us-atlas && node scripts/build_map.mjs
node scripts/check_page.mjs # needs Playwright
Each script's header says where to download its inputs. The national summary files are large, but you only need the header, the national line (0100000US) and the lines that start with 310M, which are the metros.
The code is licensed under the Apache License 2.0. The license does not grant use of the Dating Atlas name (see below).
The data in data/ and atlas/data.js is derived from the sources listed above and stays under their terms, not this license. Census Bureau data (ACS, PUMS, population estimates, Gazetteer) is in the public domain. The PRRI county religion estimates, the 2020 U.S. Religion Census, the CES, and tonmcg's county election results belong to their publishers, so check their terms before reusing those numbers.
Dating Atlas™ is a trademark of the repository owner (compbiolover). Please don't use the name for other dating tools or services without permission.
raw_to_json.py, religion_to_json.py, faith_to_json.py; structure/ces_politics.py for the POL_* constants and NONE_SPLIT |
RAW (dem), REL, FAITH, POL_FIT, POL_OR, NONE_SPLIT, polShares(), valRaw() |
| 9 | Shares your diet | Gallup 2023, NHANES, Pew 2020 (kosher), ISPU 2022 (halal); kosher and halal scale by the local Jewish or Muslim share | none | DIET_RATE, DIET_POL, KOSHER, HALAL, dietFit() |
| 10 | Compatible on kids | ACS B11012 single parents (local); Pew 2019, 2023 and 2024 on wanting kids and dating parents; optionally, NSFG 2022–23 on when singles who want kids expect their first | acs_to_json.py; structure/nsfg_timing.py | ACS.metros[...].kw/km, WANT, PARENT_NO, KID_WHEN, whenFit(), kidsFit() |
| 11 | Mutual attraction | Speed-dating yes rates (Fisman et al. 2006), adjusted for your age, height, degree, job and kids (Antfolk 2017, Stulp 2013, NHANES, SCAL 2022, Pew 2019) | none; checked by structure/speed_dating.py | ATTRACT, AGE_GAP, HT, EDU_PEN, JOB_PEN, traitRel() |
| 12 | On the apps now | Pew 2022 (28% of singles looking), LGB singles ×1.5 (Pew 2019) | none | APPS, LGB_APPS |
scripts/build_map.mjsRebuilds the map's metro positions and state outlines (MAPXY, USMAP) from the 2023 Census Gazetteer and us-atlas. |
scripts/check_page.mjs | Not a data script: loads the page in headless Chromium and fails on a script error, a failed self-check, a missing result, a pinned number that moved, or a climate filter that lets other metros through. The deploy workflow runs it before publishing. |