熊猫-提高申请方法的绩效

时间:2020-05-19 11:34:00

标签: python python-3.x pandas

我有一种情况,我需要根据同一行中另一列中的值和另一数据帧中的值来转换特定列的值。

示例-

print(parent_df)
       school         location      modifed_date
0      school_1       New Delhi     2020-04-06
1      school_2       Kolkata       2020-04-06
2      school_3       Bengaluru     2020-04-06
3      school_4       Mumbai        2020-04-06
4      school_5       Chennai       2020-04-06

print(location_df)
       school          location     
0      school_10       New Delhi
1      school_20       Kolkata     
2      school_30       Bengaluru
3      school_40       Mumbai       
4      school_50       Chennai

根据此用例,我需要基于同一df中存在的parent_df列和location中存在的location属性来转换location_df中存在的学校名称

为实现此转换,我编写了以下方法。

def transform_school_name(row, location_df):
    name_alias = location_df[location_df['location'] == row['location']]
    if len(name_alias) > 0:
        return location_df.school.iloc[0]
    else:
        return row['school']

这就是我调用此方法的方式

parent_df['school'] = parent_df.apply(UtilityMethods.transform_school_name, args=(self.location_df,), axis=1)

问题在于,对于46K记录,我看到整个转换大约在2分钟内发生,这太慢了。如何改善此解决方案的性能?

已编辑

以下是我正在处理的实际场景,其中需要进行一些小的转换,然后才能替换原始列中的值。我不确定是否可以通过下面的答案之一中提到的replace()方法来完成。

print(parent_df)
       school         location                  modifed_date    type
0      school_1       _pre_New Delhi_post       2020-04-06      Govt
1      school_2       _pre_Kolkata_post         2020-04-06      Private
2      school_3       _pre_Bengaluru_post       2020-04-06      Private
3      school_4       _pre_Mumbai_post          2020-04-06      Govt
4      school_5       _pre_Chennai_post         2020-04-06      Private

print(location_df)
           school          location     type
    0      school_10       New Delhi    Govt
    1      school_20       Kolkata      Private
    2      school_30       Bengaluru    Private

自定义方法代码

def transform_school_name(row, location_df):
location_values = row['location'].split('_')
name_alias = location_df[location_df['location'] == location_values[1]]
name_alias = name_alias[name_alias['type'] == location_df['type']]
if len(name_alias) > 0:
    return location_df.school.iloc[0]
else:
    return row['school']


def transform_school_name(row, location_df):
    name_alias = location_df[location_df['location'] == row['location']]
    if len(name_alias) > 0:
        return location_df.school.iloc[0]
    else:
        return row['school']

这是我需要处理的实际情况,因此使用replace()方法无济于事。

4 个答案:

答案 0 :(得分:4)

您可以使用map/replace

parent_df['school'] = parent_df.location.replace(location_df.set_index('location')['school'])

输出:

      school   location modifed_date
0  school_10  New Delhi   2020-04-06
1  school_20    Kolkata   2020-04-06
2  school_30  Bengaluru   2020-04-06
3  school_40     Mumbai   2020-04-06
4  school_50    Chennai   2020-04-06

答案 1 :(得分:2)

IIUC,这更多是一个正则表达式问题,因为模式不完全匹配。首先提取所需的模式,在parent_df中创建位置到location_df的映射,然后映射值。

pat =  '.*?' + '(' + '|'.join(location_df['location']) + ')' + '.*?' 

mapping = parent_df['location'].str.extract(pat)[0].map(location_df.set_index('location')['school'])

parent_df['school'] = mapping.combine_first(parent_df['school'])
parent_df


    school      location            modifed_date    type
0   school_10   _pre_New Delhi_post 2020-04-06      Govt
1   school_20   _pre_Kolkata_post   2020-04-06      Private
2   school_30   _pre_Bengaluru_post 2020-04-06      Private
3   school_4    _pre_Mumbai_post    2020-04-06      Govt
4   school_5    _pre_Chennai_post   2020-04-06      Private

答案 2 :(得分:2)

据我了解,已编辑任务将执行以下更新:

  • 对于 parent_df 中的每一行,
  • location_df 中找到具有匹配位置的行(属于 位置列和类型),
  • 如果找到,请用 school 覆盖 parent_df 中的 school 列 从刚刚找到的行中。

