Python numpy 中缺失值的奇怪时间

Strange timings on missing values in Python numpy

我有一个很长的一维 numpy 数组,其中有大约 10% 的缺失值。我想重复将其缺失值 (np.nan) 更改为其他值。我知道有两种方法可以做到这一点:

data[np.isnan(data)] = 0 或函数 np.copyto(data, 0, where=np.isnan(data))

有时我想在那里放零,其他时候我想恢复 nans。 我认为反复重新计算np.isnan函数会很慢,最好保存nan的位置。下面代码的一些计时结果是违反直觉的。

我运行以下:

import numpy as np
import sys
print(sys.version)
print(sys.version_info)
print(f'numpy version {np.__version__}')
data = np.random.random(100000)
data[data<0.1] = 0
data[data==0] = np.nan
%timeit missing = np.isnan(data)
%timeit wheremiss = np.where(np.isnan(data))
missing = np.isnan(data)
wheremiss = np.where(np.isnan(data))
print("Use missing list store 0")
%timeit data[missing] = 0
data[data==0] = np.nan
%timeit data[wheremiss] = 0
data[data==0] = np.nan
%timeit np.copyto(data, 0, where=missing)
print("Use isnan function store 0")
data[data==0] = np.nan
%timeit data[np.isnan(data)] = 0
data[data==0] = np.nan
%timeit np.copyto(data, 0, where=np.isnan(data))
print("Use missing list store np.nan")
data[data==0] = np.nan
%timeit data[missing] = np.nan
data[data==0] = np.nan
%timeit data[wheremiss] = np.nan
data[data==0] = np.nan
%timeit np.copyto(data, np.nan, where=missing)
print("Use isnan function store np.nan")
data[data==0] = np.nan
%timeit data[np.isnan(data)] = np.nan
data[data==0] = np.nan
%timeit np.copyto(data, np.nan, where=np.isnan(data))

我得到了以下输出(我冒昧地在时间线中添加了数字,以便以后参考):

3.7.3 | packaged by conda-forge | (default, Jul  1 2019, 22:01:29) [MSC v.1900 64 bit (AMD64)]
sys.version_info(major=3, minor=7, micro=3, releaselevel='final', serial=0)
numpy version 1.17.1
01. 30 µs ± 2.68 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
02. 219 µs ± 24.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Use missing list store 0
03. 339 µs ± 23.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
04. 26 µs ± 1.92 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
05. 287 µs ± 26.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Use isnan function store 0
06. 38.5 µs ± 2.76 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
07. 43.8 µs ± 4.67 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Use missing list store np.nan
08. 328 µs ± 30.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
09. 24.8 µs ± 2.03 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
10. 322 µs ± 30 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
Use isnan function store np.nan
11. 356 µs ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
12. 300 µs ± 4.29 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

所以这是第一个问题。为什么存储 np.nan 的时间比存储 0 的时间长将近 10 倍? (比较第 6 和 7 行与第 11 和 12 行)

与使用 isnan 函数重新计算缺失值相比,为什么使用 missing 的存储列表需要更长的时间? (比较第 3 行和第 5 行与第 6 行和第 7 行)

这只是出于好奇。我可以看到最快的方法是使用 np.where 来获取索引列表(因为我只缺少 10%)。但如果我有更多,事情可能不会那么明显。

因为你没有衡量你认为的自己!你在做测试时改变了你的 data,并且 timeit 运行 多次测试。因此,额外的 运行s 是 运行ning 改变的数据。当您下次将值更改为 0 时 运行 isnan 您什么也得不到,分配基本上是空操作。而当您分配 nan 时,这会导致在下一次迭代中完成更多工作。

你关于何时使用 np.where 与将其保留为 bool 数组的问题有点困难。它将涉及不同数据类型的相对大小(例如 bool 是 1 个字节,int64 是 8 个字节),所选值的比例,分布与 CPU/memory 子系统的优化(例如,它们主要是在一个块中还是均匀分布),执行 np.where 的相对成本与结果将被重用的次数,以及我现在无法想到的其他事情。

对于其他用户,可能值得指出 RAM latency (i.e. speed) is >100 times slower than L1 cache,因此保持内存访问可预测对于最大化缓存利用率很重要