From 9cb4b9d56eb89acd61467f384e0f631d52bb9977 Mon Sep 17 00:00:00 2001 From: AmandaBirmingham Date: Wed, 24 Apr 2024 16:44:31 -0700 Subject: [PATCH] Switch to fits based on raw counts rather than CPMS--simpler but requires test rewrites --- docs/absolute_quant_example.xlsx | Bin 35690 -> 66304 bytes pysyndna/src/calc_cell_counts.py | 41 +- pysyndna/src/fit_syndna_models.py | 43 +- pysyndna/tests/data/modelling_input.tsv | 392 +---------------- pysyndna/tests/data/modelling_output.tsv | 47 +-- pysyndna/tests/data/models.yml | 30 +- pysyndna/tests/test_calc_cell_counts.py | 512 +++++++++++++---------- pysyndna/tests/test_fit_syndna_models.py | 234 +++-------- 8 files changed, 424 insertions(+), 875 deletions(-) diff --git a/docs/absolute_quant_example.xlsx b/docs/absolute_quant_example.xlsx index 1b932aea51af6c4cb3fb1d3a3bb05c04be682037..07e61dd4353d1f9bbb68838c3380acd000176c45 100644 GIT binary patch delta 62145 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 diff --git a/pysyndna/src/calc_cell_counts.py b/pysyndna/src/calc_cell_counts.py index 8604125..f294eb1 100644 --- a/pysyndna/src/calc_cell_counts.py +++ b/pysyndna/src/calc_cell_counts.py @@ -31,8 +31,7 @@ SEQUENCED_SAMPLE_GDNA_MASS_NG_KEY = 'sequenced_sample_gdna_mass_ng' OGU_ID_KEY = 'ogu_id' OGU_READ_COUNT_KEY = 'ogu_read_count' -OGU_CPM_KEY = 'ogu_CPM' -LOG_10_OGU_CPM_KEY = 'log10_ogu_CPM' +LOG_10_OGU_READ_COUNT_KEY = 'log10_ogu_read_count' OGU_PERCENT_COVERAGE_KEY = 'percent_coverage_of_ogu' TOTAL_OGU_READS_KEY = 'total_reads_per_ogu' LOG_10_OGU_GDNA_MASS_NG_KEY = 'log10_ogu_gdna_mass_ng' @@ -365,16 +364,11 @@ def _calc_ogu_cell_counts_df_for_sample( sample_df = working_df[ working_df[SAMPLE_ID_KEY] == sample_id].copy() - # get the total reads sequenced for this sample - sample_total_reads = per_sample_info_df.loc[ - per_sample_info_df[SAMPLE_ID_KEY] == sample_id, - SAMPLE_TOTAL_READS_KEY].values[0] - # predict mass of each OGU's gDNA in this sample from its counts # using the linear model ogu_gdna_masses = _calc_ogu_gdna_mass_ng_series_for_sample( sample_df, linregress_result[SLOPE_KEY], - linregress_result[INTERCEPT_KEY], sample_total_reads) + linregress_result[INTERCEPT_KEY]) sample_df[OGU_GDNA_MASS_NG_KEY] = \ sample_df[OGU_ID_KEY].map(ogu_gdna_masses) @@ -413,8 +407,7 @@ def _calc_ogu_cell_counts_df_for_sample( def _calc_ogu_gdna_mass_ng_series_for_sample( sample_df: pd.DataFrame, sample_linregress_slope: float, - sample_linregress_intercept: float, - sample_total_reads: int) -> pd.Series: + sample_linregress_intercept: float) -> pd.Series: """Calculates mass of OGU gDNA in ng for each OGU in a sample. @@ -427,9 +420,6 @@ def _calc_ogu_gdna_mass_ng_series_for_sample( Slope of the linear regression model for the sample. sample_linregress_intercept: float Intercept of the linear regression model for the sample. - sample_total_reads: int - Total number of reads for the sample (including all reads, not just - aligned ones). Returns ------- @@ -439,23 +429,26 @@ def _calc_ogu_gdna_mass_ng_series_for_sample( """ working_df = sample_df.copy() - # add a column of counts per million (CPM) for each ogu by dividing - # each read_count by the total number of reads for this sample - # and then multiplying by a million (1,000,000) - # NB: dividing int/int in python gives float - working_df[OGU_CPM_KEY] = (working_df[OGU_READ_COUNT_KEY] / - sample_total_reads) * 1000000 + # NOTE that the linear regressions were originally done as described in + # the Zaramela et al notebooks, where the log10 of the CPM values were + # used as the independent variable. Later scripts by Oriane Moyne + # showed that this is not necessary and that it is equivalent to simply + # use log10 of the read counts as the independent variable (as long as it + # is used for *both* the fit and the prediction, of course!). Please see + # documentation on the fit_syndna_models.src._fit_linear_regression_models + # method for a full description of this change. - # add column of log10(ogu CPM) by taking log base 10 of the ogu CPM column - working_df[LOG_10_OGU_CPM_KEY] = np.log10(working_df[OGU_CPM_KEY]) + # add column of log10(ogu read counts) + working_df[LOG_10_OGU_READ_COUNT_KEY] = \ + np.log10(working_df[OGU_READ_COUNT_KEY]) # calculate log10(ogu gdna mass) of each OGU's gDNA in this sample - # by multiplying each OGU's log10(ogu CPM) by the slope of this sample's - # regression model and adding the model's intercept. + # by multiplying each OGU's log10(ogu read count) by the slope of this + # sample's regression model and adding the model's intercept. # NB: this requires that the linear regression models were derived # using synDNA masses *in ng* and not in some other unit. working_df[LOG_10_OGU_GDNA_MASS_NG_KEY] = ( - working_df[LOG_10_OGU_CPM_KEY] * + working_df[LOG_10_OGU_READ_COUNT_KEY] * sample_linregress_slope + sample_linregress_intercept) diff --git a/pysyndna/src/fit_syndna_models.py b/pysyndna/src/fit_syndna_models.py index 22aab11..22bc290 100644 --- a/pysyndna/src/fit_syndna_models.py +++ b/pysyndna/src/fit_syndna_models.py @@ -22,8 +22,7 @@ SYNDNA_POOL_MASS_NG_KEY = 'mass_syndna_input_ng' SAMPLE_TOTAL_READS_KEY = 'raw_reads_r1r2' SYNDNA_COUNTS_KEY = 'read_count' -COUNTS_PER_MIL_KEY = 'CPM' -LOG10_COUNTS_PER_MIL_KEY = 'log10_CPM' +LOG10_SYNDNA_COUNTS_KEY = 'log10_read_count' SYNDNA_INDIV_NG_KEY = 'syndna_ng' LOG10_SYNDNA_INDIV_NG_KEY = 'log10_syndna_ng' LIN_REGRESS_RESULT_KEY = 'lin_regress_by_sample_id' @@ -206,15 +205,32 @@ def _fit_linear_regression_models(working_df: pd.DataFrame) -> \ This function fits a linear regression model for each sample, predicting log10(mass of instances of a sequence) within a sample - from log10(counts per million for that sequence) within the sample, + from log10(read counts for that sequence) within the sample, using spike-in data from synDNAs. + Note that this function originally followed the R notebooks for Zaramela + et al. and fit the log10 of the mass of the syndna in the sample to the + log10 of the counts per million of the read for that syndna in the sample. + However, later work by Oriane Moyne left out the CPMs and demonstrated + that one can achieve the same end result (of OGU mass predictions) by + fitting the log10 mass of the syndna in the sample to log10 of the read + counts themselves. (The slope of the fits are the same in both cases, + while the intercepts differ by a constant factor because the CPM + calculation modifies the read count by a constant factor--dividing by + total reads in the sample and multiplying by a million.) When one uses + the resulting fits on the raw read counts for OGUs, one gets the same + mass predictions as when using the fits on the CPMs on the CPMs for the + OGUs. (HOWEVER, since I know it will come up: note that this is not true if + one looks at the mass predictions from the original Zaramela R notebooks, + which unintentionally used a different total counts value for the CPM + calculation in the fit than they used for the CPM calculation in the + OGU mass prediction, leading to inconsistent results.) + Parameters ---------- working_df: pd.DataFrame Long-form dataframe containing at least SAMPLE_ID_KEY, - SYNDNA_COUNTS_KEY, SAMPLE_TOTAL_READS_KEY, and - SYNDNA_INDIV_NG_KEY columns. + SYNDNA_COUNTS_KEY, and SYNDNA_INDIV_NG_KEY columns. Returns ------- @@ -230,23 +246,16 @@ def _fit_linear_regression_models(working_df: pd.DataFrame) -> \ # drop any rows where the count value is 0--can't take log of 0 working_df = working_df[working_df[SYNDNA_COUNTS_KEY] > 0].copy() - # add a column of counts per million (CPM) by dividing the count value - # in each read_count by the total number of reads for its sample_id and - # then multiplying by a million (1,000,000) - working_df.loc[:, COUNTS_PER_MIL_KEY] = \ - (working_df[SYNDNA_COUNTS_KEY] / - working_df[SAMPLE_TOTAL_READS_KEY]) * 1000000 - - # add a column of log10(CMP) by taking the log base 10 of the CPM column - working_df.loc[:, LOG10_COUNTS_PER_MIL_KEY] = \ - np.log10(working_df[COUNTS_PER_MIL_KEY]) + # add a column for the log10 of the syndna read count column + working_df.loc[:, LOG10_SYNDNA_COUNTS_KEY] = \ + np.log10(working_df[SYNDNA_COUNTS_KEY]) # add a column for the log10 of the syndna ng column working_df.loc[:, LOG10_SYNDNA_INDIV_NG_KEY] = \ np.log10(working_df[SYNDNA_INDIV_NG_KEY]) # loop over each sample id and fit a linear regression model predicting - # log10(dna ng) from log10(counts per million) + # log10(dna ng) from log10(syndna read counts) linregress_by_sample_id = {} log_msgs_list = [] for curr_sample_id in working_df[SAMPLE_ID_KEY].unique(): @@ -255,7 +264,7 @@ def _fit_linear_regression_models(working_df: pd.DataFrame) -> \ try: curr_linregress_result = scipy.stats.linregress( - curr_sample_df[LOG10_COUNTS_PER_MIL_KEY], + curr_sample_df[LOG10_SYNDNA_COUNTS_KEY], curr_sample_df[LOG10_SYNDNA_INDIV_NG_KEY]) except Exception: # TODO: I need to know what kind of errors this can throw; diff --git a/pysyndna/tests/data/modelling_input.tsv b/pysyndna/tests/data/modelling_input.tsv index 96b5318..a683644 100644 --- a/pysyndna/tests/data/modelling_input.tsv +++ b/pysyndna/tests/data/modelling_input.tsv @@ -1,376 +1,16 @@ -# The data in this file is based on the contents of the filled synDNAMetaAgM object -# in its state immediately before cell [37] in the notebook at -# https://github.com/lzaramela/SynDNA/blob/main/SynDNA_saliva_linear_models.ipynb . -# The following columns have been renamed to match the column name expectations of -# the code under test: -# ID -> sample_name -# Counts -> read_count -# TotalReads -> raw_reads_r1r2 -# SynDNA -> syndna_id -# Additionally, the syndna_ng has been added, with values inferred from the Dilution2 -# column. The regression models in the notebook are trained to predict Dilution2, and -# in https://github.com/lzaramela/SynDNA/blob/main/SynDNA_saliva_samples_analysis.ipynb -# it is stated that read weight = 10^(-predicted_value) -# so the syndna_ng = 10^(-1*Dilution2) -# All other columns were deleted. -sample_name read_count raw_reads_r1r2 syndna_id syndna_ng -A1_pool1_Fwd 93135 3216923 p126 0.1 -A1_pool1_Rev 90897 3216923 p126 0.1 -C1_pool1_Fwd 56878 1723417 p126 0.1 -C1_pool1_Rev 56064 1723417 p126 0.1 -D1_pool1_Fwd 49065 2606004 p126 0.1 -D1_pool1_Rev 47258 2606004 p126 0.1 -E1_pool1_Fwd 25180 4755108 p126 0.1 -E1_pool1_Rev 44711 4755108 p126 0.1 -F1_pool1_Fwd 29056 4399124 p126 0.1 -F1_pool1_Rev 89709 4399124 p126 0.1 -H1_pool1_Fwd 47599 3333446 p126 0.1 -H1_pool1_Rev 84648 3333446 p126 0.1 -A1_pool2_Fwd 43 3969121 p126 0.00001 -A1_pool2_Rev 43 3969121 p126 0.00001 -C1_pool2_Fwd 53 3076204 p126 0.00001 -C1_pool2_Rev 52 3076204 p126 0.00001 -D1_pool2_Fwd 38 3876399 p126 0.00001 -D1_pool2_Rev 37 3876399 p126 0.00001 -E1_pool2_Fwd 7 5129999 p126 0.00001 -E1_pool2_Rev 20 5129999 p126 0.00001 -F1_pool2_Fwd 9 2441440 p126 0.00001 -F1_pool2_Rev 21 2441440 p126 0.00001 -H1_pool2_Fwd 8 2342284 p126 0.00001 -H1_pool2_Rev 7 2342284 p126 0.00001 -A1_pool3_Fwd 7437 2586532 p126 0.01 -A1_pool3_Rev 7335 2586532 p126 0.01 -C1_pool3_Fwd 6754 2782849 p126 0.01 -C1_pool3_Rev 6420 2782849 p126 0.01 -D1_pool3_Fwd 1813 4195427 p126 0.01 -D1_pool3_Rev 3514 4195427 p126 0.01 -E1_pool3_Fwd 1311 5384906 p126 0.01 -E1_pool3_Rev 2611 5384906 p126 0.01 -F1_pool3_Fwd 1359 2202357 p126 0.01 -F1_pool3_Rev 2243 2202357 p126 0.01 -H1_pool3_Fwd 3961 2261101 p126 0.01 -H1_pool3_Rev 3860 2261101 p126 0.01 -A1_pool1_Fwd 15190 3216923 p136 0.01 -A1_pool1_Rev 15002 3216923 p136 0.01 -C1_pool1_Fwd 10308 1723417 p136 0.01 -C1_pool1_Rev 10212 1723417 p136 0.01 -D1_pool1_Fwd 8086 2606004 p136 0.01 -D1_pool1_Rev 7868 2606004 p136 0.01 -E1_pool1_Fwd 5787 4755108 p136 0.01 -E1_pool1_Rev 7217 4755108 p136 0.01 -F1_pool1_Fwd 9342 4399124 p136 0.01 -F1_pool1_Rev 15057 4399124 p136 0.01 -H1_pool1_Fwd 11411 3333446 p136 0.01 -H1_pool1_Rev 14005 3333446 p136 0.01 -A1_pool2_Fwd 309 3969121 p136 0.0001 -A1_pool2_Rev 296 3969121 p136 0.0001 -C1_pool2_Fwd 200 3076204 p136 0.0001 -C1_pool2_Rev 198 3076204 p136 0.0001 -D1_pool2_Fwd 156 3876399 p136 0.0001 -D1_pool2_Rev 171 3876399 p136 0.0001 -E1_pool2_Fwd 57 5129999 p136 0.0001 -E1_pool2_Rev 82 5129999 p136 0.0001 -F1_pool2_Fwd 92 2441440 p136 0.0001 -F1_pool2_Rev 105 2441440 p136 0.0001 -H1_pool2_Fwd 94 2342284 p136 0.0001 -H1_pool2_Rev 89 2342284 p136 0.0001 -A1_pool3_Fwd 19 2586532 p136 0.00001 -A1_pool3_Rev 20 2586532 p136 0.00001 -C1_pool3_Fwd 20 2782849 p136 0.00001 -C1_pool3_Rev 17 2782849 p136 0.00001 -D1_pool3_Fwd 6 4195427 p136 0.00001 -D1_pool3_Rev 11 4195427 p136 0.00001 -E1_pool3_Fwd 10 5384906 p136 0.00001 -E1_pool3_Rev 14 5384906 p136 0.00001 -F1_pool3_Fwd 11 2202357 p136 0.00001 -F1_pool3_Rev 11 2202357 p136 0.00001 -H1_pool3_Fwd 4 2261101 p136 0.00001 -H1_pool3_Rev 3 2261101 p136 0.00001 -A1_pool1_Fwd 2447 3216923 p146 0.001 -A1_pool1_Rev 2421 3216923 p146 0.001 -C1_pool1_Fwd 1637 1723417 p146 0.001 -C1_pool1_Rev 1597 1723417 p146 0.001 -D1_pool1_Fwd 1615 2606004 p146 0.001 -D1_pool1_Rev 1577 2606004 p146 0.001 -E1_pool1_Fwd 1308 4755108 p146 0.001 -E1_pool1_Rev 1525 4755108 p146 0.001 -F1_pool1_Fwd 1995 4399124 p146 0.001 -F1_pool1_Rev 2762 4399124 p146 0.001 -H1_pool1_Fwd 2082 3333446 p146 0.001 -H1_pool1_Rev 2443 3333446 p146 0.001 -A1_pool2_Fwd 2613 3969121 p146 0.001 -A1_pool2_Rev 2608 3969121 p146 0.001 -C1_pool2_Fwd 2067 3076204 p146 0.001 -C1_pool2_Rev 2044 3076204 p146 0.001 -D1_pool2_Fwd 1763 3876399 p146 0.001 -D1_pool2_Rev 1743 3876399 p146 0.001 -E1_pool2_Fwd 992 5129999 p146 0.001 -E1_pool2_Rev 1115 5129999 p146 0.001 -F1_pool2_Fwd 1198 2441440 p146 0.001 -F1_pool2_Rev 1360 2441440 p146 0.001 -H1_pool2_Fwd 1239 2342284 p146 0.001 -H1_pool2_Rev 1211 2342284 p146 0.001 -A1_pool3_Fwd 132504 2586532 p146 0.1 -A1_pool3_Rev 131671 2586532 p146 0.1 -C1_pool3_Fwd 147081 2782849 p146 0.1 -C1_pool3_Rev 143805 2782849 p146 0.1 -D1_pool3_Fwd 100972 4195427 p146 0.1 -D1_pool3_Rev 115473 4195427 p146 0.1 -E1_pool3_Fwd 80514 5384906 p146 0.1 -E1_pool3_Rev 95222 5384906 p146 0.1 -F1_pool3_Fwd 80140 2202357 p146 0.1 -F1_pool3_Rev 89378 2202357 p146 0.1 -H1_pool3_Fwd 116257 2261101 p146 0.1 -H1_pool3_Rev 115382 2261101 p146 0.1 -A1_pool1_Fwd 308 3216923 p156 0.0001 -A1_pool1_Rev 296 3216923 p156 0.0001 -C1_pool1_Fwd 216 1723417 p156 0.0001 -C1_pool1_Rev 206 1723417 p156 0.0001 -D1_pool1_Fwd 220 2606004 p156 0.0001 -D1_pool1_Rev 211 2606004 p156 0.0001 -E1_pool1_Fwd 165 4755108 p156 0.0001 -E1_pool1_Rev 183 4755108 p156 0.0001 -F1_pool1_Fwd 308 4399124 p156 0.0001 -F1_pool1_Rev 367 4399124 p156 0.0001 -H1_pool1_Fwd 228 3333446 p156 0.0001 -H1_pool1_Rev 238 3333446 p156 0.0001 -A1_pool2_Fwd 32934 3969121 p156 0.01 -A1_pool2_Rev 32742 3969121 p156 0.01 -C1_pool2_Fwd 28465 3076204 p156 0.01 -C1_pool2_Rev 28239 3076204 p156 0.01 -D1_pool2_Fwd 23022 3876399 p156 0.01 -D1_pool2_Rev 22842 3876399 p156 0.01 -E1_pool2_Fwd 14084 5129999 p156 0.01 -E1_pool2_Rev 15357 5129999 p156 0.01 -F1_pool2_Fwd 16135 2441440 p156 0.01 -F1_pool2_Rev 17833 2441440 p156 0.01 -H1_pool2_Fwd 16673 2342284 p156 0.01 -H1_pool2_Rev 16378 2342284 p156 0.01 -A1_pool3_Fwd 0 2586532 p156 0.0001 -A1_pool3_Rev 0 2586532 p156 0.0001 -C1_pool3_Fwd 2 2782849 p156 0.0001 -C1_pool3_Rev 2 2782849 p156 0.0001 -D1_pool3_Fwd 0 4195427 p156 0.0001 -D1_pool3_Rev 0 4195427 p156 0.0001 -E1_pool3_Fwd 0 5384906 p156 0.0001 -E1_pool3_Rev 0 5384906 p156 0.0001 -F1_pool3_Fwd 0 2202357 p156 0.0001 -F1_pool3_Rev 0 2202357 p156 0.0001 -H1_pool3_Fwd 1 2261101 p156 0.0001 -H1_pool3_Rev 1 2261101 p156 0.0001 -A1_pool1_Fwd 77 3216923 p166 0.00001 -A1_pool1_Rev 77 3216923 p166 0.00001 -C1_pool1_Fwd 53 1723417 p166 0.00001 -C1_pool1_Rev 50 1723417 p166 0.00001 -D1_pool1_Fwd 47 2606004 p166 0.00001 -D1_pool1_Rev 43 2606004 p166 0.00001 -E1_pool1_Fwd 58 4755108 p166 0.00001 -E1_pool1_Rev 64 4755108 p166 0.00001 -F1_pool1_Fwd 59 4399124 p166 0.00001 -F1_pool1_Rev 70 4399124 p166 0.00001 -H1_pool1_Fwd 44 3333446 p166 0.00001 -H1_pool1_Rev 53 3333446 p166 0.00001 -A1_pool2_Fwd 370813 3969121 p166 0.1 -A1_pool2_Rev 365291 3969121 p166 0.1 -C1_pool2_Fwd 333256 3076204 p166 0.1 -C1_pool2_Rev 325222 3076204 p166 0.1 -D1_pool2_Fwd 258698 3876399 p166 0.1 -D1_pool2_Rev 254361 3876399 p166 0.1 -E1_pool2_Fwd 154325 5129999 p166 0.1 -E1_pool2_Rev 161531 5129999 p166 0.1 -F1_pool2_Fwd 179862 2441440 p166 0.1 -F1_pool2_Rev 193064 2441440 p166 0.1 -H1_pool2_Fwd 184524 2342284 p166 0.1 -H1_pool2_Rev 180271 2342284 p166 0.1 -A1_pool3_Fwd 3566 2586532 p166 0.001 -A1_pool3_Rev 3493 2586532 p166 0.001 -C1_pool3_Fwd 4371 2782849 p166 0.001 -C1_pool3_Rev 4296 2782849 p166 0.001 -D1_pool3_Fwd 3212 4195427 p166 0.001 -D1_pool3_Rev 3368 4195427 p166 0.001 -E1_pool3_Fwd 2832 5384906 p166 0.001 -E1_pool3_Rev 3049 5384906 p166 0.001 -F1_pool3_Fwd 2455 2202357 p166 0.001 -F1_pool3_Rev 2629 2202357 p166 0.001 -H1_pool3_Fwd 3059 2261101 p166 0.001 -H1_pool3_Rev 2955 2261101 p166 0.001 -A1_pool1_Fwd 149 3216923 p226 0.00001 -A1_pool1_Rev 148 3216923 p226 0.00001 -C1_pool1_Fwd 84 1723417 p226 0.00001 -C1_pool1_Rev 82 1723417 p226 0.00001 -D1_pool1_Fwd 96 2606004 p226 0.00001 -D1_pool1_Rev 79 2606004 p226 0.00001 -E1_pool1_Fwd 50 4755108 p226 0.00001 -E1_pool1_Rev 77 4755108 p226 0.00001 -F1_pool1_Fwd 64 4399124 p226 0.00001 -F1_pool1_Rev 148 4399124 p226 0.00001 -H1_pool1_Fwd 65 3333446 p226 0.00001 -H1_pool1_Rev 120 3333446 p226 0.00001 -A1_pool2_Fwd 115005 3969121 p226 0.1 -A1_pool2_Rev 114633 3969121 p226 0.1 -C1_pool2_Fwd 79301 3076204 p226 0.1 -C1_pool2_Rev 77693 3076204 p226 0.1 -D1_pool2_Fwd 73145 3876399 p226 0.1 -D1_pool2_Rev 73438 3876399 p226 0.1 -E1_pool2_Fwd 23052 5129999 p226 0.1 -E1_pool2_Rev 40658 5129999 p226 0.1 -F1_pool2_Fwd 25628 2441440 p226 0.1 -F1_pool2_Rev 45429 2441440 p226 0.1 -H1_pool2_Fwd 42652 2342284 p226 0.1 -H1_pool2_Rev 40369 2342284 p226 0.1 -A1_pool3_Fwd 10229 2586532 p226 0.01 -A1_pool3_Rev 10057 2586532 p226 0.01 -C1_pool3_Fwd 8954 2782849 p226 0.01 -C1_pool3_Rev 8521 2782849 p226 0.01 -D1_pool3_Fwd 2771 4195427 p226 0.01 -D1_pool3_Rev 4755 4195427 p226 0.01 -E1_pool3_Fwd 1988 5384906 p226 0.01 -E1_pool3_Rev 3651 5384906 p226 0.01 -F1_pool3_Fwd 2033 2202357 p226 0.01 -F1_pool3_Rev 3044 2202357 p226 0.01 -H1_pool3_Fwd 5264 2261101 p226 0.01 -H1_pool3_Rev 5113 2261101 p226 0.01 -A1_pool1_Fwd 1075 3216923 p236 0.0001 -A1_pool1_Rev 1059 3216923 p236 0.0001 -C1_pool1_Fwd 714 1723417 p236 0.0001 -C1_pool1_Rev 690 1723417 p236 0.0001 -D1_pool1_Fwd 529 2606004 p236 0.0001 -D1_pool1_Rev 521 2606004 p236 0.0001 -E1_pool1_Fwd 426 4755108 p236 0.0001 -E1_pool1_Rev 564 4755108 p236 0.0001 -F1_pool1_Fwd 581 4399124 p236 0.0001 -F1_pool1_Rev 975 4399124 p236 0.0001 -H1_pool1_Fwd 749 3333446 p236 0.0001 -H1_pool1_Rev 980 3333446 p236 0.0001 -A1_pool2_Fwd 19813 3969121 p236 0.01 -A1_pool2_Rev 19790 3969121 p236 0.01 -C1_pool2_Fwd 14903 3076204 p236 0.01 -C1_pool2_Rev 14712 3076204 p236 0.01 -D1_pool2_Fwd 11496 3876399 p236 0.01 -D1_pool2_Rev 11629 3876399 p236 0.01 -E1_pool2_Fwd 4816 5129999 p236 0.01 -E1_pool2_Rev 6642 5129999 p236 0.01 -F1_pool2_Fwd 5653 2441440 p236 0.01 -F1_pool2_Rev 7616 2441440 p236 0.01 -H1_pool2_Fwd 6851 2342284 p236 0.01 -H1_pool2_Rev 6641 2342284 p236 0.01 -A1_pool3_Fwd 170386 2586532 p236 0.1 -A1_pool3_Rev 168909 2586532 p236 0.1 -C1_pool3_Fwd 146576 2782849 p236 0.1 -C1_pool3_Rev 142508 2782849 p236 0.1 -D1_pool3_Fwd 66073 4195427 p236 0.1 -D1_pool3_Rev 91892 4195427 p236 0.1 -E1_pool3_Fwd 53787 5384906 p236 0.1 -E1_pool3_Rev 75963 5384906 p236 0.1 -F1_pool3_Fwd 55786 2202357 p236 0.1 -F1_pool3_Rev 70265 2202357 p236 0.1 -H1_pool3_Fwd 104293 2261101 p236 0.1 -H1_pool3_Rev 102846 2261101 p236 0.1 -A1_pool1_Fwd 3189 3216923 p246 0.001 -A1_pool1_Rev 3129 3216923 p246 0.001 -C1_pool1_Fwd 2128 1723417 p246 0.001 -C1_pool1_Rev 2097 1723417 p246 0.001 -D1_pool1_Fwd 2064 2606004 p246 0.001 -D1_pool1_Rev 2055 2606004 p246 0.001 -E1_pool1_Fwd 1645 4755108 p246 0.001 -E1_pool1_Rev 1912 4755108 p246 0.001 -F1_pool1_Fwd 2512 4399124 p246 0.001 -F1_pool1_Rev 3464 4399124 p246 0.001 -H1_pool1_Fwd 2674 3333446 p246 0.001 -H1_pool1_Rev 3206 3333446 p246 0.001 -A1_pool2_Fwd 3603 3969121 p246 0.001 -A1_pool2_Rev 3527 3969121 p246 0.001 -C1_pool2_Fwd 2976 3076204 p246 0.001 -C1_pool2_Rev 2936 3076204 p246 0.001 -D1_pool2_Fwd 2334 3876399 p246 0.001 -D1_pool2_Rev 2329 3876399 p246 0.001 -E1_pool2_Fwd 1278 5129999 p246 0.001 -E1_pool2_Rev 1457 5129999 p246 0.001 -F1_pool2_Fwd 1381 2441440 p246 0.001 -F1_pool2_Rev 1621 2441440 p246 0.001 -H1_pool2_Fwd 1556 2342284 p246 0.001 -H1_pool2_Rev 1564 2342284 p246 0.001 -A1_pool3_Fwd 10 2586532 p246 0.00001 -A1_pool3_Rev 11 2586532 p246 0.00001 -C1_pool3_Fwd 23 2782849 p246 0.00001 -C1_pool3_Rev 22 2782849 p246 0.00001 -D1_pool3_Fwd 14 4195427 p246 0.00001 -D1_pool3_Rev 18 4195427 p246 0.00001 -E1_pool3_Fwd 15 5384906 p246 0.00001 -E1_pool3_Rev 15 5384906 p246 0.00001 -F1_pool3_Fwd 10 2202357 p246 0.00001 -F1_pool3_Rev 12 2202357 p246 0.00001 -H1_pool3_Fwd 24 2261101 p246 0.00001 -H1_pool3_Rev 23 2261101 p246 0.00001 -A1_pool1_Fwd 25347 3216923 p256 0.01 -A1_pool1_Rev 24856 3216923 p256 0.01 -C1_pool1_Fwd 17385 1723417 p256 0.01 -C1_pool1_Rev 17027 1723417 p256 0.01 -D1_pool1_Fwd 17074 2606004 p256 0.01 -D1_pool1_Rev 16836 2606004 p256 0.01 -E1_pool1_Fwd 14323 4755108 p256 0.01 -E1_pool1_Rev 15570 4755108 p256 0.01 -F1_pool1_Fwd 21205 4399124 p256 0.01 -F1_pool1_Rev 27240 4399124 p256 0.01 -H1_pool1_Fwd 20780 3333446 p256 0.01 -H1_pool1_Rev 23705 3333446 p256 0.01 -A1_pool2_Fwd 369 3969121 p256 0.0001 -A1_pool2_Rev 364 3969121 p256 0.0001 -C1_pool2_Fwd 267 3076204 p256 0.0001 -C1_pool2_Rev 281 3076204 p256 0.0001 -D1_pool2_Fwd 233 3876399 p256 0.0001 -D1_pool2_Rev 234 3876399 p256 0.0001 -E1_pool2_Fwd 126 5129999 p256 0.0001 -E1_pool2_Rev 145 5129999 p256 0.0001 -F1_pool2_Fwd 173 2441440 p256 0.0001 -F1_pool2_Rev 174 2441440 p256 0.0001 -H1_pool2_Fwd 194 2342284 p256 0.0001 -H1_pool2_Rev 196 2342284 p256 0.0001 -A1_pool3_Fwd 210 2586532 p256 0.0001 -A1_pool3_Rev 213 2586532 p256 0.0001 -C1_pool3_Fwd 204 2782849 p256 0.0001 -C1_pool3_Rev 199 2782849 p256 0.0001 -D1_pool3_Fwd 153 4195427 p256 0.0001 -D1_pool3_Rev 158 4195427 p256 0.0001 -E1_pool3_Fwd 140 5384906 p256 0.0001 -E1_pool3_Rev 157 5384906 p256 0.0001 -F1_pool3_Fwd 127 2202357 p256 0.0001 -F1_pool3_Rev 140 2202357 p256 0.0001 -H1_pool3_Fwd 169 2261101 p256 0.0001 -H1_pool3_Rev 168 2261101 p256 0.0001 -A1_pool1_Fwd 237329 3216923 p266 0.1 -A1_pool1_Rev 230898 3216923 p266 0.1 -C1_pool1_Fwd 163444 1723417 p266 0.1 -C1_pool1_Rev 159927 1723417 p266 0.1 -D1_pool1_Fwd 160215 2606004 p266 0.1 -D1_pool1_Rev 157659 2606004 p266 0.1 -E1_pool1_Fwd 132842 4755108 p266 0.1 -E1_pool1_Rev 140392 4755108 p266 0.1 -F1_pool1_Fwd 208828 4399124 p266 0.1 -F1_pool1_Rev 256378 4399124 p266 0.1 -H1_pool1_Fwd 186374 3333446 p266 0.1 -H1_pool1_Rev 208570 3333446 p266 0.1 -A1_pool2_Fwd 35 3969121 p266 0.00001 -A1_pool2_Rev 33 3969121 p266 0.00001 -C1_pool2_Fwd 25 3076204 p266 0.00001 -C1_pool2_Rev 26 3076204 p266 0.00001 -D1_pool2_Fwd 35 3876399 p266 0.00001 -D1_pool2_Rev 34 3876399 p266 0.00001 -E1_pool2_Fwd 21 5129999 p266 0.00001 -E1_pool2_Rev 21 5129999 p266 0.00001 -F1_pool2_Fwd 17 2441440 p266 0.00001 -F1_pool2_Rev 19 2441440 p266 0.00001 -H1_pool2_Fwd 12 2342284 p266 0.00001 -H1_pool2_Rev 12 2342284 p266 0.00001 -A1_pool3_Fwd 1117 2586532 p266 0.001 -A1_pool3_Rev 1093 2586532 p266 0.001 -C1_pool3_Fwd 1450 2782849 p266 0.001 -C1_pool3_Rev 1432 2782849 p266 0.001 -D1_pool3_Fwd 1063 4195427 p266 0.001 -D1_pool3_Rev 1140 4195427 p266 0.001 -E1_pool3_Fwd 884 5384906 p266 0.001 -E1_pool3_Rev 963 5384906 p266 0.001 -F1_pool3_Fwd 762 2202357 p266 0.001 -F1_pool3_Rev 807 2202357 p266 0.001 -H1_pool3_Fwd 927 2261101 p266 0.001 -H1_pool3_Rev 921 2261101 p266 0.001 \ No newline at end of file +# The data in this file is based on the full data in the +# "zaramela linear reg CPM&counts" sheet of the "absolute_quant_example.xlsx" +# file, after removing columns not directly needed for the linear regression. +# Note that the data in that spreadsheet itself comes from the Zaramela et al. +# R notebooks; see that file for precise details. +sample_name read_count syndna_id syndna_ng +A 93135 p126 0.099999995 +A 15190 p136 0.01 +A 2447 p146 0.001 +A 308 p156 1E-04 +A 77 p166 1E-05 +A 149 p226 1E-05 +A 1075 p236 1E-04 +A 3189 p246 0.001 +A 25347 p256 0.01 +A 237329 p266 0.099999995 \ No newline at end of file diff --git a/pysyndna/tests/data/modelling_output.tsv b/pysyndna/tests/data/modelling_output.tsv index 8687675..37d7391 100644 --- a/pysyndna/tests/data/modelling_output.tsv +++ b/pysyndna/tests/data/modelling_output.tsv @@ -1,44 +1,5 @@ -# This file is based on -# https://github.com/lzaramela/SynDNA/blob/main/data/saliva_linear_models.tsv -# with the values of the a_intercept and b_intercept columns negated -# (because the Zaramela code generates regression models that predict the -# *negative* log10 of the read weight while the code under test predicts just -# log10 of the read weight.) -# All other columns were deleted. +# The fit in this file is the +# "sample A regression of log10_syndna_ng from log10_raw_counts" fit in the +# "zaramela linear reg CPM&counts" sheet of the "absolute_quant_example.xlsx". ID a_intercept b_slope -A1_pool1_Fwd -6.775395054 1.244876524 -A1_pool1_Rev -6.771631397 1.246759136 -C1_pool1_Fwd -6.878100856 1.241529167 -C1_pool1_Rev -6.848393273 1.236655876 -D1_pool1_Fwd -6.642112139 1.250771588 -D1_pool1_Rev -6.575257456 1.236368669 -E1_pool1_Fwd -6.209075909 1.267741008 -E1_pool1_Rev -6.34471994 1.274278208 -F1_pool1_Fwd -6.38086082 1.244062976 -F1_pool1_Rev -6.605655262 1.239447269 -H1_pool1_Fwd -6.440192081 1.205881471 -H1_pool1_Rev -6.588296111 1.212090029 -A1_pool2_Fwd -6.041983871 1.061187469 -A1_pool2_Rev -6.02539348 1.058027999 -C1_pool2_Fwd -6.056199167 1.060877447 -C1_pool2_Rev -6.069492391 1.065961909 -D1_pool2_Fwd -5.938894948 1.083900408 -D1_pool2_Rev -5.944595969 1.085696514 -E1_pool2_Fwd -5.303220104 1.027856635 -E1_pool2_Rev -5.530968001 1.070901249 -F1_pool2_Fwd -5.727181515 1.034888555 -F1_pool2_Rev -5.901821005 1.058843954 -H1_pool2_Fwd -5.674505663 0.998484653 -H1_pool2_Rev -5.648429195 0.993954412 -A1_pool3_Fwd -5.76337605 1.00578283 -A1_pool3_Rev -5.793669519 1.015138091 -C1_pool3_Fwd -5.133881506 0.828831285 -C1_pool3_Rev -5.117345873 0.828171831 -D1_pool3_Fwd -5.343334407 1.024461683 -D1_pool3_Rev -5.545901564 1.052359855 -E1_pool3_Fwd -5.316389176 1.081948957 -E1_pool3_Rev -5.440241897 1.080885471 -F1_pool3_Fwd -5.665952742 1.067055184 -F1_pool3_Rev -5.740173228 1.063047339 -H1_pool3_Fwd -4.930454427 0.787220436 -H1_pool3_Rev -4.900925447 0.781458157 \ No newline at end of file +A -7.40709604550579 1.244876524 \ No newline at end of file diff --git a/pysyndna/tests/data/models.yml b/pysyndna/tests/data/models.yml index 97929b1..b2cafe6 100644 --- a/pysyndna/tests/data/models.yml +++ b/pysyndna/tests/data/models.yml @@ -1,22 +1,22 @@ "example1": "slope": 1.24487652379132 - "intercept": -6.77539505390338 - "rvalue": 0.9865030975156575 - "pvalue": 1.428443560659758e-07 - "stderr": 0.07305408550335003 - "intercept_stderr": 0.2361976278251443 + "intercept": -7.35593916054843 + "rvalue": 0.986503097515657 + "pvalue": 1.42844356065977E-07 + "stderr": 0.0730540855033502 + "intercept_stderr": 0.271274537363401 "example2": "slope": 1.24675913604407 - "intercept": -7.155318973708384 - "rvalue": 0.9863241797356326 - "pvalue": 1.505381146809759e-07 - "stderr": 0.07365795255302438 - "intercept_stderr": 0.2563956755844754 + "intercept": -7.45004083037736 + "rvalue": 0.986324179735633 + "pvalue": 1.5053811468097E-07 + "stderr": 0.073657952553024 + "intercept_stderr": 0.2729411999326 # example4 is a copy of example2 "example4": "slope": 1.24675913604407 - "intercept": -7.155318973708384 - "rvalue": 0.9863241797356326 - "pvalue": 1.505381146809759e-07 - "stderr": 0.07365795255302438 - "intercept_stderr": 0.2563956755844754 \ No newline at end of file + "intercept": -7.45004083037736 + "rvalue": 0.986324179735633 + "pvalue": 1.5053811468097E-07 + "stderr": 0.073657952553024 + "intercept_stderr": 0.2729411999326 \ No newline at end of file diff --git a/pysyndna/tests/test_calc_cell_counts.py b/pysyndna/tests/test_calc_cell_counts.py index 48a3864..6aaeb44 100644 --- a/pysyndna/tests/test_calc_cell_counts.py +++ b/pysyndna/tests/test_calc_cell_counts.py @@ -36,26 +36,28 @@ class TestCalcCellCountsData: # and a lower gdna density value (which leadds to a smaller amount of # syndna pool and thus a bunch of interesting calculation knock-ons). - # The example1 linear model is what we get from running Zaramela's - # "A1_pool1_Fwd" sample data through the linear modelling code (see - # modelling_input.tsv and test_fit_syndna_models.py.) The slope and - # intercept (all that is provided by Zaramela) match those from that - # work (see modelling_output.tsv for details). - # The example2 linear model is what we get from running the linear - # modelling code on the "Sample B" data from test_fit_syndna_models.py; - # it does not match any Zaramela results because Sample B is made up. - # See FitSyndnaModelsTest.lingress_results comments for details. + # The example1 (Sample A) and example2 (Sample B) values are taken from + # FitSyndnaModelsTest.lingress_results, which gets them from those + # calculated in Excel (see results for full data + # on "linear regressions counts" sheet of "absolute_quant_example.xlsx"). + # Note that these do not and *should* NOT be expected to match any results + # in Zaramela's linear models; see FitSyndnaModelsTest.lingress_results + # comments for details. linregresses_dict = { 'example1': { - "slope": 1.24487652379132, "intercept": -6.77539505390338, - "rvalue": 0.9865030975156575, "pvalue": 1.428443560659758e-07, - "stderr": 0.07305408550335003, - "intercept_stderr": 0.2361976278251443}, + "slope": 1.24487652379132, + "intercept": -7.35593916054843, + "rvalue": 0.986503097515657, + "pvalue": 1.42844356065977E-07, + "stderr": 0.0730540855033502, + "intercept_stderr": 0.271274537363401}, 'example2': { - "slope": 1.24675913604407, "intercept": -7.155318973708384, - "rvalue": 0.9863241797356326, "pvalue": 1.505381146809759e-07, - "stderr": 0.07365795255302438, - "intercept_stderr": 0.2563956755844754} + "slope": 1.24675913604407, + "intercept": -7.45004083037736, + "rvalue": 0.986324179735633, + "pvalue": 1.5053811468097E-07, + "stderr": 0.073657952553024, + "intercept_stderr": 0.2729411999326} } # Values from "absolute_quant_example.xlsx" @@ -73,7 +75,7 @@ class TestCalcCellCountsData: # the OGU_READ_COUNT_KEY values for each sample mass_and_totals_dict = { SAMPLE_ID_KEY: ["example1", "example2"], - SAMPLE_TOTAL_READS_KEY: [472140, 611913], + SAMPLE_TOTAL_READS_KEY: [3216923, 611913], SEQUENCED_SAMPLE_GDNA_MASS_NG_KEY: [5, 4.76], GDNA_MASS_TO_SAMPLE_MASS_RATIO_KEY: [7.1867431342E-06, 4.7470988923E-06] @@ -120,8 +122,8 @@ class TestCalcCellCountsData: # 26130 counts at position 8 (for N. subflava, bc example 2 has # zero counts here), by a factor of 10 at position 10 # (for F. periodonticum, bc example 2 has 10x the counts of example 1 - # at this position), and by 12133 at position 12 (for H. influenzae, - # bc example 2 has 12 counts here instead of 12145). + # at this position), and by 12045 at position 12 (for H. influenzae, + # bc example 2 has 100 counts here instead of 12145). # All other positions are identical. OGU_READ_COUNT_KEY: [79951, 93024, 86188, 45441, 31185, 24929, 1975, 0, 22303, 197830, 14478, 100, 14609] @@ -154,8 +156,9 @@ class TestCalcCellCountsData: 0.06528070535624650000], # for the arrays below, the 1st, 2nd, and 7th values (those for # L. gasseri, R. albus, and L. valderiana) match those worked out in - # detail for example1 in the "absolute_quant_example.xlsx" spreadsheet - # (in the section using the truncated Avogadro's #, as Zaramela did). + # detail for example 1 on the "full_calcs on correct masses" sheet in + # the "absolute_quant_example.xlsx" spreadsheet (in the section using + # the truncated Avogadro's #, as Zaramela did). # The remainder were not worked out individually and come from the code # calculations. OGU_GENOMES_PER_G_OF_GDNA_KEY: [5.26939603e+13, 2.77101748e+13, @@ -187,11 +190,29 @@ class TestCalcCellCountsData: 41071667.042116195] }) - # This dict contains the results of calculations done on example1 - # using the full Avogadro's number (6.022e23) instead of the truncated one. + # This dict contains the gdna mass from the + # "PredictedOguMass" column of the + # "Mass results for sample A regression for full data on log10_read_count" + # table on the "linear regressions counts" sheet of + # "absolute_quant_example.xlsx", and + # the results of calculations done on example 1 + # using the full Avogadro's number instead of the truncated one. example1_ogu_full_outputs_full_avogadro_dict = ( example1_ogu_full_inputs_dict.copy()) example1_ogu_full_outputs_full_avogadro_dict.update({ + OGU_GDNA_MASS_NG_KEY: [0.055906776, + 0.067506894, + 0.061387889, + 0.027669949, + 0.01731683, + 0.01310435, + 0.000558001, + 0.013894854, + 0.011408701, + 0.009826844, + 0.006662378, + 0.005353421, + 0.006737505], # Note that this is assuming that examples 1 and 2 were run TOTAL_OGU_READS_KEY: [x + y for x, y in zip( example1_ogu_full_inputs_dict[OGU_READ_COUNT_KEY], @@ -212,35 +233,64 @@ class TestCalcCellCountsData: 103.533221485547], # for the arrays below, the 1st, 2nd, and 7th values (those for # L. gasseri, R. albus, and L. valderiana) match those worked out in - # detail for example1 in the "absolute_quant_example.xlsx" spreadsheet - # (in the section using the FULL Avogadro's #, not matching Zaramela). + # detail for example1 in the "full_calcs on correct masses" sheet of + # the "absolute_quant_example.xlsx" spreadsheet + # (in the section using the FULL Avogadro's #, NOT matching Zaramela). # The remainder were not worked out individually and come from the code # calculations. - OGU_GENOMES_PER_G_OF_GDNA_KEY: [ - 52695192015949.67, 27710822536547.69, 21897704979729.094, - 12866488251594.062, 12674159207435.06, 11582576292095.531, - 11223075218306.252, 10879422748260.775, 9289882608698.639, - 7100063146106.998, 5809957491032.718, 5718752608946.0205, - 5715054273735.247], - OGU_CELLS_PER_G_OF_GDNA_KEY: [ - 52695192015949.67, 27710822536547.69, 21897704979729.094, - 12866488251594.062, 12674159207435.06, 11582576292095.531, - 11223075218306.252, 10879422748260.775, 9289882608698.639, - 7100063146106.998, 5809957491032.718, 5718752608946.0205, - 5715054273735.247], - OGU_CELLS_PER_G_OF_SAMPLE_KEY: [ - 378706809.42597693, 199150563.60756874, 157373180.91780522, - 92468146.10340859, 91085926.66579163, 83241000.64356525, - 80657358.76977262, 78187616.74012242, 66764000.05558892, - 51026330.06767092, 41754672.108673245, 41099206.04853115, - 41072627.06334715] + OGU_GENOMES_PER_G_OF_GDNA_KEY: [5438576851832.26, + 2859984606316.35, + 2260023103719.98, + 1327927321117.10, + 1308077383248.35, + 1195417056032.46, + 1158313590928.33, + 1122845831970.39, + 958792227127.95, + 732784863204.92, + 599635357838.64, + 590222264508.60, + 589840565922.86], + OGU_CELLS_PER_G_OF_GDNA_KEY: [5438576851832.26, + 2859984606316.35, + 2260023103719.98, + 1327927321117.10, + 1308077383248.35, + 1195417056032.46, + 1158313590928.33, + 1122845831970.39, + 958792227127.95, + 732784863204.92, + 599635357838.64, + 590222264508.60, + 589840565922.86], + OGU_CELLS_PER_G_OF_SAMPLE_KEY: [39085654.85, + 20553974.73, + 16242205.52, + 9543472.56, + 9400816.15, + 8591155.32, + 8324502.25, + 8069604.57, + 6890593.46, + 5266336.58, + 4309425.29, + 4241775.81, + 4239032.64] }) + # NB: the reason there is no "example1_ogu_filtered_" is that the + # filtering threshold used in the test does not drop any samples from + # example1, so the filtered example1 data is the same as the full. + + # NB: the reason there is no "example2_ogu_full_" is that + # example 2 isn't being used to test generating an unfiltered dataset; + # all those tests are being done with example 1. + # This dict contains the results of calculations done on *filtered* - # example2 data using the full Avogadro's number (6.022e23) instead of - # the truncated one. - # The ogu id, ogu length, and ogu read counts are exactly the same as in - # the example 2 full data *except* that Neisseria subflava and + # example2 data using the full Avogadro's number instead of the truncated + # one. The ogu id, ogu length, and ogu read counts are exactly the same as + # in the example 2 full data *except* that Neisseria subflava and # Haemophilus influenzae have been removed (for falling below the # min_coverage = 1 threshold, which is the default min_coverage value in # our analysis system). @@ -289,21 +339,46 @@ class TestCalcCellCountsData: 151.731341482939, 1194.203338, 105.484891342717, 103.533221485547], - OGU_GENOMES_PER_G_OF_GDNA_KEY: [ - 17086455403978.045, 8987677125515.266, 7101240813289.261, - 4167468287030.2075, 4102264162505.8833, 3747369928484.789, - 3613767901730.258, 3004973286163.8184, 40527863244164.32, - 1877803149989.8623, 1847161346896.6194], - OGU_CELLS_PER_G_OF_GDNA_KEY: [ - 17086455403978.045, 8987677125515.266, 7101240813289.261, - 4167468287030.2075, 4102264162505.8833, 3747369928484.789, - 3613767901730.258, 3004973286163.8184, 40527863244164.32, - 1877803149989.8623, 1847161346896.6194], - OGU_CELLS_PER_G_OF_SAMPLE_KEY: [ - 81111093.52155752, 42665392.12688357, 33710292.398721, - 19783384.089056477, 19473853.661753666, 17789135.636548474, - 17154913.603333004, 14264905.358139353, 192389774.71365833, - 8914117.253274327, 8768657.583752317] + # for the arrays below, the 1st, 2nd, and 7th values (those for + # L. gasseri, R. albus, and L. valderiana) match those worked out in + # detail for example 2 in the "full_calcs on correct masses" sheet of + # the "absolute_quant_example.xlsx" spreadsheet + # (in the section using the FULL Avogadro's #, NOT matching Zaramela). + # The remainder were not worked out individually and come from the code + # calculations. + OGU_GENOMES_PER_G_OF_GDNA_KEY: [4.698763e+12, + 2.471605e+12, + 1.952836e+12, + 1.146051e+12, + 1.128120e+12, + 1.030524e+12, + 9.937836e+11, + 8.263655e+11, + 1.114513e+13, + 5.163945e+11, + 5.079680e+11], + OGU_CELLS_PER_G_OF_GDNA_KEY: [4.698763e+12, + 2.471605e+12, + 1.952836e+12, + 1.146051e+12, + 1.128120e+12, + 1.030524e+12, + 9.937836e+11, + 8.263655e+11, + 1.114513e+13, + 5.163945e+11, + 5.079680e+11], + OGU_CELLS_PER_G_OF_SAMPLE_KEY: [2.230549e+07, + 1.173295e+07, + 9.270306e+06, + 5.440416e+06, + 5.355296e+06, + 4.891999e+06, + 4.717589e+06, + 3.922839e+06, + 5.290705e+07, + 2.451376e+06, + 2.411375e+06] } # This dict contains the results of calculations done on example1 and @@ -330,21 +405,51 @@ class TestCalcCellCountsData: # The two 0 values are for N. subflava and H. influenzae, which were # removed from example2 data due to low coverage. OGU_CELLS_PER_G_OF_GDNA_KEY: [ - [21897704979729.094, 7101240813289.261], - [7100063146106.998, 40527863244164.32], - [5718752608946.0205, 0], - [52695192015949.67, 17086455403978.045], - [11223075218306.252, 3613767901730.258], - [9289882608698.639, 3004973286163.8184], - [10879422748260.775, 0], - [12674159207435.06, 4102264162505.8833], - [27710822536547.69, 8987677125515.266], - [11582576292095.531, 3747369928484.789], - [5809957491032.718, 1877803149989.8623], - [12866488251594.062, 4167468287030.2075], - [5715054273735.247, 1847161346896.6194]], + [2260023103719.98, 1952836093054.1], + [732784863204.92, 11145133111026.7], + [590222264508.6, 0], + [5438576851832.26, 4698762891240.72], + [1158313590928.33, 993783562051.94], + [958792227127.95, 826365482621.33], + [1122845831970.39, 0], + [1308077383248.35, 1128119680829.86], + [2859984606316.35, 2471604715978.24], + [1195417056032.46, 1030524022882.84], + [599635357838.64, 516394509546.61], + [1327927321117.1, 1146050767964.49], + [589840565922.86, 507968035834.47] + ] } + # NB: The test values for example1 here are *slightly* different than + # those in self.example1_ogu_full_outputs_full_avogadro_dict because + # the gdna-to-sample mass ratio calculated internally during this + # soup-to-nuts function has more digits past the decimal than does the + # example1 entry in the manually-populated self.mass_and_totals_dict. + # Since we are multiplying/dividing by large numbers like e.g., 10^9 + # (to change ng to g), this ends up making a slight difference in the + # end product: for example, for L.gasseri, + # 3908565*5.46* cells instead of 3908565*4.85* cells, + # 8324502.*38* instead of 8324502.*25* for L. valderiana, + # and 2055397*5.06* instead of 2055397*4.73* for R. albus. + # Remember, with reordering, the 4th sub-array is for L. gasseri, + # the 5th is for L. valderiana, and the 9th is for R. albus. + example1_example4_cells_per_g_sample = [ + [16242205.78, 6489214.14], + [5266336.67, 37034933.76], + [4241775.87, 0], + [39085655.46, 15613844.24], + [8324502.38, 3302312.14], + [6890593.56, 2745987.02], + [8069604.7, 0], + [9400816.3, 3748706.92], + [20553975.06, 8213066.28], + [8591155.45, 3424399.56], + [4309425.36, 1715963.04], + [9543472.71, 3808291.37], + [4239032.7, 1687962.12] + ] + @classmethod def combine_inputs(cls): sample_names = cls.generate_sample_names_list(use_filtered_ex2=False) @@ -362,6 +467,8 @@ def combine_inputs(cls): @classmethod def combine_filtered_out(cls, col_name): + # NB: it *is* correct to use the "full" example1 and the "filtered" + # example2; see comments above in the property definitions for details. example1_copy = ( TestCalcCellCountsData.example1_ogu_full_outputs_full_avogadro_dict[col_name].copy()) example2_copy = ( @@ -427,39 +534,8 @@ def test_calc_ogu_cell_counts_per_g_of_sample_for_qiita(self): # example4 has the same counts as example2 counts_vals = TestCalcCellCountsData.make_combined_counts_np_array() - # NB: The test values for example1 here are *slightly* different than - # those in self.example1_ogu_full_outputs_full_avogadro_dict because - # the gdna-to-sample mass ratio calculated internally during this - # soup-to-nuts function has more digits past the decimal than does the - # example1 entry in the manually-populated self.mass_and_totals_dict. - # Since we are multiplying/dividing by large numbers like e.g., 10^9 - # (to change ng to g), this ends up making a slight difference in the - # end product: for example, for L.gasseri, - # 3787068*15* cells instead of 3787068*09* cells, - # 80657360 instead of 80657358 for L. valderiana, - # and 199150566 instead of 199150563 for R. albus. - # The values for example 4 are about an order of magnitude smaller - # than those for example 1 and thus match those from - # "absolute_quant_example.xlsx" more closely: - # both 56777765 for L. gasseri (with rounding), - # 12008439 instead of 12008440 for L. valderiana, - # and both 29865774 for R. albus. - # Remember, with reordering, the 4th sub-array is for L. gasseri, - # the 5th is for L. valderiana, and the 9th is for R. albus. - ogu_cell_counts_per_g_sample = np.array([ - [157373183.3914873, 23597204.3149076], - [51026330.8697321, 134672840.2210325], - [41099206.6945521, 0], - [378706815.3787082, 56777764.5887874], - [80657360.0375914, 12008439.3369959], - [66764001.1050239, 9985433.5965833], - [78187617.9691203, 0], - [91085928.0975326, 13631697.3528372], - [199150566.7379318, 29865774.0278729], - [83241001.9519951, 12452394.7533948], - [41754672.7649972, 6239881.9809863], - [92468147.5568761, 13848368.6486051], - [41072627.7089503, 6138060.2138924]]) + ogu_cell_counts_per_g_sample = np.array( + TestCalcCellCountsData.example1_example4_cells_per_g_sample) sample_info_df = pd.DataFrame(sample_info_dict) prep_info_df = pd.DataFrame(prep_info_dict) @@ -490,7 +566,8 @@ def test_calc_ogu_cell_counts_per_g_of_sample_for_qiita(self): a_tester = Testers() a_tester.assert_biom_tables_equal( - expected_out_biom, output_dict[CELL_COUNT_RESULT_KEY]) + expected_out_biom, output_dict[CELL_COUNT_RESULT_KEY], + decimal_precision=1) self.assertEqual( "The following items have % coverage lower than the minimum of " "1.0: ['example4;Neisseria subflava', " @@ -524,39 +601,8 @@ def test_calc_ogu_cell_counts_per_g_of_sample_for_qiita_w_casts(self): # example4 has the same counts as example2 counts_vals = TestCalcCellCountsData.make_combined_counts_np_array() - # NB: The test values for example1 here are *slightly* different than - # those in self.example1_ogu_full_outputs_full_avogadro_dict because - # the gdna-to-sample mass ratio calculated internally during this - # soup-to-nuts function has more digits past the decimal than does the - # example1 entry in the manually-populated self.mass_and_totals_dict. - # Since we are multiplying/dividing by large numbers like e.g., 10^9 - # (to change ng to g), this ends up making a slight difference in the - # end product: for example, for L.gasseri, - # 3787068*15* cells instead of 3787068*09* cells, - # 80657360 instead of 80657358 for L. valderiana, - # and 199150566 instead of 199150563 for R. albus. - # The values for example 4 are about an order of magnitude smaller - # than those for example 1 and thus match those from - # "absolute_quant_example.xlsx" more closely: - # both 56777765 for L. gasseri (with rounding), - # 12008439 instead of 12008440 for L. valderiana, - # and both 29865774 for R. albus. - # Remember, with reordering, the 4th sub-array is for L. gasseri, - # the 5th is for L. valderiana, and the 9th is for R. albus. - ogu_cell_counts_per_g_sample = np.array([ - [157373183.3914873, 23597204.3149076], - [51026330.8697321, 134672840.2210325], - [41099206.6945521, 0], - [378706815.3787082, 56777764.5887874], - [80657360.0375914, 12008439.3369959], - [66764001.1050239, 9985433.5965833], - [78187617.9691203, 0], - [91085928.0975326, 13631697.3528372], - [199150566.7379318, 29865774.0278729], - [83241001.9519951, 12452394.7533948], - [41754672.7649972, 6239881.9809863], - [92468147.5568761, 13848368.6486051], - [41072627.7089503, 6138060.2138924]]) + ogu_cell_counts_per_g_sample = np.array( + TestCalcCellCountsData.example1_example4_cells_per_g_sample) sample_info_df = pd.DataFrame(sample_info_dict) prep_info_df = pd.DataFrame(prep_info_dict) @@ -588,7 +634,8 @@ def test_calc_ogu_cell_counts_per_g_of_sample_for_qiita_w_casts(self): a_tester = Testers() a_tester.assert_biom_tables_equal( - expected_out_biom, output_dict[CELL_COUNT_RESULT_KEY]) + expected_out_biom, output_dict[CELL_COUNT_RESULT_KEY], + decimal_precision=1) self.assertEqual( "The following items have % coverage lower than the minimum of " "1.0: ['example4;Neisseria subflava', " @@ -624,39 +671,12 @@ def test_calc_ogu_cell_counts_per_g_of_sample_for_qiita_w_negs(self): # example4 has the same counts as example2 counts_vals = TestCalcCellCountsData.make_combined_counts_np_array() - # NB: The test values for example1 here are *slightly* different than - # those in self.example1_ogu_full_outputs_full_avogadro_dict because - # the gdna-to-sample mass ratio calculated internally during this - # soup-to-nuts function has more digits past the decimal than does the - # example1 entry in the manually-populated self.mass_and_totals_dict. - # Since we are multiplying/dividing by large numbers like e.g., 10^9 - # (to change ng to g), this ends up making a slight difference in the - # end product: for example, for L.gasseri, - # 3787068*15* cells instead of 3787068*09* cells, - # 80657360 instead of 80657358 for L. valderiana, - # and 199150566 instead of 199150563 for R. albus. - # The values for example 4 are about an order of magnitude smaller - # than those for example 1 and thus match those from - # "absolute_quant_example.xlsx" more closely: - # both 56777765 for L. gasseri (with rounding), - # 12008439 instead of 12008440 for L. valderiana, - # and both 29865774 for R. albus. - # Remember, with reordering, the 4th sub-array is for L. gasseri, - # the 5th is for L. valderiana, and the 9th is for R. albus. - ogu_cell_counts_per_g_sample = np.array([ - [157373183.3914873], - [51026330.8697321], - [41099206.6945521], - [378706815.3787082], - [80657360.0375914], - [66764001.1050239], - [78187617.9691203], - [91085928.0975326], - [199150566.7379318], - [83241001.9519951], - [41754672.7649972], - [92468147.5568761], - [41072627.7089503]]) + # Results are returned only for example 1 because example 4 has a + # negative aliquot mass + ogu_cell_counts_per_g_sample = np.array( + [[x[0]] for x in + TestCalcCellCountsData.example1_example4_cells_per_g_sample] + ) sample_info_df = pd.DataFrame(sample_info_dict) prep_info_df = pd.DataFrame(prep_info_dict) @@ -687,7 +707,8 @@ def test_calc_ogu_cell_counts_per_g_of_sample_for_qiita_w_negs(self): a_tester = Testers() a_tester.assert_biom_tables_equal( - expected_out_biom, output_dict[CELL_COUNT_RESULT_KEY]) + expected_out_biom, output_dict[CELL_COUNT_RESULT_KEY], + decimal_precision=1) self.assertEqual( "Dropping samples with negative values in necessary " "prep/sample column(s): example4", @@ -830,12 +851,12 @@ def test_calc_ogu_cell_counts_biom(self): params_df, TestCalcCellCountsData.linregresses_dict, counts_biom, lengths_df, read_len, min_coverage, min_rsquared, output_metric) - # NB: only checking results to 2 decimals because Ubuntu and Mac + # NB: only checking results to 1 decimal because Ubuntu and Mac # differ past that point. Not that it matters much since the decimal # portion of values this huge is not very important. a_tester = Testers() a_tester.assert_biom_tables_equal(expected_out_biom, output_biom, - decimal_precision=2) + decimal_precision=1) self.assertListEqual( ["The following items have % coverage lower than the minimum of " "1.0: ['example2;Neisseria subflava', " @@ -947,12 +968,12 @@ def test_calc_ogu_cell_counts_biom_w_cast(self): params_df, TestCalcCellCountsData.linregresses_dict, counts_biom, lengths_df, read_len, min_coverage, min_rsquared, output_metric) - # NB: only checking results to 2 decimals because Ubuntu and Mac + # NB: only checking results to 1 decimal because Ubuntu and Mac # differ past that point. Not that it matters much since the decimal # portion of values this huge is not very important. a_tester = Testers() a_tester.assert_biom_tables_equal(expected_out_biom, output_biom, - decimal_precision=2) + decimal_precision=1) self.assertListEqual( ["The following items have % coverage lower than the minimum of " "1.0: ['example2;Neisseria subflava', " @@ -969,16 +990,26 @@ def test__calc_long_format_ogu_cell_counts_df(self): } ogu_masses = \ - TestCalcCellCountsData.example1_ogu_full_outputs_short_avogadro_dict[ + TestCalcCellCountsData.example1_ogu_full_outputs_full_avogadro_dict[ OGU_GDNA_MASS_NG_KEY].copy() - # NB: The example2 test values were NOT verified manually. They - # are what the code that produces correct results for all the - # values downstream of these values produces for these values. - ogu_masses.extend([ - 0.16721225613234225, 0.20196161687112832, 0.1836288899469535, - 0.08266911949481899, 0.051700594315481685, 0.03910745610301345, - 0.0016573186101851826, 0.03403997782058165, 0.517402166874799, - 0.01986227733996378, 0.02008659206780049]) + # NB: The example 2 gdna mass values come from the "PredictedOguMass" + # column of the "Mass results for sample B regression for full data on log10_read_count" + # table on the "linear regressions counts" sheet of + # "absolute_quant_example.xlsx", but with the 8th and 12th (1-based) + # values removed since this is for *filtered* example 2 data. + ogu_masses.extend([0.04598325, + 0.055539299, + 0.050497812, + 0.022733948, + 0.014217626, + 0.010754522, + 0.000455761, + # NUM! + 0.009360969, + 0.142285222, + 0.005462112, + # 1.10529E-05, + 0.005523798]) # NB: this test is NOT using the truncated version of Avogadro's # that # was used in the notebook, so the results are slightly different @@ -1175,7 +1206,7 @@ def test__calc_ogu_cell_counts_df_for_sample(self): per_sample_info_df = pd.DataFrame(TestCalcCellCountsData.mass_and_totals_dict) expected_additions_dict = { - k: TestCalcCellCountsData.example1_ogu_full_outputs_short_avogadro_dict[k] for k in + k: TestCalcCellCountsData.example1_ogu_full_outputs_full_avogadro_dict[k] for k in (OGU_ID_KEY, OGU_GDNA_MASS_NG_KEY, OGU_GENOMES_PER_G_OF_GDNA_KEY, OGU_CELLS_PER_G_OF_GDNA_KEY, @@ -1190,7 +1221,7 @@ def test__calc_ogu_cell_counts_df_for_sample(self): output_df, output_msgs = _calc_ogu_cell_counts_df_for_sample( sample_id, TestCalcCellCountsData.linregresses_dict, per_sample_info_df, input_df, - min_rsquared, is_test=True) + min_rsquared, is_test=False) pd.testing.assert_frame_equal(expected_out_df, output_df) self.assertListEqual([], output_msgs) @@ -1232,7 +1263,7 @@ def test__calc_ogu_cell_counts_df_for_sample_w_log_msgs_low_rsquared(self): self.assertIsNone(output_df) self.assertListEqual(['R^2 of linear regression for sample example1 ' - 'is 0.9731883614079868, which is less than the ' + 'is 0.9731883614079859, which is less than the ' 'minimum allowed value of 0.99.'], output_msgs) @@ -1255,42 +1286,15 @@ def test__calc_gdna_mass_to_sample_mass_by_sample_df(self): output_series = _calc_gdna_mass_to_sample_mass_by_sample_df(inputs_df) pd.testing.assert_series_equal(expected_series, output_series) - def test__calc_ogu_gdna_mass_ng_series_for_sample(self): - input_dict = {k: TestCalcCellCountsData.example1_ogu_full_inputs_dict[k] for k in - (OGU_ID_KEY, OGU_READ_COUNT_KEY)} - - # Inputs are taken from the values for A1_pool1_Fwd in the - # linear models file at https://github.com/lzaramela/SynDNA/blob/main/data/saliva_linear_models.tsv , - # EXCEPT the values of the a_intercept and b_intercept columns are - # negated (because the Zaramela code generates regression models that - # predict the *negative* log10 of the read weight while the code under - # test predicts just log10 of the read weight). - slope = 1.24487652379132 - intercept = -6.77539505390338 - - # This number comes from summing all the reads in the input_df. - # This matches what was done for the Zaramela calculations. I - # suspect that this should perhaps be the total reads for the - # whole sample, but for testing this will do. - sample_total_reads = 472140 - - input_df = pd.DataFrame(input_dict) - expected_series = pd.Series( - TestCalcCellCountsData.example1_ogu_full_outputs_short_avogadro_dict[ - OGU_GDNA_MASS_NG_KEY], - index=TestCalcCellCountsData.example1_ogu_full_inputs_dict[OGU_ID_KEY], - name=OGU_GDNA_MASS_NG_KEY) - expected_series.index.name = OGU_ID_KEY - - output_series = _calc_ogu_gdna_mass_ng_series_for_sample( - input_df, slope, intercept, sample_total_reads) - - assert_series_equal(expected_series, output_series) - def test__calc_ogu_genomes_per_g_of_gdna_series_for_sample(self): # this is the default value for our experimental system total_sample_gdna_mass_ng = 5 + # NOTE: the input mass values here are the originals from the Zaramela + # R notebook, which had an issue in the calculation. HOWEVER, + # that doesn't matter here because they are inputs, not outputs: IF + # you input these values, you get these outputs (regardless of whether + # these input values are meaningful). input_dict = {k: TestCalcCellCountsData.example1_ogu_full_outputs_short_avogadro_dict[k] for k in (OGU_ID_KEY, OGU_LEN_IN_BP_KEY, OGU_GDNA_MASS_NG_KEY)} @@ -1335,6 +1339,11 @@ def test__calc_ogu_genomes_series_for_sample(self): 28574.60346] } + # NOTE: the input mass values here are the originals from the Zaramela + # R notebook, which had an issue in the calculation. HOWEVER, + # that doesn't matter here because they are inputs, not outputs: IF + # you input these values, you get these outputs (regardless of whether + # these input values are meaningful). input_dict = {k: TestCalcCellCountsData.example1_ogu_full_outputs_short_avogadro_dict[k] for k in (OGU_ID_KEY, OGU_LEN_IN_BP_KEY, OGU_GDNA_MASS_NG_KEY)} @@ -1350,3 +1359,54 @@ def test__calc_ogu_genomes_series_for_sample(self): input_df, is_test=True) assert_series_equal(expected_series, output_series) + + def test__calc_ogu_gdna_mass_ng_series_for_sample(self): + input_dict = \ + {k: TestCalcCellCountsData.example1_ogu_full_inputs_dict[k] + for k in (OGU_ID_KEY, OGU_READ_COUNT_KEY)} + + # The slope and intercept values are taken from + # those calculated in Excel (see + # "sample A regression of log10_syndna_ng from log10_raw_counts" + # for full data on "zaramela linear reg CPM&counts" sheet of + # "absolute_quant_example.xlsx"). + # Note that the intercept does not and *should* NOT be expected to + # match the intercept in Zaramela's linear models because we are + # fitting log10(raw counts) not log10(CPM), but this comes + # out in the wash when one actually *uses* the models to predict mass + # (HOWEVER, this is not true for the masses taken directly from the + # Zaramela R notebook because it unintentionally used a different total + # read value when calculating the OGU CPM than when calculating + # the syndna CPM to get the fit.) + slope = 1.24487652379132 + intercept = -7.40709604550579 + + # This list contains the gdna mass from the + # "Local mass using RawCounts (no TotalReads)" column in the + # "linear regressions counts" sheet of "absolute_quant_example.xlsx" + expected_ogu_masses = \ + [0.04969441309413190000, + 0.06000552485579740000, + 0.05456646428684080000, + 0.02459526358752070000, + 0.01539258341742420000, + 0.01164819449827620000, + 0.00049599588089929300, + 0.01235085741392710000, + 0.01014096594158730000, + 0.00873488502026095000, + 0.00592205420878227000, + 0.00475854893636541000, + 0.00598883339989535000] + + input_df = pd.DataFrame(input_dict) + expected_series = pd.Series( + expected_ogu_masses, + index=TestCalcCellCountsData.example1_ogu_full_inputs_dict[OGU_ID_KEY], + name=OGU_GDNA_MASS_NG_KEY) + expected_series.index.name = OGU_ID_KEY + + output_series = _calc_ogu_gdna_mass_ng_series_for_sample( + input_df, slope, intercept) + + assert_series_equal(expected_series, output_series) \ No newline at end of file diff --git a/pysyndna/tests/test_fit_syndna_models.py b/pysyndna/tests/test_fit_syndna_models.py index 3f0aa85..96966e1 100644 --- a/pysyndna/tests/test_fit_syndna_models.py +++ b/pysyndna/tests/test_fit_syndna_models.py @@ -77,29 +77,37 @@ class FitSyndnaModelsTestData(): # The slope, intercept, rvalue, stderr (of slope), # intercept_stderr, and p-value (of slope) values for these results # match those calculated in Excel (see results for full data - # on "linear regressions" sheet of "absolute_quant_example.xlsx"). + # on "linear regressions counts" sheet of "absolute_quant_example.xlsx"). # Note that these do not and *should* NOT be expected to match any results - # in Zaramela's linear models (see modelling_output.tsv) because - # (i) sample B is a chimera of realistic data from multiple Zaramela - # samples but isn't directly comparable to a single one of them, and - # (ii) sample A is directly comparable to Zaramela's sample + # in Zaramela's linear models because: + # (i) we are fitting log10(raw counts) not log10(CPM). Note this comes out + # in the wash when one actually *uses* the models to predict mass: + # you get the same masses using the model fit to log10(raw counts) + # as you do using the model fit to log10(CPM) (which makes sense since all + # the CPM calculation does is divide the raw counts by a constant + # [total reads] and then multiply by another constant [a million]) but + # the models themselves are slightly different--same slope but different + # intercepts. + # (ii) sample A has the same counts as Zaramela's sample # "A1_pool1_Fwd" *but* we use a different pool mass than Zaramela, # so the same syndna counts are based on different masses. + # (iii) sample B is a chimera of realistic data from multiple Zaramela + # samples but isn't directly comparable to a single one of them lingress_results = { 'A': { - "slope": 1.244876523791319, - "intercept": -6.7242381884894655, - "rvalue": 0.9865030975156575, - "pvalue": 1.428443560659758e-07, - "stderr": 0.07305408550335003, - "intercept_stderr": 0.2361976278251443}, + "slope": 1.24487652379132, + "intercept": -7.35593916054843, + "rvalue": 0.986503097515657, + "pvalue": 1.42844356065977E-07, + "stderr": 0.0730540855033502, + "intercept_stderr": 0.271274537363401}, 'B': { "slope": 1.24675913604407, - "intercept": -7.155318973708384, - "rvalue": 0.9863241797356326, - "pvalue": 1.505381146809759e-07, - "stderr": 0.07365795255302438, - "intercept_stderr": 0.2563956755844754} + "intercept": -7.45004083037736, + "rvalue": 0.986324179735633, + "pvalue": 1.5053811468097E-07, + "stderr": 0.073657952553024, + "intercept_stderr": 0.2729411999326} } prep_info_dict = copy.deepcopy( @@ -117,128 +125,6 @@ class FitSyndnaModelsTestData(): class FitSyndnaModelsTest(TestCase): - # # concentrations taken from table in Fig. 1A of - # # https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9765022/ - # syndna_concs_dict = { - # SYNDNA_ID_KEY: ["p126", "p136", "p146", "p156", "p166", "p226", - # "p236", "p246", "p256", "p266"], - # SYNDNA_INDIV_NG_UL_KEY: [1, 0.1, 0.01, 0.001, 0.0001, 0.0001, - # 0.001, 0.01, 0.1, 1], - # } - # - # # made-up placeholders :) - # sample_ids = ["A", "B"] - # - # # Total reads come from the "TotalReads" column of - # # https://github.com/lzaramela/SynDNA/blob/main/data/synDNA_metadata_updated.tsv - # # for the record with "ID" = "A1_pool1_Fwd". - # # Syndna pool mass is the default value expected in our experimental - # # system. - # a_sample_syndna_weights_and_total_reads_dict = { - # SAMPLE_ID_KEY: [sample_ids[0]], - # SAMPLE_TOTAL_READS_KEY: [3216923], - # SYNDNA_POOL_MASS_NG_KEY: [0.25], - # } - # - # # Total reads come from the "TotalReads" column of - # # https://github.com/lzaramela/SynDNA/blob/main/data/synDNA_metadata_updated.tsv - # # for the record with "ID" of "A1_pool1_Fwd" and "C1_pool1_Fwd". - # # Syndna pool masses are plausible values for our experimental system. - # a_b_sample_syndna_weights_and_total_reads_dict = { - # SAMPLE_ID_KEY: sample_ids, - # SAMPLE_TOTAL_READS_KEY: [3216923, 1723417], - # SYNDNA_POOL_MASS_NG_KEY: [0.25, 0.2], - # } - # - # # Total reads come from the "TotalReads" column of - # # https://github.com/lzaramela/SynDNA/blob/main/data/synDNA_metadata_updated.tsv - # # for the record with "ID" of "A1_pool1_Fwd", "C1_pool1_Fwd", and - # # "D1_pool1_Fwd". - # # Syndna pool masses are plausible values for our experimental system. - # a_b_c_sample_syndna_weights_and_total_reads_dict = { - # SAMPLE_ID_KEY: [sample_ids[0], sample_ids[1], "C"], - # SAMPLE_TOTAL_READS_KEY: [3216923, 1723417, 2606004], - # SYNDNA_POOL_MASS_NG_KEY: [0.25, 0.2, 0.3], - # } - # - # # The below sample values come from the - # # "A1_pool1_S21_L001_R1_001.fastq_output_forward_paired.fq.sam.bam.f13_r1.fq_synDNA" - # # and "A1_pool2_S22_L001_R1_001.fastq_output_forward_paired.fq.sam.bam.f13_r1.fq_synDNA" - # # columns of https://github.com/lzaramela/SynDNA/blob/main/data/synDNA_Fwd_Rev_sam.biom.tsv , - # # while the syndna ids are inferred from the contents of the "OTUID" - # # column and a knowledge of the Zaramela naming scheme. - # reads_per_syndna_per_sample_dict = { - # SYNDNA_ID_KEY: ["p126", "p136", "p146", "p156", "p166", "p226", - # "p236", "p246", "p256", "p266"], - # sample_ids[0]: [93135, 15190, 2447, 308, 77, 149, 1075, 3189, 25347, 237329], - # sample_ids[1]: [90897, 15002, 2421, 296, 77, 148, 1059, 3129, 24856, 230898], - # } - # - # # The slope, intercept, rvalue, stderr (of slope), - # # intercept_stderr, and p-value (of slope) values for these results - # # match those calculated in Excel (see results for full data - # # on "linear regressions" sheet of "absolute_quant_example.xlsx"). - # # Note that these do not and *should* NOT be expected to match any results - # # in Zaramela's linear models (see modelling_output.tsv) because - # # (i) sample B is a chimera of realistic data from multiple Zaramela - # # samples but isn't directly comparable to a single one of them, and - # # (ii) sample A is directly comparable to Zaramela's sample - # # "A1_pool1_Fwd" *but* we use a different pool mass than Zaramela, - # # so the same syndna counts are based on different masses. - # lingress_results = { - # 'A': { - # "slope": 1.244876523791319, - # "intercept": -6.7242381884894655, - # "rvalue": 0.9865030975156575, - # "pvalue": 1.428443560659758e-07, - # "stderr": 0.07305408550335003, - # "intercept_stderr": 0.2361976278251443}, - # 'B': { - # "slope": 1.24675913604407, - # "intercept": -7.155318973708384, - # "rvalue": 0.9863241797356326, - # "pvalue": 1.505381146809759e-07, - # "stderr": 0.07365795255302438, - # "intercept_stderr": 0.2563956755844754} - # } - # - # prep_info_dict = copy.deepcopy( - # a_b_sample_syndna_weights_and_total_reads_dict) - # prep_info_dict["sequencing_type"] = ["shotgun", "shotgun"] - # prep_info_dict["syndna_pool_number"] = [1, 1] - # - # # combine each item in self.reads_per_syndna_per_sample_dict["A"] with - # # the analogous item in self.reads_per_syndna_per_sample_dict["B"] - # # to make an array of two-item arrays, and turn this into an np.array - # reads_per_syndna_per_sample_array = np.array( - # [list(x) for x in zip( - # reads_per_syndna_per_sample_dict["A"], - # reads_per_syndna_per_sample_dict["B"])]) - - # def assert_dicts_almost_equal(self, d1, d2): - # """Assert that two dicts are almost equal. - # - # Parameters - # ---------- - # d1 : dict - # The first dict to compare - # d2 : dict - # The second dict to compare - # - # Raises - # ------ - # AssertionError - # If the dicts are not almost equal - # """ - # self.assertIsInstance(d1, dict) - # self.assertIsInstance(d2, dict) - # self.assertEqual(d1.keys(), d2.keys()) - # for k in d1.keys(): - # for m in d1[k].keys(): - # m1 = d1[k][m] - # m2 = d2[k][m] - # self.assertAlmostEqual(m1, m2) - def setUp(self): self.maxDiff = None self.data_dir = os.path.join(os.path.dirname(__file__), 'data') @@ -253,19 +139,19 @@ def test_fit_linear_regression_models_for_qiita(self): # These are text versions of the linear regression results # for the full data (see self.lingress_results and the - # "linear regressions" sheet of "absolute_quant_example.xlsx"). + # "linear regressions counts" sheet of "absolute_quant_example.xlsx"). expected_out = { 'lin_regress_by_sample_id': 'A:\n' - ' intercept: -6.724238188489\n' - ' intercept_stderr: 0.236197627825\n' + ' intercept: -7.355939160548\n' + ' intercept_stderr: 0.271274537363\n' ' pvalue: 1.42844e-07\n' ' rvalue: 0.986503097515\n' ' slope: 1.244876523791\n' ' stderr: 0.073054085503\n' 'B:\n' - ' intercept: -7.155318973708\n' - ' intercept_stderr: 0.256395675584\n' + ' intercept: -7.450040830377\n' + ' intercept_stderr: 0.272941199932\n' ' pvalue: 1.50538e-07\n' ' rvalue: 0.986324179735\n' ' slope: 1.246759136044\n' @@ -292,19 +178,19 @@ def test_fit_linear_regression_models_for_qiita_w_casts(self): # These are text versions of the linear regression results # for the full data (see self.lingress_results and the - # "linear regressions" sheet of "absolute_quant_example.xlsx"). + # "linear regressions counts" sheet of "absolute_quant_example.xlsx"). expected_out = { 'lin_regress_by_sample_id': 'A:\n' - ' intercept: -6.724238188489\n' - ' intercept_stderr: 0.236197627825\n' + ' intercept: -7.355939160548\n' + ' intercept_stderr: 0.271274537363\n' ' pvalue: 1.42844e-07\n' ' rvalue: 0.986503097515\n' ' slope: 1.244876523791\n' ' stderr: 0.073054085503\n' 'B:\n' - ' intercept: -7.155318973708\n' - ' intercept_stderr: 0.256395675584\n' + ' intercept: -7.450040830377\n' + ' intercept_stderr: 0.272941199932\n' ' pvalue: 1.50538e-07\n' ' rvalue: 0.986324179735\n' ' slope: 1.246759136044\n' @@ -333,15 +219,15 @@ def test_fit_linear_regression_models_for_qiita_w_alt_config(self): expected_out = { 'lin_regress_by_sample_id': 'A:\n' - ' intercept: -8.198448239722\n' - ' intercept_stderr: 0.543935662662\n' + ' intercept: -9.005671912625\n' + ' intercept_stderr: 0.624713705225\n' ' pvalue: 1.287067e-05\n' ' rvalue: 0.958056670088\n' ' slope: 1.590774502959\n' ' stderr: 0.168235061352\n' 'B:\n' - ' intercept: -8.723558660515\n' - ' intercept_stderr: 0.586319521146\n' + ' intercept: -9.100255596079\n' + ' intercept_stderr: 0.624155431954\n' ' pvalue: 1.2820953e-05\n' ' rvalue: 0.958097757898\n' ' slope: 1.593537551784\n' @@ -366,13 +252,13 @@ def test_fit_linear_regression_models_for_qiita_w_negs(self): min_counts = 50 # These are text versions of the linear regression results - # for the full data (see self.lingress_results and the - # "linear regressions" sheet of "absolute_quant_example.xlsx"). + # for the full data (see self.lingress_results and the full data fit on + # "linear regressions counts" sheet of "absolute_quant_example.xlsx"). expected_out = { 'lin_regress_by_sample_id': 'B:\n' - ' intercept: -7.155318973708\n' - ' intercept_stderr: 0.256395675584\n' + ' intercept: -7.450040830377\n' + ' intercept_stderr: 0.272941199932\n' ' pvalue: 1.50538e-07\n' ' rvalue: 0.986324179735\n' ' slope: 1.246759136044\n' @@ -397,19 +283,19 @@ def test_fit_linear_regression_models_for_qiita_w_log_msgs(self): # These are text versions of the linear regression results # for the data with syndnas with <200 total counts removed (see - # "linear regressions" sheet of "absolute_quant_example.xlsx"). + # "linear regressions counts" sheet of "absolute_quant_example.xlsx"). expected_out = { 'lin_regress_by_sample_id': 'A:\n' - ' intercept: -6.767160120684\n' - ' intercept_stderr: 0.301479875957\n' + ' intercept: -7.404604502655\n' + ' intercept_stderr: 0.34490591545\n' ' pvalue: 2.170514e-06\n' ' rvalue: 0.982777689569\n' ' slope: 1.256194910944\n' ' stderr: 0.089276147107\n' 'B:\n' - ' intercept: -7.196128673001\n' - ' intercept_stderr: 0.326579863246\n' + ' intercept: -7.49322862874\n' + ' intercept_stderr: 0.347041014613\n' ' pvalue: 2.289073e-06\n' ' rvalue: 0.982512701026\n' ' slope: 1.25681918648\n' @@ -522,23 +408,23 @@ def test_fit_linear_regression_models_w_log_msgs(self): # The slope, intercept, rvalue, stderr (of slope), # intercept_stderr, and p-value (of slope) values for these results # match those calculated in Excel (see results for data with - # syndnas with <200 total counts removed on "linear regressions" sheet - # of "absolute_quant_example.xlsx"). + # syndnas with <200 total counts removed on "linear regressions counts" + # sheet of "absolute_quant_example.xlsx"). expected_out_dict = { 'A': { - "slope": 1.2561949109446753, - "intercept": -6.7671601206840855, + "slope": 1.25619491094468, + "intercept": -7.40460450265582, "rvalue": 0.982777689569875, - "pvalue": 2.1705143708536327e-06, - "stderr": 0.08927614710714807, - "intercept_stderr": 0.30147987595768355}, + "pvalue": 2.17051437085363E-06, + "stderr": 0.0892761471071481, + "intercept_stderr": 0.344905915450109}, 'B': { - "slope": 1.2568191864801976, - "intercept": -7.196128673001381, - "rvalue": 0.9825127010266727, - "pvalue": 2.2890733334160456e-06, - "stderr": 0.09002330756867402, - "intercept_stderr": 0.32657986324660143} + "slope": 1.2568191864802, + "intercept": -7.49322862874098, + "rvalue": 0.982512701026673, + "pvalue": 2.28907333341599E-06, + "stderr": 0.0900233075686737, + "intercept_stderr": 0.347041014613366} } expected_out_msgs = [ "The following sample ids were in the experiment info but not in "