处理缺失数据#
在本节中,我们将讨论 cudf 中的缺失值(也称为 NA)。cudf 支持所有数据类型中的缺失值。这些缺失值由 <NA> 表示。这些值也被称为“null 值”。
如何检测缺失值#
要检测缺失值,可以使用 isna() 和 notna() 函数。
import cudf
import numpy as np
rng = np.random.default_rng()
df = cudf.DataFrame({"a": [1, 2, None, 4], "b": [0.1, None, 2.3, 17.17]})
df
| a | b | |
|---|---|---|
| 0 | 1 | 0.1 |
| 1 | 2 | <NA> |
| 2 | <NA> | 2.3 |
| 3 | 4 | 17.17 |
df.isna()
| a | b | |
|---|---|---|
| 0 | False | False |
| 1 | False | True |
| 2 | True | False |
| 3 | False | False |
df["a"].notna()
0 True
1 True
2 False
3 True
Name: a, dtype: bool
需要注意的是,在 Python(和 NumPy)中,nan 不相等,但 None 相等。请注意,cudf/NumPy 利用了 np.nan != np.nan 的事实,并将 None 视为 np.nan。
None == None
True
np.nan == np.nan
False
因此,与上述情况相比,标量与 None/np.nan 的相等性比较不提供有用的信息。
df["b"] == np.nan
0 False
1 <NA>
2 False
3 False
Name: b, dtype: bool
s = cudf.Series([None, 1, 2])
s
0 <NA>
1 1
2 2
dtype: int64
s == None
0 <NA>
1 <NA>
2 <NA>
dtype: bool
s = cudf.Series([1, 2, np.nan], nan_as_null=False)
s
0 1.0
1 2.0
2 NaN
dtype: float64
s == np.nan
0 False
1 False
2 False
dtype: bool
浮点数据类型和缺失数据#
因为 NaN 是浮点数,即使只有单个缺失值的整型列也会被转换为浮点数据类型。然而,这默认不会发生。
默认情况下,如果将 NaN 值传递给 Series 构造函数,它将被视为 <NA> 值。
cudf.Series([1, 2, np.nan])
0 1
1 2
2 <NA>
dtype: int64
因此,要将 NaN 视为 NaN,必须将 nan_as_null=False 参数传递给 Series 构造函数。
cudf.Series([1, 2, np.nan], nan_as_null=False)
0 1.0
1 2.0
2 NaN
dtype: float64
日期时间#
对于 datetime64 类型,cudf 不支持 NaT 值。相反,这些 numpy 和 pandas 特有的值在 cudf 中被视为 null 值 (<NA>)。NaT 的实际底层值是 min(int64),并且 cudf 在将 cudf 对象转换为 pandas 对象时会保留此底层值。
import pandas as pd
datetime_series = cudf.Series(
[pd.Timestamp("20120101"), pd.NaT, pd.Timestamp("20120101")]
)
datetime_series
0 2012-01-01 00:00:00.000000000
1 NaT
2 2012-01-01 00:00:00.000000000
dtype: datetime64[ns]
datetime_series.to_pandas()
0 2012-01-01
1 NaT
2 2012-01-01
dtype: datetime64[ns]
对 datetime 列中包含 <NA> 值的行执行任何操作,将在结果列的相同位置产生 <NA> 值
datetime_series - datetime_series
0 0 days 00:00:00
1 NaT
2 0 days 00:00:00
dtype: timedelta64[ns]
包含缺失数据的计算#
Null 值通过 pandas 对象之间的算术运算自然传播。
df1 = cudf.DataFrame(
{
"a": [1, None, 2, 3, None],
"b": cudf.Series([np.nan, 2, 3.2, 0.1, 1], nan_as_null=False),
}
)
df2 = cudf.DataFrame(
{"a": [1, 11, 2, 34, 10], "b": cudf.Series([0.23, 22, 3.2, None, 1])}
)
df1
| a | b | |
|---|---|---|
| 0 | 1 | NaN |
| 1 | <NA> | 2.0 |
| 2 | 2 | 3.2 |
| 3 | 3 | 0.1 |
| 4 | <NA> | 1.0 |
df2
| a | b | |
|---|---|---|
| 0 | 1 | 0.23 |
| 1 | 11 | 22.0 |
| 2 | 2 | 3.2 |
| 3 | 34 | <NA> |
| 4 | 10 | 1.0 |
df1 + df2
| a | b | |
|---|---|---|
| 0 | 2 | NaN |
| 1 | <NA> | 24.0 |
| 2 | 4 | 6.4 |
| 3 | 37 | <NA> |
| 4 | <NA> | 2.0 |
在对 Series 中的数据求和时,NA 值将被视为 0。
df1["a"]
0 1
1 <NA>
2 2
3 3
4 <NA>
Name: a, dtype: int64
df1["a"].sum()
np.int64(6)
由于 NA 值被视为 0,在这种情况下,均值将为 2 (1 + 0 + 2 + 3 + 0)/5 = 2
df1["a"].mean()
np.float64(2.0)
为了在上述计算中保留 NA 值,sum 和 mean 支持 skipna 参数。默认情况下其值设置为 True,我们可以将其更改为 False 以保留 NA 值。
df1["a"].sum(skipna=False)
np.float64(nan)
df1["a"].mean(skipna=False)
np.float64(nan)
默认情况下,累积方法(如 cumsum 和 cumprod)会忽略 NA 值。
df1["a"].cumsum()
0 1
1 <NA>
2 3
3 6
4 <NA>
Name: a, dtype: int64
要在累积方法中保留 NA 值,请提供 skipna=False。
df1["a"].cumsum(skipna=False)
0 1
1 <NA>
2 <NA>
3 <NA>
4 <NA>
Name: a, dtype: int64
Null/nan 的求和/求积#
空的或全部为 NA 的 DataFrame Series 的和为 0。
cudf.Series([np.nan], nan_as_null=False).sum()
np.float64(0.0)
cudf.Series([np.nan], nan_as_null=False).sum(skipna=False)
np.float64(nan)
cudf.Series([], dtype="float64").sum()
np.float64(0.0)
空的或全部为 NA 的 DataFrame Series 的积为 1。
cudf.Series([np.nan], nan_as_null=False).prod()
np.float64(1.0)
cudf.Series([np.nan], nan_as_null=False).prod(skipna=False)
np.float64(nan)
cudf.Series([], dtype="float64").prod()
np.float64(1.0)
GroupBy 中的 NA 值#
默认情况下,GroupBy 中的 NA 组会被自动排除。例如
df1
| a | b | |
|---|---|---|
| 0 | 1 | NaN |
| 1 | <NA> | 2.0 |
| 2 | 2 | 3.2 |
| 3 | 3 | 0.1 |
| 4 | <NA> | 1.0 |
df1.groupby("a").mean()
| b | |
|---|---|
| a | |
| 3 | 0.1 |
| 1 | NaN |
| 2 | 3.2 |
也可以通过传递 dropna=False 将 NA 包含在组中
df1.groupby("a", dropna=False).mean()
| b | |
|---|---|
| a | |
| 3 | 0.1 |
| 1 | NaN |
| 2 | 3.2 |
| <NA> | 1.5 |
插入缺失数据#
所有数据类型都支持通过赋值插入缺失值。Series 中的任何特定位置都可以通过将其赋值为 None 来设为 null。
series = cudf.Series([1, 2, 3, 4])
series
0 1
1 2
2 3
3 4
dtype: int64
series[2] = None
series
0 1
1 2
2 <NA>
3 4
dtype: int64
填充缺失值: fillna#
fillna() 可以用非 NA 数据填充 NA 和 NaN 值。
df1
| a | b | |
|---|---|---|
| 0 | 1 | NaN |
| 1 | <NA> | 2.0 |
| 2 | 2 | 3.2 |
| 3 | 3 | 0.1 |
| 4 | <NA> | 1.0 |
df1["b"].fillna(10)
0 10.0
1 2.0
2 3.2
3 0.1
4 1.0
Name: b, dtype: float64
用 cudf 对象填充#
您还可以使用可对齐的 dict 或 Series 来 fillna。dict 的标签或 Series 的索引必须与您希望填充的 frame 的列匹配。这种用法的场景是使用该列的平均值填充 DataFrame。
import cupy as cp
cp_rng = cp.random.default_rng()
dff = cudf.DataFrame(cp_rng.standard_normal((10, 3)), columns=list("ABC"))
dff.iloc[3:5, 0] = np.nan
dff.iloc[4:6, 1] = np.nan
dff.iloc[5:8, 2] = np.nan
dff
| A | B | C | |
|---|---|---|---|
| 0 | 0.974978 | 0.282769 | 2.254196 |
| 1 | 1.863146 | -0.514037 | -0.047407 |
| 2 | -0.979084 | 0.127691 | 0.713527 |
| 3 | NaN | -0.245523 | -0.000601 |
| 4 | NaN | NaN | 0.198238 |
| 5 | 0.006034 | NaN | NaN |
| 6 | 1.139740 | -1.005057 | NaN |
| 7 | 0.542860 | -0.802587 | NaN |
| 8 | 0.669212 | -1.225563 | 0.831125 |
| 9 | 0.407667 | -0.851054 | -0.334266 |
dff.fillna(dff.mean())
| A | B | C | |
|---|---|---|---|
| 0 | 0.974978 | 0.282769 | 2.254196 |
| 1 | 1.863146 | -0.514037 | -0.047407 |
| 2 | -0.979084 | 0.127691 | 0.713527 |
| 3 | 0.578069 | -0.245523 | -0.000601 |
| 4 | 0.578069 | -0.529170 | 0.198238 |
| 5 | 0.006034 | -0.529170 | 0.516402 |
| 6 | 1.139740 | -1.005057 | 0.516402 |
| 7 | 0.542860 | -0.802587 | 0.516402 |
| 8 | 0.669212 | -1.225563 | 0.831125 |
| 9 | 0.407667 | -0.851054 | -0.334266 |
dff.fillna(dff.mean()[1:3])
| A | B | C | |
|---|---|---|---|
| 0 | 0.974978 | 0.282769 | 2.254196 |
| 1 | 1.863146 | -0.514037 | -0.047407 |
| 2 | -0.979084 | 0.127691 | 0.713527 |
| 3 | NaN | -0.245523 | -0.000601 |
| 4 | NaN | -0.529170 | 0.198238 |
| 5 | 0.006034 | -0.529170 | 0.516402 |
| 6 | 1.139740 | -1.005057 | 0.516402 |
| 7 | 0.542860 | -0.802587 | 0.516402 |
| 8 | 0.669212 | -1.225563 | 0.831125 |
| 9 | 0.407667 | -0.851054 | -0.334266 |
删除包含缺失数据的轴标签: dropna#
可以使用 dropna() 排除缺失数据
df1
| a | b | |
|---|---|---|
| 0 | 1 | NaN |
| 1 | <NA> | 2.0 |
| 2 | 2 | 3.2 |
| 3 | 3 | 0.1 |
| 4 | <NA> | 1.0 |
df1.dropna(axis=0)
| a | b | |
|---|---|---|
| 2 | 2 | 3.2 |
| 3 | 3 | 0.1 |
df1.dropna(axis=1)
| 0 |
|---|
| 1 |
| 2 |
| 3 |
| 4 |
对于 Series,也有等效的 dropna() 可用。
df1["a"].dropna()
0 1
2 2
3 3
Name: a, dtype: int64
替换通用值#
通常我们希望用其他值替换任意值。
Series 中的 replace() 和 DataFrame 中的 replace() 提供了一种高效且灵活的方式来执行此类替换。
series = cudf.Series([0.0, 1.0, 2.0, 3.0, 4.0])
series
0 0.0
1 1.0
2 2.0
3 3.0
4 4.0
dtype: float64
series.replace(0, 5)
0 5.0
1 1.0
2 2.0
3 3.0
4 4.0
dtype: float64
我们还可以将任何值替换为 <NA> 值。
series.replace(0, None)
0 <NA>
1 1.0
2 2.0
3 3.0
4 4.0
dtype: float64
您可以使用一个值列表替换另一个值列表
series.replace([0, 1, 2, 3, 4], [4, 3, 2, 1, 0])
0 4.0
1 3.0
2 2.0
3 1.0
4 0.0
dtype: float64
您也可以指定一个映射字典
series.replace({0: 10, 1: 100})
0 10.0
1 100.0
2 2.0
3 3.0
4 4.0
dtype: float64
对于 DataFrame,您可以按列指定单个值
df = cudf.DataFrame({"a": [0, 1, 2, 3, 4], "b": [5, 6, 7, 8, 9]})
df
| a | b | |
|---|---|---|
| 0 | 0 | 5 |
| 1 | 1 | 6 |
| 2 | 2 | 7 |
| 3 | 3 | 8 |
| 4 | 4 | 9 |
df.replace({"a": 0, "b": 5}, 100)
| a | b | |
|---|---|---|
| 0 | 100 | 100 |
| 1 | 1 | 6 |
| 2 | 2 | 7 |
| 3 | 3 | 8 |
| 4 | 4 | 9 |
字符串/正则表达式替换#
cudf 支持使用 replace API 替换字符串值
d = {"a": list(range(4)), "b": list("ab.."), "c": ["a", "b", None, "d"]}
df = cudf.DataFrame(d)
df
| a | b | c | |
|---|---|---|---|
| 0 | 0 | a | a |
| 1 | 1 | b | b |
| 2 | 2 | . | <NA> |
| 3 | 3 | . | d |
df.replace(".", "A Dot")
| a | b | c | |
|---|---|---|---|
| 0 | 0 | a | a |
| 1 | 1 | b | b |
| 2 | 2 | A Dot | <NA> |
| 3 | 3 | A Dot | d |
df.replace([".", "b"], ["A Dot", None])
| a | b | c | |
|---|---|---|---|
| 0 | 0 | a | a |
| 1 | 1 | <NA> | <NA> |
| 2 | 2 | A Dot | <NA> |
| 3 | 3 | A Dot | d |
替换几个不同的值 (列表 -> 列表)
df.replace(["a", "."], ["b", "--"])
| a | b | c | |
|---|---|---|---|
| 0 | 0 | b | b |
| 1 | 1 | b | b |
| 2 | 2 | -- | <NA> |
| 3 | 3 | -- | d |
仅在列 ‘b’ 中搜索 (字典 -> 字典)
df.replace({"b": "."}, {"b": "replacement value"})
| a | b | c | |
|---|---|---|---|
| 0 | 0 | a | a |
| 1 | 1 | b | b |
| 2 | 2 | 替换值 | <NA> |
| 3 | 3 | 替换值 | d |
数值替换#
replace() 也可以像 fillna() 一样使用。
df = cudf.DataFrame(cp_rng.standard_normal((10, 2)))
df[rng.random(df.shape[0]) > 0.5] = 1.5
df.replace(1.5, None)
| 0 | 1 | |
|---|---|---|
| 0 | -0.272310839 | -0.753041385 |
| 1 | 0.450345774 | -0.010457805 |
| 2 | -0.423440013 | -0.503168227 |
| 3 | 0.921114386 | -1.263712911 |
| 4 | <NA> | <NA> |
| 5 | -0.209139866 | -0.936384873 |
| 6 | 0.548776457 | -0.488282384 |
| 7 | <NA> | <NA> |
| 8 | <NA> | <NA> |
| 9 | <NA> | <NA> |
通过传递列表可以替换多个值。
df00 = df.iloc[0, 0]
df.replace([1.5, df00], [5, 10])
| 0 | 1 | |
|---|---|---|
| 0 | 10.000000 | -0.753041 |
| 1 | 0.450346 | -0.010458 |
| 2 | -0.423440 | -0.503168 |
| 3 | 0.921114 | -1.263713 |
| 4 | 5.000000 | 5.000000 |
| 5 | -0.209140 | -0.936385 |
| 6 | 0.548776 | -0.488282 |
| 7 | 5.000000 | 5.000000 |
| 8 | 5.000000 | 5.000000 |
| 9 | 5.000000 | 5.000000 |
您还可以在 DataFrame 原位操作
df.replace(1.5, None, inplace=True)
df
| 0 | 1 | |
|---|---|---|
| 0 | -0.272310839 | -0.753041385 |
| 1 | 0.450345774 | -0.010457805 |
| 2 | -0.423440013 | -0.503168227 |
| 3 | 0.921114386 | -1.263712911 |
| 4 | <NA> | <NA> |
| 5 | -0.209139866 | -0.936384873 |
| 6 | 0.548776457 | -0.488282384 |
| 7 | <NA> | <NA> |
| 8 | <NA> | <NA> |
| 9 | <NA> | <NA> |