为啥在 pandas 行中使用列表作为值允许我即时更新?

Posted

技术标签:

【中文标题】为啥在 pandas 行中使用列表作为值允许我即时更新?【英文标题】:Why does using a list as value in pandas row allow me to update on the fly?为什么在 pandas 行中使用列表作为值允许我即时更新? 【发布时间】:2020-07-27 06:15:48 【问题描述】:

我有一些我不理解的行为。如果有人能解释为什么这确实有效,并告诉我一个更好的方法来做到这一点,那就太好了,它变得非常复杂。

我的目标是连接给定染色体上彼此相邻的遗传变异。这应该会找到最长 5 bp(位置)的任何变体组合。这意味着如果有 5 个位置彼此相邻,则它们应该全部连接起来,然后不应再次检查这些位置。然后对于长度为 4、3、2 的组合也是如此。我使用这一行 'for var_len in [5, 4, 3, 2, 1]:' 和 set already_updated 来处理这个问题。此解决方案有效,但前提是我将 'tumour_alts' 放在我的 df 的列表中。这是为什么?谁能告诉我如何在不诉诸迭代的情况下获得相同的输出?

df = pd.DataFrame([['chr1',13,['A']],
              ['chr1',5,['A']],
              ['chr1',6,['G']],
              ['chr2',9,['G']],
               ['chr1',4,['C']],
              ['chr1',11,['T']]],
              columns=['chrom','pos','tumour_alts'],
             index=['chr1:13','chr1:5','chr1:6','chr2:9','chr1:4','chr1:11'])
already_updated = set([])
for chrom, df_tmp in df.groupby('chrom'):
    df_tmp = df_tmp.sort_values(by=['pos'])
    for var_len in [5, 4, 3, 2, 1]:
        df_tmp['dif'] = df_tmp.pos.diff(var_len)
        hits=df_tmp[df_tmp['dif'] == var_len]
        for hit in hits.pos:
            rows = df_tmp[(df_tmp.pos <= hit) & (df_tmp.pos >= (hit - var_len))]
            update = dict(rows.iloc[0])
            for i in range(var_len):
                i+=1
                update_tmp = dict(rows.iloc[i])
                key = update_tmp.get('chrom') + ':'+str(update_tmp.get('pos'))
                if key not in already_updated:
                    df = df.drop(index=(key))
                    update['tumour_alts'][0]+=update_tmp.get('tumour_alts')[0]
                    already_updated.add(key)


df

chrom   pos tumour_alts
chr1:13 chr1    13  [A]
chr2:9  chr2    9   [G]
chr1:4  chr1    4   [CAG]
chr1:11 chr1    11  [T]

编辑:我增加了复杂性以更好地描述问题。

给定:

df = pd.DataFrame([['chr1',13,['A']],
                   ['chr1',3,['A']],
                  ['chr1',5,['A']],
                  ['chr1',6,['G']],
                  ['chr2',9,['G']],
                   ['chr1',4,['C']],
                  ['chr1',11,['T']],
                  ['chr1',55,['A']],
                  ['chr1',56,['G']],
                  ['chr2',95,['G']],],
                  columns=['chrom','pos','tumour_alts'],
                 index=['chr1:13','chr1:3','chr1:5','chr1:6','chr2:9','chr1:4','chr1:11','chr1:55','chr1:56','chr1:95'])
df = df.sort_values('pos').sort_values('chrom')

我希望:

chrom   pos tumour_alts
chr1:3  chr1    3   [ACAG]
chr1:11 chr1    11  [T]
chr1:13 chr1    13  [A]
chr1:55 chr1    55  [AG]
chr2:9  chr2    9   [G]
chr2:95 chr2    95  [G]

【问题讨论】:

【参考方案1】:

您可以尝试使用 groupby 而不是 for 循环。另外,我不确定您所说的“这应该找到最长 5 bp(位置)长的任何变体组合”是什么意思,所以我没有将其包含在下面的代码中。

