当前行值以上的行数的条件计数

3
也许这是一些初学者问题,但我的思路真的卡住了。
我有一个数据框,其中有一个名为 x 的列中包含某些值,分成两组。
   x     group
1  1.7   a
2  0     b
3  2.3   b
4  2.7   b
5  8.6   a
6  5.4   b
7  4.2   a
8  5.7   b

我的目的是对于每一行,计算另一组有多少行的值大于当前行的值。因此,为了更清楚地说明,对于第一行(a组),我要查找有多少行b组的值大于1.7(答案为4)。最终结果应该如下:

   x     group   result
1  1.7   a       4
2  0     b       3
3  2.3   b       2
4  2.7   b       2
5  8.6   a       0
6  5.4   b       1
7  4.2   a       2
8  5.7   b       1

我的数据框有多行,因此理想情况下我希望也有一个相对快的解决方案。


2
你所说的“几行”是指多少?100还是10,000?而且你真实情况中只有两个组吗? - Ben.T
1
我大约有130,000行数据。只有两个组,是(a和b)。 - kon176
6个回答

4

使用 np.searchsorted:

df['result'] = 0

a = df.loc[df['group'] == 'a', 'x']
b = df.loc[df['group'] == 'b', 'x']

df.loc[a.index, 'result'] = len(b) - np.searchsorted(np.sort(b), a)
df.loc[b.index, 'result'] = len(a) - np.searchsorted(np.sort(a), b)

输出:

>>> df
     x group  result
1  1.7     a       4
2  0.0     b       3
3  2.3     b       2
4  2.7     b       2
5  8.6     a       0
6  5.4     b       1
7  4.2     a       2
8  5.7     b       1

130K条记录的性能表现

>>> %%timeit
    a = df.loc[df['group'] == 'a', 'x']
    b = df.loc[df['group'] == 'b', 'x']
    len(b) - np.searchsorted(np.sort(b), a)
    len(a) - np.searchsorted(np.sort(a), b)

31.8 ms ± 319 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

设置:

N = 130000
df = pd.DataFrame({'x': np.random.randint(1, 1000, N),
                   'group': np.random.choice(['a', 'b'], N, p=(0.7, 0.3))})

2

这里有一种方法。根据每个组内x值的降序rank,并使用merge_asof将df与自身合并,在交换组名以将a中的排名值与b中的排名值合并,反之亦然。

# needed for the merge_asof
df = df.sort_values('x')

res = (
    pd.merge_asof(
        df.reset_index(), # to keep original index order
        df.assign(
            # to compare a with b in the merge
            group = df['group'].map({'a':'b', 'b':'a'}), 
            # rank descending to get the number of number above current number
            result = df.groupby('group')['x'].rank(ascending=False)),
        by='group', # same group first, knowing you exchange groups in second df
        on='x', direction='forward') # look forward on x to get the rank
      # complete the result column
      .fillna({'result':0})
      .astype({'result':int})
      # for cosmetic
      .set_index('index')
      .rename_axis(None)
      .sort_index()
)
print(res)
#      x group  result
# 1  1.7     a       4
# 2  0.0     b       3
# 3  2.3     b       2
# 4  2.7     b       2
# 5  8.6     a       0
# 6  5.4     b       1
# 7  4.2     a       2
# 8  5.7     b       1

2
你可以对值进行排序并使用掩码来按照其他组进行cumsum
df2 = df.sort_values(by='x', ascending=False)
m = df2['group'].eq('a')
df['result'] = m.cumsum().mask(m).fillna((~m).cumsum().where(m)).astype(int)

输出:

     x group  result
1  1.7     a       4
2  0.0     b       3
3  2.3     b       2
4  2.7     b       2
5  8.6     a       0
6  5.4     b       1
7  4.2     a       2
8  5.7     b       1

1

这应该是相当高效的,只需要对所有x进行一次排序,然后计算累加和。

df2 = df.sort_values('x', ascending=False).reset_index()
df2['acount'] = (df['group'] == 'a').cumsum()
df2['bcount'] = (df['group'] == 'b').cumsum()
df2 = df2.fillna(0)
df2

此时,df2看起来是这样的:
    index   x   group   acount  bcount
0   5       8.6 a       0.0     0.0
1   8       5.7 b       1.0     0.0
2   6       5.4 b       1.0     1.0
3   7       4.2 a       1.0     2.0
4   4       2.7 b       1.0     3.0
5   3       2.3 b       2.0     3.0
6   1       1.7 a       2.0     4.0
7   2       0.0 b       3.0     4.0

现在恢复索引并根据组选择acountbcount:
df2 = df2.set_index('index').sort_index()
df2['result'] = np.where(df['group']=='a', df2['bcount'],df2['acount']).astype(int)
df2[['x','result']]

最终结果


    x   group   result
index           
1   1.7 a       4
2   0.0 b       3
3   2.3 b       2
4   2.7 b       1
5   8.6 a       0
6   5.4 b       1
7   4.2 a       2
8   5.7 b       1

性能表现(在与@Corralien相同的130000行测试中,硬件不同显然)

65.4 ms ± 957 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

1
与 Corralien 的解决方案并没有太大的不同,但您可以使用广播来检查组"a"中的所有元素是否符合组"b"中的所有元素,并计算满足条件的数量。然后将结果连接回去。
import pandas as pd
import numpy as np

a = df.loc[df['group'] == 'a', 'x']
b = df.loc[df['group'] == 'b', 'x']

result = pd.concat([
            pd.Series(np.sum(a.to_numpy() < b.to_numpy()[:, None], axis=0), index=a.index),
            pd.Series(np.sum(b.to_numpy() < a.to_numpy()[:, None], axis=0), index=b.index)])

df['result'] = result

     x group  result
1  1.7     a       4
2  0.0     b       3
3  2.3     b       2
4  2.7     b       2
5  8.6     a       0
6  5.4     b       1
7  4.2     a       2
8  5.7     b       1

0
一个快速的解决方案是使用pandas的DataFrame.apply方法。
df['result'] = df.apply(lambda row: df[(df['group'] != row['group']) & (df['x'] > row['x'])].x.count(), axis=1)

1
有 10 万行数据时,这种方法可能会很慢。 - Ben.T
@Ben.T 你说得完全正确。 - oh_my_lawdy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接