Python 使用混合数据类型列高效地更新数据帧中的值

Python 使用混合数据类型列高效地更新数据帧中的值,python,pandas,numpy,Python,Pandas,Numpy,我有一个形状为(700000,5000)的大熊猫数据框,其中包含混合数据类型的列(大部分是int8,一些是float64,还有一些是datetime64[ns])。对于数据帧中的每一行,如果另一列也等于零,我希望将某些列的值设置为零 如果我在数据帧上迭代并使用iloc设置值,速度会非常慢。我试过iTerrow和itertuples 1。i如图所示 ix_1 = 3 ix_to_change = [20, 24, 51] # Actually it is almost 5000 columns

我有一个形状为(700000,5000)的大熊猫数据框,其中包含混合数据类型的列(大部分是int8,一些是float64,还有一些是datetime64[ns])。对于数据帧中的每一行,如果另一列也等于零,我希望将某些列的值设置为零

如果我在数据帧上迭代并使用iloc设置值,速度会非常慢。我试过iTerrow和itertuples

1。i如图所示

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。熊猫loc和iloc

df.loc[df.iloc[:, ix_1]==0, df.columns[ix_to_change]] = 0
然后,我尝试深入到底层numpy数组,该数组在性能方面运行良好,但在数据类型方面遇到了问题

它快速遍历底层数组,但新的数据帧具有所有“对象”数据类型。如果我尝试设置每列的数据类型(如本例中所示),则datetime列将失败—可能是因为它们包含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)
有没有更好的方法让我在第一时间进行更新

或者,如果没有,我应该如何保持数据类型不变(datetime列不在需要更改的列列表中)

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

更新 根据注释中的要求,下面是一个简单的示例,演示了int8数据类型在放入numpy后如何变成对象数据类型。要明确的是,这只是上面方法4的一个问题(这是我到目前为止唯一的非慢速方法-如果我可以解决这个数据类型问题的话):

TL;博士 为了提高Pandas/NumPy效率,请勿在列中使用混合类型(
object
dtype)。有一些方法可以将序列转换为数字,然后有效地进行操作


可以使用来确定数字列。假设这些是您希望更新值的唯一对象,那么您可以将它们提供给

它快速迭代底层数组,但新的 数据帧具有所有“对象”数据类型

如果您只剩下
object
dtype系列,那么您对
ix_to_change
的定义似乎包括非数字系列。在这种情况下,应将所有数字列转换为数字数据类型。例如,使用
pd.to\u 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
与预期一样,数字列不转换为
对象
数据类型系列:

print(df.dtypes)

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

这在更新值时使用了NumPy迭代的效率,并解决了数据类型问题

# 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数组,只包含我想要“零”的列,它们都是int8dtype

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


这避免了对big dataframe的缓慢iloc/loc调用,最终所有列上的数据类型都保持不变。

您可以尝试在dask dataframe OK中使用.loc和.iloc,因此问题是您使用的是
X=df.values
。不要这样做,因为单个NumPy数组只能有一个数据类型。在熊猫内部,每个系列都有一个单独的NumPy数组/d类型。您可以使用Pandas来利用这一点。谢谢@jpp,您关于Pandas数据帧是引擎盖下的numpy数组集合(即,列而不是行的集合)的观点是我在更新2中找到解决方案的提示。“非常感谢。”本,当然。但是你应该把你的解决方案作为一个答案贴出来,并且(如果可能的话)解释一下你做了什么。通过这种方式,其他用户可以查看/投票,您甚至可以接受自己的解决方案。好的,是的,现在从更新2移动到答案。OP say
也尝试使用pandas索引,但速度也非常慢(尽管比iterrows或itertuples好)。3.pandas loc&iloc df.loc[df.iloc[:,ix_1]==0,df.columns[ix_to_change]=0
@jezrael,我怀疑问题在于
ix_to_change
定义和生成的
对象
数据类型列。我描述的方法保证您只处理数字系列。应首先将带有dtype对象的数字系列转换为数字。这应该是有效的。这取决于实际数据,请决定如何最好地使用实际数据;)@耶斯雷尔,是的,我添加了更多的描述。很明显,如果OP留下
object
dtype-series(如上所述),它的效率将很低。最好的方法是先转换为数字。@Ben,如果是这样,那么您不应该在末尾看到“所有列都转换为
对象
”。我建议您添加一个最小的示例来说明这种情况的发生。因为,现在,我拒绝相信一个
int
序列在将一些值转换为
0
后会变成
object
# 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