减少列中具有重复值的行并以不同方式汇总列的其余部分

Reduce rows that has a repeated values in a column and summarises rest of column in different ways

我有一个table这样的

import pandas as pd

data = [
    ['ACOT', '00001', '', '', 1.5, 20, 30],
    ['ACOT', '00002', '', '', 1.7, 20, 33],
    ['ACOT', '00003', '','NA_0001' ,1.4, 20, 40],
    ['PAN', '000090', 'canonical', '', 0.5, 10, 30],
    ['PAN', '000091', '', '', 0.4, 10, 30],
    ['TOM', '000080', 'canonical', '', 0.4, 10, 15],
    ['TOM', '000040', '', '', 1.7, 10, 300]
]

df = pd.DataFrame(data, columns=[
    'Gene_name', 'Transcript_ID', 'canonical', 'mane', 'metrics','start','end'])

输出

Gene_name   Transcript_ID   canonical   mane    metrics start   end
0   ACOT    00001                               1.5 20  30
1   ACOT    00002                       NA_0001 1.7 20  33
2   ACOT    00003                               1.4 20  40
3   PAN     000090          canonical   NA_00090    0.5 10  30
4   PAN     000091                              0.4 10  30
5   TOM     000080          canonical           0.4 10  15
6   TOM     000040                              1.7 10  300

我想要这个输出

Gene_name       canonical   mane    metrics
0   ACOT             No       Yes    1.4-1.5
4   PAN              Yes      Yes    0.5-0.4    
5   TOM              Yes       No    1.7-0.4    

如果 mane/canonical 有值,则输入 yes 否则输入 no。创建指标列中找到的最高值和最低值之间的范围。

编辑:除了我的解决方案有效之外,我建议接受@965311532 的回答,因为它是“pandas-way”和“pythonic”的完美结合“ 代码。行数少得多,阅读起来也更好。

现在我的解决方案: 我不得不说您的示例代码没有提供准确的输出数据。例如查看 mane 列。而你想要的输出中的 metrics 列没有意义,因为 min-max 和 max-min 是混合的。

我的示例代码将此输出作为示例数据:

  Gene_name  canonical     mane  metrics
0      ACOT                          1.5
1      ACOT                          1.7
2      ACOT             NA_0001      1.4
3       PAN  canonical               0.5
4       PAN                          0.4
5       TOM  canonical               0.4
6       TOM                          1.7

转换后的结果为

          canonical mane  metrics
Gene_name
ACOT             No   No  1.4-1.7
PAN             Yes   No  0.4-0.5
TOM             Yes   No  0.4-1.7

这就是解决代码。

#!/usr/bin/env python3
import pandas as pd

data = [
    ['ACOT', '00001', '', '', 1.5, 20, 30],
    ['ACOT', '00002', '', '', 1.7, 20, 33],
    ['ACOT', '00003', '','NA_0001' ,1.4, 20, 40],
    ['PAN', '000090', 'canonical', '', 0.5, 10, 30],
    ['PAN', '000091', '', '', 0.4, 10, 30],
    ['TOM', '000080', 'canonical', '', 0.4, 10, 15],
    ['TOM', '000040', '', '', 1.7, 10, 300]
]

df = pd.DataFrame(data, columns=[
    'Gene_name', 'Transcript_ID', 'canonical', 'mane', 'metrics','start','end'])

# select only the columns we need
df = df.loc[:, ['Gene_name', 'canonical', 'mane', 'metrics']]

# helper function to used in apply()
def my_helper(group):
    # temporary store the result as a dict
    result = {}

    # check for existance of the values
    # it is easy because columnname, value and result column
    # have the same "name"
    for col in ['canonical', 'mane']:
        result[col] = \
            'Yes' if group[col].isin([col]).any() else 'No'


    # I use string format here. Could also be a tuple,
    # list, Series or what every you want.
    result['metrics'] = '{}-{}'.format(group.metrics.min(),
                                       group.metrics.max())

    return pd.Series(result)

desired = df.groupby('Gene_name').apply(my_helper)

这里的重要部分是使用 groupby(),然后在单独的辅助函数中进行计算和检查。

一些细节

关于那条线

def my_helper(group):
    result = {'Gene_name': group.Gene_name.iloc[0]}

my_helper() 函数为您的每个 Gene_name 调用了三次。 group 参数是一个 DataFrame。例如,在第一次调用中,它的内容如下所示:

  Gene_name canonical     mane  metrics
0      ACOT                         1.5
1      ACOT                         1.7
2      ACOT            NA_0001      1.4

为了收集结果,我在这里使用了 dict(),因为它的键稍后用作结果数据框的列名。

关于这条线:

for col in ['canonical', 'mane']:
    result[col] = \
        'Yes' if group[col].isin([col]).any() else 'No'

这可能看起来有点连线,但恕我直言,它更像 pythonic。也可以这样多写几行

if group['canonical'].isin(['canonical']).any():
    result['canonical'] = 'Yes'
else:
    result['canonical'] = 'No'
# and the same for 'mane'

.isin(['canonical']) 部分为您提供布尔值列表。 .any() return True 如果该列表中有最小值 True,如果没有则 False

关于那条线

return pd.Series(result)

此处从 dict 创建了一个 Series 对象。从 my_helper() 的第一次调用开始,该系列看起来像这样:

Gene_name       ACOT
canonical         No
mane              No
metrics      1.4-1.7

这是一个列表,其中每个项目都有自己的名称。完整的代码将生成三个这样的系列对象(每个 Gene_name)。 apply() 确实将每个系列用作一行并将它们粘合在一起到一个新的数据框。

你可以这样做:

f = lambda x: "Yes" if x.any() else "No" # For canonical and mane
df = df.groupby('Gene_name').agg({'canonical': f, 'mane': f, 'metrics': ['min', 'max']})

# Rename columns
df.columns = ["canonical", "mane", "metrics_min", "metrics_max"]

输出:

            canonical   mane    metrics_min  metrics_max
Gene_name               
ACOT        No          Yes     1.4          1.7
PAN         Yes         Yes     0.4          0.5
TOM         Yes         No      0.4          1.7