熊猫:如何对单个列使用apply()函数?

时间:2016-01-23 10:04:28

标签: python pandas dataframe python-3.5

我有一个包含两列的pandas数据框。我需要更改第一列的值而不影响第二列,只需更改第一列值即可返回整个数据框。如何在pandas中使用apply来做到这一点?

7 个答案:

答案 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()。在下文中,我将详细解释它们。

I。简单的解决方案

直接的解决方案是@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,则需要其他一些东西。

II。使用整个数据框的解决方案

或者,您可以按照in thisthis 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

这样做,您可以根据列名创建一个通用函数!这是一个坏主意,因为下次您要使用此功能时,您将无法使用。更糟:可能是为了使它与现有功能一起使用而在其他数据框中重命名了列。 (到那儿去做。这是一个湿滑的斜坡!)


III。不使用apply()的替代解决方案

尽管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。列表并不详尽:

  1. 请勿使用apply()。这不是开玩笑。对于大多数数字运算,大熊猫中存在矢量化方法。 if / else块通常可以使用boolean indexing df['col1'] = df.assign(col1=df.col1.apply(complex_function)) 的组合进行重构。我的示例.loc可以通过这种方式重构。
  2. 重构为Cython。如果您有一个复杂的方程式,并且方程式的参数位于您的数据框中,则可能是个好主意。请查看the official pandas user guide了解更多信息。
  3. 使用complex_function参数。从理论上讲,这将提高apply()if you are just applying a NumPy reduction function的性能,因为消除了pd.Series的开销。当然,您的函数必须接受ndarray。您必须将函数重构为NumPy。这样,您将获得巨大的性能提升。
  4. 使用第三方程序包。您应该尝试的第一件事是Numba。我不知道@durjoy提到的swifter;并且可能还有许多其他软件包在这里值得一提。
  5. 尝试/失败/重复。如上所述,根据使用情况,map()和applymap()可以更快。只需为不同的版本计时,然后选择最快的版本即可。这种方法是最繁琐的,而性能提升却最少。

答案 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中选择正确的列。