高效地更新混合数据类型列的 Pandas 数据帧中的值。

4
我有一个巨大的pandas数据帧,形状为(700,000, 5,000),包含各种类型的列(主要是int8,一些float64和几个datetime64[ns])。对于数据框中的每一行,如果另一列也等于零,我想将某些列的值设置为零。
如果我迭代数据框并使用iloc设置值,则速度非常慢。我尝试过iterrows和itertuples,例如:
1.使用iterrows方法
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. itertuples:

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

我也尝试过使用pandas索引,但它仍然很慢(虽然比iterrows或itertuples好)。

3. pandas loc & iloc

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

我尝试过降低到底层的numpy数组,性能方面很好,但是我在dtype方面遇到了问题。

它快速地迭代底层数组,但新的数据框架具有所有“对象”dtype。如果我尝试按列设置dtype(如此示例),则会在日期时间列上失败 - 可能是因为它们包含NaT项。

4. numpy

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)

有没有更好的方法让我首先进行更新?
如果没有,那么我应该如何保持我的数据类型不变(日期时间列不在要更改的列列表中,如果相关的话)?
或者,也许有一种更好的方法可以使用更新后的numpy数组来更新原始数据框,其中我只更新了更改的列(所有这些列都是int8)?
更新
根据评论的要求,这里是一个最小的示例,说明int8数据类型在降入numpy后变为对象数据类型。 明确地说,这仅适用于上述第4种方法(这是我目前唯一的非慢速方法 - 如果我可以解决此数据类型问题)。
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

您可以尝试在Dask DataFrame中使用.loc和.iloc。http://dask.pydata.org/en/latest/dataframe.html - Charles R
1
好的,问题在于你正在使用 X = df.values。不要这样做,因为单个NumPy数组只能有一个dtype。在Pandas内部,每个系列都有一个单独的NumPy数组/ dtype。你可以使用Pandas来利用这一点。 - jpp
1
感谢@jpp,你提到pandas数据框在底层是numpy数组的集合(即列的集合而不是行)的观点,这是我在更新2中找到解决方案的提示。非常感谢。 - Ben
@Ben,当然可以。但你应该自己将你的解决方案发布为答案,并(如果可能)解释你所做的事情。这样其他用户就可以看到/投票,并且你甚至可以接受自己的解决方案。 - jpp
好的,是的,现在从“更新2”改为了答案。 - Ben
2个回答

1

简而言之

为了Pandas / NumPy的效率,请不要在同一列中使用混合类型(object dtype)。有可用的方法将系列转换为数字,然后高效地操作它们。


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

它快速地迭代了基础数组,但新的数据框具有所有“对象”dtype。

考虑到您只剩下object dtype系列,似乎您对ix_to_change的定义包括非数字系列。在这种情况下,您应该将所有数字列转换为数字dtype。例如,使用pd.to_numeric
df[ix_to_change] = df[ix_to_change].apply(pd.to_numeric, errors='coerce')

Pandas / NumPy在性能方面不会对object dtype系列有所帮助,如果这是您想要的。这些系列在内部表示为指针序列,类似于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

不会将数值列转换为 object 数据类型的序列,这是预期的:

print(df.dtypes)

A             float64
B               int64
C              object
D      datetime64[ns]
E                bool
key             int64
dtype: object

OP说:“我也尝试使用 pandas 的索引,但仍然很慢(虽然比 iterrows 或 itertuples 好)。
  1. pandas loc & iloc
df.loc[df.iloc[:, ix_1]==0, df.columns[ix_to_change]] = 0。”
- jezrael
这取决于实际数据,操作者最好根据实际数据来决定如何处理它。;) - jezrael
在我的实际情况中,ix_to_change 中的所有列都是 int8。我的“3. pandas loc & iloc”方法与此数值方法实际上是相同的,但不幸的是仍然非常缓慢。dtype 问题只出现在 numpy 方法中,在那里它实际上运行得非常快,但一旦我将 numpy 数组包装在新的 pandas dataframe 中,就需要将对象 dtype 转换回原始 dtype。 - Ben
1
@Ben,如果这是真的,那么你最后不应该得到“所有列都转换为object”。我建议您添加一个最小的示例,展示这个问题的确切发生方式。因为现在,我不相信将int系列转换为0之后会变成object - jpp
好的,问题在于你正在使用 X = df.values。不要这样做,因为单个NumPy数组只能有一个dtype。在Pandas内部,每个系列都有一个单独的NumPy数组/ dtype。你可以利用Pandas来实现这一点。 - jpp
显示剩余4条评论

0

这种方法利用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 数据类型。

我在这些 X 行上进行迭代(比在 pandas 中更有效),然后 X.T 给我数组,可以用来替换 pandas 中的整个列。

这避免了在大型数据框上进行缓慢的 iloc / loc 调用,最终所有列的数据类型都保持不变。


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