更改tabular.tabarray或numpy.recarray的数据类型(dtype)

1 投票
2 回答
1801 浏览
提问于 2025-04-17 07:20

我想在Python中像电子表格那样表示数据。我心想:“肯定有人写过这样的模块!”于是我去了PyPI,发现了Tabular,它用强大的数据处理功能包装了NumPy的recarrays。太好了!可惜的是,当涉及到字符串时,它似乎根本不像电子表格。

>>> import tabular as tb
>>> t = tb.tabarray(records=[('bork', 1, 3.5), ('stork', 2, -4.0)], names=['a','b','c'])
>>> t
tabarray([('bork', 1, 3.5), ('stork', 2, -4.0)], 
      dtype=[('a', '|S5'), ('b', '<i8'), ('c', '<f8')])
>>> t['a'][0] = 'gorkalork, but not mork'
>>> t
tabarray([('gorka', 1, 3.5), ('stork', 2, -4.0)], 
      dtype=[('a', '|S5'), ('b', '<i8'), ('c', '<f8')])

呃……tabarray!你把我的字符串截断了!真的?!NumPy的dtype '|S5'意味着它是一个最多5个字符的字符串,但拜托!更新一下数据类型。如果需要的话,重新格式化整个列。无论如何,别悄悄地丢掉我的数据!

我尝试了几种其他方法,但都没能解决问题。例如,它在创建tabarray时会推测数据类型和大小,但在添加记录时却不这样做:

>>> t.addrecords(('mushapushalussh', 3, 4.44))
tabarray([('gorka', 1, 3.5), ('stork', 2, -4.0), ('musha', 3, 4.44)], 
      dtype=[('a', '|S5'), ('b', '<i8'), ('c', '<f8')])

我尝试切出整个列,改变它的类型,设置值,然后重新赋值:

>>> firstcol_long = firstcol.astype('|S15')
>>> firstcol_long
tabarray(['gorka', 'stork'], 
      dtype='|S15')
>>> firstcol_long[0] = 'morkapork'
>>> firstcol_long
tabarray(['morkapork', 'stork'], 
      dtype='|S15')
>>> t['a'] = firstcol_long
>>> t
tabarray([('morka', 1, 3.5), ('stork', 2, -4.0)], 
      dtype=[('a', '|S5'), ('b', '<i8'), ('c', '<f8')])
>>> 

值赋值是正确的,但原来的数据类型仍然在起作用,我之前正确的数据又被悄悄截断了。我甚至尝试了明确设置数据类型:

>>> t = tb.tabarray(records=[('bork', 1, 3.5), ('stork', 2, -4.0)], dtype=[('a', str),('b', int),('c', float)])
>>> t
tabarray([('', 1, 3.5), ('', 2, -4.0)], 
      dtype=[('a', '|S0'), ('b', '<i8'), ('c', '<f8')])

天哪!这更糟糕!它正确地映射了intfloat类型,但它猜测str意味着我想要0长度的字符串,结果把所有数据都截断成了空。长话短说,tabular不仅开箱即用时不像电子表格,我也找不到让它正常工作的办法。性能对我来说不是大问题。我的电子表格最多可能有几百或几千行,我很乐意让系统做一些数据复制来简化我的代码。Tabular在许多其他方面似乎都很合适。

我想我可以用一个子类来扩展tabular,让所有字符串默认设置为一个不太可能的较大值(比如1024或4096字节),并且如果分配了更大的字符串就抛出异常。虽然这样有点粗糙……但有没有更好的替代方案?我稍微研究了一下numpy.recarray等,没看到明确的办法……但我会第一个承认我对NumPy完全不熟悉。实际上,数据处理程序可能会将字符串的长度增加到初始最大值之外。肯定有高功能的模块应该能适应这一点。1974年记录导向数据库中常见的“就截断它!”的方法,不能是2011年Python的先进状态!

有什么想法和建议吗?

2 个回答

2

我不是想冒犯你,但你对 numpy 的理解有些偏差。

你其实不需要 numpy。你需要的是一个 dict(字典)。

numpy 数组并不是通用的数据容器。它们是用来存储相同类型数据的高效内存容器。它的主要目的是作为一种低级数据容器,可以几乎不占用额外内存地存储数据(比如,存储 100 个 64 位的浮点数在 numpy 数组中只需要 800 字节的内存,而用列表存储则需要两倍的内存)。

如果你想存储不同类型的数据,那就不要使用 numpy 数组。

