Skip to content

Commit d79ce68

Browse files
endre bakken stovnerendre bakken stovner
authored andcommitted
Merge branch 'master' of github.com:biocore-ntnu/pyranges
2 parents aaf296b + 586b62a commit d79ce68

8 files changed

Lines changed: 139 additions & 44 deletions

File tree

CHANGELOG.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
# 0.0.109 (16.09.21)
2+
- fix overlap invert behavior
3+
- add intersect invert flag
4+
- fix subtract in cases where more than 1024 intervals overlapped a single interval
5+
16
# 0.0.106/107/108(hotfixes) (07/8.09.21)
27
- fix join with slack mutating first arg
38
- add flag use_other_strand in join, nearest, k_nearest

pyranges/methods/intersection.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ def _intersection(scdf, ocdf, **kwargs):
6161

6262
def _overlap(scdf, ocdf, **kwargs):
6363

64-
invert = kwargs["invert"]
6564
return_indexes = kwargs.get("return_indexes", False)
6665

6766
if scdf.empty or ocdf.empty:
@@ -83,9 +82,6 @@ def _overlap(scdf, ocdf, **kwargs):
8382
else:
8483
_indexes = it.has_overlaps(starts, ends, indexes)
8584

86-
if invert:
87-
_indexes = scdf.index.difference(_indexes)
88-
8985
if return_indexes:
9086
return _indexes
9187

pyranges/methods/subtraction.py

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,87 @@
11
import pandas as pd
2-
from ncls import NCLS
2+
import numpy as np
33

44

5-
def _subtraction(scdf, ocdf, **kwargs):
5+
def add_rows_per_group(df):
6+
last_rows = df.groupby("__ix__").last().reset_index()
7+
last_rows.loc[:, "__last__"] = True
8+
df = pd.concat([df, last_rows], ignore_index=True)
9+
df = df.sort_values("__ix__", ascending=True)
10+
return df
611

7-
if ocdf.empty or scdf.empty:
12+
def _subtraction(scdf, **kwargs):
13+
if scdf.empty:
814
return scdf
915

10-
strandedness = kwargs["strandedness"]
11-
strand = True if strandedness else False
16+
falses = np.zeros(len(scdf), dtype=bool)
17+
scdf.insert(scdf.shape[1], "__first__", falses)
18+
scdf.insert(scdf.shape[1], "__last__", falses)
1219

13-
chromosome = scdf.Chromosome.head(1).iloc[0]
14-
kwargs["chromosome"] = chromosome
20+
scdf = add_rows_per_group(scdf)
1521

16-
if "Strand" in ocdf and strand:
17-
strand = scdf.Strand.head(1).iloc[0]
18-
kwargs["strand"] = strand
22+
scdf.insert(scdf.shape[1], "NewStart", scdf.End__deleteme__.shift(fill_value=-1))
23+
scdf.insert(scdf.shape[1], "NewEnd", scdf.Start__deleteme__)
24+
scdf.insert(scdf.shape[1], "__ix2__", np.arange(len(scdf)))
1925

20-
o = NCLS(ocdf.Start.values, ocdf.End.values, ocdf.index.values)
26+
first_rows = scdf.groupby(scdf.__ix__, as_index=False).first()
27+
scdf.loc[scdf.__ix2__.isin(first_rows.__ix2__), "__first__"] = True
2128

22-
idx_self, new_starts, new_ends = o.set_difference_helper(
23-
scdf.Start.values, scdf.End.values, scdf.index.values)
29+
scdf.loc[:, "NewStart"] = np.where(scdf.__first__, scdf.Start, scdf.NewStart)
2430

25-
missing_idx = pd.Index(scdf.index).difference(idx_self)
31+
scdf.loc[scdf.__first__ & ~(scdf.Start__deleteme__ >= scdf.Start), "NewStart"] = -1
32+
scdf.loc[:, "NewEnd"] = np.where(scdf.__last__, scdf.End, scdf.NewEnd)
33+
scdf.loc[:, "NewStart"] = np.where(scdf.__last__, scdf.End__deleteme__, scdf.NewStart)
34+
scdf.loc[scdf.__last__ & ~(scdf.End__deleteme__ <= scdf.End), ["NewEnd", "NewStart"]] = -1
2635

27-
idx_to_drop = new_starts != -1
2836

29-
new_starts = new_starts[idx_to_drop]
30-
new_ends = new_ends[idx_to_drop]
37+
scdf = scdf[~((scdf.NewStart == -1) | (scdf.NewEnd == -1))]
38+
scdf = scdf.drop(["Start", "End"], axis=1)
39+
scdf.rename(columns={"NewStart": "Start", "NewEnd": "End"}, inplace=True)
3140

