+
+
+
+
+
+ chrom_1
+ start_1
+ end_1
+ chrom_2
+ start_2
+ end_2
+ region_chrom
+ region_start
+ region_end
+ region_id
+
+
+
+
+ 0
+ chr1
+ 100
+ 200
+ chr1
+ 1000
+ 2000
+ chr1
+ 100
+ 400
+ 0
+
+
+ 1
+ chr1
+ 2000
+ 3000
+ chr1
+ 200
+ 300
+ chr1
+ 100
+ 400
+ 0
+
+
+ 2
+ chr1
+ 3000
+ 4000
+ chr1
+ 300
+ 400
+ chr1
+ 100
+ 400
+ 0
+
+
+
+
+
+
+
+We can also restrict the positions to filter on, by passing different anchor parameters. For example, we can filter for contacts, where the first position overlaps with our target:
+
+
+```python
+query_steps = [
+ Overlap(target_region, anchor_mode=Anchor(mode="ANY", anchors=[1]))
+]
+Query(query_steps=query_steps)\
+ .build(contacts)\
+ .compute()\
+ .filter(regex=r"chrom|start|end|id")
+```
+
+
+
+
+
+
+
+
+
+
+ chrom_1
+ start_1
+ end_1
+ chrom_2
+ start_2
+ end_2
+ region_chrom
+ region_start
+ region_end
+ region_id
+
+
+
+
+ 0
+ chr1
+ 100
+ 200
+ chr1
+ 1000
+ 2000
+ chr1
+ 100
+ 400
+ 0
+
+
+
+
+
+
+
+This time, only the first contact overlaps.
+
+The same functionality is implemented also for the Pixels class
+
+## Selecting a subset of contacts at multiple genomic regions
+The Overlap class is also capable of selecting contacts at multiple genomic regions. Here, the behavior of `Overlap` deviates from a simple filter, because if a given contact overlaps with multiple regions, it will be returned multiple times.
+
+Specify target regions
+
+
+```python
+target_regions = pd.DataFrame({
+ "chrom": ['chr1', 'chr1'],
+ "start": [100, 150],
+ "end": [400, 200],
+})
+```
+
+
+```python
+query_steps = [
+ Overlap(target_regions, anchor_mode=Anchor(mode="ANY", anchors=[1]))
+]
+Query(query_steps=query_steps)\
+ .build(contacts)\
+ .compute()\
+ .filter(regex=r"chrom|start|end|id")
+```
+
+
+
+
+
+
+
+
+
+
+ chrom_1
+ start_1
+ end_1
+ chrom_2
+ start_2
+ end_2
+ region_chrom
+ region_start
+ region_end
+ region_id
+
+
+
+
+ 0
+ chr1
+ 100
+ 200
+ chr1
+ 1000
+ 2000
+ chr1
+ 100
+ 400
+ 0
+
+
+ 1
+ chr1
+ 100
+ 200
+ chr1
+ 1000
+ 2000
+ chr1
+ 150
+ 200
+ 1
+
+
+
+
+
+
+
+In this example, the contact overlapping both regions is duplicated.
+
+The same functionality is implemented also for the pixels class.
+
+## Calculating the distance to a target region and aggregating the result
+In this example, we calculate the distance of pixels to target regions and aggregate based on the distances. This is a very common use case in so-called pileup analyses, where we want to investigate the average behavior around regions of interest.
+
+
+```python
+from spoc.pixels import Pixels
+from spoc.query_engine import DistanceTransformation, DistanceMode
+import pandas as pd
+import numpy as np
+from itertools import product
+```
+
+First we define a set of target pixels
+
+
+```python
+def complete_synthetic_pixels():
+ """Pixels that span two regions densely"""
+ np.random.seed(42)
+ # genomic region_1
+ pixels_1 = [
+ {
+ "chrom": tup[0],
+ "start_1": tup[1],
+ "start_2": tup[2],
+ "start_3": tup[3],
+ "count": np.random.randint(0, 10),
+ }
+ for tup in product(
+ ["chr1"],
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ )
+ ]
+ # genomic region_2
+ pixels_2 = [
+ {
+ "chrom": tup[0],
+ "start_1": tup[1],
+ "start_2": tup[2],
+ "start_3": tup[3],
+ "count": np.random.randint(0, 10),
+ }
+ for tup in product(
+ ["chr2"],
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ )
+ ]
+ return pd.concat((pd.DataFrame(pixels_1), pd.DataFrame(pixels_2)))
+```
+
+
+```python
+pixels = Pixels(complete_synthetic_pixels(), number_fragments=3, binsize=50_000)
+```
+
+Then we define the target regions we are interested in.
+
+
+```python
+target_regions = pd.DataFrame(
+ {
+ "chrom": ["chr1", "chr2"],
+ "start": [900_000, 900_000],
+ "end": [1_100_000, 1_100_000],
+ }
+ )
+```
+
+We are then interested in selecting all contacts that are contained within these pixels and then calculate the distance to them. The selection step can be done with the `Overlap` class that we described above. The distance transformation can be done with the `DistanceTransformation` query step. This query step takes an instance of genomic data that contains regions (as defined by it's schema) and calculates the distance to all position columns. All distances are calculated with regards to the center of each assigned region. Since genomic positions are defined by a start and end,the `DistanceTransformation` query step has a `DistanceMode` parameter that defines whether we would like to calculate the distance with regard to the start of a genomic position, the end or it's center.
+
+
+```python
+query_steps = [
+ Overlap(target_regions, anchor_mode=Anchor(mode="ANY")),
+ DistanceTransformation(
+ distance_mode=DistanceMode.LEFT,
+ ),
+]
+```
+
+We can then execute this query plan using the Query class. This well add an distance column to the genomic dataset returned.
+
+
+```python
+Query(query_steps=query_steps)\
+ .build(pixels)\
+ .compute()\
+ .filter(regex=r"chrom|distance")
+```
+
+
+
+
+
+
+
+
+
+
+ chrom_1
+ chrom_2
+ chrom_3
+ region_chrom
+ distance_1
+ distance_2
+ distance_3
+
+
+
+
+ 0
+ chr1
+ chr1
+ chr1
+ chr1
+ -100000.0
+ -100000.0
+ -100000.0
+
+
+ 1
+ chr1
+ chr1
+ chr1
+ chr1
+ -100000.0
+ -100000.0
+ -50000.0
+
+
+ 2
+ chr1
+ chr1
+ chr1
+ chr1
+ -100000.0
+ -100000.0
+ 0.0
+
+
+ 3
+ chr1
+ chr1
+ chr1
+ chr1
+ -100000.0
+ -100000.0
+ 50000.0
+
+
+ 4
+ chr1
+ chr1
+ chr1
+ chr1
+ -100000.0
+ -100000.0
+ 100000.0
+
+
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+ ...
+
+
+ 245
+ chr2
+ chr2
+ chr2
+ chr2
+ 100000.0
+ 100000.0
+ -100000.0
+
+
+ 246
+ chr2
+ chr2
+ chr2
+ chr2
+ 100000.0
+ 100000.0
+ -50000.0
+
+
+ 247
+ chr2
+ chr2
+ chr2
+ chr2
+ 100000.0
+ 100000.0
+ 0.0
+
+
+ 248
+ chr2
+ chr2
+ chr2
+ chr2
+ 100000.0
+ 100000.0
+ 50000.0
+
+
+ 249
+ chr2
+ chr2
+ chr2
+ chr2
+ 100000.0
+ 100000.0
+ 100000.0
+
+
+
+
250 rows × 7 columns
+
+
+
+
+## Aggregating genomic data based on it's distance to a target region
+In this example, we extend the above use-case to aggregate the results based on the distance columns added. This is a common use-case to calculate aggregate statistics for different distance levels. To achieve this, we employ the same query plan as above and extend it using the `DistanceAggregation` query step.
+
+
+```python
+from spoc.query_engine import DistanceAggregation, AggregationFunction
+```
+
+The `DistanceAggregation` class requires the following parameters:
+- `value_columns`: Thie specifies the value to aggregate
+- `function`: The aggregation function to use. This is the enumerated type `AggregationFunction`
+- `densify_output`: Whether missing distance values should be filled with empty values (specific empty value depends on the aggregation function)
+
+Note that there are two different average functions available, `AVG` and `AVG_WITH_EMPTY`. `AVG` performs and average over all available columns, where as `AVG_WITH_EMPTY` counts missing distances per regions as 0.
+
+
+```python
+query_steps = [
+ Overlap(target_regions, anchor_mode=Anchor(mode="ALL")),
+ DistanceTransformation(),
+ DistanceAggregation(
+ value_column='count',
+ function=AggregationFunction.AVG,
+ ),
+]
+```
+
+
+```python
+Query(query_steps=query_steps)\
+ .build(pixels)\
+ .compute()
+```
+
+
+
+
+
+
+
+
+
+
+ distance_1
+ distance_2
+ distance_3
+ count
+
+
+
+
+ 0
+ -100000.0
+ -100000.0
+ -100000.0
+ 4.5
+
+
+ 1
+ -100000.0
+ -100000.0
+ -50000.0
+ 3.0
+
+
+ 2
+ -100000.0
+ -100000.0
+ 0.0
+ 5.5
+
+
+ 3
+ -100000.0
+ -100000.0
+ 50000.0
+ 5.0
+
+
+ 4
+ -100000.0
+ -100000.0
+ 100000.0
+ 6.0
+
+
+ ...
+ ...
+ ...
+ ...
+ ...
+
+
+ 120
+ 100000.0
+ 100000.0
+ -100000.0
+ 8.0
+
+
+ 121
+ 100000.0
+ 100000.0
+ -50000.0
+ 4.5
+
+
+ 122
+ 100000.0
+ 100000.0
+ 0.0
+ 4.5
+
+
+ 123
+ 100000.0
+ 100000.0
+ 50000.0
+ 4.5
+
+
+ 124
+ 100000.0
+ 100000.0
+ 100000.0
+ 0.0
+
+
+
+
125 rows × 4 columns
+
+
+
+
+In addition, we can also aggregate on a subset of distance positions, using the `position_list` parameter:
+
+
+```python
+query_steps = [
+ Overlap(target_regions, anchor_mode=Anchor(mode="ALL")),
+ DistanceTransformation(),
+ DistanceAggregation(
+ value_column='count',
+ function=AggregationFunction.AVG,
+ position_list=[1,2]
+ ),
+]
+```
+
+
+```python
+Query(query_steps=query_steps)\
+ .build(pixels)\
+ .compute()
+```
+
+
+
+
+
+
+
+
+
+
+ distance_1
+ distance_2
+ count
+
+
+
+
+ 0
+ -100000.0
+ -100000.0
+ 4.8
+
+
+ 1
+ -100000.0
+ -50000.0
+ 4.5
+
+
+ 2
+ -100000.0
+ 0.0
+ 5.3
+
+
+ 3
+ -100000.0
+ 50000.0
+ 4.7
+
+
+ 4
+ -100000.0
+ 100000.0
+ 5.3
+
+
+ 5
+ -50000.0
+ -100000.0
+ 4.8
+
+
+ 6
+ -50000.0
+ -50000.0
+ 4.4
+
+
+ 7
+ -50000.0
+ 0.0
+ 4.5
+
+
+ 8
+ -50000.0
+ 50000.0
+ 5.4
+
+
+ 9
+ -50000.0
+ 100000.0
+ 3.4
+
+
+ 10
+ 0.0
+ -100000.0
+ 2.0
+
+
+ 11
+ 0.0
+ -50000.0
+ 3.5
+
+
+ 12
+ 0.0
+ 0.0
+ 4.4
+
+
+ 13
+ 0.0
+ 50000.0
+ 5.4
+
+
+ 14
+ 0.0
+ 100000.0
+ 4.3
+
+
+ 15
+ 50000.0
+ -100000.0
+ 5.3
+
+
+ 16
+ 50000.0
+ -50000.0
+ 4.7
+
+
+ 17
+ 50000.0
+ 0.0
+ 4.0
+
+
+ 18
+ 50000.0
+ 50000.0
+ 4.2
+
+
+ 19
+ 50000.0
+ 100000.0
+ 6.1
+
+
+ 20
+ 100000.0
+ -100000.0
+ 5.4
+
+
+ 21
+ 100000.0
+ -50000.0
+ 2.8
+
+
+ 22
+ 100000.0
+ 0.0
+ 3.6
+
+
+ 23
+ 100000.0
+ 50000.0
+ 5.2
+
+
+ 24
+ 100000.0
+ 100000.0
+ 4.3
+
+
+
+
+
+
diff --git a/docs/snipping.md b/docs/snipping.md
deleted file mode 100644
index b80ae61..0000000
--- a/docs/snipping.md
+++ /dev/null
@@ -1,150 +0,0 @@
-# Snipping examples
-
-## Background
-
-Snipping is the act of extracting rectangular "snippets" from a dataset containing pore-c interactions in the form of pixels. For example, we might have a file containing two-way interactions (similar to conventional Hi-C) in the form of a matrix of genomic bins that encode the interaction frequency of these pixels. We can extract the neighborhood of TAD-boundaries from this file, specifically neighborhoods centered at TAD-boundaries that stretch 1MB upstream and downstream. The result of this extraction is a 3-dimensional matrix, where the first two dimensions encode the relative genomic offset and the third dimension encodes the particular examples, here TAD-boundaries.
-
-## Average representation of snippets
-
-Snipping is often the first step of an analysis pipeline, where the extraction of snippets is followed by reducing them along the example dimension to obtain an average representation of a particular genomic neighbourhood.
-
-## Higher-order contacts and snipping
-
-If we want to extract snippets from a higher-order contact file, things get a little more complicated than for two-way contacts. Specifically, we are often interested in obtaining a two-dimensional representation of a certain high-dimensional snippet to enable visualization. To do this, we need to define different ways of snipping from a high-dimensional file. For example, we might want to know which two-dimensional contacts are associated with a set of third coordinates, like TAD-boundaries. Or we might want to know which genomic positions are associated with a two-dimensional query. To account for this, spoc implements different "snipping strategies" that encapsulate the different ways we might want to query a high-dimensional contact file.
-
-
-## Snipper class
-The snipper class is spoc's central interface to access snipping functionality. It can be used with different snipping strategies to extract snippets. The idea is that a snipper can hold different snipping strategies that can be deployed to achieve the desired result.
-
-
-## Snipping strategies and snipping values
-
-### Snipping strategies
-
-Snipping strategies are specific ways of snipping higher-order contacts. For example, the `Triplet1DSnippingStrategy` implements snipping 2D pieces from triplets based on a set of 1d-coordinates. This can be used to ask questions like:
-
-- Do sister chromatids form internal loops when they contact each other?
-
-The idea is that a specific snipping strategy encapsulates all information of extracting snippets, such as the size of the snippets, potential offsets, values, etc. The constructor of a particular snipping strategy is specific to that strategy. Still, the snipping implementation has a unified strategy to allow the composition of different strategies in a snipper class.
-
-### Snipping values
-
-Usually, the values that are extracted from a target file can be either ICCF (iteratively corrected contact frequencies) or observed-over-expected values. The latter represents the ratio of the actually observed contacts and the expected contacts for randomly chosen genomic regions.
-
-
-```python
-from spoc.snipping import Snipper
-from spoc.snipping.snipping_strategies import Triplet1DSnippingStrategy,
- SnippingValues
-
-snip_center = Triplet1DSnippingStrategy(
- bin_size=100_000,
- half_window_size=2_000_000,
- snipping_value=SnippingValues.ICCF)
-snipper = Snipper([snip_center])
-```
-
-- Here the snipper is instantiated with a single snipping strategy that snips ICCF values from the center of a set of regions
-
-
-```python
-tad_boundaries = pd.read_csv("../playground/tad_boundaries.csv")
-```
-
-- TADs as target regions are loaded
-
-
-```python
-result = snipper.snip("../playground/test.parquet", tad_boundaries, threads=40)
-```
-
-- Snipper is used to snip ICCF values around TAD-boundaries with a 1Mbp half-window size
-
-
-```python
-f, ax = plt.subplots()
-sbn.heatmap(np.log2(result[0]))
-plt.show()
-```
-
-
-
-![png](snipping_files/snipping_13_0.png)
-
-
-
-## Combining snipping strategies
-Multiple snipping strategies can be used and dispatched together.
-
-### Snip ICCF and Obs/Exp together
-
-
-```python
-snip_center_obs_exp = Triplet1DSnippingStrategy(
- bin_size=100_000,
- half_window_size=2_000_000,
- snipping_value=SnippingValues.OBSEXP)
-snipper = Snipper([snip_center, snip_center_obs_exp])
-```
-
-
-```python
-result = snipper.snip("../playground/test.parquet", tad_boundaries, threads=40)
-```
-
-
-```python
-f, ax = plt.subplots(1, 2)
-sbn.heatmap(np.log2(result[0]), ax=ax[0], square=True)
-ax[0].set_title("ICCF")x
-sbn.heatmap(np.log2(result[1]),
- ax=ax[1],
- cmap="RdBu_r",
- vmin=-0.5,
- vmax=0.5,
- square=True)
-ax[1].set_title("Obs/Exp")
-f.set_size_inches(10, 6)
-plt.show()
-```
-
-
-
-![png](snipping_files/snipping_19_0.png)
-
-
-
-Generate triplet strategies with different offsets.
-
-
-```python
-offset_strategies = [
- Triplet1DSnippingStrategy(
- bin_size=100_000,
- half_window_size=2_000_000,
- snipping_value=SnippingValues.ICCF,
- relative_offset=offset)
- for offset in np.arange(-1_000_000, 1_000_001, 500_000)
-]
-snipper = Snipper(offset_strategies)
-```
-
-
-```python
-result = snipper.snip("../playground/test.parquet", tad_boundaries, threads=40)
-```
-
-
-```python
-f, ax = plt.subplots(1, len(result))
-for index, array in enumerate(result):
- sbn.heatmap(np.log2(result[index]), ax=ax[index], square=True)
-f.set_size_inches(20, 5)
-plt.show()
-```
-
-
-
-![png](snipping_files/snipping_24_0.png)
-
-
diff --git a/mkdocs.yml b/mkdocs.yml
index e862868..2faff7f 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -17,7 +17,9 @@ nav:
- Load example dataset: load_example_dataset.md
- Transformation pipeline: transformation_pipeline.md
- Data Structures: data_structures.md
- - Snipping: snipping.md
+ - Query Engine:
+ - Usage guide: query_engine_usage.md
+ - Class interface: query_engine_interface.md
- Contributing: contributing.md
- API:
- CLI: cli.md
@@ -25,6 +27,7 @@ nav:
- Contacts: contacts.md
- Fragments: fragments.md
- Pixels: pixels.md
+ - Query Engine: query_engine.md
- Dataframe models: dataframe_models.md
- File parameters models: file_parameters_models.md
plugins:
@@ -35,6 +38,11 @@ markdown_extensions:
anchor_linenums: true
line_spans: __span
pygments_lang_class: true
+ - pymdownx.superfences:
+ custom_fences:
+ - name: mermaid
+ class: mermaid
+ format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
diff --git a/notebooks/data_structures.ipynb b/notebooks/data_structures.ipynb
index 05b768e..7c2ddcc 100644
--- a/notebooks/data_structures.ipynb
+++ b/notebooks/data_structures.ipynb
@@ -225,7 +225,8 @@
"metadata": {},
"outputs": [],
"source": [
- "fragments = FileManager(use_dask=True).load_fragments(\"../tests/test_files/good_porec.parquet\")"
+ "from spoc.io import DataMode\n",
+ "fragments = FileManager(DataMode.DASK).load_fragments(\"../tests/test_files/good_porec.parquet\")"
]
},
{
@@ -351,7 +352,7 @@
"metadata": {},
"outputs": [],
"source": [
- "fragments = FileManager(use_dask=True).load_fragments(\"../tests/test_files/bad_porec.parquet\")"
+ "fragments = FileManager(DataMode.DASK).load_fragments(\"../tests/test_files/bad_porec.parquet\")"
]
},
{
diff --git a/notebooks/query_engine_usage.ipynb b/notebooks/query_engine_usage.ipynb
new file mode 100644
index 0000000..e64ff4e
--- /dev/null
+++ b/notebooks/query_engine_usage.ipynb
@@ -0,0 +1,1299 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# Query engine\n",
+ "This technical document describes the spoc query engine, a set of classes that implements spoc's interface for querying multi-dimensional genomic data."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Principles\n",
+ "\n",
+ "### Composable pieces\n",
+ "Spoc's query engine consists of composable pieces that can be combined to produce an expressive query language. These pieces represent basic operations on genomic data that are easily implemented and understood on their own. This allows a great degree of flexibility, while also allowing predefined recipes that less experienced users can get started with.\n",
+ "\n",
+ "### Lazy evaluation\n",
+ "The spoc query engine is designed with lazy evaluation as a guiding principle. This means that data queries are only executed when they are needed to minimize loading data into memory and computational overhead. To enable this, spoc queries have a construction phase, which specifies the operations to be executed and an execution phase, that actually executes the query."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Query plans and query steps\n",
+ "\n",
+ "The most important ingredient in this query language is a class that implements the `QueryStep` protocol. This protocol serves two purposes:\n",
+ "\n",
+ "- It exposes a way to validate the data schema during query building\n",
+ "- It implements adding itself to a query\n",
+ "\n",
+ "This way, query steps can be combined into a query plan that specifies the analysis to be executed. In generaly, spoc supports the following types of query steps:\n",
+ "\n",
+ "- **Overlap**: Overlaps genomic data with a set of genomic intervals\n",
+ "- **Aggregation**: Aggregates the data passed into the query using an aggregation function\n",
+ "- **Transform**: Adds columns to query based on a fixed or custom computation\n",
+ "\n",
+ "Specific examples of query steps are:\n",
+ "\n",
+ "- **DistanceTransformation**: Example of a transfomration. Adds distance of genomic positions to regions added by Overlap\n",
+ "- **DistanceAggregation**: Exaple of an aggregation. Aggregates the distances to genomic regions using an aggregation function.\n",
+ "\n",
+ "### Input and output of query steps\n",
+ "\n",
+ "A query step takes as input a class that implements the `GenomicData` protocol. This protocol allows retrievel of the data schema (a thin wrapper over a pandera dataframe schema) as well as the data itself. The output of a query step is again a class that ipmlements the `GenomicData` protocol to allow composition. Specific examples of possible inputs are:\n",
+ "\n",
+ "- **Pixels**: Represents input pixels\n",
+ "- **Contacts**: Represents input contacts\n",
+ "- **QueryPlan**: The result of a query step\n",
+ "\n",
+ "### Composition of query steps\n",
+ "\n",
+ "To allow specifying complex queries, query steps need to be combined. This is done using the `Query` class. It takes a query plan (a list of `QueryStep` instances) as input, exposes the `build` method, which takes input data, validates all query steps and adds them to the resulting `QueryPlan` instance that is returned.\n",
+ "\n",
+ "### Manifestation of results\n",
+ "\n",
+ "So far, we have only talked about specifying the query to be executed, but not how to actually execute it. A `QueryPlan` has a `compute()` method that returns the manifested dataframe as a `pd.DataFrame` instance. This is the step that actually executes the specified query."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Examples"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Selecting a subset of contacts at a single genomic position\n",
+ "In this example, we want to select a subset of genomic contacts at a single location. For this, we first load the required input data:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from spoc.query_engine import Overlap, Anchor, Query\n",
+ "from spoc.contacts import Contacts\n",
+ "import pandas as pd\n",
+ "\n",
+ "contacts = Contacts.from_uri(\"../tests/test_files/contacts_unlabelled_2d_v2.parquet::2\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Then we specify a target region"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "target_region = pd.DataFrame({\n",
+ " \"chrom\": ['chr1'],\n",
+ " \"start\": [100],\n",
+ " \"end\": [400],\n",
+ "})"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First, we want to select all contacts where any of the fragments constituting the contact overlaps the target region. To perform this action, we use the Overlap class and pass the target region as well as an instance of the `Anchor` class. The `Anchor` dataclass allows us to specify how we want to filter contacts for region overlap. It has two attributes `mode` and `anchors`. `Anchors` indicates the positions we want to filter on (default is all positions) and `mode` specifies whether we require all positions to overlap or any position to overlap. So for example, if we want all of our two-way contacts for which any of the positions overlap, we would use `Anchor(mode='ANY', anchors=[1,2])`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "query_steps = [\n",
+ " Overlap(target_region, anchor_mode=Anchor(mode=\"ANY\", anchors=[1,2]))\n",
+ "]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A query plan is a list of qury steps that can be used in the basic query class"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "query = Query(query_steps=query_steps)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `.build` method executes the query plan and retuns a `QueryPlan` object"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "
"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "result = query.build(contacts)\n",
+ "result"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `.load_result` method of the `QueryResult` object can be executed using `.load_result`, which returns a `pd.DataFrame`. The resulting dataframe has additional columns that represent the regions, with which the input contacts overlapped."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " chrom_1 \n",
+ " start_1 \n",
+ " end_1 \n",
+ " chrom_2 \n",
+ " start_2 \n",
+ " end_2 \n",
+ " region_chrom \n",
+ " region_start \n",
+ " region_end \n",
+ " region_id \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 200 \n",
+ " chr1 \n",
+ " 1000 \n",
+ " 2000 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 400 \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " chr1 \n",
+ " 2000 \n",
+ " 3000 \n",
+ " chr1 \n",
+ " 200 \n",
+ " 300 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 400 \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " chr1 \n",
+ " 3000 \n",
+ " 4000 \n",
+ " chr1 \n",
+ " 300 \n",
+ " 400 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 400 \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " chrom_1 start_1 end_1 chrom_2 start_2 end_2 region_chrom region_start \\\n",
+ "0 chr1 100 200 chr1 1000 2000 chr1 100 \n",
+ "1 chr1 2000 3000 chr1 200 300 chr1 100 \n",
+ "2 chr1 3000 4000 chr1 300 400 chr1 100 \n",
+ "\n",
+ " region_end region_id \n",
+ "0 400 0 \n",
+ "1 400 0 \n",
+ "2 400 0 "
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = result.compute()\n",
+ "print(type(df))\n",
+ "df.filter(regex=r\"chrom|start|end|id\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can also restrict the positions to filter on, by passing different anchor parameters. For example, we can filter for contacts, where the first position overlaps with our target:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " chrom_1 \n",
+ " start_1 \n",
+ " end_1 \n",
+ " chrom_2 \n",
+ " start_2 \n",
+ " end_2 \n",
+ " region_chrom \n",
+ " region_start \n",
+ " region_end \n",
+ " region_id \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 200 \n",
+ " chr1 \n",
+ " 1000 \n",
+ " 2000 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 400 \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " chrom_1 start_1 end_1 chrom_2 start_2 end_2 region_chrom region_start \\\n",
+ "0 chr1 100 200 chr1 1000 2000 chr1 100 \n",
+ "\n",
+ " region_end region_id \n",
+ "0 400 0 "
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "query_steps = [\n",
+ " Overlap(target_region, anchor_mode=Anchor(mode=\"ANY\", anchors=[1]))\n",
+ "]\n",
+ "Query(query_steps=query_steps)\\\n",
+ " .build(contacts)\\\n",
+ " .compute()\\\n",
+ " .filter(regex=r\"chrom|start|end|id\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "This time, only the first contact overlaps."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The same functionality is implemented also for the Pixels class"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Selecting a subset of contacts at multiple genomic regions\n",
+ "The Overlap class is also capable of selecting contacts at multiple genomic regions. Here, the behavior of `Overlap` deviates from a simple filter, because if a given contact overlaps with multiple regions, it will be returned multiple times."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Specify target regions"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "target_regions = pd.DataFrame({\n",
+ " \"chrom\": ['chr1', 'chr1'],\n",
+ " \"start\": [100, 150],\n",
+ " \"end\": [400, 200],\n",
+ "})"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " chrom_1 \n",
+ " start_1 \n",
+ " end_1 \n",
+ " chrom_2 \n",
+ " start_2 \n",
+ " end_2 \n",
+ " region_chrom \n",
+ " region_start \n",
+ " region_end \n",
+ " region_id \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 200 \n",
+ " chr1 \n",
+ " 1000 \n",
+ " 2000 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 400 \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " chr1 \n",
+ " 100 \n",
+ " 200 \n",
+ " chr1 \n",
+ " 1000 \n",
+ " 2000 \n",
+ " chr1 \n",
+ " 150 \n",
+ " 200 \n",
+ " 1 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " chrom_1 start_1 end_1 chrom_2 start_2 end_2 region_chrom region_start \\\n",
+ "0 chr1 100 200 chr1 1000 2000 chr1 100 \n",
+ "1 chr1 100 200 chr1 1000 2000 chr1 150 \n",
+ "\n",
+ " region_end region_id \n",
+ "0 400 0 \n",
+ "1 200 1 "
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "query_steps = [\n",
+ " Overlap(target_regions, anchor_mode=Anchor(mode=\"ANY\", anchors=[1]))\n",
+ "]\n",
+ "Query(query_steps=query_steps)\\\n",
+ " .build(contacts)\\\n",
+ " .compute()\\\n",
+ " .filter(regex=r\"chrom|start|end|id\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In this example, the contact overlapping both regions is duplicated."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The same functionality is implemented also for the pixels class."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Calculating the distance to a target region and aggregating the result\n",
+ "In this example, we calculate the distance of pixels to target regions and aggregate based on the distances. This is a very common use case in so-called pileup analyses, where we want to investigate the average behavior around regions of interest."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from spoc.pixels import Pixels\n",
+ "from spoc.query_engine import DistanceTransformation, DistanceMode\n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "from itertools import product"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "First we define a set of target pixels"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def complete_synthetic_pixels():\n",
+ " \"\"\"Pixels that span two regions densely\"\"\"\n",
+ " np.random.seed(42)\n",
+ " # genomic region_1\n",
+ " pixels_1 = [\n",
+ " {\n",
+ " \"chrom\": tup[0],\n",
+ " \"start_1\": tup[1],\n",
+ " \"start_2\": tup[2],\n",
+ " \"start_3\": tup[3],\n",
+ " \"count\": np.random.randint(0, 10),\n",
+ " }\n",
+ " for tup in product(\n",
+ " [\"chr1\"],\n",
+ " np.arange(900_000, 1_150_000, 50_000),\n",
+ " np.arange(900_000, 1_150_000, 50_000),\n",
+ " np.arange(900_000, 1_150_000, 50_000),\n",
+ " )\n",
+ " ]\n",
+ " # genomic region_2\n",
+ " pixels_2 = [\n",
+ " {\n",
+ " \"chrom\": tup[0],\n",
+ " \"start_1\": tup[1],\n",
+ " \"start_2\": tup[2],\n",
+ " \"start_3\": tup[3],\n",
+ " \"count\": np.random.randint(0, 10),\n",
+ " }\n",
+ " for tup in product(\n",
+ " [\"chr2\"],\n",
+ " np.arange(900_000, 1_150_000, 50_000),\n",
+ " np.arange(900_000, 1_150_000, 50_000),\n",
+ " np.arange(900_000, 1_150_000, 50_000),\n",
+ " )\n",
+ " ]\n",
+ " return pd.concat((pd.DataFrame(pixels_1), pd.DataFrame(pixels_2)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "pixels = Pixels(complete_synthetic_pixels(), number_fragments=3, binsize=50_000)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Then we define the target regions we are interested in."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "target_regions = pd.DataFrame(\n",
+ " {\n",
+ " \"chrom\": [\"chr1\", \"chr2\"],\n",
+ " \"start\": [900_000, 900_000],\n",
+ " \"end\": [1_100_000, 1_100_000],\n",
+ " }\n",
+ " )"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We are then interested in selecting all contacts that are contained within these pixels and then calculate the distance to them. The selection step can be done with the `Overlap` class that we described above. The distance transformation can be done with the `DistanceTransformation` query step. This query step takes an instance of genomic data that contains regions (as defined by it's schema) and calculates the distance to all position columns. All distances are calculated with regards to the center of each assigned region. Since genomic positions are defined by a start and end,the `DistanceTransformation` query step has a `DistanceMode` parameter that defines whether we would like to calculate the distance with regard to the start of a genomic position, the end or it's center."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "query_steps = [\n",
+ " Overlap(target_regions, anchor_mode=Anchor(mode=\"ANY\")),\n",
+ " DistanceTransformation(\n",
+ " distance_mode=DistanceMode.LEFT,\n",
+ " ),\n",
+ "]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We can then execute this query plan using the Query class. This well add an distance column to the genomic dataset returned."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " chrom_1 \n",
+ " chrom_2 \n",
+ " chrom_3 \n",
+ " region_chrom \n",
+ " distance_1 \n",
+ " distance_2 \n",
+ " distance_3 \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " -50000.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 0.0 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 50000.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " chr1 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 100000.0 \n",
+ " \n",
+ " \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " \n",
+ " \n",
+ " 245 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " -100000.0 \n",
+ " \n",
+ " \n",
+ " 246 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " -50000.0 \n",
+ " \n",
+ " \n",
+ " 247 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 0.0 \n",
+ " \n",
+ " \n",
+ " 248 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 50000.0 \n",
+ " \n",
+ " \n",
+ " 249 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " chr2 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
250 rows × 7 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " chrom_1 chrom_2 chrom_3 region_chrom distance_1 distance_2 distance_3\n",
+ "0 chr1 chr1 chr1 chr1 -100000.0 -100000.0 -100000.0\n",
+ "1 chr1 chr1 chr1 chr1 -100000.0 -100000.0 -50000.0\n",
+ "2 chr1 chr1 chr1 chr1 -100000.0 -100000.0 0.0\n",
+ "3 chr1 chr1 chr1 chr1 -100000.0 -100000.0 50000.0\n",
+ "4 chr1 chr1 chr1 chr1 -100000.0 -100000.0 100000.0\n",
+ ".. ... ... ... ... ... ... ...\n",
+ "245 chr2 chr2 chr2 chr2 100000.0 100000.0 -100000.0\n",
+ "246 chr2 chr2 chr2 chr2 100000.0 100000.0 -50000.0\n",
+ "247 chr2 chr2 chr2 chr2 100000.0 100000.0 0.0\n",
+ "248 chr2 chr2 chr2 chr2 100000.0 100000.0 50000.0\n",
+ "249 chr2 chr2 chr2 chr2 100000.0 100000.0 100000.0\n",
+ "\n",
+ "[250 rows x 7 columns]"
+ ]
+ },
+ "execution_count": 15,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Query(query_steps=query_steps)\\\n",
+ " .build(pixels)\\\n",
+ " .compute()\\\n",
+ " .filter(regex=r\"chrom|distance\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Aggregating genomic data based on it's distance to a target region\n",
+ "In this example, we extend the above use-case to aggregate the results based on the distance columns added. This is a common use-case to calculate aggregate statistics for different distance levels. To achieve this, we employ the same query plan as above and extend it using the `DistanceAggregation` query step."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from spoc.query_engine import DistanceAggregation, AggregationFunction"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "The `DistanceAggregation` class requires the following parameters:\n",
+ "- `value_columns`: Thie specifies the value to aggregate\n",
+ "- `function`: The aggregation function to use. This is the enumerated type `AggregationFunction`\n",
+ "- `densify_output`: Whether missing distance values should be filled with empty values (specific empty value depends on the aggregation function)\n",
+ "\n",
+ "Note that there are two different average functions available, `AVG` and `AVG_WITH_EMPTY`. `AVG` performs and average over all available columns, where as `AVG_WITH_EMPTY` counts missing distances per regions as 0."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "query_steps = [\n",
+ " Overlap(target_regions, anchor_mode=Anchor(mode=\"ALL\")),\n",
+ " DistanceTransformation(),\n",
+ " DistanceAggregation(\n",
+ " value_column='count',\n",
+ " function=AggregationFunction.AVG,\n",
+ " ),\n",
+ "]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " distance_1 \n",
+ " distance_2 \n",
+ " distance_3 \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 4.5 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " -50000.0 \n",
+ " 3.0 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 0.0 \n",
+ " 5.5 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 50000.0 \n",
+ " 5.0 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 100000.0 \n",
+ " 6.0 \n",
+ " \n",
+ " \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " ... \n",
+ " \n",
+ " \n",
+ " 120 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " -100000.0 \n",
+ " 8.0 \n",
+ " \n",
+ " \n",
+ " 121 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " -50000.0 \n",
+ " 4.5 \n",
+ " \n",
+ " \n",
+ " 122 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 0.0 \n",
+ " 4.5 \n",
+ " \n",
+ " \n",
+ " 123 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 50000.0 \n",
+ " 4.5 \n",
+ " \n",
+ " \n",
+ " 124 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 0.0 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
125 rows × 4 columns
\n",
+ "
"
+ ],
+ "text/plain": [
+ " distance_1 distance_2 distance_3 count\n",
+ "0 -100000.0 -100000.0 -100000.0 4.5\n",
+ "1 -100000.0 -100000.0 -50000.0 3.0\n",
+ "2 -100000.0 -100000.0 0.0 5.5\n",
+ "3 -100000.0 -100000.0 50000.0 5.0\n",
+ "4 -100000.0 -100000.0 100000.0 6.0\n",
+ ".. ... ... ... ...\n",
+ "120 100000.0 100000.0 -100000.0 8.0\n",
+ "121 100000.0 100000.0 -50000.0 4.5\n",
+ "122 100000.0 100000.0 0.0 4.5\n",
+ "123 100000.0 100000.0 50000.0 4.5\n",
+ "124 100000.0 100000.0 100000.0 0.0\n",
+ "\n",
+ "[125 rows x 4 columns]"
+ ]
+ },
+ "execution_count": 18,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Query(query_steps=query_steps)\\\n",
+ " .build(pixels)\\\n",
+ " .compute()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "In addition, we can also aggregate on a subset of distance positions, using the `position_list` parameter:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "query_steps = [\n",
+ " Overlap(target_regions, anchor_mode=Anchor(mode=\"ALL\")),\n",
+ " DistanceTransformation(),\n",
+ " DistanceAggregation(\n",
+ " value_column='count',\n",
+ " function=AggregationFunction.AVG,\n",
+ " position_list=[1,2]\n",
+ " ),\n",
+ "]"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " distance_1 \n",
+ " distance_2 \n",
+ " count \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " -100000.0 \n",
+ " -100000.0 \n",
+ " 4.8 \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " -100000.0 \n",
+ " -50000.0 \n",
+ " 4.5 \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " -100000.0 \n",
+ " 0.0 \n",
+ " 5.3 \n",
+ " \n",
+ " \n",
+ " 3 \n",
+ " -100000.0 \n",
+ " 50000.0 \n",
+ " 4.7 \n",
+ " \n",
+ " \n",
+ " 4 \n",
+ " -100000.0 \n",
+ " 100000.0 \n",
+ " 5.3 \n",
+ " \n",
+ " \n",
+ " 5 \n",
+ " -50000.0 \n",
+ " -100000.0 \n",
+ " 4.8 \n",
+ " \n",
+ " \n",
+ " 6 \n",
+ " -50000.0 \n",
+ " -50000.0 \n",
+ " 4.4 \n",
+ " \n",
+ " \n",
+ " 7 \n",
+ " -50000.0 \n",
+ " 0.0 \n",
+ " 4.5 \n",
+ " \n",
+ " \n",
+ " 8 \n",
+ " -50000.0 \n",
+ " 50000.0 \n",
+ " 5.4 \n",
+ " \n",
+ " \n",
+ " 9 \n",
+ " -50000.0 \n",
+ " 100000.0 \n",
+ " 3.4 \n",
+ " \n",
+ " \n",
+ " 10 \n",
+ " 0.0 \n",
+ " -100000.0 \n",
+ " 2.0 \n",
+ " \n",
+ " \n",
+ " 11 \n",
+ " 0.0 \n",
+ " -50000.0 \n",
+ " 3.5 \n",
+ " \n",
+ " \n",
+ " 12 \n",
+ " 0.0 \n",
+ " 0.0 \n",
+ " 4.4 \n",
+ " \n",
+ " \n",
+ " 13 \n",
+ " 0.0 \n",
+ " 50000.0 \n",
+ " 5.4 \n",
+ " \n",
+ " \n",
+ " 14 \n",
+ " 0.0 \n",
+ " 100000.0 \n",
+ " 4.3 \n",
+ " \n",
+ " \n",
+ " 15 \n",
+ " 50000.0 \n",
+ " -100000.0 \n",
+ " 5.3 \n",
+ " \n",
+ " \n",
+ " 16 \n",
+ " 50000.0 \n",
+ " -50000.0 \n",
+ " 4.7 \n",
+ " \n",
+ " \n",
+ " 17 \n",
+ " 50000.0 \n",
+ " 0.0 \n",
+ " 4.0 \n",
+ " \n",
+ " \n",
+ " 18 \n",
+ " 50000.0 \n",
+ " 50000.0 \n",
+ " 4.2 \n",
+ " \n",
+ " \n",
+ " 19 \n",
+ " 50000.0 \n",
+ " 100000.0 \n",
+ " 6.1 \n",
+ " \n",
+ " \n",
+ " 20 \n",
+ " 100000.0 \n",
+ " -100000.0 \n",
+ " 5.4 \n",
+ " \n",
+ " \n",
+ " 21 \n",
+ " 100000.0 \n",
+ " -50000.0 \n",
+ " 2.8 \n",
+ " \n",
+ " \n",
+ " 22 \n",
+ " 100000.0 \n",
+ " 0.0 \n",
+ " 3.6 \n",
+ " \n",
+ " \n",
+ " 23 \n",
+ " 100000.0 \n",
+ " 50000.0 \n",
+ " 5.2 \n",
+ " \n",
+ " \n",
+ " 24 \n",
+ " 100000.0 \n",
+ " 100000.0 \n",
+ " 4.3 \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " distance_1 distance_2 count\n",
+ "0 -100000.0 -100000.0 4.8\n",
+ "1 -100000.0 -50000.0 4.5\n",
+ "2 -100000.0 0.0 5.3\n",
+ "3 -100000.0 50000.0 4.7\n",
+ "4 -100000.0 100000.0 5.3\n",
+ "5 -50000.0 -100000.0 4.8\n",
+ "6 -50000.0 -50000.0 4.4\n",
+ "7 -50000.0 0.0 4.5\n",
+ "8 -50000.0 50000.0 5.4\n",
+ "9 -50000.0 100000.0 3.4\n",
+ "10 0.0 -100000.0 2.0\n",
+ "11 0.0 -50000.0 3.5\n",
+ "12 0.0 0.0 4.4\n",
+ "13 0.0 50000.0 5.4\n",
+ "14 0.0 100000.0 4.3\n",
+ "15 50000.0 -100000.0 5.3\n",
+ "16 50000.0 -50000.0 4.7\n",
+ "17 50000.0 0.0 4.0\n",
+ "18 50000.0 50000.0 4.2\n",
+ "19 50000.0 100000.0 6.1\n",
+ "20 100000.0 -100000.0 5.4\n",
+ "21 100000.0 -50000.0 2.8\n",
+ "22 100000.0 0.0 3.6\n",
+ "23 100000.0 50000.0 5.2\n",
+ "24 100000.0 100000.0 4.3"
+ ]
+ },
+ "execution_count": 20,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "Query(query_steps=query_steps)\\\n",
+ " .build(pixels)\\\n",
+ " .compute()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "spoc-dev",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.0"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/notebooks/snipping.ipynb b/notebooks/snipping.ipynb
deleted file mode 100644
index 7055a14..0000000
--- a/notebooks/snipping.ipynb
+++ /dev/null
@@ -1,322 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "# Snipping examples\n",
- "\n",
- "## Background\n",
- "\n",
- "Snipping is the act of extraction rectangular \"snippets\" from a dataset containing pore-c interactions in the form of pixels. For example, we might have a file containing two-way interactions (similar to conventional Hi-C) in the form of a matrix of genomic bins, that encode the interaction frequency of these pixels. We might want to extract the neighborhood of TAD-boundaries form this file, specifically, neighborhoods centered at TAD-boundaries that stretch 1MB upstream and downstream. The results of this extraction is a 3-dimensional matrix, where the first two dimensions encode the relative genomic offset and the third dimension encodes the particular examples, here TAD-boundaries.\n",
- "\n",
- "## Average representation of snippets\n",
- "\n",
- "Snipping is often the first step of an anlysis pipeline, where the extraction of snippets is followed by reducing them along the example dimension to obtain an average representation of a particular genomic neighborhood.\n",
- "\n",
- "## Higher-order contacts and snipping\n",
- "\n",
- "If we want to extract snippets from a higher-order contact file, things get a little more complicated than for two-way contacts. Specifically, we are often interested in obtaining a two-dimensional representation of a certain high-dimensional snippet to enable visualization. To do this, we need to define different ways of snipping from a high-dimensional file. For example, we might want to know, which two-dimensional contacts are associated with a set of third coordinates, like TAD-boundaries. Or we might want to know which genomic positions are associated with a two-dimensional query. To account for this, spoc implements different \"snipping strategies\" that encapsulate the different ways we might want to query a high-dimensional contact file."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 21,
- "metadata": {},
- "outputs": [],
- "source": [
- "import pandas as pd\n",
- "import matplotlib.pyplot as plt\n",
- "import numpy as np\n",
- "import seaborn as sbn"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Snipper class\n",
- "The snipper class is spoc's central interface to access snipping functionality. It can be used with different snipping strategies to extract snippets. The idea is that a snipper can hold different snipping strategies that can be deployed to achieve the desired result."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "metadata": {},
- "outputs": [],
- "source": [
- "from spoc.snipping import Snipper"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Snipping strategies and snipping vlaues\n",
- "\n",
- "### Snipping strategies\n",
- "\n",
- "Snipping strategies are specific ways of snipping higher order contacts. For example, the `Triplet1DSnippingStrategy` implements snipping 2D pieces from triplets based on a set of 1d-coordinates. This can be used to ask questions like:\n",
- "\n",
- "- Do sister chromatids form internal loops, when they contact each other?\n",
- "\n",
- "The idea is that a specific snipping strategy encapsulates all information of extracting snippets such as the size of the snippets, potential offsets, values etc. The constructor of a specific snipping strategy is specific to that strategy, but the snipping implementation has a unified strategy to allow composing different strategies in a snipper class.\n",
- "\n",
- "### Snipping values\n",
- "\n",
- "Usually, the values that are extracted from a target file can be either ICCF (iteratively corrected contact frequencies), or observed-over-expected values. The latter represents the ratio of the actually observed contacts and the expected contacts for randomly chosen genomic regions."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 5,
- "metadata": {},
- "outputs": [],
- "source": [
- "from spoc.snipping.snipping_strategies import Triplet1DSnippingStrategy, SnippingValues"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 27,
- "metadata": {},
- "outputs": [],
- "source": [
- "snip_strat_center = Triplet1DSnippingStrategy(bin_size=100_000,\n",
- " half_window_size=2_000_000,\n",
- " snipping_value=SnippingValues.ICCF)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 28,
- "metadata": {},
- "outputs": [],
- "source": [
- "snipper = Snipper([snip_strat_center])"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "- Here the snipper is instantiated with a single snipping strategy, that snips ICCF values from the center of a set of regions"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 29,
- "metadata": {},
- "outputs": [],
- "source": [
- "tad_boundaries = pd.read_csv(\"../playground/tad_boundaries.csv\")"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "- TADs as target regions are loaded"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 30,
- "metadata": {},
- "outputs": [],
- "source": [
- "result = snipper.snip(\"../playground/test.parquet\", tad_boundaries, threads=40)"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "- Snipper is used to snip ICCF values around TAD-boundaries with a 1Mbp half-window size"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 31,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "f, ax = plt.subplots()\n",
- "sbn.heatmap(np.log2(result[0]))\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "## Combining snipping strategies\n",
- "Multiple snipping strategies can be used and dispatched together."
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "### Snip ICCF and Obs/Exp together"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 38,
- "metadata": {},
- "outputs": [],
- "source": [
- "snip_strat_center_obs_exp = Triplet1DSnippingStrategy(bin_size=100_000,\n",
- " half_window_size=2_000_000,\n",
- " snipping_value=SnippingValues.OBSEXP) "
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 39,
- "metadata": {},
- "outputs": [],
- "source": [
- "snipper = Snipper([snip_strat_center, snip_strat_center_obs_exp])"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 40,
- "metadata": {},
- "outputs": [],
- "source": [
- "result = snipper.snip(\"../playground/test.parquet\", tad_boundaries, threads=40)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 45,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAA28AAAHiCAYAAAB/fvTTAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy89olMNAAAACXBIWXMAAA9hAAAPYQGoP6dpAADzbUlEQVR4nOzdeVxU9f4/8NcsMOygIIu7RSqkaUkp5lKKoqJm0qKZmZmVSqXe6y1bzPTeq5m5ZKYtplaU3brl16VM0khTXMLIHVskuiqiIiDbzDAzvz/8OTVx3p9AHGR5PR+P83joeZ9z5nMOM/OZz1neb53D4XCAiIiIiIiIajX91W4AERERERER/TUO3oiIiIiIiOoADt6IiIiIiIjqAA7eiIiIiIiI6gAO3oiIiIiIiOoADt6IiIiIiIjqAA7eiIiIiIiI6gAO3oiIiIiIiOoADt6IiIiIiIjqAOPVbgAREdUc69lf3LZtj5Br3LZtIiKqnx7TtXbbtpc7sty27auFV96IiIiIiIjqAF55IyJqSOy2q90CIiIiJ4PuaregbuGVNyIiIiIiojqAV96IiBoSh/1qt4CIiMjJoOOlt6rg4I2IqCGxc/BGRES1B2+brBreNklERERERFQH8MobEVED4uBtk0REVIvwtsmq4ZU3IiIiIiKiOoBX3oiIGhI+80ZERLUIn3mrGl55IyIiIiIiqgN45Y2IqCHhM29ERFSL8Jm3quGVNyIiIiIiojqAV96IiBoSu+1qt4CIiMiJz7xVDQdvREQNCW+bJCKiWoS3TVYNb5skIiIiIiKqA3jljYioIWGpACIiqkV4JalqeLyIiIiIiIjqAF55IyJqQBx85o2IiGoRPvNWNbzyRkREREREVAfwyhsRUUPCZ96IiKgWYamAquGVNyIiIiIiojqAV96IiBoSPvNGRES1CJ95qxoO3oiIGhK77Wq3gIiIyIm3TVYNb5skIiIiIiKqA3jljYioIeFtk0REVIvwtsmq4ZU3IiIiIiKiOoBX3oiIGhKWCiAiolqEz7xVDa+8ERERERER1QG88kZE1JDwmTciIqpF+Mxb1fDKGxERERERUR3AK29ERA0Jn3kjIqJahM+8VQ0Hb0REDYjDwSLdRERUe3DwVjW8bZKIiIiIiKgO4JU3IqKGhAlLiIioFmHCkqrhlTciIiIiIqI6gFfeiIgaEiYsISKiWoTPvFUNr7wRERERERHVAbzyRkTUkPCZNyIiqkX4zFvV8MobERERERFRHcArb0REDYmddd6IiKj24DNvVcMrb0RERERERHUAB29ERA2Jw+6+iYiIqIoMOp3bpsuxdOlStG7dGl5eXujatSv27NlTqfXWrFkDnU6HYcOGXdbrVhYHb0REDYnd7r6JiIioigw6901V9dFHH2Hq1Kl44YUXsG/fPnTq1Anx8fHIzc1VrpeVlYW///3v6Nmz52Uehcrj4I2IiIiIiBq8BQsWYPz48Rg7diyio6OxfPly+Pj44J133hHXsdlsGDVqFF588UVcc801bm8jB29ERA0Jb5skIqJaxJ23TZrNZhQWFrpMZrNZsx0WiwXp6emIi4tzztPr9YiLi0NaWprY/lmzZiE0NBTjxo274sdGCwdvRERERERU78yZMweBgYEu05w5czSXPXv2LGw2G8LCwlzmh4WFIScnR3Odb7/9FitWrMBbb711xdsuYakAIqKGhM+mERFRLaJ3Y5Hu6dOnY+rUqS7zTCbTFdn2hQsXMHr0aLz11lsICQm5ItusDA7eiIiIiIio3jGZTJUerIWEhMBgMOD06dMu80+fPo3w8PAKy//888/IysrCkCFDnPPs//8EqdFoRGZmJq699tpqtF4bB29ERA0Jr7wREVEtoqslVbo9PT3RpUsXbNmyxZnu3263Y8uWLUhKSqqwfPv27XHgwAGXec899xwuXLiAxYsXo0WLFm5pJwdvRERERETU4E2dOhVjxoxBTEwMbrnlFixatAjFxcUYO3YsAOCBBx5As2bNMGfOHHh5eaFDhw4u6wcFBQFAhflXEgdvREQNiMNhu9pNICIictLXkitvAHDvvffizJkzmDFjBnJyctC5c2ds2rTJmcQkOzsbev3VzfeoczgcjqvaAiIiqjGlqXKtmuryvu0ht22biIjqp03X3ui2bQ/4+Xu3bftqYakAIiIiIiKiOoCDNyKihqSWFOk+ceIE7r//fgQHB8Pb2xsdO3bEd99956adJiKi2kpn0Lltqo/4zBsREdWo8+fP49Zbb8Xtt9+OL774Ak2aNMGPP/6IRo0aXe2mERER1WocvBERNSS1oFTASy+9hBYtWmDlypXOeW3atLmKLSIioqulNiUsqQt42yQREdWodevWISYmBnfffTdCQ0Nx44034q233rrazSIiIqr1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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "f, ax = plt.subplots(1, 2)\n",
- "sbn.heatmap(np.log2(result[0]), ax=ax[0], square=True)\n",
- "ax[0].set_title(\"ICCF\")x\n",
- "sbn.heatmap(np.log2(result[1]), ax=ax[1], cmap=\"RdBu_r\", vmin=-0.5, vmax=0.5, square=True)\n",
- "ax[1].set_title(\"Obs/Exp\")\n",
- "f.set_size_inches(10, 6)\n",
- "plt.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Generate triplet strategies with different offsets"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 50,
- "metadata": {},
- "outputs": [],
- "source": [
- "offset_strategies = [\n",
- " Triplet1DSnippingStrategy(bin_size=100_000,\n",
- " half_window_size=2_000_000,\n",
- " snipping_value=SnippingValues.ICCF,\n",
- " relative_offset=offset)\n",
- " for offset in np.arange(-1_000_000, 1_000_001, 500_000)\n",
- "]"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 52,
- "metadata": {},
- "outputs": [],
- "source": [
- "snipper = Snipper(offset_strategies)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 53,
- "metadata": {},
- "outputs": [],
- "source": [
- "result = snipper.snip(\"../playground/test.parquet\", tad_boundaries, threads=40)"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "metadata": {},
- "outputs": [
- {
- "data": {
- "image/png": "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",
- "text/plain": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "f, ax = plt.subplots(1, len(result))\n",
- "for index, array in enumerate(result):\n",
- " sbn.heatmap(np.log2(result[index]), ax=ax[index], square=True)\n",
- "f.set_size_inches(20, 5)\n",
- "plt.show()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3.8.0 ('spoc-dev')",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.8.0"
- },
- "orig_nbformat": 4,
- "vscode": {
- "interpreter": {
- "hash": "ac12263fcb70de6040950844f8173d0af40e7d81fd3cab43722576f4e93bb274"
- }
- }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/setup.py b/setup.py
index 6df90e0..164f19d 100644
--- a/setup.py
+++ b/setup.py
@@ -1,8 +1,7 @@
#!/usr/bin/env python
-
"""The setup script."""
-
-from setuptools import setup, find_packages
+from setuptools import find_packages
+from setuptools import setup
requirements = [
@@ -17,7 +16,7 @@
"ipykernel",
"matplotlib>=3.5.3",
"seaborn>=0.11.2",
- "duckdb==0.3.4",
+ "duckdb==0.9.1",
"sparse==0.13.0",
"numba>=0.57.0",
]
diff --git a/spoc/cli.py b/spoc/cli.py
index a1c1326..01b627d 100644
--- a/spoc/cli.py
+++ b/spoc/cli.py
@@ -1,11 +1,15 @@
"""Console script for spoc."""
import sys
+
import click
+
from spoc.contacts import ContactManipulator
-from spoc.fragments import FragmentAnnotator, FragmentExpander
+from spoc.contacts import Contacts
+from spoc.fragments import FragmentAnnotator
+from spoc.fragments import FragmentExpander
from spoc.io import FileManager
+from spoc.models.dataframe_models import DataMode
from spoc.pixels import GenomicBinner
-from spoc.contacts import Contacts
@click.group()
@@ -85,7 +89,7 @@ def bin_contacts(
"""
# load data from disk
- file_manager = FileManager(use_dask=True)
+ file_manager = FileManager(DataMode.DASK)
contacts = Contacts.from_uri(contact_path)
# binning
binner = GenomicBinner(bin_size=bin_size)
@@ -110,7 +114,7 @@ def merge_contacts(contact_paths, output):
contact_paths (tuple): Paths to the input contact files.
output (str, optional): Path to the output merged contact file.
"""
- file_manager = FileManager(use_dask=True)
+ file_manager = FileManager(DataMode.DASK)
# get list of parameters
parameters = [file_manager.list_contacts(p) for p in contact_paths]
# get parameter counts -> if count > 1 then we need to concatenate
diff --git a/spoc/contacts.py b/spoc/contacts.py
index 32b4ed4..0f08941 100644
--- a/spoc/contacts.py
+++ b/spoc/contacts.py
@@ -1,12 +1,22 @@
"""Managing multi-way contacts."""
-
from __future__ import annotations # needed for self reference in type hints
-from itertools import permutations, product
-from typing import List, Optional, Dict
-import pandas as pd
+
+from itertools import permutations
+from itertools import product
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+
import dask.dataframe as dd
+import duckdb
import numpy as np
-from spoc.models.dataframe_models import ContactSchema, DataFrame
+import pandas as pd
+
+from spoc.models.dataframe_models import ContactSchema
+from spoc.models.dataframe_models import DataFrame
+from spoc.models.dataframe_models import DataMode
+from spoc.models.dataframe_models import GenomicDataSchema
from spoc.models.file_parameter_models import ContactsParameters
@@ -51,10 +61,15 @@ def __init__(
number_fragments=self.number_fragments,
contains_metadata=self.contains_metadata,
)
+ # TODO: make this work for duckdb pyrelation -> switch to mode
if isinstance(contact_frame, pd.DataFrame):
- self.is_dask = False
+ self.data_mode = DataMode.PANDAS
+ elif isinstance(contact_frame, dd.DataFrame):
+ self.data_mode = DataMode.DASK
+ elif isinstance(contact_frame, duckdb.DuckDBPyRelation):
+ self.data_mode = DataMode.DUCKDB
else:
- self.is_dask = True
+ raise ValueError("Unknown data mode!")
self._data = self._schema.validate(contact_frame)
self.metadata_combi = metadata_combi
self.label_sorted = label_sorted
@@ -62,7 +77,7 @@ def __init__(
self.symmetry_flipped = symmetry_flipped
@staticmethod
- def from_uri(uri, mode="pandas"):
+ def from_uri(uri, mode=DataMode.PANDAS):
"""Construct contacts from uri.
Will match parameters based on the following order:
@@ -76,7 +91,7 @@ def from_uri(uri, mode="pandas"):
# import here to avoid circular imports
from spoc.io import FileManager
- return FileManager(use_dask=mode == "dask").load_contacts(uri)
+ return FileManager(mode).load_contacts(uri)
def get_global_parameters(self) -> ContactsParameters:
"""Returns global parameters"""
@@ -88,6 +103,10 @@ def get_global_parameters(self) -> ContactsParameters:
symmetry_flipped=self.symmetry_flipped,
)
+ def get_schema(self) -> GenomicDataSchema:
+ """Returns the schema of the underlying data"""
+ return self._schema
+
def _guess_number_fragments(self, contact_frame: DataFrame) -> int:
"""Guesses the number of fragments from the contact frame"""
return max(int(i.split("_")[1]) for i in contact_frame.columns if "start" in i)
@@ -99,22 +118,26 @@ def get_label_values(self) -> List[str]:
raise ValueError("Contacts do not contain metadata!")
output = set()
for i in range(self.number_fragments):
- if self.is_dask:
+ if self.data_mode == DataMode.DASK:
output.update(self.data[f"metadata_{i+1}"].unique().compute())
- else:
+ elif self.data_mode == DataMode.PANDAS:
output.update(self.data[f"metadata_{i+1}"].unique())
- return output
+ else:
+ raise ValueError("Label values not supported for duckdb!")
+ return list(output)
def get_chromosome_values(self) -> List[str]:
"""Returns all chromosome values"""
# TODO: This could be put in global metadata of parquet file
output = set()
for i in range(self.number_fragments):
- if self.is_dask:
+ if self.data_mode == DataMode.DASK:
output.update(self.data[f"chrom_{i+1}"].unique().compute())
- else:
+ elif self.data_mode == DataMode.PANDAS:
output.update(self.data[f"chrom_{i+1}"].unique())
- return output
+ else:
+ raise ValueError("Chromosome values not supported for duckdb!")
+ return list(output)
@property
def data(self):
@@ -150,18 +173,22 @@ def merge_contacts(self, merge_list: List[Contacts]) -> Contacts:
# validate that merge is possible
if len({i.number_fragments for i in merge_list}) != 1:
raise ValueError("All contacts need to have the same order!")
- if len({i.is_dask for i in merge_list}) != 1:
- raise ValueError("Mixture of dask and pandas dataframes is not supported!")
+ if len({i.data_mode for i in merge_list}) != 1:
+ raise ValueError("Mixture of dataframes is not supported!")
# TODO: assert all have same labelling state
number_fragments = merge_list[0].number_fragments
- if merge_list[0].is_dask:
+ if merge_list[0].data_mode == DataMode.DASK:
return Contacts(
dd.concat([i.data for i in merge_list]),
number_fragments=number_fragments,
)
- return Contacts(
- pd.concat([i.data for i in merge_list]), number_fragments=number_fragments
- )
+ elif merge_list[0].data_mode == DataMode.PANDAS:
+ return Contacts(
+ pd.concat([i.data for i in merge_list]),
+ number_fragments=number_fragments,
+ )
+ else:
+ raise ValueError("Merging duckdb relations is not supported!")
@staticmethod
def _generate_rename_columns(order, start_index=1):
@@ -317,13 +344,15 @@ def sort_labels(self, contacts: Contacts) -> Contacts:
)
)
# determine which method to use for concatenation
- if contacts.is_dask:
+ if contacts.data_mode == DataMode.DASK:
# this is a bit of a hack to get the index sorted. Dask does not support index sorting
result = (
dd.concat(subsets).reset_index().sort_values("index").set_index("index")
)
- else:
+ elif contacts.data_mode == DataMode.PANDAS:
result = pd.concat(subsets).sort_index()
+ else:
+ raise ValueError("Sorting labels for duckdb relations is not implemented.")
return Contacts(
result, number_fragments=contacts.number_fragments, label_sorted=True
)
@@ -365,7 +394,7 @@ def _sort_chromosomes(self, df: DataFrame, number_fragments: int) -> DataFrame:
def _generate_binary_label_mapping(
self, label_values: List[str], number_fragments: int
- ) -> Dict[str, str]:
+ ) -> Dict[Tuple[str, ...], Tuple[str, ...]]:
sorted_labels = sorted(label_values)
mapping = {}
for i in range(number_fragments + 1):
@@ -422,13 +451,17 @@ def equate_binary_labels(self, contacts: Contacts) -> Contacts:
subset[f"metadata_{i+1}"] = j
subsets.append(subset)
# determine which method to use for concatenation
- if contacts.is_dask:
+ if contacts.data_mode == DataMode.DASK:
# this is a bit of a hack to get the index sorted. Dask does not support index sorting
result = (
dd.concat(subsets).reset_index().sort_values("index").set_index("index")
)
- else:
+ elif contacts.data_mode == DataMode.PANDAS:
result = pd.concat(subsets).sort_index()
+ else:
+ raise ValueError(
+ "Equate binary labels for duckdb relations is not implemented."
+ )
return Contacts(
result,
number_fragments=contacts.number_fragments,
diff --git a/spoc/fragments.py b/spoc/fragments.py
index a2ae4cd..519d3a3 100644
--- a/spoc/fragments.py
+++ b/spoc/fragments.py
@@ -1,14 +1,18 @@
"""This part of spoc is responsible for dealing aligned fragments that
have not yet been converted to contacts. It deals with label information
as well as expanding fragments to contacts."""
-
-from typing import Dict, Union
from itertools import combinations
-import pandas as pd
+from typing import Dict
+from typing import Union
+
import dask.dataframe as dd
import numpy as np
-from .models.dataframe_models import FragmentSchema, ContactSchema, DataFrame
+import pandas as pd
+
from .contacts import Contacts
+from .models.dataframe_models import ContactSchema
+from .models.dataframe_models import DataFrame
+from .models.dataframe_models import FragmentSchema
class Fragments:
diff --git a/spoc/io.py b/spoc/io.py
index 15c8a9b..0e9cb77 100644
--- a/spoc/io.py
+++ b/spoc/io.py
@@ -1,35 +1,50 @@
"""Persisting functionality of spoc that manages writing to and reading from the filesystem."""
-
+import json
+import os
import pickle
-from typing import Dict, Union, List, Optional, Tuple
+from functools import partial
from hashlib import md5
-import os
-import json
from pathlib import Path
-import pandas as pd
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Tuple
+from typing import Union
+
import dask.dataframe as dd
+import duckdb
+import pandas as pd
+
from spoc.contacts import Contacts
-from spoc.pixels import Pixels
-from spoc.models.file_parameter_models import (
- ContactsParameters,
- PixelParameters,
- GlobalParameters,
-)
from spoc.fragments import Fragments
+from spoc.models.dataframe_models import DataMode
+from spoc.models.file_parameter_models import ContactsParameters
+from spoc.models.file_parameter_models import GlobalParameters
+from spoc.models.file_parameter_models import PixelParameters
+from spoc.pixels import Pixels
+
+# Instantiate one duckdb connection to be used for all duckdb relations
+DUCKDB_CONNECTION = duckdb.connect(database=":memory:")
class FileManager:
"""Is responsible for loading and writing files
Args:
- use_dask (bool, optional): Whether to use Dask for reading Parquet files. Defaults to False.
+ data_mode (DataMode, optional): Data mode. Defaults to DataMode.PANDAS.
"""
- def __init__(self, use_dask: bool = False) -> None:
- if use_dask:
+ def __init__(self, data_mode: DataMode = DataMode.PANDAS) -> None:
+ if data_mode == DataMode.DUCKDB:
+ self._parquet_reader_func = partial(
+ duckdb.read_parquet, connection=DUCKDB_CONNECTION
+ )
+ elif data_mode == DataMode.DASK:
self._parquet_reader_func = dd.read_parquet
- else:
+ elif data_mode == DataMode.PANDAS:
self._parquet_reader_func = pd.read_parquet
+ else:
+ raise ValueError(f"Data mode {data_mode} not supported!")
@staticmethod
def write_label_library(path: str, data: Dict[str, bool]) -> None:
@@ -163,17 +178,17 @@ def _parse_uri(
Tuple(str, Dict[str, str]): Tuple containing the path and a dictionary of parameters.
"""
# parse uri
- uri = uri.split("::")
+ uri_arguments = uri.split("::")
# validate uri
- if len(uri) < min_fields:
+ if len(uri_arguments) < min_fields:
raise ValueError(
f"Uri: {uri} is not valid. Must contain at least Path, number_fragments and binsize"
)
- params = dict(zip(uri_parameters, uri[1:]))
+ params = dict(zip(uri_parameters, uri_arguments[1:]))
# rewrite metadata_combi parameter
if "metadata_combi" in params.keys() and params["metadata_combi"] != "None":
params["metadata_combi"] = str(tuple(params["metadata_combi"]))
- return uri[0], params
+ return uri_arguments[0], params
def _fuzzy_match_parameters(
self,
@@ -207,10 +222,7 @@ def _fuzzy_match_parameters(
return matched_parameters[0]
def load_pixels(
- self,
- path: str,
- global_parameters: Optional[PixelParameters] = None,
- load_dataframe: bool = True,
+ self, path: str, global_parameters: Optional[PixelParameters] = None
) -> Pixels:
"""Loads specific pixels instance based on global parameters.
load_dataframe specifies whether the dataframe should be loaded, or whether pixels
@@ -219,7 +231,6 @@ def load_pixels(
Args:
path (str): Path to the pixel data.
global_parameters (PixelParameters): Global parameters.
- load_dataframe (bool, optional): Whether to load the dataframe. Defaults to True.
Returns:
Pixels: Pixels object containing the pixel data.
@@ -242,11 +253,8 @@ def load_pixels(
parsed_parameters, metadata
)
# rewrite path to contain parent folder
- pixel_path = Path(path) / pixel_path
- if load_dataframe:
- df = self._parquet_reader_func(pixel_path)
- else:
- df = pixel_path
+ full_pixel_path = Path(path) / pixel_path
+ df = self._parquet_reader_func(full_pixel_path)
return Pixels(df, **matched_parameters.dict())
def load_contacts(
@@ -278,8 +286,8 @@ def load_contacts(
parsed_parameters, metadata
)
# rewrite path to contain parent folder
- contacts_path = Path(path) / contacts_path
- df = self._parquet_reader_func(contacts_path)
+ full_contacts_path = Path(path) / contacts_path
+ df = self._parquet_reader_func(full_contacts_path)
return Contacts(df, **matched_parameters.dict())
@staticmethod
diff --git a/spoc/models/dataframe_models.py b/spoc/models/dataframe_models.py
index 4633d40..af42ff4 100644
--- a/spoc/models/dataframe_models.py
+++ b/spoc/models/dataframe_models.py
@@ -1,14 +1,22 @@
"""Dataframe models"""
-
-from typing import Iterable, Union, Dict
import copy
-import pandera as pa
-import pandas as pd
+from enum import auto
+from enum import Enum
+from typing import Dict
+from typing import Iterable
+from typing import List
+from typing import Optional
+from typing import Protocol
+from typing import Union
+
import dask.dataframe as dd
+import duckdb
+import pandas as pd
+import pandera as pa
# Define dataframe type
-DataFrame = Union[pd.DataFrame, dd.DataFrame]
+DataFrame = Union[pd.DataFrame, dd.DataFrame, duckdb.DuckDBPyRelation]
FragmentSchema = pa.DataFrameSchema(
{
@@ -29,6 +37,106 @@
coerce=True,
)
+RegionSchema = pa.DataFrameSchema(
+ {
+ "region_id": pa.Column(),
+ "region_chrom": pa.Column(str),
+ "region_start": pa.Column(int),
+ "region_end": pa.Column(int),
+ },
+ coerce=True,
+ unique=["region_id"],
+)
+
+# Protocol for genomic data
+
+
+class GenomicDataSchema(Protocol):
+ """Protocol for genomic data schema
+ to be used in the query engine"""
+
+ def get_position_fields(self) -> Dict[int, List[str]]:
+ """Returns the position fields as a dictionary
+ of framgent index to the respective fields"""
+
+ def get_contact_order(self) -> int:
+ """Returns the order of the genomic data"""
+
+ def get_schema(self) -> pa.DataFrameSchema:
+ """Return the schema of the underlying data"""
+
+ def get_binsize(self) -> Optional[int]:
+ """Returns the binsize of the genomic data"""
+
+ def get_region_number(self) -> Optional[int]:
+ """Returns the number of regions in the genomic data
+ if present."""
+
+ def get_half_window_size(self) -> Optional[int]:
+ """Returns the window size of the genomic data
+ if present."""
+
+
+class QueryStepDataSchema:
+ """Implements GenomicDataSchema for query steps
+ with generic columns"""
+
+ # pylint: disable=too-many-arguments
+ # arguments needed to define the schema
+ def __init__(
+ self,
+ columns: List[str],
+ position_fields: Dict[int, List[str]],
+ contact_order: int,
+ binsize: Optional[int] = None,
+ region_number: Optional[int] = None,
+ half_window_size: Optional[int] = None,
+ ) -> None:
+ self._columns = columns
+ self._contact_order = contact_order
+ self._position_fields = position_fields
+ self._binsize = binsize
+ self._region_number = region_number
+ self._half_window_size = half_window_size
+ self._schema = pa.DataFrameSchema(
+ {column: pa.Column() for column in columns},
+ coerce=True,
+ )
+
+ def get_position_fields(self) -> Dict[int, List[str]]:
+ """
+ Returns the position fields as a dictionary.
+
+ Returns:
+ A dictionary where the keys are integers representing positions
+ and the values are lists of strings representing the fields.
+ """
+ return self._position_fields
+
+ def get_contact_order(self) -> int:
+ """
+ Returns the contact order of the object.
+ """
+ return self._contact_order
+
+ def get_schema(self) -> pa.DataFrameSchema:
+ """Return the schema of the underlying data"""
+ return self._schema
+
+ def get_binsize(self) -> Optional[int]:
+ """Returns the binsize of the genomic data"""
+ return self._binsize
+
+ def get_region_number(self) -> Optional[int]:
+ """Returns the number of regions in the genomic data
+ if present."""
+ return self._region_number
+
+ def get_half_window_size(self) -> Optional[int]:
+ """Returns the half window size of the genomic data
+ if present."""
+ return self._half_window_size
+
# schemas for higher order contacts
@@ -113,6 +221,27 @@ def validate_header(self, data_frame: DataFrame) -> None:
self._schema, data_frame, "Header is invalid!"
)
+ def get_schema(self) -> pa.DataFrameSchema:
+ """
+ Get the schema of the DataFrame.
+
+ Returns:
+ pa.DataFrameSchema: The schema of the DataFrame.
+ """
+ return self._schema
+
+ def get_position_fields(self) -> Dict[int, List[str]]:
+ """Returns the position fields as a dictionary
+ of framgent index to the respective fields"""
+ return {
+ i: [f"chrom_{i}", f"start_{i}", f"end_{i}"]
+ for i in range(1, self._number_fragments + 1)
+ }
+
+ def get_contact_order(self) -> int:
+ """Returns the order of the genomic data"""
+ return self._number_fragments
+
def validate(self, data_frame: DataFrame) -> DataFrame:
"""Validate multiway contact dataframe
@@ -120,8 +249,25 @@ def validate(self, data_frame: DataFrame) -> DataFrame:
data_frame (DataFrame): The DataFrame to validate.
"""
self.validate_header(data_frame)
+ if isinstance(data_frame, duckdb.DuckDBPyRelation):
+ # duckdb does not support schema validation
+ return data_frame
return self._schema.validate(data_frame)
+ def get_binsize(self) -> Optional[int]:
+ """Returns the binsize of the genomic data"""
+ return None
+
+ def get_region_number(self) -> Optional[int]:
+ """Returns the number of regions in the genomic data
+ if present."""
+ return None
+
+ def get_half_window_size(self) -> Optional[int]:
+ """Returns the window size of the genomic data
+ if present."""
+ return None
+
class PixelSchema:
"""Dynamic schema for N-way pixels
@@ -131,9 +277,15 @@ class PixelSchema:
same_chromosome (bool, optional): Whether the fragments are on the same chromosome. Defaults to True.
"""
- def __init__(self, number_fragments: int = 3, same_chromosome: bool = True) -> None:
+ def __init__(
+ self,
+ number_fragments: int = 3,
+ same_chromosome: bool = True,
+ binsize: Optional[int] = None,
+ ) -> None:
self._number_fragments = number_fragments
self._same_chromosome = same_chromosome
+ self._binsize = binsize
self._schema = pa.DataFrameSchema(
dict(
self._get_constant_fields(),
@@ -182,6 +334,46 @@ def validate_header(self, data_frame: DataFrame) -> None:
self._schema, data_frame, "Header is invalid!"
)
+ def get_schema(self) -> pa.DataFrameSchema:
+ """
+ Get the schema of the DataFrame.
+
+ Returns:
+ pa.DataFrameSchema: The schema of the DataFrame.
+ """
+ return self._schema
+
+ def get_position_fields(self) -> Dict[int, List[str]]:
+ """Returns the position fields as a dictionary
+ of framgent index to the respective fields"""
+ if self._same_chromosome:
+ return {
+ i: ["chrom", f"start_{i}"] for i in range(1, self._number_fragments + 1)
+ }
+ else:
+ return {
+ i: [f"chrom_{i}", f"start_{i}"]
+ for i in range(1, self._number_fragments + 1)
+ }
+
+ def get_binsize(self) -> Optional[int]:
+ """Returns the binsize of the genomic data"""
+ return self._binsize
+
+ def get_region_number(self) -> Optional[int]:
+ """Returns the number of regions in the genomic data
+ if present."""
+ return None
+
+ def get_contact_order(self) -> int:
+ """Returns the order of the genomic data"""
+ return self._number_fragments
+
+ def get_half_window_size(self) -> Optional[int]:
+ """Returns the window size of the genomic data
+ if present."""
+ return None
+
def validate(self, data_frame: DataFrame) -> DataFrame:
"""Validate multiway contact dataframe
@@ -189,4 +381,16 @@ def validate(self, data_frame: DataFrame) -> DataFrame:
data_frame (DataFrame): The DataFrame to validate.
"""
+ self.validate_header(data_frame)
+ if isinstance(data_frame, duckdb.DuckDBPyRelation):
+ # duckdb does not support schema validation
+ return data_frame
return self._schema.validate(data_frame)
+
+
+class DataMode(Enum):
+ """Enum for data mode"""
+
+ PANDAS = auto()
+ DASK = auto()
+ DUCKDB = auto()
diff --git a/spoc/models/file_parameter_models.py b/spoc/models/file_parameter_models.py
index 38c072b..c89405e 100644
--- a/spoc/models/file_parameter_models.py
+++ b/spoc/models/file_parameter_models.py
@@ -1,7 +1,10 @@
"""This file contains data classes for parameters
of spoc data structures"""
-from typing import Optional, Tuple, List
-from pydantic import BaseModel, Field
+from typing import List
+from typing import Optional
+from typing import Tuple
+
+from pydantic import BaseModel
class GlobalParameters(BaseModel):
@@ -15,6 +18,7 @@ class GlobalParameters(BaseModel):
@classmethod
def get_uri_fields(cls) -> List[str]:
+ """Returns the fields that should be included in the URI"""
raise NotImplementedError
def __hash__(self) -> int:
@@ -35,6 +39,7 @@ class ContactsParameters(GlobalParameters):
@classmethod
def get_uri_fields(cls) -> List[str]:
+ """Returns the fields that should be included in the URI"""
# Specific parameters needed to enforce order
return [
"number_fragments",
@@ -53,6 +58,7 @@ class PixelParameters(GlobalParameters):
@classmethod
def get_uri_fields(cls) -> List[str]:
+ """Returns the fields that should be included in the URI"""
# Specific parameters needed to enforce order
return [
"number_fragments",
diff --git a/spoc/pixels.py b/spoc/pixels.py
index 4d6e8d3..ff4d9cc 100644
--- a/spoc/pixels.py
+++ b/spoc/pixels.py
@@ -1,12 +1,19 @@
"""This part of spoc is responsible for binned,
higher order contacts in the form of 'genomic pixels'"""
-from pathlib import Path
-from typing import Union, Optional, List
-import pandas as pd
+from functools import partial
+from typing import List
+from typing import Optional
+
import dask.dataframe as dd
-from spoc.models.dataframe_models import PixelSchema, DataFrame
-from spoc.models.file_parameter_models import PixelParameters
+import duckdb
+import pandas as pd
+
from spoc.contacts import Contacts
+from spoc.models.dataframe_models import DataFrame
+from spoc.models.dataframe_models import DataMode
+from spoc.models.dataframe_models import GenomicDataSchema
+from spoc.models.dataframe_models import PixelSchema
+from spoc.models.file_parameter_models import PixelParameters
class Pixels:
@@ -38,9 +45,9 @@ class Pixels:
def __init__(
self,
- pixel_source: Union[pd.DataFrame, dd.DataFrame, str],
- number_fragments: Optional[int] = None,
- binsize: Optional[int] = None,
+ pixel_source: DataFrame,
+ number_fragments: int,
+ binsize: int,
metadata_combi: Optional[List[str]] = None,
label_sorted: bool = False,
binary_labels_equal: bool = False,
@@ -51,7 +58,9 @@ def __init__(
can be a pandas or dask dataframe or a path. Caveat is that
if pixels are a path, source data is not validated."""
self._schema = PixelSchema(
- number_fragments=number_fragments, same_chromosome=same_chromosome
+ number_fragments=number_fragments,
+ same_chromosome=same_chromosome,
+ binsize=binsize,
)
self._same_chromosome = same_chromosome
self._number_fragments = number_fragments
@@ -60,18 +69,19 @@ def __init__(
self._symmetry_flipped = symmetry_flipped
self._metadata_combi = metadata_combi
self._label_sorted = label_sorted
- if isinstance(pixel_source, (pd.DataFrame, dd.DataFrame)):
- self._data = self._schema.validate(pixel_source)
- self._path = None
+ # get data mode
+ if isinstance(pixel_source, pd.DataFrame):
+ self.data_mode = DataMode.PANDAS
+ elif isinstance(pixel_source, dd.DataFrame):
+ self.data_mode = DataMode.DASK
+ elif isinstance(pixel_source, duckdb.DuckDBPyRelation):
+ self.data_mode = DataMode.DUCKDB
else:
- # check whether path exists
- if not Path(pixel_source).exists():
- raise ValueError(f"Path: {pixel_source} does not exist!")
- self._path = Path(pixel_source)
- self._data = None
+ raise ValueError("Unknown data mode!")
+ self._data = self._schema.validate(pixel_source)
@staticmethod
- def from_uri(uri, mode="path") -> "Pixels":
+ def from_uri(uri, mode=DataMode.PANDAS) -> "Pixels":
"""Construct pixels from uri.
Will match parameters based on the following order:
@@ -95,19 +105,7 @@ def from_uri(uri, mode="path") -> "Pixels":
# pylint: disable=import-outside-toplevel
from spoc.io import FileManager
- # get read mode
- if mode == "path":
- load_dataframe = False
- use_dask = False
- elif mode == "pandas":
- load_dataframe = True
- use_dask = False
- else:
- load_dataframe = True
- use_dask = True
- return FileManager(use_dask=use_dask).load_pixels(
- uri, load_dataframe=load_dataframe
- )
+ return FileManager(mode).load_pixels(uri)
def get_global_parameters(self) -> PixelParameters:
"""Returns global parameters of pixels
@@ -125,15 +123,6 @@ def get_global_parameters(self) -> PixelParameters:
same_chromosome=self._same_chromosome,
)
- @property
- def path(self) -> str:
- """Returns path of pixels
-
- Returns:
- str: The path of the pixels.
- """
- return self._path
-
@property
def data(self) -> DataFrame:
"""Returns pixels as dataframe
@@ -200,6 +189,10 @@ def same_chromosome(self) -> bool:
"""
return self._same_chromosome
+ def get_schema(self) -> GenomicDataSchema:
+ """Returns the schema of the underlying data"""
+ return self._schema
+
class GenomicBinner:
"""Bins higher order contacts into genomic bins of fixed size.
@@ -208,43 +201,45 @@ class GenomicBinner:
Args:
bin_size (int): The size of the genomic bins.
-
"""
def __init__(self, bin_size: int) -> None:
self._bin_size = bin_size
- self._contact_order = None
- def _get_assigned_bin_output_structure(self):
- columns = [f"chrom_{index}" for index in range(1, self._contact_order + 1)] + [
- f"start_{index}" for index in range(1, self._contact_order + 1)
+ def _get_assigned_bin_output_structure(self, contact_order: int):
+ columns = [f"chrom_{index}" for index in range(1, contact_order + 1)] + [
+ f"start_{index}" for index in range(1, contact_order + 1)
]
return pd.DataFrame(columns=columns).astype(int)
- def _assign_bins(self, data_frame: pd.DataFrame) -> pd.DataFrame:
+ def _assign_bins(
+ self, data_frame: pd.DataFrame, contact_order: int
+ ) -> pd.DataFrame:
# capture empty dataframe
if data_frame.empty:
- return self._get_assigned_bin_output_structure()
+ return self._get_assigned_bin_output_structure(contact_order)
return data_frame.assign(
**{
f"start_{index}": (data_frame[f"pos_{index}"] // self._bin_size)
* self._bin_size
- for index in range(1, self._contact_order + 1)
+ for index in range(1, contact_order + 1)
}
).filter(regex="(chrom|start)")
- def _assign_midpoints(self, contacts: dd.DataFrame) -> dd.DataFrame:
+ def _assign_midpoints(
+ self, contacts: dd.DataFrame, contact_order: int
+ ) -> dd.DataFrame:
"""Collapses start-end to a middle position"""
return contacts.assign(
**{
f"pos_{index}": (contacts[f"start_{index}"] + contacts[f"end_{index}"])
// 2
- for index in range(1, self._contact_order + 1)
+ for index in range(1, contact_order + 1)
}
).drop(
[
c
- for index in range(1, self._contact_order + 1)
+ for index in range(1, contact_order + 1)
for c in [f"start_{index}", f"end_{index}"]
],
axis=1,
@@ -261,19 +256,22 @@ def bin_contacts(self, contacts: Contacts, same_chromosome: bool = True) -> Pixe
Pixels: The binned genomic pixels.
"""
- self._contact_order = contacts.number_fragments
- contacts_w_midpoints = self._assign_midpoints(contacts.data)
- if contacts.is_dask:
+ contact_order = contacts.number_fragments
+ contacts_w_midpoints = self._assign_midpoints(contacts.data, contact_order)
+ if contacts.data_mode == DataMode.DASK:
contact_bins = contacts_w_midpoints.map_partitions(
- self._assign_bins, meta=self._get_assigned_bin_output_structure()
+ partial(self._assign_bins, contact_order=contact_order),
+ meta=self._get_assigned_bin_output_structure(contact_order),
)
+ elif contacts.data_mode == DataMode.PANDAS:
+ contact_bins = self._assign_bins(contacts_w_midpoints, contact_order)
else:
- contact_bins = self._assign_bins(contacts_w_midpoints)
+ raise ValueError(f"Data mode: {contacts.data_mode} not supported!")
pixels = (
contact_bins.groupby(
[
c
- for index in range(1, self._contact_order + 1)
+ for index in range(1, contact_order + 1)
for c in [f"chrom_{index}", f"start_{index}"]
],
observed=True,
@@ -290,26 +288,25 @@ def bin_contacts(self, contacts: Contacts, same_chromosome: bool = True) -> Pixe
& (pixels.chrom_2.astype(str) == pixels.chrom_3.astype(str))
]
.drop(
- [f"chrom_{index}" for index in range(2, self._contact_order + 1)],
+ [f"chrom_{index}" for index in range(2, contact_order + 1)],
axis=1,
)
.rename(columns={"chrom_1": "chrom"})
)
# sort pixels
pixels_sorted = pixels.sort_values(
- ["chrom"]
- + [f"start_{index}" for index in range(1, self._contact_order + 1)]
+ ["chrom"] + [f"start_{index}" for index in range(1, contact_order + 1)]
).reset_index(drop=True)
else:
pixels_sorted = pixels.sort_values(
- [f"chrom_{index}" for index in range(1, self._contact_order + 1)]
- + [f"start_{index}" for index in range(1, self._contact_order + 1)]
+ [f"chrom_{index}" for index in range(1, contact_order + 1)]
+ + [f"start_{index}" for index in range(1, contact_order + 1)]
).reset_index(drop=True)
# construct pixels and return
return Pixels(
pixels_sorted,
same_chromosome=same_chromosome,
- number_fragments=self._contact_order,
+ number_fragments=contact_order,
binsize=self._bin_size,
binary_labels_equal=contacts.binary_labels_equal,
symmetry_flipped=contacts.symmetry_flipped,
diff --git a/spoc/query_engine.py b/spoc/query_engine.py
new file mode 100644
index 0000000..6069897
--- /dev/null
+++ b/spoc/query_engine.py
@@ -0,0 +1,622 @@
+"""This file contains the classes making up the query engine."""
+from enum import Enum
+from itertools import product
+from typing import Any
+from typing import Callable
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Protocol
+from typing import Tuple
+from typing import TypeVar
+from typing import Union
+
+import dask.dataframe as dd
+import duckdb
+import numpy as np
+import pandas as pd
+from pydantic import BaseModel
+
+from spoc.io import DUCKDB_CONNECTION
+from spoc.models.dataframe_models import GenomicDataSchema
+from spoc.models.dataframe_models import QueryStepDataSchema
+from spoc.models.dataframe_models import RegionSchema
+
+
+T = TypeVar("T")
+
+
+def convert_string_to_enum(enum_class: Callable[[str], T], string: str) -> T:
+ """Converts a string to an enum value"""
+ try:
+ return enum_class(string.upper())
+ except ValueError as exc:
+ raise ValueError(f"Invalid value for {enum_class.__name__}: {string}") from exc
+
+
+class GenomicData(Protocol):
+ """Protocol for genomic data
+ to be used in the query engine"""
+
+ @property
+ def data(self) -> Union[pd.DataFrame, duckdb.DuckDBPyRelation, dd.DataFrame]:
+ """Return the data in the object"""
+
+ def get_schema(self) -> GenomicDataSchema:
+ """Return the schema of the underlying data"""
+
+
+class QueryStep(Protocol):
+ """Protocol for query steps"""
+
+ def validate(self, data_schema: GenomicDataSchema) -> None:
+ """Validate the query step against the data schema"""
+
+ def __call__(self, *args: Any, **kwds: Any) -> "QueryPlan":
+ """Apply the query step to the data"""
+
+
+# TODO: think about allowing anchor composition
+class Anchor(BaseModel):
+ """Represents an anchor.
+
+ Attributes:
+ mode (str): The mode of the anchor. (Can be "ANY" or "ALL")
+ anchors (Optional[List[int]]): The list of anchor values (optional).
+ """
+
+ mode: str
+ anchors: Optional[List[int]] = None
+
+ def __repr__(self) -> str:
+ return f"Anchor(mode={self.mode}, anchors={self.anchors})"
+
+ def __str__(self) -> str:
+ return self.__repr__()
+
+
+class Overlap:
+ """
+ This class represents an overlap calculation used for contact and pixel selection.
+ It provides methods to validate the filter against a data schema,
+ convert data to a duckdb relation, construct a filter string,
+ and apply the filter to the data.
+ """
+
+ def __init__(
+ self,
+ regions: pd.DataFrame,
+ anchor_mode: Union[Anchor, Tuple[str, List[int]]],
+ half_window_size: Optional[int] = None,
+ ) -> None:
+ """
+ Initialize the Overlap object.
+
+ Args:
+ regions (pd.DataFrame): A DataFrame containing the regions data.
+ anchor_mode (Union[Anchor,Tuple[str,List[int]]]): The anchor mode to be used.
+ half_window_size (Optional[int]): The window size the regions should be expanded to. Defaults to None and is inferred from the data.
+
+ Returns:
+ None
+ """
+ # add ids to regions if they don't exist
+ if "id" not in regions.columns:
+ regions["id"] = range(len(regions))
+ if half_window_size is not None:
+ expanded_regions = regions.copy()
+ # create midpoint
+ expanded_regions["midpoint"] = (
+ expanded_regions["start"] + expanded_regions["end"]
+ ) // 2
+ # expand regions
+ expanded_regions["start"] = expanded_regions["midpoint"] - half_window_size
+ expanded_regions["end"] = expanded_regions["midpoint"] + half_window_size
+ # drop midpoint
+ expanded_regions = expanded_regions.drop(columns=["midpoint"])
+ self._regions = RegionSchema.validate(
+ expanded_regions.add_prefix("region_")
+ )
+ self._half_window_size = half_window_size
+ else:
+ self._regions = RegionSchema.validate(regions.add_prefix("region_"))
+ # infer window size -> variable regions will have largest possible window size
+ self._half_window_size = int(
+ (self._regions["region_end"] - self._regions["region_start"]).max() // 2
+ )
+ if isinstance(anchor_mode, tuple):
+ self._anchor_mode = Anchor(mode=anchor_mode[0], anchors=anchor_mode[1])
+ else:
+ self._anchor_mode = anchor_mode
+
+ def validate(self, data_schema: GenomicDataSchema) -> None:
+ """Validate the filter against the data schema"""
+ # check whether an anchor is specified that is not in the data
+ if self._anchor_mode.anchors is not None:
+ if not all(
+ anchor in data_schema.get_position_fields().keys()
+ for anchor in self._anchor_mode.anchors
+ ):
+ raise ValueError(
+ "An anchor is specified that is not in the data schema."
+ )
+
+ def _convert_to_duckdb(
+ self,
+ data: Union[pd.DataFrame, dd.DataFrame],
+ ) -> duckdb.DuckDBPyRelation:
+ """
+ Converts the data to a duckdb relation.
+
+ Parameters:
+ data (Union[pd.DataFrame, dd.DataFrame, duckdb.DuckDBPyRelation]): The input data to be converted.
+
+ Returns:
+ duckdb.DuckDBPyRelation: The converted duckdb relation.
+ """
+ if isinstance(data, dd.DataFrame):
+ data = data.compute()
+ return duckdb.from_df(data, connection=DUCKDB_CONNECTION)
+
+ def _contstruct_filter(self, position_fields: Dict[int, List[str]]) -> str:
+ """Constructs the filter string.
+
+ Args:
+ position_fields (List[str]): List of position fields.
+
+ Returns:
+ str: The constructed filter string.
+
+ Raises:
+ NotImplementedError: If the length of fields is not equal to 3.
+ """
+ query_strings = []
+ join_string = " or " if self._anchor_mode.mode == "ANY" else " and "
+ # subset on anchor regions
+ if self._anchor_mode.anchors is not None:
+ subset_positions = [
+ position_fields[anchor] for anchor in self._anchor_mode.anchors
+ ]
+ else:
+ subset_positions = list(position_fields.values())
+ for fields in subset_positions:
+ chrom, start, end = fields
+ output_string = f"""(data.{chrom} = regions.region_chrom and
+ (
+ data.{start} between regions.region_start and regions.region_end or
+ data.{end} between regions.region_start and regions.region_end or
+ regions.region_start between data.{start} and data.{end}
+ )
+ )"""
+ query_strings.append(output_string)
+ return join_string.join(query_strings)
+
+ def _get_transformed_schema(
+ self,
+ data_frame: duckdb.DuckDBPyRelation,
+ input_schema: GenomicDataSchema,
+ position_fields: Dict[int, List[str]],
+ ) -> GenomicDataSchema:
+ """Returns the schema of the transformed data."""
+ # construct schema
+ return QueryStepDataSchema(
+ columns=data_frame.columns,
+ position_fields=position_fields,
+ contact_order=input_schema.get_contact_order(),
+ binsize=input_schema.get_binsize(),
+ region_number=len(self._regions),
+ half_window_size=self._half_window_size,
+ )
+
+ def _add_end_position(
+ self,
+ data_frame: duckdb.DuckDBPyRelation,
+ bin_size: Optional[int],
+ position_fields: Dict[int, List[str]],
+ ) -> duckdb.DuckDBPyRelation:
+ """Adds an end position column to the dataframe"""
+ position_columns = {j for i in position_fields.values() for j in i}
+ non_position_columns = [
+ column for column in data_frame.columns if column not in position_columns
+ ]
+ new_position_clause = [
+ f"data.chrom as chrom_{position}, data.start_{position} as start_{position}, data.start_{position} + {bin_size} as end_{position}"
+ for position in position_fields.keys()
+ ]
+ # add end position
+ return data_frame.set_alias("data").project(
+ ",".join(new_position_clause + non_position_columns)
+ )
+
+ def __repr__(self) -> str:
+ return f"Overlap(anchor_mode={self._anchor_mode})"
+
+ def __call__(self, genomic_data: GenomicData) -> GenomicData:
+ """Apply the filter to the data"""
+ # get input schema
+ input_schema = genomic_data.get_schema()
+ # bring input to duckdb dataframe
+ if isinstance(genomic_data.data, duckdb.DuckDBPyRelation):
+ genomic_df = genomic_data.data
+ else:
+ genomic_df = self._convert_to_duckdb(genomic_data.data)
+ regions = self._convert_to_duckdb(self._regions)
+ # get position columns and construct filter
+ position_fields = input_schema.get_position_fields()
+ # add end position if not present
+ if len(position_fields[1]) == 2:
+ genomic_df = self._add_end_position(
+ genomic_df, input_schema.get_binsize(), position_fields
+ )
+ position_fields = {
+ position: [f"chrom_{position}", f"start_{position}", f"end_{position}"]
+ for position in position_fields.keys()
+ }
+ # construct query
+ snipped_df = genomic_df.set_alias("data").join(
+ regions.set_alias("regions"), self._contstruct_filter(position_fields)
+ )
+ return QueryPlan(
+ snipped_df,
+ self._get_transformed_schema(snipped_df, input_schema, position_fields),
+ )
+
+
+class AggregationFunction(Enum):
+ """Enum for aggregation functions.
+ Options are:
+ SUM: Sum of values.
+ AVG_WITH_EMPTY: Average of values, empty values are counted as 0.
+ AVG: Average of values, empty values are not counted.
+ COUNT: Number of values.
+ """
+
+ SUM: str = "SUM"
+ AVG_WITH_EMPTY: str = "AVG_WITH_EMPTY"
+ AVG: str = "AVG"
+ COUNT: str = "COUNT"
+
+
+class DistanceAggregation:
+ """Aggregation based on distances from a region. Uses all available distances."""
+
+ def __init__(
+ self,
+ value_column: str,
+ function: Union[AggregationFunction, str] = AggregationFunction.AVG,
+ densify_output: bool = True,
+ position_list: Optional[List[int]] = None,
+ ) -> None:
+ """Initialize the aggregation.
+
+ Args:
+ value_column (str): The name of the column to be aggregated.
+ function (Union[AggregationFunction,str]): The aggregation function to be applied. Defaults to AggregationFunction.AVG.
+ densify_output (bool, optional): Whether to densify the output. Defaults to True.
+ This requires a binsize value to be set in the data schema.
+ position_list (Optional[List[int]]): The list of positions to use for aggregations, starting with 1. Defaults to using all positions.
+ """
+ if isinstance(function, str):
+ parsed_function = convert_string_to_enum(AggregationFunction, function)
+ else:
+ parsed_function = function
+ self._function = parsed_function
+ self._value_column = value_column
+ self._densify_output = densify_output
+ self._position_list = position_list
+
+ def validate(self, data_schema: GenomicDataSchema) -> None:
+ """Validate the aggregation against the data schema"""
+ # check that at leastl one distance field is present
+ if "distance_1" not in data_schema.get_schema().columns:
+ raise ValueError("No distance fields in data schema.")
+ # check that all position fields are present
+ if self._position_list is not None:
+ for position in self._position_list:
+ if position not in data_schema.get_position_fields():
+ raise ValueError(f"Position {position} not in data schema.")
+ # check that value column is present
+ if self._value_column not in data_schema.get_schema().columns:
+ raise ValueError("Value column not in data schema.")
+ # check for binsize -> only pixels have that
+ if data_schema.get_binsize() is None:
+ raise ValueError("No binsize specified in data schema.")
+ # check for window size
+ if data_schema.get_half_window_size() is None:
+ raise ValueError("No window size specified in data schema.")
+
+ def _get_transformed_schema(
+ self,
+ data_frame: duckdb.DuckDBPyRelation,
+ input_schema: GenomicDataSchema,
+ position_fields: Dict[int, List[str]],
+ ) -> GenomicDataSchema:
+ """Returns the schema of the transformed data."""
+ # construct schema
+ return QueryStepDataSchema(
+ columns=data_frame.columns,
+ position_fields=position_fields,
+ contact_order=len(position_fields),
+ binsize=input_schema.get_binsize(),
+ )
+
+ def _aggregate_distances(
+ self,
+ data_frame: duckdb.DuckDBPyRelation,
+ input_schema: GenomicDataSchema,
+ position_fields: Dict[int, List[str]],
+ ) -> duckdb.DuckDBPyRelation:
+ """Aggregates the distances."""
+ # get distance columns
+ distance_columns = [
+ f"distance_{position}" for position in position_fields.keys()
+ ]
+ # construct aggregation
+ if self._function == AggregationFunction.COUNT:
+ aggregation_string = (
+ f"COUNT(*) as {self._value_column}_{self._function.name.lower()}"
+ )
+ elif self._function == AggregationFunction.AVG_WITH_EMPTY:
+ # For average, we need to sum up the values and divide by the number of regions
+ aggregation_string = f"SUM({self._value_column})::float/{input_schema.get_region_number()} as {self._value_column}_{self._function.name.lower()}"
+ else:
+ aggregation_string = f"{self._function.name}({self._value_column}) as {self._value_column}_{self._function.name.lower()}"
+ data_frame = (
+ data_frame.set_alias("data")
+ .aggregate(
+ ",".join(distance_columns + [aggregation_string]),
+ )
+ .order(",".join(distance_columns))
+ )
+ return data_frame
+
+ def _create_empty_dense_output(
+ self,
+ input_schema: GenomicDataSchema,
+ position_fields: Dict[int, List[str]],
+ ) -> duckdb.DuckDBPyRelation:
+ """Create dense value columns for all distances."""
+ binsize: Optional[int] = input_schema.get_binsize()
+ if binsize is None:
+ raise ValueError("No binsize specified in data schema.")
+ int_binsize: int = binsize
+ windowsize: Optional[int] = input_schema.get_half_window_size()
+ if windowsize is None:
+ raise ValueError("No window size specified in data schema.")
+ int_windowsize: int = windowsize
+ # create combinations of distances
+ distance_combinations = pd.DataFrame(
+ product(
+ np.arange(
+ -(np.floor(int_windowsize / int_binsize) * int_binsize),
+ (np.floor(int_windowsize / int_binsize) * int_binsize) + 1,
+ int_binsize,
+ ),
+ repeat=len(position_fields.keys()),
+ ),
+ columns=[f"distance_{i}" for i in position_fields.keys()],
+ )
+ # fill value
+ if self._function in (
+ AggregationFunction.COUNT,
+ AggregationFunction.SUM,
+ AggregationFunction.AVG_WITH_EMPTY,
+ ):
+ distance_combinations["fill_value"] = 0
+ else:
+ distance_combinations["fill_value"] = np.nan
+ return duckdb.from_df(distance_combinations, connection=DUCKDB_CONNECTION)
+
+ def _fill_empty_output(
+ self,
+ data_frame: duckdb.DuckDBPyRelation,
+ empty_dense_output: duckdb.DuckDBPyRelation,
+ position_fields: Dict[int, List[str]],
+ ) -> duckdb.DuckDBPyRelation:
+ """Fill empty output with values from dense output."""
+ # get distance columns
+ distance_columns = [f"distance_{i}" for i in position_fields.keys()]
+ # construct join and coalesce output
+ data_frame = (
+ data_frame.set_alias("data")
+ .join(
+ empty_dense_output.set_alias("empty_dense_output"),
+ ",".join(distance_columns),
+ how="right",
+ )
+ .project(
+ ",".join(distance_columns)
+ + f", COALESCE(data.{self._value_column}_{self._function.name.lower()}, empty_dense_output.fill_value) as {self._value_column}"
+ )
+ .set_alias("filled")
+ .order(",".join([f"filled.{col}" for col in distance_columns]))
+ )
+ return data_frame
+
+ def __call__(self, genomic_data: GenomicData) -> GenomicData:
+ """Apply the aggregation to the data"""
+ # get input schema
+ input_schema = genomic_data.get_schema()
+ # bring input to duckdb dataframe
+ if isinstance(genomic_data.data, duckdb.DuckDBPyRelation):
+ genomic_df = genomic_data.data
+ else:
+ genomic_df = duckdb.from_df(genomic_data.data, connection=DUCKDB_CONNECTION)
+ # get position columns
+ position_fields = input_schema.get_position_fields()
+ if self._position_list is not None:
+ position_fields = {
+ position: position_fields[position] for position in self._position_list
+ }
+ # construct transformation
+ aggregated_data = self._aggregate_distances(
+ genomic_df, input_schema, position_fields
+ )
+ if self._densify_output:
+ empty_dense_output = self._create_empty_dense_output(
+ input_schema, position_fields
+ )
+ aggregated_data = self._fill_empty_output(
+ aggregated_data, empty_dense_output, position_fields
+ )
+ return QueryPlan(
+ aggregated_data,
+ self._get_transformed_schema(
+ aggregated_data, input_schema, position_fields
+ ),
+ )
+
+
+class DistanceMode(Enum):
+ """Enum for distance modes."""
+
+ LEFT: str = "LEFT"
+ RIGHT: str = "RIGHT"
+ BOTH: str = "BOTH"
+ MIDPOINT: str = "MIDPOINT"
+
+
+class DistanceTransformation:
+ """Adds distance columns for each position field relative
+ to required region_columns."""
+
+ def __init__(
+ self, distance_mode: Union[DistanceMode, str] = DistanceMode.LEFT
+ ) -> None:
+ """Initialize the transformation.
+
+ Args:
+ distance_mode (Union[DistanceMode,str]): The distance mode to be used. Defaults to DistanceMode.MIDPOINT.
+ Specifies how the distance is calculated relative to the region midpoint.
+ Note that the distance is always calculated relative to the midpoint of the region.
+ If a binsize is specificed in the data schema, this needs to be set to LEFT.
+ """
+ if isinstance(distance_mode, str):
+ distance_mode = convert_string_to_enum(DistanceMode, distance_mode)
+ self._distance_mode = distance_mode
+
+ def validate(self, data_schema: GenomicDataSchema) -> None:
+ """Validate the transformation against the data schema"""
+ # check that there are position fields and region columns
+ if not data_schema.get_position_fields():
+ raise ValueError("No position fields in data schema.")
+ schema_columns = data_schema.get_schema().columns
+ required_columns = ["region_chrom", "region_start", "region_end"]
+ if not all(column in schema_columns for column in required_columns):
+ raise ValueError("No region columns in data schema.")
+ if (
+ self._distance_mode != DistanceMode.LEFT
+ and data_schema.get_binsize() is not None
+ ):
+ raise ValueError(
+ "Binsize specified in data schema, but distance mode is not set to LEFT."
+ )
+
+ def _create_transform_columns(
+ self, genomic_df: duckdb.DuckDBPyRelation, input_schema: GenomicDataSchema
+ ) -> duckdb.DuckDBPyRelation:
+ """Creates the transform columns for the given position fields"""
+ # position fields
+ position_fields = input_schema.get_position_fields()
+ # get existing columns
+ transform_strings = [f"data.{column}" for column in genomic_df.columns]
+ # check whether binsize is specified
+ if input_schema.get_binsize() is not None:
+ binsize = input_schema.get_binsize()
+ else:
+ binsize = 1
+ # create transform columns
+ for position_field, fields in position_fields.items():
+ _, start, end = fields
+ if self._distance_mode == DistanceMode.MIDPOINT:
+ output_string = f"""(FLOOR((data.{start} + data.{end})/2) - FLOOR((data.region_start + data.region_end)/2))
+ as distance_{position_field}"""
+ if self._distance_mode == DistanceMode.LEFT:
+ output_string = f"""data.{start} - FLOOR((FLOOR(data.region_start/{binsize}) * {binsize}
+ + FLOOR(data.region_end/{binsize}) * {binsize})/2) as distance_{position_field}"""
+ if self._distance_mode == DistanceMode.RIGHT:
+ output_string = f"""data.{end} - FLOOR((data.region_start + data.region_end)/2) as distance_{position_field}"""
+ if self._distance_mode == DistanceMode.BOTH:
+ output_string = f"""data.{start} - FLOOR((data.region_start + data.region_end)/2) as start_distance_{position_field},
+ data.{end} - FLOOR((data.region_start + data.region_end)/2) as end_distance_{position_field}"""
+ transform_strings.append(output_string)
+ return ",".join(transform_strings)
+
+ def _get_transformed_schema(
+ self, data_frame: duckdb.DuckDBPyRelation, input_schema: GenomicDataSchema
+ ) -> GenomicDataSchema:
+ """Returns the schema of the transformed data."""
+ # construct schema
+ return QueryStepDataSchema(
+ columns=data_frame.columns,
+ position_fields=input_schema.get_position_fields(),
+ contact_order=input_schema.get_contact_order(),
+ binsize=input_schema.get_binsize(),
+ region_number=input_schema.get_region_number(),
+ half_window_size=input_schema.get_half_window_size(),
+ )
+
+ def __call__(self, genomic_data: GenomicData) -> GenomicData:
+ """Apply the transformation to the data"""
+ # get input schema
+ input_schema = genomic_data.get_schema()
+ # bring input to duckdb dataframe
+ if isinstance(genomic_data.data, duckdb.DuckDBPyRelation):
+ genomic_df = genomic_data.data
+ else:
+ genomic_df = duckdb.from_df(genomic_data.data, connection=DUCKDB_CONNECTION)
+ # construct transformation
+ transformed_df = genomic_df.set_alias("data").project(
+ self._create_transform_columns(genomic_df, input_schema)
+ )
+ return QueryPlan(
+ transformed_df, self._get_transformed_schema(transformed_df, input_schema)
+ )
+
+
+class QueryPlan:
+ """Result of a query"""
+
+ def __init__(
+ self,
+ data: Union[pd.DataFrame, duckdb.DuckDBPyRelation],
+ schema: GenomicDataSchema,
+ ) -> None:
+ self._data = data
+ self._schema = schema
+
+ @property
+ def data(self) -> Union[pd.DataFrame, duckdb.DuckDBPyRelation]:
+ """Returns the result as a dataframe object, either in memory or as a relation object"""
+ return self._data
+
+ def compute(self) -> pd.DataFrame:
+ """Loads the result into memory"""
+ if isinstance(self._data, duckdb.DuckDBPyRelation):
+ return self._data.to_df()
+ return self._data
+
+ def get_schema(self) -> GenomicDataSchema:
+ """Returns the schema of the result"""
+ return self._schema
+
+
+# pylint: disable=too-few-public-methods
+# this is a wrapper with one task, so it only has one method
+class Query:
+ """Basic query engine that runs a query plan on the data"""
+
+ def __init__(self, query_steps: List[QueryStep]) -> None:
+ self._query_steps = query_steps
+
+ def build(self, input_data: GenomicData) -> QueryPlan:
+ """Runs the query on the data and returns the result"""
+ # instantiate query result
+ query_plan = QueryPlan(input_data.data, input_data.get_schema())
+ # run query
+ for step in self._query_steps:
+ # validate schema
+ step.validate(query_plan.get_schema())
+ # apply step
+ query_plan = step(query_plan)
+ return query_plan
diff --git a/tests/fixtures/symmetry.py b/tests/conftest.py
similarity index 98%
rename from tests/fixtures/symmetry.py
rename to tests/conftest.py
index 02152f5..4bdc06a 100644
--- a/tests/fixtures/symmetry.py
+++ b/tests/conftest.py
@@ -1,8 +1,10 @@
"""Fixtures for testing symmetry.py"""
# pylint: disable=redefined-outer-name
-import pytest
-import pandas as pd
+from __future__ import annotations
+
import dask.dataframe as dd
+import pandas as pd
+import pytest
@pytest.fixture
@@ -23,7 +25,7 @@ def unlabelled_contacts_2d():
"mapping_quality_2": [10, 10, 10],
"align_score_2": [10, 10, 10],
"align_base_qscore_2": [10, 10, 10],
- }
+ },
)
@@ -56,7 +58,7 @@ def unlabelled_contacts_3d():
"mapping_quality_3": [10, 10, 5],
"align_score_3": [10, 10, 5],
"align_base_qscore_3": [10, 10, 5],
- }
+ },
)
@@ -83,7 +85,7 @@ def unlabelled_contacts_2d_flipped():
"mapping_quality_2": [10, 10, 15],
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
- }
+ },
)
@@ -111,7 +113,7 @@ def unlabelled_contacts_3d_flipped():
"mapping_quality_3": [10, 10, 15],
"align_score_3": [10, 10, 15],
"align_base_qscore_3": [10, 10, 15],
- }
+ },
)
@@ -135,7 +137,7 @@ def labelled_binary_contacts_2d():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["B", "A", "A"],
- }
+ },
)
@@ -159,7 +161,7 @@ def labelled_binary_contacts_2d_sorted():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["B", "B", "A"],
- }
+ },
)
@@ -190,7 +192,7 @@ def labelled_binary_contacts_3d():
"align_score_3": [10, 10, 15],
"align_base_qscore_3": [10, 10, 15],
"metadata_3": ["B", "A", "A"],
- }
+ },
)
@@ -221,7 +223,7 @@ def labelled_binary_contacts_3d_sorted():
"align_score_3": [10, 10, 15],
"align_base_qscore_3": [10, 10, 15],
"metadata_3": ["B", "B", "A"],
- }
+ },
)
@@ -245,7 +247,7 @@ def binary_contacts_not_equated_2d():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["B", "B", "A"],
- }
+ },
)
@@ -276,7 +278,7 @@ def binary_contacts_not_equated_3d():
"align_score_3": [10, 10, 15],
"align_base_qscore_3": [10, 10, 15],
"metadata_3": ["B", "B", "B"],
- }
+ },
)
@@ -314,7 +316,7 @@ def binary_contacts_not_equated_4d():
"align_score_4": [10, 10, 15],
"align_base_qscore_4": [10, 10, 15],
"metadata_4": ["B", "B", "B"],
- }
+ },
)
@@ -338,7 +340,7 @@ def binary_contacts_equated_2d():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["A", "B", "A"],
- }
+ },
)
@@ -369,7 +371,7 @@ def binary_contacts_equated_3d():
"align_score_3": [10, 10, 15],
"align_base_qscore_3": [10, 10, 15],
"metadata_3": ["B", "B", "A"],
- }
+ },
)
@@ -407,7 +409,7 @@ def binary_contacts_equated_4d():
"align_score_4": [10, 10, 15],
"align_base_qscore_4": [10, 10, 15],
"metadata_4": ["B", "B", "A"],
- }
+ },
)
@@ -431,7 +433,7 @@ def labelled_binary_contacts_2d_unflipped():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["B", "B", "A"],
- }
+ },
)
@@ -462,7 +464,7 @@ def labelled_binary_contacts_3d_unflipped():
"align_score_3": [10, 10, 15, 14],
"align_base_qscore_3": [10, 10, 15, 14],
"metadata_3": ["B", "B", "B", "A"],
- }
+ },
)
@@ -493,7 +495,7 @@ def labelled_binary_contacts_3d_unflipped_example2():
"align_score_3": [10, 10, 14],
"align_base_qscore_3": [10, 10, 14],
"metadata_3": ["B", "B", "A"],
- }
+ },
)
@@ -517,7 +519,7 @@ def labelled_binary_contacts_2d_flipped():
"align_score_2": [10, 10, 10],
"align_base_qscore_2": [10, 10, 10],
"metadata_2": ["B", "B", "A"],
- }
+ },
)
@@ -548,7 +550,7 @@ def labelled_binary_contacts_3d_flipped():
"align_score_3": [10, 10, 15, 20],
"align_base_qscore_3": [10, 10, 15, 20],
"metadata_3": ["B", "B", "B", "A"],
- }
+ },
)
@@ -579,7 +581,7 @@ def labelled_binary_contacts_3d_flipped_example2():
"align_score_3": [10, 10, 14],
"align_base_qscore_3": [10, 10, 14],
"metadata_3": ["B", "B", "A"],
- }
+ },
)
@@ -601,7 +603,7 @@ def unlabelled_contacts_diff_chrom_2d():
"mapping_quality_2": [10, 10, 10],
"align_score_2": [10, 10, 10],
"align_base_qscore_2": [10, 10, 10],
- }
+ },
)
@@ -629,7 +631,7 @@ def unlabelled_contacts_diff_chrom_3d():
"mapping_quality_3": [10, 10, 5],
"align_score_3": [10, 10, 5],
"align_base_qscore_3": [10, 10, 5],
- }
+ },
)
@@ -663,7 +665,7 @@ def unlabelled_contacts_diff_chrom_4d():
"mapping_quality_4": [10, 10, 10],
"align_score_4": [10, 10, 10],
"align_base_qscore_4": [10, 10, 10],
- }
+ },
)
@@ -697,7 +699,7 @@ def unlabelled_contacts_diff_chrom_4d_flipped():
"mapping_quality_4": [10, 10, 10],
"align_score_4": [10, 10, 10],
"align_base_qscore_4": [10, 10, 10],
- }
+ },
)
@@ -725,7 +727,7 @@ def unlabelled_contacts_diff_chrom_3d_flipped():
"mapping_quality_3": [10, 10, 15],
"align_score_3": [10, 10, 15],
"align_base_qscore_3": [10, 10, 15],
- }
+ },
)
@@ -747,7 +749,7 @@ def unlabelled_contacts_diff_chrom_2d_flipped():
"mapping_quality_2": [10, 10, 15],
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
- }
+ },
)
@@ -771,7 +773,7 @@ def labelled_binary_contacts_diff_chrom_2d():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["B", "B", "A"],
- }
+ },
)
@@ -795,7 +797,7 @@ def labelled_binary_contacts_diff_chrom_2d_flipped():
"align_score_2": [10, 10, 15],
"align_base_qscore_2": [10, 10, 15],
"metadata_2": ["B", "B", "A"],
- }
+ },
)
@@ -826,7 +828,7 @@ def labelled_binary_contacts_diff_chrom_3d():
"align_score_3": [10, 10, 15, 14],
"align_base_qscore_3": [10, 10, 15, 14],
"metadata_3": ["B", "B", "B", "A"],
- }
+ },
)
@@ -857,5 +859,5 @@ def labelled_binary_contacts_diff_chrom_3d_flipped():
"align_score_3": [10, 10, 15, 20],
"align_base_qscore_3": [10, 10, 15, 20],
"metadata_3": ["B", "B", "B", "A"],
- }
+ },
)
diff --git a/tests/contacts_tests/test_contacts.py b/tests/contacts_tests/test_contacts.py
index edb467c..effaaf1 100644
--- a/tests/contacts_tests/test_contacts.py
+++ b/tests/contacts_tests/test_contacts.py
@@ -1,20 +1,10 @@
"""Tests for the contacts module."""
-
# pylint: disable=redefined-outer-name
-import pytest
import pandas as pd
-import pandera as pa
-import numpy as np
-import dask.dataframe as dd
+import pytest
from spoc import contacts
-# pytlint: disable=unused-import
-from ..fixtures.symmetry import (
- unlabelled_contacts_2d,
- labelled_binary_contacts_2d_sorted,
-)
-
@pytest.fixture
def contact_manipulator():
diff --git a/tests/contacts_tests/test_symmetry.py b/tests/contacts_tests/test_symmetry.py
index cb7ebdf..0eea19f 100644
--- a/tests/contacts_tests/test_symmetry.py
+++ b/tests/contacts_tests/test_symmetry.py
@@ -1,45 +1,12 @@
"""Tests for dealing with symmetry flipping for labelled and unlabelled contacts."""
# pylint: disable=redefined-outer-name
# pylint: disable=unused-import
-
-import pytest
-import pandas as pd
-import pandera as pa
-import numpy as np
import dask.dataframe as dd
-from spoc.contacts import Contacts, ContactManipulator
-from ..fixtures.symmetry import (
- unlabelled_contacts_2d,
- unlabelled_contacts_2d_flipped,
- unlabelled_contacts_3d,
- unlabelled_contacts_3d_flipped,
- labelled_binary_contacts_2d,
- labelled_binary_contacts_2d_sorted,
- labelled_binary_contacts_3d,
- labelled_binary_contacts_3d_sorted,
- binary_contacts_not_equated_2d,
- binary_contacts_not_equated_3d,
- binary_contacts_not_equated_4d,
- binary_contacts_equated_2d,
- binary_contacts_equated_3d,
- binary_contacts_equated_4d,
- labelled_binary_contacts_2d_unflipped,
- labelled_binary_contacts_2d_flipped,
- labelled_binary_contacts_3d_unflipped,
- labelled_binary_contacts_3d_unflipped_example2,
- labelled_binary_contacts_3d_flipped,
- labelled_binary_contacts_3d_flipped_example2,
- unlabelled_contacts_diff_chrom_2d,
- unlabelled_contacts_diff_chrom_3d,
- unlabelled_contacts_diff_chrom_4d,
- unlabelled_contacts_diff_chrom_3d_flipped,
- unlabelled_contacts_diff_chrom_2d_flipped,
- unlabelled_contacts_diff_chrom_4d_flipped,
- labelled_binary_contacts_diff_chrom_2d,
- labelled_binary_contacts_diff_chrom_2d_flipped,
- labelled_binary_contacts_diff_chrom_3d,
- labelled_binary_contacts_diff_chrom_3d_flipped,
-)
+import pandas as pd
+import pytest
+
+from spoc.contacts import ContactManipulator
+from spoc.contacts import Contacts
@pytest.mark.parametrize(
diff --git a/tests/fragments_tests/test_fragments.py b/tests/fragments_tests/test_fragments.py
index 0cbbad6..4e35ce0 100644
--- a/tests/fragments_tests/test_fragments.py
+++ b/tests/fragments_tests/test_fragments.py
@@ -1,19 +1,13 @@
"""Tests for the fragments module."""
-
# pylint: disable=redefined-outer-name
-import pytest
-import pandas as pd
-import numpy as np
import dask.dataframe as dd
+import numpy as np
+import pandas as pd
+import pytest
+from spoc import contacts
from spoc import fragments
-# pytlint: disable=unused-import
-from ..fixtures.symmetry import (
- unlabelled_contacts_2d,
- labelled_binary_contacts_2d_sorted,
-)
-
@pytest.fixture
def triplet_expander():
diff --git a/tests/fragments_tests/test_labels.py b/tests/fragments_tests/test_labels.py
index 3a8a593..f2ca601 100644
--- a/tests/fragments_tests/test_labels.py
+++ b/tests/fragments_tests/test_labels.py
@@ -1,11 +1,10 @@
"""Tests for label functionality"""
# pylint: disable=redefined-outer-name
-
-import pytest
+import dask.dataframe as dd
+import numpy as np
import pandas as pd
import pandera as pa
-import numpy as np
-import dask.dataframe as dd
+import pytest
from spoc import fragments
diff --git a/tests/io_tests/test_io_contacts.py b/tests/io_tests/test_io_contacts.py
index 856b853..567f1ee 100644
--- a/tests/io_tests/test_io_contacts.py
+++ b/tests/io_tests/test_io_contacts.py
@@ -1,16 +1,18 @@
"""This file tests the io module for contacts"""
# pylint: disable=redefined-outer-name
-import tempfile
-import os
import json
+import os
import shutil
+import tempfile
from pathlib import Path
-import pytest
+
import dask.dataframe as dd
+import pytest
+
from spoc.contacts import Contacts
from spoc.io import FileManager
+from spoc.models.dataframe_models import DataMode
from spoc.models.file_parameter_models import ContactsParameters
-from ..fixtures.symmetry import unlabelled_contacts_2d, labelled_binary_contacts_2d
def _create_tmp_dir():
@@ -98,7 +100,7 @@ def test_read_contacts_as_pandas_df(example_contacts_w_metadata):
contacts_dir, expected_parameters, paths, dataframes = example_contacts_w_metadata
# read metadata
for path, expected, df in zip(paths, expected_parameters, dataframes):
- contacts = FileManager(use_dask=False).load_contacts(contacts_dir, expected)
+ contacts = FileManager().load_contacts(contacts_dir, expected)
assert contacts.get_global_parameters() == expected
assert contacts.data.equals(df)
@@ -108,7 +110,7 @@ def test_read_contacts_as_dask_df(example_contacts_w_metadata):
contacts_dir, expected_parameters, paths, dataframes = example_contacts_w_metadata
# read metadata
for path, expected, df in zip(paths, expected_parameters, dataframes):
- contacts = FileManager(use_dask=True).load_contacts(contacts_dir, expected)
+ contacts = FileManager(DataMode.DASK).load_contacts(contacts_dir, expected)
assert contacts.get_global_parameters() == expected
assert contacts.data.compute().equals(df)
@@ -306,7 +308,7 @@ def test_load_contacts_from_uri_fails_without_required_parameters(df, params, re
file_name = tmpdirname + "/" + "test.parquet"
FileManager().write_contacts(file_name, contacts)
# try loading without required parameters
- with pytest.raises(ValueError) as e:
+ with pytest.raises(ValueError):
Contacts.from_uri(file_name)
@@ -427,5 +429,5 @@ def test_load_contacts_from_uri_fails_with_ambiguous_specification(df, params, r
FileManager().write_contacts(file_name, contacts)
FileManager().write_contacts(file_name, contacts2)
# load contacts
- with pytest.raises(ValueError) as e:
+ with pytest.raises(ValueError):
Contacts.from_uri(file_name + "::" + uri)
diff --git a/tests/io_tests/test_io_pixels.py b/tests/io_tests/test_io_pixels.py
index dc4f09b..f4c622f 100644
--- a/tests/io_tests/test_io_pixels.py
+++ b/tests/io_tests/test_io_pixels.py
@@ -1,14 +1,17 @@
"""This file tests the io module for pixels"""
# pylint: disable=redefined-outer-name
-import tempfile
-import os
import json
+import os
import shutil
+import tempfile
from pathlib import Path
-import pytest
-import pandas as pd
+
import dask.dataframe as dd
+import pandas as pd
+import pytest
+
from spoc.io import FileManager
+from spoc.models.dataframe_models import DataMode
from spoc.models.file_parameter_models import PixelParameters
from spoc.pixels import Pixels
@@ -119,24 +122,12 @@ def test_read_pixels_metadata_json_fails_gracefully():
assert e.value == "Metadata file not found at bad_path/metadata.json"
-def test_read_pixels_as_path(example_pixels_w_metadata):
- """Test reading pixels metadata json file"""
- pixels_dir, expected_parameters, paths, _ = example_pixels_w_metadata
- # read metadata
- for path, expected in zip(paths, expected_parameters):
- pixels = FileManager().load_pixels(pixels_dir, expected, load_dataframe=False)
- assert pixels.path == path
- assert pixels.get_global_parameters() == expected
-
-
def test_read_pixels_as_pandas_df(example_pixels_w_metadata):
"""Test reading pixels metadata json file"""
pixels_dir, expected_parameters, paths, dataframes = example_pixels_w_metadata
# read metadata
for path, expected, df in zip(paths, expected_parameters, dataframes):
- pixels = FileManager(use_dask=False).load_pixels(
- pixels_dir, expected, load_dataframe=True
- )
+ pixels = FileManager(DataMode.PANDAS).load_pixels(pixels_dir, expected)
assert pixels.get_global_parameters() == expected
assert pixels.data.equals(df)
@@ -146,9 +137,7 @@ def test_read_pixels_as_dask_df(example_pixels_w_metadata):
pixels_dir, expected_parameters, paths, dataframes = example_pixels_w_metadata
# read metadata
for path, expected, df in zip(paths, expected_parameters, dataframes):
- pixels = FileManager(use_dask=True).load_pixels(
- pixels_dir, expected, load_dataframe=True
- )
+ pixels = FileManager(DataMode.DASK).load_pixels(pixels_dir, expected)
assert pixels.get_global_parameters() == expected
assert pixels.data.compute().equals(df)
@@ -282,7 +271,7 @@ def test_load_pixels_from_uri_fails_without_required_parameters(df, params, requ
file_name = tmpdirname + "/" + "test.parquet"
FileManager().write_pixels(file_name, pixels)
# try loading without required parameters
- with pytest.raises(ValueError) as e:
+ with pytest.raises(ValueError):
Pixels.from_uri(file_name)
@@ -418,5 +407,5 @@ def test_load_pixels_from_uri_fails_with_ambiguous_specification(df, params, req
FileManager().write_pixels(file_name, pixels)
FileManager().write_pixels(file_name, pixels2)
# load pixels
- with pytest.raises(ValueError) as e:
+ with pytest.raises(ValueError):
Pixels.from_uri(file_name + "::" + uri)
diff --git a/tests/fixtures/__init__.py b/tests/query_engine/__init__.py
similarity index 100%
rename from tests/fixtures/__init__.py
rename to tests/query_engine/__init__.py
diff --git a/tests/query_engine/conftest.py b/tests/query_engine/conftest.py
new file mode 100644
index 0000000..e75fdf7
--- /dev/null
+++ b/tests/query_engine/conftest.py
@@ -0,0 +1,149 @@
+"""Shared fixtures for query engine tests."""
+from __future__ import annotations
+
+import pandas as pd
+import pytest
+
+from spoc.contacts import Contacts
+from spoc.pixels import Pixels
+from spoc.query_engine import Anchor
+from spoc.query_engine import Overlap
+
+
+@pytest.fixture(name="example_2d_df")
+def example_2d_df_fixture():
+ """Example 2d contacts"""
+ return pd.DataFrame(
+ {
+ "chrom_1": ["chr1", "chr1", "chr1", "chr1"],
+ "start_1": [100, 100, 750, 400],
+ "end_1": [200, 200, 780, 500],
+ "mapping_quality_1": [30, 30, 30, 30],
+ "align_score_1": [100, 100, 100, 100],
+ "align_base_qscore_1": [100, 100, 100, 100],
+ "chrom_2": ["chr1", "chr1", "chr1", "chr1"],
+ "start_2": [300, 100, 300, 750],
+ "end_2": [400, 200, 400, 780],
+ "mapping_quality_2": [30, 30, 30, 30],
+ "align_score_2": [100, 100, 100, 100],
+ "align_base_qscore_2": [100, 100, 100, 100],
+ # read name serves as id
+ "read_name": ["read1", "read2", "read3", "read4"],
+ "read_length": [100, 100, 100, 100],
+ },
+ )
+
+
+@pytest.fixture(name="pixel_dataframe")
+def pixel_dataframe_fixture():
+ """A dataframe containing pixels"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1"] * 4,
+ "start_1": [180, 180, 750, 400],
+ "start_2": [300, 180, 300, 750],
+ "count": [1, 2, 3, 4], # contact count is id
+ },
+ )
+
+
+@pytest.fixture(name="single_region")
+def single_region_fixture():
+ """Single region"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1"],
+ "start": [150],
+ "end": [200],
+ },
+ )
+
+
+@pytest.fixture(name="single_region_2")
+def single_region_2_fixture():
+ """Single region"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1"],
+ "start": [700],
+ "end": [800],
+ },
+ )
+
+
+@pytest.fixture(name="single_region_3")
+def single_region_3_fixture():
+ """Single region"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1"],
+ "start": [750],
+ "end": [850],
+ },
+ )
+
+
+@pytest.fixture(name="multi_region")
+def multi_region_fixture():
+ """Multi region"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1", "chr1"],
+ "start": [150, 700],
+ "end": [200, 800],
+ },
+ )
+
+
+@pytest.fixture(name="multi_region_2")
+def multi_region_2_fixture():
+ """Multi region"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1", "chr1"],
+ "start": [150, 180],
+ "end": [200, 220],
+ },
+ )
+
+
+@pytest.fixture(name="contacts_without_regions")
+def contacts_without_regions_fixture(example_2d_df):
+ """Example 2d contacts"""
+ return Contacts(example_2d_df)
+
+
+@pytest.fixture(name="pixels_without_regions")
+def pixels_wihtout_regions_fixture(pixel_dataframe):
+ """Pixels without regions"""
+ return Pixels(pixel_dataframe, number_fragments=2, binsize=10)
+
+
+@pytest.fixture(name="contacts_with_single_region")
+def contacts_with_single_region_fixture(contacts_without_regions, single_region):
+ """Contacts with single region"""
+ return Overlap(single_region, anchor_mode=Anchor(mode="ANY"))(
+ contacts_without_regions,
+ )
+
+
+@pytest.fixture(name="contacts_with_multiple_regions")
+def contacts_with_multiple_regions_fixture(contacts_without_regions, multi_region):
+ """Contacts with multiple regions"""
+ return Overlap(multi_region, anchor_mode=Anchor(mode="ANY"))(
+ contacts_without_regions,
+ )
+
+
+@pytest.fixture(name="pixels_with_single_region")
+def pixels_with_single_region_fixture(pixels_without_regions, single_region):
+ """Pixels with single region"""
+ return Overlap(single_region, anchor_mode=Anchor(mode="ANY"))(
+ pixels_without_regions,
+ )
+
+
+@pytest.fixture(name="pixels_with_multiple_regions")
+def pixels_with_multiple_regions_fixture(pixels_without_regions, multi_region):
+ """Pixels with multiple regions"""
+ return Overlap(multi_region, anchor_mode=Anchor(mode="ANY"))(pixels_without_regions)
diff --git a/tests/query_engine/test_contact_selection.py b/tests/query_engine/test_contact_selection.py
new file mode 100644
index 0000000..daae42d
--- /dev/null
+++ b/tests/query_engine/test_contact_selection.py
@@ -0,0 +1,300 @@
+"""Tests for contact selection"""
+import dask.dataframe as dd
+import duckdb
+import pytest
+
+from spoc.contacts import Contacts
+from spoc.io import DUCKDB_CONNECTION
+from spoc.query_engine import Anchor
+from spoc.query_engine import Overlap
+from spoc.query_engine import Query
+
+
+@pytest.fixture(name="example_2d_contacts_pandas")
+def example_2d_contacts_pandas_fixture(example_2d_df):
+ """Example 2d contacts"""
+ return Contacts(example_2d_df)
+
+
+@pytest.fixture(name="example_2d_contacts_dask")
+def example_2d_contacts_dask_fixture(example_2d_df):
+ """Example 2d contacts"""
+ return Contacts(dd.from_pandas(example_2d_df, npartitions=2))
+
+
+@pytest.fixture(name="example_2d_contacts_duckdb")
+def example_2d_contacts_duckdb_fixture(example_2d_df):
+ """Example 2d contacts"""
+ return Contacts(duckdb.from_df(example_2d_df, connection=DUCKDB_CONNECTION))
+
+
+# happy path
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_no_filter_returns_all_contacts(contact_fixture, request):
+ """Test that no filter returns all contacts"""
+ contacts = request.getfixturevalue(contact_fixture)
+ query = Query(query_steps=[])
+ result = query.build(contacts)
+ assert result.compute().shape[0] == 4
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_any_anchor_region_returns_correct_contacts(
+ contact_fixture, single_region, request
+):
+ """Test that any anchor region returns correct contacts"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [Overlap(regions=single_region, anchor_mode=Anchor(mode="ANY"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == 2
+ assert sorted(result.compute().read_name.tolist()) == sorted(["read1", "read2"])
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_all_anchor_regions_returns_correct_contacts(
+ contact_fixture, single_region, request
+):
+ """Test that all anchor regions returns correct contacts"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [Overlap(regions=single_region, anchor_mode=Anchor(mode="ALL"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == 1
+ assert sorted(result.compute().read_name.tolist()) == sorted(["read2"])
+
+
+@pytest.mark.parametrize(
+ "contact_fixture,anchors,expected_reads",
+ [
+ (source_data, anchors, expected_reads)
+ for source_data, anchors, expected_reads in zip(
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ]
+ * 3,
+ [[1]] * 3 + [[2]] * 3 + [[1, 2]] * 3,
+ [["read3"]] * 3 + [["read4"]] * 3 + [[]] * 3,
+ )
+ ],
+)
+def test_specific_anchor_regions_returns_correct_contacts(
+ contact_fixture, anchors, expected_reads, single_region_2, request
+):
+ """Test that specific anchor regions returns correct contacts"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [
+ Overlap(
+ regions=single_region_2, anchor_mode=Anchor(mode="ALL", anchors=anchors)
+ )
+ ]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == len(expected_reads)
+ assert sorted(result.compute().read_name.tolist()) == sorted(expected_reads)
+
+
+@pytest.mark.parametrize(
+ "contact_fixture,anchors,expected_reads",
+ [
+ (source_data, anchors, expected_reads)
+ for source_data, anchors, expected_reads in zip(
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ]
+ * 3,
+ [[1]] * 3 + [[2]] * 3 + [[1, 2]] * 3,
+ [["read3"]] * 3 + [["read4"]] * 3 + [[]] * 3,
+ )
+ ],
+)
+def test_specific_anchor_regions_returns_correct_contacts_point_region(
+ contact_fixture, anchors, expected_reads, single_region_3, request
+):
+ """Test that specific anchor regions returns correct contacts, when
+ the region is a point region and windowsize is passed"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [
+ Overlap(
+ regions=single_region_3,
+ anchor_mode=Anchor(mode="ALL", anchors=anchors),
+ half_window_size=50,
+ )
+ ]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == len(expected_reads)
+ assert sorted(result.compute().read_name.tolist()) == sorted(expected_reads)
+ assert result.get_schema().get_half_window_size() == 50
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_any_anchor_region_returns_correct_contacts_multi_region(
+ contact_fixture, multi_region, request
+):
+ """Test that any anchor region returns correct contacts"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [Overlap(regions=multi_region, anchor_mode=Anchor(mode="ANY"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == 4
+ assert sorted(result.compute().read_name.tolist()) == sorted(
+ ["read1", "read2", "read3", "read4"]
+ )
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_all_anchor_regions_returns_correct_contacts_multi_region(
+ contact_fixture, multi_region, request
+):
+ """Test that all anchor regions returns correct contacts"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [Overlap(regions=multi_region, anchor_mode=Anchor(mode="ALL"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == 1
+ assert sorted(result.compute().read_name.tolist()) == sorted(["read2"])
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_contacts_duplicated_for_multiple_overlapping_regions(
+ contact_fixture, multi_region_2, request
+):
+ """
+ This test verifies that when multiple overlapping regions are specified as anchor regions,
+ the query returns duplicated contacts for each overlapping region.
+ """
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [Overlap(regions=multi_region_2, anchor_mode=Anchor(mode="ALL"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == 2
+ assert sorted(result.compute().read_name.tolist()) == sorted(["read2", "read2"])
+
+
+@pytest.mark.parametrize(
+ "contact_fixture,anchors,expected_reads",
+ [
+ (source_data, anchors, expected_reads)
+ for source_data, anchors, expected_reads in zip(
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ]
+ * 3,
+ [[1]] * 3 + [[2]] * 3 + [[1, 2]] * 3,
+ [["read1", "read2", "read3"]] * 3
+ + [["read2", "read4"]] * 3
+ + [["read2"]] * 3,
+ )
+ ],
+)
+def test_specific_anchor_regions_returns_correct_contacts_multi_region(
+ contact_fixture, anchors, expected_reads, multi_region, request
+):
+ """Test that specific anchor regions returns correct contacts"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [
+ Overlap(regions=multi_region, anchor_mode=Anchor(mode="ALL", anchors=anchors))
+ ]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(contacts)
+ # test
+ assert result.compute().shape[0] == len(expected_reads)
+ assert sorted(result.compute().read_name.tolist()) == sorted(expected_reads)
+
+
+# validation problems
+
+
+@pytest.mark.parametrize(
+ "contact_fixture",
+ [
+ "example_2d_contacts_pandas",
+ "example_2d_contacts_dask",
+ "example_2d_contacts_duckdb",
+ ],
+)
+def test_specific_anchor_region_not_in_contacts_raises_error(
+ contact_fixture, single_region, request
+):
+ """Test that specific anchor region not in contacts raises error"""
+ # setup
+ contacts = request.getfixturevalue(contact_fixture)
+ query_plan = [
+ Overlap(regions=single_region, anchor_mode=Anchor(mode="ALL", anchors=[3]))
+ ]
+ with pytest.raises(ValueError):
+ query = Query(query_steps=query_plan)
+ query.build(contacts)
diff --git a/tests/query_engine/test_distance_aggregation.py b/tests/query_engine/test_distance_aggregation.py
new file mode 100644
index 0000000..c77ae86
--- /dev/null
+++ b/tests/query_engine/test_distance_aggregation.py
@@ -0,0 +1,502 @@
+"""Tests for the aggregation functions in the query engine."""
+from itertools import product
+
+import numpy as np
+import pandas as pd
+import pytest
+
+from spoc.pixels import Pixels
+from spoc.query_engine import AggregationFunction
+from spoc.query_engine import Anchor
+from spoc.query_engine import DistanceAggregation
+from spoc.query_engine import DistanceMode
+from spoc.query_engine import DistanceTransformation
+from spoc.query_engine import Overlap
+from spoc.query_engine import Query
+
+
+@pytest.fixture(name="pixels_with_distance")
+def pixels_with_distance_fixture(pixels_with_single_region):
+ """Pixels with single region"""
+ return DistanceTransformation()(pixels_with_single_region)
+
+
+@pytest.fixture(name="complete_synthetic_pixels_df")
+def complete_synthetic_pixels_df_fixture():
+ """Pixels that span two regions densely"""
+ np.random.seed(42)
+ # genomic region_1
+ pixels_1 = [
+ {
+ "chrom": tup[0],
+ "start_1": tup[1],
+ "start_2": tup[2],
+ "start_3": tup[3],
+ "count": np.random.randint(0, 10),
+ }
+ for tup in product(
+ ["chr1"],
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ )
+ ]
+ # genomic region_2
+ pixels_2 = [
+ {
+ "chrom": tup[0],
+ "start_1": tup[1],
+ "start_2": tup[2],
+ "start_3": tup[3],
+ "count": np.random.randint(0, 10),
+ }
+ for tup in product(
+ ["chr2"],
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ np.arange(900_000, 1_150_000, 50_000),
+ )
+ ]
+ return pd.concat((pd.DataFrame(pixels_1), pd.DataFrame(pixels_2)))
+
+
+@pytest.fixture(name="incomplete_synthetic_pixels_df")
+def incomplete_synthetic_pixels_df_fixture():
+ """Pixels that span two regions sparsely"""
+ np.random.seed(42)
+ # genomic region 1
+ pixels_1 = [
+ {
+ "chrom": tup[0],
+ "start_1": tup[1],
+ "start_2": tup[2],
+ "start_3": tup[3],
+ "count": np.random.randint(0, 10),
+ }
+ for tup in product(
+ ["chr1"],
+ np.arange(900_000, 1_000_000, 50_000),
+ np.arange(900_000, 1_000_000, 50_000),
+ np.arange(900_000, 1_000_000, 50_000),
+ )
+ ]
+ # genomic region_2
+ pixels_2 = [
+ {
+ "chrom": tup[0],
+ "start_1": tup[1],
+ "start_2": tup[2],
+ "start_3": tup[3],
+ "count": np.random.randint(0, 10),
+ }
+ for tup in product(
+ ["chr2"],
+ np.arange(1_000_000, 1_150_000, 50_000),
+ np.arange(1_000_000, 1_150_000, 50_000),
+ np.arange(1_000_000, 1_150_000, 50_000),
+ )
+ ]
+ return pd.concat((pd.DataFrame(pixels_1), pd.DataFrame(pixels_2)))
+
+
+@pytest.fixture(name="incomplete_synthetic_pixels_dense_df")
+def incomplete_synthetic_pixels_dense_df_fixture(
+ complete_synthetic_pixels_df, incomplete_synthetic_pixels_df
+):
+ """Pixels that span two regions sparsely
+ with missing pixels filled with 0."""
+ return incomplete_synthetic_pixels_df.merge(
+ complete_synthetic_pixels_df[["chrom", "start_1", "start_2", "start_3"]],
+ on=["chrom", "start_1", "start_2", "start_3"],
+ how="outer",
+ ).fillna(0)
+
+
+@pytest.fixture
+def single_region():
+ """Single region"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1"],
+ "start": [900_000],
+ "end": [1_100_000],
+ },
+ index=[0],
+ )
+
+
+@pytest.fixture
+def single_region_not_binaligned():
+ """Single region that is not aligned to the bin size"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1"],
+ "start": [920_000],
+ "end": [1_120_000],
+ },
+ index=[0],
+ )
+
+
+@pytest.fixture
+def two_regions():
+ """Two regions"""
+ return pd.DataFrame(
+ {
+ "chrom": ["chr1", "chr2"],
+ "start": [900_000, 900_000],
+ "end": [1_100_000, 1_100_000],
+ }
+ )
+
+
+@pytest.mark.parametrize(
+ "genomic_data_fixture",
+ [
+ "contacts_without_regions",
+ "pixels_without_regions",
+ "contacts_with_single_region",
+ "contacts_with_multiple_regions",
+ "pixels_with_single_region",
+ "pixels_with_multiple_regions",
+ ],
+)
+def test_input_wo_distance_rejected(genomic_data_fixture, request):
+ """Test that the validation fails for incorrect inputs."""
+ genomic_data = request.getfixturevalue(genomic_data_fixture)
+ with pytest.raises(ValueError):
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="value", function=AggregationFunction.COUNT
+ ),
+ ],
+ )
+ query.build(genomic_data)
+
+
+def test_input_wo_data_column_rejected(pixels_with_distance):
+ """Test that the validation fails for incorrect inputs."""
+ with pytest.raises(ValueError):
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="test_column_does_no_exist",
+ function=AggregationFunction.AVG,
+ ),
+ ],
+ )
+ query.build(pixels_with_distance)
+
+
+def test_contacts_rejected(contacts_with_single_region):
+ """Test that the validation fails for incorrect inputs."""
+ with pytest.raises(ValueError):
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="value", function=AggregationFunction.COUNT
+ ),
+ ],
+ )
+ query.build(contacts_with_single_region)
+
+
+@pytest.mark.parametrize(
+ "aggregation_spoc, aggregation_pandas, region_fixture",
+ [
+ (AggregationFunction.COUNT, "count", "single_region"),
+ (AggregationFunction.COUNT, "count", "two_regions"),
+ (AggregationFunction.COUNT, "count", "single_region_not_binaligned"),
+ ("COUNT", "count", "single_region_not_binaligned"),
+ (AggregationFunction.SUM, "sum", "single_region"),
+ (AggregationFunction.SUM, "sum", "two_regions"),
+ (AggregationFunction.SUM, "sum", "single_region_not_binaligned"),
+ ("SUM", "sum", "single_region_not_binaligned"),
+ (AggregationFunction.AVG, "mean", "single_region"),
+ (AggregationFunction.AVG, "mean", "two_regions"),
+ (AggregationFunction.AVG, "mean", "single_region_not_binaligned"),
+ ("AVG", "mean", "single_region_not_binaligned"),
+ ],
+)
+def test_aggregations_on_dense_input(
+ complete_synthetic_pixels_df,
+ aggregation_spoc,
+ aggregation_pandas,
+ region_fixture,
+ request,
+):
+ """Test sum aggregation on dense input."""
+ # setup (pixels here are points to make the test easier)
+ pixels = Pixels(complete_synthetic_pixels_df, binsize=50_000, number_fragments=3)
+ region = request.getfixturevalue(region_fixture)
+ mapped_pixels = Query(
+ query_steps=[
+ Overlap(region, anchor_mode=Anchor(mode="ANY")),
+ DistanceTransformation(distance_mode=DistanceMode.LEFT),
+ ],
+ ).build(pixels)
+ mapped_pixels_df = mapped_pixels.compute()
+ cat_dtype = pd.CategoricalDtype(range(-100_000, 150_000, 50_000))
+ mapped_pixels_df["distance_1"] = mapped_pixels_df["distance_1"].astype(cat_dtype)
+ mapped_pixels_df["distance_2"] = mapped_pixels_df["distance_2"].astype(cat_dtype)
+ mapped_pixels_df["distance_3"] = mapped_pixels_df["distance_3"].astype(cat_dtype)
+ expected_aggregation = (
+ mapped_pixels_df.groupby(["distance_1", "distance_2", "distance_3"])
+ .agg(count=("count", aggregation_pandas))
+ .astype(float)
+ .reset_index()
+ .rename(columns={"count": f"count_{aggregation_pandas}"})
+ .sort_values(["distance_1", "distance_2", "distance_3"])
+ )
+ # execute aggregation
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="count", function=aggregation_spoc, densify_output=False
+ ),
+ ],
+ )
+ actual_aggregation = query.build(mapped_pixels).compute()
+ # test
+ np.testing.assert_array_almost_equal(
+ expected_aggregation.values,
+ actual_aggregation.values,
+ )
+
+
+@pytest.mark.parametrize(
+ "aggregation_spoc, aggregation_pandas, region_fixture",
+ [
+ (AggregationFunction.COUNT, "count", "single_region"),
+ (AggregationFunction.COUNT, "count", "two_regions"),
+ (AggregationFunction.COUNT, "count", "single_region_not_binaligned"),
+ ("COUNT", "count", "single_region_not_binaligned"),
+ (AggregationFunction.SUM, "sum", "single_region"),
+ (AggregationFunction.SUM, "sum", "two_regions"),
+ (AggregationFunction.SUM, "sum", "single_region_not_binaligned"),
+ ("SUM", "sum", "single_region_not_binaligned"),
+ (AggregationFunction.AVG, "mean", "single_region"),
+ (AggregationFunction.AVG, "mean", "two_regions"),
+ (AggregationFunction.AVG, "mean", "single_region_not_binaligned"),
+ ("AVG", "mean", "single_region_not_binaligned"),
+ ],
+)
+def test_aggregations_on_dense_input_with_reduced_dimensionality(
+ complete_synthetic_pixels_df,
+ aggregation_spoc,
+ aggregation_pandas,
+ region_fixture,
+ request,
+):
+ """Test sum aggregation on dense input."""
+ # setup (pixels here are points to make the test easier)
+ pixels = Pixels(complete_synthetic_pixels_df, binsize=50_000, number_fragments=3)
+ region = request.getfixturevalue(region_fixture)
+ mapped_pixels = Query(
+ query_steps=[
+ Overlap(region, anchor_mode=Anchor(mode="ANY")),
+ DistanceTransformation(distance_mode=DistanceMode.LEFT),
+ ],
+ ).build(pixels)
+ mapped_pixels_df = mapped_pixels.compute()
+ cat_dtype = pd.CategoricalDtype(range(-100_000, 150_000, 50_000))
+ mapped_pixels_df["distance_1"] = mapped_pixels_df["distance_1"].astype(cat_dtype)
+ mapped_pixels_df["distance_2"] = mapped_pixels_df["distance_2"].astype(cat_dtype)
+ expected_aggregation = (
+ mapped_pixels_df.groupby(["distance_1", "distance_2"])
+ .agg(count=("count", aggregation_pandas))
+ .astype(float)
+ .reset_index()
+ .rename(columns={"count": f"count_{aggregation_pandas}"})
+ .sort_values(["distance_1", "distance_2"])
+ )
+ # execute aggregation
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="count",
+ function=aggregation_spoc,
+ densify_output=False,
+ position_list=[1, 2],
+ ),
+ ],
+ )
+ actual_aggregation = query.build(mapped_pixels).compute()
+ # test
+ np.testing.assert_array_almost_equal(
+ expected_aggregation.values,
+ actual_aggregation.values,
+ )
+
+
+# pylint: disable=too-many-arguments
+@pytest.mark.parametrize(
+ "aggregation_spoc, aggregation_pandas, region_fixture",
+ [
+ (AggregationFunction.COUNT, "count", "single_region"),
+ (AggregationFunction.COUNT, "count", "two_regions"),
+ (AggregationFunction.COUNT, "count", "single_region_not_binaligned"),
+ (AggregationFunction.SUM, "sum", "single_region"),
+ (AggregationFunction.SUM, "sum", "two_regions"),
+ (AggregationFunction.SUM, "sum", "single_region_not_binaligned"),
+ (AggregationFunction.AVG, "mean", "single_region"),
+ (AggregationFunction.AVG, "mean", "two_regions"),
+ (AggregationFunction.AVG, "mean", "single_region_not_binaligned"),
+ (AggregationFunction.AVG_WITH_EMPTY, "mean", "single_region"),
+ (AggregationFunction.AVG_WITH_EMPTY, "mean", "two_regions"),
+ (AggregationFunction.AVG_WITH_EMPTY, "mean", "single_region_not_binaligned"),
+ ],
+)
+def test_aggregations_on_sparse_input(
+ incomplete_synthetic_pixels_df,
+ incomplete_synthetic_pixels_dense_df,
+ aggregation_spoc,
+ aggregation_pandas,
+ region_fixture,
+ request,
+):
+ """Test sum aggregation on dense input."""
+ # setup
+ incomplete_pixels = Pixels(
+ incomplete_synthetic_pixels_df, binsize=50_000, number_fragments=3
+ )
+ incomplete_dense_pixels = Pixels(
+ incomplete_synthetic_pixels_dense_df, binsize=50_000, number_fragments=3
+ )
+ query_plan = Query(
+ query_steps=[
+ Overlap(
+ request.getfixturevalue(region_fixture), anchor_mode=Anchor(mode="ANY")
+ ),
+ DistanceTransformation(distance_mode=DistanceMode.LEFT),
+ ],
+ )
+ mapped_pixels = query_plan.build(incomplete_pixels)
+ mapped_incomplete_dense_pixels_df = query_plan.build(
+ incomplete_dense_pixels
+ ).compute()
+ if aggregation_spoc == AggregationFunction.AVG_WITH_EMPTY:
+ # when we test the AVG_WITH_EMPTY function, we need to use the dense pixels
+ # where missing values with 0 count are filled in
+ pixel_frame_for_expected = mapped_incomplete_dense_pixels_df
+ else:
+ pixel_frame_for_expected = mapped_pixels.compute()
+ pixel_frame_for_expected["distance_1"] = pixel_frame_for_expected[
+ "distance_1"
+ ].astype(pd.CategoricalDtype(range(-100_000, 150_000, 50_000)))
+ pixel_frame_for_expected["distance_2"] = pixel_frame_for_expected[
+ "distance_2"
+ ].astype(pd.CategoricalDtype(range(-100_000, 150_000, 50_000)))
+ pixel_frame_for_expected["distance_3"] = pixel_frame_for_expected[
+ "distance_3"
+ ].astype(pd.CategoricalDtype(range(-100_000, 150_000, 50_000)))
+ expected_aggregation = (
+ pixel_frame_for_expected.groupby(["distance_1", "distance_2", "distance_3"])
+ .agg(count=("count", aggregation_pandas))
+ .astype(float)
+ .reset_index()
+ .rename(columns={"count": f"count_{aggregation_pandas}"})
+ .sort_values(["distance_1", "distance_2", "distance_3"])
+ )
+ # execute aggregation
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="count", function=aggregation_spoc, densify_output=True
+ ),
+ ],
+ )
+ # test
+ np.testing.assert_array_almost_equal(
+ expected_aggregation.values, query.build(mapped_pixels).compute()
+ )
+
+
+# pylint: disable=too-many-arguments
+@pytest.mark.parametrize(
+ "aggregation_spoc, aggregation_pandas, region_fixture",
+ [
+ (AggregationFunction.COUNT, "count", "single_region"),
+ (AggregationFunction.COUNT, "count", "two_regions"),
+ (AggregationFunction.COUNT, "count", "single_region_not_binaligned"),
+ (AggregationFunction.SUM, "sum", "single_region"),
+ (AggregationFunction.SUM, "sum", "two_regions"),
+ (AggregationFunction.SUM, "sum", "single_region_not_binaligned"),
+ (AggregationFunction.AVG, "mean", "single_region"),
+ (AggregationFunction.AVG, "mean", "two_regions"),
+ (AggregationFunction.AVG, "mean", "single_region_not_binaligned"),
+ # for aggregation function with empty, we need to sum up and divide by region number
+ # just taking the mean with take the mean with respect to every triplet pixel
+ (AggregationFunction.AVG_WITH_EMPTY, lambda s: s.sum(), "single_region"),
+ (AggregationFunction.AVG_WITH_EMPTY, lambda s: s.sum() / 2, "two_regions"),
+ (
+ AggregationFunction.AVG_WITH_EMPTY,
+ lambda s: s.sum(),
+ "single_region_not_binaligned",
+ ),
+ ],
+)
+def test_aggregations_on_sparse_input_with_reduced_dimensionality(
+ incomplete_synthetic_pixels_df,
+ incomplete_synthetic_pixels_dense_df,
+ aggregation_spoc,
+ aggregation_pandas,
+ region_fixture,
+ request,
+):
+ """Test aggregation on sparse input with reduced dimensionality."""
+ # setup
+ incomplete_pixels = Pixels(
+ incomplete_synthetic_pixels_df, binsize=50_000, number_fragments=3
+ )
+ incomplete_dense_pixels = Pixels(
+ incomplete_synthetic_pixels_dense_df, binsize=50_000, number_fragments=3
+ )
+ query_plan = Query(
+ query_steps=[
+ Overlap(
+ request.getfixturevalue(region_fixture), anchor_mode=Anchor(mode="ANY")
+ ),
+ DistanceTransformation(distance_mode=DistanceMode.LEFT),
+ ],
+ )
+ mapped_pixels = query_plan.build(incomplete_pixels)
+ mapped_incomplete_dense_pixels_df = query_plan.build(
+ incomplete_dense_pixels
+ ).compute()
+ if aggregation_spoc == AggregationFunction.AVG_WITH_EMPTY:
+ # when we test the AVG_WITH_EMPTY function, we need to use the dense pixels
+ # where missing values with 0 count are filled in
+ pixel_frame_for_expected = mapped_incomplete_dense_pixels_df
+ else:
+ pixel_frame_for_expected = mapped_pixels.compute()
+ pixel_frame_for_expected["distance_1"] = pixel_frame_for_expected[
+ "distance_1"
+ ].astype(pd.CategoricalDtype(range(-100_000, 150_000, 50_000)))
+ pixel_frame_for_expected["distance_2"] = pixel_frame_for_expected[
+ "distance_2"
+ ].astype(pd.CategoricalDtype(range(-100_000, 150_000, 50_000)))
+ expected_aggregation = (
+ pixel_frame_for_expected.groupby(["distance_1", "distance_2"])
+ .agg(count=("count", aggregation_pandas))
+ .astype(float)
+ .reset_index()
+ .rename(columns={"count": f"count_{aggregation_pandas}"})
+ .sort_values(["distance_1", "distance_2"])
+ )
+ # execute aggregation
+ query = Query(
+ query_steps=[
+ DistanceAggregation(
+ value_column="count",
+ function=aggregation_spoc,
+ densify_output=True,
+ position_list=[1, 2],
+ ),
+ ],
+ )
+ # test
+ np.testing.assert_array_almost_equal(
+ expected_aggregation.values, query.build(mapped_pixels).compute()
+ )
diff --git a/tests/query_engine/test_distance_transformation.py b/tests/query_engine/test_distance_transformation.py
new file mode 100644
index 0000000..2278006
--- /dev/null
+++ b/tests/query_engine/test_distance_transformation.py
@@ -0,0 +1,125 @@
+"""Tests for transformations"""
+from __future__ import annotations
+
+import numpy as np
+import pytest
+
+from spoc.query_engine import DistanceMode
+from spoc.query_engine import DistanceTransformation
+from spoc.query_engine import Query
+
+
+@pytest.mark.parametrize(
+ "genomic_data_fixture",
+ [
+ "contacts_without_regions",
+ "pixels_without_regions",
+ ],
+)
+def test_incompatible_input_rejected(genomic_data_fixture, request):
+ """Tests that incompatible input raises a ValueError"""
+ genomic_data = request.getfixturevalue(genomic_data_fixture)
+ with pytest.raises(ValueError):
+ query = Query(
+ query_steps=[
+ DistanceTransformation(DistanceMode.MIDPOINT),
+ ],
+ )
+ query.build(genomic_data)
+
+
+@pytest.mark.parametrize(
+ "genomic_data_fixture",
+ ["contacts_with_single_region", "contacts_with_multiple_regions"],
+)
+def test_distance_calculated_correctly_contacts(genomic_data_fixture, request):
+ """Tests that the distance is calculated correctly for contacts"""
+ genomic_data = request.getfixturevalue(genomic_data_fixture)
+ query = Query(
+ query_steps=[
+ DistanceTransformation(DistanceMode.MIDPOINT),
+ ],
+ )
+ result = query.build(genomic_data).compute()
+ # check that the distance is correct
+ region_midpoints = (result["region_start"] + result["region_end"]) // 2
+ position_1_midpoint = (result["start_1"] + result["end_1"]) // 2
+ position_2_midpoint = (result["start_2"] + result["end_2"]) // 2
+ assert np.allclose(
+ position_1_midpoint - region_midpoints,
+ result["distance_1"],
+ )
+ assert np.allclose(
+ position_2_midpoint - region_midpoints,
+ result["distance_2"],
+ )
+
+
+@pytest.mark.parametrize(
+ "genomic_data_fixture",
+ ["pixels_with_single_region", "pixels_with_multiple_regions"],
+)
+def test_distance_midpoint_rejected_pixels(genomic_data_fixture, request):
+ """Tests that the distance calculation is rejected with midpoint distance mode for pixels."""
+ genomic_data = request.getfixturevalue(genomic_data_fixture)
+ query = Query(
+ query_steps=[
+ DistanceTransformation(DistanceMode.MIDPOINT),
+ ],
+ )
+ with pytest.raises(ValueError):
+ query.build(genomic_data)
+
+
+@pytest.mark.parametrize(
+ "genomic_data_fixture",
+ ["pixels_with_single_region", "pixels_with_multiple_regions"],
+)
+def test_distance_pixels(genomic_data_fixture, request):
+ """Tests distance calculation succeeds for pixels with distancemode left."""
+ genomic_data = request.getfixturevalue(genomic_data_fixture)
+ query = Query(
+ query_steps=[
+ DistanceTransformation(DistanceMode.LEFT),
+ ],
+ )
+ result = query.build(genomic_data).compute()
+ # check that the distance is correct
+ region_midpoints = (result["region_start"] + result["region_end"]) // 2
+ position_1_midpoint = result["start_1"]
+ position_2_midpoint = result["start_2"]
+ assert np.allclose(
+ position_1_midpoint - region_midpoints,
+ result["distance_1"],
+ )
+ assert np.allclose(
+ position_2_midpoint - region_midpoints,
+ result["distance_2"],
+ )
+
+
+@pytest.mark.parametrize(
+ "genomic_data_fixture",
+ ["pixels_with_single_region", "pixels_with_multiple_regions"],
+)
+def test_distance_pixels_left_string_arg(genomic_data_fixture, request):
+ """Tests distance calculation succeeds for pixels with distancemode left, specified as string."""
+ genomic_data = request.getfixturevalue(genomic_data_fixture)
+ query = Query(
+ query_steps=[
+ DistanceTransformation("left"),
+ ],
+ )
+ result = query.build(genomic_data).compute()
+ # check that the distance is correct
+ region_midpoints = (result["region_start"] + result["region_end"]) // 2
+ position_1_midpoint = result["start_1"]
+ position_2_midpoint = result["start_2"]
+ assert np.allclose(
+ position_1_midpoint - region_midpoints,
+ result["distance_1"],
+ )
+ assert np.allclose(
+ position_2_midpoint - region_midpoints,
+ result["distance_2"],
+ )
diff --git a/tests/query_engine/test_pixel_selection.py b/tests/query_engine/test_pixel_selection.py
new file mode 100644
index 0000000..c0c84e4
--- /dev/null
+++ b/tests/query_engine/test_pixel_selection.py
@@ -0,0 +1,269 @@
+"""Tests for the pixel selection"""
+import dask.dataframe as dd
+import duckdb
+import numpy as np
+import pytest
+
+from spoc.io import DUCKDB_CONNECTION
+from spoc.pixels import Pixels
+from spoc.query_engine import Anchor
+from spoc.query_engine import Overlap
+from spoc.query_engine import Query
+
+
+@pytest.fixture(name="pixels_dask")
+def pixels_dask_fixture(pixel_dataframe):
+ """A dask dataframe containing pixels"""
+ return Pixels(
+ dd.from_pandas(pixel_dataframe, npartitions=2),
+ number_fragments=2,
+ binsize=10,
+ )
+
+
+@pytest.fixture(name="pixels_pandas")
+def pixels_pandas_fixture(pixel_dataframe):
+ """A pandas dataframe containing pixels"""
+ return Pixels(pixel_dataframe, number_fragments=2, binsize=10)
+
+
+@pytest.fixture(name="pixels_duckdb")
+def pixels_duckdb_fixture(pixel_dataframe):
+ """A duckdb dataframe containing pixels"""
+ return Pixels(
+ duckdb.from_df(pixel_dataframe, DUCKDB_CONNECTION),
+ number_fragments=2,
+ binsize=10,
+ )
+
+
+# happy path
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_duckdb",
+ ],
+)
+def test_no_filter_returns_all_pixels(pixels_fixture, request):
+ """Test that no filter returns all pixels"""
+ pixels = request.getfixturevalue(pixels_fixture)
+ query = Query(query_steps=[])
+ result = query.build(pixels)
+ assert result.compute().shape[0] == 4
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ],
+)
+def test_any_anchor_region_returns_correct_pixels(
+ pixels_fixture, single_region, request
+):
+ """Test that any anchor region returns correct pixels"""
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [Overlap(regions=single_region, anchor_mode=Anchor(mode="ANY"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test correct selection
+ assert result.compute().shape[0] == 2
+ assert sorted(result.compute()["count"].tolist()) == sorted([1, 2])
+ # test addition of end columns
+ assert np.allclose(result.compute()["start_1"] + 10, result.compute()["end_1"])
+ assert np.allclose(result.compute()["start_2"] + 10, result.compute()["end_2"])
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ],
+)
+def test_all_anchor_regions_returns_correct_pixels(
+ pixels_fixture, single_region, request
+):
+ """Test that all anchor regions returns correct pixels"""
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [Overlap(regions=single_region, anchor_mode=Anchor(mode="ALL"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test
+ assert result.compute().shape[0] == 1
+ assert sorted(result.compute()["count"].tolist()) == sorted([2])
+
+
+@pytest.mark.parametrize(
+ "pixel_fixture,anchors,expected_reads",
+ [
+ (source_data, anchors, expected_reads)
+ for source_data, anchors, expected_reads in zip(
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ]
+ * 3,
+ [[1]] * 3 + [[2]] * 3 + [[1, 2]] * 3,
+ [[3]] * 3 + [[4]] * 3 + [[]] * 3,
+ )
+ ],
+)
+def test_specific_anchor_regions_returns_correct_pixels(
+ pixel_fixture, anchors, expected_reads, single_region_2, request
+):
+ """Test that specific anchor regions returns correct pixels"""
+ # setup
+ pixels = request.getfixturevalue(pixel_fixture)
+ query_plan = [
+ Overlap(
+ regions=single_region_2, anchor_mode=Anchor(mode="ALL", anchors=anchors)
+ )
+ ]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test
+ assert result.compute().shape[0] == len(expected_reads)
+ assert sorted(result.compute()["count"].tolist()) == sorted(expected_reads)
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ],
+)
+def test_any_anchor_region_returns_correct_pixels_multi_region(
+ pixels_fixture, multi_region, request
+):
+ """Test that any anchor region returns correct pixels"""
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [Overlap(regions=multi_region, anchor_mode=Anchor(mode="ANY"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test
+ assert result.compute().shape[0] == 4
+ assert sorted(result.compute()["count"].tolist()) == sorted([1, 2, 3, 4])
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ],
+)
+def test_all_anchor_regions_returns_correct_pixels_multi_region(
+ pixels_fixture, multi_region, request
+):
+ """Test that all anchor regions returns correct pixels"""
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [Overlap(regions=multi_region, anchor_mode=Anchor(mode="ALL"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test
+ assert result.compute().shape[0] == 1
+ assert sorted(result.compute()["count"].tolist()) == sorted([2])
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ],
+)
+def test_pixels_duplicated_for_multiple_overlapping_regions(
+ pixels_fixture, multi_region_2, request
+):
+ """
+ This test verifies that when multiple overlapping regions are specified as anchor regions,
+ the query returns duplicated pixels for each overlapping region.
+ """
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [Overlap(regions=multi_region_2, anchor_mode=Anchor(mode="ALL"))]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test
+ assert result.compute().shape[0] == 2
+ assert sorted(result.compute()["count"].tolist()) == sorted([2, 2])
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture,anchors,expected_reads",
+ [
+ (source_data, anchors, expected_reads)
+ for source_data, anchors, expected_reads in zip(
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ]
+ * 3,
+ [[1]] * 3 + [[2]] * 3 + [[1, 2]] * 3,
+ [[1, 2, 3]] * 3 + [[2, 4]] * 3 + [[2]] * 3,
+ )
+ ],
+)
+def test_specific_anchor_regions_returns_correct_pixels_multi_region(
+ pixels_fixture, anchors, expected_reads, multi_region, request
+):
+ """Test that specific anchor regions returns correct pixels"""
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [
+ Overlap(regions=multi_region, anchor_mode=Anchor(mode="ALL", anchors=anchors))
+ ]
+ # execution
+ query = Query(query_steps=query_plan)
+ result = query.build(pixels)
+ # test
+ assert result.compute().shape[0] == len(expected_reads)
+ assert sorted(result.compute()["count"].tolist()) == sorted(expected_reads)
+
+
+# validation problems
+
+
+@pytest.mark.parametrize(
+ "pixels_fixture",
+ [
+ "pixels_pandas",
+ "pixels_dask",
+ "pixels_duckdb",
+ ],
+)
+def test_specific_anchor_region_not_in_pixels_raises_error(
+ pixels_fixture, single_region, request
+):
+ """Test that specific anchor region not in pixels raises error"""
+ # setup
+ pixels = request.getfixturevalue(pixels_fixture)
+ query_plan = [
+ Overlap(regions=single_region, anchor_mode=Anchor(mode="ALL", anchors=[3]))
+ ]
+ with pytest.raises(ValueError):
+ query = Query(query_steps=query_plan)
+ query.build(pixels)
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 7ef394b..b0d9277 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -1,17 +1,17 @@
"""Tests for CLI of spoc"""
# pylint: disable=redefined-outer-name
-
-import shutil
import os
-import pytest
-import pandas as pd
-from pandas.testing import assert_frame_equal
+import shutil
+
import numpy as np
+import pandas as pd
+import pytest
from click.testing import CliRunner
+from pandas.testing import assert_frame_equal
from spoc import cli
-from spoc.io import FileManager
from spoc.contacts import Contacts
+from spoc.io import FileManager
from spoc.models import dataframe_models
from spoc.pixels import Pixels
@@ -189,7 +189,9 @@ def test_merge_contacts_works_with_compatible_contacts(mergable_triplet_files):
# check number of files
assert len(FileManager().list_contacts(output_path)) == 1
# check content of file
- labelled_fragments = Contacts.from_uri(f"{output_path}::3", mode="pandas").data
+ labelled_fragments = Contacts.from_uri(
+ f"{output_path}::3", mode=dataframe_models.DataMode.PANDAS
+ ).data
assert len(labelled_fragments) == 8
first_half = labelled_fragments.iloc[:4, :].reset_index(drop=True)
second_half = labelled_fragments.iloc[4:, :].reset_index(drop=True)
@@ -219,5 +221,7 @@ def test_bin_contacts(good_triplet_file_for_pixels, expected_pixels):
output_path = "tmp/test_output5.parquet"
runner.invoke(cli.bin_contacts, [good_triplet_file_for_pixels, output_path])
# check content of file
- pixels = Pixels.from_uri(f"{output_path}::3::10000", mode="pandas")
+ pixels = Pixels.from_uri(
+ f"{output_path}::3::10000", mode=dataframe_models.DataMode.PANDAS
+ )
np.array_equal(pixels.data.values, expected_pixels.values)
diff --git a/tests/test_pixels.py b/tests/test_pixels.py
index e015a70..a38afd0 100644
--- a/tests/test_pixels.py
+++ b/tests/test_pixels.py
@@ -1,10 +1,10 @@
"""Tests for the pixels module"""
# pylint: disable=redefined-outer-name
-
-import pytest
import dask.dataframe as dd
-import pandas as pd
import numpy as np
+import pandas as pd
+import pytest
+
from spoc import pixels
from spoc.contacts import Contacts