Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix the two sided test on IBMA Fishers and Stouffers estimators #903

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 67 additions & 59 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ on:
- "main"
pull_request:
branches:
- '*'
- "*"

concurrency:
group: testing-${{ github.ref }}
Expand All @@ -26,37 +26,37 @@ jobs:
- id: result_step
uses: mstachniuk/ci-skip@master
with:
commit-filter: '[skip ci];[ci skip];[skip github]'
commit-filter-separator: ';'
commit-filter: "[skip ci];[ci skip];[skip github]"
commit-filter-separator: ";"

run_unit_tests:
name: Unit tests
needs: check_skip
if: ${{ needs.check_skip.outputs.skip == 'false' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest", "macos-latest"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
fail-fast: false
matrix:
os: ["ubuntu-latest", "macos-latest"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: 'Set up python'
- name: "Set up python"
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: 'Install NiMARE'
python-version: ${{ matrix.python-version }}
- name: "Install NiMARE"
shell: bash {0}
run: pip install -e .[tests,cbmr]
- name: 'Run tests'
- name: "Run tests"
shell: bash {0}
run: make unittest
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: unit_${{ matrix.os }}_${{ matrix.python-version }}
path: coverage.xml
Expand All @@ -68,27 +68,27 @@ jobs:
if: ${{ needs.check_skip.outputs.skip == 'false' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: 'Set up python'
- name: "Set up python"
uses: actions/setup-python@v2
with:
python-version: 3.8
- name: 'Install NiMARE'
python-version: 3.8
- name: "Install NiMARE"
shell: bash {0}
run: pip install -e .[minimum,tests,cbmr]
- name: 'Run tests'
- name: "Run tests"
shell: bash {0}
run: make unittest
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: unit_minimum
path: coverage.xml
Expand All @@ -100,27 +100,27 @@ jobs:
if: ${{ needs.check_skip.outputs.skip == 'false' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: 'Set up python'
- name: "Set up python"
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: 'Install NiMARE'
python-version: ${{ matrix.python-version }}
- name: "Install NiMARE"
shell: bash {0}
run: pip install -e .[tests,cbmr]
- name: 'Run tests'
- name: "Run tests"
shell: bash {0}
run: make test_performance_estimators
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: performance_estimator
path: coverage.xml
Expand All @@ -132,27 +132,27 @@ jobs:
if: ${{ needs.check_skip.outputs.skip == 'false' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: 'Set up python'
- name: "Set up python"
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: 'Install NiMARE'
python-version: ${{ matrix.python-version }}
- name: "Install NiMARE"
shell: bash {0}
run: pip install -e .[tests,cbmr]
- name: 'Run tests'
- name: "Run tests"
shell: bash {0}
run: make test_performance_correctors
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: performance_corrector
path: coverage.xml
Expand All @@ -164,27 +164,27 @@ jobs:
if: ${{ needs.check_skip.outputs.skip == 'false' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: 'Set up python'
- name: "Set up python"
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: 'Install NiMARE'
python-version: ${{ matrix.python-version }}
- name: "Install NiMARE"
shell: bash {0}
run: pip install -e .[tests,cbmr]
- name: 'Run tests'
- name: "Run tests"
shell: bash {0}
run: make test_performance_smoke
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: performance_smoke
path: coverage.xml
Expand All @@ -196,41 +196,49 @@ jobs:
if: ${{ needs.check_skip.outputs.skip == 'false' }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
fail-fast: false
matrix:
os: ["ubuntu-latest"]
python-version: ["3.8"]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v2
- name: 'Set up python'
- name: "Set up python"
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: 'Install NiMARE'
python-version: ${{ matrix.python-version }}
- name: "Install NiMARE"
shell: bash {0}
run: pip install -e .[tests]
- name: 'Run tests'
- name: "Run tests"
shell: bash {0}
run: make test_cbmr_importerror
- name: Upload artifacts
uses: actions/upload-artifact@v2
uses: actions/upload-artifact@v4
with:
name: cbmr_importerror
path: coverage.xml
if: success()

upload_to_codecov:
name: Upload coverage
needs: [run_unit_tests,run_unit_tests_with_minimum_dependencies,test_performance_estimators,test_performance_correctors,test_performance_smoke,test_cbmr_importerror]
needs:
[
run_unit_tests,
run_unit_tests_with_minimum_dependencies,
test_performance_estimators,
test_performance_correctors,
test_performance_smoke,
test_cbmr_importerror,
]
runs-on: "ubuntu-latest"
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Download artifacts
uses: actions/download-artifact@v2
uses: actions/download-artifact@v4
- name: Upload to CodeCov
uses: codecov/codecov-action@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion examples/02_meta-analyses/12_plot_ibma_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
###############################################################################
# Contribution table
# ``````````````````````````````````````````````````````````````````````````````
result.tables["z_corr-FDR_method-indep_diag-Jackknife_tab-counts_tail-positive"]
result.tables["z_corr-FDR_method-indep_diag-Jackknife_tab-counts"]

###############################################################################
# Report
Expand Down
65 changes: 50 additions & 15 deletions nimare/meta/ibma.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,6 @@ class Fishers(IBMAEstimator):
def __init__(self, two_sided=True, **kwargs):
super().__init__(**kwargs)
self.two_sided = two_sided
self._mode = "concordant" if self.two_sided else "directed"

def _generate_description(self):
description = (
Expand All @@ -243,13 +242,32 @@ def _fit_model(self, stat_maps):
"""Fit the model to the data."""
n_studies, n_voxels = stat_maps.shape

pymare_dset = pymare.Dataset(y=stat_maps)
est = pymare.estimators.FisherCombinationTest(mode=self._mode)
est.fit_dataset(pymare_dset)
est_summary = est.summary()
# Run Stouffers for right tail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (performance): Consider performance implications of running two separate tests for two-sided cases

The new implementation runs two separate tests when two_sided is True, which could potentially double the computation time. Consider if there's a way to optimize this, especially for large datasets.

pos_est = pymare.estimators.FisherCombinationTest(mode="directed")
pos_pymare_dset = pymare.Dataset(y=stat_maps)
pos_est.fit_dataset(pos_pymare_dset)
pos_est_summary = pos_est.summary()

z_map = pos_est_summary.z.squeeze()
p_map = pos_est_summary.p.squeeze()

if self.two_sided:
# Run Stouffers for left tail
neg_est = pymare.estimators.FisherCombinationTest(mode="directed")
neg_pymare_dset = pymare.Dataset(y=-stat_maps)
neg_est.fit_dataset(neg_pymare_dset)
neg_est_summary = neg_est.summary()

# Find the minimum p-values
p_map = np.minimum(p_map, neg_est_summary.p.squeeze())

# Create z_map based on which p-value is smaller
z_map = np.where(
p_map <= neg_est_summary.p.squeeze(),
z_map,
-neg_est_summary.z.squeeze(),
)

z_map = est_summary.z.squeeze()
p_map = est_summary.p.squeeze()
dof_map = np.tile(n_studies - 1, n_voxels).astype(np.int32)

return z_map, p_map, dof_map
Expand Down Expand Up @@ -381,7 +399,6 @@ def __init__(
self.normalize_contrast_weights = normalize_contrast_weights

self.two_sided = two_sided
self._mode = "concordant" if self.two_sided else "directed"

def _preprocess_input(self, dataset):
"""Preprocess additional inputs to the Estimator from the Dataset as needed."""
Expand Down Expand Up @@ -440,8 +457,6 @@ def _fit_model(self, stat_maps, study_mask=None, corr=None):
# when using the aggressive mask.
study_mask = np.arange(n_studies)

est = pymare.estimators.StoufferCombinationTest(mode=self._mode)

contrast_maps, sub_corr = None, None
if corr is not None:
contrast_maps = np.tile(self.inputs_["contrast_names"][study_mask], (n_voxels, 1)).T
Expand All @@ -460,12 +475,32 @@ def _fit_model(self, stat_maps, study_mask=None, corr=None):

weight_maps = np.tile(weights, (n_voxels, 1)).T

pymare_dset = pymare.Dataset(y=stat_maps, n=weight_maps, v=contrast_maps)
est.fit_dataset(pymare_dset, corr=sub_corr)
est_summary = est.summary()
# Run Stouffers for right tail
pos_est = pymare.estimators.StoufferCombinationTest(mode="directed")
pos_pymare_dset = pymare.Dataset(y=stat_maps, n=weight_maps, v=contrast_maps)
pos_est.fit_dataset(pos_pymare_dset, corr=sub_corr)
pos_est_summary = pos_est.summary()

z_map = pos_est_summary.z.squeeze()
p_map = pos_est_summary.p.squeeze()

if self.two_sided:
# Run Stouffers for left tail
neg_est = pymare.estimators.StoufferCombinationTest(mode="directed")
neg_pymare_dset = pymare.Dataset(y=-stat_maps, n=weight_maps, v=contrast_maps)
neg_est.fit_dataset(neg_pymare_dset, corr=sub_corr)
neg_est_summary = neg_est.summary()

# Find the minimum p-values
p_map = np.minimum(p_map, neg_est_summary.p.squeeze())

# Create z_map based on which p-value is smaller
z_map = np.where(
p_map <= neg_est_summary.p.squeeze(),
z_map,
-neg_est_summary.z.squeeze(),
)

z_map = est_summary.z.squeeze()
p_map = est_summary.p.squeeze()
dof_map = np.tile(n_studies - 1, n_voxels).astype(np.int32)

return z_map, p_map, dof_map
Expand Down
Loading