SQ-837: Refine the sample metadata TSV handling and write user guides#67
Conversation
Users can use this to add their own user defined columns for these samples.
For example: divbase-cli query tsv "Population:2-3" --metadata-tsv-name \ tutorial_mock_metadata_mgpv3snps.tsv Pandas read_csv() infer if columns are numerical or string based on the data so we already had the correct type in the df. See: https://pandas.pydata.org/docs/reference/api/pandas.read_csv.html
Might need some more testing and docs, but the basics are in place.
I.e inquality OR range OR discrete value example "Weight": ">50,30-40,25,45"
Up until now, warnings were only displayed in the worker logs.
Use textwrap to ensure that bulleted warnings keep bullet indentation when warnings string is too long to fit within the terminal width.
Use lambda functions and pandas .apply() to split values on semicolons and apply filters accordingly. This allows users to define sample metadata TSVs where cells can contain multiple values separated by semicolons, and filter strings can match any of those values. Sample_ID Group S1 1 S2 1;2 S3 2
There might be an issue with semicolon separated numerical values...
Pandas will not infer a column as numeric if it contains semicolon-separated values, even if those values are numeric. This means that the inequalities and range logic will not work for such values. This adds a helper method that checks if a column contains semicolon-separated numeric values and raises an error if the values are of mixed types (e.g. "1";two;3). The lambda functions in the run_query() logic will convert each semicolon-separated numerical value float or int before applying the inequality or range logic.
For semicolon-separated columns.
Return 400 and print error message with details about the mixed numeric and string types in the column.
String filtering has less set operations that the numerical filters (inquealities, range, discrete) since it only takes discrete values. Will thus need less test coverage for now.
And tests that act on that column. This ensure that the query logic can handle columns that contain some semicolon-separated and columns with no semicolon-separated values
Some of these tests fail at the moment which show spots where the SidecarQueryManager's type infereance and handling should either be improved or clarified in the docs.
Add helper methods to keep the checks DRY, and update fixture and tests.
To make the code easier to read, and a little more DRY. Also took the opurtunity to change the lamda functions used to create filters for numerical operations on the dataframe into named nested functions. This should hopefully be a little more readable and still comply with the Ruff linter.
Could be a common use case to have hyphens in string values, e.g. "North-East", "South-West". Numerical values are not supported and will raise an exception.
Big refactoring. The same processing needs to be applied to positive and to negative user-inputted filter values. NOT conditions are applied with AND logic after positive conditions: in the end, the rows must NOT match any negated values. To try to keep the code DRY and manageable, several helper methods have been added.
Whitespaces inside values are preserved
…-loc' into sample-metadata-functionalities
Simplifies the CLI validator class and collects all validation logic in the shared validator.
Was now only a thin wrapper around the SharedMetadataValidator.
Was -full-sample-mismatch-names before when it only was for the sample names.
Instead of semicolons. Also add flag for generating a column that will results in errors in SharedMetadataValidator
|
Thanks for the feedback on this here and in person, @RMCrean . After ruminating on and trying out your suggestion of using Python list style arrays and After implementing the bracket arrays, I decided to work on simplifying this part of the code so that it will be easier for us to maintain in the future and easier for you to overview in the review. I did not work on the centralised task exception handling yet. Perhaps that would be better suited for its own branch? SharedMetadataValidatorAfter this refactoring, Warnings and errors detected by The warnings and errors are now aggregated per column instead per row to create more compact messages. For instance, an error like Bracket array implementation
If the evaluation passes, it applies a reformatting of the string literal to a Python list to the dataframe. If no errors are raised by If MetadataValidationResult consumers: CLI validator and Query engineCLI validator: Server-side query engine:
CommandsThis TSV fixture is designed to be a vertical slice of the metadata TSV error and warning handler: # Client-side validator
divbase-cli dimensions validate-metadata-file tests/fixtures/sample_metadata_incorrect_formatting_to_test_tsv_validator.tsvIf you run this against the project you uploaded the 5000 sample mock data to with the commands in PR #70, you should hopefully see how the errors and warnings truncates the sample mismatches. Add the The mock metadata generator was also updated to accommodate the refactoring. If you want, you can regenerate the mock TSV with bracket array multi-values and validate it against the project dimensions where you put the mock 5000 sample VCF. python scripts/generate_mock_sample_metadata.py --vcf mock_vcf_5000s_100r.vcf.gz --output mock_metadata_mock_vcf_5000s_100r.tsv --columns 25 --add-warning-column
divbase-cli dimensions validate-metadata-file mock_metadata_mock_vcf_5000s_100r.tsv --project <PROJECT_WHERE_THE_MOCK_5000_DATA_IS>
# Can also upload it to the bucket. Not needed for the client side validator, but for queries.
divbase-cli files upload mock_metadata_mock_vcf_5000s_100r.tsv --project <PROJECT_WHERE_THE_MOCK_5000_DATA_IS> |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 32 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
packages/divbase-api/src/divbase_api/worker/tasks.py:204
sample_metadata_query_taskcallslatest_version_of_all_files()and_check_that_dimensions_is_up_to_date_with_VCF_files_in_bucket()twice back-to-back with identical arguments. This duplicates an S3 listing call (potentially expensive) and can double the runtime of every metadata query task. Remove the duplicated second call, or document why the check must run twice (it doesn’t appear to here).
latest_versions_of_bucket_files = s3_file_manager.latest_version_of_all_files(bucket_name=bucket_name)
_check_that_dimensions_is_up_to_date_with_VCF_files_in_bucket(
vcf_dimensions_data=vcf_dimensions_data,
latest_versions_of_bucket_files=latest_versions_of_bucket_files,
project_id=project_id,
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
As suggested by copilot review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Base on suggestions in copilot review
RMCrean
left a comment
There was a problem hiding this comment.
Awesome job with these changes! 👍 I've tried to stress test the system. I really like the validator aspect of the sample metadata and good to know this approach scales fine timing wise for relatively big sample metadata files. Sounds good to focus on the exception handling another time.
One thing that I realized now when I was playing around (maybe not from this PR per say), but I feel like it is was worth trying to improve:
divbase-cli dimensions update
divbase-cli query bcftools-pipe --tsv-filter "Area:Northern Portugal" --command "view -s SAMPLES" --metadata-tsv-name sample_metadata.tsv
# job works and creates output
divbase-cli query bcftools-pipe --tsv-filter "Area:Northern Portugal" --command "view -s SAMPLES" --metadata-tsv-name sample_metadata.tsv
# job fails as previous job's results files creates error as dimensions update needs to be run again.
# error message:
The following VCF files or file versions in the project are not part of the project's VCF dimensions: 'result_of_job_29.vcf.gz' Please run 'divbase-cli dimensions update --project <project_name>' and then submit the query again.
# running dimensions update now will make will make the next query work again. My understanding is dimensions update is adding the results files to the class SkippedVCFDB(BaseDBModel). An alternative solution might be to modify _check_that_dimensions_is_up_to_date_with_VCF_files_in_bucket fn to just auto skip all files that start with the QUERY_RESULTS_FILE_PREFIX. Then dimensions update doesn't need to be run in between jobs. Not sure if there are any other files that are being included in skipped table other than results files?
| self.warnings.append(warning_msg) | ||
| self.query_result = self.df | ||
| self.query_message = "Invalid filter conditions - returning ALL records" | ||
| self.query_message = f"Invalid filter conditions ({self.filter_string}) - returning ALL records" |
There was a problem hiding this comment.
Question: Why return all records for invalid conditions? Would it not be better to reject the request and say it had invalid formatting?
I'm thinking a user half reading the output (especially if large) or using the output in a pipeline would not necessarily see this warning
There was a problem hiding this comment.
Oh, that is a remnant from the previous iteration that relied on soft warnings instead of errors. It was also never tested at scale with the 5000 sample file. I completely agree that it would be better like you suggested. Updated in eb84afc.
Now a query on an non-existent column returns an SidecarInvalidFilterError. E.g. divbase-cli query tsv "NonExistentColumn:value"
Based on code review suggestion.
For validator and template generator. As suggested in code review.
Not needed anymore due to mkdocs GH action. This file was commited to avoid a merge conflict, but turns out it was unnecessary.
E.g. divbase-cli query tsv "NonExistentColumn:value" if NonExistentColumn not in the TSV file.
This avoids having to run dimensions update between each job that produces a results VCF file. Additonally, results VCF files cannot be indexed by dimensions update due to a DivBase-added header, so even if a user renames their results files to not have the prefix, they will still be stopped from being used in VCF queries.
|
Awesome, thanks for all the feedback on this. It has really made this a much better implementation! Good that you noticed that behaviour with results files and the dimensions index. It was not intended to behave like that, but perhaps I never really tested submitting back-to-back jobs? I implemented your suggestions and added an e2e test for this. About the result file checks: Thanks for spotting this. These are exactly the things we need to identify right now! Will merge now 🚀 |
This PR is a major refactoring of the sidecar sample metadata TSV query logic. Focus was on adding functions like numerical operations, and improving UX.
Things covered in this PR:
divbase-cli dimensions show --unique-samplesnow gives number and names of samples (and scaffolds for --unique-samples`)Sidecar metadata TSV format and filtering syntax
To not repeat what I wrote in the user guide, I would suggest spinning up mkdocs and reading: http://localhost:8008/divbase/user-guides/sidecar-metadata/
Also updated the quick-start guide, but there is really no room there to discuss the metadata format and query syntax there.
Create pre-filled template
The first column in the TSV,
Sample_IDis the only column that we stricly enforce since it needs to match exactly with the sample IDs in the VCFs in the project. To help users get this column right, I added a CLI command that reads the dimensions index of the project and generates at TSV with this first column populated with the sample names.For instance, if
local-project-1has files as perlocal_dev_setup.pyanddivbase-cli dimensions update --project local-project-1has been run:will save a single-column TSV with all unique sample names from the dimensions file to
sample_metadata_<project_name>.tsv. Output filename can be controlled with--output.TSV file Validation
The TSV validator runs client-side and is intended to be run on local TSV files before they are uploaded to DivBase. It validates the content in the TSV; checks on the query filters are done elsewhere. The validator does make one request to the server to fetch a list of the samples from the project's dimensions index to be able to validate sample names. Therefore I sorted the CLI command under
divbase-cli dimensionsjust like the TSV template creator.We talked about having Pandas in the libs to be able to ensure that the same code can be used client-side and server-side for doing the same checks. To my surpise, Pandas was already in the libs package... I could have sworn that it was in the api package when I looked last week! Well, anyway, that made it an easy choice to refactor all shared code to a class in the libs package.
For
local-project-1after its dimensions index has been updated:Metadata queries
In addition to the shared TSV content validator that is also run by SidecarQueryManager when a metadata query begins processing, there are also checks for the filter processing. For instance they e.g. check if numerical filters can be applied or not. This part of the code does not raise errors, only warnings. The user will typically get metadata query results but with warnings.