从Pandas Dataframe 中删除“主导”行(所有值均低于任何其他行值的行)

i5desfxk  于 2023-01-11  发布在  其他
关注(0)|答案(5)|浏览(158)

编辑:为清晰起见,更改了示例df
我有一个 Dataframe ,类似于下面给出的 Dataframe (除了真实的的 Dataframe 有几千行和几千列,值是浮点数):

df = pd.DataFrame([[6,5,4,3,8], [6,5,4,3,6], [1,1,3,9,5], [0,1,2,7,4], [2, 0, 0, 4, 0])

    0   1   2   3   4
0   6   5   4   3   8
1   6   5   4   3   6
2   1   1   3   9   5
3   0   1   2   7   4
4   2   0   0   4   0

在这个 Dataframe 中,我想删除所有值都小于或等于任何其他行的所有行。对于这个简单的例子,行1和行3应该被删除(分别由行0和行2“支配"):

filtered df:
    0   1   2   3   4
0   6   5   4   3   8
2   1   1   3   9   5
4   2   0   0   4   0

如果该方法可以考虑浮点错误,那就更好了,因为我的真实的 Dataframe 包含浮点数(即不是删除所有值都小于/等于的行,而是值不应该小于一个小数量(例如0.0001))。
我处理这个问题的初步想法如下:
1.选择第一行
1.使用列表解析将其他行与它进行比较(见下文)
1.删除返回True的所有行
1.对下一行重复此操作
列表理解代码:

selected_row = df.loc[0
[(df.loc[r]<=selected_row).all() and (df.loc[r]<selected_row).any() for r in range(len(df))]
[False, True, False, False, False]

然而,这似乎很难有效。任何关于如何(有效)解决这个问题的建议都将非常感谢。

yb3bgrhw

yb3bgrhw1#

我们可以尝试使用broadcasting

import pandas as pd

df = pd.DataFrame([
    [6, 5, 4, 3, 8], [6, 5, 4, 3, 6], [1, 1, 3, 9, 5],
    [0, 1, 2, 7, 4], [2, 0, 0, 4, 0]
])

# Need to ensure only one of each row present since comparing to 1
# there needs to be one and only one of each row
df = df.drop_duplicates()

# Broadcasted comparison explanation below
cmp = (df.values[:, None] <= df.values).all(axis=2).sum(axis=1) == 1

# Filter using the results from the comparison
df = df[cmp]

df

0  1  2  3  4
0  6  5  4  3  8
2  1  1  3  9  5
4  2  0  0  4  0

直觉:
通过DataFrame广播比较操作:
一个二个一个一个
然后我们可以检查axis=2上的all

(df.values[:, None] <= df.values).all(axis=2)
[[ True False False False False]   # Rows le [6 5 4 3 8]
 [ True  True False False False]   # Rows le [6 5 4 3 6]
 [False False  True False False]   # Rows le [1 1 3 9 5]
 [False False  True  True False]   # Rows le [0 1 2 7 4]
 [False False False False  True]]  # Rows le [2 0 0 4 0]

然后我们可以使用sum来计算小于或等于以下值的行数:

(df.values[:, None] <= df.values).all(axis=2).sum(axis=1)
[1 2 1 2 1]

只有1行小于或等于(仅自匹配)的行是要保留的行。因为我们使用drop_duplicates,所以 Dataframe 中不会有重复项,因此只有True值是自匹配值,而那些小于或等于:

(df.values[:, None] <= df.values).all(axis=2).sum(axis=1) == 1
[ True False  True False  True]

然后,它将成为DataFrame的筛选器:

df = df[[True, False, True, False, True]]

df

0  1  2  3  4
0  6  5  4  3  8
2  1  1  3  9  5
4  2  0  0  4  0
sigwle7e

sigwle7e2#

主导行的预期比例是多少?您将处理的数据集的大小和可用内存是多少?
虽然像broadcasting approach这样的解决方案非常聪明和高效(矢量化),但它将无法处理大型 Dataframe ,因为广播的大小将迅速突破内存限制(100,000 ×10的输入数组将无法运行在大多数计算机上)。
这里有另一种方法可以避免测试所有的组合,避免在内存中一次性计算所有的东西。由于循环的原因,它比较慢,但是它应该能够处理更大的数组。当支配行的比例增加时,它也会运行得更快。
总的来说,它将数据集与第一行进行比较,删除受支配的行,将第一行移到末尾,然后重新开始,直到完成一个完整的循环。如果随着时间的推移删除了行,则比较的次数会减少。

def get_dominants_loop(df):
    from tqdm import tqdm
    seen = [] # keep track of tested rows
    idx = df.index # initial index
    for i in tqdm(range(len(df)+1)):
        x = idx[0]
        if x in seen:   # done a full loop
            return df.loc[idx]
        seen.append(idx[0])
        # check which rows are dominated and drop them from the index
        idx = (df.loc[idx]-df.loc[x]).le(0).all(axis=1)
        # put tested row at the end
        idx = list(idx[~idx].index)+[x]

要删除从属行:

df = get_dominants_loop(df)
  • 注意。我在这里使用tqdm是为了有一个进度条。代码运行时不需要它 *

在广播方法无法运行的情况下进行快速基准测试:在大多数行不占优势的情况下,100 k ×10 ~(-1)〈2 min;大多数行占优势时为4s

j5fpnvbx

j5fpnvbx3#

您可以尝试:

df[df.shift(1)[0] >= df[1][0]]

输出

| | 无|1个|第二章|三个|四个|
| - ------|- ------|- ------|- ------|- ------|- ------|
| 1个|六个|五个|四个|三个|六个|
| 第二章|1个|1个|三个|九|五个|

dxxyhpgq

dxxyhpgq4#

你可以试试这样的方法:

# Cartesian product
x = np.tile(df, df.shape[0]).reshape(-1, df.shape[1])
y = np.tile(df.T, df.shape[0]).T

# Remove same rows
#dups = np.all(x == y, axis=1)
#x = x[~dups]
#y = y[~dups]
x = np.delete(x, slice(None, None, df.shape[0]+1), axis=0)
y = np.delete(y, slice(None, None, df.shape[0]+1), axis=0)

# Keep dominant rows
m = x[np.all(x >= y, axis=1)]
>>> m
array([[6, 5, 4, 3, 8],
       [1, 1, 3, 9, 5]])
# Before remove duplicates
# df1 = pd.DataFrame({'x': x.tolist(), 'y': y.tolist()})
>>> df1
                  x                y
0   [6, 5, 4, 3, 8]  [6, 5, 4, 3, 8]  # dup
1   [6, 5, 4, 3, 8]  [6, 5, 4, 3, 6]  # DOMINANT
2   [6, 5, 4, 3, 8]  [1, 1, 3, 9, 5]
3   [6, 5, 4, 3, 8]  [0, 1, 2, 7, 4]
4   [6, 5, 4, 3, 6]  [6, 5, 4, 3, 8]
5   [6, 5, 4, 3, 6]  [6, 5, 4, 3, 6]  # dup
6   [6, 5, 4, 3, 6]  [1, 1, 3, 9, 5]
7   [6, 5, 4, 3, 6]  [0, 1, 2, 7, 4]
8   [1, 1, 3, 9, 5]  [6, 5, 4, 3, 8]
9   [1, 1, 3, 9, 5]  [6, 5, 4, 3, 6]
10  [1, 1, 3, 9, 5]  [1, 1, 3, 9, 5]  # dup
11  [1, 1, 3, 9, 5]  [0, 1, 2, 7, 4]  # DOMINANT
12  [0, 1, 2, 7, 4]  [6, 5, 4, 3, 8]
13  [0, 1, 2, 7, 4]  [6, 5, 4, 3, 6]
14  [0, 1, 2, 7, 4]  [1, 1, 3, 9, 5]
15  [0, 1, 2, 7, 4]  [0, 1, 2, 7, 4]  # dup
ie3xauqp

ie3xauqp5#

下面是使用df.apply()的方法

m = (pd.concat(df.apply(lambda x: df.ge(x,axis=1),axis=1).tolist(),keys = df.index)
.all(axis=1)
.groupby(level=0)
.sum()
.eq(1))

ndf = df.loc[m]

输出:

0  1  2  3  4
0  6  5  4  3  8
2  1  1  3  9  5
4  2  0  0  4  0

相关问题