我有一种情况,我需要根据同一行中另一列中的值和另一数据帧中的值来转换特定列的值。
示例-
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()
方法无济于事。
答案 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)
据我了解,已编辑任务将执行以下更新:
要执行此操作,请按以下步骤操作:
第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% 为我的原始解决方案。
看看location_values[1]]
。它检索 pre 段,而
实际上应该检索下一个段(城市名称)。
因此无需根据第一个条件创建临时列表 然后缩小范围,并使用第二个条件进行过滤。 您的两个条件(位置和类型的相等性)都可以 在一条指令中执行,因此执行时间有点长 较短。
在“肯定”情况下返回的值应来自 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"]
据我了解,它会产生您想要的东西: