NURSING HOME FACILITIES & STAFFING / PYTHON + CSV
Compare nursing-home facilities in Python
Add capacity, CMS ratings, RN staffing and turnover to a facility research list, keeping measurement periods and footnotes alongside the results.
Compare a facility research portfolio
You have CMS nursing-home identifiers and want to add facility capacity, ratings and staffing context to a research list. This example joins three facilities to the reference and prints their certified beds, overall and staffing stars, reported RN hours and total nursing turnover.
The optional example input CSV contains fictional project labels paired with real facility identifiers. The same records appear in the portfolio list in the Python code below. Only the project labels are invented; the facility names and measures come from CMS through the prepared product.
Download the product samples
These are smaller selections of the files you receive when you buy the dataset. facilities.csv contains all product fields for the three facilities. facility_measures.csv contains every one of their 21 selected measures, including missing values and footnotes. Save both files in the same working folder. The full download uses the same filenames and columns.
Code example
The code keeps CMS identifiers as text and selects two detailed measures before joining. It works in a notebook, IPython or a Python script using only the standard library.
Paths are relative to your working folder; use from pathlib import Path; print(Path.cwd()) to check it, or supply full paths.
import csv
from pathlib import Path
# Fictional project labels with real CMS facility IDs.
# The same three input records are also supplied as example_input.csv.
portfolio = [
{"project": "Durham research", "facility_id": "345001"},
{"project": "Chapel Hill research", "facility_id": "345199"},
{"project": "Cary research", "facility_id": "345445"},
]
folder = Path(".") # Folder containing the two sample files or the full download.
with (folder / "facilities.csv").open(newline="", encoding="utf-8-sig") as file:
facilities = {row["facility_id"]: row for row in csv.DictReader(file)}
with (folder / "facility_measures.csv").open(newline="", encoding="utf-8-sig") as file:
measures = {
(row["facility_id"], row["measure_id"]): row
for row in csv.DictReader(file)
if row["measure_id"] in {"reported_rn_hours", "total_nurse_turnover_pct"}
}
results = []
for item in portfolio:
facility = facilities.get(item["facility_id"])
rn = measures.get((item["facility_id"], "reported_rn_hours"), {})
turnover = measures.get((item["facility_id"], "total_nurse_turnover_pct"), {})
result = {
**item,
"facility_name": facility["facility_name"] if facility else "",
"certified_beds": facility["certified_beds"] if facility else "",
"overall_stars": facility["overall_rating"] if facility else "",
"staffing_stars": facility["staffing_rating"] if facility else "",
"rn_hours_per_resident_day": rn.get("value", ""),
"rn_status": rn.get("value_status", "facility_not_found"),
"rn_period": f"{rn.get('period_start', '')} to {rn.get('period_end', '')}",
"rn_note": rn.get("footnote_text", ""),
"turnover_percent": turnover.get("value", ""),
"turnover_status": turnover.get("value_status", "facility_not_found"),
"turnover_period": f"{turnover.get('period_start', '')} to {turnover.get('period_end', '')}",
"turnover_note": turnover.get("footnote_text", ""),
}
results.append(result)
print(f"{result['project']} | {item['facility_id']} | {result['facility_name']}")
print(f" Certified beds: {result['certified_beds'] or 'not available'}; "
f"overall stars: {result['overall_stars'] or 'not available'}; "
f"staffing stars: {result['staffing_stars'] or 'not available'}")
print(f" RN hours/resident/day: {result['rn_hours_per_resident_day'] or 'not available'} "
f"({result['rn_period']})")
print(f" Total nursing turnover: {result['turnover_percent'] or 'not available'}% "
f"({result['turnover_period']})")
for note in (result["rn_note"], result["turnover_note"]):
if note:
print(f" CMS note: {note}")
Analysis of the output
Durham research | 345001 | Hillcrest Convalescent Center
Certified beds: 86; overall stars: 5; staffing stars: 5
RN hours/resident/day: 1.29975 (2026-01-01 to 2026-03-31)
Total nursing turnover: 31.4% (2025-01-01 to 2025-12-31)
Chapel Hill research | 345199 | Carol Woods
Certified beds: 30; overall stars: 5; staffing stars: 5
RN hours/resident/day: 1.79061 (2026-01-01 to 2026-03-31)
Total nursing turnover: 35.1% (2025-01-01 to 2025-12-31)
Cary research | 345445 | Glenaire
Certified beds: 71; overall stars: 5; staffing stars: 5
RN hours/resident/day: 0.88077 (2026-01-01 to 2026-03-31)
Total nursing turnover: 36.8% (2025-01-01 to 2025-12-31)
The three records now have facility context ready for a portfolio review. Certified beds describe federally certified capacity in the source; they are not a count of available beds today. Overall stars and staffing stars are separate CMS ratings. All three facilities happen to have five stars in both columns in this edition, while their published RN staffing and turnover measures differ.
Hillcrest’s reported RN value is 1.29975 hours per resident day, compared with 1.79061 at Carol Woods and 0.88077 at Glenaire. These ratios describe reported RN hours relative to resident days during January–March 2026. They are not nurse headcounts or a description of any one shift. They are unadjusted measures; the full file also supplies selected case-mix-adjusted staffing ratios for comparisons that need that context.
Total nursing turnover is 31.4%, 35.1% and 36.8%, respectively, for calendar year 2025. It covers a different employee group and period from the RN staffing ratios. Keeping the periods beside the values helps you describe what you compared rather than calling everything “October staffing.” These are research examples, not recommendations of facilities.
For your own portfolio, a missing measure remains blank with not_available status. The code prints CMS’s explanation when a staffing or turnover footnote exists. Check footnotes even when the value is published: a numerical rating can carry a qualification. Absence from this edition is not proof that a facility never existed.
Using the full dataset
Extract the purchased ZIP and point folder to that directory. Replace portfolio with your records, or read your CSV with csv.DictReader using project and facility_id columns. Preserve six-character IDs, including leading zeros and letters.
The complete reference contains 14,690 facilities and 308,490 detailed measure rows. Select measure_id before joining for one result per facility. Joining all measures creates 21 rows per facility; do not sum repeated bed counts or count those rows as separate facilities.
The SSA county code in the facility reference is not Census county FIPS, and a postal ZIP is not a Census ZCTA. No county or NPI crosswalk is included. Our hospital reference supports a related facility-research task, but its ratings use different definitions.
Sources and versions
This fixed edition uses CMS Provider Information and data collection intervals, released October 8, 2026. CMS metadata was modified September 30; the files retain a September 1 processing date. ACD captured these inputs October 18 UTC and packaged this release on 2026-10-18 UTC.
Staffing levels cover the first quarter of 2026 and turnover covers 2025. Ratings combine their own underlying periods; the product does not assign them the staffing quarter. The CMS Five-Star methodology explains the distinct rating domains and limits of comparisons, including state-relative inspection ratings. Original CMS data are available free.
Read the source and preparation record for scope, accounting and version details.
Example checked October 18, 2026 against both these samples and the complete purchased files. Source and processing records, sample checksums · Expected result CSV. Independent preparation; no agency endorsement.