Pandas DataFrame 和 NumPy 数组怪异 - df.to_numpy()、np.asarray(df) 和 np.array(df) 给出不同的内存使用

Pandas DataFrame and NumPy array weirdness - df.to_numpy(), np.asarray(df), and np.array(df) give different memory usages

我正在将现有的 Pandas Dataframe 转换为 Numpy 数组。数据框没有 NaN 值并且不是稀疏填充的(从 .csv 文件读入)。此外,为了查看内存使用情况,我执行了以下操作:

sum(df.memory_usage)

2400128

sys.getsizeof(df)

2400144

上述 16 字节的小差异可以忽略不计,并且可以理解,因为使用 sys.getsizeofdf.memory_usage 和求和时大小计算不同并且内存使用的开销不同(供参考, df.info() 或使用 pandas_profiling 库。

现在,将其转换为 Numpy 数组时,内存使用情况似乎存在巨大差异:

sys.getsizeof(np.array(df))

2400120

sys.getsizeof(df.to_numpy())

120

对我来说,这没有任何意义,因为两个数组的类型相同,大小和数据也相同:

np.array(df)

array([[1.0000e+00, 2.0000e+04, 2.0000e+00, ..., 0.0000e+00, 0.0000e+00,
    1.0000e+00],
   [2.0000e+00, 1.2000e+05, 2.0000e+00, ..., 0.0000e+00, 2.0000e+03,
    1.0000e+00],
   [3.0000e+00, 9.0000e+04, 2.0000e+00, ..., 1.0000e+03, 5.0000e+03,
    0.0000e+00],
   ...,
   [1.1998e+04, 9.0000e+04, 1.0000e+00, ..., 3.0000e+03, 4.0000e+03,
    0.0000e+00],
   [1.1999e+04, 2.8000e+05, 1.0000e+00, ..., 3.5000e+02, 2.0950e+03,
    0.0000e+00],
   [1.2000e+04, 2.0000e+04, 1.0000e+00, ..., 0.0000e+00, 0.0000e+00,
    1.0000e+00]])

df.to_numpy() # or similarly, np.asarray(df)

array([[1.0000e+00, 2.0000e+04, 2.0000e+00, ..., 0.0000e+00, 0.0000e+00,
    1.0000e+00],
   [2.0000e+00, 1.2000e+05, 2.0000e+00, ..., 0.0000e+00, 2.0000e+03,
    1.0000e+00],
   [3.0000e+00, 9.0000e+04, 2.0000e+00, ..., 1.0000e+03, 5.0000e+03,
    0.0000e+00],
   ...,
   [1.1998e+04, 9.0000e+04, 1.0000e+00, ..., 3.0000e+03, 4.0000e+03,
    0.0000e+00],
   [1.1999e+04, 2.8000e+05, 1.0000e+00, ..., 3.5000e+02, 2.0950e+03,
    0.0000e+00],
   [1.2000e+04, 2.0000e+04, 1.0000e+00, ..., 0.0000e+00, 0.0000e+00,
    1.0000e+00]])

我发现 df.to_numpy() 使用 np.asarray 来执行转换,所以我也尝试了这个:

sys.getsizeof(np.asarray(df))

120

np.asarray(df)df.to_numpy() 总共使用 120 个字节,而 np.array(df)2400120字节!这没有任何意义!

两个数组都没有存储为稀疏数组,并且如上所示,具有完全相同的输出(并且通过检查类型,这是相同的)。

不知道如何解决这个问题,因为从内存的角度来看这似乎没有任何意义。我试图理解内存使用中的这种巨大差异,因为 .csv 文件中的所有值都是整数或浮点数,并且不存在缺失值或 NaN 值。也许 np.asarray(df)(因此 df.to_numpy())正在做与 np.array(df) 不同的事情,或者 sys.getsizeof 正在做一些奇怪的事情,但我似乎无法解决这个问题。

经过深入分析,我想我找到了这个问题的答案(尽管我认为它并不令人满意)。

首先,我分别存储了每个值,以确保将值分配给变量:

nparr1 = np.array(df)
nparr2 = df.to_numpy()

接下来,比较了每个项目的类型和大小,这表明这些数组中的每一个在格式或存储方面没有差异。这很莫名其妙,然后针对numpy数组itemsizesize发现了以下内容。接下来,对每个项目执行 .itemsize.size

nparr1.itemsize * nparr1.size

2400000

nparr2.itemsize * nparr2.size

2400000

真奇怪!现在这些值正在匹配。这也可以使用与 nbytes 一起使用的字节来检查,它产生与上面相同的值。

nparr1.nbytes

2400000

nparr2.nbytes

2400000

所以不是有什么神奇的压缩算法,而是内存占用都摆在那里。似乎 sys.getsizeof 由于某种原因(仍未解决)有奇怪的行为。但是,请注意与上述问题的不同之处在于:

sys.getsizeof(np.array(df))

2400120

sys.getsizeof(df.to_numpy())

120

现在,奇怪的是,nparr1.nbytes 产生了 2400000。似乎 2400120 - 2400000 = 120。因此,似乎 sys.getsizeof(df.to_numpy()) 产生了开销成本(可能是指向该内存地址的指针)并且 sys.getsizeof(np.array(df)) 产生了 2400000 的完整内存负载加上 120 的开销,即 2400120。我希望这个是正确的分析,如果其他人有其他见解或 df.to_numpy()/np.asarray(df)np.array(df) 的幕后实际发生的事情以及数据如何存储在内存中,以及 sys.getsizeof 的怪异行为,我很乐意了解更多内存操作的不同之处。

我已经与一位同事核实了这一点,这就是我们的决定(我的同事也在同一个 .csv 文件上独立测试了这一点并得出了相同的结论)。然而,对于幕后真正发生的事情以及完全相同的 numpy 数组的这种意外、奇怪的行为,这并不是一个令人满意的答案。

A numpy 具有 shapedtype 等属性,以及 data buffer,它是一个平面 C 数组,用于存储值。

arr.nbytes    # 2400000

告诉您该数据缓冲区的大小。因此,如果数组是 (300,10000) float dtype,那将是 300*1000*8 字节。

getsizeof 2400120 正在报告该缓冲区加上用于数组对象本身、形状元组和 dtype 等的 120 个字节

但数组可能是另一个数组的 view。它将有自己的 120 'overhead',但引用另一个数组的数据缓冲区。 getsizeof 只报告那个 120,而不是共享内存。实际上,它告诉我们该视图消耗了多少额外内存。

数据框是一个复杂的对象,包含索引数组、列名列表(或数组)等。数据的存储方式取决于列数据类型。列可以被视为 Series,或类似 dtype 的列组。我认为在您的情况下,所有列都具有相同的 dtype,因此数据存储在 2d numpy 数组中。它是数据帧 getsizeof 报告的那个数组的数据缓冲区。

df.values
dt.to_numpy()

return 该数据数组的 view。因此 getsizeof 只报告 120.

np.array(df) returns 该数组的副本,它有自己的数据缓冲区,因此是完整大小。阅读其 docs.

np.asarray(df) 有一个 copy=False 参数,因此如果可能的话 return 是一个 view

总而言之,view 的概念是理解您看到的差异的关键。 sys.getsizeof 不是很有用的度量,除非您已经了解对象的组织方式。最好查看您使用的函数的文档,包括 np.arraynp.asarray.to_numpy.