处理缺失数据#

在本节中,我们将讨论 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 值,summean 支持 skipna 参数。默认情况下其值设置为 True,我们可以将其更改为 False 以保留 NA 值。

df1["a"].sum(skipna=False)
np.float64(nan)
df1["a"].mean(skipna=False)
np.float64(nan)

默认情况下,累积方法(如 cumsumcumprod)会忽略 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=FalseNA 包含在组中

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 数据填充 NANaN 值。

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>