Combining Parkinson’s disease taxonomic analyses#

In this notebook we aim to imitate the analyses in ABaCo demo: Parkinson’s disease gut microbiome” where the aim was to “integrate the 9 studies while preserving key distinctions from the two patient states (Parkinson’s v.s. Healthy).”


# uncomment if colab
# !pip install mgnipy

Curating the taxonomic datasets with metadata#

Searching for studies using MGnifier#

To start we configure our MGnipy client and access the MGnify API Studies resource.

We will filter our query to studies of the gut microbiome that mention “parkinson”s disease.

We can preview the resulting query urls via .explain()

from mgnipy import MGnipy

# Initialize MGnipy with a cache directory
MG = MGnipy(cache_dir="downloads")

# Search for studies related to Parkinson's disease in the human gut microbiome
pd_studies = MG.studies(
    search="parkinson",
    biome_lineage="root:Host-associated:Human:Digestive system:Large intestine:Fecal",
)

# Show all of the request urls for the search i.e., the query set
pd_studies.explain()
https://www.ebi.ac.uk/metagenomics/api/v2/studies?biome_lineage=root%3AHost-associated%3AHuman%3ADigestive+system%3ALarge+intestine%3AFecal&search=parkinson&page=1

looks good. we can proceed with actually executing the list query/queries via .get(). To enrich our list of studies with metadata details we can do this in bulk using .enrich_details() or asynchronously via .aenrich_details()

# as http client manager
async with MG:
    # populate study list
    await pd_studies.aget()
    # enrich study list with metadta
    await pd_studies.aenrich_details()

# can view as pandas or even save to file if you prefer
study_meta = pd_studies.metadata
# taking a look
study_meta.to_pandas(expand_nested_dicts=True)

Hide code cell output

accession ena_accessions title updated_at downloads first_accession metadata__study_name metadata__center_name metadata__study_title metadata__study_accession metadata__study_description metadata__secondary_study_accession biome__biome_name biome__lineage
0 MGYS00005755 [PRJNA510730, SRP173877] Microbiota composition of Parkinson's disease ... 2026-05-06T11:35:50.490000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... SRP173877 NaN NaN NaN NaN NaN NaN Fecal root:Host-associated:Human:Digestive system:La...
1 MGYS00005130 [ERP112853, PRJEB30401] Gut Microbiome Alterations Drive Distinct Meta... 2026-05-06T10:02:48.163000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP112853 Gut Microbiome and Parkinson's Disease University of Cagliari Gut Microbiome Alterations Drive Distinct Meta... PRJEB30401 Parkinson's disease is a neurodegenerative dis... ERP112853 Fecal root:Host-associated:Human:Digestive system:La...
2 MGYS00001650 [ERP004264, PRJEB4927] Alterations of the Fecal Microbiome in Parkins... 2026-05-28T15:47:02.598000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP004264 Fecal Microbiome in Parkinson's Disease Institute of Biotechnology;University of Helsi... Alterations of the Fecal Microbiome in Parkins... PRJEB4927 In the course of Parkinson’s disease (PD), the... ERP004264 Fecal root:Host-associated:Human:Digestive system:La...
3 MGYS00006121 [ERP142200, PRJEB57228] Dietary intervention of people with Parkinson'... 2026-05-28T15:47:01.432000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP142200 NaN NaN NaN NaN NaN NaN Fecal root:Host-associated:Human:Digestive system:La...
4 MGYS00006760 [ERP148661, PRJEB63522] EMG produced TPA metagenomics assembly of PRJN... 2026-05-28T15:47:02.672000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP148661 NaN NaN NaN NaN NaN NaN Fecal root:Host-associated:Human:Digestive system:La...
5 MGYS00006759 [ERP146353, PRJEB61255] EMG produced TPA metagenomics assembly of PRJN... 2026-05-28T15:47:02.660000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP146353 NaN NaN NaN NaN NaN NaN Fecal root:Host-associated:Human:Digestive system:La...
6 MGYS00005601 [ERP113090, PRJEB30615] Identification of Intestinal Bacterial Taxa wi... 2026-05-28T15:47:01.054000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP113090 NaN NaN NaN NaN NaN NaN Fecal root:Host-associated:Human:Digestive system:La...
7 MGYS00005129 [ERP109659, PRJEB27564] Gut microbiota in Parkinson's disease: tempora... 2026-05-06T12:25:31.349000+00:00 [{'file_type': 'tsv', 'download_type': 'Taxono... ERP109659 Parkinson's disease gut microbiota follow-up Institute of Biotechnology;University of Helsi... Gut microbiota in Parkinson's disease: tempora... PRJEB27564 Aiming to explore the temporal stability of gu... ERP109659 Fecal root:Host-associated:Human:Digestive system:La...

Now that we found some studies that match our sesarch criteria, we can take a look at their datasets.


Using MGazine to access the study datasets#

we can access the mgazine of datasets (kinda like a list of available datasets) via .datasets attribute. The study details we retrieved above will also be passed on to the mgazine

# access mgazine
MZ = pd_studies.datasets

# take a look
print(MZ)

Hide code cell output

MGazine containing:
- MGnify pipeline versions: ['v3', 'v4_1', 'v5', 'v6']
- Number of downloads: 72
- Short descriptions: ['Complete GO annotation',
 'DwC-Ready summary of 16S-V3-V4 ASV taxonomies using -PR2 as ref DB',
 'DwC-Ready summary of 16S-V3-V4 ASV taxonomies using -SILVA as ref DB',
 'DwC-Ready summary of closed-ref taxonomies using ITSoneDB as ref DB',
 'DwC-Ready summary of closed-ref taxonomies using PR2 as ref DB',
 'DwC-Ready summary of closed-ref taxonomies using SILVA-LSU as ref DB',
 'DwC-Ready summary of closed-ref taxonomies using SILVA-SSU as ref DB',
 'GO slim annotation',
 'InterPro matches',
 'Phylum level taxonomies',
 'Phylum level taxonomies LSU',
 'Phylum level taxonomies SSU',
 'Summary of DADA2-PR2 taxonomies',
 'Summary of DADA2-SILVA taxonomies',
 'Summary of ITSoneDB taxonomies',
 'Summary of PR2 taxonomies',
 'Summary of SILVA-LSU taxonomies',
 'Summary of SILVA-SSU taxonomies',
 'Taxonomic assignments',
 'Taxonomic assignments LSU',
 'Taxonomic assignments SSU',
 'Taxonomic diversity metrics']
- Nonempty metadata sets: .mgnify_studies

Notice in “Nonempty metadata sets” we can see that the study details we collected above are preserved in the mgazine.

The mgnify_studies attribute is a MGnifyMetadata object so contains all the same methods for viewing e.g.:

  • MZ_SSU.mgnify_studies.to_pandas(expand_nested_dicts=True)

  • ... .to_list()

  • ... .to_polars()

  • ... .records()

  • etc.

Later on in Using MGnetizer to colllect more metadata we will assign additional sets of metadata to .mgnify_runs and .biosamples_metadata which will also convert the lists of records into a MGnifyMetadata object

Filtering the dataset list#

We can filter by the pipeline version and short descriptions of the datasets.

For the ABaCo demo we will use the taxonomic analyses and we will use v4 onwards due to differences in pipeline versions and specifically SILVA databases that were used for the taxonomic analysis

