FDA 510(k) Data in Python: Pull, Clean and Analyse Clearances
510(k) clearance data answers questions that matter to device companies and consultants: how many clearances in a product code per year, who the frequent applicants are, how long reviews take, and which predicates show up. All of it is public through openFDA. This walkthrough uses plain Python and pandas.
What's in the 510(k) dataset
The openFDA endpoint https://api.fda.gov/device/510k.json holds one record per 510(k) submission. Useful fields:
k_number: the submission ID (e.g. K231234)applicant: the submitting firmdevice_name: the device as described in the submissionproduct_code: three-letter FDA product codedate_received,decision_date: dates asYYYY-MM-DDstringsdecision_code/decision_description: e.g. substantially equivalentclearance_type: Traditional, Special, Abbreviatedadvisory_committee/advisory_committee_description: review panelopenfda.device_class,openfda.regulation_number: classification details, when mapped
The record also contains a contact field naming an individual. For most analysis you don't need it, and if you republish data you should drop it.
Step 1: Get the data
For the full dataset, use the bulk download rather than paging the API:
import requests, zipfile, io, json, pandas as pd
manifest = requests.get("https://api.fda.gov/download.json").json()
parts = manifest["results"]["device"]["510k"]["partitions"]
frames = []
for p in parts:
r = requests.get(p["file"])
with zipfile.ZipFile(io.BytesIO(r.content)) as z:
data = json.load(z.open(z.namelist()[0]))
frames.append(pd.json_normalize(data["results"]))
df = pd.concat(frames, ignore_index=True)
print(len(df))
For a filtered pull, the API is fine:
url = "https://api.fda.gov/device/510k.json"
params = {"search": 'product_code:"QAS"', "limit": 1000}
res = requests.get(url, params=params).json()["results"]
Step 2: Clean
df = df.drop(columns=["contact"], errors="ignore")
for col in ["date_received", "decision_date"]:
df[col] = pd.to_datetime(df[col], errors="coerce")
df["review_days"] = (df["decision_date"] - df["date_received"]).dt.days
df["year"] = df["decision_date"].dt.year
# openfda fields arrive as lists; take the first value
for col in ["openfda.device_class", "openfda.regulation_number"]:
if col in df:
df[col] = df[col].apply(lambda v: v[0] if isinstance(v, list) and v else None)
df["applicant_norm"] = (df["applicant"].str.upper()
.str.replace(r"[^\w\s]", "", regex=True)
.str.replace(r"\b(INC|LLC|LTD|CORP|CORPORATION|CO|GMBH)\b", "", regex=True)
.str.strip())
Check for odd values: a few records have missing decision dates or negative review days due to data-entry issues. Filter them out of timing analysis rather than deleting them.
Step 3: Analyse
Clearances per year for one product code:
code = "QAS"
(df[df.product_code == code]
.groupby("year").size()
.plot(kind="bar", title=f"510(k) clearances, product code {code}"))
Median review time by clearance type:
df.groupby("clearance_type")["review_days"].median().sort_values()
Top applicants in a panel:
(df[df.advisory_committee_description == "Radiology"]
.applicant_norm.value_counts().head(20))
Step 4: Add classification
The openfda.device_class field is often present, but for complete coverage join to FDA's device classification data (https://api.fda.gov/device/classification.json) on product_code. That gives device class, regulation number and whether the code is exempt from 510(k).
cls = pd.json_normalize(requests.get(
"https://api.fda.gov/device/classification.json",
params={"limit": 1000}).json()["results"])
df = df.merge(cls[["product_code", "device_class", "regulation_number"]],
on="product_code", how="left", suffixes=("", "_cls"))
(You'll need to page through classification fully; it's around 6,000+ codes. Bulk download is simpler.)
Step 5: Combine with PMA and enforcement
Higher-risk devices go through PMA, not 510(k). The PMA endpoint (device/pma.json) has a different shape: supplements, pma_number, supplement_number. If you're mapping a product code's full regulatory story, stack 510(k) and PMA decisions on product_code, then bring in device enforcement reports on the same key.
Keeping it current
The 510(k) data updates monthly. If you build dashboards, you'll want to re-pull, re-clean and spot what's new or revised. That's where most of the maintenance time goes.
The Apex US Regulatory Events Feed provides 510(k) and PMA decisions already cleaned and joined to classification, alongside enforcement reports, drug shortages and a weekly_changes table. You can read it straight into pandas with the open-source delta-sharing client:
import delta_sharing
df = delta_sharing.load_as_pandas("config.share#us_regulatory_feed.gold.device_clearances")
There are free 100-row samples at https://apex-feed.pages.dev/#samples. Pricing is at https://apex-feed.pages.dev ($50 snapshot, $39/month, $390/year). If you only need one analysis, the free script above does the job.
Source: U.S. FDA via openFDA. Apex is not affiliated with or endorsed by the U.S. Food and Drug Administration. Code is illustrative; check field names against current openFDA documentation.