Skill Tracer
Skills › Scientific Agent Skills › imaging-data-commons › clinical_data_guide.md

clinical_data_guide.md

336 lines · skills/imaging-data-commons/references/clinical_data_guide.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Clinical Data Guide for IDC
2
3**Tested with:** idc-index 0.12.5 (IDC data version v24)
4
5Clinical data (demographics, diagnoses, therapies, lab tests, staging) accompanies many IDC imaging collections. This guide covers how to discover, access, and integrate clinical data with imaging data using `idc-index`.
6

Clinical Data Guide for IDC

Tested with: idc-index 0.12.5 (IDC data version v24)

Clinical data (demographics, diagnoses, therapies, lab tests, staging) accompanies many IDC imaging collections. This guide covers how to discover, access, and integrate clinical data with imaging data using idc-index.

Raw lines 1 to 6
7## When to Use This Guide
8
9Use this guide when you need to:
10- Find what clinical metadata is available for a collection
11- Filter patients by clinical criteria (e.g., cancer stage, treatment history)
12- Join clinical attributes with imaging data for cohort selection
13- Understand and decode coded values in clinical tables
15For basic clinical data access, see the "Clinical Data Access" section in the main SKILL.md. This guide provides detailed workflows and advanced patterns.

When to Use This Guide

Use this guide when you need to:

  • Find what clinical metadata is available for a collection
  • Filter patients by clinical criteria (e.g., cancer stage, treatment history)
  • Join clinical attributes with imaging data for cohort selection
  • Understand and decode coded values in clinical tables

For basic clinical data access, see the "Clinical Data Access" section in the main SKILL.md. This guide provides detailed workflows and advanced patterns.

Raw lines 7 to 16
17## Prerequisites
19Needs `idc-index` installed — run `python scripts/check_version.py`, which reports the installed
20version and prints the install command for the interpreter you are running.
22No BigQuery credentials are required. `fetch_index("clinical_index")` downloads the
23dictionary and the release-specific clinical Parquet tables from public storage; network
24access is needed on first fetch.

Prerequisites

Needs idc-index installed — run python scripts/check_version.py, which reports the installed version and prints the install command for the interpreter you are running.

No BigQuery credentials are required. fetch_index("clinical_index") downloads the dictionary and the release-specific clinical Parquet tables from public storage; network access is needed on first fetch.

Raw lines 17 to 25
Runs code python scripts/check_version.py :19
26## Understanding Clinical Data in IDC

Understanding Clinical Data in IDC

Raw lines 26 to 27
28### What is Clinical Data?
30Clinical data refers to non-imaging information that accompanies medical images:
31- Patient demographics (age, sex, race)
32- Clinical history (diagnoses, surgeries, therapies)
33- Lab tests and pathology results
34- Cancer staging (clinical and pathological)
35- Treatment outcomes

What is Clinical Data?

Clinical data refers to non-imaging information that accompanies medical images:

  • Patient demographics (age, sex, race)
  • Clinical history (diagnoses, surgeries, therapies)
  • Lab tests and pathology results
  • Cancer staging (clinical and pathological)
  • Treatment outcomes
Raw lines 28 to 36
37### Data Organization
39Clinical data in IDC comes from collection-specific spreadsheets provided by data submitters. IDC parses these into queryable tables accessible via `idc-index`.
41**Important characteristics:**
42- Clinical data is **not harmonized** across collections (terms and formats vary)
43- Not all collections have clinical data (check availability first)
44- All data is **anonymized** - `dicom_patient_id` links to imaging

Data Organization

Clinical data in IDC comes from collection-specific spreadsheets provided by data submitters. IDC parses these into queryable tables accessible via idc-index.

Important characteristics:

  • Clinical data is not harmonized across collections (terms and formats vary)
  • Not all collections have clinical data (check availability first)
  • All data is anonymized - dicom_patient_id links to imaging
