Skip to content

Genomic operations

Range operations

Source code in polars_bio/range_op.py
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
class IntervalOperations:

    @staticmethod
    def overlap(
        df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        suffixes: tuple[str, str] = ("_1", "_2"),
        on_cols: Union[list[str], None] = None,
        cols1: Union[list[str], None] = ["chrom", "start", "end"],
        cols2: Union[list[str], None] = ["chrom", "start", "end"],
        algorithm: str = "Coitrees",
        low_memory: bool = False,
        overlap_output: Literal["join", "left"] = "join",
        distinct_output: bool = False,
        output_type: str = "polars.LazyFrame",
        read_options1: Union[ReadOptions, None] = None,
        read_options2: Union[ReadOptions, None] = None,
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Find pairs of overlapping genomic intervals.
        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time. Both inputs must have the same coordinate
        system.

        Output modes (`overlap_output`, `distinct_output`):

        - `overlap_output="join"` (default): returns joined pairs, suffixing columns from both
          inputs. This is the historical behavior.
        - `overlap_output="left"`: returns only rows from `df1` that overlap at least one row in
          `df2`, keeping `df1` columns with their original names. By default it preserves overlap
          multiplicity — one `df1` row for each matching `df2` row.
        - `overlap_output="left", distinct_output=True`: returns each overlapping `df1` row once by
          row identity. Duplicate `df1` rows are preserved by identity; this does not apply
          `DISTINCT` over projected values.

        ```python
        pb.overlap(df1, df2, overlap_output="join")                        # joined pairs (default)
        pb.overlap(df1, df2, overlap_output="left")                        # df1 rows overlapping df2
        pb.overlap(df1, df2, overlap_output="left", distinct_output=True)  # each df1 row once
        ```

        Parameters:
            df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
            df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
            cols1: The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            cols2:  The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            suffixes: Suffixes for the columns of the two overlapped sets.
            on_cols: List of additional column names to join on. default is None.
            algorithm: The algorithm to use for the overlap operation. Available options: Coitrees, IntervalTree, ArrayIntervalTree, Lapper, SuperIntervals
            low_memory: If True, use low memory method for output generation. This caps the output batch size, trading some performance for significantly lower peak memory consumption. Recommended for operations that produce very large result sets.
            overlap_output: Output shape for overlap. "join" returns the current joined df1/df2 rows with suffixes. "left" returns only df1 rows that overlap at least one df2 row, preserving duplicate df1 rows and original column names.
            distinct_output: When overlap_output="left", True returns each overlapping df1 row once by row identity. False returns one df1 row per matching df2 row.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            read_options1: Additional options for reading the input files.
            read_options2: Additional options for reading the input files.
            projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

        Raises:
            MissingCoordinateSystemError: If either input lacks coordinate system metadata
                and `datafusion.bio.coordinate_system_check` is "true" (default). Use polars-bio
                I/O functions (scan_*, read_*) which automatically set metadata, or set it manually
                on Polars DataFrames via `df.config_meta.set(coordinate_system_zero_based=True/False)`
                or on Pandas DataFrames via `df.attrs["coordinate_system_zero_based"] = True/False`.
                Set `pb.set_option("datafusion.bio.coordinate_system_check", False)` to disable
                strict checking and fall back to global coordinate system setting.
            CoordinateSystemMismatchError: If inputs have different coordinate systems.

        Note:
            1. The default output format, i.e.  [LazyFrame](https://docs.pola.rs/api/python/stable/reference/lazyframe/index.html), is recommended for large datasets as it supports output streaming and lazy evaluation.
            This enables efficient processing of large datasets without loading the entire output dataset into memory.
            2. Streaming is only supported for polars.LazyFrame output.

        Example:
            ```python
            import polars_bio as pb
            import pandas as pd

            df1 = pd.DataFrame([
                ['chr1', 1, 5],
                ['chr1', 3, 8],
                ['chr1', 8, 10],
                ['chr1', 12, 14]],
            columns=['chrom', 'start', 'end']
            )
            df1.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

            df2 = pd.DataFrame(
            [['chr1', 4, 8],
             ['chr1', 10, 11]],
            columns=['chrom', 'start', 'end' ]
            )
            df2.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

            overlapping_intervals = pb.overlap(df1, df2, output_type="pandas.DataFrame")

            overlapping_intervals
                chrom_1         start_1     end_1 chrom_2       start_2  end_2
            0     chr1            1          5     chr1            4          8
            1     chr1            3          8     chr1            4          8

            ```

        Todo:
             Support for on_cols.
        """

        _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

        # Get filter_op from DataFrame metadata
        filter_op = _get_filter_op_from_metadata(df1, df2)

        cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
        cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
        range_options = RangeOptions(
            range_op=RangeOp.Overlap,
            filter_op=filter_op,
            suffixes=suffixes,
            columns_1=cols1,
            columns_2=cols2,
            overlap_alg=algorithm,
            overlap_low_memory=low_memory,
            overlap_output=_parse_overlap_output_mode(overlap_output),
            distinct_output=distinct_output,
        )

        return range_operation(
            df1,
            df2,
            range_options,
            output_type,
            ctx,
            read_options1,
            read_options2,
            projection_pushdown,
        )

    @staticmethod
    def nearest(
        df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        suffixes: tuple[str, str] = ("_1", "_2"),
        on_cols: Union[list[str], None] = None,
        cols1: Union[list[str], None] = ["chrom", "start", "end"],
        cols2: Union[list[str], None] = ["chrom", "start", "end"],
        k: int = 1,
        overlap: bool = True,
        distance: bool = True,
        output_type: str = "polars.LazyFrame",
        read_options: Union[ReadOptions, None] = None,
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Find pairs of closest genomic intervals.
        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time. Both inputs must have the same coordinate
        system.

        Parameters:
            df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
            df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
            cols1: The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            cols2:  The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            suffixes: Suffixes for the columns of the two overlapped sets.
            on_cols: List of additional column names to join on. default is None.
            k: Number of nearest neighbors to return per query interval. Default is 1.
            overlap: If True (default), include overlapping intervals in results. If False, only return non-overlapping nearest neighbors.
            distance: If True (default), include a `distance` column in the output. If False, omit it.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            read_options: Additional options for reading the input files.
            projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

        Raises:
            MissingCoordinateSystemError: If either input lacks coordinate system metadata
                and `datafusion.bio.coordinate_system_check` is "true" (default).
            CoordinateSystemMismatchError: If inputs have different coordinate systems.

        Note:
            The default output format, i.e. [LazyFrame](https://docs.pola.rs/api/python/stable/reference/lazyframe/index.html), is recommended for large datasets as it supports output streaming and lazy evaluation.
            This enables efficient processing of large datasets without loading the entire output dataset into memory.

        Example:

        Todo:
            Support for on_cols.
        """

        _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

        # Get filter_op from DataFrame metadata
        filter_op = _get_filter_op_from_metadata(df1, df2)

        cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
        cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
        range_options = RangeOptions(
            range_op=RangeOp.Nearest,
            filter_op=filter_op,
            suffixes=suffixes,
            columns_1=cols1,
            columns_2=cols2,
            nearest_k=k,
            include_overlaps=overlap,
            compute_distance=distance,
        )
        return range_operation(
            df1,
            df2,
            range_options,
            output_type,
            ctx,
            read_options,
            projection_pushdown=projection_pushdown,
        )

    @staticmethod
    def coverage(
        df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        suffixes: tuple[str, str] = ("_1", "_2"),
        on_cols: Union[list[str], None] = None,
        cols1: Union[list[str], None] = ["chrom", "start", "end"],
        cols2: Union[list[str], None] = ["chrom", "start", "end"],
        output_type: str = "polars.LazyFrame",
        read_options: Union[ReadOptions, None] = None,
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Calculate intervals coverage.
        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time. Both inputs must have the same coordinate
        system.

        Parameters:
            df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
            df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
            cols1: The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            cols2:  The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            suffixes: Suffixes for the columns of the two overlapped sets.
            on_cols: List of additional column names to join on. default is None.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            read_options: Additional options for reading the input files.
            projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

        Raises:
            MissingCoordinateSystemError: If either input lacks coordinate system metadata
                and `datafusion.bio.coordinate_system_check` is "true" (default).
            CoordinateSystemMismatchError: If inputs have different coordinate systems.

        Note:
            The default output format, i.e. [LazyFrame](https://docs.pola.rs/api/python/stable/reference/lazyframe/index.html), is recommended for large datasets as it supports output streaming and lazy evaluation.
            This enables efficient processing of large datasets without loading the entire output dataset into memory.

        Example:

        Todo:
            Support for on_cols.
        """

        _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

        # Get filter_op from DataFrame metadata
        filter_op = _get_filter_op_from_metadata(df1, df2)

        cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
        cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
        range_options = RangeOptions(
            range_op=RangeOp.Coverage,
            filter_op=filter_op,
            suffixes=suffixes,
            columns_1=cols1,
            columns_2=cols2,
        )
        return range_operation(
            df2,
            df1,
            range_options,
            output_type,
            ctx,
            read_options,
            projection_pushdown=projection_pushdown,
        )

    @staticmethod
    def count_overlaps(
        df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        suffixes: tuple[str, str] = ("", "_"),
        cols1: Union[list[str], None] = ["chrom", "start", "end"],
        cols2: Union[list[str], None] = ["chrom", "start", "end"],
        on_cols: Union[list[str], None] = None,
        output_type: str = "polars.LazyFrame",
        naive_query: bool = True,
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Count pairs of overlapping genomic intervals.
        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time. Both inputs must have the same coordinate
        system.

        Parameters:
            df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
            df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
            suffixes: Suffixes for the columns of the two overlapped sets.
            cols1: The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            cols2:  The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            on_cols: List of additional column names to join on. default is None.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            naive_query: If True, use naive query for counting overlaps based on overlaps.
            projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

        Raises:
            MissingCoordinateSystemError: If either input lacks coordinate system metadata
                and `datafusion.bio.coordinate_system_check` is "true" (default).
            CoordinateSystemMismatchError: If inputs have different coordinate systems.

        Example:
            ```python
            import polars_bio as pb
            import pandas as pd

            df1 = pd.DataFrame([
                ['chr1', 1, 5],
                ['chr1', 3, 8],
                ['chr1', 8, 10],
                ['chr1', 12, 14]],
            columns=['chrom', 'start', 'end']
            )
            df1.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

            df2 = pd.DataFrame(
            [['chr1', 4, 8],
             ['chr1', 10, 11]],
            columns=['chrom', 'start', 'end' ]
            )
            df2.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

            counts = pb.count_overlaps(df1, df2, output_type="pandas.DataFrame")

            counts

            chrom  start  end  count
            0  chr1      1    5      1
            1  chr1      3    8      1
            2  chr1      8   10      0
            3  chr1     12   14      0
            ```

        Todo:
             Support return_input.
        """
        _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

        # Get filter_op and zero_based from DataFrame metadata
        zero_based = validate_coordinate_systems(df1, df2, ctx)
        filter_op = FilterOp.Strict if zero_based else FilterOp.Weak

        my_ctx = get_py_ctx()
        on_cols = [] if on_cols is None else on_cols
        cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
        cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
        if naive_query:
            range_options = RangeOptions(
                range_op=RangeOp.CountOverlapsNaive,
                filter_op=filter_op,
                suffixes=suffixes,
                columns_1=cols1,
                columns_2=cols2,
            )
            return range_operation(df2, df1, range_options, output_type, ctx)
        df1 = read_df_to_datafusion(my_ctx, df1)
        df2 = read_df_to_datafusion(my_ctx, df2)

        curr_cols = set(df1.schema().names) | set(df2.schema().names)
        s1start_s2end = prevent_column_collision("s1starts2end", curr_cols)
        s1end_s2start = prevent_column_collision("s1ends2start", curr_cols)
        contig = prevent_column_collision("contig", curr_cols)
        count = prevent_column_collision("count", curr_cols)
        starts = prevent_column_collision("starts", curr_cols)
        ends = prevent_column_collision("ends", curr_cols)
        is_s1 = prevent_column_collision("is_s1", curr_cols)
        suff, _ = suffixes
        df1, df2 = df2, df1
        df1 = df1.select(
            *(
                [
                    literal(1).alias(is_s1),
                    col(cols1[1]).alias(s1start_s2end),
                    col(cols1[2]).alias(s1end_s2start),
                    col(cols1[0]).alias(contig),
                ]
                + on_cols
            )
        )
        df2 = df2.select(
            *(
                [
                    literal(0).alias(is_s1),
                    col(cols2[2]).alias(s1end_s2start),
                    col(cols2[1]).alias(s1start_s2end),
                    col(cols2[0]).alias(contig),
                ]
                + on_cols
            )
        )

        df = df1.union(df2)

        partitioning = [col(contig)] + [col(c) for c in on_cols]
        df = df.select(
            *(
                [
                    s1start_s2end,
                    s1end_s2start,
                    contig,
                    is_s1,
                    datafusion.functions.sum(col(is_s1))
                    .over(
                        datafusion.expr.Window(
                            partition_by=partitioning,
                            order_by=[
                                col(s1start_s2end).sort(),
                                col(is_s1).sort(ascending=zero_based),
                            ],
                        )
                    )
                    .alias(starts),
                    datafusion.functions.sum(col(is_s1))
                    .over(
                        datafusion.expr.Window(
                            partition_by=partitioning,
                            order_by=[
                                col(s1end_s2start).sort(),
                                col(is_s1).sort(ascending=(not zero_based)),
                            ],
                        )
                    )
                    .alias(ends),
                ]
                + on_cols
            )
        )
        df = df.filter(col(is_s1) == 0)
        df = df.select(
            *(
                [
                    col(contig).alias(cols1[0] + suff),
                    col(s1end_s2start).alias(cols1[1] + suff),
                    col(s1start_s2end).alias(cols1[2] + suff),
                ]
                + on_cols
                + [(col(starts) - col(ends)).alias(count)]
            )
        )

        return convert_result(df, output_type)

    @staticmethod
    def merge(
        df: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        min_dist: int = 0,
        cols: Union[list[str], None] = ["chrom", "start", "end"],
        on_cols: Union[list[str], None] = None,
        output_type: str = "polars.LazyFrame",
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Merge overlapping intervals. It is assumed that start < end.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time.

        Parameters:
            df: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED  and Parquet are supported.
            min_dist: Minimum distance (integer) between intervals to merge. Default is 0.
            cols: The names of columns containing the chromosome, start and end of the
                genomic intervals, provided separately for each set.
            on_cols: List of additional column names for clustering. default is None.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

        Raises:
            MissingCoordinateSystemError: If input lacks coordinate system metadata
                and `datafusion.bio.coordinate_system_check` is "true" (default).

        Example:

        Todo:
            Support for on_cols.
        """
        suffixes = ("_1", "_2")
        _validate_overlap_input(cols, cols, on_cols, suffixes, output_type)

        # Get filter_op from DataFrame metadata
        filter_op = _get_filter_op_from_metadata_single(df)

        cols = DEFAULT_INTERVAL_COLUMNS if cols is None else cols
        range_options = RangeOptions(
            range_op=RangeOp.Merge,
            filter_op=filter_op,
            columns_1=cols,
            columns_2=cols,
            min_dist=min_dist,
        )

        return range_operation(
            df,
            df,
            range_options,
            output_type,
            ctx,
            projection_pushdown=projection_pushdown,
        )

    @staticmethod
    def cluster(
        df: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        min_dist: int = 0,
        cols: Union[list[str], None] = ["chrom", "start", "end"],
        output_type: str = "polars.LazyFrame",
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Assign cluster IDs to overlapping or nearby genomic intervals.

        Groups intervals that overlap or are within ``min_dist`` of each other
        into clusters. Each row is annotated with a cluster ID and the
        cluster's merged start/end boundaries.

        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time.

        Parameters:
            df: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
            min_dist: Minimum distance (integer) between intervals to cluster. Default is 0.
            cols: The names of columns containing the chromosome, start and end of the
                genomic intervals.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            projection_pushdown: Enable column projection pushdown.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame with original
            interval columns plus ``cluster``, ``cluster_start``, ``cluster_end``.

        Raises:
            MissingCoordinateSystemError: If input lacks coordinate system metadata
                and ``datafusion.bio.coordinate_system_check`` is "true" (default).
        """
        suffixes = ("_1", "_2")
        _validate_overlap_input(cols, cols, None, suffixes, output_type)

        filter_op = _get_filter_op_from_metadata_single(df)

        cols = DEFAULT_INTERVAL_COLUMNS if cols is None else cols
        range_options = RangeOptions(
            range_op=RangeOp.Cluster,
            filter_op=filter_op,
            columns_1=cols,
            columns_2=cols,
            min_dist=min_dist,
        )

        return range_operation(
            df,
            df,
            range_options,
            output_type,
            ctx,
            projection_pushdown=projection_pushdown,
        )

    @staticmethod
    def complement(
        df: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        view_df: Union[pl.DataFrame, pl.LazyFrame, "pd.DataFrame", None] = None,
        cols: Union[list[str], None] = ["chrom", "start", "end"],
        view_cols: Union[list[str], None] = None,
        output_type: str = "polars.LazyFrame",
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Compute the complement of genomic intervals — the gaps between them.

        Returns intervals that represent the genomic regions **not** covered
        by the input intervals. If ``view_df`` is provided, gaps are computed
        within the boundaries of the view (e.g., chromosome sizes); otherwise
        each contig spans ``[0, i64::MAX)``.

        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time.

        Parameters:
            df: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
            view_df: Optional DataFrame defining contig boundaries (e.g., chromosome sizes). Each row should have contig, start, end columns.
            cols: The names of columns containing the chromosome, start and end of the
                genomic intervals.
            view_cols: Column names for the view table. Defaults to ``cols`` when not specified.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            projection_pushdown: Enable column projection pushdown.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of complement
            intervals (contig, start, end).

        Raises:
            MissingCoordinateSystemError: If input lacks coordinate system metadata
                and ``datafusion.bio.coordinate_system_check`` is "true" (default).
        """
        suffixes = ("_1", "_2")
        _validate_overlap_input(cols, cols, None, suffixes, output_type)

        filter_op = _get_filter_op_from_metadata_single(df)

        cols = DEFAULT_INTERVAL_COLUMNS if cols is None else cols
        view_cols = cols if view_cols is None else view_cols

        # Register view table in DataFusion if provided
        view_table_name = None
        if view_df is not None:
            view_table_name = _register_view_table(view_df, view_cols[0])
        else:
            logger.warning(
                "No view_df provided — complement will span [0, i64::MAX) per contig. "
                "Pass a view_df with contig boundaries (e.g., chromosome sizes) "
                "for meaningful results."
            )

        range_options = RangeOptions(
            range_op=RangeOp.Complement,
            filter_op=filter_op,
            columns_1=cols,
            columns_2=cols,
            view_table=view_table_name,
            view_columns=view_cols,
        )

        return range_operation(
            df,
            df,
            range_options,
            output_type,
            ctx,
            projection_pushdown=projection_pushdown,
        )

    @staticmethod
    def subtract(
        df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
        cols1: Union[list[str], None] = ["chrom", "start", "end"],
        cols2: Union[list[str], None] = ["chrom", "start", "end"],
        output_type: str = "polars.LazyFrame",
        projection_pushdown: bool = True,
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
        """
        Subtract the second set of intervals from the first.

        For each interval in ``df1``, removes any portion that overlaps with
        intervals in ``df2``. The result contains the remaining fragments.

        Bioframe inspired API.

        The coordinate system (0-based or 1-based) is automatically detected from
        DataFrame metadata set at I/O time. Both inputs must have the same coordinate
        system.

        Parameters:
            df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
            df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
            cols1: The names of columns containing the chromosome, start and end of the
                genomic intervals for the first set.
            cols2: The names of columns containing the chromosome, start and end of the
                genomic intervals for the second set.
            output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
            projection_pushdown: Enable column projection pushdown.

        Returns:
            **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the
            remaining interval fragments (contig, start, end).

        Raises:
            MissingCoordinateSystemError: If either input lacks coordinate system metadata
                and ``datafusion.bio.coordinate_system_check`` is "true" (default).
            CoordinateSystemMismatchError: If inputs have different coordinate systems.
        """
        suffixes = ("_1", "_2")
        _validate_overlap_input(cols1, cols2, None, suffixes, output_type)

        filter_op = _get_filter_op_from_metadata(df1, df2)

        cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
        cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
        range_options = RangeOptions(
            range_op=RangeOp.Subtract,
            filter_op=filter_op,
            columns_1=cols1,
            columns_2=cols2,
        )

        return range_operation(
            df1,
            df2,
            range_options,
            output_type,
            ctx,
            projection_pushdown=projection_pushdown,
        )

cluster(df, min_dist=0, cols=['chrom', 'start', 'end'], output_type='polars.LazyFrame', projection_pushdown=True) staticmethod

Assign cluster IDs to overlapping or nearby genomic intervals.

Groups intervals that overlap or are within min_dist of each other into clusters. Each row is annotated with a cluster ID and the cluster's merged start/end boundaries.

Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time.

Parameters:

Name Type Description Default
df Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.

required
min_dist int

Minimum distance (integer) between intervals to cluster. Default is 0.

0
cols Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals.

['chrom', 'start', 'end']
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
projection_pushdown bool

Enable column projection pushdown.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame with original

Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

interval columns plus cluster, cluster_start, cluster_end.

Raises:

Type Description
MissingCoordinateSystemError

If input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

Source code in polars_bio/range_op.py
@staticmethod
def cluster(
    df: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    min_dist: int = 0,
    cols: Union[list[str], None] = ["chrom", "start", "end"],
    output_type: str = "polars.LazyFrame",
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Assign cluster IDs to overlapping or nearby genomic intervals.

    Groups intervals that overlap or are within ``min_dist`` of each other
    into clusters. Each row is annotated with a cluster ID and the
    cluster's merged start/end boundaries.

    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time.

    Parameters:
        df: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
        min_dist: Minimum distance (integer) between intervals to cluster. Default is 0.
        cols: The names of columns containing the chromosome, start and end of the
            genomic intervals.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        projection_pushdown: Enable column projection pushdown.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame with original
        interval columns plus ``cluster``, ``cluster_start``, ``cluster_end``.

    Raises:
        MissingCoordinateSystemError: If input lacks coordinate system metadata
            and ``datafusion.bio.coordinate_system_check`` is "true" (default).
    """
    suffixes = ("_1", "_2")
    _validate_overlap_input(cols, cols, None, suffixes, output_type)

    filter_op = _get_filter_op_from_metadata_single(df)

    cols = DEFAULT_INTERVAL_COLUMNS if cols is None else cols
    range_options = RangeOptions(
        range_op=RangeOp.Cluster,
        filter_op=filter_op,
        columns_1=cols,
        columns_2=cols,
        min_dist=min_dist,
    )

    return range_operation(
        df,
        df,
        range_options,
        output_type,
        ctx,
        projection_pushdown=projection_pushdown,
    )

complement(df, view_df=None, cols=['chrom', 'start', 'end'], view_cols=None, output_type='polars.LazyFrame', projection_pushdown=True) staticmethod

Compute the complement of genomic intervals — the gaps between them.

Returns intervals that represent the genomic regions not covered by the input intervals. If view_df is provided, gaps are computed within the boundaries of the view (e.g., chromosome sizes); otherwise each contig spans [0, i64::MAX).

Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time.

Parameters:

Name Type Description Default
df Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.

required
view_df Union[DataFrame, LazyFrame, 'pd.DataFrame', None]

Optional DataFrame defining contig boundaries (e.g., chromosome sizes). Each row should have contig, start, end columns.

None
cols Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals.

['chrom', 'start', 'end']
view_cols Union[list[str], None]

Column names for the view table. Defaults to cols when not specified.

None
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
projection_pushdown bool

Enable column projection pushdown.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of complement

Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

intervals (contig, start, end).

Raises:

Type Description
MissingCoordinateSystemError

If input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

Source code in polars_bio/range_op.py
@staticmethod
def complement(
    df: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    view_df: Union[pl.DataFrame, pl.LazyFrame, "pd.DataFrame", None] = None,
    cols: Union[list[str], None] = ["chrom", "start", "end"],
    view_cols: Union[list[str], None] = None,
    output_type: str = "polars.LazyFrame",
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Compute the complement of genomic intervals — the gaps between them.

    Returns intervals that represent the genomic regions **not** covered
    by the input intervals. If ``view_df`` is provided, gaps are computed
    within the boundaries of the view (e.g., chromosome sizes); otherwise
    each contig spans ``[0, i64::MAX)``.

    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time.

    Parameters:
        df: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
        view_df: Optional DataFrame defining contig boundaries (e.g., chromosome sizes). Each row should have contig, start, end columns.
        cols: The names of columns containing the chromosome, start and end of the
            genomic intervals.
        view_cols: Column names for the view table. Defaults to ``cols`` when not specified.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        projection_pushdown: Enable column projection pushdown.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of complement
        intervals (contig, start, end).

    Raises:
        MissingCoordinateSystemError: If input lacks coordinate system metadata
            and ``datafusion.bio.coordinate_system_check`` is "true" (default).
    """
    suffixes = ("_1", "_2")
    _validate_overlap_input(cols, cols, None, suffixes, output_type)

    filter_op = _get_filter_op_from_metadata_single(df)

    cols = DEFAULT_INTERVAL_COLUMNS if cols is None else cols
    view_cols = cols if view_cols is None else view_cols

    # Register view table in DataFusion if provided
    view_table_name = None
    if view_df is not None:
        view_table_name = _register_view_table(view_df, view_cols[0])
    else:
        logger.warning(
            "No view_df provided — complement will span [0, i64::MAX) per contig. "
            "Pass a view_df with contig boundaries (e.g., chromosome sizes) "
            "for meaningful results."
        )

    range_options = RangeOptions(
        range_op=RangeOp.Complement,
        filter_op=filter_op,
        columns_1=cols,
        columns_2=cols,
        view_table=view_table_name,
        view_columns=view_cols,
    )

    return range_operation(
        df,
        df,
        range_options,
        output_type,
        ctx,
        projection_pushdown=projection_pushdown,
    )

count_overlaps(df1, df2, suffixes=('', '_'), cols1=['chrom', 'start', 'end'], cols2=['chrom', 'start', 'end'], on_cols=None, output_type='polars.LazyFrame', naive_query=True, projection_pushdown=True) staticmethod

Count pairs of overlapping genomic intervals. Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time. Both inputs must have the same coordinate system.

Parameters:

Name Type Description Default
df1 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see register_vcf). CSV with a header, BED and Parquet are supported.

required
df2 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED and Parquet are supported.

required
suffixes tuple[str, str]

Suffixes for the columns of the two overlapped sets.

('', '_')
cols1 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
cols2 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
on_cols Union[list[str], None]

List of additional column names to join on. default is None.

None
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
naive_query bool

If True, use naive query for counting overlaps based on overlaps.

True
projection_pushdown bool

Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

Raises:

Type Description
MissingCoordinateSystemError

If either input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

CoordinateSystemMismatchError

If inputs have different coordinate systems.

Example
import polars_bio as pb
import pandas as pd

df1 = pd.DataFrame([
    ['chr1', 1, 5],
    ['chr1', 3, 8],
    ['chr1', 8, 10],
    ['chr1', 12, 14]],
columns=['chrom', 'start', 'end']
)
df1.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

df2 = pd.DataFrame(
[['chr1', 4, 8],
 ['chr1', 10, 11]],
columns=['chrom', 'start', 'end' ]
)
df2.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

counts = pb.count_overlaps(df1, df2, output_type="pandas.DataFrame")

counts

chrom  start  end  count
0  chr1      1    5      1
1  chr1      3    8      1
2  chr1      8   10      0
3  chr1     12   14      0
Todo

Support return_input.

Source code in polars_bio/range_op.py
@staticmethod
def count_overlaps(
    df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    suffixes: tuple[str, str] = ("", "_"),
    cols1: Union[list[str], None] = ["chrom", "start", "end"],
    cols2: Union[list[str], None] = ["chrom", "start", "end"],
    on_cols: Union[list[str], None] = None,
    output_type: str = "polars.LazyFrame",
    naive_query: bool = True,
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Count pairs of overlapping genomic intervals.
    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time. Both inputs must have the same coordinate
    system.

    Parameters:
        df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
        df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
        suffixes: Suffixes for the columns of the two overlapped sets.
        cols1: The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        cols2:  The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        on_cols: List of additional column names to join on. default is None.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        naive_query: If True, use naive query for counting overlaps based on overlaps.
        projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

    Raises:
        MissingCoordinateSystemError: If either input lacks coordinate system metadata
            and `datafusion.bio.coordinate_system_check` is "true" (default).
        CoordinateSystemMismatchError: If inputs have different coordinate systems.

    Example:
        ```python
        import polars_bio as pb
        import pandas as pd

        df1 = pd.DataFrame([
            ['chr1', 1, 5],
            ['chr1', 3, 8],
            ['chr1', 8, 10],
            ['chr1', 12, 14]],
        columns=['chrom', 'start', 'end']
        )
        df1.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

        df2 = pd.DataFrame(
        [['chr1', 4, 8],
         ['chr1', 10, 11]],
        columns=['chrom', 'start', 'end' ]
        )
        df2.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

        counts = pb.count_overlaps(df1, df2, output_type="pandas.DataFrame")

        counts

        chrom  start  end  count
        0  chr1      1    5      1
        1  chr1      3    8      1
        2  chr1      8   10      0
        3  chr1     12   14      0
        ```

    Todo:
         Support return_input.
    """
    _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

    # Get filter_op and zero_based from DataFrame metadata
    zero_based = validate_coordinate_systems(df1, df2, ctx)
    filter_op = FilterOp.Strict if zero_based else FilterOp.Weak

    my_ctx = get_py_ctx()
    on_cols = [] if on_cols is None else on_cols
    cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
    cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
    if naive_query:
        range_options = RangeOptions(
            range_op=RangeOp.CountOverlapsNaive,
            filter_op=filter_op,
            suffixes=suffixes,
            columns_1=cols1,
            columns_2=cols2,
        )
        return range_operation(df2, df1, range_options, output_type, ctx)
    df1 = read_df_to_datafusion(my_ctx, df1)
    df2 = read_df_to_datafusion(my_ctx, df2)

    curr_cols = set(df1.schema().names) | set(df2.schema().names)
    s1start_s2end = prevent_column_collision("s1starts2end", curr_cols)
    s1end_s2start = prevent_column_collision("s1ends2start", curr_cols)
    contig = prevent_column_collision("contig", curr_cols)
    count = prevent_column_collision("count", curr_cols)
    starts = prevent_column_collision("starts", curr_cols)
    ends = prevent_column_collision("ends", curr_cols)
    is_s1 = prevent_column_collision("is_s1", curr_cols)
    suff, _ = suffixes
    df1, df2 = df2, df1
    df1 = df1.select(
        *(
            [
                literal(1).alias(is_s1),
                col(cols1[1]).alias(s1start_s2end),
                col(cols1[2]).alias(s1end_s2start),
                col(cols1[0]).alias(contig),
            ]
            + on_cols
        )
    )
    df2 = df2.select(
        *(
            [
                literal(0).alias(is_s1),
                col(cols2[2]).alias(s1end_s2start),
                col(cols2[1]).alias(s1start_s2end),
                col(cols2[0]).alias(contig),
            ]
            + on_cols
        )
    )

    df = df1.union(df2)

    partitioning = [col(contig)] + [col(c) for c in on_cols]
    df = df.select(
        *(
            [
                s1start_s2end,
                s1end_s2start,
                contig,
                is_s1,
                datafusion.functions.sum(col(is_s1))
                .over(
                    datafusion.expr.Window(
                        partition_by=partitioning,
                        order_by=[
                            col(s1start_s2end).sort(),
                            col(is_s1).sort(ascending=zero_based),
                        ],
                    )
                )
                .alias(starts),
                datafusion.functions.sum(col(is_s1))
                .over(
                    datafusion.expr.Window(
                        partition_by=partitioning,
                        order_by=[
                            col(s1end_s2start).sort(),
                            col(is_s1).sort(ascending=(not zero_based)),
                        ],
                    )
                )
                .alias(ends),
            ]
            + on_cols
        )
    )
    df = df.filter(col(is_s1) == 0)
    df = df.select(
        *(
            [
                col(contig).alias(cols1[0] + suff),
                col(s1end_s2start).alias(cols1[1] + suff),
                col(s1start_s2end).alias(cols1[2] + suff),
            ]
            + on_cols
            + [(col(starts) - col(ends)).alias(count)]
        )
    )

    return convert_result(df, output_type)

coverage(df1, df2, suffixes=('_1', '_2'), on_cols=None, cols1=['chrom', 'start', 'end'], cols2=['chrom', 'start', 'end'], output_type='polars.LazyFrame', read_options=None, projection_pushdown=True) staticmethod

Calculate intervals coverage. Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time. Both inputs must have the same coordinate system.

Parameters:

Name Type Description Default
df1 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see register_vcf). CSV with a header, BED and Parquet are supported.

required
df2 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED and Parquet are supported.

required
cols1 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
cols2 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
suffixes tuple[str, str]

Suffixes for the columns of the two overlapped sets.

('_1', '_2')
on_cols Union[list[str], None]

List of additional column names to join on. default is None.

None
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
read_options Union[ReadOptions, None]

Additional options for reading the input files.

None
projection_pushdown bool

Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

Raises:

Type Description
MissingCoordinateSystemError

If either input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

CoordinateSystemMismatchError

If inputs have different coordinate systems.

Note

The default output format, i.e. LazyFrame, is recommended for large datasets as it supports output streaming and lazy evaluation. This enables efficient processing of large datasets without loading the entire output dataset into memory.

Example:

Todo

Support for on_cols.

Source code in polars_bio/range_op.py
@staticmethod
def coverage(
    df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    suffixes: tuple[str, str] = ("_1", "_2"),
    on_cols: Union[list[str], None] = None,
    cols1: Union[list[str], None] = ["chrom", "start", "end"],
    cols2: Union[list[str], None] = ["chrom", "start", "end"],
    output_type: str = "polars.LazyFrame",
    read_options: Union[ReadOptions, None] = None,
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Calculate intervals coverage.
    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time. Both inputs must have the same coordinate
    system.

    Parameters:
        df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
        df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
        cols1: The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        cols2:  The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        suffixes: Suffixes for the columns of the two overlapped sets.
        on_cols: List of additional column names to join on. default is None.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        read_options: Additional options for reading the input files.
        projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

    Raises:
        MissingCoordinateSystemError: If either input lacks coordinate system metadata
            and `datafusion.bio.coordinate_system_check` is "true" (default).
        CoordinateSystemMismatchError: If inputs have different coordinate systems.

    Note:
        The default output format, i.e. [LazyFrame](https://docs.pola.rs/api/python/stable/reference/lazyframe/index.html), is recommended for large datasets as it supports output streaming and lazy evaluation.
        This enables efficient processing of large datasets without loading the entire output dataset into memory.

    Example:

    Todo:
        Support for on_cols.
    """

    _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

    # Get filter_op from DataFrame metadata
    filter_op = _get_filter_op_from_metadata(df1, df2)

    cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
    cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
    range_options = RangeOptions(
        range_op=RangeOp.Coverage,
        filter_op=filter_op,
        suffixes=suffixes,
        columns_1=cols1,
        columns_2=cols2,
    )
    return range_operation(
        df2,
        df1,
        range_options,
        output_type,
        ctx,
        read_options,
        projection_pushdown=projection_pushdown,
    )

merge(df, min_dist=0, cols=['chrom', 'start', 'end'], on_cols=None, output_type='polars.LazyFrame', projection_pushdown=True) staticmethod

Merge overlapping intervals. It is assumed that start < end.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time.

Parameters:

Name Type Description Default
df Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.

required
min_dist int

Minimum distance (integer) between intervals to merge. Default is 0.

0
cols Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
on_cols Union[list[str], None]

List of additional column names for clustering. default is None.

None
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
projection_pushdown bool

Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

Raises:

Type Description
MissingCoordinateSystemError

If input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

Example:

Todo

Support for on_cols.

Source code in polars_bio/range_op.py
@staticmethod
def merge(
    df: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    min_dist: int = 0,
    cols: Union[list[str], None] = ["chrom", "start", "end"],
    on_cols: Union[list[str], None] = None,
    output_type: str = "polars.LazyFrame",
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Merge overlapping intervals. It is assumed that start < end.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time.

    Parameters:
        df: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED  and Parquet are supported.
        min_dist: Minimum distance (integer) between intervals to merge. Default is 0.
        cols: The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        on_cols: List of additional column names for clustering. default is None.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

    Raises:
        MissingCoordinateSystemError: If input lacks coordinate system metadata
            and `datafusion.bio.coordinate_system_check` is "true" (default).

    Example:

    Todo:
        Support for on_cols.
    """
    suffixes = ("_1", "_2")
    _validate_overlap_input(cols, cols, on_cols, suffixes, output_type)

    # Get filter_op from DataFrame metadata
    filter_op = _get_filter_op_from_metadata_single(df)

    cols = DEFAULT_INTERVAL_COLUMNS if cols is None else cols
    range_options = RangeOptions(
        range_op=RangeOp.Merge,
        filter_op=filter_op,
        columns_1=cols,
        columns_2=cols,
        min_dist=min_dist,
    )

    return range_operation(
        df,
        df,
        range_options,
        output_type,
        ctx,
        projection_pushdown=projection_pushdown,
    )

nearest(df1, df2, suffixes=('_1', '_2'), on_cols=None, cols1=['chrom', 'start', 'end'], cols2=['chrom', 'start', 'end'], k=1, overlap=True, distance=True, output_type='polars.LazyFrame', read_options=None, projection_pushdown=True) staticmethod

Find pairs of closest genomic intervals. Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time. Both inputs must have the same coordinate system.

Parameters:

Name Type Description Default
df1 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see register_vcf). CSV with a header, BED and Parquet are supported.

required
df2 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED and Parquet are supported.

required
cols1 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
cols2 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
suffixes tuple[str, str]

Suffixes for the columns of the two overlapped sets.

('_1', '_2')
on_cols Union[list[str], None]

List of additional column names to join on. default is None.

None
k int

Number of nearest neighbors to return per query interval. Default is 1.

1
overlap bool

If True (default), include overlapping intervals in results. If False, only return non-overlapping nearest neighbors.

True
distance bool

If True (default), include a distance column in the output. If False, omit it.

True
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
read_options Union[ReadOptions, None]

Additional options for reading the input files.

None
projection_pushdown bool

Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

Raises:

Type Description
MissingCoordinateSystemError

If either input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

CoordinateSystemMismatchError

If inputs have different coordinate systems.

Note

The default output format, i.e. LazyFrame, is recommended for large datasets as it supports output streaming and lazy evaluation. This enables efficient processing of large datasets without loading the entire output dataset into memory.

Example:

Todo

Support for on_cols.

Source code in polars_bio/range_op.py
@staticmethod
def nearest(
    df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    suffixes: tuple[str, str] = ("_1", "_2"),
    on_cols: Union[list[str], None] = None,
    cols1: Union[list[str], None] = ["chrom", "start", "end"],
    cols2: Union[list[str], None] = ["chrom", "start", "end"],
    k: int = 1,
    overlap: bool = True,
    distance: bool = True,
    output_type: str = "polars.LazyFrame",
    read_options: Union[ReadOptions, None] = None,
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Find pairs of closest genomic intervals.
    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time. Both inputs must have the same coordinate
    system.

    Parameters:
        df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
        df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
        cols1: The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        cols2:  The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        suffixes: Suffixes for the columns of the two overlapped sets.
        on_cols: List of additional column names to join on. default is None.
        k: Number of nearest neighbors to return per query interval. Default is 1.
        overlap: If True (default), include overlapping intervals in results. If False, only return non-overlapping nearest neighbors.
        distance: If True (default), include a `distance` column in the output. If False, omit it.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        read_options: Additional options for reading the input files.
        projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

    Raises:
        MissingCoordinateSystemError: If either input lacks coordinate system metadata
            and `datafusion.bio.coordinate_system_check` is "true" (default).
        CoordinateSystemMismatchError: If inputs have different coordinate systems.

    Note:
        The default output format, i.e. [LazyFrame](https://docs.pola.rs/api/python/stable/reference/lazyframe/index.html), is recommended for large datasets as it supports output streaming and lazy evaluation.
        This enables efficient processing of large datasets without loading the entire output dataset into memory.

    Example:

    Todo:
        Support for on_cols.
    """

    _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

    # Get filter_op from DataFrame metadata
    filter_op = _get_filter_op_from_metadata(df1, df2)

    cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
    cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
    range_options = RangeOptions(
        range_op=RangeOp.Nearest,
        filter_op=filter_op,
        suffixes=suffixes,
        columns_1=cols1,
        columns_2=cols2,
        nearest_k=k,
        include_overlaps=overlap,
        compute_distance=distance,
    )
    return range_operation(
        df1,
        df2,
        range_options,
        output_type,
        ctx,
        read_options,
        projection_pushdown=projection_pushdown,
    )

overlap(df1, df2, suffixes=('_1', '_2'), on_cols=None, cols1=['chrom', 'start', 'end'], cols2=['chrom', 'start', 'end'], algorithm='Coitrees', low_memory=False, overlap_output='join', distinct_output=False, output_type='polars.LazyFrame', read_options1=None, read_options2=None, projection_pushdown=True) staticmethod

Find pairs of overlapping genomic intervals. Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time. Both inputs must have the same coordinate system.

Output modes (overlap_output, distinct_output):

  • overlap_output="join" (default): returns joined pairs, suffixing columns from both inputs. This is the historical behavior.
  • overlap_output="left": returns only rows from df1 that overlap at least one row in df2, keeping df1 columns with their original names. By default it preserves overlap multiplicity — one df1 row for each matching df2 row.
  • overlap_output="left", distinct_output=True: returns each overlapping df1 row once by row identity. Duplicate df1 rows are preserved by identity; this does not apply DISTINCT over projected values.
pb.overlap(df1, df2, overlap_output="join")                        # joined pairs (default)
pb.overlap(df1, df2, overlap_output="left")                        # df1 rows overlapping df2
pb.overlap(df1, df2, overlap_output="left", distinct_output=True)  # each df1 row once

Parameters:

Name Type Description Default
df1 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see register_vcf). CSV with a header, BED and Parquet are supported.

required
df2 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED and Parquet are supported.

required
cols1 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
cols2 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals, provided separately for each set.

['chrom', 'start', 'end']
suffixes tuple[str, str]

Suffixes for the columns of the two overlapped sets.

('_1', '_2')
on_cols Union[list[str], None]

List of additional column names to join on. default is None.

None
algorithm str

The algorithm to use for the overlap operation. Available options: Coitrees, IntervalTree, ArrayIntervalTree, Lapper, SuperIntervals

'Coitrees'
low_memory bool

If True, use low memory method for output generation. This caps the output batch size, trading some performance for significantly lower peak memory consumption. Recommended for operations that produce very large result sets.

False
overlap_output Literal['join', 'left']

Output shape for overlap. "join" returns the current joined df1/df2 rows with suffixes. "left" returns only df1 rows that overlap at least one df2 row, preserving duplicate df1 rows and original column names.

'join'
distinct_output bool

When overlap_output="left", True returns each overlapping df1 row once by row identity. False returns one df1 row per matching df2 row.

False
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
read_options1 Union[ReadOptions, None]

Additional options for reading the input files.

None
read_options2 Union[ReadOptions, None]

Additional options for reading the input files.

None
projection_pushdown bool

Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

Raises:

Type Description
MissingCoordinateSystemError

If either input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default). Use polars-bio I/O functions (scan_, read_) which automatically set metadata, or set it manually on Polars DataFrames via df.config_meta.set(coordinate_system_zero_based=True/False) or on Pandas DataFrames via df.attrs["coordinate_system_zero_based"] = True/False. Set pb.set_option("datafusion.bio.coordinate_system_check", False) to disable strict checking and fall back to global coordinate system setting.

CoordinateSystemMismatchError

If inputs have different coordinate systems.

Note
  1. The default output format, i.e. LazyFrame, is recommended for large datasets as it supports output streaming and lazy evaluation. This enables efficient processing of large datasets without loading the entire output dataset into memory.
  2. Streaming is only supported for polars.LazyFrame output.
Example
import polars_bio as pb
import pandas as pd

df1 = pd.DataFrame([
    ['chr1', 1, 5],
    ['chr1', 3, 8],
    ['chr1', 8, 10],
    ['chr1', 12, 14]],
columns=['chrom', 'start', 'end']
)
df1.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

df2 = pd.DataFrame(
[['chr1', 4, 8],
 ['chr1', 10, 11]],
columns=['chrom', 'start', 'end' ]
)
df2.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

overlapping_intervals = pb.overlap(df1, df2, output_type="pandas.DataFrame")

overlapping_intervals
    chrom_1         start_1     end_1 chrom_2       start_2  end_2
0     chr1            1          5     chr1            4          8
1     chr1            3          8     chr1            4          8
Todo

Support for on_cols.

Source code in polars_bio/range_op.py
@staticmethod
def overlap(
    df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    suffixes: tuple[str, str] = ("_1", "_2"),
    on_cols: Union[list[str], None] = None,
    cols1: Union[list[str], None] = ["chrom", "start", "end"],
    cols2: Union[list[str], None] = ["chrom", "start", "end"],
    algorithm: str = "Coitrees",
    low_memory: bool = False,
    overlap_output: Literal["join", "left"] = "join",
    distinct_output: bool = False,
    output_type: str = "polars.LazyFrame",
    read_options1: Union[ReadOptions, None] = None,
    read_options2: Union[ReadOptions, None] = None,
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Find pairs of overlapping genomic intervals.
    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time. Both inputs must have the same coordinate
    system.

    Output modes (`overlap_output`, `distinct_output`):

    - `overlap_output="join"` (default): returns joined pairs, suffixing columns from both
      inputs. This is the historical behavior.
    - `overlap_output="left"`: returns only rows from `df1` that overlap at least one row in
      `df2`, keeping `df1` columns with their original names. By default it preserves overlap
      multiplicity — one `df1` row for each matching `df2` row.
    - `overlap_output="left", distinct_output=True`: returns each overlapping `df1` row once by
      row identity. Duplicate `df1` rows are preserved by identity; this does not apply
      `DISTINCT` over projected values.

    ```python
    pb.overlap(df1, df2, overlap_output="join")                        # joined pairs (default)
    pb.overlap(df1, df2, overlap_output="left")                        # df1 rows overlapping df2
    pb.overlap(df1, df2, overlap_output="left", distinct_output=True)  # each df1 row once
    ```

    Parameters:
        df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table (see [register_vcf][polars_bio.data_processing.register_vcf]). CSV with a header, BED and Parquet are supported.
        df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame or a registered table. CSV with a header, BED  and Parquet are supported.
        cols1: The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        cols2:  The names of columns containing the chromosome, start and end of the
            genomic intervals, provided separately for each set.
        suffixes: Suffixes for the columns of the two overlapped sets.
        on_cols: List of additional column names to join on. default is None.
        algorithm: The algorithm to use for the overlap operation. Available options: Coitrees, IntervalTree, ArrayIntervalTree, Lapper, SuperIntervals
        low_memory: If True, use low memory method for output generation. This caps the output batch size, trading some performance for significantly lower peak memory consumption. Recommended for operations that produce very large result sets.
        overlap_output: Output shape for overlap. "join" returns the current joined df1/df2 rows with suffixes. "left" returns only df1 rows that overlap at least one df2 row, preserving duplicate df1 rows and original column names.
        distinct_output: When overlap_output="left", True returns each overlapping df1 row once by row identity. False returns one df1 row per matching df2 row.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        read_options1: Additional options for reading the input files.
        read_options2: Additional options for reading the input files.
        projection_pushdown: Enable column projection pushdown to optimize query performance by only reading the necessary columns at the DataFusion level.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the overlapping intervals.

    Raises:
        MissingCoordinateSystemError: If either input lacks coordinate system metadata
            and `datafusion.bio.coordinate_system_check` is "true" (default). Use polars-bio
            I/O functions (scan_*, read_*) which automatically set metadata, or set it manually
            on Polars DataFrames via `df.config_meta.set(coordinate_system_zero_based=True/False)`
            or on Pandas DataFrames via `df.attrs["coordinate_system_zero_based"] = True/False`.
            Set `pb.set_option("datafusion.bio.coordinate_system_check", False)` to disable
            strict checking and fall back to global coordinate system setting.
        CoordinateSystemMismatchError: If inputs have different coordinate systems.

    Note:
        1. The default output format, i.e.  [LazyFrame](https://docs.pola.rs/api/python/stable/reference/lazyframe/index.html), is recommended for large datasets as it supports output streaming and lazy evaluation.
        This enables efficient processing of large datasets without loading the entire output dataset into memory.
        2. Streaming is only supported for polars.LazyFrame output.

    Example:
        ```python
        import polars_bio as pb
        import pandas as pd

        df1 = pd.DataFrame([
            ['chr1', 1, 5],
            ['chr1', 3, 8],
            ['chr1', 8, 10],
            ['chr1', 12, 14]],
        columns=['chrom', 'start', 'end']
        )
        df1.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

        df2 = pd.DataFrame(
        [['chr1', 4, 8],
         ['chr1', 10, 11]],
        columns=['chrom', 'start', 'end' ]
        )
        df2.attrs["coordinate_system_zero_based"] = False  # 1-based coordinates

        overlapping_intervals = pb.overlap(df1, df2, output_type="pandas.DataFrame")

        overlapping_intervals
            chrom_1         start_1     end_1 chrom_2       start_2  end_2
        0     chr1            1          5     chr1            4          8
        1     chr1            3          8     chr1            4          8

        ```

    Todo:
         Support for on_cols.
    """

    _validate_overlap_input(cols1, cols2, on_cols, suffixes, output_type)

    # Get filter_op from DataFrame metadata
    filter_op = _get_filter_op_from_metadata(df1, df2)

    cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
    cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
    range_options = RangeOptions(
        range_op=RangeOp.Overlap,
        filter_op=filter_op,
        suffixes=suffixes,
        columns_1=cols1,
        columns_2=cols2,
        overlap_alg=algorithm,
        overlap_low_memory=low_memory,
        overlap_output=_parse_overlap_output_mode(overlap_output),
        distinct_output=distinct_output,
    )

    return range_operation(
        df1,
        df2,
        range_options,
        output_type,
        ctx,
        read_options1,
        read_options2,
        projection_pushdown,
    )

subtract(df1, df2, cols1=['chrom', 'start', 'end'], cols2=['chrom', 'start', 'end'], output_type='polars.LazyFrame', projection_pushdown=True) staticmethod

Subtract the second set of intervals from the first.

For each interval in df1, removes any portion that overlaps with intervals in df2. The result contains the remaining fragments.

Bioframe inspired API.

The coordinate system (0-based or 1-based) is automatically detected from DataFrame metadata set at I/O time. Both inputs must have the same coordinate system.

Parameters:

Name Type Description Default
df1 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.

required
df2 Union[str, DataFrame, LazyFrame, 'pd.DataFrame']

Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.

required
cols1 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals for the first set.

['chrom', 'start', 'end']
cols2 Union[list[str], None]

The names of columns containing the chromosome, start and end of the genomic intervals for the second set.

['chrom', 'start', 'end']
output_type str

Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.

'polars.LazyFrame'
projection_pushdown bool

Enable column projection pushdown.

True

Returns:

Type Description
Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

polars.LazyFrame or polars.DataFrame or pandas.DataFrame of the

Union[LazyFrame, DataFrame, 'pd.DataFrame', DataFrame]

remaining interval fragments (contig, start, end).

Raises:

Type Description
MissingCoordinateSystemError

If either input lacks coordinate system metadata and datafusion.bio.coordinate_system_check is "true" (default).

CoordinateSystemMismatchError

If inputs have different coordinate systems.

Source code in polars_bio/range_op.py
@staticmethod
def subtract(
    df1: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    df2: Union[str, pl.DataFrame, pl.LazyFrame, "pd.DataFrame"],
    cols1: Union[list[str], None] = ["chrom", "start", "end"],
    cols2: Union[list[str], None] = ["chrom", "start", "end"],
    output_type: str = "polars.LazyFrame",
    projection_pushdown: bool = True,
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame", datafusion.DataFrame]:
    """
    Subtract the second set of intervals from the first.

    For each interval in ``df1``, removes any portion that overlaps with
    intervals in ``df2``. The result contains the remaining fragments.

    Bioframe inspired API.

    The coordinate system (0-based or 1-based) is automatically detected from
    DataFrame metadata set at I/O time. Both inputs must have the same coordinate
    system.

    Parameters:
        df1: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
        df2: Can be a path to a file, a polars DataFrame, or a pandas DataFrame. CSV with a header, BED and Parquet are supported.
        cols1: The names of columns containing the chromosome, start and end of the
            genomic intervals for the first set.
        cols2: The names of columns containing the chromosome, start and end of the
            genomic intervals for the second set.
        output_type: Type of the output. default is "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame" or "datafusion.DataFrame" are also supported.
        projection_pushdown: Enable column projection pushdown.

    Returns:
        **polars.LazyFrame** or polars.DataFrame or pandas.DataFrame of the
        remaining interval fragments (contig, start, end).

    Raises:
        MissingCoordinateSystemError: If either input lacks coordinate system metadata
            and ``datafusion.bio.coordinate_system_check`` is "true" (default).
        CoordinateSystemMismatchError: If inputs have different coordinate systems.
    """
    suffixes = ("_1", "_2")
    _validate_overlap_input(cols1, cols2, None, suffixes, output_type)

    filter_op = _get_filter_op_from_metadata(df1, df2)

    cols1 = DEFAULT_INTERVAL_COLUMNS if cols1 is None else cols1
    cols2 = DEFAULT_INTERVAL_COLUMNS if cols2 is None else cols2
    range_options = RangeOptions(
        range_op=RangeOp.Subtract,
        filter_op=filter_op,
        columns_1=cols1,
        columns_2=cols2,
    )

    return range_operation(
        df1,
        df2,
        range_options,
        output_type,
        ctx,
        projection_pushdown=projection_pushdown,
    )

Pileup

Per-base read depth (pileup) operations on alignment files.

Computes per-position depth from BAM/SAM/CRAM files by walking CIGAR operations, producing mosdepth-compatible coverage blocks.

Source code in polars_bio/pileup_op.py
class PileupOperations:
    """Per-base read depth (pileup) operations on alignment files.

    Computes per-position depth from BAM/SAM/CRAM files by walking CIGAR
    operations, producing mosdepth-compatible coverage blocks.
    """

    @staticmethod
    def depth(
        path: str,
        filter_flag: int = 1796,
        min_mapping_quality: int = 0,
        binary_cigar: bool = True,
        dense_mode: str = "auto",
        use_zero_based: Optional[bool] = None,
        per_base: bool = False,
        output_type: str = "polars.LazyFrame",
    ) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame"]:
        """Compute per-base read depth (pileup) from a BAM/SAM/CRAM file.

        Walks CIGAR operations to produce coverage blocks -- similar to
        mosdepth / samtools depth.

        Args:
            path: Path to alignment file (.bam, .sam, or .cram).
                Index files (BAI/CSI/CRAI) are auto-discovered.
            filter_flag: SAM flag mask -- reads with any of these flags are
                excluded. Default 1796 (unmapped, secondary, failed QC,
                duplicate).
            min_mapping_quality: Minimum MAPQ threshold. Default 0
                (no filter).
            binary_cigar: Use binary CIGAR parsing (faster). Default True.
            dense_mode: Accumulation strategy:

                - ``"auto"`` -- use dense accumulation when contig lengths are
                  available in schema metadata (default).
                - ``"force"`` -- always use dense accumulation.
                - ``"disable"`` -- always use sparse (event-list) accumulation.
            use_zero_based: Coordinate system for output positions.

                - ``None`` (default) -- use global config (``pb.options``),
                  which defaults to 1-based.
                - ``True`` -- 0-based half-open coordinates.
                - ``False`` -- 1-based closed coordinates.
            per_base: If True, emit one row per genomic position (like
                ``samtools depth -a``) instead of RLE coverage blocks.
                Requires dense mode (BAM header with contig lengths).
                Default False.
            output_type: One of ``"polars.LazyFrame"``,
                ``"polars.DataFrame"``, or ``"pandas.DataFrame"``.

        Returns:
            DataFrame with columns depending on ``per_base``:

            - Block mode (default): ``contig`` (Utf8), ``pos_start`` (Int32),
              ``pos_end`` (Int32), ``coverage`` (Int16).
            - Per-base mode: ``contig`` (Utf8), ``pos`` (Int32),
              ``coverage`` (Int16).

        Example:
            ```python
            import polars_bio as pb

            # Basic depth computation (RLE blocks)
            df = pb.depth("alignments.bam").collect()

            # Per-base output (one row per position)
            df = pb.depth("alignments.bam", per_base=True).collect()

            # With MAPQ filter
            df = pb.depth("alignments.bam", min_mapping_quality=20).collect()

            # As pandas DataFrame
            pdf = pb.depth("alignments.bam", output_type="pandas.DataFrame")
            ```
        """
        from polars_bio.polars_bio import (
            PileupOptions,
            py_get_table_schema,
            py_register_pileup_table,
        )

        zero_based = _resolve_zero_based(use_zero_based)

        opts = PileupOptions(
            filter_flag=filter_flag,
            min_mapping_quality=min_mapping_quality,
            binary_cigar=binary_cigar,
            dense_mode=dense_mode,
            zero_based=zero_based,
            per_base=per_base,
        )

        # 1. Register table (no execution)
        table_name = py_register_pileup_table(ctx, path, opts)

        # 2. Get schema without materializing data
        schema = py_get_table_schema(ctx, table_name)
        empty_table = pa.table(
            {field.name: pa.array([], type=field.type) for field in schema}
        )
        polars_schema = dict(pl.from_arrow(empty_table).schema)

        # 3. Define streaming callback (executed only on .collect())
        def _pileup_source(
            with_columns: Union[pl.Expr, None],
            predicate: Union[pl.Expr, None],
            n_rows: Union[int, None],
            _batch_size: Union[int, None],
        ) -> Iterator[pl.DataFrame]:
            from polars_bio.polars_bio import py_read_table

            from .context import ctx as _ctx

            query_df = py_read_table(_ctx, table_name)

            # Projection pushdown
            projection_applied = False
            if with_columns is not None:
                requested_cols = _extract_column_names_from_expr(with_columns)
                if requested_cols:
                    try:
                        select_exprs = [
                            query_df.parse_sql_expr(f'"{c}"') for c in requested_cols
                        ]
                        query_df = query_df.select(*select_exprs)
                        projection_applied = True
                    except Exception as e:
                        logger.debug("Projection pushdown failed: %s", e)

            # Predicate pushdown (optimization only; client-side filter is truth)
            needs_client_filter = predicate is not None
            if predicate is not None:
                from .pushdown import apply_predicate_pushdown

                query_df, needs_client_filter = apply_predicate_pushdown(
                    query_df,
                    predicate,
                    {
                        "string_cols": {"contig"},
                        "uint32_cols": {"pos", "pos_start", "pos_end", "coverage"},
                        "float32_cols": None,
                    },
                    log=logger,
                )

            # Limit pushdown
            if n_rows and n_rows > 0:
                query_df = query_df.limit(int(n_rows))

            # Stream batches
            df_stream = query_df.execute_stream()
            progress_bar = tqdm(unit="rows")
            remaining = int(n_rows) if n_rows is not None else None

            for batch in df_stream:
                out = pl.DataFrame(batch.to_pyarrow())

                # Client-side predicate filtering (source of truth)
                if predicate is not None and needs_client_filter:
                    out = out.filter(predicate)

                # Client-side projection fallback
                if with_columns is not None and not projection_applied:
                    out = out.select(with_columns)

                if remaining is not None:
                    if remaining <= 0:
                        break
                    if len(out) > remaining:
                        out = out.head(remaining)
                    remaining -= len(out)

                progress_bar.update(len(out))
                yield out

                if remaining is not None and remaining <= 0:
                    return

            # Clean up registered table to free memory
            try:
                _ctx.deregister_table(table_name)
            except Exception:
                pass

        # 4. Create lazy frame
        lf = register_io_source(_pileup_source, schema=polars_schema)
        set_coordinate_system(lf, zero_based)

        # 5. Handle output_type
        if output_type == "polars.LazyFrame":
            return lf
        elif output_type == "polars.DataFrame":
            return lf.collect()
        elif output_type == "pandas.DataFrame":
            if pd is None:
                raise ImportError(
                    "pandas is not installed. Please run `pip install pandas` "
                    "or `pip install polars-bio[pandas]`."
                )
            return lf.collect().to_pandas()
        else:
            raise ValueError(f"Invalid output_type: {output_type!r}")

depth(path, filter_flag=1796, min_mapping_quality=0, binary_cigar=True, dense_mode='auto', use_zero_based=None, per_base=False, output_type='polars.LazyFrame') staticmethod

Compute per-base read depth (pileup) from a BAM/SAM/CRAM file.

Walks CIGAR operations to produce coverage blocks -- similar to mosdepth / samtools depth.

Parameters:

Name Type Description Default
path str

Path to alignment file (.bam, .sam, or .cram). Index files (BAI/CSI/CRAI) are auto-discovered.

required
filter_flag int

SAM flag mask -- reads with any of these flags are excluded. Default 1796 (unmapped, secondary, failed QC, duplicate).

1796
min_mapping_quality int

Minimum MAPQ threshold. Default 0 (no filter).

0
binary_cigar bool

Use binary CIGAR parsing (faster). Default True.

True
dense_mode str

Accumulation strategy:

  • "auto" -- use dense accumulation when contig lengths are available in schema metadata (default).
  • "force" -- always use dense accumulation.
  • "disable" -- always use sparse (event-list) accumulation.
'auto'
use_zero_based Optional[bool]

Coordinate system for output positions.

  • None (default) -- use global config (pb.options), which defaults to 1-based.
  • True -- 0-based half-open coordinates.
  • False -- 1-based closed coordinates.
None
per_base bool

If True, emit one row per genomic position (like samtools depth -a) instead of RLE coverage blocks. Requires dense mode (BAM header with contig lengths). Default False.

False
output_type str

One of "polars.LazyFrame", "polars.DataFrame", or "pandas.DataFrame".

'polars.LazyFrame'

Returns:

Type Description
Union[LazyFrame, DataFrame, DataFrame]

DataFrame with columns depending on per_base:

Union[LazyFrame, DataFrame, DataFrame]
  • Block mode (default): contig (Utf8), pos_start (Int32), pos_end (Int32), coverage (Int16).
Union[LazyFrame, DataFrame, DataFrame]
  • Per-base mode: contig (Utf8), pos (Int32), coverage (Int16).
Example
import polars_bio as pb

# Basic depth computation (RLE blocks)
df = pb.depth("alignments.bam").collect()

# Per-base output (one row per position)
df = pb.depth("alignments.bam", per_base=True).collect()

# With MAPQ filter
df = pb.depth("alignments.bam", min_mapping_quality=20).collect()

# As pandas DataFrame
pdf = pb.depth("alignments.bam", output_type="pandas.DataFrame")
Source code in polars_bio/pileup_op.py
@staticmethod
def depth(
    path: str,
    filter_flag: int = 1796,
    min_mapping_quality: int = 0,
    binary_cigar: bool = True,
    dense_mode: str = "auto",
    use_zero_based: Optional[bool] = None,
    per_base: bool = False,
    output_type: str = "polars.LazyFrame",
) -> Union[pl.LazyFrame, pl.DataFrame, "pd.DataFrame"]:
    """Compute per-base read depth (pileup) from a BAM/SAM/CRAM file.

    Walks CIGAR operations to produce coverage blocks -- similar to
    mosdepth / samtools depth.

    Args:
        path: Path to alignment file (.bam, .sam, or .cram).
            Index files (BAI/CSI/CRAI) are auto-discovered.
        filter_flag: SAM flag mask -- reads with any of these flags are
            excluded. Default 1796 (unmapped, secondary, failed QC,
            duplicate).
        min_mapping_quality: Minimum MAPQ threshold. Default 0
            (no filter).
        binary_cigar: Use binary CIGAR parsing (faster). Default True.
        dense_mode: Accumulation strategy:

            - ``"auto"`` -- use dense accumulation when contig lengths are
              available in schema metadata (default).
            - ``"force"`` -- always use dense accumulation.
            - ``"disable"`` -- always use sparse (event-list) accumulation.
        use_zero_based: Coordinate system for output positions.

            - ``None`` (default) -- use global config (``pb.options``),
              which defaults to 1-based.
            - ``True`` -- 0-based half-open coordinates.
            - ``False`` -- 1-based closed coordinates.
        per_base: If True, emit one row per genomic position (like
            ``samtools depth -a``) instead of RLE coverage blocks.
            Requires dense mode (BAM header with contig lengths).
            Default False.
        output_type: One of ``"polars.LazyFrame"``,
            ``"polars.DataFrame"``, or ``"pandas.DataFrame"``.

    Returns:
        DataFrame with columns depending on ``per_base``:

        - Block mode (default): ``contig`` (Utf8), ``pos_start`` (Int32),
          ``pos_end`` (Int32), ``coverage`` (Int16).
        - Per-base mode: ``contig`` (Utf8), ``pos`` (Int32),
          ``coverage`` (Int16).

    Example:
        ```python
        import polars_bio as pb

        # Basic depth computation (RLE blocks)
        df = pb.depth("alignments.bam").collect()

        # Per-base output (one row per position)
        df = pb.depth("alignments.bam", per_base=True).collect()

        # With MAPQ filter
        df = pb.depth("alignments.bam", min_mapping_quality=20).collect()

        # As pandas DataFrame
        pdf = pb.depth("alignments.bam", output_type="pandas.DataFrame")
        ```
    """
    from polars_bio.polars_bio import (
        PileupOptions,
        py_get_table_schema,
        py_register_pileup_table,
    )

    zero_based = _resolve_zero_based(use_zero_based)

    opts = PileupOptions(
        filter_flag=filter_flag,
        min_mapping_quality=min_mapping_quality,
        binary_cigar=binary_cigar,
        dense_mode=dense_mode,
        zero_based=zero_based,
        per_base=per_base,
    )

    # 1. Register table (no execution)
    table_name = py_register_pileup_table(ctx, path, opts)

    # 2. Get schema without materializing data
    schema = py_get_table_schema(ctx, table_name)
    empty_table = pa.table(
        {field.name: pa.array([], type=field.type) for field in schema}
    )
    polars_schema = dict(pl.from_arrow(empty_table).schema)

    # 3. Define streaming callback (executed only on .collect())
    def _pileup_source(
        with_columns: Union[pl.Expr, None],
        predicate: Union[pl.Expr, None],
        n_rows: Union[int, None],
        _batch_size: Union[int, None],
    ) -> Iterator[pl.DataFrame]:
        from polars_bio.polars_bio import py_read_table

        from .context import ctx as _ctx

        query_df = py_read_table(_ctx, table_name)

        # Projection pushdown
        projection_applied = False
        if with_columns is not None:
            requested_cols = _extract_column_names_from_expr(with_columns)
            if requested_cols:
                try:
                    select_exprs = [
                        query_df.parse_sql_expr(f'"{c}"') for c in requested_cols
                    ]
                    query_df = query_df.select(*select_exprs)
                    projection_applied = True
                except Exception as e:
                    logger.debug("Projection pushdown failed: %s", e)

        # Predicate pushdown (optimization only; client-side filter is truth)
        needs_client_filter = predicate is not None
        if predicate is not None:
            from .pushdown import apply_predicate_pushdown

            query_df, needs_client_filter = apply_predicate_pushdown(
                query_df,
                predicate,
                {
                    "string_cols": {"contig"},
                    "uint32_cols": {"pos", "pos_start", "pos_end", "coverage"},
                    "float32_cols": None,
                },
                log=logger,
            )

        # Limit pushdown
        if n_rows and n_rows > 0:
            query_df = query_df.limit(int(n_rows))

        # Stream batches
        df_stream = query_df.execute_stream()
        progress_bar = tqdm(unit="rows")
        remaining = int(n_rows) if n_rows is not None else None

        for batch in df_stream:
            out = pl.DataFrame(batch.to_pyarrow())

            # Client-side predicate filtering (source of truth)
            if predicate is not None and needs_client_filter:
                out = out.filter(predicate)

            # Client-side projection fallback
            if with_columns is not None and not projection_applied:
                out = out.select(with_columns)

            if remaining is not None:
                if remaining <= 0:
                    break
                if len(out) > remaining:
                    out = out.head(remaining)
                remaining -= len(out)

            progress_bar.update(len(out))
            yield out

            if remaining is not None and remaining <= 0:
                return

        # Clean up registered table to free memory
        try:
            _ctx.deregister_table(table_name)
        except Exception:
            pass

    # 4. Create lazy frame
    lf = register_io_source(_pileup_source, schema=polars_schema)
    set_coordinate_system(lf, zero_based)

    # 5. Handle output_type
    if output_type == "polars.LazyFrame":
        return lf
    elif output_type == "polars.DataFrame":
        return lf.collect()
    elif output_type == "pandas.DataFrame":
        if pd is None:
            raise ImportError(
                "pandas is not installed. Please run `pip install pandas` "
                "or `pip install polars-bio[pandas]`."
            )
        return lf.collect().to_pandas()
    else:
        raise ValueError(f"Invalid output_type: {output_type!r}")

FastQC

FastQC quality-control operations on FASTQ files.

Source code in polars_bio/fastqc_op.py
class FastQCOperations:
    """FastQC quality-control operations on FASTQ files."""

    @staticmethod
    def fastqc(
        path: str,
        modules: Optional[List[str]] = None,
        group: bool = True,
    ) -> FastQCResult:
        """Compute FastQC modules over a FASTQ file in one streaming pass.

        Args:
            path: Path to a FASTQ file (plain, .gz, or .bgz).
            modules: Module names to compute; ``None`` computes all twelve
                (``basic_stats``, ``per_base_quality``, ``per_seq_quality``,
                ``per_base_content``, ``per_seq_gc``, ``per_base_n``,
                ``seq_length``, ``overrepresented``, ``adapter_content``,
                ``dup_levels``, ``per_tile_quality``, ``kmer_content``).
                Accessing a non-computed module on the result raises ``KeyError``.
            group: Reserved for FastQC-style position binning of long reads
                (``group=False`` == FastQC ``--nogroup``). No-op for Phase 1
                modules.

        Returns:
            FastQCResult with ``.tidy``, per-module LazyFrames, and
            ``.summary()``.

        Example:
            ```python
            import polars_bio as pb

            qc = pb.fastqc("reads_R1.fastq.gz")
            qc.per_base_quality.collect()
            qc.summary().collect()
            ```
        """
        if modules is not None:
            if not modules:
                raise ValueError(
                    "modules list must not be empty; pass None to compute all modules"
                )
            unknown = [m for m in modules if m not in ALL_MODULES]
            if unknown:
                raise ValueError(
                    f"unknown fastqc modules {unknown}; valid: {ALL_MODULES}"
                )
        computed = list(modules) if modules is not None else list(ALL_MODULES)
        if not group:
            warnings.warn(
                "group=False (FastQC --nogroup) is not yet implemented; all Phase 1 "
                "modules are computed as if group=True. The parameter is reserved for "
                "future position-binning support and currently has no effect.",
                UserWarning,
                stacklevel=2,
            )
        tidy = _run_tidy(path, modules)
        return FastQCResult(tidy, computed)

fastqc(path, modules=None, group=True) staticmethod

Compute FastQC modules over a FASTQ file in one streaming pass.

Parameters:

Name Type Description Default
path str

Path to a FASTQ file (plain, .gz, or .bgz).

required
modules Optional[List[str]]

Module names to compute; None computes all twelve (basic_stats, per_base_quality, per_seq_quality, per_base_content, per_seq_gc, per_base_n, seq_length, overrepresented, adapter_content, dup_levels, per_tile_quality, kmer_content). Accessing a non-computed module on the result raises KeyError.

None
group bool

Reserved for FastQC-style position binning of long reads (group=False == FastQC --nogroup). No-op for Phase 1 modules.

True

Returns:

Type Description
FastQCResult

FastQCResult with .tidy, per-module LazyFrames, and

FastQCResult

.summary().

Example
import polars_bio as pb

qc = pb.fastqc("reads_R1.fastq.gz")
qc.per_base_quality.collect()
qc.summary().collect()
Source code in polars_bio/fastqc_op.py
@staticmethod
def fastqc(
    path: str,
    modules: Optional[List[str]] = None,
    group: bool = True,
) -> FastQCResult:
    """Compute FastQC modules over a FASTQ file in one streaming pass.

    Args:
        path: Path to a FASTQ file (plain, .gz, or .bgz).
        modules: Module names to compute; ``None`` computes all twelve
            (``basic_stats``, ``per_base_quality``, ``per_seq_quality``,
            ``per_base_content``, ``per_seq_gc``, ``per_base_n``,
            ``seq_length``, ``overrepresented``, ``adapter_content``,
            ``dup_levels``, ``per_tile_quality``, ``kmer_content``).
            Accessing a non-computed module on the result raises ``KeyError``.
        group: Reserved for FastQC-style position binning of long reads
            (``group=False`` == FastQC ``--nogroup``). No-op for Phase 1
            modules.

    Returns:
        FastQCResult with ``.tidy``, per-module LazyFrames, and
        ``.summary()``.

    Example:
        ```python
        import polars_bio as pb

        qc = pb.fastqc("reads_R1.fastq.gz")
        qc.per_base_quality.collect()
        qc.summary().collect()
        ```
    """
    if modules is not None:
        if not modules:
            raise ValueError(
                "modules list must not be empty; pass None to compute all modules"
            )
        unknown = [m for m in modules if m not in ALL_MODULES]
        if unknown:
            raise ValueError(
                f"unknown fastqc modules {unknown}; valid: {ALL_MODULES}"
            )
    computed = list(modules) if modules is not None else list(ALL_MODULES)
    if not group:
        warnings.warn(
            "group=False (FastQC --nogroup) is not yet implemented; all Phase 1 "
            "modules are computed as if group=True. The parameter is reserved for "
            "future position-binning support and currently has no effect.",
            UserWarning,
            stacklevel=2,
        )
    tidy = _run_tidy(path, modules)
    return FastQCResult(tidy, computed)