要执行此操作,请按以下步骤操作:

第1步:生成MultiIndex以按城市和 学校类型:

ind = pd.MultiIndex.from_arrays([parent_df.location.str
    .split('_', expand=True)[2], parent_df.type])

对于您的样本数据,结果为:

MultiIndex([('New Delhi',    'Govt'),
            (  'Kolkata', 'Private'),
            ('Bengaluru', 'Private'),
            (   'Mumbai',    'Govt'),
            (  'Chennai', 'Private')],
           names=[2, 'type'])

不用担心奇怪的第一级列名( 2 ),它将很快消失。

步骤2 :生成“新”位置列表:

locList = location_df.set_index(['location', 'type']).school[ind].tolist()

结果是:

['school_10', 'school_20', 'school_30', nan, nan]

对于前3所学校,已经找到了东西,对于后2所却没有发现。

第3步:通过“非空”使用上述列表进行实际更新 面具:

parent_df.school = parent_df.school.mask(pd.notnull(locList), locList)

执行速度

由于使用了向量化操作并通过索引查找,我的代码 运行速度明显快于将 apply 应用于每一行。

示例:我复制了您的 parent_df 10,000次,并使用 %timeit 您的代码的执行时间(实际上有些变化 版本,如下所述)和我的。

为了允许重复执行,我更改了两个版本,以便它们设置 school_2 列,而 school 保持不变。

您的代码正在运行 34.9 ,而我的代码-仅 161 ms- 261 快十倍。

再快一点的版本

如果 parent_df 具有默认索引(从 0 开始的连续数字), 那么整个操作可以通过单个指令执行:

parent_df.school = location_df.set_index(['location', 'type']).school[
    pd.MultiIndex.from_arrays(
        [parent_df.location.str.split('_', expand=True)[2],
         parent_df.type])
    ]\
    .reset_index(drop=True)\
    .combine_first(parent_df.school)

步骤:

  • location_df.set_index(...)-将索引设置为2个“条件”列。
  • .school-仅保留 school 列(带有上面的索引)。
  • [...]-从中检索MultiIndex指示的元素 在内部定义。
  • pd.MultiIndex.from_arrays(-创建MultiIndex。
  • parent_df.location.str.split('_', expand=True)[2]-第一级别 MultiIndex的内容-位置中的“城市”部分。
  • parent_df.type-MultiIndex的第二级-类型
  • reset_index(...)-将MultiIndex更改为默认索引 (现在索引与 parent_df 中的索引相同。
  • combine_first(...)-在生成的结果中覆盖 NaN 值 到目前为止是来自 school 的原始值。
  • parent_df.school =-将结果保存回 school 列中。 出于测试目的,可以更改执行速度,以检查执行速度 与 parent_df ['school_2']

根据我的评估,执行时间比以下时间缩短了9% 为我的原始解决方案。

您的代码更正

  1. 看看location_values[1]]。它检索 pre 段,而 实际上应该检索下一个段(城市名称)。

  2. 因此无需根据第一个条件创建临时列表 然后缩小范围,并使用第二个条件进行过滤。 您的两个条件(位置类型的相等性)都可以 在一条指令中执行,因此执行时间有点长 较短。

  3. 在“肯定”情况下返回的值应来自 name_alias , 不是 location_df

因此,如果由于某种原因您希望保留代码,请更改 各自的片段为:

name_alias = location_df[location_df['location'].eq(location_values[2]) &
    location_df['type'].eq(row.type)]
if len(name_alias) > 0:
    return name_alias.school.iloc[0]
else:
    return row['school']

答案 3 :(得分:0)

如果我正确阅读了该问题,则您使用apply方法实现的是一种联接操作。 Pandas擅长矢量化操作,加上它基于C的join(“合并”)实现几乎可以肯定比基于python / apply的实现更快。因此,我将尝试使用以下解决方案:

parent_df["location_short"] = parent_df.location.str.split("_", expand=True)[2]
parent_df = pd.merge(parent_df, location_df, how = "left", left_on=["location_short", "type"], 
                     right_on=["location", "type"], suffixes = ["", "_by_location"])

parent_df.loc[parent_df.school_by_location.notna(), "school"] = \
      parent_df.loc[parent_df.school_by_location.notna(), "school_by_location"]

据我了解,它会产生您想要的东西:

enter image description here