pandas 使用比一堆选择和合并操作更少的重复代码来聚合框架

qoefvg9y  于 11个月前  发布在  其他
关注(0)|答案(4)|浏览(120)

我试图总结/聚合一个框架如下。虽然代码给出了正确的结果,但它非常重复,我想避免这种情况。我认为我需要使用像groupbyaggapply,等等,但找不到这样做的方法。目标是在最后计算df_summ。我认为我使用了太多的中间嵌套,选择了行,而且太多的merge s把结果拼在一起,我觉得一定有更简单的方法,但是想不出来。
真实的df_stats输入字符串有数百万行,而df_summ输出字符串有几十列。下面显示的输入只是一个最小的可复制示例。

import io
import pandas as pd

TESTDATA="""
enzyme  regions   N   length
AaaI    all       10  238045
AaaI    all       20  170393
AaaI    all       30  131782
AaaI    all       40  103790
AaaI    all       50  81246
AaaI    all       60  62469
AaaI    all       70  46080
AaaI    all       80  31340
AaaI    all       90  17188
AaaI    captured  10  292735
AaaI    captured  20  229824
AaaI    captured  30  193605
AaaI    captured  40  163710
AaaI    captured  50  138271
AaaI    captured  60  116122
AaaI    captured  70  95615
AaaI    captured  80  73317
AaaI    captured  90  50316
AagI    all       10  88337
AagI    all       20  19144
AagI    all       30  11030
AagI    all       40  8093
AagI    all       50  6394
AagI    all       60  4991
AagI    all       70  3813
AagI    all       80  2759
AagI    all       90  1666
AagI    captured  10  34463
AagI    captured  20  19220
AagI    captured  30  15389
AagI    captured  40  12818
AagI    captured  50  10923
AagI    captured  60  9261
AagI    captured  70  7753
AagI    captured  80  6201
AagI    captured  90  4495
"""

df_stats = pd.read_csv(io.StringIO(TESTDATA), sep='\s+')

df_cap_N90 = df_stats[(df_stats['N'] == 90) & (df_stats['regions'] == 'captured')].drop(columns=['regions', 'N'])
df_cap_N50 = df_stats[(df_stats['N'] == 50) & (df_stats['regions'] == 'captured')].drop(columns=['regions', 'N'])

df_all_N50 = df_stats[(df_stats['N'] == 50) & (df_stats['regions'] == 'all')     ].drop(columns=['regions', 'N'])

df_summ_cap_N50_all_N50 = pd.merge(df_cap_N50, df_all_N50, on='enzyme', how='inner', suffixes=('_cap_N50', '_all_N50'))
df_summ_cap_N50_all_N50['cap_N50_all_N50'] = (df_summ_cap_N50_all_N50['length_cap_N50'] -
                                              df_summ_cap_N50_all_N50['length_all_N50'])
print(df_summ_cap_N50_all_N50)

df_summ_cap_N90_all_N50 = pd.merge(df_cap_N90, df_all_N50, on='enzyme', how='inner', suffixes=('_cap_N90', '_all_N50'))
df_summ_cap_N90_all_N50['cap_N90_all_N50'] = df_summ_cap_N90_all_N50['length_cap_N90'] - df_summ_cap_N90_all_N50['length_all_N50']
print(df_summ_cap_N90_all_N50)

df_summ = pd.merge(df_summ_cap_N50_all_N50.drop(columns=['length_cap_N50', 'length_all_N50']),
                   df_summ_cap_N90_all_N50.drop(columns=['length_cap_N90', 'length_all_N50']),
                   on='enzyme', how='inner')
print(df_summ)

字符串
印刷品:

enzyme  length_cap_N50  length_all_N50  cap_N50_all_N50
0   AaaI          138271           81246            57025
1   AagI           10923            6394             4529
  enzyme  length_cap_N90  length_all_N50  cap_N90_all_N50
0   AaaI           50316           81246           -30930
1   AagI            4495            6394            -1899
  enzyme  cap_N50_all_N50  cap_N90_all_N50
0   AaaI            57025           -30930
1   AagI             4529            -1899

本问题背后的生物信息学背景说明:

  • (可以跳过这里,它描述了python代码背后的领域知识)*