Raw lines 37 to 45
46### The clinical_index Table
48The `clinical_index` serves as a dictionary/catalog of all available clinical data:
50| Column | Purpose | Use For |
51|--------|---------|---------|
52| `collection_id` | Collection identifier | Filtering by collection |
53| `table_name` | Full BigQuery table reference | BigQuery queries (if needed) |
54| `short_table_name` | Short name | `get_clinical_table()` method |
55| `column` | Column name in table | Selecting data columns |
56| `column_label` | Human-readable description | Searching for concepts |
57| `values` | Observed attribute values for the column | Interpreting coded values |

The clinical_index Table

The clinical_index serves as a dictionary/catalog of all available clinical data:

ColumnPurposeUse For
collection_idCollection identifierFiltering by collection
table_nameFull BigQuery table referenceBigQuery queries (if needed)
short_table_nameShort nameget_clinical_table() method
columnColumn name in tableSelecting data columns
column_labelHuman-readable descriptionSearching for concepts
valuesObserved attribute values for the columnInterpreting coded values
Raw lines 46 to 58
59### The `values` Column
61The `values` column contains an array of observed attribute values for the column defined in the `column` field. Each entry has:
62- **option_code**: The actual value observed in that column
63- **option_description**: Human-readable description of that value (from data dictionary if available, otherwise `None`)
65For ACRIN collections, value descriptions come from provided data dictionaries. For other collections, they are derived from inspection of the actual data values.
67**Note:** For columns with >20 unique values, the `values` array is left empty (`[]`) for simplicity.

The values Column

The values column contains an array of observed attribute values for the column defined in the column field. Each entry has:

  • option_code: The actual value observed in that column
  • option_description: Human-readable description of that value (from data dictionary if available, otherwise None)

For ACRIN collections, value descriptions come from provided data dictionaries. For other collections, they are derived from inspection of the actual data values.

Note: For columns with >20 unique values, the values array is left empty ([]) for simplicity.

Raw lines 59 to 68
69## Core Workflow

Core Workflow

Raw lines 69 to 70
71### Step 1: Fetch Clinical Index
73```python
74from idc_index import IDCClient
76client = IDCClient()
77client.fetch_index('clinical_index')
79# View available columns
80print(client.clinical_index.columns.tolist())
81```

Step 1: Fetch Clinical Index

from idc_index import IDCClient

client = IDCClient()
client.fetch_index('clinical_index')

# View available columns
print(client.clinical_index.columns.tolist())
Raw lines 71 to 82
83### Step 2: Discover Available Clinical Data
85```python
86# List all collections with clinical data
87collections_with_clinical = client.clinical_index["collection_id"].unique().tolist()
88print(f"{len(collections_with_clinical)} collections have clinical data")
90# Find clinical attributes for a specific collection
91nlst_columns = client.clinical_index[client.clinical_index['collection_id']=='nlst']
92nlst_columns[['short_table_name', 'column', 'column_label', 'values']]
93```

Step 2: Discover Available Clinical Data

# List all collections with clinical data
collections_with_clinical = client.clinical_index["collection_id"].unique().tolist()
print(f"{len(collections_with_clinical)} collections have clinical data")

# Find clinical attributes for a specific collection
nlst_columns = client.clinical_index[client.clinical_index['collection_id']=='nlst']
nlst_columns[['short_table_name', 'column', 'column_label', 'values']]
Raw lines 83 to 94
95### Step 3: Search for Specific Attributes
97```python
98# Search by keyword in column_label (case-insensitive)
99stage_attrs = client.clinical_index[
100 client.clinical_index["column_label"].str.contains("[Ss]tage", na=False)
102stage_attrs[["collection_id", "short_table_name", "column", "column_label"]]
103```

Step 3: Search for Specific Attributes

# Search by keyword in column_label (case-insensitive)
stage_attrs = client.clinical_index[
    client.clinical_index["column_label"].str.contains("[Ss]tage", na=False)
]
stage_attrs[["collection_id", "short_table_name", "column", "column_label"]]
Raw lines 95 to 104
105### Step 4: Load Clinical Table
107```python
108# Load table using short_table_name
109nlst_canc_df = client.get_clinical_table("nlst_canc")
111# Examine structure
112print(f"Rows: {len(nlst_canc_df)}, Columns: {len(nlst_canc_df.columns)}")
113nlst_canc_df.head()
114```

