如何高效地将函数应用于大型 Pandas 数据帧的行?

3

我正在尝试从一个当前的数据集创建一个用于模型训练的数据集。该数据集是关于二十一点游戏的,每一行代表一个玩家如何打牌。

这个数据表可能长这个样子:

|Card1|Card2|Card3|Card4|Card5|PlayerTotal|DealerCard1|Win/Lose
|   7 | 10  |  0  |  0  |  0  |  17       |    10     |  0
|   4 | 3   |  10 |  0  |  0  |  17       |     8     |  1

我希望将其转化为仅包含玩家手中的牌、庄家手中的牌和胜利/失败总和的行。然而,如果已经发出了超过两张牌(因此玩家要求加牌),则我希望在每个阶段(玩家每次加牌之前)制作该样本的多个行。
因此,示例将变为:
|PlayerTotal|DealerCard1|Win/Lose
|    17     |     10    |  0
|    7      |     8     |  1
|    17     |     8     |  1

如何有效地实现这个功能?

对于小数据集,我可以使用pd.apply和自定义函数(带有if语句)轻松完成此操作,但是一旦我使用整个数据集(约1百万个数据点),它就会非常慢且占用大量内存。

类似于这样:

def extractRounds(x):
    totals = []
    totals.append(x[0:2], x[5], x[6]])

    if x[2] > 0:
        totals.append([sum(x[0:3]), x[5], x[6]])
    else:
        return pd.Series(totals)

    if x[3] > 0:
        totals.append([sum(x[0:4]), x[5], x[6]])
    else:
        return pd.Series(totals)

    if x[4] > 0:
        totals.append([sum(x[0:5]), x[5], x[6]])

    return pd.Series(totals)


b = (a.apply(extractRounds, axis = 1)).stack()

我猜测不太高效或有效的是extractRounds(x)函数。

所以,我想知道是否尝试对每一行应用函数是错误的方法,或者有更好的方法?

如果这不清楚,请告诉我。谢谢!


在你的输出的第二行中,如果DealerCard1是'6',那么你希望在最后一列中有'0'吗? - LoMaPh
不是因为胜负列是手牌最终是否获胜,而不是当前手牌是否会赢或输。这来自数据集,其中包含大量玩过二十一点的手牌及其结果。这样说清楚了吗? - scrottty
好的,那么您可以先进行一些符号操作以添加额外的行,然后再进行求和。您的数据是CSV格式吗? - LoMaPh
是的,但我已经通过pandas读取它了。 - scrottty
2个回答

1
你可以使用melt将数据转换为长格式,添加累积和,然后仅排除卡3-5的零卡值。并且排除卡1,因为玩家总是最少有两张牌。
以下是示例数据框:
import pandas as pd
import numpy as np

raw = pd.DataFrame({'Card1': [7, 4],
                    'Card2': [10, 3],
                    'Card3': [0, 10],
                    'Card4': [0, 0],
                    'Card5': [0, 0],
                    'DealerCard1': [10, 8],
                    'PlayerTotal': [17, 17],
                    'Win/Lose': [0, 1]})

raw.index.name = 'Game'

使用melt函数创建另一个长格式的数据框:
df = (raw.reset_index()
     .melt(value_vars=['Card1', 'Card2', 'Card3', 'Card4', 'Card5'], 
           id_vars=['Game', 'DealerCard1', 'Win/Lose'],
           value_name='CardValue', 
           var_name='Card')
     .sort_values('Game')
     .reset_index(drop=True))

重新创建PlayerTotal列作为累积总和:
df['PlayerTotal'] = df.groupby('Game')['CardValue'].apply(np.cumsum)

然后您可以排除卡1和零卡,选择所需的列:

df.loc[(df['CardValue']!=0) & (df['Card']!='Card1'), ['PlayerTotal', 'DealerCard1', 'Win/Lose']]

这将给你:
PlayerTotal DealerCard1 Win/Lose
1   17  10  0
6   7   8   1
7   17  8   1

谢谢。这好多了!虽然在整个数据集上运行仍需要一点时间,但比之前快得多 :) 我唯一改变的是将“Card”添加到排序中以便正确排序所有样本。感谢您的帮助! - scrottty

0

您可以使用命令行工具向CSV文件中添加额外的行并进行求和。

假设CSV文件data.csv的前几行如下:

Card1,Card2,Card3,Card4,Card5,PlayerTotal,DealerCard1,Win/Lose
7,10,0,0,0,17,10,0
4,3,10,0,0,17,8,1

运行以下命令将给我们所需的输出。
sed 's/\(.*,\)\(.*,\)\([1-9][0-9]*,\)\(.*,.*,.*,.*,.*\)/\1\2\3\4\n\1\20,\4/' data.csv | cut -d ',' -f 1,2,3,7,8 | awk -F ',' 'NR>1 {print $1+$2+$3 "," $4 "," $5}' > data_2.csv

它创建一个名为data_2.csv的文件,其中包含

17,10,0
17,8,1
7,8,1

--------------------------------

命令说明:

sed 's/\(.*,\)\(.*,\)\([1-9][0-9]*,\)\(.*,.*,.*,.*,.*\)/\1\2\3\4\n\1\20,\4/' data.csv

逐行读取data.csv,如果某一行第三列的值为0,则添加另一行,其中第三列也是0。

| cut -d ',' -f 1,2,3,7,8

读取前一步骤的数据并过滤出第1、2、3、7、8列(这些是我们关心的列)

| awk -F ',' 'NR>1 {print $1+$2+$3 "," $4 "," $5}' > data_2.csv

从上一步读取数据,将前三列相加并将其与最后两列一起写入名为data_2.csv的文件中。


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