如何使用一组嵌套的 IF 规则创建一个新的数据框列(应用非常慢)
How to create a new dataframe column with a set of nested IF rules (apply is very slow)
我需要什么
我需要根据一组嵌套的 if 语句在 pandas 数据框中创建新列。例如
if city == 'London' and income > 10000:
return 'group 1'
elif city == 'Manchester' or city == 'Leeds':
return 'group 2'
elif borrower_age > 50:
return 'group 3'
else:
return 'group 4'
这实际上是一种简化 - 在大多数情况下,我需要创建 10 个或更多可能的输出,而不是上面的 4 个,但希望您能理解要点。
问题
我的问题是我还没有找到使代码速度快到可以接受的方法。
我知道,如果选择是二进制的,我可以使用 numpy.where()
之类的东西,但我还没有找到一种方法来矢量化代码或无论如何让它足够快。
我想我可能可以嵌套一些 np.where
语句,这样会更快,但是代码会更难阅读并且更容易出错。
我试过的
我试过以下方法:
+────────────────────────────────────────────────+──────────────+
| Method | Time (secs) |
+────────────────────────────────────────────────+──────────────+
| dataframe.apply | 29 |
| dataframe.apply on a numba-optimised function | 31 |
| sqlite | 16 |
+────────────────────────────────────────────────+──────────────+
“sqlite”表示:将数据帧加载到 sqlite 内存数据库中,在那里创建新字段,然后导出回数据帧
Sqlite 速度更快,但仍然慢得令人无法接受:SQL 服务器 运行 在同一台机器上执行相同的操作不到一秒钟。不过,我宁愿不依赖外部 SQL 服务器,因为即使在无法访问 SQL 服务器的机器上,代码也应该能够 运行。
我还尝试创建一个 numba 函数,它逐行循环,但我知道 numba 不支持字符串(或者至少我无法让它工作)。
玩具示例
import numpy as np
import pandas as pd
import sqlite3
import time
import numba
start = time.time()
running_time = pd.Series()
n = int(1e6)
df1 = pd.DataFrame()
df1['income']=np.random.lognormal(0.4,0.4, n) *20e3
df1['loan balance'] = np.maximum(0, np.minimum(30e3, 5e3 * np.random.randn(n) + 20e3 ) )
df1['city'] = np.random.choice(['London','Leeds','Truro','Manchester','Liverpool'] , n )
df1['city'] = df1['city'].astype('|S80')
df1['borrower age'] = np.maximum(22, np.minimum(70, 30 * np.random.randn(n) + 30 ) )
df1['# children']=np.random.choice( [0,1,2,3], n, p= [0.4,0.3,0.25,0.05] )
df1['rate'] = np.maximum(0.5e-2, np.minimum(10e-2, 1e-2 * np.random.randn(n) + 4e-2 ) )
running_time['data frame creation'] = time.time() - start
conn = sqlite3.connect(":memory:", detect_types = sqlite3.PARSE_DECLTYPES)
cur = conn.cursor()
df1.to_sql("df1", conn, if_exists ='replace')
cur.execute("ALTER TABLE df1 ADD new_field nvarchar(80)")
cur.execute('''UPDATE df1
SET new_field = case when city = 'London' AND income > 10000 then 'group 1'
when city = 'Manchester' or city = 'Leeds' then 'group 2'
when 'borrower age' > 50 then 'group 3'
else 'group 4'
end
''')
df_from_sql = pd.read_sql('select * from df1', conn)
running_time['sql lite'] = time.time() - start
def my_func(city, income, borrower_age):
if city == 'London' and income > 10000:
return 'group 1'
elif city == 'Manchester' or city == 'Leeds':
return 'group 2'
elif borrower_age > 50:
return 'group 3'
else:
return 'group 4'
df1['new_field'] = df1.apply( lambda x: my_func( x['city'], x['income'], x['borrower age'] ) , axis =1)
running_time['data frame apply'] = time.time() - start
@numba.jit(nopython = True)
def my_func_numba_apply(city, income, borrower_age):
if city == 'London' and income > 10000:
return 'group 1'
elif city == 'Manchester' or city == 'Leeds':
return 'group 2'
elif borrower_age > 50:
return 'group 3'
else:
return 'group 4'
df1['new_field numba_apply'] = df1.apply( lambda x: my_func_numba_apply( x['city'], x['income'], x['borrower age'] ) , axis =1)
running_time['data frame numba'] = time.time() - start
x = np.concatenate(([0], running_time))
execution_time = pd.Series(np.diff(x) , running_time.index)
print(execution_time)
我发现的其他问题
我发现了很多其他问题,但是 none 直接解决了我的问题。
大多数其他问题要么很容易向量化(例如,只有两个选择,因此 np.where
效果很好),要么他们推荐了基于 numba 的解决方案,在我的情况下,这实际上恰好更慢。
例如
Speed up applying function to a list of pandas dataframes
这个有日期,所以不太适用
加入,所以不太适用
试试 numpy.select
:
conditions = [df["city"].eq("London") & df["income"].gt(10000),
df["city"].isin(["Manchester", "Leeds"]),
df["borrower_age"].gt(50)]
choices = ["Group 1", "Group 2", "Group 3"]
df["New Field"] = np.select(conditions, choices, "Group 4")
或者将条件作为字典并在 np.select
:
中使用
conditions = {"Group 1": df1["city"].eq("London") & df1["income"].gt(10000),
"Group 2": df1["city"].isin(["Manchester", "Leeds"]),
"Group 3": df1["borrower age"].gt(50)}
df["New Field"] = np.select(conditions.values(), conditions.keys(), "Group 4")
我需要什么
我需要根据一组嵌套的 if 语句在 pandas 数据框中创建新列。例如
if city == 'London' and income > 10000:
return 'group 1'
elif city == 'Manchester' or city == 'Leeds':
return 'group 2'
elif borrower_age > 50:
return 'group 3'
else:
return 'group 4'
这实际上是一种简化 - 在大多数情况下,我需要创建 10 个或更多可能的输出,而不是上面的 4 个,但希望您能理解要点。
问题
我的问题是我还没有找到使代码速度快到可以接受的方法。
我知道,如果选择是二进制的,我可以使用 numpy.where()
之类的东西,但我还没有找到一种方法来矢量化代码或无论如何让它足够快。
我想我可能可以嵌套一些 np.where
语句,这样会更快,但是代码会更难阅读并且更容易出错。
我试过的
我试过以下方法:
+────────────────────────────────────────────────+──────────────+
| Method | Time (secs) |
+────────────────────────────────────────────────+──────────────+
| dataframe.apply | 29 |
| dataframe.apply on a numba-optimised function | 31 |
| sqlite | 16 |
+────────────────────────────────────────────────+──────────────+
“sqlite”表示:将数据帧加载到 sqlite 内存数据库中,在那里创建新字段,然后导出回数据帧
Sqlite 速度更快,但仍然慢得令人无法接受:SQL 服务器 运行 在同一台机器上执行相同的操作不到一秒钟。不过,我宁愿不依赖外部 SQL 服务器,因为即使在无法访问 SQL 服务器的机器上,代码也应该能够 运行。
我还尝试创建一个 numba 函数,它逐行循环,但我知道 numba 不支持字符串(或者至少我无法让它工作)。
玩具示例
import numpy as np
import pandas as pd
import sqlite3
import time
import numba
start = time.time()
running_time = pd.Series()
n = int(1e6)
df1 = pd.DataFrame()
df1['income']=np.random.lognormal(0.4,0.4, n) *20e3
df1['loan balance'] = np.maximum(0, np.minimum(30e3, 5e3 * np.random.randn(n) + 20e3 ) )
df1['city'] = np.random.choice(['London','Leeds','Truro','Manchester','Liverpool'] , n )
df1['city'] = df1['city'].astype('|S80')
df1['borrower age'] = np.maximum(22, np.minimum(70, 30 * np.random.randn(n) + 30 ) )
df1['# children']=np.random.choice( [0,1,2,3], n, p= [0.4,0.3,0.25,0.05] )
df1['rate'] = np.maximum(0.5e-2, np.minimum(10e-2, 1e-2 * np.random.randn(n) + 4e-2 ) )
running_time['data frame creation'] = time.time() - start
conn = sqlite3.connect(":memory:", detect_types = sqlite3.PARSE_DECLTYPES)
cur = conn.cursor()
df1.to_sql("df1", conn, if_exists ='replace')
cur.execute("ALTER TABLE df1 ADD new_field nvarchar(80)")
cur.execute('''UPDATE df1
SET new_field = case when city = 'London' AND income > 10000 then 'group 1'
when city = 'Manchester' or city = 'Leeds' then 'group 2'
when 'borrower age' > 50 then 'group 3'
else 'group 4'
end
''')
df_from_sql = pd.read_sql('select * from df1', conn)
running_time['sql lite'] = time.time() - start
def my_func(city, income, borrower_age):
if city == 'London' and income > 10000:
return 'group 1'
elif city == 'Manchester' or city == 'Leeds':
return 'group 2'
elif borrower_age > 50:
return 'group 3'
else:
return 'group 4'
df1['new_field'] = df1.apply( lambda x: my_func( x['city'], x['income'], x['borrower age'] ) , axis =1)
running_time['data frame apply'] = time.time() - start
@numba.jit(nopython = True)
def my_func_numba_apply(city, income, borrower_age):
if city == 'London' and income > 10000:
return 'group 1'
elif city == 'Manchester' or city == 'Leeds':
return 'group 2'
elif borrower_age > 50:
return 'group 3'
else:
return 'group 4'
df1['new_field numba_apply'] = df1.apply( lambda x: my_func_numba_apply( x['city'], x['income'], x['borrower age'] ) , axis =1)
running_time['data frame numba'] = time.time() - start
x = np.concatenate(([0], running_time))
execution_time = pd.Series(np.diff(x) , running_time.index)
print(execution_time)
我发现的其他问题
我发现了很多其他问题,但是 none 直接解决了我的问题。
大多数其他问题要么很容易向量化(例如,只有两个选择,因此 np.where
效果很好),要么他们推荐了基于 numba 的解决方案,在我的情况下,这实际上恰好更慢。
例如
Speed up applying function to a list of pandas dataframes
这个有日期,所以不太适用
加入,所以不太适用
试试 numpy.select
:
conditions = [df["city"].eq("London") & df["income"].gt(10000),
df["city"].isin(["Manchester", "Leeds"]),
df["borrower_age"].gt(50)]
choices = ["Group 1", "Group 2", "Group 3"]
df["New Field"] = np.select(conditions, choices, "Group 4")
或者将条件作为字典并在 np.select
:
conditions = {"Group 1": df1["city"].eq("London") & df1["income"].gt(10000),
"Group 2": df1["city"].isin(["Manchester", "Leeds"]),
"Group 3": df1["borrower age"].gt(50)}
df["New Field"] = np.select(conditions.values(), conditions.keys(), "Group 4")