我有一个有两列的熊猫数据框架。我需要在不影响第二列的情况下改变第一列的值,并返回整个数据框架,只是第一列的值改变了。我如何在熊猫中使用apply()来做到这一点?


当前回答

如果你真的很关心你的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核心计算结果,因此它将比普通应用函数快得多。试着让我知道它是否对你有用。

其他回答

如果您需要修改列,请先复制您的数据框架

这里的许多回答建议修改一些列并将新值赋给旧列。通常会得到SettingWithCopyWarning:一个值正试图从数据帧(DataFrame)的片拷贝上设置。警告。当你的数据帧是从另一个数据帧创建的,但不是一个正确的副本时,就会发生这种情况。

若要消除此警告,请复制并分配回。

df = df.copy()
df['a'] = df['a'].apply('add', other=1)

Apply()只需要函数名

可以通过简单地将函数名传递给apply()来调用函数(不需要lambda)。如果函数需要附加参数,可以将它们作为关键字参数传递,也可以将位置参数作为args=传递。例如,假设你的数据框架中有文件路径,你需要读取这些路径中的文件。

def read_data(path, sep=',', usecols=[0]):
    return pd.read_csv(path, sep=sep, usecols=usecols)

df = pd.DataFrame({'paths': ['../x/yz.txt', '../u/vw.txt']})

df['paths'].apply(read_data)                            # you don't need lambda

df['paths'].apply(read_data, args=(',', [0, 1]))        # pass the positional arguments to `args=`

df['paths'].apply(read_data, sep=',', usecols=[0, 1])   # pass as keyword arguments

不要应用函数,而是直接调用适当的方法

通过apply()在列上应用自定义函数几乎不太理想。因为apply()是具有pandas开销的Python循环的语法糖,它通常比在列表理解中调用相同的函数要慢,更不用说调用优化的pandas方法了。几乎所有的数值运算符都可以直接应用在列上,并且都有相应的方法。

# add 1 to every element in column `a`
df['a'] += 1

# for every row, subtract column `a` value from column `b` value
df['c'] = df['b'] - df['a']

如果想要应用具有If -else块的函数,则可能应该使用numpy.where()或numpy.select()。它要快得多。如果您的数据行大于10k行,您将立即注意到差异。

例如,如果您有一个类似于下面func()的自定义函数,那么您可以直接操作列并使用numpy.select()返回值,而不是将其应用于列。

def func(row):
    if row == 'a':
        return 1
    elif row == 'b':
        return 2
    else:
        return -999

# instead of applying a `func` to each row of a column, use `numpy.select` as below

import numpy as np
conditions = [df['col'] == 'a', df['col'] == 'b']
choices = [1, 2]
df['new'] = np.select(conditions, choices, default=-999)

正如你所看到的,numpy.select()与if-else梯子的语法差异非常小;只需要将条件和选项分离到单独的列表中。对于其他选项,请查看这个答案。

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

示例数据:

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

尽管给出的响应是正确的,但它们修改了初始数据帧,这并不总是可取的(并且,给定OP要求“使用apply”示例,可能它们想要一个返回新数据帧的版本,就像apply那样)。

这可以通过使用assign实现:如文档所述(重点是我的),对现有列进行赋值是有效的:

为数据框架分配新列。 返回一个包含所有原始列和新列的新对象。重新分配的现有列将被覆盖。

简而言之:

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

让我使用datetime并考虑null或空格来尝试一个复杂的计算。我在一个datetime列上减少30年,并使用apply方法以及lambda和转换datetime格式。行if x != "否则x将相应地处理所有空格或null。

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)

对于单列最好使用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