Step 4: Load Clinical Table

# Load table using short_table_name
nlst_canc_df = client.get_clinical_table("nlst_canc")

# Examine structure
print(f"Rows: {len(nlst_canc_df)}, Columns: {len(nlst_canc_df.columns)}")
nlst_canc_df.head()
Raw lines 105 to 115
116### Step 5: Map Coded Values to Descriptions
118Many clinical attributes use coded values. The `values` column in `clinical_index` contains an array of observed values with their descriptions (when available).
120```python
121# Get the clinical_index rows for NLST
122nlst_clinical_columns = client.clinical_index[client.clinical_index['collection_id']=='nlst']
124# Get observed values for a specific column
125# Filter to the row for 'clinical_stag' and extract the values array
126clinical_stag_values = nlst_clinical_columns[
127 nlst_clinical_columns['column']=='clinical_stag'
128]['values'].values[0]
130# View the observed values and their descriptions
131print(clinical_stag_values)
132# Output: array([{'option_code': '.M', 'option_description': 'Missing'},
133# {'option_code': '110', 'option_description': 'Stage IA'},
134# {'option_code': '120', 'option_description': 'Stage IB'}, ...])
136# Create mapping dictionary from codes to descriptions
137mapping_dict = {item['option_code']: item['option_description'] for item in clinical_stag_values}
139# Apply to DataFrame - convert column to string first for consistent matching
140nlst_canc_df['clinical_stag_meaning'] = nlst_canc_df['clinical_stag'].astype(str).map(mapping_dict)
141```

Step 5: Map Coded Values to Descriptions

Many clinical attributes use coded values. The values column in clinical_index contains an array of observed values with their descriptions (when available).

# Get the clinical_index rows for NLST
nlst_clinical_columns = client.clinical_index[client.clinical_index['collection_id']=='nlst']

# Get observed values for a specific column
# Filter to the row for 'clinical_stag' and extract the values array
clinical_stag_values = nlst_clinical_columns[
    nlst_clinical_columns['column']=='clinical_stag'
]['values'].values[0]

# View the observed values and their descriptions
print(clinical_stag_values)
# Output: array([{'option_code': '.M', 'option_description': 'Missing'},
#                {'option_code': '110', 'option_description': 'Stage IA'},
#                {'option_code': '120', 'option_description': 'Stage IB'}, ...])

# Create mapping dictionary from codes to descriptions
mapping_dict = {item['option_code']: item['option_description'] for item in clinical_stag_values}

# Apply to DataFrame - convert column to string first for consistent matching
nlst_canc_df['clinical_stag_meaning'] = nlst_canc_df['clinical_stag'].astype(str).map(mapping_dict)
Raw lines 116 to 142
143### Step 6: Join with Imaging Data
145The `dicom_patient_id` column links clinical data to imaging. It matches the `PatientID` column in the imaging index.
147```python
148# Pandas merge approach
149import pandas as pd
151# Get NLST CT imaging data
152nlst_imaging = client.index[(client.index['collection_id']=='nlst') & (client.index['Modality']=='CT')]
154# Join with clinical data
155merged = pd.merge(
156 nlst_imaging[['PatientID', 'StudyInstanceUID']].drop_duplicates(),
157 nlst_canc_df[['dicom_patient_id', 'clinical_stag', 'clinical_stag_meaning']],
158 left_on='PatientID',
159 right_on='dicom_patient_id',
160 how='inner'
162```
164```python
165# SQL join approach
166# Clinical tables loaded via get_clinical_table() are not automatically
167# registered in DuckDB. Register the DataFrame manually before joining.
168nlst_canc_df = client.get_clinical_table("nlst_canc")
169import duckdb
170connection = duckdb.connect()
171connection.register("index", client.index)
172connection.register("nlst_canc", nlst_canc_df)
174query = """
175SELECT
176 index.PatientID,
177 index.StudyInstanceUID,
178 index.Modality,
179 nlst_canc.clinical_stag
180FROM index
181JOIN nlst_canc ON index.PatientID = nlst_canc.dicom_patient_id
182WHERE index.collection_id = 'nlst' AND index.Modality = 'CT'
183"""
184results = connection.execute(query).df()
185```

