Pandas 赋值结果为 NaN 的布尔过滤器

Pandas Boolean Filter with Assignment resulting in NaN

我很好奇为什么 Pandas 中同时布尔索引 + 赋值的这个玩具示例不起作用:

df = pd.DataFrame({'Source': ['A', 'B', 'C', 'A', 'B', 'C'],
                   'Period': ['1 hr', '1 hr', '1 hr', '24 hr', '24 hr', '24 hr'],
                   'CO': [1.1, 1.2, 1.3, 2.1, 2.2, 2.3],
                   'DPM': [11.1, 11.2, 11.3, 12.1, 12.2, 12.3],
                   'NOx': [21.1, 21.2, 21.3, 22.1, 22.2, 22.3]})

生成的玩具 DataFrame 在这里:

  Source Period   CO   DPM   NOx
0      A   1 hr  1.1  11.1  21.1
1      B   1 hr  1.2  11.2  21.2
2      C   1 hr  1.3  11.3  21.3
3      A  24 hr  2.1  12.1  22.1
4      B  24 hr  2.2  12.2  22.2
5      C  24 hr  2.3  12.3  22.3

现在,我希望最终的 DataFrame 采用 24 hr 值并将其分配给源 A 和 B 的 1 hr 值。最终的 DataFrame 应如下所示:

  Source Period   CO   DPM   NOx
0      A   1 hr  2.1  12.1  22.1
1      B   1 hr  2.2  12.2  22.2
2      C   1 hr  1.3  11.3  21.3
3      A  24 hr  2.1  12.1  22.1
4      B  24 hr  2.2  12.2  22.2
5      C  24 hr  2.3  12.3  22.3

我尝试执行以下命令:

df.loc[df['Source'].isin(['A', 'B']) & (df['Period'] == '1 hr'), ['CO', 'DPM', 'NOx']] =\ 
 df.loc[df['Source'].isin(['A', 'B']) & (df['Period'] == '24 hr'), ['CO', 'DPM', 'NOx']]

但最后我的 DataFrame 被 NaNs 替换了:

  Source Period   CO   DPM   NOx
0      A   1 hr  NaN   NaN   NaN
1      B   1 hr  NaN   NaN   NaN
2      C   1 hr  1.3  11.3  21.3
3      A  24 hr  2.1  12.1  22.1
4      B  24 hr  2.2  12.2  22.2
5      C  24 hr  2.3  12.3  22.3

分配的 LHS 和 RHS 上的过滤器表达式都正确过滤了相同的行数,似乎分配是在它被丢弃的地方。我该如何正确地做到这一点?请注意,我只想更改 CO、DPM 和 NOx 值,而不是任何其他列。

问题是索引不匹配。您可以通过使用底层 numpy 数组来解决该问题:

msk = (df['Period'] == '24 hr')
cols = ['DPM', 'NOx']
df.loc[~msk & df['Source'].isin(['A','B']), cols] = df.loc[msk & df['Source'].isin(['A','B']), cols].to_numpy()

输出:

  Source Period   CO   DPM   NOx
0      A   1 hr  1.1  12.1  22.1
1      B   1 hr  1.2  12.2  22.2
2      C   1 hr  1.3  11.3  21.3
3      A  24 hr  2.1  12.1  22.1
4      B  24 hr  2.2  12.2  22.2
5      C  24 hr  2.3  12.3  22.3

请注意,如果每种“来源”类型的“1 小时”和“24 小时”之间存在 one-to-one 关系,这只会如您所愿。

您也可以使用 groupby + last:

cols = ['DPM', 'NOx']
filt = df['Source'].isin(['A','B'])
df.loc[filt, cols] = df[filt].groupby('Source')[cols].transform('last')