Skip to content

Commit aa29fab

Browse files
authored
[FIX] be more permissive with sample size (#938)
* supress cluttering warning * throw warnings instead of errors for sample_size(s) * fix style * fix style * add tests with more coverage * test io more comprehensively * style
1 parent 14eb078 commit aa29fab

2 files changed

Lines changed: 140 additions & 26 deletions

File tree

nimare/io.py

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -63,31 +63,53 @@ def _analysis_to_dict(study, analysis):
6363
},
6464
}
6565

66-
sample_sizes = analysis.metadata.get("sample_sizes")
67-
sample_size = None
66+
sample_sizes = analysis.metadata.get("sample_sizes", None)
67+
sample_size = analysis.metadata.get("sample_size", None)
6868

6969
# Validate sample sizes if present
7070
if sample_sizes is not None and not isinstance(sample_sizes, (list, tuple)):
71-
raise TypeError(
72-
f"Expected sample_sizes to be list or tuple, but got {type(sample_sizes)}"
71+
LGR.warning(
72+
f"Expected sample_sizes to be list or tuple, but got {type(sample_sizes)}."
7373
)
74-
75-
if not sample_sizes:
76-
# Try to get single sample size from analysis or study metadata
77-
sample_size = analysis.metadata.get("sample_size")
78-
if sample_size is None:
79-
sample_size = study.metadata.get("sample_size")
80-
74+
sample_sizes = None
75+
elif sample_sizes is not None:
76+
# Validate each sample size in the list
77+
for i, ss in enumerate(sample_sizes):
78+
if not isinstance(ss, (int, float)):
79+
LGR.warning(
80+
f"Expected sample_sizes[{i}] to be numeric, but got {type(ss)}."
81+
" Attempting to convert to numeric."
82+
)
83+
try:
84+
sample_sizes[i] = int(ss)
85+
except (ValueError, TypeError):
86+
try:
87+
sample_sizes[i] = float(ss)
88+
except (ValueError, TypeError):
89+
LGR.warning(f"Could not convert {ss} to numeric from type {type(ss)}.")
90+
sample_sizes = None
91+
break
92+
93+
if not sample_sizes and sample_size:
8194
# Validate single sample size if present
82-
if sample_size is not None and not isinstance(sample_size, (int, float)):
83-
raise TypeError(f"Expected sample_size to be numeric, but got {type(sample_size)}")
84-
85-
# Add sample size info to result if available
86-
if sample_sizes or sample_size is not None:
95+
if not isinstance(sample_size, (int, float)):
96+
LGR.warning(
97+
f"Expected sample_size to be numeric, but got {type(sample_size)}."
98+
" Attempting to convert to numeric."
99+
)
87100
try:
88-
result["metadata"]["sample_sizes"] = sample_sizes or [sample_size]
89-
except TypeError as e:
90-
raise TypeError(f"Error converting sample size data to list: {str(e)}") from e
101+
sample_sizes = [int(sample_size)]
102+
except (ValueError, TypeError):
103+
try:
104+
sample_sizes = [float(sample_size)]
105+
except (ValueError, TypeError):
106+
LGR.warning(
107+
f"Could not convert {sample_size} to"
108+
f" numeric from type {type(sample_size)}."
109+
)
110+
sample_sizes = None
111+
if sample_sizes:
112+
result["metadata"]["sample_sizes"] = sample_sizes
91113

92114
# Handle annotations if present
93115
if analysis.annotations:

nimare/tests/test_io.py

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Test nimare.io (Dataset IO/transformations)."""
22

3+
import copy
34
import os
45

56
import pytest
@@ -52,16 +53,107 @@ def test_convert_nimads_to_dataset_single_sample_size(
5253
assert "sample_sizes" in dset.metadata.columns
5354

5455

55-
def test_analysis_to_dict_invalid_sample_sizes_type(example_nimads_studyset):
56-
"""Test _analysis_to_dict raises ValueError when sample_sizes is not a list/tuple."""
56+
@pytest.mark.parametrize(
57+
"sample_sizes_val,sample_size_val,expect_col,expect_warning",
58+
[
59+
(5, None, False, True),
60+
([5, "6", "7.3", "not_a_number"], None, False, True),
61+
(None, 10, True, False),
62+
(None, 10.5, True, False),
63+
(None, "12", True, True),
64+
(None, "13.5", True, True),
65+
(None, "not_a_number", False, True),
66+
(None, None, False, False),
67+
([], 7, True, False),
68+
([], None, False, False),
69+
],
70+
)
71+
def test_analysis_to_dict_sample_size(
72+
example_nimads_studyset, sample_sizes_val, sample_size_val, expect_col, expect_warning, caplog
73+
):
74+
"""Test conversion of nimads JSON to nimare dataset with different sample_size(s) values."""
5775
studyset = Studyset(example_nimads_studyset)
58-
# Set sample_sizes to an int rather than list/tuple
5976
for study in studyset.studies:
6077
for analysis in study.analyses:
61-
analysis.metadata["sample_sizes"] = 5
62-
with pytest.raises(TypeError):
63-
# Trigger conversion which internally calls _analysis_to_dict
64-
io.convert_nimads_to_dataset(studyset)
78+
analysis.metadata.clear()
79+
if sample_sizes_val is not None:
80+
analysis.metadata["sample_sizes"] = sample_sizes_val
81+
if sample_size_val is not None:
82+
analysis.metadata["sample_size"] = sample_size_val
83+
84+
with caplog.at_level("WARNING"):
85+
dset = io.convert_nimads_to_dataset(studyset)
86+
assert isinstance(dset, nimare.dataset.Dataset)
87+
if expect_col:
88+
assert "sample_sizes" in dset.metadata.columns
89+
else:
90+
assert "sample_sizes" not in dset.metadata.columns
91+
if expect_warning:
92+
assert any(
93+
"sample_size" in record.message or "sample_sizes" in record.message
94+
for record in caplog.records
95+
)
96+
else:
97+
assert not any(
98+
"sample_size" in record.message or "sample_sizes" in record.message
99+
for record in caplog.records
100+
)
101+
102+
103+
@pytest.mark.parametrize(
104+
"annotation_mod,expect_success,expect_typeerror",
105+
[
106+
# No annotation at all
107+
(None, True, False),
108+
# Annotation with empty notes
109+
(lambda ann: ann.update({"notes": []}), True, False),
110+
# Annotation with extra irrelevant key
111+
(lambda ann: ann.update({"extra_key": 123}), True, False),
112+
# Annotation with missing 'notes' key (should fail)
113+
(lambda ann: ann.pop("notes", None), False, True),
114+
# Annotation with mismatched analysis id in notes (should warn/fail)
115+
(
116+
lambda ann: ann["notes"].append(
117+
{
118+
"analysis_name": "Fake",
119+
"publication": "Fake",
120+
"study": ann["notes"][0]["study"],
121+
"study_year": 2025,
122+
"analysis": "not_in_studyset",
123+
"authors": "Nobody",
124+
"note": {"include": False},
125+
"study_name": "Fake",
126+
}
127+
),
128+
True,
129+
True,
130+
),
131+
],
132+
)
133+
def test_analysis_to_dict_annotation(
134+
example_nimads_studyset,
135+
example_nimads_annotation,
136+
annotation_mod,
137+
expect_success,
138+
expect_typeerror,
139+
):
140+
"""Test conversion of nimads JSON to nimare dataset with various annotation modifications."""
141+
studyset = Studyset(example_nimads_studyset)
142+
if annotation_mod is not None:
143+
annotation = copy.deepcopy(example_nimads_annotation)
144+
annotation_mod(annotation)
145+
if expect_typeerror:
146+
with pytest.raises((TypeError, ValueError, KeyError)):
147+
studyset.annotations = annotation
148+
io.convert_nimads_to_dataset(studyset)
149+
else:
150+
studyset.annotations = annotation
151+
dset = io.convert_nimads_to_dataset(studyset)
152+
assert expect_success
153+
else:
154+
# No annotation
155+
dset = io.convert_nimads_to_dataset(studyset)
156+
assert isinstance(dset, nimare.dataset.Dataset)
65157

66158

67159
def test_convert_sleuth_to_dataset_smoke():

0 commit comments

Comments
 (0)