Creating a manifest from a wide-form tabular file for a longitudinal study with imaging and non-imaging visits¶
In this example, we have a longitudinal study with both non-imaging and imaging visits. Specifically, non-imaging (neuropsychological) data was collected every year, and imaging data (anatomical only) was collected every two years.
We start with two CSV files:
example2-demographics_neuropsych.csvcontains demographics information and dates for the neuropsych visitsPARTICIPANT
SEX
DATE_OF_BIRTH
DATE_NEUROPSYCH1
DATE_NEUROPSYCH2
DATE_NEUROPSYCH3
ABC_001
F
1970/12/31
2015/01/30
2016/02/01
2017/02/10
ABC_002
M
1967/02/20
2015/02/19
2016/02/22
2017/02/25
ABC_003
F
1955/05/21
2016/03/03
2017/03/10
example2-mri.csvcontains dates for the MRI visitsPARTICIPANT
DATE_MRI1
DATE_MRI2
ABC_001
2015/02/07
2017/02/15
ABC_002
2015/02/26
2017/03/01
ABC_003
2016/03/09
These files give us the following information:
The study has 3 participants
Each participant has 3 non-imaging visits and 2 imaging visits
Given that we know that all imaging sessions collected anatomical data only, we have all the information required for the manifest file. Here is a manifest-generation script that does the job:
Attention
The script below was written for Python 3.11 with pandas 2.2.3.
It may not work with older/different versions.
1#!/usr/bin/env python
2"""Manifest-generation script for Example 2."""
3
4from pathlib import Path
5
6import pandas as pd
7
8if __name__ == "__main__":
9 # get the path to the demographics/neuropsych file and the MRI file
10 # we assume that it is in the same directory as this script
11 path_neuropsych = Path(__file__).parent / "example2-demographics_neuropsych.csv"
12 path_mri = Path(__file__).parent / "example2-mri.csv"
13
14 # load the files and merge them
15 df_neuropsych = pd.read_csv(path_neuropsych, dtype=str)
16 df_mri = pd.read_csv(path_mri, dtype=str)
17 df_merged = pd.merge(
18 df_neuropsych, df_mri, how="left", left_on="PARTICIPANT", right_on="PARTICIPANT"
19 )
20
21 data_for_manifest = []
22 for _, row in df_merged.iterrows():
23 # remove underscores
24 participant_id = row["PARTICIPANT"].replace("_", "")
25
26 # each row in the demographics file is multiple rows in the manifest file
27 for visit_id in [
28 "NEUROPSYCH1",
29 "NEUROPSYCH2",
30 "NEUROPSYCH3",
31 "MRI1",
32 "MRI2",
33 ]:
34 # if the DATE column is empty, the visit did not happen yet
35 if pd.isna(row[f"DATE_{visit_id}"]):
36 continue
37
38 # session_id is only defined for MRI visits
39 if visit_id.startswith("MRI"):
40 session_id = visit_id.removeprefix("MRI")
41
42 # all participants only have anat datatype
43 datatype = ["anat"]
44 else:
45 session_id = pd.NA
46 datatype = []
47
48 # create the manifest entry
49 data_for_manifest.append(
50 {
51 "participant_id": participant_id,
52 "visit_id": visit_id,
53 "session_id": session_id,
54 "datatype": datatype,
55 }
56 )
57
58 df_manifest = pd.DataFrame(data_for_manifest)
59
60 # write the manifest in the same directory as this script
61 df_manifest.to_csv(
62 Path(__file__).parent / "example2-manifest.tsv", sep="\t", index=False
63 )
Running this script creates a manifest that looks like this:
participant_id |
visit_id |
session_id |
datatype |
|---|---|---|---|
ABC001 |
NEUROPSYCH1 |
[] |
|
ABC001 |
NEUROPSYCH2 |
[] |
|
ABC001 |
NEUROPSYCH3 |
[] |
|
ABC001 |
MRI1 |
1 |
[‘anat’] |
ABC001 |
MRI2 |
2 |
[‘anat’] |
ABC002 |
NEUROPSYCH1 |
[] |
|
ABC002 |
NEUROPSYCH2 |
[] |
|
ABC002 |
NEUROPSYCH3 |
[] |
|
ABC002 |
MRI1 |
1 |
[‘anat’] |
ABC002 |
MRI2 |
2 |
[‘anat’] |
ABC003 |
NEUROPSYCH1 |
[] |
|
ABC003 |
NEUROPSYCH2 |
[] |
|
ABC003 |
MRI1 |
1 |
[‘anat’] |