Step 6: Join with Imaging Data

The dicom_patient_id column links clinical data to imaging. It matches the PatientID column in the imaging index.

# Pandas merge approach
import pandas as pd

# Get NLST CT imaging data
nlst_imaging = client.index[(client.index['collection_id']=='nlst') & (client.index['Modality']=='CT')]

# Join with clinical data
merged = pd.merge(
    nlst_imaging[['PatientID', 'StudyInstanceUID']].drop_duplicates(),
    nlst_canc_df[['dicom_patient_id', 'clinical_stag', 'clinical_stag_meaning']],
    left_on='PatientID',
    right_on='dicom_patient_id',
    how='inner'
)
# SQL join approach
# Clinical tables loaded via get_clinical_table() are not automatically
# registered in DuckDB. Register the DataFrame manually before joining.
nlst_canc_df = client.get_clinical_table("nlst_canc")
import duckdb
connection = duckdb.connect()
connection.register("index", client.index)
connection.register("nlst_canc", nlst_canc_df)

query = """
SELECT
  index.PatientID,
  index.StudyInstanceUID,
  index.Modality,
  nlst_canc.clinical_stag
FROM index
JOIN nlst_canc ON index.PatientID = nlst_canc.dicom_patient_id
WHERE index.collection_id = 'nlst' AND index.Modality = 'CT'
"""
results = connection.execute(query).df()
Raw lines 143 to 186
187## Common Use Cases

Common Use Cases

Raw lines 187 to 188
189### Use Case 1: Select Patients by Cancer Stage
191```python
192from idc_index import IDCClient
193import pandas as pd
195client = IDCClient()
196client.fetch_index('clinical_index')
198# Load clinical table
199nlst_canc = client.get_clinical_table("nlst_canc")
201# Select Stage IV patients (code '400')
202stage_iv_patients = nlst_canc[nlst_canc['clinical_stag'] == '400']['dicom_patient_id']
204# Get CT imaging studies for these patients
205stage_iv_studies = pd.merge(
206 client.index[(client.index['collection_id']=='nlst') & (client.index['Modality']=='CT')],
207 stage_iv_patients,
208 left_on='PatientID',
209 right_on='dicom_patient_id',
210 how='inner'
211)['StudyInstanceUID'].drop_duplicates()
213print(f"Found {len(stage_iv_studies)} CT studies for Stage IV patients")
214```

Use Case 1: Select Patients by Cancer Stage

from idc_index import IDCClient
import pandas as pd

client = IDCClient()
client.fetch_index('clinical_index')

# Load clinical table
nlst_canc = client.get_clinical_table("nlst_canc")

# Select Stage IV patients (code '400')
stage_iv_patients = nlst_canc[nlst_canc['clinical_stag'] == '400']['dicom_patient_id']

# Get CT imaging studies for these patients
stage_iv_studies = pd.merge(
    client.index[(client.index['collection_id']=='nlst') & (client.index['Modality']=='CT')],
    stage_iv_patients,
    left_on='PatientID',
    right_on='dicom_patient_id',
    how='inner'
)['StudyInstanceUID'].drop_duplicates()

print(f"Found {len(stage_iv_studies)} CT studies for Stage IV patients")
Raw lines 189 to 215
216### Use Case 2: Find Collections with Specific Clinical Attributes
218```python
219# Find collections with chemotherapy information
220chemo_collections = client.clinical_index[
221 client.clinical_index["column_label"].str.contains("[Cc]hemotherapy", na=False)
222]["collection_id"].unique()
224print(f"Collections with chemotherapy data: {list(chemo_collections)}")
225```

Use Case 2: Find Collections with Specific Clinical Attributes

