高效地将numpy数组中的数组转换为pandas系列中的数组

4
我该如何高效地将一个numpy数组的numpy数组转换为数组列表?最终,我想创建一个由数组组成的pandas系列作为数据框的一列。如果有更好的直接方法,那也很好。
下面的可重现代码解决了使用list()或.tolist()的问题,但是两者在我的实际数据集上都太慢了。我正在寻找一些更快的方法。
import numpy as np 
import pandas as pd

a = np.array([np.array([0,1,2,3]), np.array([4,5,6,7])])

s = pd.Series(a.tolist())

s = pd.Series(list(a))

这导致形状从 a.shape = (2,4) 变为 s.values.shape = (2,)

2
为什么不用 pd.DataFrame(a) - miradulo
a 是一个二维数组,形状为 (2,4)。它不是一个数组的数组(除非你先构造了一个形状为 (2,) 的对象数组)。这应该映射到一个有 4 列的 DataFrame。或者你真的想要一个每个元素都是数组(和对象 dtype)的 Series 吗?我认为那不会是一个高效的 Series。它也不是一个高效的数组。 - hpaulj
@hpaulj - 是的,我“想要一个每个元素都是数组的系列”。 - Clay
@miradulo 这将导致嵌套数组中的每个元素都有一个单独的列。我希望生成的数据框只有一列,其中每行都有一个a的嵌套数组。 - Clay
你知道如何创建一个包含数组的一维数组吗?对象数据类型?你的示例 a 不符合要求。尝试改变子数组长度或包含 None - hpaulj
从速度上来说,哪个更慢,a.tolist() 还是从该列表创建 Series? - hpaulj
2个回答

8
你的
In [2]: a = np.array([np.array([0,1,2,3]), np.array([4,5,6,7])])
   ...: 
a是一个(2,4)的数值数组;我们也可以简单地写成a = np.array([[0,1,2,3],[4,5,6,7]])。创建一个由数组组成的(2,)数组需要不同的构造方法。
正如其他人所写的那样,创建一个数据框架非常容易:
In [3]: pd.DataFrame(a)     # dtypes int64
Out[3]: 
   0  1  2  3
0  0  1  2  3
1  4  5  6  7

但是将其制作成系列会出现错误:

In [4]: pd.Series(a)
---------------------------------------------------------------------------
...
Exception: Data must be 1-dimensional

如果您能展示出现的错误以及为什么尝试使用列表输入,那么您的问题会更清晰明了:

In [5]: pd.Series(a.tolist())
Out[5]: 
0    [0, 1, 2, 3]
1    [4, 5, 6, 7]
dtype: object
In [6]: pd.Series(list(a))
Out[6]: 
0    [0, 1, 2, 3]
1    [4, 5, 6, 7]
dtype: object

表面上看起来它们是相同的,但是当我们查看Series的实际元素时,我们会发现一个包含列表,另一个包含数组。这是因为tolistlist()从数组中创建不同的列表。

In [8]: Out[5][0]
Out[8]: [0, 1, 2, 3]
In [9]: Out[6][0]
Out[9]: array([0, 1, 2, 3])

我的经验是,a.tolist()非常快。 list(a)等效于[i for i in a];实际上它会在a的第一维上迭代,每次返回一个1d数组(行)。


让我们将a更改为一个1d对象dtype数组:

In [14]: a = np.array([np.array([0,1,2,3]), np.array([4,5,6,7]), np.array([1]), None])
In [15]: a
Out[15]: 
array([array([0, 1, 2, 3]), array([4, 5, 6, 7]), array([1]), None],
      dtype=object)

现在我们可以从中创建一个序列(Series):
In [16]: pd.Series(a)
Out[16]: 
0    [0, 1, 2, 3]
1    [4, 5, 6, 7]
2             [1]
3            None
dtype: object
In [17]: Out[16][0]
Out[17]: array([0, 1, 2, 3])

事实上,我们可以从一个只包含原始2行的切片a中制作一个系列。
In [18]: pd.Series(a[:2])
Out[18]: 
0    [0, 1, 2, 3]
1    [4, 5, 6, 7]
dtype: object

如何构建一维对象数据类型数组的技巧在其他SO问题中已经深入讨论过。

请注意,像这样的Series不会像多列DataFrame一样运行。我曾看到有人试图编写csv文件,其中像这样的元素被保存为带引号的字符串。


让我们比较一些构建时间:

创建两种类型的更大的数组:

In [25]: a0 = np.ones([1000,4],int)
In [26]: a1 = np.empty(1000, object)
In [27]: a1[:] = [np.ones(4,int) for _ in range(1000)]
# a1[:] = list(a0)   # faster

首先创建一个DataFrame:

In [28]: timeit pd.DataFrame(a0)
136 µs ± 919 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

这与Out[3]的时间相同,显然只是使用2D数组(任何大小)作为values创建DataFrame的开销。

像您所做的那样创建系列:

In [29]: timeit pd.Series(list(a0))
434 µs ± 12.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
In [30]: timeit pd.Series(a0.tolist())
315 µs ± 5.64 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

这两个比a小的更长,反映了创建过程中迭代的特性。

使用一维对象数组:

In [31]: timeit pd.Series(a1)
103 µs ± 1.66 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

这与小型1d数组相同。就像In[28]一样,我认为只有创建Series对象的开销,然后将其分配给一个未更改的值数组。
现在构建a1数组的速度较慢。
a1这样的对象数组在许多方面都像列表-它包含指向内存中其他位置的对象的指针。如果元素类型不同(例如包括字符串或None),则可能很有用,但从计算上来说,它并不等同于2d数组。
总之,如果源数组确实是1d对象dtype数组,则可以快速从中创建一个Series。如果它确实是2d数组,则您需要以某种方式将其首先转换为列表或1d对象数组。

1
你可以从具有相同长度列表的字典或列表创建DataFrame。在前一种情况下,pandas将键转换为列名,并将列表转换为列值;在后一种情况下,每个列表被视为行。
import numpy as np 
import pandas as pd

a = np.array([np.array([0,1,2,3]), np.array([4,5,6,7])])
df = pd.DataFrame()
df['a'] = a.tolist()
df

输出:

    a
0   [0, 1, 2, 3]
1   [4, 5, 6, 7]

@Clay 第一行应该是 [0,4],第二行是 [1,5]? - Krishna
不,第1行第1列应该是array([0,1,2,3]),第2行第1列应该是array([4,5,6,7])。如果您可以先从a创建一个数据框,然后将每一行转换为一个新列中的数组,而不使用for循环,那么应该可以解决问题。 - Clay
pd.DataFrame({'a':a.tolist()})? - Krishna
谢谢@krishna,但是对于我的实际用例来说,仍然使用.tolist()太慢了。 - Clay
我可以知道你实际使用情况的形状吗? - Krishna
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接