【问题标题】:Optimizing Applying User Defined Function that Reference Multiple DF's优化应用引用多个 DF 的用户定义函数
【发布时间】:2020-11-07 03:32:25
【问题描述】:

import pandas as pd
afltv = pd.DataFrame({'FICO': [0, 0, 700, 700],
                      'LTV': [0, 70, 0, 70],
                      'Adj': [10, 11, 12, 13]})
gfltv = pd.DataFrame({'FICO': [0, 0, 700, 700],
                      'LTV': [0, 70, 0, 70],
                      'Adj': [1, 2, 3, 4]})
df = pd.DataFrame({'Investor': ['a','a','e','f'],
                      'FICO': [600, 699, 700, 701],
                      'LTV': [69, 70, 71, 90]})
df2 = pd.DataFrame({'Investor': ['a','a','e','f'],
                      'FICO': [600, 699, 700, 701],
                      'LTV': [69, 70, 71, 90]})
dflist = [df,df2]

想要

我想根据以下逻辑将一列Adj 值附加到dflist 中的每个数据帧:

  • Adj 值是通过将[FICO, LTV] 列中的 ma​​x le(最大小于或等于)值与查找数据帧(afltvgfltv)。
  • 有条件地使用查找数据帧:如果输入行的Investor 列是'a',则使用afltv。否则使用gfltv

期望的输出

print(df)  # the same for df2

  Investor  FICO  LTV  value
0        a   600   69   10.0
1        a   699   70   11.0
2        e   700   71    4.0
3        f   701   90    4.0

上一次尝试

我有一个实现需要将近 3 个小时才能完成 400K 行。

  1. 根据投资者列,设置table 变量。例如,如果df.Investor == 'a' 那么table = afltv

  2. df.FICOdf.ltv(df2 相同)分别转换为table.FICOtable.LTV 中最接近的值,而无需重复。例如,df.FICO = 699,table.FICO 中的值为 0 和 700,则转换结果应为 0。

  3. 将步骤 2 的结果存储在变量 cscorelscore 中(对每个变量执行步骤 2 中描述的相同过程)

  4. 将 .loc 与步骤 3 中的变量一起使用以返回标量值 步骤 1 中设置的 table 变量

def find_value(row):

###Based on df.Investor (passed as row.Investor), set 'table' to be one 
###   of the df's established above - those df's contain the desired 
###   results values in the 'adj' column

    if row['Investor'] == 'a':
        table = afltv.copy()
    else:
        table = gfltv.copy() 
       
###Convert FICO (described in step 2) and store in cscore 

    table.drop(table[table.FICO>row['FICO']].index, inplace=True)
    table.reset_index(drop=True, inplace=True)
    cscore = table.loc[(table['FICO']-row['FICO']).abs().argsort(), 'FICO'].values[0]

###Convert LTV as described in step 2

    table.drop(table[table.LTV>row['LTV']].index, inplace=True)
    table.reset_index(drop=True, inplace=True)
    lscore = table.loc[(table['LTV']-row['LTV']).abs().argsort(), 'LTV'].values[0]

###Use .loc and the variables we set in order to return a scalar value from
###   table.adj

    adj = table.loc[(table['LTV']==lscore) & (table['FICO']==cscore), 'Adj'].values

    return adj 

以这种方式产生所需的输出

for i in dflist:
    i['value'] = i.apply(find_value, axis=1).astype(float)

我还有多个以类似方式运行的函数,取一行并返回一个标量值。我尝试将列系列传递给函数以加快速度,但我有返回布尔值的比较,所以这似乎不起作用。

我正在寻求任何改进建议。我有几个关于优化的问题:

  1. 循环遍历数据框以应用函数是一种不好的做法吗?我应该将它们合并为一个 df 并应用一次吗?

  2. 在我应用的函数中创建新的 df 并在该函数中运行 .loc 效率低吗?

  3. 基于问题 2,在函数中转换我用于 .loc(FICO 和 LTV)的值是否更有意义,但跳过 .loc 部分?我可能会在函数之外进行合并,而不是 .loc。

