Skip to content

Commit 0afcc8c

Browse files
committed
Changes for MESSES
A few little changes and bug fixes that came out of updating the messes package.
1 parent e1a2f4b commit 0afcc8c

8 files changed

Lines changed: 106 additions & 42 deletions

File tree

docs/todo.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,24 @@ Hunter also wanted to consider adding things like the _factors properties into t
88

99
Think about adding an "UNASSIGNED" data block for the datasets we found that have a results_file instead of having the data in the mwTab file.
1010
Pretty sure most of these if not all are all unnassigned data where there are basically bins and no metabolite assinments.
11+
12+
13+
Had some new undocumented requirements pop up when submitting the Helsley data. The following is an email from Eoin about it:
14+
Hi Travis,
15+
You should have received an automated email regarding the completion of your submission.
16+
There were a couple of problems with the mwtab files. Our submission system requires a single common study design block for all analyses that have the same set of headings for all samples (even if some of them are blank) Headings like Sex and Resected tissue type have to include all samples.
17+
Also, the MW needs a mandatory column which indicates the source of each sample-I added one and designated Liver as the source of each patient sample. The “Show all samples link” in the main page (https://dev.metabolomicsworkbench.org:22222/data/subject_fetch.php?STUDY_ID=ST004733&STUDY_TYPE=MS&RESULT_TYPE=1&Access=IloQ2417) shows the proper layout.
18+
The other issue was that there were a large number of unassigned annotations (m/z_rt features) that may not be added to an mwtab file. These are submitted as tab-delimited text files and saved with the raw data. I pulled these out of the mwtab files and saved them, one for each analysis:
19+
ST004733_AN008002_Results.txt (124.1K)
20+
ST004733_AN008003_Results.txt (249.1K)
21+
ST004733_AN008004_Results.txt (135K)
22+
ST004733_AN008005_Results.txt (254.2K)
23+
ST004733_AN008006_Results.txt (109.5K)
24+
The can be seen under the “Download raw/supplementary data” on the main page.
25+
26+
Need to address these. New check in validation for all SSF to have the same factors.
27+
Must have a new required factor "Sample source" for every sample. It might not have to have that name, but that is
28+
the name most common in the latest deposited datasets.
29+
Add check in validation that Metabolite names aren't just numbers or rt_mz or something like that.
30+
Those now have to be submitted as results files.
31+

src/mwtab/cli.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ def cli(cmdargs):
266266
optional_to_path = cmdargs.get('--to-path')
267267
optional_output_item = cmdargs.get('--output-item')
268268
required_output_item = cmdargs.get('<output-item>')
269-
download_results_files = cmdargs['--results-files']
269+
download_results_files = cmdargs.get('--results-files')
270270

271271

272272
# mwtab convert ...

src/mwtab/metadata_column_matching.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -725,7 +725,7 @@ def make_list_regex(element_regex: str, delimiter: str , quoted_elements: bool =
725725

726726
ColumnFinder("inchi",
727727
NameMatcher(in_strings = ['inchi'],
728-
not_in_strings = ['key'],),
728+
not_in_strings = ['key', 'base_inchi', 'representative_inchi', 'isotopic_inchi'],),
729729
ValueMatcher(values_regex = '(' + CHEAP_INCHI + '|' + BRACKETED_LIST_OF_INCHI + ')',)),
730730

731731
ColumnFinder("smiles",
@@ -782,6 +782,7 @@ def make_list_regex(element_regex: str, delimiter: str , quoted_elements: bool =
782782
ValueMatcher(values_regex = '(' + ION + '|' + \
783783
LIST_OF_IONS + '|' + \
784784
LIST_OF_IONS_SPACE + '|' + \
785+
LIST_OF_IONS_MIXED + '|' + \
785786
NUMS + '|' + \
786787
LIST_OF_NUMS + '|' + \
787788
NUMS + r'(\s*>\s*|\s*<\s*)' + NUMS + '|' + \

src/mwtab/mwtab.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ def print_subject_sample_factors(self, section_key, f=sys.stdout, file_format="m
776776
# for file missing "Additional sample data" items
777777
if len(formatted_items) < 4:
778778
line += "\t"
779-
print(line, file=f)
779+
print(line.replace('\n', ' '), file=f)
780780

781781
def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
782782
"""Print `mwtab` section into a file or stdout.
@@ -804,13 +804,13 @@ def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
804804

805805
results_string = self.prefixes.get(section_key, "") + key + cw * " " + "\t"
806806
results_string += self._create_result_file_string(section_key, key, "mwtab")
807-
print(results_string, file=f)
807+
print(results_string.replace('\n', ' '), file=f)
808808

809809
# prints #MS_METABOLITE_DATA, #NMR_METABOLITE_DATA, or #NMR_BINNED_DATA sections
810810
elif key == "Units":
811-
print("{}:UNITS{}\t{}".format(section_key, cw * " ", value), file=f)
811+
print("{}:UNITS{}\t{}".format(section_key, cw * " ", value).replace('\n', ' '), file=f)
812812
elif key == "Data":
813-
print("{}_START".format(section_key), file=f)
813+
print("{}_START".format(section_key).replace('\n', ' '), file=f)
814814

815815
if "METABOLITE" in section_key:
816816
# prints "Samples" line at head of data section
@@ -819,7 +819,7 @@ def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
819819
sample_names = self._samples
820820
elif self[section_key][key]:
821821
sample_names = [k for k in self[section_key][key][0].keys()][1:]
822-
print("\t".join(["Samples"] + sample_names), file=f)
822+
print("\t".join(["Samples"] + sample_names).replace('\n', ' '), file=f)
823823
if sample_names:
824824
# prints "Factors" line at head of data section
825825
if self._factors is not None:
@@ -848,10 +848,10 @@ def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
848848
break
849849
factors_list.append(factors_dict[k])
850850
if factors_list:
851-
print("\t".join(["Factors"] + factors_list), file=f)
851+
print("\t".join(["Factors"] + factors_list).replace('\n', ' '), file=f)
852852

853853
for k, i in enumerate(self[section_key][key]):
854-
print("\t".join(i.values()), file=f)
854+
print("\t".join(i.values()).replace('\n', ' '), file=f)
855855

856856
else: # NMR_BINNED_DATA
857857
# Only print if there is data to print.
@@ -860,20 +860,38 @@ def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
860860
elif self[section_key][key]:
861861
binned_header = [k for k in self[section_key][key][0].keys()][1:]
862862

863-
print("\t".join(["Bin range(ppm)"] + binned_header), file=f)
863+
print("\t".join(["Bin range(ppm)"] + binned_header).replace('\n', ' '), file=f)
864864

865-
for i in self[section_key][key]:
866-
print("\t".join(i.values()), file=f)
865+
for i, data_dict in enumerate(self[section_key][key]):
866+
867+
if 'Bin range(ppm)' in data_dict:
868+
if data_dict['Metabolite'] != data_dict['Bin range(ppm)']:
869+
print("Warning: The \"Metabolite\" key and \"Bin range(ppm)\" "
870+
f"key in ['NMR_BINNED_DATA']['Data'][{i}] are different "
871+
"values. Only the value in \"Bin range(ppm)\" will be written out.")
872+
del self[section_key][key][i]['Metabolite']
873+
print("\t".join(data_dict.values()), file=f)
874+
self[section_key][key][i]['Metabolite'] = self[section_key][key][i]['Bin range(ppm)']
875+
else:
876+
new_dict = self._default_dict_type()
877+
new_dict['Bin range(ppm)'] = data_dict['Metabolite']
878+
del data_dict['Metabolite']
879+
# Note that the update method cannot be used here because it can mess up DuplicatesDict.
880+
for key2, value2 in data_dict.items():
881+
new_dict[key2] = value2
882+
print("\t".join(new_dict.values()), file=f)
883+
self[section_key][key][i]['Metabolite'] = new_dict['Bin range(ppm)']
884+
867885

868-
print("{}_END".format(section_key), file=f)
886+
print("{}_END".format(section_key).replace('\n', ' '), file=f)
869887

870888
# prints #METABOLITES section
871889
elif key in ("Metabolites", "Extended"):
872890
if key == "Metabolites":
873891
print("#METABOLITES", file=f)
874892
print("METABOLITES_START", file=f)
875893
else:
876-
print("EXTENDED_{}_START".format(section_key), file=f)
894+
print("EXTENDED_{}_START".format(section_key).replace('\n', ' '), file=f)
877895

878896
if key == "Metabolites" and self._metabolite_header is not None:
879897
metabolite_header = self._metabolite_header
@@ -883,15 +901,15 @@ def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
883901
metabolite_header = [k for k in self[section_key][key][0].keys()][1:]
884902
else:
885903
metabolite_header = []
886-
print("\t".join(["metabolite_name"] + metabolite_header), file=f)
904+
print("\t".join(["metabolite_name"] + metabolite_header).replace('\n', ' '), file=f)
887905

888906
for i in self[section_key][key]:
889-
print("\t".join(i.values()), file=f)
907+
print("\t".join(i.values()).replace('\n', ' '), file=f)
890908

891909
if key == "Metabolites":
892910
print("METABOLITES_END", file=f)
893911
else:
894-
print("EXTENDED_{}_END".format(section_key), file=f)
912+
print("EXTENDED_{}_END".format(section_key).replace('\n', ' '), file=f)
895913

896914
else:
897915
# Filenames don't get split.
@@ -908,13 +926,13 @@ def print_block(self, section_key, f=sys.stdout, file_format="mwtab"):
908926
# I fixed this by adding a check to skip filenames, but just in case I also don't
909927
# let empty lines be printed.
910928
if line:
911-
print("{}{}{}\t{}".format(self.prefixes.get(section_key, ""), key, cw * " ", " ".join(line)), file=f)
929+
print("{}{}{}\t{}".format(self.prefixes.get(section_key, ""), key, cw * " ", " ".join(line)).replace('\n', ' '), file=f)
912930
line = [word]
913931
length = len(word)
914-
print("{}{}{}\t{}".format(self.prefixes.get(section_key, ""), key, cw * " ", " ".join(line)),
932+
print("{}{}{}\t{}".format(self.prefixes.get(section_key, ""), key, cw * " ", " ".join(line)).replace('\n', ' '),
915933
file=f)
916934
else:
917-
print("{}{}{}\t{}".format(self.prefixes.get(section_key, ""), key, cw * " ", value), file=f)
935+
print("{}{}{}\t{}".format(self.prefixes.get(section_key, ""), key, cw * " ", value).replace('\n', ' '), file=f)
918936

919937
# Note that indent cannot be None or json will use a version of the
920938
# encoder written in C and DuplicatesDict will not be printed correctly.
@@ -1101,7 +1119,7 @@ def _set_key_order(self):
11011119
Sets the key order to a certain order for better reproducibility and consistency.
11021120
"""
11031121
key_order = \
1104-
{'METABOLOMICS WORKBENCH': {},
1122+
{'METABOLOMICS WORKBENCH': {'STUDY_ID': [], 'ANALYSIS_ID': [], 'VERSION': [], 'CREATED_ON': []},
11051123
'PROJECT': {},
11061124
'STUDY': {},
11071125
'SUBJECT': {},

src/mwtab/validator.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,10 @@ def validate_metabolites(mwtabfile, data_section_key, mwtabfile_tables):
360360
metabolites_errors.append({'message': message, 'tags': ['value'], 'section': data_section_key, 'sub-section': 'Metabolites',
361361
'ID': '15', 'name': 'Standard Column Name Match'})
362362
for column_name in column_matches:
363+
# Do not want to check columns that look similar if a direct match already exits, for example retention_time and retention_time%units.
364+
# A message for retention_time%units would be spurious.
365+
if name in columns and column_name.lower() != name:
366+
continue
363367
if column_name in columns_to_standard_columns:
364368
columns_to_standard_columns[column_name].append(name)
365369
else:
@@ -797,7 +801,8 @@ def create_better_error_messages(errors_generator: Iterable[jsonschema.exception
797801
container_noun = 'key'
798802

799803
if key_is_required:
800-
message = message + f' A legitimate value should be provided for this required {container_noun}.'
804+
message = message + f' A legitimate value should be provided for this required {container_noun}.' + \
805+
' You may have to ignore this error in certain situations. For example, SOLVENT_A for a GCMS experiment.'
801806
else:
802807
message = message + f' Either a legitimate value should be provided for this {container_noun}, or it should be removed altogether.'
803808
else:
@@ -930,7 +935,10 @@ def validate_table_values(mwtabfile, data_section_key, mwtabfile_tables, na_valu
930935
# Look for overbalanced values, so if 90% of a column is dominated by a single value print a warning.
931936
for i, column in enumerate([column for column in data_df.columns if column != 'Metabolite']):
932937
temp_column = data_df.loc[:, column].astype(str)
933-
value_counts = temp_column.value_counts(dropna=False)
938+
# There are fairly sparse data sets where this check will just be spurious if blanks/NAs are included.
939+
value_counts = temp_column.value_counts(dropna = True if table_name == 'Data' else False)
940+
if table_name == 'Data':
941+
value_counts = value_counts[value_counts.index != '']
934942
value_counts = value_counts / value_counts.sum()
935943
if any(value_counts > .9) and len(value_counts) > 1:
936944
message = (f'Warning: {format_column_name(column, data_df.columns.get_loc(column)+1)} '

0 commit comments

Comments
 (0)