【问题标题】:Python 3: remove overlaps in tablePython 3:删除表中的重叠
【发布时间】:2019-07-24 22:18:05
【问题描述】:

我有一个表格(程序的简化输出),我需要对其进行过滤:

id   hit from   to value
A   hit1    56  102 0.00085
B   hit2    89  275 0.00034
B   hit3    240 349 0.00034
C   hit4    332 480 3.40E-15
D   hit5    291 512 3.80E-24
D   hit6    287 313 0.00098
D   hit7    381 426 0.00098
D   hit8    287 316 0.0029
D   hit9    373 422 0.0029
D   hit10   514 600 0.0021

对于每个 id,df 应按 from 排序,如果有重叠的命中,则保留 value 较低的那个。

到目前为止,这是我的代码,它首先由from 开始,然后由value 开始:

import pandas
df = pandas.read_csv("table", sep='\s+', names=["id", "hit", "from", "to", "value"])
df.sort_values(['from', "value"]).groupby('id')

但是我如何检查重叠(fromto)并删除具有更高分数的重叠?

这是我的预期输出:

id   hit from   to valu
A   hit1    56  102 0.00085
C   hit4    332 480 3.40E-15
D   hit5    291 512 3.80E-24
D   hit10   514 600 0.0021

请注意,id B 有两个相同值的重叠命中,因此两个条目都将被踢出。

【问题讨论】:

  • 你能在测试数据中放一些重叠的值吗?
  • 你能解释一下重叠吗?
  • 很抱歉,如果不清楚。将fromto 视为坐标。因此A 没有任何重叠命中,因为它是唯一的命中,而B 在坐标 240 到 275 中有重叠。D 有 5 个重叠命中,其中 1 个必须根据最低选择value,但最后一个hit10D 的其他部分没有重叠。
  • 似乎在你的逻辑下,id 'D', hit 'hit5' 应该被删除,但你在你的例子中有它。我读对你的逻辑了吗?
  • 应该保留LOWER值的命中

标签: python pandas sorting filtering


【解决方案1】:

如果你不介意你的代码中有多行,我猜这样的东西应该可以工作......(这里是python新手......)source

df.sort_values(['from', "value"]).groupby('id')
df.drop_duplicates(subset=['id', 'value'], keep=False, inplace=True)

"keep" 参数设置为 false,因为您根本不想要重复的行。

这会导致:

  id    hit from   to     value
0  A   hit1   56  102   0.00085
3  C   hit4  332  480  3.40E-15
4  D   hit5  291  512  3.80E-24
9  D  hit10  514  600    0.0021

并摆脱凌乱的索引列:

df.reset_index(drop=True, inplace=True)

这会导致:

  id    hit from   to     value
0  A   hit1   56  102   0.00085
1  C   hit4  332  480  3.40E-15
2  D   hit5  291  512  3.80E-24
3  D  hit10  514  600    0.0021

PS:这是我第一次回答,所以请温柔。而且,我还在学习英语。

【讨论】:

  • 解决方案不好,没有去除重叠,不幸的是数据不是 MCVE,所以得到像需要 OP 一样的输出。但是用另一个数据解决方案失败了。
  • 我同意,不幸的是,这个解决方案只是删除重复值......
  • 那么 this answerthis question 可能会有所帮助? @萨拉哈
【解决方案2】:

首先我们引入一个独特的ID并使用pd.Interval

df['ID'] = range(df.shape[0])
df['Interval'] = df.apply(lambda x: pd.Interval(x['from'], x['to'], closed='both'), axis=1)

在这之后我们加入 df 并计算重叠部分:

columns = ['id', 'Interval', 'ID']
connected = df[columns].merge(df[columns], on='id')
connected['Overlap'] = connected.apply(lambda x: x['Interval_x'].overlaps(x['Interval_y']), axis=1) 
connected = connected.loc[connected['Overlap'] == True, ['id', 'ID_x', 'ID_y']]

现在我们知道哪些 ID 重叠,但我们不知道它们中的哪些构建了连接组件。一般来说,这不能通过像重新编码这样的简单算法来完成,但graph theory 有点帮助。所以我们建立一个图

graph = connected.groupby(['id', 'ID_x']).agg(list)

并通过depth first search计算连通分量

def connections(graph, id):
    def dict_to_df(d):
        df = pd.DataFrame(data=[d.keys(), d.values()], index=['ID', 'Subgraph']).T
        df['id'] = id
        return df[['id', 'Subgraph', 'ID']]

    def dfs(node, num):
        visited[node] = num
        for _node in graph.loc[node].iloc[0]:
            if _node not in visited:
                dfs(_node, num)

    visited = {}
    graph = graph.loc[id]
    for (num, node) in enumerate(graph.index):
        if node not in visited:
            dfs(node, num)

    return dict_to_df(visited)

dfs = []
for id in graph.index.get_level_values(0).unique():
    dfs.append(connections(graph, id))

conns = pd.concat(dfs)

conns 保存连接的组件,我们可以将它们放在一起:

data = df.merge(conns[['Subgraph', 'ID']], on=['ID'])

我们的最后一个任务是选择我们想要保留的行:

def select_min(x):
    m = x['value'].min()
    if len(x) > 1 and (x['value'] == m).all():
        return -1
    else:
        return x['value'].idxmin()

selected = data.groupby(['id', 'Subgraph'])['value', 'ID'].apply(select_min)
selected = selected[selected >= 0]

现在我们完成了:

print(df.loc[df.ID.isin(selected), :].drop(columns=['ID', 'Interval']))
  id    hit  from   to         value
0  A   hit1    56  102  8.500000e-04
3  C   hit4   332  480  3.400000e-15
4  D   hit5   291  512  3.800000e-24
9  D  hit10   514  600  2.100000e-03

