我有一个包含两列的pandas数据框。我需要更改第一列的值而不影响第二列,只需更改第一列值即可返回整个数据框。如何在pandas中使用apply来做到这一点?
答案 0 :(得分:234)
将样本数据框df
指定为:
a,b
1,2
2,3
3,4
4,5
你想要的是:
df['a'] = df['a'].apply(lambda x: x + 1)
返回:
a b
0 2 2
1 3 3
2 4 4
3 5 5
答案 1 :(得分:33)
根本不需要功能。您可以直接处理整个列。
示例数据:
>>> df = pd.DataFrame({'a': [100, 1000], 'b': [200, 2000], 'c': [300, 3000]})
>>> df
a b c
0 100 200 300
1 1000 2000 3000
列a
中的所有值的一半:
>>> df.a = df.a / 2
>>> df
a b c
0 50 200 300
1 500 2000 3000
答案 2 :(得分:30)
对于单个列,最好使用in
,如下所示:
map()
答案 3 :(得分:15)
给出以下数据框df
和函数complex_function
,
import pandas as pd
def complex_function(x, y=0):
if x > 5 and x > y:
return 1
else:
return 2
df = pd.DataFrame(data={'col1': [1, 4, 6, 2, 7], 'col2': [6, 7, 1, 2, 8]})
col1 col2
0 1 6
1 4 7
2 6 1
3 2 2
4 7 8
有多种解决方案可仅在一列上使用apply()。在下文中,我将详细解释它们。
直接的解决方案是@Fabio Lamanna的解决方案:
df['col1'] = df['col1'].apply(complex_function)
输出:
col1 col2
0 2 6
1 2 7
2 1 1
3 2 2
4 1 8
仅第一列被修改,第二列未更改。解决方案是美丽的。它只是一行代码,读起来几乎像英语:“接受'col1'并将函数complex_function应用于它。”
但是,如果您需要其他列中的数据,例如'col2',它不起作用。如果要将'col2'的值传递给y
的变量complex_function
,则需要其他一些东西。
或者,您可以按照in this或this SO post所述使用整个数据框:
df['col1'] = df.apply(lambda x: complex_function(x['col1']), axis=1)
或者,如果您喜欢(像我一样)没有lambda函数的解决方案:
def apply_complex_function(x): return complex_function(x['col1'])
df['col1'] = df.apply(apply_complex_function, axis=1)
此解决方案中有很多事情需要说明。 apply()函数适用于pd.Series 和 pd.DataFrame。但是您不能使用df['col1'] = df.apply(complex_function).loc[:, 'col1']
,因为它会抛出ValueError
。
因此,您需要提供使用哪一列的信息。要使事情复杂,请使用apply()函数does only accept callables。为了解决这个问题,您需要定义一个{λ1}}作为参数的(lambda)函数;也就是说,我们将列信息包装在另一个函数中。
不幸的是,axis参数的默认值为零(x['col1']
),这意味着它将尝试按列而不是按行执行。在第一个解决方案中这不是问题,因为我们为apply()提供了一个pd.Series。但是现在输入是一个数据帧,我们必须是显式的(axis=0
)。 (让我惊奇的是我经常忘记这一点。)
您是否喜欢带有lambda函数的版本是主观的。在我看来,即使没有抛出lambda函数,代码行也足够复杂以至于无法读取。您只需要(lambda)函数作为包装器即可。这只是锅炉代码。读者不应对此感到困扰。
现在,您可以轻松修改此解决方案,以将第二列考虑在内:
axis=1
输出:
def apply_complex_function(x): return complex_function(x['col1'], x['col2'])
df['col1'] = df.apply(apply_complex_function, axis=1)
在索引4处,该值已从1更改为2,因为第一个条件 col1 col2
0 2 6
1 2 7
2 1 1
3 2 2
4 2 8
为true,但是第二个条件7 > 5
为false。
请注意,您只需要更改第一行代码(即函数),而无需更改第二行。
从不将列信息放入函数中。
7 > 8
这样做,您可以根据列名创建一个通用函数!这是一个坏主意,因为下次您要使用此功能时,您将无法使用。更糟:可能是为了使它与现有功能一起使用而在其他数据框中重命名了列。 (到那儿去做。这是一个湿滑的斜坡!)
尽管OP特别要求使用apply()解决方案,但还是提出了其他解决方案。例如,@ George Petrov的答案建议使用map(),@ Thibaut Dubernet的答案建议使用assign()。
我完全同意apply()为seldom the best solution,因为apply()为not vectorized。这是一个基于元素的操作,具有昂贵的函数调用和pd.Series的开销。
使用apply()的一个原因是您要使用现有函数,而性能不是问题。或者您的函数太复杂了,以至于没有向量化版本。
使用apply()的另一个原因是在combination with groupby()中。 请注意,DataFrame.apply()和GroupBy.apply()是不同的功能。
因此,考虑一些替代方案确实很有意义:
def bad_idea(x):
return x['col1'] ** 2
仅适用于pd.Series,但接受dict和pd.Series作为输入。将map()与函数一起使用几乎可以与apply()互换使用。它可以比apply()更快。有关更多详细信息,请参见this SO post。map()
df['col1'] = df['col1'].map(complex_function)
对于数据帧几乎相同。它不支持pd.Series,它将始终返回一个数据帧。但是,它可以更快。 documentation states:“ 在当前实现中,applymap在第一列/行上调用func两次,以决定它可以采用快速还是慢速代码路径。”。但是,如果性能确实很重要,那么您应该寻找替代方法。applymap()
df['col1'] = df.applymap(complex_function).loc[:, 'col1']
不能替代apply()。仅在最基本的用例中,它具有类似的行为。它不适用于assign()
。如下面的示例所示,您仍然需要apply()。 main use case for assign() is method chaining,因为它在不更改原始数据帧的情况下返回了数据帧。complex_function
我在这里只提到它,因为它是由其他答案建议的,例如@durjoy。列表并不详尽:
df['col1'] = df.assign(col1=df.col1.apply(complex_function))
的组合进行重构。我的示例.loc
可以通过这种方式重构。complex_function
参数。从理论上讲,这将提高apply()if you are just applying a NumPy reduction function的性能,因为消除了pd.Series的开销。当然,您的函数必须接受ndarray。您必须将函数重构为NumPy。这样,您将获得巨大的性能提升。答案 4 :(得分:4)
如果您真的很关心apply函数的执行速度,并且有一个庞大的数据集需要处理,则可以使用swifter来加快执行速度,以下是在swifts上熊猫数据框的示例:
import pandas as pd
import swifter
def fnc(m):
return m*3+4
df = pd.DataFrame({"m": [1,2,3,4,5,6], "c": [1,1,1,1,1,1], "x":[5,3,6,2,6,1]})
# apply a self created function to a single column in pandas
df["y"] = df.m.swifter.apply(fnc)
这将使您所有的CPU内核能够计算结果,因此比正常的应用功能要快得多。尝试让我知道它是否对您有用。
答案 5 :(得分:4)
让我尝试使用datetime并考虑空值或空格的复杂计算。我在datetime列上减少了30年,并且使用apply
方法以及lambda
和转换datetime格式。第if x != '' else x
行将相应处理所有空白或空值。
df['Date'] = df['Date'].fillna('')
df['Date'] = df['Date'].apply(lambda x : ((datetime.datetime.strptime(str(x), '%m/%d/%Y') - datetime.timedelta(days=30*365)).strftime('%Y%m%d')) if x != '' else x)
答案 6 :(得分:1)
尽管给定的响应是正确的,但它们并不总是希望修改初始数据帧(并且,如果OP要求示例“使用apply
”,则可能是他们想要的版本返回一个新数据框,如apply
一样。
使用assign
可以实现:根据文档说明(强调是我的观点),对现有列的assign
有效:
将新列分配给DataFrame。
返回一个新对象,其中包含所有原始列以及新列。 重新分配的现有列将被覆盖。
简而言之:
In [1]: import pandas as pd
In [2]: df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])
In [3]: df.assign(a=lambda df: df.a / 2)
Out[3]:
a b c
0 7.5 15 5
1 10.0 10 7
2 12.5 30 9
In [4]: df
Out[4]:
a b c
0 15 15 5
1 20 10 7
2 25 30 9
请注意,该函数将传递给整个数据帧,而不仅是要修改的列,因此您需要确保在lambda中选择正确的列。