Python: 添加一个没有for循环的复杂条件列
Python: Add a complex conditional column without for loop
我正在尝试向我的数据框添加一个“条件”列。我可以用 for 循环来做,但我知道这效率不高。
我的代码可以简化并提高效率吗?
(我试过掩码,但我无法理解语法,因为我是 python 的相对新手)。
import pandas as pd
path = (r"C:\Users\chris\Documents\UKHR\PythonSand\PY_Scripts\CleanModules\Racecards")
hist_file = r"\x3RC_trnhist.xlsx"
racecard_path = path + hist_file
df = pd.read_excel(racecard_path)
df["Mask"] = df["HxFPos"].copy
df["Total"] = df["HxFPos"].copy
cnt = -1
for trn in df["HxRun"]:
cnt = cnt + 1
if df.loc[cnt,"HxFPos"] > 6 or df.loc[cnt,"HxTotalBtn"] > 30:
df.loc[cnt,"Mask"] = 0
elif df.loc[cnt,"HxFPos"] < 2 and df.loc[cnt,"HxRun"] < 4 and df.loc[cnt,"HxTotalBtn"] < 10:
df.loc[cnt,"Mask"] = 1
elif df.loc[cnt,"HxFPos"] < 4 and df.loc[cnt,"HxRun"] < 9 and df.loc[cnt,"HxTotalBtn"] < 10:
df.loc[cnt,"Mask"] = 1
elif df.loc[cnt,"HxFPos"] < 5 and df.loc[cnt,"HxRun"] < 20 and df.loc[cnt,"HxTotalBtn"] < 20:
df.loc[cnt,"Mask"] = 1
else:
df.loc[cnt,"Mask"] = 0
df.loc[cnt,"Total"] = df.loc[cnt,"Mask"] * df.loc[cnt,"HxFPos"]
df.to_excel(r'C:\Users\chris\Documents\UKHR\PythonSand\PY_Scripts\CleanModules\Racecards\cond_col.xlsx', index = False)
样本data/output:
HxRun HxFPos HxTotalBtn Mask Total
7 5 8 0 0
13 3 2.75 1 3
12 5 3.75 0 0
11 5 5.75 0 0
11 7 9.25 0 0
11 9 14.5 0 0
10 10 26.75 0 0
8 4 19.5 1 4
8 8 67 0 0
编辑 - 这是我找到答案的地方:Pandas conditional creation of a series/dataframe column
来自@Hossein-Kalbasi
我刚找到答案 - 如果这不是最有效的,请发表评论。
df.loc[(((df['HxFPos']<3)&(df['HxRun']<5)|(df['HxRun']>4)&(df['HxFPos']<5)&(df['HxRun']<9)|(df['HxRun']>8)&(df['HxFPos']<6)&(df['HxRun']<30))&(df['HxTotalBtn']<30)), 'Mask'] = 1
对复杂的向量化表达式使用 df.assign()
尽可能使用矢量化 pandas 运算符和方法;避免迭代。你可以像这样做一个复杂的矢量化expression/assignment:
.loc[]
df.assign()
- 或者
df.query
(如果你喜欢 SQL 语法)
或者如果你坚持通过 迭代 来做(你不应该),你永远不需要使用显式 for-loop 和 .loc[]
作为你做到了,你可以使用:
df.apply(your_function_or_lambda, axis=1)
- 或
df.iterrows()
作为备用
df.assign()
(或df.query
)当你有很长的列名(就像你所做的那样)时,在复杂的表达式中被重复使用的情况会减少。
df.assign()
的解决方案
为清楚起见重写你的公式
当我们删除所有不需要的 .loc[]
调用时,您的公式归结为:
HxFPos > 6 or HxTotalBtn > 30:
Mask = 0
HxFPos < 2 and HxRun < 4 and HxTotalBtn < 10:
Mask = 1
HxFPos < 4 and HxRun < 9 and HxTotalBtn < 10:
Mask = 1
HxFPos < 5 and HxFPos < 20 and HxTotalBtn < 20:
Mask = 1
else:
Mask = 0
pandas 没有原生 case-statement/method。
为清楚起见,重命名变量 HxFPos->f
、HxFPos->r
、HxTotalBtn->btn
:
(f > 6) or (btn > 30):
Mask = 0
(f < 2) and (r < 4) and (btn < 10):
Mask = 1
(f < 4) and (r < 9) and (btn < 10):
Mask = 1
(f < 5) and (r < 20) and (btn < 20):
Mask = 1
else:
Mask = 0
所以 Mask
的整个布尔表达式实际上是由 (f <= 6) or (btn <= 30)
门控的。 (实际上,如果您想进一步优化,您的子句意味着您只能为 (f < 5) 和 (r < 20) 和 (btn < 20) 使用 Mask=1。)
Mask = ((f<= 6) & (btn <= 30)) & ... you_do_the_rest
向量化你的表达式
所以,这是对第一行的矢量化重写。请注意,比较 > 和 < 是矢量化的,矢量化的布尔运算符是 |和 &(而不是 'and'、'or'),并且您需要将比较括起来以获得正确的运算符优先级:
>>> (df['HxFPos']>6) | (df['HxTotalBtn']>30)
0 False
1 False
2 False
3 False
4 True
5 True
6 True
7 False
8 True
dtype: bool
现在输出是一个逻辑表达式(8 个布尔向量);您可以直接在 df.loc[logical_expression_for_row, 'Mask']
.
中使用它
同样:
((df['HxFPos']<2) & (df['HxRun']<4)) & (df['HxTotalBtn']<10)
我正在尝试向我的数据框添加一个“条件”列。我可以用 for 循环来做,但我知道这效率不高。 我的代码可以简化并提高效率吗? (我试过掩码,但我无法理解语法,因为我是 python 的相对新手)。
import pandas as pd
path = (r"C:\Users\chris\Documents\UKHR\PythonSand\PY_Scripts\CleanModules\Racecards")
hist_file = r"\x3RC_trnhist.xlsx"
racecard_path = path + hist_file
df = pd.read_excel(racecard_path)
df["Mask"] = df["HxFPos"].copy
df["Total"] = df["HxFPos"].copy
cnt = -1
for trn in df["HxRun"]:
cnt = cnt + 1
if df.loc[cnt,"HxFPos"] > 6 or df.loc[cnt,"HxTotalBtn"] > 30:
df.loc[cnt,"Mask"] = 0
elif df.loc[cnt,"HxFPos"] < 2 and df.loc[cnt,"HxRun"] < 4 and df.loc[cnt,"HxTotalBtn"] < 10:
df.loc[cnt,"Mask"] = 1
elif df.loc[cnt,"HxFPos"] < 4 and df.loc[cnt,"HxRun"] < 9 and df.loc[cnt,"HxTotalBtn"] < 10:
df.loc[cnt,"Mask"] = 1
elif df.loc[cnt,"HxFPos"] < 5 and df.loc[cnt,"HxRun"] < 20 and df.loc[cnt,"HxTotalBtn"] < 20:
df.loc[cnt,"Mask"] = 1
else:
df.loc[cnt,"Mask"] = 0
df.loc[cnt,"Total"] = df.loc[cnt,"Mask"] * df.loc[cnt,"HxFPos"]
df.to_excel(r'C:\Users\chris\Documents\UKHR\PythonSand\PY_Scripts\CleanModules\Racecards\cond_col.xlsx', index = False)
样本data/output:
HxRun HxFPos HxTotalBtn Mask Total
7 5 8 0 0
13 3 2.75 1 3
12 5 3.75 0 0
11 5 5.75 0 0
11 7 9.25 0 0
11 9 14.5 0 0
10 10 26.75 0 0
8 4 19.5 1 4
8 8 67 0 0
编辑 - 这是我找到答案的地方:Pandas conditional creation of a series/dataframe column
来自@Hossein-Kalbasi
我刚找到答案 - 如果这不是最有效的,请发表评论。
df.loc[(((df['HxFPos']<3)&(df['HxRun']<5)|(df['HxRun']>4)&(df['HxFPos']<5)&(df['HxRun']<9)|(df['HxRun']>8)&(df['HxFPos']<6)&(df['HxRun']<30))&(df['HxTotalBtn']<30)), 'Mask'] = 1
对复杂的向量化表达式使用 df.assign()
尽可能使用矢量化 pandas 运算符和方法;避免迭代。你可以像这样做一个复杂的矢量化expression/assignment:
.loc[]
df.assign()
- 或者
df.query
(如果你喜欢 SQL 语法)
或者如果你坚持通过 迭代 来做(你不应该),你永远不需要使用显式 for-loop 和 .loc[]
作为你做到了,你可以使用:
df.apply(your_function_or_lambda, axis=1)
- 或
df.iterrows()
作为备用
df.assign()
(或df.query
)当你有很长的列名(就像你所做的那样)时,在复杂的表达式中被重复使用的情况会减少。
df.assign()
的解决方案
为清楚起见重写你的公式
当我们删除所有不需要的 .loc[]
调用时,您的公式归结为:
HxFPos > 6 or HxTotalBtn > 30:
Mask = 0
HxFPos < 2 and HxRun < 4 and HxTotalBtn < 10:
Mask = 1
HxFPos < 4 and HxRun < 9 and HxTotalBtn < 10:
Mask = 1
HxFPos < 5 and HxFPos < 20 and HxTotalBtn < 20:
Mask = 1
else:
Mask = 0
pandas 没有原生 case-statement/method。
为清楚起见,重命名变量 HxFPos->f
、HxFPos->r
、HxTotalBtn->btn
:
(f > 6) or (btn > 30):
Mask = 0
(f < 2) and (r < 4) and (btn < 10):
Mask = 1
(f < 4) and (r < 9) and (btn < 10):
Mask = 1
(f < 5) and (r < 20) and (btn < 20):
Mask = 1
else:
Mask = 0
所以 Mask
的整个布尔表达式实际上是由 (f <= 6) or (btn <= 30)
门控的。 (实际上,如果您想进一步优化,您的子句意味着您只能为 (f < 5) 和 (r < 20) 和 (btn < 20) 使用 Mask=1。)
Mask = ((f<= 6) & (btn <= 30)) & ... you_do_the_rest
向量化你的表达式
所以,这是对第一行的矢量化重写。请注意,比较 > 和 < 是矢量化的,矢量化的布尔运算符是 |和 &(而不是 'and'、'or'),并且您需要将比较括起来以获得正确的运算符优先级:
>>> (df['HxFPos']>6) | (df['HxTotalBtn']>30)
0 False
1 False
2 False
3 False
4 True
5 True
6 True
7 False
8 True
dtype: bool
现在输出是一个逻辑表达式(8 个布尔向量);您可以直接在 df.loc[logical_expression_for_row, 'Mask']
.
同样:
((df['HxFPos']<2) & (df['HxRun']<4)) & (df['HxTotalBtn']<10)