pandas 在DataFrame中搜索不完整的重复行时处理NaN

7uhlpewt  于 2023-03-16  发布在  其他
关注(0)|答案(2)|浏览(126)

这有点难以解释,但请耐心听我解释,假设我们有以下数据集:

df = pd.DataFrame({'foo': [1, 1, 1, 8, 1, 5, 5, 5],
                   'bar': [2, float('nan'), 2, 5, 2, 3, float('nan'), 6],
                   'abc': [3, 3, 3, 7, float('nan'), 9, 9, 7],
                   'def': [4, 4, 4, 2, 4, 8, 8, 8]})
print(df)
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
3    8  5.0  7.0    2
4    1  2.0  NaN    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8
7    5  6.0  7.0    8

我们的目标是查找所有重复的行。但是,其中一些重复的行是不完整的,因为它们具有NaN值。尽管如此,我们也希望查找这些重复的行。因此,预期结果为:

foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
4    1  2.0  NaN    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8

如果我们试图直接这样做,那么只能得到完整的行:

print(df[df.duplicated(keep=False)])
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
2    1  2.0  3.0    4

我们可以尝试通过只使用没有任何缺失值的列来绕过它:

print(df[df.duplicated(['foo', 'def'], keep=False)])
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
4    1  2.0  NaN    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8
7    5  6.0  7.0    8

非常接近,但还不完全是。结果我们遗漏了“abc”列中的一条关键信息,它可以让我们确定第7行不是重复的。因此,我们希望包含它:

print(df[df.duplicated(['foo', 'def', 'abc'], keep=False)])
>>>
   foo  bar  abc  def
0    1  2.0  3.0    4
1    1  NaN  3.0    4
2    1  2.0  3.0    4
5    5  3.0  9.0    8
6    5  NaN  9.0    8

它成功地删除了第7行,但是也删除了第4行。NaN被认为是它自己的独立值,而不是可以等于任何值的值,因此它在第4行的存在使我们无法检测到这个重复值。
现在,我意识到我们并不确定第4行是否真的是[1,2,3,4],就我们所知,它可以是完全不同的值,比如[1,2,9,4],但是让我们假设值1和4实际上是一些奇怪的特定值,例如,34900和23893。假设还有更多的列也完全相同。此外,完全重复的行不仅仅是0和2,还有200多行,另外40行的所有列都具有相同的值,但“abc”除外。所以对于这个特殊的重复组,这种巧合是极不可能的,这就是为什么我们确定记录[1,2,3,4]是有问题的,第4行几乎肯定是重复的。
但是,如果[1,2,3,4]不是唯一的重复项组,则其他一些组的“foo”和“def”列中可能有非常不特定的值,如1和500。碰巧,在子集中包含“abc”列将非常有助于解决此问题,因为“abc”列中的值几乎总是非常特定的。并允许以近乎确定的方式确定所有重复项。但有一个缺点-“abc”列有缺失值,因此使用它会牺牲对一些NaN重复项的检测。其中一些我们知道是重复项(如前面提到的40个),因此这是一个艰难的困境。
处理这种情况的最佳方法是什么?如果我们能在重复检测期间使NaN等于所有值而不是零,这将解决这个问题,那将是很好的。但我怀疑这是可能的。我应该一组一组地手动检查吗?

xqk2d5yq

xqk2d5yq1#

感谢@cs95的帮助,当我们对值排序时,默认情况下NaN被放在排序组的末尾,如果不完整的记录中有一个与现有值重复的值,而不是这个NaN,它会在NaN的正上方结束。这意味着我们可以用ffill()方法用那个值填充NaN。用最接近的行的数据重新填充缺失的数据,这样我们就可以更准确地判断该行是否重复。
我最终使用的代码(根据这个可重现的示例进行了调整)如下所示:

#printing all duplicates
col_list = ['foo', 'def', 'abc', 'bar']
show_mask = df.sort_values(col_list).ffill().duplicated(col_list, keep=False).sort_index()
df[show_mask].sort_values(col_list)

#deleting duplicates, but keeping one record per duplicate group
delete_mask = df.sort_values(col_list).ffill().duplicated(col_list).sort_index()
df = df[~delete_mask].reset_index(drop=True)

可以使用bfill()代替ffill(),因为这是颠倒应用的相同原理,但需要将方法的一些默认参数改为相反的参数,即na_position='first'keep='last'sort_index()只是用来消除重新索引警告。
请注意,列的顺序非常重要,因为它用于排序优先级。要确保缺失值上方的记录是要复制的正确值,必须先枚举所有没有缺失值的列。并且只有那些具有多样性的列。对于前一列,顺序并不重要。对于后一列,从具有最多多样性的列开始是至关重要的/特定值,并以最少多样性/特定值结尾(float -〉int -〉string -〉bool是一个很好的经验法则,但它在很大程度上取决于列在数据集中表示的变量的确切类型)。在本例中,它们都是相同的,但即使在这里,如果您将'bar'放在'abc'之前,也不会得到正确的答案。
即便如此,这也不是一个完美的解决方案。它做得很好,将记录的最完整版本放在顶部,并在需要时将其中的信息转移到下面不太完整的版本。但也有可能记录的完全完整版本根本不存在。例如,假设有记录[53 Nans 8]和[5 NaN 98](也没有[5 3 9 8]记录),这个方案不能让他们交换丢失的片段,它会在前者中放入9,但在后者中NaN将保持为空,并且将导致这些副本不被注意。
如果只处理一个不完整的列,这不是问题,但是每添加一个不完整的列都会使这种情况越来越频繁。然而,添加所有列仍然是更可取的,因为未能检测到一些重复总比列表中出现一些虚假重复要好,除非使用所有列,否则这种可能性很大。

e7arh2l6

e7arh2l62#

很抱歉打扰您,但恐怕您的代码并不总是像预期的那样工作。
下面是一个例子:

column_list = ['c1','c2','c3']
data = [
    [1,2,3],
    [np.nan,2,3],
    [1,np.nan,3],
    [2,3,4],
    [1,1,1],
    [1,2,3],
]

df = pd.DataFrame(
    columns=column_list,
    data=data)
df
+----+------+------+------+
|    |   c1 |   c2 |   c3 |
|----+------+------+------|
|  0 |    1 |    2 |    3 |
|  1 |  nan |    2 |    3 |
|  2 |    1 |  nan |    3 |
|  3 |    2 |    3 |    4 |
|  4 |    1 |    1 |    1 |
|  5 |    1 |    2 |    3 |
+----+------+------+------+
sorted_df = df.sort_values(column_list)
mask = sorted_df.ffill().duplicated(column_list).sort_index()
df[np.logical_not(mask)]

结果:

+----+------+------+------+
|    |   c1 |   c2 |   c3 |
|----+------+------+------|
|  0 |    1 |    2 |    3 |
|  1 |  nan |    2 |    3 |
|  3 |    2 |    3 |    4 |
|  4 |    1 |    1 |    1 |
+----+------+------+------+

相关问题