Python 提供了很多内置的数据结构。在这种情况下,你应该使用字典或列表。

6

作为表格设计的其中一位设计者,我得说,第一个回答者的观点基本上是对的。

楼主,你所说的“截断”行为其实是NumPy的一个基本问题,而Tabular正是基于NumPy的。不过,说这是一个应该修复的“错误”并不准确,更像是NumPy(和Tabular)本身的一个“限制”。

正如第一个回答者提到的,NumPy对数据结构的大小有绝对的要求。一旦你分配了一个特定数据类型的numpy数组,这个数组就必须保持这个数据类型——否则,就必须初始化一个新的数组来使用新的内存。对于字符串数据类型来说,字符串的长度是这个数据类型的一个固定部分——你不能简单地把一个长度为N的字符串数组转换成一个长度为M的字符串数组。

固定的数据类型对于NumPy在性能上超越标准Python对象是至关重要的。因为有了固定的数据类型,NumPy对象知道每个对象分配了多少字节,可以直接“跳”到内存中某个特定位置,而不需要读取和处理所有中间的内容,这一点与Python列表不同。当然,这也限制了可以自然成为numpy数组的对象类型……或者说,限制了对numpy数组可以进行的操作。与完全可变的Python列表不同(例如,你可以用任何其他Python对象替换列表中的任何元素,而不会影响列表中其他对象的内存分配),你不能把numpy数组的值更改为不同数据类型的对象——因为那样字节的管理就会出问题。如果第N个项目的大小突然大于数组中所有其他项目,那剩下的所有项目的数据和位置会发生什么呢?

你可能不喜欢NumPy在你尝试进行“非法”赋值时的默认行为——也许你希望出现错误,而不是默默地截断?如果是这样,你应该在NumPy的讨论组上发帖,因为我认为这是一个比Tabular更根本的问题——无论我们个人对错误处理的看法如何,我们都希望与NumPy在这方面保持一致。

你可能也不喜欢Tabular如何进行数据类型推断。实际上,NumPy避免使用数据类型推断,基本上总是要求用户明确指定数据类型。这在某种程度上是好的,因为它要求用户考虑这些问题,但有时这也很麻烦。Tabular试图找到一个大多数情况下都有效的折中方案,但有时会失败——在这种情况下,只需像NumPy构造函数一样指定相同的关键字参数,就可以覆盖默认设置。

我认为你说“1974年记录导向数据库的常见方法不能成为2011年Python的先进状态”并不完全正确。实际上,NumPy的内存管理基础确实与1970年代使用的工具完全相同——这可能让人惊讶,但许多优化过的NumPy部分仍然是基于Fortran构建的!那些年代的内存分配问题至今仍然无法避免,尽管NumPy大多数时候提供了更干净、更简单的接口。但必须说,如果你“乐意让系统进行一些数据复制以简化我的代码”——那么NumPy和Tabular可能不适合你,因为默默的数据复制及其所代表的一切,显然与这些包的设计意图相悖。

所以问题变成了:你的目标是什么?如果你真的需要在数组操作上获得性能,那么就使用NumPy——在这种情况下,Tabular提供类似电子表格的操作——但要在NumPy的限制内。如果你不需要性能,那么一开始就没有必要使用类似数组的对象,你可以更加灵活。然而,Tabular的电子表格操作并不扩展到一般的Python对象——而且甚至不清楚如何进行这种扩展。

另外,我想再补充一点(相当重要)——楼主,如果性能不是你的主要问题,但你仍然想使用Tabular作为电子表格操作的来源,你可以在每次需要更改数据类型的操作中,重新调用Tabular数组构造函数。也就是说,如果在某个操作中你需要将赋值改为一个新的更大的字符串数据类型,只需每次构造一个新的Tabarray。这显然在性能上不太好,但如果这不是你的限制,那就没问题。

这里的关键点是,Tabular和NumPy设定了某些标准来判断什么是“快”或“慢”——然后,强迫你对那些会慢的操作保持明确。他们从不允许你像Matlab那样在后台隐藏非常慢的操作。语法上简单的东西应该是快的——如果你想做一些会慢的事情,你就需要在代码中多花点力气去实现,从而关注正在发生的事情。因此,你的代码最终会更简洁、更好,但写起来仍然比直接用C或Fortran要简单得多。实际上,这一原则在Python本身也适用——尽管对于什么算作“快”或“慢”的标准有所不同。

希望这对你有帮助,

D

撰写回答