使用最符合Python风格的方式按条件连接Pandas单元格

4

我有以下Pandas数据框,带有城市和到达时间(arr)两列:

city      arr  final_target
paris     11   paris_11
paris     12   paris_12
dallas    22   dallas
miami     15   miami
paris     16   paris_16

我的目标是在城市名为巴黎时将paris和arr号码连接填入final_target列,而当名称不为巴黎时,只需填写名称。

最Pythonic的方法是什么?

4个回答

5
什么是最pythonic的方法来做这个?
这要看定义。如果它更可取、最常见和最快的方法,那么np.where解决方案就是最pythonic的方法。
使用numpy.where,如果需要pandaic,这些解决方案也是向量化的,所以应该更可取,像apply(在内部循环)这样:
df['final_target'] = np.where(df['city'].eq('paris'), 
                              df['city'] + '_' + df['arr'].astype(str), 
                              df['city'])

pandas替代品:

df['final_target'] = df['city'].mask(df['city'].eq('paris'), 
                                     df['city'] + '_' + df['arr'].astype(str))

df['final_target'] = df['city'].where(df['city'].ne('paris'), 
                                      df['city'] + '_' + df['arr'].astype(str))
print (df)
     city  arr final_target
0   paris   11     paris_11
1   paris   12     paris_12
2  dallas   22       dallas
3   miami   15        miami
4   paris   16     paris_16

性能:

#50k rows
df = pd.concat([df] * 10000, ignore_index=True)
    

In [157]: %%timeit
     ...: df['final_target'] = np.where(df['city'].eq('paris'), 
     ...:                               df['city'] + '_' + df['arr'].astype(str), 
     ...:                               df['city'])
     ...:                               
48.6 ms ± 444 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [158]: %%timeit
     ...: df['city'] + (df['city'] == 'paris')*('_' + df['arr'].astype(str))
     ...: 
     ...: 
49.2 ms ± 1.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [159]: %%timeit
     ...: df['final_target'] = df['city']
     ...: df.loc[df['city'] == 'paris', 'final_target'] +=  '_' + df['arr'].astype(str)
     ...: 
63.8 ms ± 764 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [160]: %%timeit
     ...: df['final_target'] = df.apply(lambda x: x.city + '_' + str(x.arr) if x.city == 'paris' else x.city, axis = 1)
     ...: 
     ...: 
1.33 s ± 119 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

你能看到我解决方案中的速度比较吗? - eroot163pi
你能解释一下为什么我的解决方案中第三个速度由于内存问题而失败,而其他速度却没有问题吗? - eroot163pi
@eroot163pi - 不清楚,可能是与掩码有关的问题。 - jezrael

3

使用 loc 来试试这个简洁的两行代码:

df['final_target'] = df['city']
df.loc[df['city'] == 'paris', 'final_target'] +=  '_' + df.loc[df['city'] == 'paris', 'arr'].astype(str)

这个解决方案首先将 df['city'] 指定为 final_target 列,然后如果 city 列是 paris,就添加下划线分隔的 arr 列。
在我看来,这可能是最 Pythonic 和整洁的方式。
print(df)

     city  arr final_target
0   paris   11     paris_11
1   paris   12     paris_12
2  dallas   22       dallas
3   miami   15        miami
4   paris   16     paris_16

我认为这个需要更多的内存,在大输入上会失败。 - eroot163pi
@eroot163pi,你的情况怎么样?我的申请速度超级慢... - U13-Forward
@eroot163pi 对不起,我以为你是ql.user2511... - U13-Forward
1
我不确定为什么会发生那种事情 :P - U13-Forward
df.loc和df.arr它们的形状不同,但是你的输出仍然是正确的,也许是因为它们匹配了索引。关于内存错误和采样仍然不清楚。 - eroot163pi

3
一行代码就可以搞定:
df['final_target'] = df.apply(lambda x: x.city + '_' + str(x.arr) if x.city == 'paris' else x.city, axis = 1)

1
在我看来,这个解决方案不够Pythonic,因为存在向量化更快的替代方案-请查看https://dev59.com/h1QJ5IYBdhLWcg3wKykQ#54432584。 - jezrael
为什么不符合Pythonic风格? - ql.user2511
我认为这是因为循环在底层运行。我添加了一个链接,以便更好地解释为什么应该避免使用这种“方法”。 - jezrael
它说apply会消耗大量的内存,因为该函数是逐行“应用”的。它可能比其他函数慢,但我并不认为这使其“不符合Python风格”... - ql.user2511
嗯,这取决于什么是更符合Python风格的。如果更喜欢、更常见和更快的方式,则不符合Python风格。 - jezrael
1
是的,我同意。这完全取决于“pythonic”真正的含义。也许如果用户正在寻找一种“快速”的方法,那么另一种替代方法可能更合适。 - ql.user2511

0

非常简单易懂,只有一行代码,看起来很像 Python 语言。

df['city'] + (df['city'] == 'paris')*('_' + df['arr'].astype(str))

s = """city,arr,final_target
paris,11,paris_11
paris,12,paris_12
dallas,22,dallas
miami,15,miami
paris,16,paris_16"""
import pandas as pd
import io
df = pd.read_csv(io.StringIO(s)).sample(1000000, replace=True)
df

速度

%%timeit
df['city'] + (df['city'] == 'paris')*('_' + df['arr'].astype(str))
# 877 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
df['final_target'] = np.where(df['city'].eq('paris'), 
                              df['city'] + '_' + df['arr'].astype(str), 
                              df['city'])
# 874 ms ± 19.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

我不确定为什么这个例子失败了(更新:由于采样而失败),但内存错误仍然是一个谜题在使用pandas的.loc进行采样而不是直接计算时为什么会出现内存错误

%%timeit
df['final_target'] = df['city']
df.loc[df['city'] == 'paris', 'final_target'] +=  '_' + df['arr'].astype(str)

MemoryError: Unable to allocate 892. GiB for an array with shape (119671145392,) and data type int64

测试速度的行数是多少? - jezrael
希望不是5行。 - jezrael
哈哈...抱歉,让我检查一下大的。 - eroot163pi
在大的上面没有太大的区别吗? - eroot163pi
是的,它是一样的。 - jezrael
第三个出现了内存错误。 - eroot163pi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接