【问题标题】:How can I use the apply() function for a single column?如何将 apply() 函数用于单个列?
【发布时间】:2016-04-29 23:05:38
【问题描述】:

我有一个包含两列的 pandas 数据框。我需要在不影响第二列的情况下更改第一列的值,并在仅更改第一列值的情况下取回整个数据框。如何在 pandas 中使用 apply 来做到这一点?

【问题讨论】:

  • 请发布一些输入样本数据和所需的输出。
  • 在这种情况下,您几乎不应该使用apply。而是直接对列进行操作。
  • 正如 Ted Petrou 所说,尽可能避免使用apply。如果您不确定是否需要使用它,则可能不需要。我建议看看When should I ever want to use pandas apply() in my code?
  • 问题不完全清楚:是对列的每个元素应用一个函数还是对整个列应用一个函数(例如:反转列)?

标签: python pandas dataframe


【解决方案1】:

给定一个示例数据框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

【讨论】:

  • apply 绝不应该在这种情况下使用
  • @TedPetrou 你完全正确,这只是一个关于如何在单列上应用通用函数的示例,正​​如 OP 所要求的那样。
  • 当我尝试这样做时,我收到以下警告:“试图在数据帧的切片副本上设置值。尝试使用 .loc[row_indexer,col_indexer] = value 代替”
  • 出于好奇:为什么不应该在那种情况下使用 apply ?具体是什么情况?
  • @UncleBenBen 通常apply 在行上使用比向量化函数慢得多的内部循环,例如df.a = df.a / 2(见 Mike Muller 的回答)。
【解决方案2】:

对于单列最好使用map(),像这样:

df = pd.DataFrame([{'a': 15, 'b': 15, 'c': 5}, {'a': 20, 'b': 10, 'c': 7}, {'a': 25, 'b': 30, 'c': 9}])

    a   b  c
0  15  15  5
1  20  10  7
2  25  30  9



df['a'] = df['a'].map(lambda a: a / 2.)

      a   b  c
0   7.5  15  5
1  10.0  10  7
2  12.5  30  9

【讨论】:

  • 为什么map()apply() 更好?
  • 这非常有用。我用它从存储在列中的路径中提取文件名df['file_name'] = df['Path'].map(lambda a: os.path.basename(a))
  • map() 用于Series(即单列),一次对一个单元格进行操作,而 apply() 用于DataFrame,一次对整行进行操作。
  • @jpcgt 这是否意味着在这种情况下 map 比 apply 更快?
  • 使用此代码时收到错误“SettingWithCopyWarning”
【解决方案3】:

给定以下数据框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

只有第一列被修改,第二列不变。解决方案很漂亮。它只是一行代码,读起来几乎像英语:"Take 'col1' and apply the function complex_function to it."

但是,如果您需要来自另一列的数据,例如'col2',它不工作。如果你想将'col2'的值传递给complex_function的变量y,你需要别的东西。

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

或者,您可以按照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。要解决这个问题,您需要定义一个 (lambda) 函数,并将列 x['col1'] 作为参数;即我们将列信息包装在另一个函数中。

不幸的是,axis 参数的默认值为零(axis=0),这意味着它将尝试按列而不是按行执行。这在第一个解决方案中不是问题,因为我们给 apply() 一个 pd.Series。但是现在输入是一个数据框,我们必须是明确的(axis=1)。 (我惊讶于我忘记这一点的频率。)

您是否更喜欢带有 lambda 函数的版本是主观的。在我看来,这行代码足够复杂,即使没有引入 lambda 函数也可以阅读。您只需要 (lambda) 函数作为包装器。这只是锅炉代码。读者不应该被它打扰。

现在,您可以轻松修改此解决方案以考虑第二列:

    def apply_complex_function(x): return complex_function(x['col1'], x['col2'])
    df['col1'] = df.apply(apply_complex_function, axis=1)

输出:

     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     2     8

在索引 4 处,值已从 1 变为 2,因为第一个条件 7 > 5 为真,而第二个条件 7 > 8 为假。

请注意,您只需要更改第一行代码(即函数),而不需要更改第二行。


旁注

永远不要将列信息放入你的函数中。

  def bad_idea(x):
      return x['col1'] ** 2

通过这样做,您可以创建一个依赖于列名的通用函数!这是一个坏主意,因为下次您想使用此功能时,您将无法使用。更糟糕的是:也许您重命名不同数据框中的列只是为了使其与您现有的函数一起使用。 (去过那里,做到了。这是一个滑坡!)


三。不使用 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() 是不同的函数。

因此考虑一些替代方案确实有意义:

  • map() 仅适用于 pd.Series,但接受 dict 和 pd.Series 作为输入。使用带有函数的 map() 几乎可以与使用 apply() 互换。它可以比 apply() 更快。详情请见this SO post
  df['col1'] = df['col1'].map(complex_function)
  • applymap() 对于数据帧几乎相同。它不支持 pd.Series 并且它总是会返回一个数据框。但是,它可以更快。 documentation states:“在当前的实现中,applymap 在第一列/行上调用 func 两次,以决定它是否可以采用快速或慢速代码路径。”。但如果性能真的很重要,您应该寻找替代路线。
  df['col1'] = df.applymap(complex_function).loc[:, 'col1']
  • assign() 不是 apply() 的可行替代品。它仅在最基本的用例中具有类似的行为。它不适用于complex_function。您仍然需要 apply() ,如下例所示。 main use case for assign() is method chaining,因为它在不更改原始数据帧的情况下返回数据帧。
  df['col1'] = df.assign(col1=df.col1.apply(complex_function))

附件:如何加快申请速度?

我只在这里提到它,因为它是由其他答案建议的,例如@durjoy。该列表并不详尽:

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

【讨论】:

  • 如果我有比col1 更复杂的切片怎么办?如何避免重复切片表达式?比如说:df[:, ~df.columns.isin(skip_cols)]。在等式两边都写两次似乎是不规范的。
【解决方案4】:

你根本不需要函数。您可以直接处理整列。

示例数据:

>>> 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

【讨论】:

  • 如果我想用“/”分割列中的每个元素并取第一部分怎么办?
  • @KamranHosseini 使用 df['newcolumn'] = df['a'].str.split('/')[0]
  • @Arun df['a'].str.split('/') 产生一个 Series 对象,对吧?那么df['a'].str.split('/')[0] 不会从Series 中生成单个元素吗?我认为您不能将其分配给这样的整个列。
  • @TheUnknownDev 它特定于 Kamran 上面的评论。不适用于OP的情况。当由 str 和值组成的系列由 '/' 分隔时。我们可以使用它来获取第一部分。例如。系列中的“100/101”将被拆分为 100。经过测试和验证!
【解决方案5】:

虽然给定的响应是正确的,但它们修改了初始数据帧,这并不总是可取的(并且,鉴于 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 中选择正确的列。

【讨论】:

  • 我试图保持事物不变,在函数式编程中思考。我非常非常高兴您的回答! :-)
【解决方案6】:

如果你真的很关心你的 apply 函数的执行速度,并且你有一个庞大的数据集要处理,你可以使用 swifter 来加快执行速度,这里是 pandas 数据框上 swifter 的示例:

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 内核都能计算结果,因此它将比正常的应用函数快得多。如果它对您有用,请尝试告诉我。

【讨论】:

  • 伟大的图书馆和伟大的例子!
【解决方案7】:

让我尝试使用日期时间并考虑空值或空格的复杂计算。我在日期时间列上减少 30 年,并使用 apply 方法以及 lambda 并转换日期时间格式。 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)

【讨论】:

    猜你喜欢
    • 2019-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多