如何使用一组嵌套的 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")