# we can filter by passing as index
V5 = MZ["v5"]["Taxonomic assignments SSU"]
V6 = MZ["Summary of SILVA-SSU taxonomies"]

print(V5, V6)

Hide code cell output

MGazine containing:
- MGnify pipeline versions: ['v5']
- Number of downloads: 4
- Short descriptions: ['Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_studies
 MGazine containing:
- MGnify pipeline versions: ['v6']
- Number of downloads: 3
- Short descriptions: ['Summary of SILVA-SSU taxonomies']
- Nonempty metadata sets: .mgnify_studies

Combining dataset lists#

# can add magazines
MZ_SSU = V5 + V6

# print still works
print(MZ_SSU)

Hide code cell output

MGazine containing:
- MGnify pipeline versions: ['v5', 'v6']
- Number of downloads: 7
- Short descriptions: ['Summary of SILVA-SSU taxonomies', 'Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_studies

Now that we have filtered our list of datasets a bit, let’s actually get and merge the taxonomic datasets.


(Lazy)loading into one taxonomic dataset#

Currently availble in mgnipy are special MGazines for handling taxonomic assignment datasets in the classic taxa x sample/run format TaxaMGazine or in Darwin core-ready format DWCTaxaMGazine

we can also access these from an existing MGazine instance via .taxonomic or .taxonomic_dwc_ready

taxo = MZ_SSU.taxonomic
TaxaMGazine containing:
- MGnify pipeline versions: ['v5', 'v6']
- Number of downloads: 7
- Short descriptions: ['Summary of SILVA-SSU taxonomies', 'Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_studies
-----------------------
Next steps: Use `.load()` to initialize.

now we can load the datasets as polars.LazyFrame

# lazyload the mgnify taxanomic assignments datasets
taxo.load()

# calling to_pandas or to_polars will collect the data and return a dataframe
taxo.to_pandas().head()

Hide code cell output

taxonomy ERZ15614030 ERZ15614040 ERZ15614050 ERZ15614060 ERZ15614021 ERZ15614031 ERZ15614041 ERZ15614051 ERZ15614061 ... ERR3040042 ERR3040043 ERR3040044 ERR3040045 ERR3040046 ERR3040047 ERR3040048 ERR3040050 ERR3040051 ERR3040052
0 sk__Archaea NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
1 sk__Archaea;k__;p__Candidatus_Thermoplasmatota... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
2 sk__Archaea;k__;p__Candidatus_Thermoplasmatota... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN
3 sk__Archaea;k__;p__Candidatus_Thermoplasmatota... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
4 sk__Archaea;k__;p__Candidatus_Thermoplasmatota... NaN NaN NaN NaN NaN NaN NaN NaN NaN ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

5 rows × 1829 columns

additionally there is a method .taxonomic_metadata() that parses “taxonomy” into the taxonomic ranks, returning as pandas or polars dataframe which is configured via arg df_engine=. The default is pandas.

also any run and sample metadata relevant to the observations (i.e., by run accessions) can be merged and viewed using .obs_metadata() again as polars or pandas dataframes. As we know metadata() for the observations in the taxonomic mgazine is not available:

# see first 5 sample's metadata
display(taxo.obs_metadata().head())

# recall the "Nonempty metadata set:"
print(taxo)
_mgnipy_runs_accs
ERR2730148
ERR2730149
ERR2730150
ERR2730151
ERR2730152
TaxaMGazine containing:
- MGnify pipeline versions: ['v5', 'v6']
- Number of downloads: 7
- Short descriptions: ['Summary of SILVA-SSU taxonomies', 'Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_studies

if we recall from earlier and again in the print statement, we only had .mgnify_studies enriched.

We still need to enrich with run and/or sample metadata, which we will do next :)


Using MGnetizer to collect more metadata#

MGnetizer is designed to retrieve the rich metadata from MGnify for a given list of MGnify accessions/ids.

First we get the ids to pass on

# separate run and assembly ids
runs_ids = [
    x for x in taxo.runs_accessions if x.startswith("ERR") or x.startswith("SRR")
]
assembly_ids = [x for x in taxo.runs_accessions if x.startswith("ERZ")]
print(f"Runs: {len(runs_ids)}")
print(f"Assembly: {len(assembly_ids)}")
Runs: 876
Assembly: 952

Now we will instantiate the MGnetizers and pass the accessions/ids.

# initialize mgnetizer for runs and assemblies
mnet_run = MG.mgnetizer(resource="run", all_ids=runs_ids)
mnet_acc = MG.mgnetizer(resource="assembly", all_ids=assembly_ids)
print(mnet_run, mnet_acc)
MGnetizer for resource 'run' with 876 ids. 
Progress: 0 ids. 
Detail proxy: RunDetail 
Cache directory: downloads/4318aa6ab908c5817a9ab02f4a926f726ee98863655922ca623e03b6b19e4403 
 MGnetizer for resource 'assembly' with 952 ids. 
Progress: 0 ids. 
Detail proxy: AssemblyDetail 
Cache directory: downloads/cb192e1c57deabd60d7bb6e86d273fcff987cfb161e8b3ad833051dfe526a75e
# now making the API calls with context manager
async with MG:
    await mnet_run.aenrich(limit=None)
    await mnet_acc.aenrich(limit=None)
# we can combine the metadata from both runs and assemblies into one MGnifyMetadata
run_metadata = mnet_run.metadata + mnet_acc.metadata

# like any other MGnifyMetdata obj we can view as list, pd, pl
run_metadata.to_pandas().head()

Hide code cell output

experiment_type instrument_model instrument_platform sample study accession sample_accession study_accession updated_at run_accession reads_study_accession assembly_study_accession assembler_name assembler_version metadata status
0 None None None None None ERZ23880304 SAMN28062113 None 2026-05-01T18:13:37.779000+00:00 SRR19064520 None MGYS00006759 None None {} {'assembly_failed': False, 'assembly_blocked':...
1 None None None None None ERZ23877829 SAMN28061729 None 2026-05-01T18:17:01.558000+00:00 SRR19064385 None MGYS00006759 None None {} {'assembly_failed': False, 'assembly_blocked':...
2 None None None None None ERZ23878307 SAMN28061651 None 2026-05-01T18:16:31.716000+00:00 SRR19064428 None MGYS00006759 None None {} {'assembly_failed': False, 'assembly_blocked':...
3 Amplicon Illumina MiSeq ILLUMINA {'accession': 'SAMEA5180684', 'ena_accessions'... {'accession': 'MGYS00005130', 'ena_accessions'... ERR3006007 SAMEA5180684 MGYS00005130 None None None None None None None None
4 Amplicon Illumina MiSeq ILLUMINA {'accession': 'SAMN10614128', 'ena_accessions'... {'accession': 'MGYS00005755', 'ena_accessions'... SRR8352070 SAMN10614128 MGYS00005755 None None None None None None None None

if wanting to do some cleaning, do so and then save back to .data

below I clean as a pandas dataframe and then convert back to list of dicts for the MGazine

df_run = run_metadata.to_pandas()
df_run["study_accession"] = df_run["study_accession"].fillna(
    df_run["assembly_study_accession"]
)
df_run = df_run.drop(columns=["assembly_study_accession"])

Optionally we can pass this metadata back to a MGazine to help with dataset curation:

# now back to TaxaMGazine instance as list of records
taxo.mgnify_runs = df_run.to_dict(orient="records")

# and now
print(taxo)

# also the metadata is updated
taxo.obs_metadata().info()

Hide code cell output

TaxaMGazine containing:
- MGnify pipeline versions: ['v5', 'v6']
- Number of downloads: 7
- Short descriptions: ['Summary of SILVA-SSU taxonomies', 'Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_runs, .mgnify_studies
<class 'pandas.core.frame.DataFrame'>
Index: 1828 entries, ERR2730148 to SRR8352125
Data columns (total 28 columns):
 #   Column                                     Non-Null Count  Dtype  
---  ------                                     --------------  -----  
 0   experiment_type                            876 non-null    object 
 1   instrument_model                           876 non-null    object 
 2   instrument_platform                        876 non-null    object 
 3   sample_accession                           1828 non-null   object 
 4   study_accession                            1828 non-null   object 
 5   updated_at                                 952 non-null    object 
 6   run_accession                              952 non-null    object 
 7   reads_study_accession                      0 non-null      object 
 8   assembler_name                             0 non-null      object 
 9   assembler_version                          0 non-null      object 
 10  status                                     952 non-null    object 
 11  sample__accession                          876 non-null    object 
 12  sample__ena_accessions                     876 non-null    object 
 13  sample__sample_title                       296 non-null    object 
 14  sample__biome                              0 non-null      float64
 15  sample__updated_at                         876 non-null    object 
 16  study__accession                           876 non-null    object 
 17  study__ena_accessions                      876 non-null    object 
 18  study__title                               876 non-null    object 
 19  study__updated_at                          876 non-null    object 
 20  study__biome.biome_name                    876 non-null    object 
 21  study__biome.lineage                       876 non-null    object 
 22  study__metadata.study_name                 414 non-null    object 
 23  study__metadata.center_name                414 non-null    object 
 24  study__metadata.study_title                414 non-null    object 
 25  study__metadata.study_accession            414 non-null    object 
 26  study__metadata.study_description          414 non-null    object 
 27  study__metadata.secondary_study_accession  414 non-null    object 
dtypes: float64(1), object(27)
memory usage: 414.2+ KB

However, the available run/sample metadata is not consistent for all e.g. sample__sample_title which has lots missing. We can try to get additional sample metadata from the BioSamples database.


Collecting even more sample metadata using BioSampler#

BioSampler is designed to retrieve the rich sample metadata from BioSamples for a list of Run or Sample ENA accessions.

we again start from the mgnipy instance to automatically pass on the configuration

# getting list of sample ids to go to BioSamples
sample_ids = taxo.mgnify_runs.to_pandas()["sample_accession"].to_list()

bios = MG.biosampler(sample_ids=sample_ids)

print(bios)
BioSampler with 1828 sample_ids. 
Progress: 0 ids. 
Cache directory: downloads/300c23211f66966a7d9306ef28fba3cc6e8e3c77a8eb7a58805264f5785a5a5f
with bios:
    await bios.aenrich(limit=None, incl_ena=False)

and we can also pass this metadata to the TaxaMGazine instance to .biosamples_metadata for merging:

taxo.biosamples_metadata = bios.metadata.to_list(drop_duplicates=True)
print(taxo)
TaxaMGazine containing:
- MGnify pipeline versions: ['v5', 'v6']
- Number of downloads: 7
- Short descriptions: ['Summary of SILVA-SSU taxonomies', 'Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_runs, .mgnify_studies, .biosamples_metadata

and now if we look at the observations metadata we have even more possible annotations

taxo.obs_metadata().info()

Hide code cell output

<class 'pandas.core.frame.DataFrame'>
Index: 1828 entries, ERR2730148 to SRR8352125
Columns: 153 entries, experiment_type to Day_of_stool_collection_digestion_issue
dtypes: float64(1), int64(1), object(151)
memory usage: 2.1+ MB

Data cleaning based on sample (obs) metadata#

inspecting the metadata we found disease status distrbuted between the following columns:

Hide code cell source

disease_status_columns = {
    "host_phenotype": {"Y": ["Parkinson's Disease"], "N": ["Healthy Control"]},
    "host disease status": {
        "Y": ["Parkinson's disease", "Parkinson's Disease [DOID:14330]"],
        "N": ["healthy control", "Healthy [NCIT:C115935]"],
    },
    "parkinson": {"Y": ["yes"], "N": ["no"]},
    "Case_status": {"Y": ["PD"], "N": ["Control"]},
}

# filter out samples with no disease status metadata
df_obs = taxo.obs_metadata().copy()
df_obs_filt = df_obs[df_obs[disease_status_columns.keys()].notna().any(axis=1)].copy()
print(f"Filtered down to {len(df_obs_filt)} samples with disease status metadata.")

# create a new column to indicate if the sample has Parkinson's disease or not
df_obs_filt["has_parkinsons_disease"] = None
for col in disease_status_columns:
    df_obs_filt[col] = df_obs_filt[col].map(
        lambda x: (
            "Y"
            if x in disease_status_columns[col]["Y"]
            else ("N" if x in disease_status_columns[col]["N"] else None)
        )
    )
df_obs_filt["has_parkinsons_disease"] = df_obs_filt.loc[
    :, disease_status_columns.keys()
].apply(
    lambda x: "Y" if "Y" in x.values else ("N" if "N" in x.values else None), axis=1
)
print(
    f"PD samples: {len(df_obs_filt[df_obs_filt['has_parkinsons_disease'] == 'Y'])}, Non-PD samples: {len(df_obs_filt[df_obs_filt['has_parkinsons_disease'] == 'N'])}"
)

# list of studies
filt_studies = df_obs_filt["study_accession"].unique()
print(f"Studies: {filt_studies}")
Filtered down to 1458 samples with disease status metadata.
PD samples: 901, Non-PD samples: 557
Studies: ['MGYS00005129' 'MGYS00005601' 'MGYS00006121' 'MGYS00006759'
 'MGYS00005755']

We will only include the studies with samples with disease status metadata and will move on with this demonstration.

One way we can go about filtering our taxonomic datasets is repeating the above steps but starting with MGnetizer instead of searching Studies resource with MGnifier:

# collect the studies metadata and datasets for the filtered studies
new_mnet = MG.mgnetizer(resource="study", all_ids=filt_studies)
with MG:
    new_mnet.enrich()

# accessing datasets of the filtered studies
new_mz = new_mnet.datasets
V5 = new_mz["v5"]["Taxonomic assignments SSU"]
V6 = new_mz["Summary of SILVA-SSU taxonomies"]

# filter datasets to taxonomic 
new_new_mz = V5 + V6
new_taxo = new_new_mz.taxonomic
new_taxo.load()

# also add on the observation metadata that we had prepared just before
new_taxo.obs = df_obs_filt.reset_index().to_dict(orient="records")
TaxaMGazine containing:
- MGnify pipeline versions: ['v5', 'v6']
- Number of downloads: 5
- Short descriptions: ['Summary of SILVA-SSU taxonomies', 'Taxonomic assignments SSU']
- Nonempty metadata sets: .mgnify_studies
-----------------------
Next steps: Use `.load()` to initialize.

and the TaxaMGazine can handle the rest and we can export to_anndata() if we want

ad_tax = new_taxo.to_anndata()
ad_tax
AnnData object with n_obs × n_vars = 1458 × 2828
    obs: 'experiment_type', 'instrument_model', 'instrument_platform', 'sample_accession', 'study_accession', 'updated_at', 'run_accession', 'reads_study_accession', 'assembler_name', 'assembler_version', 'status', 'sample__accession', 'sample__ena_accessions', 'sample__sample_title', 'sample__biome', 'sample__updated_at', 'study__accession', 'study__ena_accessions', 'study__title', 'study__updated_at', 'study__biome.biome_name', 'study__biome.lineage', 'study__metadata.study_name', 'study__metadata.center_name', 'study__metadata.study_title', 'study__metadata.study_accession', 'study__metadata.study_description', 'study__metadata.secondary_study_accession', 'GivenID', 'RunID', 'SRA accession', 'name', 'taxid', 'ENA first public', 'ENA-CHECKLIST', 'External Id', 'INSDC center name', 'INSDC last update', 'INSDC status', 'Submitter Id', 'broad-scale environmental context', 'collection date', 'description', 'environmental medium', 'geographic location (country and/or sea)', 'geographic location (latitude)', 'geographic location (longitude)', 'host age', 'host diet', 'host disease status', 'host family relationship', 'host sex', 'host subject id', 'local environmental context', 'organism', 'project name', 'scientific_name', 'sequencing method', 'title', 'ENA-FIRST-PUBLIC', 'ENA-LAST-UPDATE', 'INSDC first public', 'environment (biome)', 'environment (feature)', 'environment (material)', 'host body product', 'human gut environmental package', 'investigation type', 'parkinson', 'pcr primers', 'target gene', 'target subfragment', 'timepoint', 'sample collection device or method', 'sample storage duration', 'sample storage temperature', 'gastrointestinal tract disorder', 'INSDC secondary accession', 'NCBI submission model', 'NCBI submission package', 'env_broad_scale', 'env_local_scale', 'env_medium', 'geo loc name', 'host', 'host_phenotype', 'isolate', 'status__biosamples_metadata', 'isolation source', 'lat lon', 'collection_date', 'descrip', 'geo_loc_name', 'isolation_source', 'lat_lon', 'Age_at_collection', 'Anti_inflammatory_drugs', 'Antibiotics_current', 'Antibiotics_past_3_months', 'Antihistamines', 'Asthma_or_COPD_med', 'BMI', 'BioSampleModel', 'Birth_control_or_estrogen', 'Blood_pressure_med', 'Blood_thinners', 'Bristol_stool_chart', 'Case_status', 'Celiac_disease', 'Cholesterol_med', 'Co_Q_10', 'Colitis', 'Constipation', 'Crohns_disease', 'Day_of_stool_collection_abdominal_pain', 'Day_of_stool_collection_bloating', 'Day_of_stool_collection_diarrhea', 'Day_of_stool_collection_excess_gas', 'Depression_anxiety_mood_med', 'Diabetes_med', 'Diarrhea', 'Do_you_drink_alcohol', 'Do_you_drink_caffeinated_beverages', 'Do_you_smoke', 'GI_cancer_past_3_months', 'Gained_10lbs_in_last_year', 'Hispanic_or_Latino', 'How_often_do_you_eat_FRUITS_or_VEGETABLES', 'How_often_do_you_eat_GRAINS', 'How_often_do_you_eat_NUTS', 'How_often_do_you_eat_POULTRY_BEEF_PORK_SEAFOOD_EGGS', 'How_often_do_you_eat_YOGURT', 'IBD', 'IBS', 'INSDC center alias', 'Indigestion_drugs', 'Intestinal_disease', 'Jewish_ancestry', 'Laxatives', 'Loss_10lbs_in_last_year', 'Pain_med', 'Probiotic', 'Race', 'Radiation_Chemo', 'SIBO', 'Sex', 'Sleep_aid', 'Thyroid_med', 'Ulcer_past_3_months', 'broker name', 'collection_method', 'Day_of_stool_collection_constipation', 'Day_of_stool_collection_digestion_issue', 'has_parkinsons_disease'
    var: 'Superkingdom', 'Kingdom', 'Phylum', 'Class', 'Order', 'Family', 'Genus', 'Species'

okay thanks for the help mgnipy and thank you MGnify for the analyses and metadata! From here out we further pre-process the data including cleaning and normalising the taxonomic matrix, followed by ABaCo for batch correction between the studies.

Preprocessing the counts#

and we will agglomerate to Genus level

import numpy as np
import scanpy as sc

from mgnipy._models.constants.tax_ranks import SILVA_TAX_RANKS

# quick cleaning
# add filled na layer
ad_tax.layers["filled_na"] = ad_tax.to_df().fillna(0)
# drop samples if library count is less than median
ad_filt = ad_tax[
    ad_tax.to_df(layer="filled_na").sum(axis=1)
    >= ad_tax.to_df(layer="filled_na").sum(axis=1).median()
]
# calc total counts per sample
total_counts = ad_filt.layers["filled_na"].sum(axis=1)
# agglom to genus level (pruning then agg)
pruned = ad_filt[:, ((ad_filt.var["Genus"] != "NA") & (ad_filt.var["Species"] != "NA"))]
# to avoid memory issues..
pruned.var["ranks_to_genus"] = pruned.var[SILVA_TAX_RANKS[:-1]].agg(";".join, axis=1)
# agg with scanpy
ad_genus = sc.get.aggregate(
    pruned,
    by="ranks_to_genus",
    func="sum",
    axis="var",
    layer="filled_na",
)
# getting the relabund
ad_genus.layers["total_counts"] = np.array([total_counts] * ad_genus.n_vars).T
ad_genus.layers["rel_abund"] = ad_genus.layers["sum"] / ad_genus.layers["total_counts"]
# prevalence threshold of 10%
ad_genus_filt = ad_genus[
    :, (ad_genus.to_df(layer="sum") > 0).sum(axis=0) >= (ad_genus.n_vars * 0.1)
]
ad_genus_filt.to_df(layer="sum")
Bacteria;NA;Actinobacteria;Coriobacteriia;Coriobacteriales;Atopobiaceae;Olsenella Bacteria;NA;Actinobacteria;Coriobacteriia;Coriobacteriales;Coriobacteriaceae;Collinsella Bacteria;NA;Actinobacteria;Coriobacteriia;Eggerthellales;Eggerthellaceae;Raoultibacter Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Alloscardovia Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Bifidobacterium Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Parascardovia Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Scardovia Bacteria;NA;Actinomycetota;Actinomycetes;Kitasatosporales;Streptomycetaceae;Streptomyces Bacteria;NA;Actinomycetota;Actinomycetes;Mycobacteriales;Lawsonellaceae;Lawsonella Bacteria;NA;Actinomycetota;Actinomycetes;Propionibacteriales;Propionibacteriaceae;Propionibacterium ... Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Ruminococcaceae;Ruminococcus Bacteria;NA;Firmicutes;Erysipelotrichia;Erysipelotrichales;Erysipelotrichaceae;Traorella Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Oxalobacteraceae;Oxalobacter Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Sutterellaceae;Duodenibacillus Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Sutterellaceae;Parasutterella Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Sutterellaceae;Sutterella Bacteria;NA;Pseudomonadota;Gammaproteobacteria;Enterobacterales;Enterobacteriaceae;Escherichia Bacteria;NA;Thermodesulfobacteriota;Desulfovibrionia;Desulfovibrionales;Desulfovibrionaceae;Bilophila Bacteria;NA;Thermodesulfobacteriota;Desulfovibrionia;Desulfovibrionales;Desulfovibrionaceae;Desulfovibrio Bacteria;NA;Verrucomicrobiota;Verrucomicrobiae;Verrucomicrobiales;Akkermansiaceae;Akkermansia
_mgnipy_runs_accs
ERR2730148 0.0 0.0 0.0 0.0 590.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 1.0 66.0 17.0 0.0 1.0
ERR2730149 0.0 0.0 0.0 0.0 1836.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 80.0 0.0 0.0 4.0
ERR2730150 0.0 0.0 0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 1.0 2.0 22.0 0.0 0.0
ERR2730151 0.0 0.0 0.0 1.0 116.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 132.0 4.0 13.0 0.0 0.0
ERR2730152 0.0 0.0 0.0 0.0 31.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 1.0 1.0 8.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
SRR8352121 0.0 0.0 0.0 0.0 410.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 1.0 27.0 1.0 14.0
SRR8352122 0.0 0.0 0.0 6.0 224.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 306.0 276.0 75.0 0.0 6.0
SRR8352123 0.0 0.0 0.0 1.0 309.0 0.0 0.0 0.0 1.0 0.0 ... 0.0 0.0 30.0 0.0 0.0 47.0 1625.0 56.0 0.0 10.0
SRR8352124 0.0 0.0 0.0 1.0 3.0 0.0 2.0 0.0 1.0 0.0 ... 0.0 0.0 3.0 0.0 0.0 9.0 0.0 59.0 0.0 1.0
SRR8352125 0.0 0.0 0.0 0.0 0.0 0.0 1.0 3.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 1.0 8589.0 47.0 4.0 1190.0

729 rows × 77 columns

ad_filt.obs["total_counts"] = ad_filt.layers["filled_na"].sum(axis=1)

this = (
    ad_filt.obs[["has_parkinsons_disease", "study_accession", "total_counts"]]
    .merge(ad_filt.to_df(layer="filled_na"), left_index=True, right_index=True)
    .reset_index()
)
this
_mgnipy_runs_accs has_parkinsons_disease study_accession total_counts sk__Archaea sk__Archaea;k__;p__Candidatus_Thermoplasmatota;c__Thermoplasmata sk__Archaea;k__;p__Candidatus_Thermoplasmatota;c__Thermoplasmata;o__Methanomassiliicoccales sk__Archaea;k__;p__Candidatus_Thermoplasmatota;c__Thermoplasmata;o__Methanomassiliicoccales;f__Methanomassiliicoccaceae;g__Methanomassiliicoccus sk__Archaea;k__;p__Candidatus_Thermoplasmatota;c__Thermoplasmata;o__Methanomassiliicoccales;f__Methanomassiliicoccaceae;g__Methanomassiliicoccus;s__Candidatus_Methanomassiliicoccus_intestinalis sk__Archaea;k__;p__Candidatus_Thermoplasmatota;c__Thermoplasmata;o__Methanomassiliicoccales;f__Methanomethylophilaceae ... sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Fabales;f__Fabaceae;g__Ammopiptanthus;s__Ammopiptanthus_mongolicus sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Fabales;f__Fabaceae;g__Arachis sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Fabales;f__Fabaceae;g__Medicago sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Fabales;f__Fabaceae;g__Medicago;s__Medicago_sativa sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Malvales;f__Malvaceae sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Poales;f__Poaceae sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Poales;f__Poaceae;g__Triticum sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Solanales sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Solanales;f__Convolvulaceae;g__Ipomoea sk__Eukaryota;k__Viridiplantae;p__Streptophyta;c__Magnoliopsida;o__Solanales;f__Solanaceae
0 ERR2730148 N MGYS00005129 148570.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
1 ERR2730149 N MGYS00005129 56668.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
2 ERR2730150 N MGYS00005129 100717.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
3 ERR2730151 N MGYS00005129 57146.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
4 ERR2730152 N MGYS00005129 38273.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
724 SRR8352121 N MGYS00005755 180881.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
725 SRR8352122 N MGYS00005755 182690.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
726 SRR8352123 Y MGYS00005755 144652.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
727 SRR8352124 Y MGYS00005755 180271.0 0.0 0.0 0.0 0.0 0.0 195.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0
728 SRR8352125 Y MGYS00005755 147351.0 0.0 0.0 0.0 0.0 0.0 1.0 ... 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0

729 rows × 2832 columns

ad_genus.obs[["has_parkinsons_disease", "study_accession"]].merge(
    ad_genus_filt.to_df(layer="sum"), left_index=True, right_index=True
).reset_index().to_csv("pd_gut_genus.csv", index=False)

Batch correction with ABaCo#

the below code is from their demo notebook

Hide code cell source

from abaco.dataloader import DataPreprocess

# Load Parkinson's disease dataset
path_to_dataset = "pd_gut_genus.csv"
batch_col = "study_accession"
bio_col = "has_parkinsons_disease"
id_col = "_mgnipy_runs_accs"

# Convert data path into compatible pd.DataFrame
df_parkinson = DataPreprocess(
    path_to_dataset, factors=[id_col, batch_col, bio_col]
).dropna()

# see if there are 3 categorical and n numeric columns (should be an extra column for location)
df_parkinson.info()

Hide code cell output

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 729 entries, 0 to 728
Data columns (total 80 columns):
 #   Column                                                                                                     Non-Null Count  Dtype   
---  ------                                                                                                     --------------  -----   
 0   _mgnipy_runs_accs                                                                                          729 non-null    category
 1   has_parkinsons_disease                                                                                     729 non-null    category
 2   study_accession                                                                                            729 non-null    category
 3   Bacteria;NA;Actinobacteria;Coriobacteriia;Coriobacteriales;Atopobiaceae;Olsenella                          729 non-null    float64 
 4   Bacteria;NA;Actinobacteria;Coriobacteriia;Coriobacteriales;Coriobacteriaceae;Collinsella                   729 non-null    float64 
 5   Bacteria;NA;Actinobacteria;Coriobacteriia;Eggerthellales;Eggerthellaceae;Raoultibacter                     729 non-null    float64 
 6   Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Alloscardovia                729 non-null    float64 
 7   Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Bifidobacterium              729 non-null    float64 
 8   Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Parascardovia                729 non-null    float64 
 9   Bacteria;NA;Actinomycetota;Actinomycetes;Bifidobacteriales;Bifidobacteriaceae;Scardovia                    729 non-null    float64 
 10  Bacteria;NA;Actinomycetota;Actinomycetes;Kitasatosporales;Streptomycetaceae;Streptomyces                   729 non-null    float64 
 11  Bacteria;NA;Actinomycetota;Actinomycetes;Mycobacteriales;Lawsonellaceae;Lawsonella                         729 non-null    float64 
 12  Bacteria;NA;Actinomycetota;Actinomycetes;Propionibacteriales;Propionibacteriaceae;Propionibacterium        729 non-null    float64 
 13  Bacteria;NA;Actinomycetota;Coriobacteriia;Coriobacteriales;Coriobacteriaceae;Collinsella                   729 non-null    float64 
 14  Bacteria;NA;Actinomycetota;Coriobacteriia;Eggerthellales;Eggerthellaceae;Adlercreutzia                     729 non-null    float64 
 15  Bacteria;NA;Actinomycetota;Coriobacteriia;Eggerthellales;Eggerthellaceae;Eggerthella                       729 non-null    float64 
 16  Bacteria;NA;Actinomycetota;Coriobacteriia;Eggerthellales;Eggerthellaceae;Gordonibacter                     729 non-null    float64 
 17  Bacteria;NA;Actinomycetota;Coriobacteriia;Eggerthellales;Eggerthellaceae;Slackia                           729 non-null    float64 
 18  Bacteria;NA;Bacillota;Bacilli;Lactobacillales;Lactobacillaceae;Lactobacillus                               729 non-null    float64 
 19  Bacteria;NA;Bacillota;Bacilli;Lactobacillales;Streptococcaceae;Lactococcus                                 729 non-null    float64 
 20  Bacteria;NA;Bacillota;Bacilli;Lactobacillales;Streptococcaceae;Streptococcus                               729 non-null    float64 
 21  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Christensenellaceae;Beduinibacterium                        729 non-null    float64 
 22  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Christensenellaceae;Christensenella                         729 non-null    float64 
 23  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Clostridiaceae;Clostridium                                  729 non-null    float64 
 24  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Clostridiaceae;Hungatella                                   729 non-null    float64 
 25  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Eubacteriaceae;Eubacterium                                  729 non-null    float64 
 26  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Eubacteriales_Family_XIII._Incertae_Sedis;Ihubacter         729 non-null    float64 
 27  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Blautia                                     729 non-null    float64 
 28  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Catenibacillus                              729 non-null    float64 
 29  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Coprococcus                                 729 non-null    float64 
 30  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Cuneatibacter                               729 non-null    float64 
 31  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Dorea                                       729 non-null    float64 
 32  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Lachnoclostridium                           729 non-null    float64 
 33  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Lachnospira                                 729 non-null    float64 
 34  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Lachnospiraceae;Roseburia                                   729 non-null    float64 
 35  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;NA;Colidextribacter                                         729 non-null    float64 
 36  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;NA;Evtepia                                                  729 non-null    float64 
 37  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;NA;Howardella                                               729 non-null    float64 
 38  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;NA;Soleaferrea                                              729 non-null    float64 
 39  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Anaerotruncus                              729 non-null    float64 
 40  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Angelakisella                              729 non-null    float64 
 41  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Faecalibacterium                           729 non-null    float64 
 42  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Flavonifractor                             729 non-null    float64 
 43  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Monoglobus                                 729 non-null    float64 
 44  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Oscillibacter                              729 non-null    float64 
 45  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Ruminococcus                               729 non-null    float64 
 46  Bacteria;NA;Bacillota;Clostridia;Eubacteriales;Oscillospiraceae;Subdoligranulum                            729 non-null    float64 
 47  Bacteria;NA;Bacillota;Erysipelotrichia;Erysipelotrichales;Turicibacteraceae;Turicibacter                   729 non-null    float64 
 48  Bacteria;NA;Bacillota;NA;NA;NA;Negativibacillus                                                            729 non-null    float64 
 49  Bacteria;NA;Bacillota;Negativicutes;Acidaminococcales;Acidaminococcaceae;Acidaminococcus                   729 non-null    float64 
 50  Bacteria;NA;Bacillota;Negativicutes;Acidaminococcales;Acidaminococcaceae;Phascolarctobacterium             729 non-null    float64 
 51  Bacteria;NA;Bacillota;Negativicutes;Veillonellales;Veillonellaceae;Allisonella                             729 non-null    float64 
 52  Bacteria;NA;Bacillota;Negativicutes;Veillonellales;Veillonellaceae;Dialister                               729 non-null    float64 
 53  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Bacteroidaceae;Bacteroides                              729 non-null    float64 
 54  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Barnesiellaceae;Coprobacter                             729 non-null    float64 
 55  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Odoribacteraceae;Odoribacter                            729 non-null    float64 
 56  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Porphyromonadaceae;Porphyromonas                        729 non-null    float64 
 57  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Prevotellaceae;Prevotella                               729 non-null    float64 
 58  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Rikenellaceae;Alistipes                                 729 non-null    float64 
 59  Bacteria;NA;Bacteroidota;Bacteroidia;Bacteroidales;Tannerellaceae;Parabacteroides                          729 non-null    float64 
 60  Bacteria;NA;Campylobacterota;Epsilonproteobacteria;Campylobacterales;Campylobacteraceae;Campylobacter      729 non-null    float64 
 61  Bacteria;NA;Firmicutes;Bacilli;Lactobacillales;Enterococcaceae;Enterococcus                                729 non-null    float64 
 62  Bacteria;NA;Firmicutes;Bacilli;Lactobacillales;Lactobacillaceae;Lactobacillus                              729 non-null    float64 
 63  Bacteria;NA;Firmicutes;Bacilli;Lactobacillales;Streptococcaceae;Streptococcus                              729 non-null    float64 
 64  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Eubacteriaceae;Eubacterium                                 729 non-null    float64 
 65  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Lachnospiraceae;Anaerostipes                               729 non-null    float64 
 66  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Lachnospiraceae;Blautia                                    729 non-null    float64 
 67  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Lachnospiraceae;Coprococcus                                729 non-null    float64 
 68  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Lachnospiraceae;Dorea                                      729 non-null    float64 
 69  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Lachnospiraceae;Lachnoclostridium                          729 non-null    float64 
 70  Bacteria;NA;Firmicutes;Clostridia;Clostridiales;Ruminococcaceae;Ruminococcus                               729 non-null    float64 
 71  Bacteria;NA;Firmicutes;Erysipelotrichia;Erysipelotrichales;Erysipelotrichaceae;Traorella                   729 non-null    float64 
 72  Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Oxalobacteraceae;Oxalobacter                 729 non-null    float64 
 73  Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Sutterellaceae;Duodenibacillus               729 non-null    float64 
 74  Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Sutterellaceae;Parasutterella                729 non-null    float64 
 75  Bacteria;NA;Pseudomonadota;Betaproteobacteria;Burkholderiales;Sutterellaceae;Sutterella                    729 non-null    float64 
 76  Bacteria;NA;Pseudomonadota;Gammaproteobacteria;Enterobacterales;Enterobacteriaceae;Escherichia             729 non-null    float64 
 77  Bacteria;NA;Thermodesulfobacteriota;Desulfovibrionia;Desulfovibrionales;Desulfovibrionaceae;Bilophila      729 non-null    float64 
 78  Bacteria;NA;Thermodesulfobacteriota;Desulfovibrionia;Desulfovibrionales;Desulfovibrionaceae;Desulfovibrio  729 non-null    float64 
 79  Bacteria;NA;Verrucomicrobiota;Verrucomicrobiae;Verrucomicrobiales;Akkermansiaceae;Akkermansia              729 non-null    float64 
dtypes: category(3), float64(77)
memory usage: 463.7 KB

Hide code cell source

from matplotlib.gridspec import GridSpec
from matplotlib.patches import Ellipse
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.spatial.distance import pdist, squareform
from skbio.stats.distance import DistanceMatrix, permanova
from skbio.stats.ordination import pcoa
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt


# Auxiliary
def permanova_ait(df, sample_label, group_label):
    samples = df[sample_label].values
    groups = df[group_label].values
    clr_data = df.select_dtypes(include="number").values

    aitch = pdist(clr_data, metric="euclidean")
    dist_mat = squareform(aitch)
    dm = DistanceMatrix(dist_mat, ids=samples)

    res_ait = permanova(distance_matrix=dm, grouping=groups)

    res_ait["R2"] = (
        res_ait["test statistic"]
        * (len(np.unique(groups)) - 1)
        / (
            res_ait["test statistic"] * (len(np.unique(groups)) - 1)
            + (len(samples) - len(np.unique(groups)))
        )
    )
    return res_ait


def pcoa_aitchison(df, sample_label, batch_label, bio_label):
    df_otu = df.select_dtypes(include="number")
    dist = pdist(df_otu, "euclidean")
    dist = squareform(dist)

    pcoa_res = pcoa(dist)
    explained = (pcoa_res.proportion_explained * 100).round(1)
    explained_dict = {"PC1": explained[0], "PC2": explained[1]}
    df_pcoa = pd.DataFrame(pcoa_res.samples[["PC1", "PC2"]], columns=["PC1", "PC2"])
    df_pcoa.index = df.index
    df_pcoa[[sample_label, batch_label, bio_label]] = df[
        [sample_label, batch_label, bio_label]
    ]
    return df_pcoa, explained_dict


def plot_pcoa_2(
    df_pcoa,
    group_col,
    df,
    sample_label,
    ax,
    explained,
    palette=None,
    xlim=None,
    ylim=None,
    marginal_size="20%",  # size of marginals relative to main
    marginal_pad=0.1,  # padding between main and marginals
    kde_bw_adjust=1.0,  # bandwidth scaling for KDE
    alpha_kde=0.5,  # fill transparency for KDE areas
    title=None,  # optional title above the top density plot
    show_legend=True,  # whether to draw the legend
):
    # compute PERMANOVA R2
    perma_r2 = permanova_ait(df, sample_label, group_col)["R2"]

    # set up axes divider for marginals
    divider = make_axes_locatable(ax)
    ax_top = divider.append_axes("top", size=marginal_size, pad=marginal_pad, sharex=ax)
    ax_right = divider.append_axes(
        "right", size=marginal_size, pad=marginal_pad, sharey=ax
    )

    # hide the marginal axes completely (no ticks, no spines)
    ax_top.axis("off")
    ax_right.axis("off")

    groups = df_pcoa[group_col].unique()
    colors = palette or plt.cm.tab10.colors

    handles = []
    labels = []

    for i, grp in enumerate(groups):
        sub = df_pcoa[df_pcoa[group_col] == grp]
        x = sub["PC1"].values
        y = sub["PC2"].values
        c = colors[i % len(colors)]

        # main scatter
        pts = ax.scatter(x, y, label=str(grp), alpha=0.7, color=c)
        handles.append(pts)
        labels.append(str(grp))

        # marginal KDEs (axes are off so only the filled area shows)
        sns.kdeplot(
            x=x,
            ax=ax_top,
            bw_adjust=kde_bw_adjust,
            fill=True,
            alpha=alpha_kde,
            color=c,
            linewidth=1.5,
        )
        sns.kdeplot(
            y=y,
            ax=ax_right,
            bw_adjust=kde_bw_adjust,
            fill=True,
            alpha=alpha_kde,
            color=c,
            linewidth=1.5,
        )

        # 95% confidence ellipse
        cov = np.cov(x, y)
        vals, vecs = np.linalg.eigh(cov)
        width, height = 2 * np.sqrt(vals * 5.991)
        angle = np.degrees(np.arctan2(*vecs[:, 0][::-1]))
        ell = Ellipse(
            xy=(x.mean(), y.mean()),
            width=width,
            height=height,
            angle=angle,
            edgecolor=c,
            facecolor="none",
            lw=2,
        )
        ax.add_patch(ell)

    # add title above the top density plot
    if title:
        ax_top.set_title(title, pad=10, fontsize=16)

    # optionally draw legend on top density axis
    if show_legend:
        ax_top.legend(
            handles,
            labels,
            title=group_col,
            bbox_to_anchor=(1.02, 1),
            loc="upper left",
            frameon=False,
            fontsize=14,
            title_fontsize=16,
        )

    # main axis formatting
    ax.set_xlabel(f"PC1 ({explained['PC1']:.1f}%)", fontsize=12)
    ax.set_ylabel(f"PC2 ({explained['PC2']:.1f}%)", fontsize=12)
    ax.text(
        0.99,
        0.99,
        f"PERMANOVA R² ({group_col}): {perma_r2:.3f}",
        transform=ax.transAxes,
        ha="right",
        va="top",
        fontsize="small",
    )
    ax.set_aspect("equal")

    if xlim is not None:
        ax.set_xlim(xlim)
    if ylim is not None:
        ax.set_ylim(ylim)

Hide code cell source

# Define figure
from abaco.dataloader import DataTransform

sns.set_style("whitegrid")
fig = plt.figure(figsize=(24, 16))
fig.suptitle("", fontsize=16, y=0.97)

gs = GridSpec(2, 1, figure=fig, wspace=0.4, hspace=0.3)

top_palette = sns.color_palette("tab10", n_colors=9)
bottom_palette = sns.color_palette("tab10", n_colors=10)[::-1][:9]

ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0])

data_clr = DataTransform(df_parkinson, factors=[id_col, batch_col, bio_col], count=True)

data_pcoa, data_exp = pcoa_aitchison(
    data_clr, sample_label=id_col, batch_label=batch_col, bio_label=bio_col
)

plot_pcoa_2(
    data_pcoa,
    group_col=batch_col,
    df=data_clr,
    sample_label=id_col,
    ax=ax1,
    explained=data_exp,
    palette=top_palette,
    title="Aitchison PCoA - Colored by Study",
    show_legend=False,
)

handles, labels = ax1.get_legend_handles_labels()

fig.legend(
    handles,
    labels,
    title="Batch",
    loc="upper right",
    frameon=False,
    bbox_to_anchor=(0.8, 0.82),
    fontsize=12,
    title_fontsize=12,
)

plot_pcoa_2(
    data_pcoa,
    group_col=bio_col,
    df=data_clr,
    sample_label=id_col,
    ax=ax2,
    explained=data_exp,
    palette=bottom_palette,
    title="Aitchison PCoA - Colored by Phenotype",
    show_legend=False,
)

handles, labels = ax2.get_legend_handles_labels()

fig.legend(
    handles,
    labels,
    title="Phenotype",
    loc="upper right",
    frameon=False,
    bbox_to_anchor=(0.78, 0.37),
    fontsize=12,
    title_fontsize=12,
)

fig.subplots_adjust(right=0.85)

plt.show()
../../_images/5adc9c45504aa9073baaec548116c951bc2b6f69c457d9d9f5155e18bc82b9cd.png
from abaco.ABaCo import metaABaCo
import torch

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Create ABaCo model
abaco_model = metaABaCo(
    data=df_parkinson,
    n_bios=2,
    bio_label=bio_col,
    n_batches=4,
    batch_label=batch_col,
    n_features=df_parkinson.select_dtypes(include="number").shape[1],
    # prior="VMM",
    device=device,
    epochs=[1000, 2000, 1000],
)

abaco_model.fit(
    seed=42,
    w_cluster_penalty=0.1,  # 0.1
    phase_1_vae_lr=1e-3,  # 1e-3
    phase_2_vae_lr=1e-3,  # 1e-3
    phase_3_vae_lr=1e-7,  # 1e-7
    adv_lr=1e-4,  # 1e-4
    disc_lr=1e-4,
)  # 1e-4

Hide code cell source

# Reconstruct the dataset using the trained ABaCo model
corrected_dataset = abaco_model.correct(seed=42)

sns.set_style("whitegrid")
fig = plt.figure(figsize=(24, 16))
fig.suptitle("", fontsize=16, y=0.97)

gs = GridSpec(2, 1, figure=fig, wspace=0.4, hspace=0.3)

top_palette = sns.color_palette("tab10", n_colors=9)
bottom_palette = sns.color_palette("tab10", n_colors=10)[::-1][:9]

ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[1, 0])

corrected_data_clr = DataTransform(
    corrected_dataset, factors=[id_col, batch_col, bio_col], count=True
)

data_pcoa, data_exp = pcoa_aitchison(
    corrected_data_clr, sample_label=id_col, batch_label=batch_col, bio_label=bio_col
)

plot_pcoa_2(
    data_pcoa,
    group_col=batch_col,
    df=corrected_data_clr,
    sample_label=id_col,
    ax=ax1,
    explained=data_exp,
    palette=top_palette,
    title="Aitchison PCoA - Colored by Study",
    show_legend=False,
)

handles, labels = ax1.get_legend_handles_labels()

fig.legend(
    handles,
    labels,
    title="Batch",
    loc="upper right",
    frameon=False,
    bbox_to_anchor=(0.77, 0.82),
    fontsize=12,
    title_fontsize=12,
)

plot_pcoa_2(
    data_pcoa,
    group_col=bio_col,
    df=corrected_data_clr,
    sample_label=id_col,
    ax=ax2,
    explained=data_exp,
    palette=bottom_palette,
    title="Aitchison PCoA - Colored by Phenotype",
    show_legend=False,
)

handles, labels = ax2.get_legend_handles_labels()

fig.legend(
    handles,
    labels,
    title="Phenotype",
    loc="upper right",
    frameon=False,
    bbox_to_anchor=(0.745, 0.37),
    fontsize=12,
    title_fontsize=12,
)

fig.subplots_adjust(right=0.85)

plt.show()
../../_images/a30405b7324c325b3c6278cea1afc40cf5697e9d62043713323c8f6bd20d65e5.png
import abaco.metrics as metrics

print("kBET results before batch correction:")
print(metrics.kBET(data_clr, batch_col))
print("\niLISI results before batch correction:")
print(metrics.iLISI_norm(data_clr, batch_col))
print("\nbatch ASW results before batch correction:")
print(1 - metrics.ASW(data_clr, batch_col))
print("\nbatch ARI results before batch correction:")
print(1 - metrics.ARI(data_clr, batch_col))

print("\n\nkBET results after batch correction:")
print(metrics.kBET(corrected_data_clr, batch_col))
print("\niLISI results after batch correction:")
print(metrics.iLISI_norm(corrected_data_clr, batch_col))
print("\nbatch ASW results after batch correction:")
print(1 - metrics.ASW(corrected_data_clr, batch_col))
print("\nbatch ARI results after batch correction:")
print(1 - metrics.ARI(corrected_data_clr, batch_col))
kBET results before batch correction:
0.0

iLISI results before batch correction:
0.13258066628820245

batch ASW results before batch correction:
0.9538833741134463

batch ARI results before batch correction:
0.4622274613227778


kBET results after batch correction:
0.8477366255144033

iLISI results after batch correction:
0.5657335478036348

batch ASW results after batch correction:
1.0137299532070756

batch ARI results after batch correction:
0.9885442589303474

batch corrected.


TODO#

working with the corrected dataset

as anndata

from skbio.stats.composition import clr
import anndata as ad

adata = ad.AnnData(corrected_dataset.iloc[:, 3:], obs=corrected_dataset.iloc[:, :3])
adata.layers["totals"] = np.array([adata.to_df().sum(axis=1)] * adata.n_vars).T
adata.layers["normalized_X"] = adata.to_df() / adata.layers["totals"]
adata.layers["clr_X"] = clr(
    np.where(adata.layers["normalized_X"] > 0, adata.layers["normalized_X"], 1e-10)
)
# Diversity: Number of unique taxa per sample as new metadata
adata.obs["X_numtaxa"] = (adata.X > 0).sum(axis=1)

# Diversity with correction: Chao1 estimator
# Chao1 = num observed taxa + (num singletons / (2 x num doubletons))
num_singletons = (adata.X == 1).sum(axis=1)
num_doubletons = (adata.X == 2).sum(axis=1)
adata.obs["X_chao1"] = (adata.X > 0).sum(axis=1) + (
    num_singletons / (2 * num_doubletons)
)

# Diversity: Shannon index H = -sum(p_i * log(p_i)), entropy
from scipy.stats import entropy

adata.obs["X_shannon"] = entropy(
    adata.layers["normalized_X"], nan_policy="omit", axis=1
)

# Evenness: Pielou evenness index P = H/H_max, H_max = ln(num species)
adata.obs["X_pielou"] = adata.obs["X_shannon"] / np.log(adata.obs["X_numtaxa"])

# Diversity: simpson index lambda = sum(p_i ^2)
adata.obs["X_simpson"] = (adata.layers["normalized_X"] ** 2).sum(axis=1)

# Diversity: inverse simpson index D = 1/lambda
adata.obs["X_inv_simpson"] = 1 / adata.obs["X_simpson"]
import seaborn as sns
import matplotlib.pyplot as plt

# preparing the df for vis
df_alphas = adata.obs[
    [
        "X_numtaxa",
        "X_chao1",
        "X_shannon",
        "X_pielou",
        "X_simpson",
        "X_inv_simpson",
        batch_col,
        bio_col,
    ]
].copy()

# plotting by bio groups
g1 = sns.pairplot(df_alphas, hue=bio_col, corner=True, height=1.8)
g1.figure.suptitle("Alpha Diversity by Biological Groups", fontsize=22)
plt.setp(g1._legend.get_texts(), fontsize=18)
plt.setp(g1._legend.get_title(), fontsize=20)

g2 = sns.pairplot(df_alphas, hue=batch_col, corner=True, height=1.8)
g2.figure.suptitle("Alpha Diversity by Batch Groups", fontsize=22)
plt.setp(g2._legend.get_texts(), fontsize=18)
plt.setp(g2._legend.get_title(), fontsize=20)
[None]
../../_images/c112cb00411694cabdc5f338e7bab5ff141ab2f169aa44b0cf0800515f24b32f.png ../../_images/919b3166b20c04e6682a69acae11388c1443bbe83f3482153e1bbcc5882a4037.png
adata.obs.groupby("has_parkinsons_disease")[
    ["X_numtaxa", "X_chao1", "X_shannon", "X_pielou", "X_simpson", "X_inv_simpson"]
].mean()
X_numtaxa X_chao1 X_shannon X_pielou X_simpson X_inv_simpson
has_parkinsons_disease
N 23.012739 inf 1.975604 0.636470 0.261707 5.416076
Y 24.636145 inf 1.965688 0.615675 0.268658 5.308188
oj = taxo.obs_metadata()

oj[oj["sample_accession"] == "SAMN28061739"][disease_status_columns.keys()]
host_phenotype host disease status parkinson Case_status
_mgnipy_runs_accs
ERZ23877886 None None None Control
new_taxo.mgnify_studies.ids
['MGYS00006121',
 'MGYS00005755',
 'MGYS00005129',
 'MGYS00006759',
 'MGYS00005601']