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.getsizeof
与 df.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数组itemsize
和size
发现了以下内容。接下来,对每个项目执行 .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
具有 shape
和 dtype
等属性,以及 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.array
、np.asarray
和 .to_numpy
.
我正在将现有的 Pandas Dataframe 转换为 Numpy 数组。数据框没有 NaN
值并且不是稀疏填充的(从 .csv
文件读入)。此外,为了查看内存使用情况,我执行了以下操作:
sum(df.memory_usage)
2400128
sys.getsizeof(df)
2400144
上述 16 字节的小差异可以忽略不计,并且可以理解,因为使用 sys.getsizeof
与 df.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数组itemsize
和size
发现了以下内容。接下来,对每个项目执行 .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
具有 shape
和 dtype
等属性,以及 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.array
、np.asarray
和 .to_numpy
.