在Pandas DataFrame列中替换超过n个连续值

4
假设我有以下DataFrame:df
df = pd.DataFrame({"a" : [1,2,2,2,2,2,2,2,2,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5], "b" : [3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,7,7], "c" : [4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,1,2,2,2,2,2,2,2,2,3,3]})

我希望替换任何列中连续重复超过10次的数字4,用10个数字4和余下的数字5代替(可能有数百列)。

例如,12个连续的数字4将被替换为十个数字4和两个数字5。

我该如何在Pandas中实现这一点?

我想应用一个lambda函数,但我不知道如何回溯足够多的行,而且它必须从末尾开始并向前移动,否则就会破坏值序列。每个查找都必须查看前面的10行,以查看它们是否全部等于4,如果是,则将当前值设置为5。

不过我完全不知道如何着手!

3个回答

4

您可以使用:

#column a is changed for 2 groups of 4
df = pd.DataFrame({
"a" : [4,4,4,4,4,4,4,4,4,4,4,4,4,4,7,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5], 
"b" : [3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,7,7], 
"c" : [4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,1,2,2,2,2,2,2,2,2,3,3]})

如果由where创建的NaN,则解决方案会连续计数4并重置,然后将boolean mask应用于原始df以通过mask4替换为5

a = df == 4
mask = a.cumsum()-a.cumsum().where(~a).ffill().fillna(0) > 10
df1 = df.mask(mask, 5)

print (df1)
    a  b  c
0   4  3  4
1   4  3  4
2   4  3  4
3   4  3  4
4   4  3  4
5   4  3  4
6   4  3  4
7   4  4  4
8   4  4  4
9   4  4  4
10  5  4  5
11  5  5  5
12  5  5  5
13  5  5  5
14  7  5  5
15  4  5  5
16  4  5  5
17  4  5  5
18  4  5  5
19  4  5  5
20  4  5  5
21  4  5  1
22  4  5  2
23  4  5  2
24  4  5  2
25  5  5  2
26  5  5  2
27  5  5  2
28  5  6  2
29  5  6  2
30  5  7  3
31  5  7  3

为了更好地检查值,可以使用concat

print (pd.concat([df, df1], axis=1, keys=['orig','new']))
   orig       new      
      a  b  c   a  b  c
0     4  3  4   4  3  4
1     4  3  4   4  3  4
2     4  3  4   4  3  4
3     4  3  4   4  3  4
4     4  3  4   4  3  4
5     4  3  4   4  3  4
6     4  3  4   4  3  4
7     4  4  4   4  4  4
8     4  4  4   4  4  4
9     4  4  4   4  4  4
10    4  4  4   5  4  5
11    4  5  4   5  5  5
12    4  5  4   5  5  5
13    4  5  4   5  5  5
14    7  5  4   7  5  5
15    4  5  4   4  5  5
16    4  5  4   4  5  5
17    4  5  4   4  5  5
18    4  5  5   4  5  5
19    4  5  5   4  5  5
20    4  5  5   4  5  5
21    4  5  1   4  5  1
22    4  5  2   4  5  2
23    4  5  2   4  5  2
24    4  5  2   4  5  2
25    4  5  2   5  5  2
26    4  5  2   5  5  2
27    4  5  2   5  5  2
28    4  6  2   5  6  2
29    5  6  2   5  6  2
30    5  7  3   5  7  3
31    5  7  3   5  7  3

1
谢谢你的回答 :) - IanS
这很棒。如何对任意列运行它- 例如,如果我想在列a、b和c上运行它?我可以通过 for column in df.columns 来完成吗? - Chris
它在所有列中都可以很好地工作,因为所有函数都是针对数据框实现的。请检查列 ac - jezrael
我添加了 concat 以便更好地验证输出,请检查最后一次编辑。 - jezrael
这真是令人难以置信。你是如何成为这样一只功夫熊猫的?我需要花大约30分钟来理解它的工作原理,但它运行得非常好。谢谢。 - Chris
我认为这需要很多小时的编码。但是这些连续的任务真的很难。谢谢你的接受! - jezrael

3

移除所有的4,使用limit=10作为参数将缺失值填充回4,然后用5来替换剩余的NA。我认为这种方法更加清晰明了,更能反映您的意图:

df[df!=4].fillna(4, limit=10).fillna(5)

如有需要,可在最后使用astype(int)将df强制转换为整数类型,因为出现NAs会使数据框转换为浮点型。

3
只有在一列中有一组 4 的情况下才有效,请查看我的样例 - 在列 a 中的第二组 4 被替换为所有值为 5。 - jezrael
顺便说一下,我被参数文档字符串误导了:“如果指定了方法,则这是连续NaN值的最大填充数量。换句话说,如果有一个间隙有超过这个连续NaN数的数量,它只会被部分填充。如果未指定方法,则这是整个轴上将填充NaN的最大条目数。” - Zeugma

1
这应该能解决问题:
import pandas as pd

df = pd.DataFrame({"a" : [1,2,2,2,2,2,2,2,2,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5], "b" : [3,3,3,3,3,3,3,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,6,6,7,7], "c" : [4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,5,5,5,1,2,2,2,2,2,2,2,2,3,3]})

def replacer(l,target_val=4,replace_val=5,repeat_max=10):
    counter = 0
    new_l = []
    for e in l:
        if e == target_val: counter += 1
        else:
            counter = 0

        if counter > repeat_max:
            new_l.append(replace_val)
        else:
            new_l.append(e)

    return new_l

df1 = df.apply(replacer)

输出:

    a  b  c
0   1  3  4
1   2  3  4
2   2  3  4
3   2  3  4
4   2  3  4
5   2  3  4
6   2  3  4
7   2  4  4
8   2  4  4
9   3  4  4
10  3  4  5
11  4  5  5
12  4  5  5
13  4  5  5
14  4  5  5
15  4  5  5
16  4  5  5
17  4  5  5
18  4  5  5
19  4  5  5
20  4  5  5
21  5  5  1
22  5  5  2
23  5  5  2
24  5  5  2
25  5  5  2
26  5  5  2
27  5  5  2
28  5  6  2
29  5  6  2
30  5  7  3
31  5  7  3

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