如何用来自 Pandas 数据框的单独 NaN 的不同值替换重复的 NaN

How to replace repeated NaNs with a different value from lone NaNs from Pandas data frame

我在一个数据框中排列了几个时间序列,类似于下面:


   category value   time_idx
0   810     0.118794    0
1   830     0.552947    0
2   1120    0.133193    0
3   1370    0.840183    0
4   810     0.129385    1
... ... ... ...
6095 1370   0.157391    1523
6096 810    0.141377    1524
6097 830    0.212254    1524
6098 1120   0.069970    1524
6099 1370   0.134947    1524

有些值为 NaN。我想要的是用 0 替换任何未重复的 NaN 值,因为我假设当时该类别的值为 0。但是,只要每个类别同时具有 NaN 值(即同时 time_idx),那么我想用 -1 替换每个值。

用一个值替换 NaN 在 Pandas 中当然是微不足道的,但是在给定时间专门替换每个类别的 NaN 的 NaN 的额外复杂性让我感到难过。我知道我可以循环遍历时间索引,但我的实际数据集将有 900 多个类别,所以我想找到一种更有效的 Pandas-esque 方法。

我唯一能想到的就是列表理解,我认为它不一定比显式循环更有效,而且我想不出一个可以正常工作的方法。

我知道我可以像这样替换所有 NaN:

data["value"] = data["value"].replace(np.nan, 0)

但我不确定如何在我的情况下实现这一点,我只想用 0 替换长 NaN。这是我目前的循环:

num_channels = data["category"].nunique()
nan_vals = data[lambda x: np.isnan(x.value)]
nan_times = nan_vals["time_idx"]

for time in nan_times:
        if nan_vals[lambda x: x.time_idx == time]["category"].nunique() < num_channels:
            # Set 0 for every channel that has nan at time t
            index = nan_vals[lambda x: x.time_idx == time].index

            data.loc[index, ["value"]] =  data.loc[index, "value"].replace(np.nan, 0)

        else:

            index = nan_vals[lambda x: x.time_idx == time].index
            data.loc[index, ["value"]] = data[lambda x: x.time_idx == time]["value"].replace(np.nan, -1)

欢迎任何想法。

这是一个例子:

给定以下数据框:

    category    value   time_idx
0   810          NaN    0
1   830          NaN    0
2   1120         NaN    0
3   1370         NaN    0
4   810      0.129385   1
5   830          NaN    1
6   1120     0.144378   1
7   1370         NaN    1
8   810      0.124334   2
9   830      0.487274   2
10  1120     0.119153   2
11  1370     0.871687   2

我想要这个输出:

    category    value   time_idx
0   810        -1.000000    0
1   830        -1.000000    0
2   1120       -1.000000    0
3   1370       -1.000000    0
4   810         0.129385    1
5   830         0.000000    1
6   1120        0.144378    1
7   1370        0.000000    1
8   810         0.124334    2
9   830         0.487274    2
10  1120        0.119153    2
11  1370        0.871687    2

在此示例中,在时间 = 0 时每个类别的值为 NaN,因此它们将被替换为 -1。在时间 = 1 时,存在非 NaN 值,因此存在的任何 NaN 值(类别 830 和 1370)都将替换为 0。

您可以使用 groupby 然后 group.isna().all() 找到所有条目均为 NaN 的那些 time_idx。您可以使用该掩码用 -1.

填充 NaN

然后使用 fillna.

0 填充所有其他 NaN
all_nas = df.groupby("time_idx").value.apply(lambda group: group.isna().all())
df = df.set_index("time_idx")
df.loc[all_nas, "value"] = -1
df = df.reset_index().fillna(0)
print(df)

#     time_idx  category     value
# 0          0       810 -1.000000
# 1          0       830 -1.000000
# 2          0      1120 -1.000000
# 3          0      1370 -1.000000
# 4          1       810  0.129385
# 5          1       830  0.000000
# 6          1      1120  0.144378
# 7          1      1370  0.000000
# 8          2       810  0.124334
# 9          2       830  0.487274
# 10         2      1120  0.119153
# 11         2      1370  0.871687

您可以按 time_idx 分组并遍历组。 然后在每个组中计算 value 列中 NaN 值的数量。 根据 nans 的数量,可以更新 value 列。


import pandas as pd

df = pd.DataFrame(
    {
        'category': [810, 830, 1120, 810, 830, 1120, 810, 830, 1120],
        'value': [None, None, None, 1, 2, None, None, None, 4],
        'time_idx': [0, 0, 0, 1, 1, 1, 2, 2, 2],
    }
)

print(df, end='\n\n')


for name, group in df.copy().groupby('time_idx'):
    num_nans = group['value'].isnull().sum()
    mask = (df['time_idx'] == name) & df['value'].isna()
    if num_nans == len(group):
        df.loc[mask, 'value'] = -1
    else:
        df.loc[mask, 'value'] = 0

print(df)

输出

   category  value  time_idx
0       810    NaN         0
1       830    NaN         0
2      1120    NaN         0
3       810    1.0         1
4       830    2.0         1
5      1120    NaN         1
6       810    NaN         2
7       830    NaN         2
8      1120    4.0         2

   category  value  time_idx
0       810   -1.0         0
1       830   -1.0         0
2      1120   -1.0         0
3       810    1.0         1
4       830    2.0         1
5      1120    0.0         1
6       810    0.0         2
7       830    0.0         2
8      1120    4.0         2