From f731cef858dba913eae50fa97d1935fc35a36c73 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 19 Apr 2016 21:36:09 -0700 Subject: [PATCH 01/11] add helper functions --- americangut/util.py | 55 +++++++++++++++++++ .../primary-processing/08-collapse_samples.md | 2 + tests/test_util.py | 54 +++++++++++++++++- 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/americangut/util.py b/americangut/util.py index b2b9ccb..3e26de1 100644 --- a/americangut/util.py +++ b/americangut/util.py @@ -11,6 +11,7 @@ from itertools import izip from StringIO import StringIO from collections import defaultdict +from biom import Table from lxml import etree from skbio.parse.sequences import parse_fastq, parse_fasta @@ -635,3 +636,57 @@ def get_single_id_lists(map_, depths): single_ids[depths[idx - 1]].extend(single_ids[depths[idx]]) return single_ids + + +def collapse_taxonomy(_bt, level=5): + """Collapses OTUs by taxonomy + + Parameters + ---------- + _bt : biom table + Table to collapse + level : int, optional + Level to collapse to. 0=kingdom, 1=phylum,...,5=genus, 6=species + Default 5 + + Returns + ------- + biom table + Collapsed biom table + + Citations + --------- + [1] http://biom-format.org/documentation/table_objects.html + """ + collapse_f = lambda id_, md: '; '.join(md['taxonomy'][:level + 1]) + collapsed = _bt.collapse(collapse_f, axis='observation', norm=False) + return collapsed + + +def collapse_full(_bt): + """Collapses full biom table to median of each OTU + + Parameters + ---------- + _bt : biom table + Table to collapse + + Returns + ------- + biom table + Collapsed biom table, one sample containing median of each OTU, + normalized. + """ + data = np.empty([_bt.length('observation'), 1]) + obs = [] + observation_metadata = [] + pos = 0 + for vals, id_, metadata in _bt.iter(axis='observation'): + data[pos][0] = np.median(vals) + obs.append(id_) + observation_metadata.append(metadata) + pos += 1 + table = Table(data, obs, ['average'], + observation_metadata=observation_metadata) + table.norm(inplace=True) + return table diff --git a/ipynb/primary-processing/08-collapse_samples.md b/ipynb/primary-processing/08-collapse_samples.md index 3164682..cd9f987 100644 --- a/ipynb/primary-processing/08-collapse_samples.md +++ b/ipynb/primary-processing/08-collapse_samples.md @@ -137,6 +137,8 @@ Finally, within each body site, we're going to collapse over categories of inter ... --output_mapping_fp $ignored ``` +We also need to write out the taxa summary files for each of the categories in the collapsed data. + As usual, let's make sure we have files. ```python diff --git a/tests/test_util.py b/tests/test_util.py index c44e7e5..52d95d7 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -7,7 +7,8 @@ from StringIO import StringIO from unittest import TestCase, main -from numpy import array, nan +from numpy import array, nan, arange +from numpy.testing import assert_almost_equal from biom import Table from pandas.util.testing import assert_frame_equal @@ -15,7 +16,7 @@ slice_mapping_file, parse_mapping_file, verify_subset, concatenate_files, trim_fasta, count_samples, count_seqs, count_unique_participants, clean_and_reformat_mapping, - add_alpha_diversity, get_single_id_lists + add_alpha_diversity, get_single_id_lists, collapse_taxonomy, collapse_full ) __author__ = "Daniel McDonald" @@ -313,6 +314,37 @@ def test_get_single_id_list(self): 'unrare': ['A', 'B', 'C', 'D', 'E']} self.assertEqual(test, known) + def test_collapse_taxonomy(self): + obs = collapse_taxonomy(table) + exp = Table(array([[100.0, 105.0, 110.0, 115.0], + [44.0, 46.0, 48.0, 50.0], + [36.0, 39.0, 42.0, 45.0]]), + ['Bacteria; Firmicutes', 'Bacteria; Bacteroidetes', + 'Bacteria; Proteobacteria'], sample_ids, + sample_metadata=sample_metadata, observation_metadata=[ + {'collapsed_ids': ['O0', 'O1', 'O7', 'O8', 'O9']}, + {'collapsed_ids': ['O5', 'O6']}, + {'collapsed_ids': ['O2', 'O3', 'O4']}]) + self.assertEqual(obs, exp) + + def test_collapse_full(self): + obs = collapse_full(table) + exp = Table(array([[0.00769230769231], [0.0282051282051], + [0.0487179487179], [0.0692307692308], + [0.0897435897436], [0.110256410256], + [0.130769230769], [0.151282051282], + [0.171794871795], [0.192307692308]]), + observ_ids, ['average'], + observation_metadata=observ_metadata) + for r in range(10): + assert_almost_equal(obs[r, 0], exp[r, 0]) + self.assertEqual(obs.ids(), exp.ids()) + self.assertItemsEqual(obs.ids('observation'), exp.ids('observation')) + + obs_meta = [] + for _, _, m in obs.iter(axis='observation'): + obs_meta.append(m) + self.assertItemsEqual(obs_meta, observ_metadata) test_mapping = """#SampleIDs\tfoo\tbar a\t1\t123123 @@ -342,6 +374,24 @@ def test_get_single_id_list(self): E GAZ:stuff 56.0 UBERON:SKIN 37 """) +data = arange(40).reshape(10, 4) +sample_ids = ['S%d' % i for i in range(4)] +observ_ids = ['O%d' % i for i in range(10)] +sample_metadata = [{'environment': 'A'}, {'environment': 'B'}, + {'environment': 'A'}, {'environment': 'B'}] +observ_metadata = [{'taxonomy': ['Bacteria', 'Firmicutes']}, + {'taxonomy': ['Bacteria', 'Firmicutes']}, + {'taxonomy': ['Bacteria', 'Proteobacteria']}, + {'taxonomy': ['Bacteria', 'Proteobacteria']}, + {'taxonomy': ['Bacteria', 'Proteobacteria']}, + {'taxonomy': ['Bacteria', 'Bacteroidetes']}, + {'taxonomy': ['Bacteria', 'Bacteroidetes']}, + {'taxonomy': ['Bacteria', 'Firmicutes']}, + {'taxonomy': ['Bacteria', 'Firmicutes']}, + {'taxonomy': ['Bacteria', 'Firmicutes']}] +table = Table(data, observ_ids, sample_ids, observ_metadata, + sample_metadata, table_id='Example Table') + if __name__ == '__main__': main() From 216b41317c6188b2de7d92a886688b8bd28e2f8c Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 19 Apr 2016 22:15:18 -0700 Subject: [PATCH 02/11] add taxa summary by category generation --- americangut/per_category.py | 36 +++++++++++++++++++ .../primary-processing/08-collapse_samples.md | 2 -- .../09-per_participant_results.md | 1 + .../primary-processing/10-participant_pdfs.md | 6 ++++ 4 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 americangut/per_category.py diff --git a/americangut/per_category.py b/americangut/per_category.py new file mode 100644 index 0000000..7a5bb47 --- /dev/null +++ b/americangut/per_category.py @@ -0,0 +1,36 @@ +from os.path import join +from copy import copy + +from biom import load_table + +import americangut.notebook_environment as agenv +from .util import collapse_full, collapse_taxonomy, get_existing_path + + +def cat_taxa_summaries(): + """Creates taxa summary files for each available summary category per site + """ + paths = copy(agenv.paths['collapsed']['notrim']['1k']) + out_dir = get_existing_path( + agenv.path['populated-templates']['result-taxa']) + del paths['ag-biom'] + for name, path in paths.items(): + # consistent naming as stool for all participant items + name = name.replace('-biom', '').replace('fecal', 'stool') + table = load_table(get_existing_path(path)) + if len(name.split('-')) == 2: + # Have entire cohort of samples for site, so need to get averages + table = collapse_full(table) + table = collapse_taxonomy(table) + ids = table.ids(axis='observation') + for col in table.ids(): + if col == 'Unknown': + continue + cleaned_col = col.split('(')[0].strip().replace(' ', '_') + filename = '-'.join([name, cleaned_col]) + '.txt' + + with open(join(out_dir, filename), 'w') as f: + for otu, val in zip(ids, table.data(col)): + if val == 0.0: + continue + f.write('%s\t%s\n' % (otu, str(val))) diff --git a/ipynb/primary-processing/08-collapse_samples.md b/ipynb/primary-processing/08-collapse_samples.md index cd9f987..3164682 100644 --- a/ipynb/primary-processing/08-collapse_samples.md +++ b/ipynb/primary-processing/08-collapse_samples.md @@ -137,8 +137,6 @@ Finally, within each body site, we're going to collapse over categories of inter ... --output_mapping_fp $ignored ``` -We also need to write out the taxa summary files for each of the categories in the collapsed data. - As usual, let's make sure we have files. ```python diff --git a/ipynb/primary-processing/09-per_participant_results.md b/ipynb/primary-processing/09-per_participant_results.md index 4ede414..626b1b9 100644 --- a/ipynb/primary-processing/09-per_participant_results.md +++ b/ipynb/primary-processing/09-per_participant_results.md @@ -13,6 +13,7 @@ Now that we've done all the bulk processing, let's generate the per-sample resul >>> import americangut.util as agu >>> import americangut.results_utils as agru >>> import americangut.per_sample as agps +>>> import americangut.per_category as agpc >>> import americangut.parallel as agpar ... >>> chp_path = agenv.activate('09-per-sample') diff --git a/ipynb/primary-processing/10-participant_pdfs.md b/ipynb/primary-processing/10-participant_pdfs.md index 4150a6e..ce6856a 100644 --- a/ipynb/primary-processing/10-participant_pdfs.md +++ b/ipynb/primary-processing/10-participant_pdfs.md @@ -48,6 +48,12 @@ >>> process_pdf = partial(agps.sample_type_processor, [create_pdf, aggregate], opts) ``` +We also need to write out the taxa summary files for each of the categories in the collapsed data. These will live in the same folder as the participant's taxonomy files. + +```python +>>> agpc.cat_taxa_summaries() +``` + ```python >>> partitions = [(process_pdf, ids)] >>> with open(successful_pdfs, 'w') as successful_pdfs_fp, open(unsuccessful_pdfs, 'w') as unsuccessful_pdfs_fp: From 9a7d6d827f4e2d557320b521171133d613553d0d Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 19 Apr 2016 22:53:48 -0700 Subject: [PATCH 03/11] fix imports --- ipynb/primary-processing/09-per_participant_results.md | 1 - ipynb/primary-processing/10-participant_pdfs.md | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/ipynb/primary-processing/09-per_participant_results.md b/ipynb/primary-processing/09-per_participant_results.md index 626b1b9..4ede414 100644 --- a/ipynb/primary-processing/09-per_participant_results.md +++ b/ipynb/primary-processing/09-per_participant_results.md @@ -13,7 +13,6 @@ Now that we've done all the bulk processing, let's generate the per-sample resul >>> import americangut.util as agu >>> import americangut.results_utils as agru >>> import americangut.per_sample as agps ->>> import americangut.per_category as agpc >>> import americangut.parallel as agpar ... >>> chp_path = agenv.activate('09-per-sample') diff --git a/ipynb/primary-processing/10-participant_pdfs.md b/ipynb/primary-processing/10-participant_pdfs.md index ce6856a..39ec247 100644 --- a/ipynb/primary-processing/10-participant_pdfs.md +++ b/ipynb/primary-processing/10-participant_pdfs.md @@ -7,6 +7,7 @@ >>> import americangut.util as agu >>> import americangut.results_utils as agru >>> import americangut.per_sample as agps +>>> import americangut.per_category as agpc >>> import americangut.parallel as agpar ... >>> chp_path = agenv.activate('10-populated-templates') From face6724a997d5437eea2ff69f9001ec8fbaeee6 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Tue, 19 Apr 2016 23:05:58 -0700 Subject: [PATCH 04/11] flake8 --- americangut/util.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/americangut/util.py b/americangut/util.py index 3e26de1..2d63c87 100644 --- a/americangut/util.py +++ b/americangut/util.py @@ -658,7 +658,8 @@ def collapse_taxonomy(_bt, level=5): --------- [1] http://biom-format.org/documentation/table_objects.html """ - collapse_f = lambda id_, md: '; '.join(md['taxonomy'][:level + 1]) + def collapse_f(id_, md): + return '; '.join(md['taxonomy'][:level + 1]) collapsed = _bt.collapse(collapse_f, axis='observation', norm=False) return collapsed From 553f777a306744031df06f51060bc1655d6f8e29 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 20 Apr 2016 09:58:55 -0700 Subject: [PATCH 05/11] upgrade setuptools --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 63ead4f..42e5e14 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ addons: packages: - texlive-latex-extra before_install: + - pip install --upgrade setuptools - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - chmod +x miniconda.sh - ./miniconda.sh -b From 284613d536d18d2ad841fa945b633f06fd5a53eb Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 20 Apr 2016 10:30:57 -0700 Subject: [PATCH 06/11] downgrade setuptools --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 42e5e14..22d0e4a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,6 @@ addons: packages: - texlive-latex-extra before_install: - - pip install --upgrade setuptools - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh - chmod +x miniconda.sh - ./miniconda.sh -b @@ -17,6 +16,7 @@ before_install: install: - conda create --yes -n env_name python=$PYTHON_VERSION --file conda_requirements.txt - source activate env_name + - conda install -y "setuptools<20.7.0" - pip install -r pip_requirements.txt - pip install -e . --no-deps From a8254bf2d4626a46b007d809be0b69ce59760cfb Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 May 2016 11:09:59 -0700 Subject: [PATCH 07/11] address comments --- americangut/per_category.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/americangut/per_category.py b/americangut/per_category.py index 7a5bb47..778ab17 100644 --- a/americangut/per_category.py +++ b/americangut/per_category.py @@ -7,19 +7,19 @@ from .util import collapse_full, collapse_taxonomy, get_existing_path -def cat_taxa_summaries(): +def cat_taxa_summaries(debug=False): """Creates taxa summary files for each available summary category per site """ paths = copy(agenv.paths['collapsed']['notrim']['1k']) out_dir = get_existing_path( - agenv.path['populated-templates']['result-taxa']) + agenv.paths['populated-templates']['result-taxa']) del paths['ag-biom'] for name, path in paths.items(): # consistent naming as stool for all participant items name = name.replace('-biom', '').replace('fecal', 'stool') table = load_table(get_existing_path(path)) if len(name.split('-')) == 2: - # Have entire cohort of samples for site, so need to get averages + # Have entire cohort of samples for site, so need to get medians table = collapse_full(table) table = collapse_taxonomy(table) ids = table.ids(axis='observation') From 5c2da8a6dc2466469dad14f1aa8017f16dbf0d52 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 2 May 2016 13:09:13 -0700 Subject: [PATCH 08/11] add test --- tests/test_per_category.py | 109 +++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/test_per_category.py diff --git a/tests/test_per_category.py b/tests/test_per_category.py new file mode 100644 index 0000000..e7ceeb2 --- /dev/null +++ b/tests/test_per_category.py @@ -0,0 +1,109 @@ +from os import remove, mkdir +from os.path import join, exists + +from unittest import TestCase, main +import americangut.notebook_environment as agenv +from americangut.util import get_new_path +from americangut.per_category import cat_taxa_summaries + + +class PerCategoryTests(TestCase): + def setUp(self): + self.taxa_base = agenv.paths['populated-templates']['result-taxa'] + + try: + get_new_path( + agenv.paths['populated-templates']['result-taxa']) + except IOError: + pass + + self.taxa_files = [ + 'ag-stool-average.txt', + 'ag-stool-sex-male.txt', + 'ag-stool-sex-other.txt', + 'ag-stool-sex-female.txt', + 'ag-oral-diet-Omnivore.txt', + 'ag-oral-diet-Vegetarian.txt', + 'ag-oral-diet-Vegan.txt', + 'ag-oral-diet-Omnivore_but_do_not_eat_red_meat.txt', + 'ag-oral-diet-Vegetarian_but_eat_seafood.txt', + 'ag-skin-sex-male.txt', + 'ag-skin-sex-female.txt', + 'ag-stool-diet-Omnivore.txt', + 'ag-stool-diet-Vegetarian.txt', + 'ag-stool-diet-Vegan.txt', + 'ag-stool-diet-Omnivore_but_do_not_eat_red_meat.txt', + 'ag-stool-diet-Vegetarian_but_eat_seafood.txt', + 'ag-skin-hand-I_am_left_handed.txt', + 'ag-skin-hand-I_am_ambidextrous.txt', + 'ag-skin-hand-I_am_right_handed.txt', + 'ag-oral-sex-male.txt', + 'ag-oral-sex-female.txt', + 'ag-stool-bmi-Overweight.txt', + 'ag-stool-bmi-Obese.txt', + 'ag-stool-bmi-Underweight.txt', + 'ag-stool-bmi-Normal.txt', + 'ag-oral-flossing-Never.txt', + 'ag-oral-flossing-Rarely.txt', + 'ag-oral-flossing-Daily.txt', + 'ag-oral-flossing-Occasionally.txt', + 'ag-oral-flossing-Regularly.txt', + 'ag-stool-age-teen.txt', + 'ag-stool-age-20s.txt', + 'ag-stool-age-60s.txt', + 'ag-stool-age-30s.txt', + 'ag-stool-age-child.txt', + 'ag-stool-age-40s.txt', + 'ag-stool-age-70+.txt', + 'ag-stool-age-baby.txt', + 'ag-stool-age-50s.txt', + 'ag-oral-average.txt', + 'ag-skin-cosmetics-Never.txt', + 'ag-skin-cosmetics-Rarely.txt', + 'ag-skin-cosmetics-Daily.txt', + 'ag-skin-cosmetics-Occasionally.txt', + 'ag-skin-cosmetics-Regularly.txt', + 'ag-skin-age-teen.txt', + 'ag-skin-age-20s.txt', + 'ag-skin-age-60s.txt', + 'ag-skin-age-30s.txt', + 'ag-skin-age-70+.txt', + 'ag-skin-age-40s.txt', + 'ag-skin-age-child.txt', + 'ag-skin-age-baby.txt', + 'ag-skin-age-50s.txt', + 'ag-skin-average.txt', + 'ag-oral-age-teen.txt', + 'ag-oral-age-20s.txt', + 'ag-oral-age-60s.txt', + 'ag-oral-age-30s.txt', + 'ag-oral-age-child.txt', + 'ag-oral-age-40s.txt', + 'ag-oral-age-70+.txt', + 'ag-oral-age-50s.txt'] + + def tearDown(self): + for f in self.taxa_files: + path = join(self.taxa_base, f) + if exists(path): + remove(path) + + def test_cat_taxa_summaries(self): + cat_taxa_summaries() + # Make sure all files created + for f in self.taxa_files: + path = join(self.taxa_base, f) + if not exists(path): + raise AssertionError('File %s not generated!' % f) + + # Test file for correct format + self.maxDiff = None + with open(join(self.taxa_base, 'ag-skin-age-teen.txt')) as f: + obs = f.read() + + exp = '' + self.assertEqual(obs, exp) + + +if __name__ == '__main__': + main() From 1e6aa1e2fea304a563ca2e635609941e23c59f9c Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 4 May 2016 12:27:02 -0700 Subject: [PATCH 09/11] add test --- americangut/per_category.py | 2 +- tests/data/ag_testing/ag-fecal.biom | Bin 0 -> 110287 bytes tests/data/ag_testing/ag-oral-flossing.biom | Bin 0 -> 119112 bytes tests/test_per_category.py | 157 +++++++++----------- 4 files changed, 69 insertions(+), 90 deletions(-) create mode 100644 tests/data/ag_testing/ag-fecal.biom create mode 100644 tests/data/ag_testing/ag-oral-flossing.biom diff --git a/americangut/per_category.py b/americangut/per_category.py index 778ab17..3822450 100644 --- a/americangut/per_category.py +++ b/americangut/per_category.py @@ -7,7 +7,7 @@ from .util import collapse_full, collapse_taxonomy, get_existing_path -def cat_taxa_summaries(debug=False): +def cat_taxa_summaries(): """Creates taxa summary files for each available summary category per site """ paths = copy(agenv.paths['collapsed']['notrim']['1k']) diff --git a/tests/data/ag_testing/ag-fecal.biom b/tests/data/ag_testing/ag-fecal.biom new file mode 100644 index 0000000000000000000000000000000000000000..5feba16167bef471bcfa1df60a3122080f58c4a5 GIT binary patch literal 110287 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

BgblDM~*dM=W(nBoyV~bbc`?Ks6Ow9j%+>n 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 bUp1?h>m^23-nxqbuU^KJ*OLG0dinnV48z2T literal 0 HcmV?d00001 diff --git a/tests/data/ag_testing/ag-oral-flossing.biom b/tests/data/ag_testing/ag-oral-flossing.biom new file mode 100644 index 0000000000000000000000000000000000000000..91c0aca7e084bd46058d3ccca7aeee150a4bf3b0 GIT binary patch literal 119112 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 UPHP(#w+g;jppOh+vGs-j0SbmOssI20 literal 0 HcmV?d00001 diff --git a/tests/test_per_category.py b/tests/test_per_category.py index e7ceeb2..9c850ad 100644 --- a/tests/test_per_category.py +++ b/tests/test_per_category.py @@ -1,109 +1,88 @@ -from os import remove, mkdir -from os.path import join, exists +from os import makedirs, listdir, rmdir +from os.path import exists, join +from shutil import rmtree from unittest import TestCase, main import americangut.notebook_environment as agenv -from americangut.util import get_new_path +from americangut.util import get_existing_path from americangut.per_category import cat_taxa_summaries class PerCategoryTests(TestCase): def setUp(self): - self.taxa_base = agenv.paths['populated-templates']['result-taxa'] - - try: - get_new_path( - agenv.paths['populated-templates']['result-taxa']) - except IOError: - pass - - self.taxa_files = [ - 'ag-stool-average.txt', - 'ag-stool-sex-male.txt', - 'ag-stool-sex-other.txt', - 'ag-stool-sex-female.txt', - 'ag-oral-diet-Omnivore.txt', - 'ag-oral-diet-Vegetarian.txt', - 'ag-oral-diet-Vegan.txt', - 'ag-oral-diet-Omnivore_but_do_not_eat_red_meat.txt', - 'ag-oral-diet-Vegetarian_but_eat_seafood.txt', - 'ag-skin-sex-male.txt', - 'ag-skin-sex-female.txt', - 'ag-stool-diet-Omnivore.txt', - 'ag-stool-diet-Vegetarian.txt', - 'ag-stool-diet-Vegan.txt', - 'ag-stool-diet-Omnivore_but_do_not_eat_red_meat.txt', - 'ag-stool-diet-Vegetarian_but_eat_seafood.txt', - 'ag-skin-hand-I_am_left_handed.txt', - 'ag-skin-hand-I_am_ambidextrous.txt', - 'ag-skin-hand-I_am_right_handed.txt', - 'ag-oral-sex-male.txt', - 'ag-oral-sex-female.txt', - 'ag-stool-bmi-Overweight.txt', - 'ag-stool-bmi-Obese.txt', - 'ag-stool-bmi-Underweight.txt', - 'ag-stool-bmi-Normal.txt', - 'ag-oral-flossing-Never.txt', - 'ag-oral-flossing-Rarely.txt', - 'ag-oral-flossing-Daily.txt', - 'ag-oral-flossing-Occasionally.txt', - 'ag-oral-flossing-Regularly.txt', - 'ag-stool-age-teen.txt', - 'ag-stool-age-20s.txt', - 'ag-stool-age-60s.txt', - 'ag-stool-age-30s.txt', - 'ag-stool-age-child.txt', - 'ag-stool-age-40s.txt', - 'ag-stool-age-70+.txt', - 'ag-stool-age-baby.txt', - 'ag-stool-age-50s.txt', - 'ag-oral-average.txt', - 'ag-skin-cosmetics-Never.txt', - 'ag-skin-cosmetics-Rarely.txt', - 'ag-skin-cosmetics-Daily.txt', - 'ag-skin-cosmetics-Occasionally.txt', - 'ag-skin-cosmetics-Regularly.txt', - 'ag-skin-age-teen.txt', - 'ag-skin-age-20s.txt', - 'ag-skin-age-60s.txt', - 'ag-skin-age-30s.txt', - 'ag-skin-age-70+.txt', - 'ag-skin-age-40s.txt', - 'ag-skin-age-child.txt', - 'ag-skin-age-baby.txt', - 'ag-skin-age-50s.txt', - 'ag-skin-average.txt', - 'ag-oral-age-teen.txt', - 'ag-oral-age-20s.txt', - 'ag-oral-age-60s.txt', - 'ag-oral-age-30s.txt', - 'ag-oral-age-child.txt', - 'ag-oral-age-40s.txt', - 'ag-oral-age-70+.txt', - 'ag-oral-age-50s.txt'] + # Make expected directory structure + makedirs('../agp_processing/10-populated-templates/taxa') + self.path = agenv.paths['collapsed']['notrim']['1k'] + agenv.paths['collapsed']['notrim']['1k'] = \ + {'ag-biom': + '../tests/data/ag_testing/category_test.biom', + 'ag-fecal': + '../tests/data/ag_testing/ag-fecal.biom', + 'ag-oral-flossing': + '../tests/data/ag_testing/ag-oral-flossing.biom'} def tearDown(self): - for f in self.taxa_files: - path = join(self.taxa_base, f) - if exists(path): - remove(path) + rmtree('../agp_processing', ignore_errors=True) + agenv.paths['collapsed']['notrim']['1k'] = self.path def test_cat_taxa_summaries(self): cat_taxa_summaries() - # Make sure all files created - for f in self.taxa_files: - path = join(self.taxa_base, f) - if not exists(path): - raise AssertionError('File %s not generated!' % f) + path = get_existing_path( + '../agp_processing/10-populated-templates/taxa') + exp = ['ag-oral-flossing-Daily.txt', 'ag-oral-flossing-Never.txt', + 'ag-oral-flossing-Occasionally.txt', + 'ag-oral-flossing-Rarely.txt', 'ag-oral-flossing-Regularly.txt', + 'ag-stool-average.txt'] + files = listdir(path) + self.assertEqual(files, exp) - # Test file for correct format - self.maxDiff = None - with open(join(self.taxa_base, 'ag-skin-age-teen.txt')) as f: + with open(join(path, 'ag-oral-flossing-Rarely.txt')) as f: obs = f.read() - - exp = '' + exp = """k__Bacteria; p__Fusobacteria; c__Fusobacteriia; o__Fusobacteriales; f__Fusobacteriaceae; g__Fusobacterium\t0.0110430107527 +k__Bacteria; p__Firmicutes; c__Bacilli; o__Bacillales; f__Planococcaceae; g__\t0.00196774193548 +k__Bacteria; p__Bacteroidetes; c__Flavobacteriia; o__Flavobacteriales; f__[Weeksellaceae]; g__Chryseobacterium\t0.0125913978495 +k__Bacteria; p__Bacteroidetes; c__Sphingobacteriia; o__Sphingobacteriales; f__Sphingobacteriaceae; g__Sphingobacterium\t0.00608602150538 +k__Bacteria; p__Firmicutes; c__Erysipelotrichi; o__Erysipelotrichales; f__Erysipelotrichaceae; g__Bulleidia\t0.00269892473118 +k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Neisseriales; f__Neisseriaceae; g__Neisseria\t0.074935483871 +k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Oribacterium\t0.00749462365591 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Xanthomonadales; f__Xanthomonadaceae; g__\t0.017311827957 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pseudomonadales; f__Moraxellaceae; g__Enhydrobacter\t0.000150537634409 +k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__\t0.00667741935484 +k__Bacteria; p__Proteobacteria; c__Alphaproteobacteria; o__Caulobacterales; f__Caulobacteraceae; g__Brevundimonas\t0.00996774193548 +k__Bacteria; p__Bacteroidetes; c__Flavobacteriia; o__Flavobacteriales; f__[Weeksellaceae]; g__\t0.00139784946237 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Klebsiella\t0.00174193548387 +k__Bacteria; p__Firmicutes; c__Bacilli; o__Bacillales; f__Staphylococcaceae; g__Staphylococcus\t0.00756989247312 +k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Prevotellaceae; g__Prevotella\t0.0509569892473 +k__Bacteria; p__Fusobacteria; c__Fusobacteriia; o__Fusobacteriales; f__Leptotrichiaceae; g__Leptotrichia\t0.0143333333333 +k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Streptococcaceae; g__Streptococcus\t0.138817204301 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pseudomonadales; f__Pseudomonadaceae; g__\t0.0051935483871 +k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__[Paraprevotellaceae]; g__[Prevotella]\t0.00132258064516 +k__Bacteria; p__Proteobacteria; c__Epsilonproteobacteria; o__Campylobacterales; f__Campylobacteraceae; g__Campylobacter\t0.00133333333333 +k__Bacteria; p__Firmicutes; c__Bacilli; o__Gemellales; f__Gemellaceae; g__\t0.0097311827957 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__\t0.0135268817204 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Haemophilus\t0.0668279569892 +k__Bacteria; p__Proteobacteria; c__Alphaproteobacteria; o__Rhizobiales; f__Brucellaceae; g__Ochrobactrum\t0.00575268817204 +k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Veillonellaceae; g__Veillonella\t0.0709247311828 +k__Bacteria; p__Bacteroidetes; c__Bacteroidia; o__Bacteroidales; f__Porphyromonadaceae; g__Porphyromonas\t0.0165698924731 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pasteurellales; f__Pasteurellaceae; g__Aggregatibacter\t0.00410752688172 +k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Neisseriales; f__Neisseriaceae; g__\t0.00394623655914 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pseudomonadales; f__Moraxellaceae; g__Acinetobacter\t0.0247634408602 +k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Aerococcaceae; g__Alloiococcus\t0.000225806451613 +k__Bacteria; p__Firmicutes; c__Clostridia; o__Clostridiales; f__Lachnospiraceae; g__Moryella\t0.000612903225806 +k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Micrococcaceae; g__Rothia\t0.114602150538 +k__Bacteria; p__Actinobacteria; c__Coriobacteriia; o__Coriobacteriales; f__Coriobacteriaceae; g__Atopobium\t0.00761290322581 +k__Bacteria; p__Proteobacteria; c__Betaproteobacteria; o__Burkholderiales; f__Comamonadaceae; g__Alicycliphilus\t0.00530107526882 +k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Actinomycetaceae; g__Actinomyces\t0.0204408602151 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Xanthomonadales; f__Xanthomonadaceae; g__Stenotrophomonas\t0.0443655913978 +k__Bacteria; p__Actinobacteria; c__Actinobacteria; o__Actinomycetales; f__Corynebacteriaceae; g__Corynebacterium\t0.000440860215054 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Enterobacteriales; f__Enterobacteriaceae; g__Morganella\t0.000161290322581 +k__Bacteria; p__Firmicutes; c__Bacilli; o__Lactobacillales; f__Carnobacteriaceae; g__Granulicatella\t0.025247311828 +k__Bacteria; p__SR1; c__; o__; f__; g__\t0.00222580645161 +k__Bacteria; p__Proteobacteria; c__Gammaproteobacteria; o__Pseudomonadales; f__Pseudomonadaceae; g__Pseudomonas\t0.0665268817204 +""" self.assertEqual(obs, exp) + if __name__ == '__main__': main() From 1eb39c88a58dde535802997c2d63b406699ba565 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Wed, 4 May 2016 13:47:40 -0700 Subject: [PATCH 10/11] directories in test --- tests/test_per_category.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/test_per_category.py b/tests/test_per_category.py index 9c850ad..bf60370 100644 --- a/tests/test_per_category.py +++ b/tests/test_per_category.py @@ -1,5 +1,5 @@ -from os import makedirs, listdir, rmdir -from os.path import exists, join +from os import makedirs, listdir +from os.path import join, dirname, realpath from shutil import rmtree from unittest import TestCase, main @@ -11,7 +11,9 @@ class PerCategoryTests(TestCase): def setUp(self): # Make expected directory structure - makedirs('../agp_processing/10-populated-templates/taxa') + currpath = dirname(realpath(__file__)) + self.dirpath = join(currpath, '../agp_processing/10-populated-templates/taxa') + makedirs(self.dirpath) self.path = agenv.paths['collapsed']['notrim']['1k'] agenv.paths['collapsed']['notrim']['1k'] = \ {'ag-biom': @@ -22,7 +24,7 @@ def setUp(self): '../tests/data/ag_testing/ag-oral-flossing.biom'} def tearDown(self): - rmtree('../agp_processing', ignore_errors=True) + rmtree(self.dirpath, ignore_errors=True) agenv.paths['collapsed']['notrim']['1k'] = self.path def test_cat_taxa_summaries(self): @@ -34,7 +36,7 @@ def test_cat_taxa_summaries(self): 'ag-oral-flossing-Rarely.txt', 'ag-oral-flossing-Regularly.txt', 'ag-stool-average.txt'] files = listdir(path) - self.assertEqual(files, exp) + self.assertItemsEqual(files, exp) with open(join(path, 'ag-oral-flossing-Rarely.txt')) as f: obs = f.read() From 5dab3104ba94e96cdc630ba5c6aeb8e1ad26ccc6 Mon Sep 17 00:00:00 2001 From: Joshua Shorenstein Date: Mon, 9 May 2016 08:33:43 -0700 Subject: [PATCH 11/11] simplofy collapse_full --- americangut/util.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/americangut/util.py b/americangut/util.py index 2d63c87..964f299 100644 --- a/americangut/util.py +++ b/americangut/util.py @@ -678,16 +678,11 @@ def collapse_full(_bt): Collapsed biom table, one sample containing median of each OTU, normalized. """ - data = np.empty([_bt.length('observation'), 1]) - obs = [] - observation_metadata = [] - pos = 0 - for vals, id_, metadata in _bt.iter(axis='observation'): - data[pos][0] = np.median(vals) - obs.append(id_) - observation_metadata.append(metadata) - pos += 1 - table = Table(data, obs, ['average'], - observation_metadata=observation_metadata) + num_obs = len(_bt.ids(axis='observation')) + table = Table(np.array( + [np.median(v) for v in _bt.iter_data(axis='observation')]).reshape( + (num_obs, 1)), + _bt.ids(axis='observation'), ['average'], + observation_metadata=_bt.metadata(axis='observation')) table.norm(inplace=True) return table