处理缺失或无效数据#
现实世界中的数据很少是干净的,也从不完美。我们经常会遇到“缺失”或“无效”的数据。数据缺失的原因有很多:仪器在现实世界中未能记录,仪器与存储读数的计算机之间暂时或没有连接,可能是我们的爬虫未能收集到所有数据,或者我们的跟踪工具未能记录所有事件……这个列表可以一直列下去。
除此之外,在我们的分析过程中,有时可能会因为除以零或取负数的对数而“损坏”我们的数据。此外,传感器或人类可能会记录我们想要以特殊方式突出显示的无效值。
在 Vaex 中,我们有 3 种表示这些特殊值的方式:
“缺失”或“掩码”值;
“非数字”或
nan值;“不可用”或
na值。
如果您使用过Vaex,您可能已经注意到一些DataFrame方法、Expression方法或方法参数引用了“missing”、“nan”、“na”。以下是一些示例:
“缺失” |
“非数字” |
“不可用” |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
接下来我们将解释这三种类型值之间的区别,它们应该在何时使用,以及为什么Vaex会对它们进行区分。
“nan” vs “missing” vs “na”#
摘要 (TLDR;)#
下表总结了缺失值、nan值和na之间的区别:
缺失或屏蔽的值 |
非数字 ( |
不可用 ( |
|
|---|---|---|---|
|
任何 |
浮点型 |
任何 |
含义 |
数据的完全缺失 |
数据存在,但已损坏或无法以数字形式表示(例如 |
缺失值和 |
使用案例 |
传感器未进行测量 |
传感器进行了测量,但数据损坏,或数学变换导致无效/非数值 |
由用户决定 |
不是数字或nan#
许多数据从业者可能错误地将术语nan和缺失值互换使用。事实上,nan值通常用作哨兵值,以一般表示无效数据。这是不准确的,因为nan值实际上是特殊的浮点值。nan是“非数字”的缩写,用于表示在浮点数序列中不是数字的值,因此本身并不缺失。它用于表示数学上未定义的值,例如0/0或log(-5),或用于存在但已损坏或无法以数字形式表示的数据。请注意,例如整数或非数字类型(如字符串)没有相应的值。
在Python中,可以通过math标准库(例如:math.nan)或通过numpy库(例如:numpy.nan)使用nan值。
那么为什么nan值与缺失值同义呢?这很难说。一个猜测是,数据从业者发现使用numpy.nan作为表示数组中“缺失”或无效值的便捷快捷方式。Numpy确实有一种通过掩码数组指示缺失值的正确方法(更多内容将在下一节中介绍),但对于许多人来说,该API可能不太方便,并且需要额外了解如何处理这些数组类型。这种效果可能被Pandas进一步增强了,在很长一段时间内,nan值是表示无效/损坏数据和真正缺失数据的唯一方式。
缺失或屏蔽值#
也许标记数据缺失的更好方法是通过缺失值或掩码值。Python 本身有一个特殊的对象来表示缺失或没有数据,那就是 None 对象,它有自己的 NoneType 类型。Python 中的 None 对象相当于 SQL 中的 NULL 值。
现代数据分析库也实现了它们自己的方式来表示缺失值。对于有缺失数据的数组,Numpy实现了所谓的“掩码数组”。在构建数组时,除了数据外,还需要提供一个布尔掩码。掩码数组中的True值表示数据数组中相应的元素缺失。在下面的示例中,掩码数组的最后一个元素缺失:
import numpy as np
data = [23, 31, 0]
mask = [False, False, True]
my_masked_array = np.ma.masked_array(data, mask)
Pyarrow 还实现了一个 null 类型,用于指示其数据结构中的缺失值。与 Numpy 使用字节掩码不同,Pyarrow 使用位掩码来指示缺失数据,这使得它更节省内存。请注意,在 Pyarrow 中,如果掩码的值为 1,则表示数据存在,而 0 表示数据缺失。与 Vaex 类似,Pyarrow 也区分了 nan 和 null 值。
在最近的版本中,Pandas 还实现了一个 pd.NA 值来表示缺失值,它可以用于各种类型的数组或 Series,而不仅仅是浮点数。
在Vaex中,如果底层数组由Pyarrow支持,则缺失数据为null值;如果底层数组是Numpy的掩码数组,则缺失数据为掩码值。
在实践中何时使用缺失值?它们用于表示未收集的数据,即传感器计划进行读数但未进行,或医生本应对患者进行扫描但未进行。
与nan值对比:缺失或屏蔽值表示数据的完全缺失,而nan值表示存在无法以数字解释的数据。这可能是一个微妙但有时重要的区别。
不可用或 na#
Vaex 还实现了引用 na 的方法,它代表“不可用”,并且是 nan 和缺失值的联合。这只有在处理浮点类型的表达式时才真正重要,因为这是唯一可以同时具有缺失值和 nan 值的类型。当然,如果你在代码中不区分 nan 和缺失值,你可以使用引用 na 的方法来涵盖这两种情况并简化开发。
示例#
让我们考虑以下DataFrame:
[1]:
import vaex
import numpy as np
import pyarrow as pa
x = np.ma.array(data=[1, 0, 3, 4, np.nan], mask=[False, True, False, False, False])
y = pa.array([10, 20, None, 40, 50])
z = pa.array(['Reggie Miller', 'Michael Jordan', None, None, 'Kobe Bryant'])
w = pa.array([
{'city': 'Indianapolis', 'team': 'Pacers'},
None,
{'city': 'Dallas', 'team': 'Mavericks'},
None,
{'city': 'Los Angeles', 'team': 'Lakers'}
])
df = vaex.from_arrays(x=x, y=y, z=z, w=w)
df
[1]:
| # | x | y | z | w |
|---|---|---|---|---|
| 0 | 1.0 | 10 | 雷吉·米勒 | {'city': 'Indianapolis', 'team': 'Pacers'} |
| 1 | -- | 20 | 迈克尔·乔丹 | -- |
| 2 | 3.0 | -- | -- | {'city': 'Dallas', 'team': 'Mavericks'} |
| 3 | 4.0 | 40 | -- | -- |
| 4 | nan | 50 | 科比·布莱恩特 | {'city': '洛杉矶', 'team': '湖人'} |
df 包含一个浮点数列 x,该列包含一个缺失(掩码)值和一个 nan 值。列 y、z 和 w 分别是 dtype 为 int、string 和 struct 的类型,除了它们的名义类型外,只能包含掩码值。
例如,如果我们想从整个DataFrame中删除所有包含缺失值的行,我们可以使用dropmissing方法:
[2]:
df.dropmissing()
[2]:
| # | x | y | z | w |
|---|---|---|---|---|
| 0 | 1 | 10 | 雷吉·米勒 | {'city': 'Indianapolis', 'team': 'Pacers'} |
| 1 | nan | 50 | 科比·布莱恩特 | {'city': '洛杉矶', 'team': '湖人'} |
我们看到所有缺失(被屏蔽)的值都被删除了,但列x中的nan值仍然存在,因为它在技术上并不“缺失”。
如果我们想从DataFrame中删除所有nan值,我们可以通过相应的dropnan方法来实现:
[3]:
df.dropnan()
[3]:
| # | x | y | z | w |
|---|---|---|---|---|
| 0 | 1.0 | 10 | 雷吉·米勒 | {'city': 'Indianapolis', 'team': 'Pacers'} |
| 1 | -- | 20 | 迈克尔·乔丹 | -- |
| 2 | 3.0 | -- | -- | {'city': 'Dallas', 'team': 'Mavericks'} |
| 3 | 4.0 | 40 | -- | -- |
现在我们看到,来自列 x 的 nan 值不再在 DataFrame 中,但所有其他缺失值仍然存在。
如果我们只是想去除所有不可直接使用的值,我们可以使用 dropna 方法:
[4]:
df.dropna()
[4]:
| # | x | y | z | w |
|---|---|---|---|---|
| 0 | 1 | 10 | 雷吉·米勒 | {'city': 'Indianapolis', 'team': 'Pacers'} |
现在我们看到只有包含有效数据条目的行保留下来。