如何在Python中选择按不同列分组的特定条件之上的行?

问题描述 投票:0回答:3

我有以下数据框:

A   B   C
1   3   2
1   7   7
1   7   7
1   5   4
2   2   1
2   8   8
2   4   5
3   5   3
3   1   9
3   4   4

我想要做的是,对于 A 列中的每组相同值,找到 B 列中的值等于 C 中的值的最后一行,然后返回最后一行之前的行,其中 B = C,包括行本身。所以预期的结果是:

A   B   C
1   3   2
1   7   7
1   7   7
2   2   1
2   8   8
3   5   3
3   1   9
3   4   4

我尝试了下面的代码,但它返回第一行之前的行,其中 B = C,而不是最后一行之前。

mask = df['B'] == df['C']
df.loc[mask[::-1].groupby(df['A']).cummax()]
python pandas group-by
3个回答
2
投票

使用反向

groupby.cummax
可以按预期工作:

m = df['B'].eq(df['C'])
out = df[m[::-1].groupby(df['A']).cummax()]

输出:

   A  B  C
0  1  3  2
1  1  7  7
2  1  7  7
4  2  2  1
5  2  8  8
7  3  5  3
8  3  1  9
9  3  4  4

可重复输入:

df = pd.DataFrame({'A': [1, 1, 1, 1, 2, 2, 2, 3, 3, 3],
                   'B': [3, 7, 7, 5, 2, 8, 4, 5, 1, 4],
                   'C': [2, 7, 7, 4, 1, 8, 5, 3, 9, 4]})

错误可能来自具有重复值的索引,在这种情况下,您可以使用底层 numpy 数组绕过索引对齐并手动反转输出:

grouper = df.loc[::-1, 'A'].values
out = df.loc[m[::-1].groupby(grouper).cummax()[::-1]]

替代输入:

df = pd.DataFrame({'A': [1, 1, 1, 1, 2, 2, 2, 3, 3, 3],
                   'B': [3, 7, 7, 5, 2, 8, 4, 5, 1, 4],
                   'C': [2, 7, 7, 4, 1, 8, 5, 3, 9, 4]},
                 index=[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                 )

选择第一个等式之前(并包括)的所有行

tmp = df.assign(m=df['B'].ne(df['C']))
# rows before the first equality
m1 = tmp.groupby('A')['m'].cummin()
# first equality
m2 = ~tmp.duplicated(['A', 'm'])

out = df[m1|m2]

注意。如果您不想包含第一个等式,只需使用

out = df[m1]

输出:

   A  B  C
0  1  3  2
1  1  7  7
4  2  2  1
5  2  8  8
7  3  5  3
8  3  1  9
9  3  4  4

2
投票

对于我正确地工作您的解决方案,这是创建帮助列的替代方案:

mask = df['B'] == df['C']
df = df[df.assign(m=mask).iloc[::-1].groupby('A')['m'].cummax()]
#or
#df = df[df.assign(m=mask).iloc[::-1].groupby('A')['m'].cummax().iloc[::-1]]
print (df)
   A  B  C
0  1  3  2
1  1  7  7
2  1  7  7
4  2  2  1
5  2  8  8
7  3  5  3
8  3  1  9
9  3  4  4

另一个想法是创建默认索引:

df = df.reset_index(drop=True)

#OP solution
mask = df['B'] == df['C']
df.loc[mask[::-1].groupby(df['A']).cummax()]

编辑:如果需要第一个相同的

B
C
列之前的所有行,请使用:

#sample data
print (df)
    A  B  C
0   1  3  2
1   1  7  7
2   1  7  7
3   1  5  4
4   1  7  7
5   1  2  2
6   2  2  1
7   2  8  8
8   2  4  5
9   3  5  3
10  3  1  9
11  3  4  4
12  4  7  7
13  4  7  7
14  4  7  5
15  5  4  4
16  5  4  4
17  5  1  1
18  5  1  1
19  6  8  4

#compare columns
m = df['B'] == df['C']
#create groups by consecutive B, C columns
df1 = df.assign(g = df[['B','C']].ne(df[['B','C']].shift()).any(axis=1).cumsum())
print (df1)
    A  B  C   g
0   1  3  2   1
1   1  7  7   2
2   1  7  7   2
3   1  5  4   3
4   1  7  7   4
5   1  2  2   5
6   2  2  1   6
7   2  8  8   7
8   2  4  5   8
9   3  5  3   9
10  3  1  9  10
11  3  4  4  11
12  4  7  7  12
13  4  7  7  12
14  4  7  5  13
15  5  4  4  14
16  5  4  4  14
17  5  1  1  15
18  5  1  1  15
19  6  8  4  16

#filter only matched B, C and aggregate min
s = df1[m].groupby('A')['g'].min()

#compare mapped A groups and filter if less or equal values
df2 = df[df1['g'].le(df['A'].map(s))]
print (df2)
    A  B  C
0   1  3  2
1   1  7  7
2   1  7  7
6   2  2  1
7   2  8  8
9   3  5  3
10  3  1  9
11  3  4  4
12  4  7  7
13  4  7  7
15  5  4  4
16  5  4  4

#if need only first row with same B, C chain duplicated
df3 = df[df1['g'].le(df['A'].map(s)) & ~df1['g'].duplicated()]
print (df3)
    A  B  C
0   1  3  2
1   1  7  7
6   2  2  1
7   2  8  8
9   3  5  3
10  3  1  9
11  3  4  4
12  4  7  7
15  5  4  4

0
投票
duckdb:

(
    df1.sql.select("*,(b=c)::int col1")
    .select("*,max(index) filter(col1=1) over(partition by a,col1) col2")
    .select("*,max(col2) over(partition by a) col3")
    .filter("index<=col3")

    .order("index")
).df()
© www.soinside.com 2019 - 2024. All rights reserved.