Drugs and formularies

Medicare Part D Prescriber PUF (prescribing by provider × drug × year)

drug_silver.fact_part_d_prescriber·live·silver·annual·Medicare Part D Prescriber Utilization
Rows
103,105,990
Columns
38
History
2020 (effective_period 2020-01-01 to 2020-12-31) to 2023 (effective_period 2023-01-01 to 2023-12-31)
Card
yesgrain, traps, filters
Typed tools
1

What it is

  1. 1CMS Medicare Part D "Prescriber by Drug" public file (MUP_DPR) silver-layer fact: one row per prescriber per drug per year giving the volume and total gross drug cost of that drug's Part D claims written by that prescriber.
  2. 2Used as the prescriber-level drug-utilization and drug-spend substrate (who prescribes what, how much it costs Medicare Part D, and how that shifts year over year), and to link prescribers/specialties to specific brand/generic drugs.

Grain

KeyGrain
prescriber_npibrand_namegeneric_namedata_year
  1. 1One row per (prescriber NPI x brand_name x generic_name x data_year).
  2. 2VERIFIED: 103,105,990 rows = 103,105,990 distinct values of (prescriber_npi, brand_name, generic_name, data_year), both with and without the is_current filter.
  3. 3BOTH brand_name and generic_name are required: the same (npi, brand, year) appears with multiple generic strings (192,828 such groups) and the same (npi, generic, year) appears with multiple brands (4,687,328 such groups), so neither alone is unique.
  4. 4Covers 1,339,949 distinct prescribers across 4 data years (2020-2023), ~25-27M rows per year.

How to read it

  1. 1Example: total Medicare Part D gross drug cost and claim volume for a drug by specialty/state in a year.
  2. 2SELECT prescriber_state, sum(total_drug_cost_usd) AS gross_cost, sum(total_claims) AS claims, count(DISTINCT prescriber_npi) AS prescribers FROM fact_part_d_prescriber WHERE is_current AND data_year=2023 AND brand_name='Eliquis' AND prescriber_type='Cardiology' GROUP BY prescriber_state ORDER BY gross_cost DESC.
  3. 3Answers which states' cardiologists drive the most Part D spend on Eliquis and at what volume.
  4. 4Sum claims/cost, not beneficiaries (suppressed in 57% of rows).

Traps

  1. 1The grain needs BOTH brand_name and generic_name -- it is NOT (npi, generic, year) [only 97.6M distinct] nor (npi, brand, year) [only 102.9M distinct]. Only (npi, brand, generic, year) hits 103,105,990 = exact row count. Dropping either column double-counts a prescriber's spend.
  2. 2Claims floor = 11, and suppressed combos are DELETED not flagged: min(total_claims)=11 and 0 rows have <11. A prescriber-drug pair absent from the table means it was either never prescribed OR had <=10 claims -- you cannot tell which, and absence must NOT be read as zero. This biases per-prescriber and long-tail-drug totals downward.
  3. 3Two independent suppression systems on the >=65 block: ge65_suppression_flag (non-null => ALL five ge65_* metrics NULL: '*'=<=10 benes, '#'=count 11-110) AND a separate ge65_bene_suppression_flag that NULLs only ge65_total_beneficiaries. NULL in any ge65 metric = redacted, never zero. The non-suppressed total_* metrics (claims/30day_fills/day_supply/drug_cost) are ALWAYS populated.
  4. 4total_beneficiaries (the all-ages bene count) is NULL in 57% of rows due to 1-10-beneficiary privacy suppression -- you cannot reliably sum beneficiary counts, only claims/fills/cost. Beneficiary-based denominators are heavily missing.
  5. 5total_drug_cost_usd is GROSS pharmacy cost (ingredient + dispensing fee + tax), NOT the Medicare-paid amount and NOT net of manufacturer rebates -- do not treat it as Medicare program net spend or as a negotiated/contracted price.
  6. 6Four columns (data_year, observed_period, effective_period_start, effective_period_end) all encode the same single calendar year; one source file per year. is_current is 100% true and asserted_at is ingestion date (2026-05), so none of the snapshot/provenance columns subdivide the grain. drug_source_version_id is entirely NULL and vintage_tag is the constant 'none'.
  7. 7prescriber_type is per-file/per-year derived (ranked Claim-Specialty > NPPES-Specialty > NPPES-Taxonomy via prescriber_type_source), so the SAME NPI can carry different specialties across years -- do not treat prescriber_type as a stable attribute of the NPI.