上面的代码是一个多步骤生物信息学项目中的一个步骤,在这个项目中,我试图根据它们切割DNA的方式找到最佳的限制性内切酶。
作为此步骤的输入,我有一个包含限制性内切酶的表(其名称存储在列enzyme中)。我想根据切割DNA方式的统计特性对酶进行排名。列regions存储两种不同的DNA区域类型,我想用这些酶来区分。列N是统计量的名称,它测量DNA被切割的精细程度(N10,...,N90),length是此统计量的值。N统计量总结了DNA片段长度分布,以核苷酸为单位测量,在精神上与分位数相似(10%,...,90%)。当我比较酶时,我想做简单的运算,如cap_N90_all_N50 = { captured N90 } - { all N50 }等。然后我按cap_N50_all_N50等的组合对酶进行排名。

qcbq4gxm

qcbq4gxm1#

你没有描述逻辑,我不明白为什么你不计算df_all_N90
也就是说,要获得预期的输出,您可以尝试pivot/sub

piv = (df_stats.loc[df_stats["N"].isin([50, 90])]
       .pivot(index="enzyme", columns=["regions", "N"], values="length"))

out = (piv["captured"].sub(piv[("all", 50)], axis=0)
           .add_prefix("cap_N").add_suffix("_all_N50").reset_index())

字符串
输出量:

print(out)

N enzyme  cap_N50_all_N50  cap_N90_all_N50
0   AaaI            57025           -30930
1   AagI             4529            -1899

[2 rows x 3 columns]

3phpmpom

3phpmpom2#

Groupby和Aggregate
我们首先按多个列进行分组- 'enzyme','regions','N'。这将每个酶,区域和N值的所有统计数据分组到一个聚合行中。
.agg()通过只取每个组的第一个值来聚合长度列。我们不再需要前面的重复过滤。所有数据都是预先分组的。

gb = df_stats.groupby(['enzyme', 'regions', 'N'])  

    stats = gb.agg(length=('length', 'first')).reset_index()

字符串
枢轴比较
统计数据框中的每个统计数据都有一行。为了便于比较,我们将其透视到列中:

pivoted = stats.pivot(index='enzyme',  
                  columns=['regions', 'N'], 
                  values='length')


现在,所有长度值都按列对齐,按N和区域分割。
矢量化列操作
我们可以直接在枢轴上取列差:

pivoted['cap_N50_all_N50'] = pivoted['captured']['50'] - pivoted['all']['50']


在一次向量化操作中对FULL列进行差分,避免了行循环。
最终汇总输出
我们只将所需的差异列输出到一个干净的摘要框架中:

df_summ = pivoted[['cap_N50_all_N50','cap_N90_all_N50']]


输出量:

cap_N50_all_N50  cap_N90_all_N50
enzyme                                   
AaaI                 57025           -30930
AagI                  4529            -1899

uqzxnwby

uqzxnwby3#

另一种方法是将df_stats转换为MultiIndex Series,过滤相关的行,并从过滤的行中减去enzymeregions='all'N=50值。然后剩下的就是将Series解栈回一个嵌套框。

out = (
    df_stats
    .set_index(['enzyme', 'regions', 'N'])['length']
    .pipe(lambda x: x.loc[:, 'captured', [50, 90]].sub(x.loc[:, 'all', 50], level='enzyme'))
    .droplevel('regions')
    .unstack()
    .add_prefix('cap_N').add_suffix('_all_N50')
    .reset_index()
)

字符串
该框架将经历以下转换:


的数据

x8diyxa7

x8diyxa74#

另一种可能性。找到每个酶的all区域N50值,从captured值中减去这些值,然后将结果旋转回每个组合的列:

all_N50 = df_stats[(df_stats['regions'] == 'all') & (df_stats['N'] == 50)].set_index('enzyme')['length']

dfe = df_stats[df_stats['regions'] == 'captured'].set_index('enzyme').drop(columns='regions')

dfe['length'] = dfe['length'].sub(all_N50, level='enzyme')

df_summ = dfe.pivot(columns='N', values='length')
df_summ.columns = pd.Series([f'cap_N{col}_all_N50' for col in df_summ], name='')
df_summ = df_summ.reset_index()

字符串
输出量:

enzyme  cap_N10_all_N50  cap_N20_all_N50  cap_N30_all_N50  cap_N40_all_N50
0   AaaI           211489           148578           112359            82464  \
1   AagI            28069            12826             8995             6424

   cap_N50_all_N50  cap_N60_all_N50  cap_N70_all_N50  cap_N80_all_N50
0            57025            34876            14369            -7929  \
1             4529             2867             1359             -193

   cap_N90_all_N50
0           -30930
1            -1899

相关问题