# Find collections with chemotherapy information
chemo_collections = client.clinical_index[
    client.clinical_index["column_label"].str.contains("[Cc]hemotherapy", na=False)
]["collection_id"].unique()

print(f"Collections with chemotherapy data: {list(chemo_collections)}")
Raw lines 216 to 226
227### Use Case 3: Examine Observed Values for a Clinical Attribute
229```python
230# Find what values have been observed for a specific attribute
231chemotherapy_rows = client.clinical_index[
232 (client.clinical_index["collection_id"] == "hcc_tace_seg") &
233 (client.clinical_index["column"] == "chemotherapy")
236# Get the observed values array
237values_list = chemotherapy_rows["values"].tolist()
238print(values_list)
239# Output: [[{'option_code': 'Cisplastin', 'option_description': None},
240# {'option_code': 'Cisplatin, Mitomycin-C', 'option_description': None}, ...]]
241```

Use Case 3: Examine Observed Values for a Clinical Attribute

# Find what values have been observed for a specific attribute
chemotherapy_rows = client.clinical_index[
    (client.clinical_index["collection_id"] == "hcc_tace_seg") &
    (client.clinical_index["column"] == "chemotherapy")
]

# Get the observed values array
values_list = chemotherapy_rows["values"].tolist()
print(values_list)
# Output: [[{'option_code': 'Cisplastin', 'option_description': None},
#           {'option_code': 'Cisplatin, Mitomycin-C', 'option_description': None}, ...]]
Raw lines 227 to 242
243### Use Case 4: Generate Viewer URLs for Selected Patients
245```python
246import random
248# Get studies for a sample Stage IV patient
249sample_patient = stage_iv_patients.iloc[0]
250studies = client.index[
251 (client.index['collection_id'] == 'nlst') &
252 (client.index['PatientID'] == sample_patient)
253]['StudyInstanceUID'].unique()
255# Generate viewer URL
256if len(studies) > 0:
257 viewer_url = client.get_viewer_URL(studyInstanceUID=studies[0])
258 print(viewer_url)
259```

Use Case 4: Generate Viewer URLs for Selected Patients

import random

# Get studies for a sample Stage IV patient
sample_patient = stage_iv_patients.iloc[0]
studies = client.index[
    (client.index['collection_id'] == 'nlst') &
    (client.index['PatientID'] == sample_patient)
]['StudyInstanceUID'].unique()

# Generate viewer URL
if len(studies) > 0:
    viewer_url = client.get_viewer_URL(studyInstanceUID=studies[0])
    print(viewer_url)
Raw lines 243 to 260
261## Key Concepts
263### column vs column_label
265- **column**: Use for selecting data from tables (programmatic access)
266- **column_label**: Use for searching/understanding what data means (human-readable)
268Some collections (like `c4kc_kits`) have identical column and column_label. Others (like ACRIN collections) have cryptic column names but descriptive labels.

column vs column_label

  • column: Use for selecting data from tables (programmatic access)
  • column_label: Use for searching/understanding what data means (human-readable)

Some collections (like c4kc_kits) have identical column and column_label. Others (like ACRIN collections) have cryptic column names but descriptive labels.

Raw lines 263 to 269
270### option_code vs option_description
272The `values` array contains observed attribute values:
273- **option_code**: The actual value observed in the column (what you filter on)
274- **option_description**: Human-readable description (from data dictionary if available, otherwise `None`)

option_code vs option_description

The values array contains observed attribute values:

  • option_code: The actual value observed in the column (what you filter on)
  • option_description: Human-readable description (from data dictionary if available, otherwise None)
Raw lines 270 to 275
276### dicom_patient_id
278Every clinical table includes `dicom_patient_id`, which matches the `PatientID` column in the imaging index. This is the key for joining clinical and imaging data.

dicom_patient_id

Every clinical table includes dicom_patient_id, which matches the PatientID column in the imaging index. This is the key for joining clinical and imaging data.

Raw lines 276 to 279
280## Troubleshooting

Troubleshooting

