BIQL Tutorial
This tutorial demonstrates how to use the BIDS Query Language (BIQL) to query neuroimaging datasets. The examples below are automatically executed and updated whenever the documentation is built.
Want to run this interactively? Click the “Open in Colab” button above to run and modify the code yourself!
Welcome to the BIQL (BIDS Query Language) tutorial! This guide will walk you through using BIQL to query BIDS neuroimaging datasets. We’ll start with basic queries and progressively explore more advanced features.
What is BIQL?
BIQL is a SQL-like query language designed specifically for querying Brain Imaging Data Structure (BIDS) datasets. It allows you to:
- Search for specific files based on BIDS entities (subject, session, task, etc.)
- Filter data using metadata from JSON sidecars
- Access participant information from participants.tsv
- Perform aggregations and grouping operations
- Export results in various formats
Prerequisites
First, let’s set up our environment and get the example data:
import tempfile
from pathlib import Path
import sys
# Install BIQL if running in Colab
if 'google.colab' in sys.modules:
!pip install git+https://github.com/astewartau/biql.git > /dev/null 2>&1
from biql import create_query_engine
# Set up paths - use a temporary directory that works in different environments
bids_examples_dir = Path(tempfile.gettempdir()) / "bids-examples"
# Clone bids-examples if it doesn't exist
if not bids_examples_dir.exists():
!git clone https://github.com/bids-standard/bids-examples.git {bids_examples_dir} > /dev/null 2>&1
Part 1: Basic Queries
Let’s start with the synthetic dataset from bids-examples. This is a simple dataset that’s perfect for learning BIQL basics.
dataset_path = bids_examples_dir / "synthetic"
q = create_query_engine(dataset_path)
q.dataset_stats()
{'total_files': 60,
'total_subjects': 5,
'files_by_datatype': {'anat': 10, 'func': 30, 'beh': 5},
'subjects': ['01', '02', '03', '04', '05'],
'datatypes': ['anat', 'beh', 'func']}
Simple Entity Queries
The most basic BIQL queries filter files by BIDS entities. You can query by any BIDS entity that appears in your filenames:
q.run_query("sub=01", format="dataframe").head(5)
| filepath | relative_path | filename | sub | ses | suffix | datatype | extension | metadata | participants | task | run | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | /tmp/bids-examples/synthetic/sub-01/ses-02/ana... | sub-01/ses-02/anat/sub-01_ses-02_T1w.nii | sub-01_ses-02_T1w.nii | 01 | 02 | T1w | anat | .nii | {} | age=34; sex=F | NaN | NaN |
| 1 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-nback_ru... | sub-01_ses-02_task-nback_run-02_bold.nii | 01 | 02 | bold | func | .nii | {} | age=34; sex=F | nback | 02 |
| 2 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-nback_ru... | sub-01_ses-02_task-nback_run-01_bold.nii | 01 | 02 | bold | func | .nii | {} | age=34; sex=F | nback | 01 |
| 3 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-rest_bol... | sub-01_ses-02_task-rest_bold.nii | 01 | 02 | bold | func | .nii | {} | age=34; sex=F | rest | NaN |
| 4 | /tmp/bids-examples/synthetic/sub-01/ses-01/ana... | sub-01/ses-01/anat/sub-01_ses-01_T1w.nii | sub-01_ses-01_T1w.nii | 01 | 01 | T1w | anat | .nii | {} | age=34; sex=F | NaN | NaN |
results = q.run_query("datatype=func")
len(results) # Number of functional files
30
q.run_query("SELECT DISTINCT task WHERE datatype=func", format="dataframe")
| task | |
|---|---|
| 0 | nback |
| 1 | rest |
Combining Conditions
You can combine multiple conditions using AND, OR, and NOT operators:
q.run_query("datatype=anat AND suffix=T1w", format="dataframe").head(5)
| filepath | relative_path | filename | sub | ses | suffix | datatype | extension | metadata | participants | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | /tmp/bids-examples/synthetic/sub-01/ses-02/ana... | sub-01/ses-02/anat/sub-01_ses-02_T1w.nii | sub-01_ses-02_T1w.nii | 01 | 02 | T1w | anat | .nii | {} | age=34; sex=F |
| 1 | /tmp/bids-examples/synthetic/sub-01/ses-01/ana... | sub-01/ses-01/anat/sub-01_ses-01_T1w.nii | sub-01_ses-01_T1w.nii | 01 | 01 | T1w | anat | .nii | {} | age=34; sex=F |
| 2 | /tmp/bids-examples/synthetic/sub-04/ses-02/ana... | sub-04/ses-02/anat/sub-04_ses-02_T1w.nii | sub-04_ses-02_T1w.nii | 04 | 02 | T1w | anat | .nii | {} | age=21; sex=F |
| 3 | /tmp/bids-examples/synthetic/sub-04/ses-01/ana... | sub-04/ses-01/anat/sub-04_ses-01_T1w.nii | sub-04_ses-01_T1w.nii | 04 | 01 | T1w | anat | .nii | {} | age=21; sex=F |
| 4 | /tmp/bids-examples/synthetic/sub-05/ses-02/ana... | sub-05/ses-02/anat/sub-05_ses-02_T1w.nii | sub-05_ses-02_T1w.nii | 05 | 02 | T1w | anat | .nii | {} | age=42; sex=M |
q.run_query("task=nback OR task=rest", format="dataframe").head(5)
| filepath | relative_path | filename | sub | ses | task | run | suffix | datatype | extension | metadata | participants | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-nback_ru... | sub-01_ses-02_task-nback_run-02_bold.nii | 01 | 02 | nback | 02 | bold | func | .nii | {} | age=34; sex=F |
| 1 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-nback_ru... | sub-01_ses-02_task-nback_run-01_bold.nii | 01 | 02 | nback | 01 | bold | func | .nii | {} | age=34; sex=F |
| 2 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-rest_bol... | sub-01_ses-02_task-rest_bold.nii | 01 | 02 | rest | NaN | bold | func | .nii | {} | age=34; sex=F |
| 3 | /tmp/bids-examples/synthetic/sub-01/ses-01/fun... | sub-01/ses-01/func/sub-01_ses-01_task-nback_ru... | sub-01_ses-01_task-nback_run-02_bold.nii | 01 | 01 | nback | 02 | bold | func | .nii | {} | age=34; sex=F |
| 4 | /tmp/bids-examples/synthetic/sub-01/ses-01/fun... | sub-01/ses-01/func/sub-01_ses-01_task-rest_bol... | sub-01_ses-01_task-rest_bold.nii | 01 | 01 | rest | NaN | bold | func | .nii | {} | age=34; sex=F |
Using WHERE Clause
For more SQL-like queries, you can use the WHERE clause:
q.run_query("WHERE sub=01 AND datatype=func", format="dataframe")
| filepath | relative_path | filename | sub | ses | task | run | suffix | datatype | extension | metadata | participants | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-nback_ru... | sub-01_ses-02_task-nback_run-02_bold.nii | 01 | 02 | nback | 02 | bold | func | .nii | {} | age=34; sex=F |
| 1 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-nback_ru... | sub-01_ses-02_task-nback_run-01_bold.nii | 01 | 02 | nback | 01 | bold | func | .nii | {} | age=34; sex=F |
| 2 | /tmp/bids-examples/synthetic/sub-01/ses-02/fun... | sub-01/ses-02/func/sub-01_ses-02_task-rest_bol... | sub-01_ses-02_task-rest_bold.nii | 01 | 02 | rest | NaN | bold | func | .nii | {} | age=34; sex=F |
| 3 | /tmp/bids-examples/synthetic/sub-01/ses-01/fun... | sub-01/ses-01/func/sub-01_ses-01_task-nback_ru... | sub-01_ses-01_task-nback_run-02_bold.nii | 01 | 01 | nback | 02 | bold | func | .nii | {} | age=34; sex=F |
| 4 | /tmp/bids-examples/synthetic/sub-01/ses-01/fun... | sub-01/ses-01/func/sub-01_ses-01_task-rest_bol... | sub-01_ses-01_task-rest_bold.nii | 01 | 01 | rest | NaN | bold | func | .nii | {} | age=34; sex=F |
| 5 | /tmp/bids-examples/synthetic/sub-01/ses-01/fun... | sub-01/ses-01/func/sub-01_ses-01_task-nback_ru... | sub-01_ses-01_task-nback_run-01_bold.nii | 01 | 01 | nback | 01 | bold | func | .nii | {} | age=34; sex=F |
Part 2: SELECT Clause and Field Selection
By default, BIQL returns all available fields. Use SELECT to choose specific fields:
q.run_query(
"SELECT sub, task, run, filename WHERE datatype=func",
format="dataframe"
).head(5)
| sub | task | run | filename | |
|---|---|---|---|---|
| 0 | 01 | nback | 02 | sub-01_ses-02_task-nback_run-02_bold.nii |
| 1 | 01 | nback | 01 | sub-01_ses-02_task-nback_run-01_bold.nii |
| 2 | 01 | rest | None | sub-01_ses-02_task-rest_bold.nii |
| 3 | 01 | nback | 02 | sub-01_ses-01_task-nback_run-02_bold.nii |
| 4 | 01 | rest | None | sub-01_ses-01_task-rest_bold.nii |
q.run_query(
"SELECT sub, relative_path WHERE suffix=T1w",
format="dataframe"
)
| sub | relative_path | |
|---|---|---|
| 0 | 01 | sub-01/ses-02/anat/sub-01_ses-02_T1w.nii |
| 1 | 01 | sub-01/ses-01/anat/sub-01_ses-01_T1w.nii |
| 2 | 04 | sub-04/ses-02/anat/sub-04_ses-02_T1w.nii |
| 3 | 04 | sub-04/ses-01/anat/sub-04_ses-01_T1w.nii |
| 4 | 05 | sub-05/ses-02/anat/sub-05_ses-02_T1w.nii |
| 5 | 05 | sub-05/ses-01/anat/sub-05_ses-01_T1w.nii |
| 6 | 02 | sub-02/ses-02/anat/sub-02_ses-02_T1w.nii |
| 7 | 02 | sub-02/ses-01/anat/sub-02_ses-01_T1w.nii |
| 8 | 03 | sub-03/ses-02/anat/sub-03_ses-02_T1w.nii |
| 9 | 03 | sub-03/ses-01/anat/sub-03_ses-01_T1w.nii |
Part 3: Pattern Matching
BIQL supports wildcards and regular expressions for flexible matching:
results = q.run_query("suffix=*bold*")
len(results) # Count of files with 'bold' in suffix
30
q.run_query(
"SELECT DISTINCT task WHERE task~=\".*back*\"",
format="dataframe"
)
| task | |
|---|---|
| 0 | nback |
Part 4: Ranges and Lists
BIQL supports convenient syntax for matching multiple values and ranges:
List Matching with IN
Use IN to match any value from a list:
# Find files for specific subjects
q.run_query(
"SELECT sub, task, filename WHERE sub IN ['01', '02', '03'] AND datatype=func",
format="dataframe"
).head()
| sub | task | filename | |
|---|---|---|---|
| 0 | 01 | nback | sub-01_ses-02_task-nback_run-02_bold.nii |
| 1 | 01 | nback | sub-01_ses-02_task-nback_run-01_bold.nii |
| 2 | 01 | rest | sub-01_ses-02_task-rest_bold.nii |
| 3 | 01 | nback | sub-01_ses-01_task-nback_run-02_bold.nii |
| 4 | 01 | rest | sub-01_ses-01_task-rest_bold.nii |
# Find specific tasks
q.run_query(
"SELECT DISTINCT sub WHERE task IN ['nback', 'rest']",
format="dataframe"
)
| sub | |
|---|---|
| 0 | 01 |
| 1 | 04 |
| 2 | 05 |
| 3 | 02 |
| 4 | 03 |
# Combining lists and other conditions
q.run_query(
"SELECT sub, COUNT(*) as file_count "
"WHERE sub IN ['01', '02'] AND task IN ['nback', 'rest'] "
"GROUP BY sub",
format="dataframe"
)
| sub | file_count | |
|---|---|---|
| 0 | 01 | 6 |
| 1 | 02 | 6 |
Range Matching
Use [start:end] syntax for numeric ranges (inclusive):
# Find runs 1 and 2 (inclusive range)
q.run_query(
"SELECT sub, task, run WHERE run=[1:2] AND datatype=func",
format="dataframe"
)
| sub | task | run | |
|---|---|---|---|
| 0 | 01 | nback | 02 |
| 1 | 01 | nback | 01 |
| 2 | 01 | nback | 02 |
| 3 | 01 | nback | 01 |
| 4 | 04 | nback | 02 |
| 5 | 04 | nback | 01 |
| 6 | 04 | nback | 02 |
| 7 | 04 | nback | 01 |
| 8 | 05 | nback | 02 |
| 9 | 05 | nback | 01 |
| 10 | 05 | nback | 02 |
| 11 | 05 | nback | 01 |
| 12 | 02 | nback | 02 |
| 13 | 02 | nback | 01 |
| 14 | 02 | nback | 02 |
| 15 | 02 | nback | 01 |
| 16 | 03 | nback | 01 |
| 17 | 03 | nback | 02 |
| 18 | 03 | nback | 02 |
| 19 | 03 | nback | 01 |
Part 5: Grouping and Aggregation
BIQL supports SQL-like grouping and aggregation functions:
q.run_query("SELECT sub, COUNT(*) GROUP BY sub", format="dataframe")
| sub | count | |
|---|---|---|
| 0 | 01 | 12 |
| 1 | 04 | 12 |
| 2 | 05 | 12 |
| 3 | 02 | 12 |
| 4 | 03 | 12 |
q.run_query(
"SELECT sub, datatype, COUNT(*) GROUP BY sub, datatype",
format="json"
)
[{'sub': '01', 'datatype': 'anat', 'count': 2},
{'sub': '01', 'datatype': 'func', 'count': 6},
{'sub': '04', 'datatype': 'anat', 'count': 2},
{'sub': '04', 'datatype': 'func', 'count': 6},
{'sub': '05', 'datatype': 'anat', 'count': 2},
{'sub': '05', 'datatype': 'func', 'count': 6},
{'sub': '02', 'datatype': 'anat', 'count': 2},
{'sub': '02', 'datatype': 'func', 'count': 6},
{'sub': '03', 'datatype': 'anat', 'count': 2},
{'sub': '03', 'datatype': 'func', 'count': 6},
{'sub': '01', 'datatype': None, 'count': 3},
{'sub': '01', 'datatype': 'beh', 'count': 1},
{'sub': '04', 'datatype': None, 'count': 3},
{'sub': '04', 'datatype': 'beh', 'count': 1},
{'sub': '05', 'datatype': None, 'count': 3},
{'sub': '05', 'datatype': 'beh', 'count': 1},
{'sub': '02', 'datatype': None, 'count': 3},
{'sub': '02', 'datatype': 'beh', 'count': 1},
{'sub': '03', 'datatype': None, 'count': 3},
{'sub': '03', 'datatype': 'beh', 'count': 1}]
# Compare DISTINCT vs non-DISTINCT - get all task names (including duplicates)
q.run_query(
"SELECT sub, (task) as all_task_names, (DISTINCT task) as unique_tasks "
"WHERE sub='01' "
"GROUP BY sub",
format="json"
)
[{'sub': '01',
'all_task_names': ['nback',
'nback',
'nback',
'nback',
'rest',
'rest',
'stroop',
None,
None,
None,
None,
None],
'unique_tasks': ['nback', 'rest', 'stroop']}]
# Get unique tasks per subject
q.run_query(
"SELECT sub, (DISTINCT task) as unique_tasks, COUNT(*) as total_files "
"WHERE sub IN ['01', '02', '03'] "
"GROUP BY sub",
format="json"
)
[{'sub': '01', 'unique_tasks': ['nback', 'rest', 'stroop'], 'total_files': 12},
{'sub': '02', 'unique_tasks': ['nback', 'rest', 'stroop'], 'total_files': 12},
{'sub': '03', 'unique_tasks': ['nback', 'rest', 'stroop'], 'total_files': 12}]
Array Aggregation with DISTINCT
BIQL supports collecting values into arrays using the (field) syntax:
(DISTINCT field)returns unique non-null values(field)returns all values including duplicates
Part 6: Working with Metadata
BIQL can query JSON sidecar metadata using the metadata. namespace.
Let’s explore a more complex dataset to see this in action:
# Switch to a dataset with more metadata
ds2_path = bids_examples_dir / "ds000117"
q2 = create_query_engine(ds2_path) if (bids_examples_dir / "ds000117").exists() else q
# Show what metadata fields are available
q2.run_query(
"SELECT DISTINCT task WHERE datatype=func",
format="dataframe"
)
| task | |
|---|---|
| 0 | facerecognition |
# For the synthetic dataset, we can still demonstrate basic grouping by task
q.run_query(
"SELECT task, COUNT(*) as file_count, "
"COUNT(DISTINCT sub) as subjects "
"GROUP BY task",
format="dataframe"
)
| task | file_count | subjects | |
|---|---|---|---|
| 0 | None | 25 | 5 |
| 1 | nback | 20 | 5 |
| 2 | rest | 10 | 5 |
| 3 | stroop | 5 | 5 |
Part 7: Participant Information
Access participant demographics using the participants. namespace:
q.run_query(
"SELECT DISTINCT sub, participants.age, participants.sex",
format="dataframe"
)
| sub | participants.age | participants.sex | |
|---|---|---|---|
| 0 | 01 | 34 | F |
| 1 | 04 | 21 | F |
| 2 | 05 | 42 | M |
| 3 | 02 | 38 | M |
| 4 | 03 | 22 | M |
q.run_query(
"SELECT sub, task, participants.age WHERE participants.age > 25",
format="dataframe"
)
| sub | task | participants.age | |
|---|---|---|---|
| 0 | 01 | None | 34 |
| 1 | 01 | nback | 34 |
| 2 | 01 | nback | 34 |
| 3 | 01 | rest | 34 |
| 4 | 01 | None | 34 |
| 5 | 01 | nback | 34 |
| 6 | 01 | rest | 34 |
| 7 | 01 | nback | 34 |
| 8 | 05 | None | 42 |
| 9 | 05 | rest | 42 |
| 10 | 05 | nback | 42 |
| 11 | 05 | nback | 42 |
| 12 | 05 | None | 42 |
| 13 | 05 | nback | 42 |
| 14 | 05 | rest | 42 |
| 15 | 05 | nback | 42 |
| 16 | 02 | None | 38 |
| 17 | 02 | rest | 38 |
| 18 | 02 | nback | 38 |
| 19 | 02 | nback | 38 |
| 20 | 02 | None | 38 |
| 21 | 02 | nback | 38 |
| 22 | 02 | nback | 38 |
| 23 | 02 | rest | 38 |
| 24 | 01 | None | 34 |
| 25 | 01 | None | 34 |
| 26 | 01 | None | 34 |
| 27 | 01 | stroop | 34 |
| 28 | 05 | None | 42 |
| 29 | 05 | None | 42 |
| 30 | 05 | None | 42 |
| 31 | 05 | stroop | 42 |
| 32 | 02 | None | 38 |
| 33 | 02 | None | 38 |
| 34 | 02 | None | 38 |
| 35 | 02 | stroop | 38 |
Part 8: Advanced Queries
Let’s combine multiple features for more complex queries:
q.run_query("""
SELECT sub, ses, task, COUNT(*) as n_runs
WHERE datatype=func AND task != rest
GROUP BY sub, ses, task
HAVING COUNT(*) > 1
ORDER BY sub, task
""", format="json")
[{'sub': '01', 'ses': '02', 'task': 'nback', 'n_runs': 2},
{'sub': '01', 'ses': '01', 'task': 'nback', 'n_runs': 2},
{'sub': '02', 'ses': '02', 'task': 'nback', 'n_runs': 2},
{'sub': '02', 'ses': '01', 'task': 'nback', 'n_runs': 2},
{'sub': '03', 'ses': '02', 'task': 'nback', 'n_runs': 2},
{'sub': '03', 'ses': '01', 'task': 'nback', 'n_runs': 2},
{'sub': '04', 'ses': '02', 'task': 'nback', 'n_runs': 2},
{'sub': '04', 'ses': '01', 'task': 'nback', 'n_runs': 2},
{'sub': '05', 'ses': '02', 'task': 'nback', 'n_runs': 2},
{'sub': '05', 'ses': '01', 'task': 'nback', 'n_runs': 2}]
q.run_query("""
SELECT sub, task,
(filename WHERE suffix='bold') as imaging_files,
(filename WHERE run='01') as run01_files,
(filename WHERE run='02') as run02_files
WHERE datatype=func
GROUP BY sub, task
""", format="table") # Using table format since arrays don't display well in dataframes
'| imaging_files | run01_files | run02_files | sub | task |\n| --------------- | --------------- | --------------- | --- | ----- |\n| [...4 items...] | [...2 items...] | [...2 items...] | 01 | nback |\n| [...2 items...] | [...0 items...] | [...0 items...] | 01 | rest |\n| [...4 items...] | [...2 items...] | [...2 items...] | 04 | nback |\n| [...2 items...] | [...0 items...] | [...0 items...] | 04 | rest |\n| [...2 items...] | [...0 items...] | [...0 items...] | 05 | rest |\n| [...4 items...] | [...2 items...] | [...2 items...] | 05 | nback |\n| [...2 items...] | [...0 items...] | [...0 items...] | 02 | rest |\n| [...4 items...] | [...2 items...] | [...2 items...] | 02 | nback |\n| [...4 items...] | [...2 items...] | [...2 items...] | 03 | nback |\n| [...2 items...] | [...0 items...] | [...0 items...] | 03 | rest |'
Part 9: Output Formats
BIQL supports multiple output formats for different use cases:
sample_query = "SELECT sub, task, run WHERE datatype=func AND sub=01"
print(q.run_query(sample_query, format="table"))
| run | sub | task |
| --- | --- | ----- |
| 02 | 01 | nback |
| 01 | 01 | nback |
| | 01 | rest |
| 02 | 01 | nback |
| | 01 | rest |
| 01 | 01 | nback |
print(q.run_query(sample_query, format="csv"))
run,sub,task
02,01,nback
01,01,nback
,01,rest
02,01,nback
,01,rest
01,01,nback
results_json = q.run_query(sample_query, format="json")
results_json[:2] # Show first 2 entries
[{'sub': '01', 'task': 'nback', 'run': '02'},
{'sub': '01', 'task': 'nback', 'run': '01'}]
print(q.run_query("WHERE sub=01 AND suffix=T1w", format="paths"))
/tmp/bids-examples/synthetic/sub-01/ses-01/anat/sub-01_ses-01_T1w.nii
/tmp/bids-examples/synthetic/sub-01/ses-02/anat/sub-01_ses-02_T1w.nii
q.run_query(sample_query, format="dataframe")
| sub | task | run | |
|---|---|---|---|
| 0 | 01 | nback | 02 |
| 1 | 01 | nback | 01 |
| 2 | 01 | rest | None |
| 3 | 01 | nback | 02 |
| 4 | 01 | rest | None |
| 5 | 01 | nback | 01 |
Part 10: Real-World Examples
Let’s look at some practical queries you might use in neuroimaging research:
q.run_query("""
SELECT sub,
COUNT(*) as total_files,
COUNT(DISTINCT datatype) as datatypes,
(DISTINCT datatype) as available_data
GROUP BY sub
""", format="json")
[{'sub': '01',
'total_files': 12,
'datatypes': 3,
'available_data': ['anat', 'beh', 'func']},
{'sub': '04',
'total_files': 12,
'datatypes': 3,
'available_data': ['anat', 'beh', 'func']},
{'sub': '05',
'total_files': 12,
'datatypes': 3,
'available_data': ['anat', 'beh', 'func']},
{'sub': '02',
'total_files': 12,
'datatypes': 3,
'available_data': ['anat', 'beh', 'func']},
{'sub': '03',
'total_files': 12,
'datatypes': 3,
'available_data': ['anat', 'beh', 'func']}]
q.run_query("""
SELECT sub, ses,
COUNT(*) as files_per_session,
(DISTINCT task) as tasks_in_session
GROUP BY sub, ses
""", format="json")
[{'sub': '01',
'ses': '02',
'files_per_session': 5,
'tasks_in_session': ['nback', 'rest']},
{'sub': '01',
'ses': '01',
'files_per_session': 6,
'tasks_in_session': ['nback', 'rest', 'stroop']},
{'sub': '04',
'ses': '02',
'files_per_session': 5,
'tasks_in_session': ['nback', 'rest']},
{'sub': '04',
'ses': '01',
'files_per_session': 6,
'tasks_in_session': ['nback', 'rest', 'stroop']},
{'sub': '05',
'ses': '02',
'files_per_session': 5,
'tasks_in_session': ['nback', 'rest']},
{'sub': '05',
'ses': '01',
'files_per_session': 6,
'tasks_in_session': ['nback', 'rest', 'stroop']},
{'sub': '02',
'ses': '02',
'files_per_session': 5,
'tasks_in_session': ['nback', 'rest']},
{'sub': '02',
'ses': '01',
'files_per_session': 6,
'tasks_in_session': ['nback', 'rest', 'stroop']},
{'sub': '03',
'ses': '02',
'files_per_session': 5,
'tasks_in_session': ['nback', 'rest']},
{'sub': '03',
'ses': '01',
'files_per_session': 6,
'tasks_in_session': ['nback', 'rest', 'stroop']},
{'sub': '01', 'ses': None, 'files_per_session': 1, 'tasks_in_session': []},
{'sub': '04', 'ses': None, 'files_per_session': 1, 'tasks_in_session': []},
{'sub': '05', 'ses': None, 'files_per_session': 1, 'tasks_in_session': []},
{'sub': '02', 'ses': None, 'files_per_session': 1, 'tasks_in_session': []},
{'sub': '03', 'ses': None, 'files_per_session': 1, 'tasks_in_session': []}]
q.run_query("""
SELECT sub,
COUNT(DISTINCT task) as unique_tasks,
(DISTINCT task) as completed_tasks,
COUNT(*) as total_functional_files
WHERE datatype=func
GROUP BY sub
HAVING COUNT(DISTINCT task) > 1 # Subjects with multiple tasks
""", format="json")
[{'sub': '01',
'unique_tasks': 2,
'completed_tasks': ['nback', 'rest'],
'total_functional_files': 6},
{'sub': '04',
'unique_tasks': 2,
'completed_tasks': ['nback', 'rest'],
'total_functional_files': 6},
{'sub': '05',
'unique_tasks': 2,
'completed_tasks': ['nback', 'rest'],
'total_functional_files': 6},
{'sub': '02',
'unique_tasks': 2,
'completed_tasks': ['nback', 'rest'],
'total_functional_files': 6},
{'sub': '03',
'unique_tasks': 2,
'completed_tasks': ['nback', 'rest'],
'total_functional_files': 6}]