32-
idx_self = idx_self[idx_to_drop]
33-
new_starts = pd.Series(new_starts, index=idx_self)
34-
new_ends = pd.Series(new_ends, index=idx_self)
41+
remove_mask = scdf.Start >= scdf.End
3542

36-
scdf = scdf.reindex(missing_idx.union(idx_self)).sort_index()
37-
new_starts = new_starts.sort_index()
38-
new_ends = new_ends.sort_index()
43+
return scdf[~remove_mask]
3944

40-
if len(idx_self):
41-
scdf.loc[scdf.index.isin(idx_self), "Start"] = new_starts.values
42-
scdf.loc[scdf.index.isin(idx_self), "End"] = new_ends.values
45+
# def _subtraction(scdf, ocdf, **kwargs):
4346

44-
if not scdf.empty:
45-
return scdf
46-
else:
47-
return None
47+
# if ocdf.empty or scdf.empty:
48+
# return scdf
49+
50+
# strandedness = kwargs["strandedness"]
51+
# strand = True if strandedness else False
52+
53+
# chromosome = scdf.Chromosome.head(1).iloc[0]
54+
# kwargs["chromosome"] = chromosome
55+
56+
# if "Strand" in ocdf and strand:
57+
# strand = scdf.Strand.head(1).iloc[0]
58+
# kwargs["strand"] = strand
59+
60+
# o = NCLS(ocdf.Start.values, ocdf.End.values, ocdf.index.values)
61+
62+
# idx_self, new_starts, new_ends = o.set_difference_helper(
63+
# scdf.Start.values, scdf.End.values, scdf.index.values)
64+
65+
# missing_idx = pd.Index(scdf.index).difference(idx_self)
66+
67+
# idx_to_drop = new_starts != -1
68+
69+
# new_starts = new_starts[idx_to_drop]
70+
# new_ends = new_ends[idx_to_drop]
71+
72+
# idx_self = idx_self[idx_to_drop]
73+
# new_starts = pd.Series(new_starts, index=idx_self)
74+
# new_ends = pd.Series(new_ends, index=idx_self)
75+
76+
# scdf = scdf.reindex(missing_idx.union(idx_self)).sort_index()
77+
# new_starts = new_starts.sort_index()
78+
# new_ends = new_ends.sort_index()
79+
80+
# if len(idx_self):
81+
# scdf.loc[scdf.index.isin(idx_self), "Start"] = new_starts.values
82+
# scdf.loc[scdf.index.isin(idx_self), "End"] = new_ends.values
83+
84+
# if not scdf.empty:
85+
# return scdf
86+
# else:
87+
# return None

pyranges/pyranges.py

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1854,7 +1854,7 @@ def insert(self, other, loc=None):
18541854
return self
18551855

18561856

1857-
def intersect(self, other, strandedness=None, how=None, nb_cpu=1):
1857+
def intersect(self, other, strandedness=None, how=None, invert=False, nb_cpu=1):
18581858