Raw lines 280 to 281
282### Issue: Clinical table not found
284**Cause:** Using wrong table name or table doesn't exist for collection
286**Solution:** Query clinical_index first to find available tables:
287```python
288client.clinical_index[client.clinical_index['collection_id']=='your_collection']['short_table_name'].unique()
289```

Issue: Clinical table not found

Cause: Using wrong table name or table doesn't exist for collection

Solution: Query clinical_index first to find available tables:

client.clinical_index[client.clinical_index['collection_id']=='your_collection']['short_table_name'].unique()
Raw lines 282 to 290
291### Issue: Empty values array
293**Cause:** The `values` array is left empty when a column has >20 unique values
295**Solution:** Load the clinical table and examine unique values directly:
296```python
297clinical_df = client.get_clinical_table("table_name")
298clinical_df['column_name'].unique()
299```

Issue: Empty values array

Cause: The values array is left empty when a column has >20 unique values

Solution: Load the clinical table and examine unique values directly:

clinical_df = client.get_clinical_table("table_name")
clinical_df['column_name'].unique()
Raw lines 291 to 300
301### Issue: Coded values not in mapping
303**Cause:** Some values may be missing from the dictionary (e.g., empty strings, special codes like `.M` for missing)
305**Solution:** Handle unmapped values gracefully:
306```python
307df['meaning'] = df['code'].astype(str).map(mapping_dict).fillna('Unknown/Missing')
308```

Issue: Coded values not in mapping

Cause: Some values may be missing from the dictionary (e.g., empty strings, special codes like .M for missing)

Solution: Handle unmapped values gracefully:

df['meaning'] = df['code'].astype(str).map(mapping_dict).fillna('Unknown/Missing')
Raw lines 301 to 309
310### Issue: No matching patients when joining
312**Cause:** Clinical data may include patients without images, or vice versa
314**Solution:** Verify patient overlap before joining:
315```python
316imaging_patients = set(client.index[client.index['collection_id']=='nlst']['PatientID'].unique())
317clinical_patients = set(clinical_df['dicom_patient_id'].unique())
318overlap = imaging_patients & clinical_patients
319print(f"Patients with both imaging and clinical data: {len(overlap)}")
320```

Issue: No matching patients when joining

Cause: Clinical data may include patients without images, or vice versa

Solution: Verify patient overlap before joining:

imaging_patients = set(client.index[client.index['collection_id']=='nlst']['PatientID'].unique())
clinical_patients = set(clinical_df['dicom_patient_id'].unique())
overlap = imaging_patients & clinical_patients
print(f"Patients with both imaging and clinical data: {len(overlap)}")
Raw lines 310 to 321
322## Resources
324**IDC Documentation:**
325- [Clinical data organization](https://learn.canceridc.dev/data/organization-of-data/clinical) - How clinical data is organized in IDC
326- [Clinical data dashboard](https://datastudio.google.com/u/0/reporting/04cf5976-4ea0-4fee-a749-8bfd162f2e87/page/p_s7mk6eybqc) - Visual summary of available clinical data
327- [idc-index clinical_index documentation](https://idc-index.readthedocs.io/en/latest/indices_reference.html#clinical-index)
329**Related Guides:**
330- `bigquery_guide.md` - Advanced clinical queries via BigQuery
331- Main SKILL.md - Core IDC workflows
333**IDC Tutorials:**
334- [clinical_data_intro.ipynb](https://github.com/ImagingDataCommons/IDC-Tutorials/blob/master/notebooks/advanced_topics/clinical_data_intro.ipynb)
335- [exploring_clinical_data.ipynb](https://github.com/ImagingDataCommons/IDC-Tutorials/blob/master/notebooks/getting_started/exploring_clinical_data.ipynb)
336- [nlst_clinical_data.ipynb](https://github.com/ImagingDataCommons/IDC-Tutorials/blob/master/notebooks/collections_demos/nlst_clinical_data.ipynb)

Resources

IDC Documentation:

Related Guides:

  • bigquery_guide.md - Advanced clinical queries via BigQuery
  • Main SKILL.md - Core IDC workflows

IDC Tutorials:

Raw lines 322 to 336