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

Ama*_*ani 207 python dataframe pandas python-3.5

我有一个有两列的pandas数据框.我需要更改第一列的值而不影响第二列,只需更改第一列值即可返回整个数据框.我怎么能用熊猫申请呢?

Fab*_*nna 286

给定一个示例数据帧df:

a,b
1,2
2,3
3,4
4,5
Run Code Online (Sandbox Code Playgroud)

你想要的是:

df['a'] = df['a'].apply(lambda x: x + 1)
Run Code Online (Sandbox Code Playgroud)

返回:

   a  b
0  2  2
1  3  3
2  4  4
3  5  5
Run Code Online (Sandbox Code Playgroud)

  • 好奇心:为什么不应该在那种情况下使用?究竟是什么情况? (18认同)
  • @UncleBenBen一般来说`apply`在行上使用内部循环,它比矢量化函数慢得多,比如`df.a = df.a/2`(参见Mike Muller的回答). (12认同)
  • 当我尝试这样做时,我收到以下警告:"正在尝试在DataFrame的切片副本上设置一个值.尝试使用.loc [row_indexer,col_indexer] = value" (9认同)
  • 绝不应该在这种情况下使用`apply` (5认同)
  • @TedPetrou你是完全正确的,它只是一个关于如何在一个列上应用通用函数的例子,正如OP所说的那样. (5认同)
  • 请注意,显然有一些内部优化与Cython,所以`内部循环行'并没有你想象的那么糟糕.[https://engineering.upside.com/a-beginners-guide-to-optimizing-pandas-code-for-speed-c09ef2c6a4d6] (2认同)
  • 如何使用def构建自己的内置函数。 (2认同)
  • @FabioLamanna 感谢您的回答。我需要使用这样的结构,但我收到此警告消息“正在尝试在 DataFrame 切片的副本上设置值”。尝试使用 .loc[row_indexer,col_indexer] = value 代替` ..我使用的是 `myDF['colName'] = myDF['colName'].apply(lambda x: x+np.random.uniform(... )`有什么好的、有效的方法来做到这一点吗? (2认同)

Geo*_*rov 50

对于单个列更好用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
Run Code Online (Sandbox Code Playgroud)

  • 为什么`map()`比`apply()`更适合单个列? (70认同)
  • map()用于Series(即单列)并且一次在一个单元格上操作,而apply()用于DataFrame,并且一次在整行上操作. (35认同)
  • 使用此代码时,我收到错误“SettingWithCopyWarning” (5认同)
  • 这非常有用。我用它从存储在列中的路径中提取文件名```df['file_name'] = df['Path'].map(lambda a: os.path.basename(a))``` (2认同)
  • @jpcgt这是否意味着映射比在这种情况下适用要快? (2认同)
  • 使用 map() 函数,您不需要 lambda 函数。这应该有效: df['file_name'] = df['Path'].map(os.path.basename) (2认同)
  • 如果“map”“一次对一个单元格进行操作”,那么这是否意味着它没有矢量化,因此比对整列执行矢量化操作慢? (2认同)

abo*_*vel 46

给定以下数据框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]})
Run Code Online (Sandbox Code Playgroud)
     col1  col2
  0     1     6
  1     4     7
  2     6     1
  3     2     2
  4     7     8
Run Code Online (Sandbox Code Playgroud)

有几种解决方案可以仅对一列使用 apply()。下面我将详细解释它们。

一、简单的解决办法

直接的解决方案是来自@Fabio Lamanna 的解决方案:

     col1  col2
  0     1     6
  1     4     7
  2     6     1
  3     2     2
  4     7     8
Run Code Online (Sandbox Code Playgroud)

输出:

     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     1     8
Run Code Online (Sandbox Code Playgroud)

只修改了第一列,第二列不变。解决方案很漂亮。它只是一行代码,读起来几乎像英语:“使用'col1'并将函数 complex_function 应用到它。

但是,如果您需要来自另一列的数据,例如“col2”,则它不起作用。如果要将 'col2' 的值传递给 的变量y,则complex_function需要其他内容。

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

或者,您可以使用此 SO 帖子中所述的整个数据框:

  df['col1'] = df['col1'].apply(complex_function)
Run Code Online (Sandbox Code Playgroud)

或者,如果您更喜欢(像我一样)没有 lambda 函数的解决方案:

     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     1     8
Run Code Online (Sandbox Code Playgroud)

这个解决方案中有很多需要解释的地方。apply() 函数适用于 pd.Seriespd.DataFrame。但是你不能使用df['col1'] = df.apply(complex_function).loc[:, 'col1'],因为它会抛出一个ValueError.

因此,您需要提供要使用的列的信息。更复杂的是, apply() 函数只接受 callables。要解决这个问题,您需要定义一个(lambda)函数,以x['col1']列为参数;即我们将列信息包装在另一个函数中。

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

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

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

  df['col1'] = df.apply(lambda x: complex_function(x['col1']), axis=1)
