高效更新具有混合 dtype 列的 pandas 数据帧中的值



我有一个形状为(700,000,5,000(的大型熊猫数据帧,其中包含混合dtype的列(主要是int8,一些float64和几个datetime64[ns](。对于数据帧中的每一行,如果另一列也等于零,我想将某些列的值设置为零。

如果我遍历数据帧并使用 iloc 设置值,它会非常慢。我已经尝试了迭代和迭代,例如

1. 迭代行

ix_1 = 3
ix_to_change = [20, 24, 51]  # Actually it is almost 5000 columns to change
for i, row in df.iterrows():
if not row[ix_1]:
df.iloc[i, ix_to_change] = 0

2. 迭代:

ix_1 = 3
ix_to_change = [20, 24, 51]  # Actually it is almost 5000 columns to change
for row in df.itertuples():
if not row[ix_1 + 1]:
df.iloc[row[0], ix_to_change] = 0

我也尝试过使用熊猫索引,但它也非常慢(尽管比迭代或迭代更好(。

3. 熊猫 LOC 和 ILOC

df.loc[df.iloc[:, ix_1]==0, df.columns[ix_to_change]] = 0

然后,我尝试下降到在性能方面工作正常的底层numpy数组,但是我遇到了dtypes的问题。

它快速遍历底层数组,但新数据帧具有所有"对象"dtype。如果我尝试设置每列的 dtypes(如本例所示(,则在日期时间列上失败 - 可能是因为它们包含 NaT 项目。

4. 麻比

X = df.values
for i, x in enumerate(X):
if not x[ix_1]:
X[i].put(ix_to_change, 0)
original_dtypes = df.dtypes
df = pd.DataFrame(data=X, index=df.index, columns=df.columns)
for col, col_dtype in original_dtypes.items():
df[c] = df[c].astype(col_dtype)

有没有更好的方法让我首先进行更新?

或者,如果没有,我应该如何保持我的 dtype 相同(日期时间列不在要更改的列列表中,以防相关(?

或者,也许有更好的方法让我使用更新的 numpy 数组更新原始数据帧,其中我只更新更改的列(所有这些都是 int8(?

更新

按照注释中的要求,这里有一个最小的示例,说明了 int8 dtype 在放入 numpy 后如何成为对象 dtype。需要明确的是,这只是上述方法 4 的问题(这是我迄今为止唯一的非慢速方法 - 如果我能解决这个 dtype 问题(:

import pandas as pd
df = pd.DataFrame({'int8_col':[10,11,12], 'float64_col':[1.5, 2.5, 3.5]})
df['int8_col'] = df['int8_col'].astype('int8')
df['datetime64_col'] = pd.to_datetime(['2018-01-01', '2018-01-02', '2018-01-03'])
>>> df.dtypes
float64_col              float64
int8_col                    int8
datetime64_col    datetime64[ns]
dtype: object
X = df.values
# At this point in real life I modify the int8 column(s) only in X
new_df = pd.DataFrame(data=X, index=df.index, columns=df.columns)
>>> new_df.dtypes
float64_col       object
int8_col          object
datetime64_col    object
dtype: object

TL;博士

为了提高 Pandas/NumPy 的效率,不要在列中使用混合类型(objectdtype(。有一些方法可以将序列转换为数字,然后有效地操作它们。


您可以使用pd.DataFrame.select_dtypes来确定数字列。假设这些是您希望更新值的唯一值,则可以将它们提供给pd.DataFrame.loc

它快速遍历底层数组,但新的 数据帧具有所有"对象"dtype。

鉴于您只剩下objectdtype 系列,似乎您对ix_to_change的定义包括非数字系列。在这种情况下,应将所有数字列转换为数字 dtype。例如,使用pd.to_numeric

df[ix_to_change] = df[ix_to_change].apply(pd.to_numeric, errors='coerce')

熊猫/NumPy 在性能方面对objectdtype 系列没有帮助,如果这是您所追求的。这些系列在内部表示为一系列指针,非常类似于list

下面是一个示例来演示您可以执行的操作:

import pandas as pd, numpy as np
df = pd.DataFrame({'key': [0, 2, 0, 4, 0],
'A': [0.5, 1.5, 2.5, 3.5, 4.5],
'B': [2134, 5634, 134, 63, 1234],
'C': ['fsaf', 'sdafas',' dsaf', 'sdgf', 'fdsg'],
'D': [np.nan, pd.to_datetime('today'), np.nan, np.nan, np.nan],
'E': [True, False, True, True, False]})
numeric_cols = df.select_dtypes(include=[np.number]).columns
df.loc[df['key'] == 0, numeric_cols] = 0

结果:

A     B       C          D      E  key
0  0.0     0    fsaf        NaT   True    0
1  1.5  5634  sdafas 2018-09-05  False    2
2  0.0     0    dsaf        NaT   True    0
3  3.5    63    sdgf        NaT   True    4
4  0.0     0    fdsg        NaT  False    0

没有按预期转换为数字列objectdtype 系列:

print(df.dtypes)
A             float64
B               int64
C              object
D      datetime64[ns]
E                bool
key             int64
dtype: object

这在更新值时使用 NumPy 迭代的效率,并解决了 dtype 问题。

# numpy array of rows. Only includes columns to update (all int8) so dtype doesn't change
X = df.iloc[:, ix_to_change].values
# Set index on key to allow enumeration to match index
key_col = df.iloc[:, ix_1]
key_col.index = range(len(key_col))
# Set entire row (~5000 values) to zeros. More efficient than updating element-wise.
zero_row = np.zeros(X.shape[1])
for i, row in enumerate(X):
if key_col[i] == 0:
X[i] = zero_row
# Transpose to get array of column arrays.
# Each column array creates and replaces a Series in the DataFrame
for i, row in enumerate(X.T):
df[df.columns[ix_to_change[i]]] = row

X 是一个 NumPy 数组,只有我想要"零"的列,它们都是 int8 dtype。

我遍历这些 X 行(这里比在 pandas 中更有效(,然后 X.T 给了我可以用来替换 pandas 中整个列的数组。

这避免了对大数据帧的缓慢 iloc/loc 调用,并且我最终在所有列上都拥有不变的 dtype。

最新更新