pandas 如何对单个列使用apply()函数?

uyhoqukh  于 2023-01-28  发布在  其他
关注(0)|答案(8)|浏览(182)

我有一个包含两列的panda Dataframe ,我需要在不影响第二列的情况下更改第一列的值,然后只更改第一列的值就可以恢复整个 Dataframe 。如何在panda中使用apply()来实现这一点?

fae0ux8s

fae0ux8s1#

给定示例 Dataframe df为:

a  b
0  1  2
1  2  3
2  3  4
3  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
uoifb46i

uoifb46i2#

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

vshtjzan3#

给定以下 Dataframe 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()。2下面我将详细解释它们。

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'的值传递给complex_function的变量y,您需要其他东西。

II.使用整个 Dataframe 的解决方案

或者,您可以使用整个 Dataframe ,如in this SO postthis one所述:

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']作为参数;也就是说,我们将列信息 Package 在另一个函数中。
不幸的是,axis参数的默认值是零(axis=0),这意味着它将尝试按列执行而不是按行执行,这在第一个解决方案中不是问题,因为我们给apply()一个pd.Series,但现在输入是一个 Dataframe ,我们必须显式(axis=1)(我很惊讶我经常忘记这一点)。
你喜欢带lambda函数还是不带lambda函数的版本是主观的。在我看来,即使没有lambda函数,这行代码也足够复杂了。你只需要把(lambda)函数作为一个 Package 器。它只是样板代码。读者不应该为它烦恼。
现在,您可以轻松地修改此解决方案,以考虑第二列:

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

这样做,你就使得一个通用函数依赖于一个列名!这是一个坏主意,因为下次你想使用这个函数时,你就不能使用了。更糟的是:也许你在不同的 Dataframe 中重命名了一个列,只是为了让它能和你现有的函数一起工作。(已经这样做了。这是一个滑坡!)

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()是不同的函数。*
因此,考虑一些替代方案确实有意义:

  • map()仅适用于pd.Series,但接受dict和pd.Series作为输入。将map()与函数一起使用几乎可以与使用apply()互换。它可能比apply()更快。有关详细信息,请参见this SO post
df['col1'] = df['col1'].map(complex_function)
  • applymap()对于 Dataframe 几乎相同。它不支持pd.Series,并且总是返回 Dataframe 。但是,它可以更快。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,因为它在不更改原始 Dataframe 的情况下返回 Dataframe 。
df['col1'] = df.assign(col1=df.col1.apply(complex_function))

附件:如何加速apply()

我之所以在这里提到它,是因为它是由其他答案建议的,例如@durjoy。这个列表并不详尽:
1.* * 不要使用apply()。**这不是开玩笑。对于大多数数值运算,向量化方法存在于panda中。if/else块通常可以使用布尔索引和.loc的组合进行重构。我的示例complex_function可以用这种方式进行重构。

1.**重构到Cython。**如果你有一个复杂的方程,并且方程的参数在你的 Dataframe 中,这可能是一个好主意。查看官方的panda用户指南以获得更多信息。
1.**使用raw=True参数。**理论上,这应该会提高apply()if you are just applying a NumPy reduction function的性能,因为pd.Series的开销被删除了。当然,你的函数必须接受ndarray。你必须将你的函数重构为NumPy。通过这样做,你将有一个巨大的性能提升。
1.使用第三方软件包首先应该尝试Numba,@durjoy提到的swifter我不知道;并且可能许多其他包在这里值得一提。
1.**尝试/失败/重复。**如上所述,map()applymap()可能更快-具体取决于使用情形。只需对不同版本计时,然后选择最快的版本。这种方法最繁琐,但性能提升最少。

5lhxktic

5lhxktic4#

你根本不需要函数,你可以直接在一整列上工作。
示例数据:

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

ghg1uchk5#

尽管给出的响应是正确的,但它们修改了初始 Dataframe ,这并不总是可取的(并且,考虑到OP要求提供"使用apply"的示例,它们可能需要一个返回新 Dataframe 的版本,就像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

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

x0fgdtte

x0fgdtte6#

如果你真的很关心apply函数的执行速度,并且你有一个巨大的数据集要处理,你可以使用swifter来加快执行速度,下面是一个在panda Dataframe 上使用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核心计算的结果,因此它会比正常的应用功能快得多。尝试让我知道,如果它成为有用的你。

zy1mlcev

zy1mlcev7#

让我尝试一个复杂的计算,使用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)
wnrlj8wa

wnrlj8wa8#

如果需要修改列,请先制作 Dataframe 的副本

这里的许多答案建议修改一些列并将新值赋给旧列。通常会收到SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.警告。当您的 Dataframe 是从另一个 Dataframe 创建的,但不是正确的副本时,会发生这种情况。
要使此警告静音,请复制一份并重新指定。

df = df.copy()
df['a'] = df['a'].apply('add', other=1)
apply()只需要函数的名称

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

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()是Python循环的语法糖,有一个Pandas开销,它通常比在列表解析中调用相同的函数要 * 慢 *,没关系,调用优化的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()来代替,它要快得多,如果你有超过10 k行的数据,你会马上注意到不同。
例如,如果您有一个类似于下面的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梯形图的语法差别非常小;只需要将条件和选项分开到单独的列表中。对于其他选项,请查看this answer

相关问题