Run Code Online (Sandbox Code Playgroud)

输出:

     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     2     8
Run Code Online (Sandbox Code Playgroud)

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

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


边注

切勿将列信息放入您的函数中。

  def apply_complex_function(x): return complex_function(x['col1'])
  df['col1'] = df.apply(apply_complex_function, axis=1) 
Run Code Online (Sandbox Code Playgroud)

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


三、不使用 apply() 的替代解决方案

尽管 OP 专门要求使用 apply() 解决方案,但还是建议了其他解决方案。例如,@George Petrov 的答案建议使用 map(),@Thibaut Dubernet 的答案建议使用assign()。

我完全同意 apply()很少是最好的解决方案,因为 apply()不是矢量化的。这是一个元素操作,具有昂贵的函数调用和来自 pd.Series 的开销。

使用 apply() 的一个原因是您想使用现有函数并且性能不是问题。或者您的函数非常复杂,以至于不存在矢量化版本。

使用 apply() 的另一个原因是与 groupby() 结合使用请注意 DataFrame.apply() 和GroupBy.apply()是不同的函数。

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

  • map()仅适用于 pd.Series,但接受 dict 和 pd.Series 作为输入。将 map() 与函数一起使用几乎可以与使用 apply() 互换。它可以比 apply() 更快。有关更多详细信息,请参阅此 SO 帖子
    def apply_complex_function(x): return complex_function(x['col1'], x['col2'])
    df['col1'] = df.apply(apply_complex_function, axis=1)
Run Code Online (Sandbox Code Playgroud)
  • applymap()数据帧几乎相同。它不支持 pd.Series 并且它总是会返回一个数据帧。但是,它可以更快。该文档状态:“在目前的实现applymap调用第一列/行FUNC两次,以决定是否可以采取快或慢的代码路径。 ”。但如果性能真的很重要,您应该寻求替代路线。
     col1  col2
  0     2     6
  1     2     7
  2     1     1
  3     2     2
  4     2     8
Run Code Online (Sandbox Code Playgroud)
  • assign()不是 apply() 的可行替代品。它仅在最基本的用例中具有类似的行为。它不适用于complex_function. 您仍然需要 apply() ,如下面的示例所示。assign()主要用例是方法链接,因为它在不更改原始数据帧的情况下返回数据帧。
  def bad_idea(x):
      return x['col1'] ** 2
Run Code Online (Sandbox Code Playgroud)

附件:如何加快申请?

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

  1. 不要使用 apply()。这不是开玩笑。对于大多数数值运算,pandas 中存在矢量化方法。If/else 块通常可以通过布尔索引.loc. 我的例子complex_function可以这样重构。
  2. 重构为 Cython。如果您有一个复杂的方程并且方程的参数在您的数据框中,这可能是一个好主意。查看官方 Pandas 用户指南以获取更多信息。
  3. 使用raw=True参数。从理论上讲,如果您只是应用 NumPy 缩减函数,这应该会提高 apply() 的性能,因为 pd.Series 的开销被删除了。当然,您的函数必须接受一个 ndarray。您必须将函数重构为 NumPy。通过这样做,您将获得巨大的性能提升。
  4. 使用第 3 方软件包。您应该尝试的第一件事是Numba。我不知道更迅速的通过@durjoy提及; 可能还有许多其他软件包在这里值得一提。
  5. 尝试/失败/重复。如上所述, map() 和 applymap() 可以更快 - 取决于用例。只需对不同版本计时并选择最快的。这种方法是最乏味的一种,性能提升最少。


Mik*_*ler 39

你根本不需要一个功能.您可以直接处理整个列.

示例数据:

>>> df = pd.DataFrame({'a': [100, 1000], 'b': [200, 2000], 'c': [300, 3000]})
>>> df

      a     b     c
0   100   200   300
1  1000  2000  3000
Run Code Online (Sandbox Code Playgroud)

列中所有值的一半a:

>>> df.a = df.a / 2
>>> df

     a     b     c
0   50   200   300
1  500  2000  3000
Run Code Online (Sandbox Code Playgroud)

  • 如果我想用“/”分割列中的每个元素并获取第一部分怎么办? (2认同)

Thi*_*net 33

尽管给定的响应是正确的,但它们修改了初始数据帧,这并不总是可取的(并且,鉴于 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
Run Code Online (Sandbox Code Playgroud)

请注意,该函数将传递整个数据帧,而不仅仅是您要修改的列,因此您需要确保在 lambda 中选择正确的列。

  • 我试图让事情保持不变,用函数式编程来思考。我非常非常高兴你的回答!:-) (3认同)

dur*_*joy 18

如果您真的很关心 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)
Run Code Online (Sandbox Code Playgroud)

这将使您的所有 CPU 内核都能计算结果,因此它会比普通的应用函数快得多。尝试并告诉我它是否对您有用。


Har*_*_pb 5

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


归档时间:

查看次数:

240487 次

最近记录:

6 年 前