_diff_test
Differential testing utilities for AnnData objects and CSV-backed expression tables.
This module exposes one public API:
diff_test
diff_test(...) computes feature-wise summary statistics plus one or more hypothesis tests across:
independent groups,
paired groups, or
nested paired comparisons such as
(target - target_control)vs(ref - ref_control).
It returns a feature-indexed pandas.DataFrame and can optionally save results to CSV, write a log file, and store a copy in adata.uns.
Full signature
def diff_test(adata, layer=None, use_raw=False,
groupby_key=None, groupby_key_target_values=[None], groupby_key_ref_values=[None],
comparison_col_tag='_target_ref',
nested_groupby_key_target_values=[(None,None)], nested_groupby_key_ref_values= [(None,None)],
nested_comparison_col_tag='_target_con_ref_con',
sortby=None,ascending=False,
tests=['ttest_ind', 'ttest_rel','mannwhitneyu', 'WilcoxonSigned','ttest_rel_nested','WilcoxonSigned_nested'],
pair_by_key=None ,
add_values2results= False,
add_adata_var_column_key_list=None,
save_table=False,
save_path=None,
save_result_to_adata_uns_as_dict=False,
logger=None,
log_inputs=True,
log_level="INFO",
save_log=True,
x_df=None,
var_df=None,
obs_df=None,
):
Supported test families
The tests argument accepts these values:
ttest_indmannwhitneyuttest_relWilcoxonSignedttest_rel_nestedWilcoxonSigned_nested
In practice:
ttest_indandmannwhitneyuare for independent groups.ttest_relandWilcoxonSignedare for paired groups.ttest_rel_nestedandWilcoxonSigned_nestedare for paired difference-of-differences workflows.
Input modes
diff_test(...) supports two mutually exclusive input modes.
AnnData mode
Use adata plus either:
adata.X,adata.layers[layer], oradata.raw.X/adata.raw.layers[layer]whenuse_raw=True.
Independent example:
import adata_science_tools as adtl
results = adtl.diff_test(
adata,
groupby_key="Treatment",
groupby_key_target_values=["drug"],
groupby_key_ref_values=["vehicle"],
tests=["ttest_ind", "mannwhitneyu"],
add_values2results=True,
sortby="Age",
save_log=False,
)
x_df / obs_df / var_df mode
You can also provide a data matrix plus matching observation and variable tables:
results = adtl.diff_test(
None,
x_df="x.csv",
obs_df="obs.csv",
var_df="var.csv",
groupby_key="Treatment",
groupby_key_target_values=["drug"],
groupby_key_ref_values=["vehicle"],
tests=["ttest_ind"],
add_adata_var_column_key_list=["annotation"],
save_table=True,
save_path="results/diff_test.csv",
save_log=True,
)
Important behavior:
If any of
x_df,obs_df, orvar_dfare provided, then all three are required.CSV paths are read with
pandas.read_csv(index_col=0).obs_df.indexmust matchx_df.index.var_df.indexmust matchx_df.columns.When CSV or DataFrame inputs are used,
adatais ignored and the function constructs a temporaryAnnData.In CSV-backed mode,
layeris reset toNoneanduse_rawis disabled.
Required keys and pairing rules
groupby_key is required for all current workflows.
pair_by_key is required when any paired or nested paired test is requested:
ttest_relWilcoxonSignedttest_rel_nestedWilcoxonSigned_nested
Paired alignment is based on the overlapping IDs in adata.obs[pair_by_key]. If the target and reference groups have no overlapping IDs, the function raises:
No overlapping pairs on '<pair_by_key>'
If groupby_key_ref_values is None, the implementation uses all other categories in groupby_key that are not present in groupby_key_target_values.
Examples by workflow
Independent comparison
results = adtl.diff_test(
adata,
groupby_key="Treatment",
groupby_key_target_values=["drug"],
groupby_key_ref_values=["vehicle"],
tests=["ttest_ind", "mannwhitneyu"],
add_values2results=True,
sortby="Age",
save_log=False,
)
This produces:
group means and CVs,
fold change and log2 fold change,
ttest_ind_*columns,mannwhitneyu_*columns,independent-group normality columns,
optional exported value and order columns such as
drug_values,vehicle_values,drug_Age_order, andvehicle_Age_order.
Paired comparison
results = adtl.diff_test(
adata,
groupby_key="Treatment",
groupby_key_target_values=["drug"],
groupby_key_ref_values=["vehicle"],
tests=["ttest_rel", "WilcoxonSigned"],
pair_by_key="SubjectID",
add_values2results=True,
save_log=False,
)
This adds paired outputs such as:
SubjectID_order,drug_minus_vehicle_values,paired_PCTchange_target_ref,ttest_rel_mean_paired_fc_target_ref,WilcoxonSigned_mean_paired_l2fc_target_ref,normality columns for paired differences.
Nested paired comparison
results = adtl.diff_test(
adata,
groupby_key="Treatment",
groupby_key_target_values=["drug"],
groupby_key_ref_values=["vehicle"],
nested_groupby_key_target_values=[("drug", "predoseDrug")],
nested_groupby_key_ref_values=[("vehicle", "predoseVeh")],
tests=["ttest_rel_nested", "WilcoxonSigned_nested"],
pair_by_key="AnimalID",
add_values2results=True,
add_adata_var_column_key_list=["feature_class"],
save_log=False,
)
This adds nested outputs such as:
drug_minus_predoseDrug_values,vehicle_minus_predoseVeh_values,drug_diffcontrol_minus_vehicle_diffcontrol_values,ttest_rel_nested_*,WilcoxonSigned_nested_*,normality columns for nested paired differences,
merged feature annotations from
adata.var.
Current implementation note:
Nested comparison lists are accepted as lists of tuples, but the function currently uses the first tuple from
nested_groupby_key_target_valuesand the first tuple fromnested_groupby_key_ref_values.
Output table shape
The returned DataFrame is indexed by feature name.
Before any statistics are computed, the function removes variables with zero expression across all observations in the chosen matrix. As a result, fully zero features do not appear in the result index.
Common column families include:
mean:{group}_{value}CVpct:{group}_{value}PCTchange_target_reffc_target_refl2fc_target_ref<test>_stat...<test>_pvals...<test>_pvals_FDR...<test>_constVAR...shapiro_pvals: ...ks_pvals: ...
When add_values2results=True, the function also stores list-valued per-feature exports, for example:
<group>_values<target>_minus_<ref>_values<pair_by_key>_order
For independent workflows:
if
sortbynames a column inadata.obs, the order columns are named with that key, such asdrug_Age_order;otherwise the order falls back to observation names.
Save and storage behavior
CSV output
If save_table=True and save_path is set, the results table is written to CSV:
results = adtl.diff_test(
adata,
groupby_key="Treatment",
groupby_key_target_values=["drug"],
groupby_key_ref_values=["vehicle"],
tests=["ttest_ind"],
save_table=True,
save_path="results/diff_test.csv",
save_log=False,
)
Log file
If save_log=True, the function writes a log file at:
<save_path>.log
For example, save_path="results/diff_test.csv" produces results/diff_test.csv.log.
save_log=True requires save_path to be set.
adata.uns storage
If save_result_to_adata_uns_as_dict=True, the function stores results at:
adata.uns["diff_test_results"][f"{groupby_key}_{targets}_over_{refs}"]
Example key:
Treatment_drug_over_vehicle
Important behavior:
list-like columns ending in
_valuesor_orderare converted to JSON strings before storage inadata.uns;this keeps the payload writable through
h5adserialization;the returned
DataFrameremains unchanged and still contains Python list values in memory.
Validation and edge cases
The current implementation and regression tests lock in the following behavior.
Alternate-input validation
Partial alternate inputs are rejected. You cannot provide only
x_dfplus one ofobs_dforvar_df.obs_df.indexmust matchx_df.index.var_df.indexmust matchx_df.columns.
Pairing validation
Paired and nested paired tests require
pair_by_key.If paired groups do not share any IDs on
pair_by_key, the function raises a no-overlap error.If target/control and ref/control nested pair orders do not match, the function raises a mismatched-pairing error.
Non-finite values
np.nan,np.inf, and-np.infare sanitized toNaNin the statistic paths before computing tests and summary metrics.This allows non-finite features to stay in the table if enough finite observations remain for the requested calculations.
Constant-variance features
Features that are constant within both compared groups, or constant in paired differences, remain in the output.
For those features, the relevant
<test>_constVAR...column isTrue.The corresponding test statistic and p-value columns are set to
NaN.
Practical notes
save_logdefaults toTruein the function signature, so examples that do not want file output should passsave_log=False.use_raw=Trueswitches the data source toadata.rawwhen available.add_adata_var_column_key_listmerges selectedadata.varcolumns onto the result table after the statistical outputs are built.diff_test(...)is a statistics-and-export utility. For model-based regression summaries, see_model_fit.md.