18591859
"""Return overlapping subintervals.
18601860
@@ -1877,6 +1877,10 @@ def intersect(self, other, strandedness=None, how=None, nb_cpu=1):
18771877
What intervals to report. By default reports all overlapping intervals. "containment"
18781878
reports intervals where the overlapping is contained within it.
18791879
1880+
invert : bool, default False
1881+
1882+
Whether to return the intervals without overlaps.
1883+
18801884
nb_cpu: int, default 1
18811885
18821886
How many cpus to use. Can at most use 1 per chromosome or chromosome/strand tuple.
@@ -1962,9 +1966,21 @@ def intersect(self, other, strandedness=None, how=None, nb_cpu=1):
19621966
kwargs = fill_kwargs(kwargs)
19631967
kwargs["sparse"] = {"self": False, "other": True}
19641968

1969+
if len(self) == 0:
1970+
return self
1971+
1972+
if invert:
1973+
self.__ix__ = np.arange(len(self))
1974+
19651975
dfs = pyrange_apply(_intersection, self, other, **kwargs)
1976+
result = pr.PyRanges(dfs)
19661977

1967-
return PyRanges(dfs)
1978+
if invert:
1979+
found_idxs = getattr(result, "__ix__", [])
1980+
result = self[~self.__ix__.isin(found_idxs)]
1981+
result = result.drop("__ix__")
1982+
1983+
return result
19681984

19691985
def items(self):
19701986

@@ -3195,6 +3211,10 @@ def overlap(self, other, strandedness=None, how="first", invert=False, nb_cpu=1)
31953211
What intervals to report. By default reports every interval in self with overlap once.
31963212
"containment" reports all intervals where the overlapping is contained within it.
31973213
3214+
invert : bool, default False
3215+
3216+
Whether to return the intervals without overlaps.
3217+
31983218
nb_cpu: int, default 1
31993219
32003220
How many cpus to use. Can at most use 1 per chromosome or chromosome/strand tuple.
@@ -3292,9 +3312,22 @@ def overlap(self, other, strandedness=None, how="first", invert=False, nb_cpu=1)
32923312
kwargs["invert"] = invert
32933313
kwargs = fill_kwargs(kwargs)
32943314

3315+
if len(self) == 0:
3316+
return self
3317+
3318+
if invert:
3319+
self = self.copy()
3320+
self.__ix__ = np.arange(len(self))
3321+
32953322
dfs = pyrange_apply(_overlap, self, other, **kwargs)
3323+
result = pr.PyRanges(dfs)
32963324

3297-
return pr.PyRanges(dfs)
3325+
if invert:
3326+
found_idxs = getattr(result, "__ix__", [])
3327+
result = self[~self.__ix__.isin(found_idxs)]
3328+
result = result.drop("__ix__")
3329+
3330+
return result
32983331

32993332
def pc(self, n=8, formatting=None):
33003333

@@ -4343,16 +4376,27 @@ def subtract(self, other, strandedness=None, nb_cpu=1):
43434376

43444377
from pyranges.methods.subtraction import _subtraction
43454378

4346-
kwargs = {"strandedness": strandedness}
4379+
strand = True if strandedness in ["same", "opposite"] else False
4380+
kwargs = {"strandedness": strandedness, "strand": strand}
43474381
kwargs["sparse"] = {"self": False, "other": True}
43484382
kwargs = fill_kwargs(kwargs)
43494383

43504384
strand = True if strandedness else False
4351-
other_clusters = other.merge(strand=strand)
4352-
4353-
result = pyrange_apply(_subtraction, self, other_clusters, **kwargs)
4385+
if len(other) != 0:
4386+
cols_original_order = self.columns.copy()
4387+
other_clusters = other.merge(strand=strand)
4388+
self.__ix__ = np.arange(len(self))
4389+
no_overlap = self.overlap(other_clusters, strandedness=strandedness, invert=True)
4390+
j = self.sort().join(other_clusters, strandedness=strandedness, suffix="__deleteme__")
4391+
result = pyrange_apply_single(_subtraction, j, **kwargs)
4392+
result = {k: v for (k, v) in result.items()}
4393+
result = PyRanges(result)
4394+
result = pr.concat([result, no_overlap])
4395+
result = PyRanges({k: v[cols_original_order] for k, v in result.items()})
4396+
else:
4397+
result = self
43544398

4355-
return PyRanges(result)
4399+
return result
43564400

43574401
def summary(self, to_stdout=True, return_df=False):
43584402

pyranges/version.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
__version__ = "0.0.108"
2-
1+
__version__ = "0.0.109"

tests/helpers.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
def assert_df_equal(df1, df2):
55

6+
print("-"*100)
7+
print("df1")
8+
print(df1)
9+
print("df2")
10+
print(df2)
11+
612
# df1.loc[:, "Start"] = df1.Start.astype(np.int64)
713
# df2.loc[:, "Start"] = df1.Start.astype(np.int64)
814
# df1.loc[:, "End"] = df1.End.astype(np.int64)

tests/test_binary.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,12 @@ def test_subtraction(gr, gr2, strandedness):
304304
names="Chromosome Start End Name Score Strand".split(),
305305
sep="\t")
306306

307+
print("subtracting" * 50)
307308
result = gr.subtract(gr2, strandedness=strandedness)
308309

310+
print("bedtools_result")
309311
print(bedtools_df)
312+
print("PyRanges result:")
310313
print(result)
311314

312315
compare_results(bedtools_df, result)

tests/test_do_not_error.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
# @reproduce_failure('5.5.4', b'AXicY2RAA4xIJDY+AAC2AAY=') # test_three_in_a_row[strandedness_chain24-method_chain24]
5151
def test_three_in_a_row(gr, gr2, gr3, strandedness_chain, method_chain):
5252

53+
print(method_chain)
54+
5355
s1, s2 = strandedness_chain
5456
f1, f2 = method_chain
5557

0 commit comments

Comments
 (0)