首页 > 解决方案 > 有没有更快的方法将熊猫数据框分成两个互补的部分?

问题描述

大家晚上好

我有一种情况,我需要根据一个特征的值将数据框分成两个互补的部分。

我的意思是,对于数据帧 1 中的每一行,我需要数据帧 2 中的互补行,该行具有该特定特征的相反值。

在我的源数据框中,我所指的功能存储在“773”列下,它可以采用0.01.0的值。

我想出了以下代码,可以充分做到这一点,但速度非常慢。拆分 10,000 行大约需要一分钟,即使在我功能强大的 EC2 实例上也是如此。

data = chunk.iloc[:,1:776]
listy1 = []
listy2 = []

for i in range(0,len(data)):
    random_row = data.sample(n=1).iloc[0]
    listy1.append(random_row.tolist())

    if random_row["773"] == 0.0:
        x = data[data["773"] == 1.0].sample(n=1).iloc[0]
        listy2.append(x.tolist())

    else: 
        x = data[data["773"] == 0.0].sample(n=1).iloc[0]
        listy2.append(x.tolist())

df1 = pd.DataFrame(listy1)
df2 = pd.DataFrame(listy2)

注意:我不关心重复行,因为这些数据被用于训练一个模型,该模型比较两个对象以判断哪个对象“更好”。

您是否对为什么这么慢有一些见解,或者有什么建议可以加快速度?

标签: pythonpandasdataframe

解决方案


numpy//高效编码的一个关键概念是尽可能使用库提供的矢量化函数scipypandas尝试一次处理多行,而不是显式地遍历行。即避免for循环和.iterrows().

提供的实现在索引方面有点微妙,但向量化的思路应该直截了当,如下所示:

  1. 一次绘制主数据集。
  2. 互补数据集:一次绘制0行,一次绘制互补1行,然后一次将它们放入相应的行中。

代码

import pandas as pd
import numpy as np
from datetime import datetime

np.random.seed(52)  # reproducibility
n = 10000
df = pd.DataFrame(
    data={
        "773": [0,1]*int(n/2),
        "dummy1": list(range(n)),
        "dummy2": list(range(0, 10*n, 10))
    }
)

t0 = datetime.now()
print("Program begins...")
# 1. draw the main dataset
draw_idx = np.random.choice(n, n)  # repeatable draw
df_main = df.iloc[draw_idx, :].reset_index(drop=True)

# 2. draw the complementary dataset

# (1) count number of 1's and 0's
n_1 = np.count_nonzero(df["773"][draw_idx].values)
n_0 = n - n_1

# (2) split data for drawing
df_0 = df[df["773"] == 0].reset_index(drop=True)
df_1 = df[df["773"] == 1].reset_index(drop=True)

# (3) draw n_1 indexes in df_0 and n_0 indexes in df_1
idx_0 = np.random.choice(len(df_0), n_1)
idx_1 = np.random.choice(len(df_1), n_0)

# (4) broadcast the drawn rows into the complementary dataset
df_comp = df_main.copy()
mask_0 = (df_main["773"] == 0).values
df_comp.iloc[mask_0 ,:] = df_1.iloc[idx_1, :].values  # df_1 into mask_0
df_comp.iloc[~mask_0 ,:] = df_0.iloc[idx_0, :].values  # df_0 into ~mask_0

print(f"Program ends in {(datetime.now() - t0).total_seconds():.3f}s...")

查看

print(df_main.head(5))
   773  dummy1  dummy2
0    0      28     280
1    1      11     110
2    1      13     130
3    1      23     230
4    0      86     860

print(df_comp.head(5))
   773  dummy1  dummy2
0    1      19     190
1    0      74     740
2    0      28     280  <- this row is complementary to df_main
3    0      60     600
4    1      37     370

效率增益:14.23s -> 0.011s (ca. 128x)


推荐阅读