# remove characters from list
df['tumour_alts'] = df['tumour_alts'].astype(str).str.replace("\[|\]", '').str.replace("'", '')
# sort values
df = df.sort_values('pos')
# groupby chrom (assuming you need to group these together)
g = df.groupby('chrom')['pos']
# check the value above and below to see if they are == eachother
mask = ~((g.shift(0) == g.shift(-1)-1) | (g.shift(0) == g.shift(1)+1))
# use cumsum to assign a number value for each group
gr = mask.cumsum()-mask.cumsum().where(~mask).ffill().fillna(0).astype(int)
# groupby gr and transform with sum to append strings together
s = df.groupby(gr)['tumour_alts'].transform(sum).drop_duplicates(keep='first').to_frame()
# drop column and merge right
df.drop(columns='tumour_alts').merge(s, left_index=True, right_index=True, how='right')

        chrom  pos tumour_alts
chr1:4   chr1    4         CAG
chr2:9   chr2    9           G
chr1:11  chr1   11           T
chr1:13  chr1   13           A

虽然上面的代码确实产生了您预期的输出,但我对染色体知之甚少,因此drop_duplicates 可能不合适。您可能希望将所有内容合并在一起,然后根据适当的参数删除行。

# remove characters from list
df['tumour_alts'] = df['tumour_alts'].astype(str).str.replace("\[|\]", '').str.replace("'", '')
# sort values
df = df.sort_values('pos')
# groupby chrom (assuming you need to group these together)
g = df.groupby('chrom')['pos']
# check the value above and below to see if the are == eachother
mask = ~((g.shift(0) == g.shift(-1)-1) | (g.shift(0) == g.shift(1)+1))
# use cumsum to assign a number value for each group
gr = mask.cumsum()-mask.cumsum().where(~mask).ffill().fillna(0).astype(int)
# groupby gr and transform with sum to append strings together
s = df.groupby(gr)['tumour_alts'].transform(sum).to_frame()
# merge right
df.merge(s, left_index=True, right_index=True, how='right')

        chrom  pos tumour_alts_x tumour_alts_y
chr1:4   chr1    4             C           CAG
chr1:5   chr1    5             A           CAG
chr1:6   chr1    6             G           CAG
chr2:9   chr2    9             G             G
chr1:11  chr1   11             T             T
chr1:13  chr1   13             A             A

【讨论】:

感谢您的回答。我已经编辑了我的问题,以进一步解释我的意思是“这应该找到最长 5 bp(位置)长的任何变体组合”。这意味着如果有 5 个位置彼此相邻,则它们应该全部连接起来,然后不应再次检查这些位置。然后对于 4、3、2 也是如此。我用这一行 'for var_len in [5, 4, 3, 2, 1]:' 和 set already_updated 来处理这个问题。 这是一个非常好的解决方案。我从中学到了很多。我试图对其进行修改以适应问题中现在描述的增加的复杂性。可以扩展掩码以捕获多行,例如 'mask = ~((g.shift(0) == g.shift(-1)-1) | (g.shift(0) == g.shift(1 )+1) | (g.shift(0) == g.shift(-2)-2) | (g.shift(0) == g.shift(2)+2)) '。但是,“gr”行需要为每个要折叠的变体组合和每个不折叠的变体创建独特的东西。这可能吗?

以上是关于为啥在 pandas 行中使用列表作为值允许我即时更新?的主要内容,如果未能解决你的问题,请参考以下文章

Pandas 数据框行到列表的字典,使用每行的第一个值作为键

在Pandas中,我如何将一个函数应用到数据框的某一行,其中行中的每一项都应该作为参数传递给函数?

Pandas 按行中的值和其他列中的值在行之间进行差异

Pandas 用 NaN 值填充列中的单元格,从行中的其他单元格中获取值

Pandas DataFrame 自动将错误值作为索引

pandas.read_excel,第一行值