Columns (38)

ColumnTypeMeaning
prescriber_npistring10-digit National Provider Identifier of the prescriber. Always present. 1,339,949 distinct. 17 rows are organization NPIs (prescriber_first_name NULL). Part of the grain key.
brand_namestringDrug brand/trade name as labeled by CMS (e.g. 'Eliquis'). 3,516 distinct. For generically-dispensed drugs CMS sets brand_name = generic_name (70,791,854 rows = 69%). Part of the grain key.
generic_namestringActive-ingredient / generic name (e.g. 'Apixaban' for Eliquis). 1,947 distinct. Part of the grain key. A single brand can map to multiple generic strings and vice-versa, which is why both are in the key.
data_yearintInteger calendar year the prescribing activity occurred (2020, 2021, 2022, 2023). Part of the grain key. Maps 1:1 to one source file per year.
prescriber_last_org_namestringPrescriber last name, or organization name for org NPIs. Descriptive only; not part of the key.
prescriber_first_namestringPrescriber first name. NULL for the 17 organization-NPI rows. Descriptive only.
prescriber_citystringCity of the prescriber's primary practice location (from NPPES). Descriptive only.
prescriber_statestring2-letter state/territory of practice. Always present. 63 distinct values including territories (PR=1.34M rows, VI, GU) and special codes: 'ZZ' (foreign, 4,359), 'XX' (unknown, 2,479), and military 'AA'/'AE'/'AP'. Top states CA/FL/TX/NY/PA.
prescriber_state_fipsstringNumeric FIPS state code matching prescriber_state. 61 distinct (vs 63 states) because some territory/special codes lack or share a numeric FIPS.
prescriber_typestringPrescriber specialty (e.g. 'Family Practice', 'Internal Medicine', 'Nurse Practitioner'). NULL in only 71 rows. Derived per-file, so the same NPI can show different specialties across years.
prescriber_type_sourcestringProvenance of prescriber_type: 'Claim-Specialty' (90%, assigned from claims), 'NPPES-Specialty' (8%), or 'NPPES-Taxonomy' (2%) fallback when claim specialty is unavailable.
total_claimsintTotal Part D claim count (incl. refills) for this prescriber-drug-year. ALWAYS populated (0 nulls). CMS suppression floor: minimum value is 11 -- any combo with <=10 claims is omitted from the file entirely (NOT flagged), so absence != zero.
total_30day_fillsdoubleClaims normalized to 30-day equivalents (a 90-day script counts ~3). Double. Always >= total_claims (0 violations observed). Always populated.
total_day_supplyintSum of days-supply across all claims for this prescriber-drug-year. Always populated.
total_drug_cost_usddoubleTotal GROSS drug cost in USD (ingredient cost + dispensing fee + sales tax = amount paid to pharmacy; NOT Medicare/patient-paid, NOT net of rebates). Always populated. Range $0 to $81.78M, median ~$668, mean ~$6,894. Core COST column.
total_beneficiariesintDistinct Part D beneficiaries who received the drug from this prescriber. NULL in 58,606,644 rows (57%) -- CMS SUPPRESSES when 1-10 beneficiaries. NULL means 'suppressed small count', not zero. Main suppressed column on the row.
ge65_total_claimsintSubset of total_claims for beneficiaries aged >=65. NULL whenever ge65_suppression_flag is non-null (46M rows). Always <= total_claims (0 violations).
ge65_total_30day_fillsdouble30-day-equivalent fills for the >=65 subgroup. NULL when ge65_suppression_flag is set.
ge65_total_drug_cost_usddoubleTotal gross drug cost (USD) for the >=65 subgroup. NULL when ge65_suppression_flag is set.
ge65_total_day_supplyintDay supply for the >=65 subgroup. NULL when ge65_suppression_flag is set.
ge65_total_beneficiariesintDistinct >=65 beneficiaries. NULL whenever EITHER ge65_suppression_flag OR ge65_bene_suppression_flag is set; populated in only 11,147,609 rows.
ge65_suppression_flagstringControls the WHOLE >=65 metric block. NULL=metrics shown (57.1M); '*'=group <=10 benes, entire >=65 block redacted (13.0M); '#'=counts 11-110, >=65 metrics redacted (33.0M). When non-null, all five ge65_* fields are NULL.
ge65_bene_suppression_flagstringSeparately gates ONLY ge65_total_beneficiaries. NULL=shown (11.1M); '*'=1-10 benes suppressed (62.8M); '#'=11-110 suppressed (29.2M). Can suppress the >=65 bene count even when the rest of the >=65 block is visible.
observed_perioddateDate the activity was observed = January 1 of data_year. Redundant with data_year (one of four temporal columns encoding the same year).
asserted_atdateVlada/CMS ingestion (assertion) date, NOT data time: 2026-05-16 for DY2023, 2026-05-19 for DY2020-2022. When this batch was loaded.
effective_period_startdateStart of the reporting period = Jan 1 of data_year (2020/2021/2022/2023-01-01). Profiler's 'time column'; same year as data_year.
effective_period_enddateEnd of reporting period = Dec 31 of data_year. With start, defines the full calendar-year window.
cms_vintage_sequenceintVintage/version sequence of the CMS release. Constant = 1 (only one vintage loaded; no restated versions present).
parse_revisionintParser revision number. Constant = 1.
parser_artifact_hashstringSHA-256 of the parser artifact/code that produced the rows. 3 distinct values (~one parser run per yearly batch). Provenance.
source_urlstringPublic CMS download URL of the source CSV (e.g. data.cms.gov/.../MUP_DPR_RY24_P04_V10_DY22_NPIBN.csv). One distinct URL per data_year.
sha256_content_hashstringSHA-256 of the raw source file content. One distinct value per data_year; pins the exact file the rows came from.
source_idstringLogical source identifier, constant 'medicare_part_d_prescriber_by_drug' for every row.
is_currentbooleanSnapshot flag for the bitemporal model. 100% TRUE here -- NO superseded rows, so it never disambiguates the grain today, but filter is_current=true defensively for future restatements.
bronze_object_keystringS3 key of the bronze (raw landed) object. 4 distinct values (one per year). Lineage to the bronze layer.
s3_version_idstringS3 object version id of the bronze object. Lineage pin to the exact stored bytes.
vintage_tagstringHuman vintage label. Constant literal string 'none' for all rows (no special vintage tagging in use).
drug_source_version_idstringDrug-source version identifier. 100% NULL in this table -- unused column.

Query it

describe_data_source(source_fqn="drug_silver.fact_part_d_prescriber")     # the card, live
query_substrate(sql="SELECT * FROM drug_silver.fact_part_d_prescriber WHERE prescriber_npi = '<value>' AND brand_name = '<value>' AND generic_name = '<value>' AND data_year = '<value>' LIMIT 100")
ItemValue
Typed tools that read itexcluded_prescriber_payment_screen
Identity keyprescriber_npi
Joinhow_to_join(source_fqn="drug_silver.fact_part_d_prescriber")
Raw doorAthena (read-only via query_substrate): drug_silver.fact_part_d_prescriber
Company namesprescriber_last_org_name resolve to a permanent Vlada ID: resolve_entity(text=<name>); the company pages hold owners and holdings

This page as markdown: /docs-md/data/drug_silver_fact_part_d_prescriber.md · every page in one file: /llms-full.txt