如何高效地从数据框中获取部分列的numpy数组?

4

动机

我经常回答一些问题,建议将数据帧的值转换为底层的numpy数组以进行更快的计算。然而,这样做有一些注意事项和一些比其他方法更好的方法。

我将提供自己的答案,以回馈社区。希望你们能找到它有用。

问题
考虑数据框df

df = pd.DataFrame(dict(A=[1, 2, 3], B=list('xyz'), C=[9, 8, 7], D=[4, 5, 6]))
print(df)

   A  B  C  D
0  1  x  9  4
1  2  y  8  5
2  3  z  7  6

使用 dtypes 属性

print(df.dtypes)

A     int64
B    object
C     int64
D     int64
dtype: object

我希望创建一个numpy数组a,它由列AC的值组成。假设有许多列,我要针对两个特定列AC进行操作。 我的尝试 我可以这样做:
df[['A', 'C']].values

array([[1, 9],
       [2, 8],
       [3, 7]])

这很准确!

不过,我可以用numpy更快地完成它。

p = [df.columns.get_loc(i) for i in ['A', 'C']]
df.values[:, p]

array([[1, 9],
       [2, 8],
       [3, 7]], dtype=object)

这样做速度更快,但不准确。注意dtype=object。我需要整数!

p = [df.columns.get_loc(i) for i in ['A', 'C']]
df.values[:, p].astype(int)

array([[1, 9],
       [2, 8],
       [3, 7]])

现在这个问题已经正确解决,但我可能不知道我所拥有的全部整数。

时间

# Clear and accurate, but slower
%%timeit 
df[['A', 'C']].values
1000 loops, best of 3: 347 µs per loop

# Not accurate, but close and fast
%%timeit 
p = [df.columns.get_loc(i) for i in ['A', 'C']]
df.values[:, p]
10000 loops, best of 3: 59.2 µs per loop

# Accurate for this test case and fast, needs to be more generalized.
%%timeit 
p = [df.columns.get_loc(i) for i in ['A', 'C']]
df.values[:, p].astype(int)
10000 loops, best of 3: 59.3 µs per loop
2个回答

4

pandas不在values属性中存储整个数据帧的单个数组。当您在数据帧上调用values属性时,它会从数据帧实际存储的底层对象(即pd.Series对象)构建数组。数据帧可以被看作是一个pd.Series对象的pd.Series对象,其中每一列都是该数据帧包含的一个这样的pd.Series对象。每个列可以有一个与其他列不同的dtype,这也是数据帧如此有用的原因之一。然而,NumPy数组必须具有一种类型。当我们在数据帧上调用values属性时,它会去到每一列,并从各自的values属性中提取数据,并将它们拼接在一起。如果每列的dtype不一致,则结果数组的dtype将被强制为object

选项1
缓慢但准确

a = df[['A', 'C']].values

这种方式较慢的原因是您要求pandas为您构建一个新的数据框架df[['A', 'C']],然后通过访问每个新数据框架列的values属性来构建数组a选项2
查找列位置,然后切片values
c = ['A', 'C']
p = [df.columns.get_loc(i) for i in c]
a = df.values[:, p].astype(df.dtypes[c[0]])

这个方案更好,因为我们只构建值数组而不需要重新构建数据框。我相信我们得到了一个具有一致数据类型的数组。如果需要进行向上转型,我会在这里处理不好。 选项3 我偏爱的方法 仅访问我关心的列的值
a = np.column_stack([df[col].values for col in ['A', 'C']])

这利用了pandas dataframe作为pd.Series的容器,我只访问我关心的列的values属性。然后从这些数组构建一个新数组。如果需要处理类型转换,numpy会处理它。


所有方法都产生相同的结果。

array([[1, 9],
       [2, 8],
       [3, 7]])

时间
小数据

%%timeit 
a = df[['A', 'C']].values
1000 loops, best of 3: 338 µs per loop

%%timeit 
c = ['A', 'C']
p = [df.columns.get_loc(i) for i in c]
a = df.values[:, p].astype(df.dtypes[c[0]])
10000 loops, best of 3: 166 µs per loop

%timeit np.column_stack([df[col].values for col in ['A', 'C']])
The slowest run took 7.36 times longer than the fastest. This could mean that an intermediate result is being cached.
100000 loops, best of 3: 8.97 µs per loop

大数据

df = pd.concat(
    [df.join(pd.DataFrame(
                np.random.randint(10, size=(3, 22)),
                columns=list(ascii_uppercase[4:])
            ))] * 10000, ignore_index=True
)


%%timeit 
a = df[['A', 'C']].values
The slowest run took 23.28 times longer than the fastest. This could mean that an intermediate result is being cached.
1000 loops, best of 3: 371 µs per loop
In [305]:

%%timeit 
c = ['A', 'C']
p = [df.columns.get_loc(i) for i in c]
a = df.values[:, p].astype(df.dtypes[c[0]])
100 loops, best of 3: 9.62 ms per loop

%timeit np.column_stack([df[col].values for col in ['A', 'C']])
The slowest run took 6.66 times longer than the fastest. This could mean that an intermediate result is being cached.
10000 loops, best of 3: 55.6 µs per loop

一个 pd 系列是否使用 numpy 数组来存储其值? - hpaulj
@hpaulj 我坦白说,我不能确定。但我相当肯定是肯定的。@property; def values 是指一个我无法追踪到的 _data 属性。但是 def __init__ 显示 data 属性被分配了一个 SingleBlockManager - piRSquared
@hpaulj 那其实是一个 numpy 数组... 有点像 :-) - piRSquared

1

try this:

np.array(zip(df['A'].values, df['C'].values))

timeit:

%%timeit
np.array(zip(df['A'].values, df['C'].values))

最慢的运行时间比最快的运行时间长了5.51倍。这可能意味着中间结果被缓存了。 10000次循环,3次取最佳结果:每次循环17.8微秒。

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