在pandas中迭代行以检查条件

7
我在 Pandas 中有以下数据框:
+-------+-------+
| Col_A | Col_B |
+-------+-------+
|  1234 |       |
|  6267 |       |
|  6364 |       |
|   573 |       |
|     0 |       |
|   838 |       |
|    92 |       |
|  3221 |       |
+-------+-------+

Col_B 应该填入 True 或 False 值。默认情况下,它是 False,但当第一个 0 出现后,DF 的其余部分应为 True。 DF 具有超过 100,000 行。

自从 Col_A 中出现第一个 "0" 值以来,将 Col_B 中的值设置为 "True" 的最快方法是什么?

+-------+--------+
| Col_A | Col_B  |
+-------+--------+
|  1234 | False  |
|  6267 | False  |
|  6364 | False  |
|   573 | False  |
|     0 | True   |
|   838 | True   |
|    92 | True   |
|  3221 | True   |
+-------+--------+
6个回答

5

使用 idxmaxloc 进行赋值

idx = df.Col_A.eq(0).idxmax()
df['Col_B'] = False
df.loc[idx:, 'Col_B'] = True

   Col_A  Col_B
0   1234  False
1   6267  False
2   6364  False
3    573  False
4      0   True
5    838   True
6     92   True
7   3221   True

使用 assign

这种方法避免了修改原始 DataFrame。

df.assign(Col_B=(df.index >= idx))

5

使用cummax运算符与eq函数

df.A.eq(0).cummax()
Out[5]: 
0    False
1    False
2    False
3    False
4     True
5     True
6     True
7     True
Name: A, dtype: bool

5
你可以使用Numpy的ufunc logical_oraccumulate方法。
df.assign(Col_B=np.logical_or.accumulate(df.Col_A.values == 0))

   Col_A  Col_B
0   1234  False
1   6267  False
2   6364  False
3    573  False
4      0   True
5    838   True
6     92   True
7   3221   True

4

您可以使用生成器表达式与next一起使用。当0出现在序列的开头附近时,这将更加高效。

@user3483203的基于NumPy的解决方案对于一般用途应该足够了。

df = pd.DataFrame({'A': [1234, 6267, 6364, 573, 0, 838, 92, 3221]})

idx = next((i for i, j in enumerate(df['A']) if j == 0), len(df['A']))

df['B'] = ~(df.index < idx)

# more verbose alternative:
# df['B'] = np.where(df.index < idx, False, True)

print(df)

      A      B
0  1234  False
1  6267  False
2  6364  False
3   573  False
4     0   True
5   838   True
6    92   True
7  3221   True

1
我喜欢生成器方法来进行短路处理,我认为当可以使用~(df.index < idx)时,np.where会增加不必要的开销。 - user3483203
运行第二行时,我遇到了 KeyError: 'A' 的错误。 - Pinky the mouse
@Pinkythemouse,你是否精确地复制了上面的代码。看起来你使用的数据框没有标记为A的系列。 - jpp
谢谢,问题已解决,似乎是因为我的“A”符号不是拉丁字母造成的拼写错误。 - Pinky the mouse

3

这里介绍了许多方法,我无法抗拒。我必须对它们进行一些性能比较:

%timeit vivek_kumar()
16.6 ms ± 495 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit numbered_user()
6.69 ms ± 116 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit warpri()
14 ms ± 216 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit jpp()
2.21 ms ± 96.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit wen()
991 µs ± 20.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit pirsquared()
938 µs ± 24.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

这项比较是在一个包含80k行数据的数据框上进行的,旨在测试其可扩展性。经过测试,wenpiRsquared的解决方案似乎最有效。请不要犹豫,为这些答案点赞。
编辑:为了透明,请看下面所使用的函数,以执行测试:
def vivek_kumar():
    data = df.copy()
    first_index = data.loc[data['Col_A'] == 0, 'Col_A'].index[0]
    data.loc[:first_index, 'Col_B'] = False
    data.loc[first_index:, 'Col_B'] = True

def numbered_user():
    data = df.copy()
    idx = data.Col_A.eq(0).idxmax()
    data['Col_B'] = False
    data.loc[idx:, 'Col_B'] = True

def warpri():
    data = df.copy()
    def update_col_b(col_a):
        return col_a == 0
    data['Col_B'] = data.Col_A.apply(update_col_b)

def jpp():
    data = df.copy()
    idx = next((i for i, j in enumerate(data['Col_A']) if j == 0), len(data['Col_A']))
    data['Col_B'] = ~(data.index < idx)

def wen():
    data = df.copy()
    data['Col_B'] = data.Col_A.eq(0).cummax()

def pirsquared():
    data = df.copy()
    # This would return a copy.  My preferred approach
    # return data.assign(Col_B=np.logical_or.accumulate(data.Col_A.values == 0))
    # This edits the dataframe in place but properly compares against the other proposals
    df['Col_B'] = np.logical_or.accumulate(data.Col_A.values == 0)

编辑2:根据piRSquared的指示,这里还有一个比较,使用assign生成数据框的副本和使用=修改现有数据框之间的区别:

def pirsquared1():
    data = df.copy()
    data = data.assign(Col_B=np.logical_or.accumulate(data.Col_A.values == 0))

%timeit pirsquared1()
923 µs ± 32.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

def pirsquared2():
    data = df.copy()
    df['Col_B'] = np.logical_or.accumulate(data.Col_A.values == 0)

%timeit pirsquared2()
598 µs ± 35.2 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

1
不错的贡献。请仔细检查您是否在进行苹果与苹果的比较。我所呈现的是对数据框的分配,而@Wen呈现的是一个系列。我的建议是,将系列分配给数据框可能存在一些开销,在您的测试中可能没有考虑到这一点。 - piRSquared
是的,这很有道理。当一些答案选择生成数据框的副本而不是在现有数据框中添加(或覆盖)列时,那是一种风格上的选择(大多数情况下)。为了进行比较,您需要将它们放在同一水平线上。您可以将我的更改为原地编辑,也可以将Wen(和其他人)的更改为生成副本。 - piRSquared
我添加了用于可复现性的函数。实际上,我忘记执行你的任务并进行了更正。请注意,在这个“小”的数据框上,它不会对性能产生影响(从计算角度来说,可能会对空间复杂度产生影响,我猜)。 - ysearka
我会快速编辑您的帖子以展示我的意思。请随意再次编辑以符合您的需求... 完成。 - piRSquared
感谢您的编辑,不过您的观点有些让我困惑。我认为我们使用的 pandas 版本不同,在我的版本(pandas 0.22.0)中,某些原因导致赋值没有就地执行。 - ysearka
显示剩余2条评论

0

col_A 中找到第一个 0 的索引

first_index = df['col_A'][df['col_A'] == 0].index[0] - 1  #-1 to get index before 0

推荐的方式(感谢@jpp):

first_index = df.loc[df['col_A'] == 0, 'col_A'].index[0] - 1

然后,使用它来填充其他列:

df.loc[:first_index, 'col_B'] = False
df.loc[first_index:, 'col_B'] = True

2
文档明确不建议使用链式索引,应使用loc代替。 - jpp
@jpp 哦,谢谢。我是以这个答案作为参考。你可以建议适当的方式吗? - Vivek Kumar
1
当然,df.loc[df['col_A'] == 0, 'col_A'].index[0] - jpp

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