【问题讨论】:

  • 您能否提供reproducible way 中的示例数据和预期输出?否则人们将无法测试。
  • 好点 - 我将汇总示例数据并更新帖子。谢谢
  • 添加示例数据和预期输出,感谢反馈!
  • 能否解释一下以明文形式获取value 列的逻辑?例如,定义明确的规则列表或查找值的一系列步骤。没有含糊的词。几乎所有的术语,例如“匹配”、“最接近”、“我想查看的表格”等都没有明确定义。我建议您不要期望潜在的回答者从您冗长的代码中推断出这些逻辑。尝试专注于定义输出的要求,而不是描述(不需要的)代码本身。
  • 将问题更新为更清晰,再次感谢您的反馈。您评论的结尾暗示我不希望代码按照当前的方式运行,但它现在确实可以正常工作,我得到了正确的结果 - 我希望得到关于我可以优化的地方的建议我的代码,所以它运行得更快。这就是为什么我解释我正在做的事情背后的逻辑,以防他们是完成相同步骤的更有效方式

标签: python pandas optimization apply


【解决方案1】:

  1. key中的key为np.searchsorted(),完美解决了“最大文件(小于等于)”的逻辑。该函数还根据文档进行了矢量化。
  2. *fltv 数据帧实际上是 (FICO x LTV) 的“扁平化”二维表。如果将它们堆叠起来,则可以使用在1. 中找到的坐标直接找到Adj 值。
  3. 使用integer array indexing 获取2. 中的值。

代码在 0.53 秒内完成 800k 行(dfdf2 分别为 400k 行)。

代码

import pandas as pd
import numpy as np

# Use *fltv, df, df2, dflist as given

#  cond =  0    , 1
ls_fltv = [afltv, gfltv]

# construct lookup tables as 2D (RICO x LTV) arrays
ls_tb = [fltv.sort_values(["FICO", "LTV"])
             .set_index(["FICO", "LTV"])["Adj"]
             .unstack(level=-1) for fltv in ls_fltv]

def search_subdf(df, cond):
    """Search on a subset of df based on condition"""

    # get df subset
    if cond == 0:
        df_sub = df[df['Investor'] == 'a']
    elif cond == 1:
        df_sub = df[df['Investor'] != 'a']

    # get lookup table
    tb = ls_tb[cond]

    # Search FICO in the index and LTV in the columns
    FICO_where = np.searchsorted(tb.index.values, df_sub["FICO"].values, side="right") - 1
    LTV_where = np.searchsorted(tb.columns.values, df_sub["LTV"].values, side="right") - 1

    # append the Adj column
    return df_sub.assign(Adj=tb.values[FICO_where, LTV_where])
    
def search(df, n_cond):
    """Search through all conditions"""
    # compute the results from cond 0 and 1, and concat vertically
    return pd.concat([search_subdf(df, i) for i in range(n_cond)])

# execute
for i, item in enumerate(dflist):
    dflist[i] = search(item, len(ls_fltv))

输出

结果

for item in dflist:
    print(item)
    print()

  Investor  FICO  LTV  Adj
0        a   600   69   10
1        a   699   70   11
2        e   700   71    4
3        f   701   90    4

  Investor  FICO  LTV  Adj
0        a   600   69   10
1        a   699   70   11
2        e   700   71    4
3        f   701   90    4

查找表

for tb in ls_tb:
    print(tb)
    print()

LTV   0   70
FICO        
0     10  11
700   12  13

LTV   0   70
FICO        
0      1   2
700    3   4

注意事项

有我的优化建议:

  • 当效率成为问题时,对numpy 数组使用矢量化操作。
    • 不要重新发明轮子。如果逻辑似乎不是非常罕见,那就去找轮子吧。
  • 避免对 pandas 数据帧进行显式迭代。
    • 如果for 循环或.apply 是不可避免的(它们在效率方面相似),请尝试使用数组而不是数据帧。
    • 请勿使用.iterrows().itertuples().append()

【讨论】:

    猜你喜欢
    • 2019-06-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-14
    • 2018-07-06
    • 1970-01-01
    • 1970-01-01
    • 2017-04-20
    相关资源
    最近更新 更多