【讨论】:

  • 感谢您的简洁解释!我收到了AttributeError: ("'pandas._libs.interval.Interval' object has no attribute 'overlaps'", 'occurred at index 0')
  • 您使用的是哪个pandas 版本(pd.__version__)? 24.1 一切正常。
  • 嗯,我很高兴!
【解决方案3】:
df = pd.DataFrame({'id': ['A', 'B', 'B', 'C', 'D', 'D' ,'D', 'D', 'D', 'D', 'D'],
                  'hit': ['hit1', 'hit2', 'hit3','hit4', 'hit5','hit6', 'hit7','hit8', 'hit9','hit10', 'hit11'],
                  'from': [56,89,240,332,291,287,381,287,373,514, 599],
                  'to':[102,275,349,480,512,313,426,316,422,600, 602],
                  'value': [0.00085,0.00034,0.00034,3.40E-15,3.80E-24,0.00098,0.00098,0.0029,0.0029,0.0021, 0.002]})

overlapMask =  df.sort_values(by = 'from')\
                 .groupby('id')\
                 .apply(lambda x: np.where(x['from'] < x['to'].shift(), 0 , 1).cumsum())\
                 .reset_index()

df['Mask'] = np.concatenate((overlapMask[0].values))


df.drop_duplicates(subset = ['id','value'], keep = False, inplace = True)


df.sort_values(by = 'value')\
  .groupby(['id', 'Mask'])\
  .head(1)\
  .reset_index()\
  .drop(['Mask', 'index'],axis = 1)\
  .sort_values(by = 'id')


    id  hit    from  to    value
2   A   hit1    56  102 8.500000e-04
1   C   hit4    332 480 3.400000e-15
0   D   hit5    291 512 3.800000e-24
3   D   hit11   599 602 2.000000e-03

所以我的解决方案使用掩码来检查重叠。通过对'from'值进行排序,并检查下一个'from'值是否小于前一个'to'值。 np.inf 只是为了确保分组中的第一个值始终为 0。

然后,我们在 df 中将掩码设为自己的列。然后我们按我们需要的所有内容进行分组,删除所有重复项,重置索引,最后删除我们的掩码。

【讨论】:

  • 我收到了TypeError: shift() got an unexpected keyword argument 'fill_value'
  • 这似乎是 pandas 0.24.0 中的新功能,请检查您的 pandas 版本。我也刚刚检查过,你似乎不需要那里的 fill_value,所以你可以删除它。发生的情况是所有内容的第一个值只是 1 而不是 0,但这不会影响掩码的分组。
  • @Saraha,你再试一次吗?
  • 我尝试在表格中添加另一行 (D hit11 599 602 0.0002)。不幸的是,代码没有考虑到这一点,因为我得到了错误的输出......
  • 没错,我错过了一个错误,我修复了它现在应该可以正常工作的代码。对此感到抱歉。
【解决方案4】:

如果你排序 id == 'D'

    id  hit from    to  value
5   D   hit6    287 313 9.800000e-04
7   D   hit8    287 316 2.900000e-03
4   D   hit5    291 512 3.800000e-24
8   D   hit9    373 422 2.900000e-03
6   D   hit7    381 426 9.800000e-04
9   D   hit10   514 600 2.100000e-03

重叠将是:

  • 命中 6、8 和 5 = 保持 5 bc 最低值

  • 命中 9 和 7 = kepp 7

  • 打10是单守吗?

【讨论】:

  • 不完全。如您所说,命中 6,8,5 重叠,但命中 9 与命中 5 重叠,命中 7 与命中 9 重叠。在这里,选择具有最低值的一个(5 个中的一个)。命中 10 根本没有重叠,所以保持。
  • 逻辑比较复杂,这周没时间复习。对不起。
【解决方案5】:

如果您按组进行,然后逐行执行,似乎实现起来相当简单。pandas 中似乎没有一种方法可以编写一个以有效方式一次对多行和多列进行操作的函数。

def strip(group):
    non_overlapping=[]
    overlapping = [list(group.itertuples())[0]]
    end = list(group.itertuples())[0].to
    for row in list(group.itertuples())[1:]:
        if row[3]<=end:
            overlapping.append(row)
            if row.to > end:
                end = row.to
        else:
            non_overlapping.append(reduce_overlap(overlapping))
            overlapping=[row]
    non_overlapping.append(reduce_overlap(overlapping))
    return non_overlapping

搜索算法非常简单,您有一组排序的非空行,它们都具有相同的 ID。你去第一个元素并结束。在此之前开始的所有后续行都是重叠的,如果它们在我们将它们结束之后结束,我们将其作为新的结束值。

def reduce_overlap(overlapping):
    overlapping= sorted(overlapping,key=lambda x: x.value)
    if len(overlapping)==1 or overlapping[0].value != overlapping[1].value:
        return overlapping[0]
    else:
        return []

为了找到要返回的值,我们按不返回任何值的值进行排序,如果碰巧它们是两个相同的值。

编辑: 这是将其应用于我尚未测试的整个数据框的函数。

def nonoverlapping(df):
  return df.Dataframe.from_records([strip(group) for name,group in df.sort_values(['from', "value"]).groupby('id')])

【讨论】:

  • 如何将函数strip(group)应用于整个数据框?
  • 您不逐组应用它。他们仍在进行一些修改以使其重新组合成一个df。我用for name,group in df.sort_values(['from', "value"]).groupby('id'): print(strip(group)) 逐组遍历它们
猜你喜欢
  • 2013-04-25
  • 1970-01-01
  • 1970-01-01
  • 2016-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-12
相关资源
最近更新 更多