Summarize a real clinical EEG catalogue#

Estimated reading time:4 minutes

Inspect participant groups, age and recording duration in ds004504 without downloading signal files. Metadata describe the released cohort, not a diagnostic test.

Before you start#

Install EEGDash with pandas and Matplotlib. Tutorial 00 introduces catalogue records; this project requires no windowing or model-training knowledge. Network access retrieves metadata only. You will obtain participant-level cohort summaries and separate recording-level acquisition summaries.

import os
from pathlib import Path

import matplotlib.pyplot as plt
import pandas as pd

from eegdash import EEGDashDataset

1. Query metadata through the same dataset loader used for EEG#

description_fields requests the participant fields needed by the summary. The description table has recording rows, so a participant with several runs would otherwise be counted repeatedly. Deduplicating by subject defines the unit for the group and age summaries. In a new longitudinal dataset, first check whether age or other participant attributes vary across sessions before choosing one row per person.

No .raw property is accessed. The constructor therefore supports cohort inspection before committing to signal downloads. This also means numerical metadata checks cannot establish whether individual recordings are usable.

dataset = EEGDashDataset(
    dataset="ds004504",
    cache_dir=Path(os.environ.get("EEGDASH_CACHE_DIR", ".eegdash_cache")),
    description_fields=["subject", "age", "group", "sex"],
)
metadata = dataset.description.copy()
assert not metadata.empty
# Count participants once even when they have several recordings.
participants = metadata.drop_duplicates("subject")
assert participants["subject"].notna().all()
print("Recordings:", len(dataset.datasets), "participants:", len(participants))
print(participants[["subject", "age", "group"]].head())
Recordings: 88 participants: 88
  subject   age group
0     056  64.0     C
1     063  66.0     C
2     038  62.0     C
3     049  62.0     C
4     003  70.0     A

2. Inspect the actual group and age distributions#

The release’s A/F/C codes are mapped explicitly to readable group names. Unrecognized or missing codes stop execution instead of silently entering a new diagnostic category. Numeric age conversion similarly exposes malformed values rather than producing a misleading distribution.

The bars count people. The table’s age count is the number with a numeric age, while mean, standard deviation and range describe that observed sample. Histograms use eight bins within each group for an overview; their edges need not align between groups, so consult the numeric summaries for precise comparisons. Group differences here may reflect recruitment and age structure, not a specific EEG biomarker.

labels = {
    "A": "Alzheimer's disease",
    "F": "Frontotemporal dementia",
    "C": "Healthy control",
}
assert participants["group"].notna().all()
assert set(participants["group"]).issubset(labels)
participants = participants.assign(
    condition=participants["group"].map(labels),
    age=pd.to_numeric(participants["age"], errors="raise"),
)
print(
    participants.groupby("condition")["age"].agg(["count", "mean", "std", "min", "max"])
)
fig, axes = plt.subplots(1, 2, figsize=(11, 4), layout="constrained")
participants["condition"].value_counts().plot.barh(ax=axes[0])
axes[0].set(xlabel="Participants", ylabel="Recorded group")
for condition, group in participants.groupby("condition"):
    axes[1].hist(group["age"].dropna(), bins=8, alpha=0.5, label=condition)
axes[1].set(xlabel="Age (years)", ylabel="Participants")
axes[1].legend()
plt.show()
project clinical dataset summary
                         count       mean       std   min   max
condition
Alzheimer's disease         36  66.388889  7.889515  49.0  79.0
Frontotemporal dementia     23  63.652174  8.221970  44.0  78.0
Healthy control             29  67.896552  5.400693  57.0  78.0

3. Summarize recording duration from catalogue fields#

Duration is the catalogue sample count divided by samples per second. Unlike the participant summaries above, each row here is a recording: multiple runs should contribute multiple durations. This helps plan later preprocessing and identify heterogeneous acquisition settings. It does not measure usable, artifact-free recording time.

records = pd.DataFrame(dataset.records)
duration = pd.to_numeric(records["ntimes"]) / pd.to_numeric(
    records["sampling_frequency"]
)
assert duration.notna().all() and (duration > 0).all()
print("Recording duration (seconds):", duration.describe())
print("Channel counts:", records["nchans"].value_counts())
Recording duration (seconds): count      88.000000
mean      802.162500
std       141.088674
min       307.100000
25%       751.425000
50%       814.050000
75%       877.100000
max      1291.100000
dtype: float64
Channel counts: nchans
19    88
Name: count, dtype: int64

Turn the summary into an analysis plan#

Before classification, decide how to handle age imbalance, select a defined recording subset, and inspect its EEG through tutorial 01’s Raw workflow. Any train/test division should keep each participant’s recordings together. These descriptive statistics alone neither fit nor validate a diagnostic model.