在Pandas中,map、applymap和apply方法的区别

704

你能告诉我何时使用这些矢量化方法以及基本示例吗?

我看到map是一个Series方法,而其他方法都是DataFrame方法。不过,我对applyapplymap方法感到困惑。为什么我们有两种方法可以将函数应用于DataFrame?同样,提供说明用法的简单示例将非常有帮助!


14
请纠正我,但我认为这些函数不是向量化方法,因为它们都涉及对应用于的元素进行循环。 - Tanguy
1
我在这里看不出任何区别:https://gist.github.com/MartinThoma/e320cbb937afb4ff766f75988f1c65e6 - Martin Thoma
Marillion,我在下面的回答中提供了非常简化和简单的示例。希望能有所帮助! - mikelowry
我应该将“DataFrame.pipe()”方法添加到比较中吗? - Say OL
12个回答

716

apply 方法在 DataFrame 的行 / 列基础上运作
applymap 方法在 DataFrame 的每个元素上运作
map 方法在 Series 的每个元素上运作


来自 Wes McKinney 的 Python for Data Analysis 一书,第 132 页 (我强烈推荐这本书):

另一个常见操作是将一个函数应用于每个列或行的 1D 数组。DataFrame 的 apply 方法恰好做到了这一点:

In [116]: frame = DataFrame(np.random.randn(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])

In [117]: frame
Out[117]: 
               b         d         e
Utah   -0.029638  1.081563  1.280300
Ohio    0.647747  0.831136 -1.549481
Texas   0.513416 -0.884417  0.195343
Oregon -0.485454 -0.477388 -0.309548

In [118]: f = lambda x: x.max() - x.min()

In [119]: frame.apply(f)
Out[119]: 
b    1.133201
d    1.965980
e    2.829781
dtype: float64

许多常见的数组统计数据(如总和和平均值)都是DataFrame方法,因此不需要使用apply。

也可以使用逐元素Python函数。假设你想从frame中的每个浮点数值计算格式化的字符串,可以使用applymap:

In [120]: format = lambda x: '%.2f' % x

In [121]: frame.applymap(format)
Out[121]: 
            b      d      e
Utah    -0.03   1.08   1.28
Ohio     0.65   0.83  -1.55
Texas    0.51  -0.88   0.20
Oregon  -0.49  -0.48  -0.31

applymap这个名称的原因是,Series有一个map方法,可以应用逐元素函数:

In [122]: frame['e'].map(format)
Out[122]: 
Utah       1.28
Ohio      -1.55
Texas      0.20
Oregon    -0.31
Name: e, dtype: object

43
严格来说,applymap在内部是通过对传递给函数参数进行一些封装(粗略地将func替换为lambda x:[func(y)for y in x],并按列应用)来实现的。 - alko
6
感谢您的解释。由于mapapplymap都是逐元素操作,我期望有一个单一的方法(可以是mapapplymap)既能用于Series也能用于DataFrame。可能存在其他设计考虑,Wes McKinney决定创建两个不同的方法。 - marillion
2
出于某种原因,它在我的副本的第129页。没有第二版或任何标签。 - Jody
1
在 Pandas 中,有没有一种方法可以在 groupby 函数的同时执行 applymap 操作? - everestial007
4
我建议不要使用format作为函数名称(例如示例2),因为format已经是一个内置函数。 - Arturo Moncada-Torres
显示剩余4条评论

339

比较map, applymapapply:上下文很重要

主要的区别如下:

定义

  • map仅在Series上定义
  • applymap仅在DataFrames上定义
  • apply在两者上都定义

输入参数

  • map接受dictSeries或可调用对象
  • applymapapply只接受可调用对象

行为

  • map对于Series是逐元素操作的。
  • applymap对于DataFrames是逐元素操作的。
  • apply也可以进行逐元素操作,但更适用于复杂的操作和聚合。其行为和返回值取决于函数本身。

使用场景(最重要的区别)

  • map 用于将一个域中的值映射到另一个域中,因此它在性能上进行了优化,例如:

    df['A'].map({1:'a', 2:'b', 3:'c'})
    
  • applymap 适用于对多行/列进行逐元素转换,例如:

    df[['A', 'B', 'C']].applymap(str.strip)
    
  • apply 用于应用无法向量化的任何函数,例如:

    df['sentences'].apply(nltk.sent_tokenize)
    
此外,还可以参考我之前写的一篇文章在我的代码中何时(不)应该使用pandas的apply()函数?,了解使用apply的最合适场景。需要注意的是,并不是很多情况下都适合使用apply,因为它通常比较

总结

map applymap apply
Defined on Series?
Defined on DataFrame?
Argument dictSeries或可调用函数1 可调用函数2 可调用函数
Elementwise?
Aggregation?
Use Case 转换/映射3 转换 更复杂的函数
Returns Series DataFrame 标量、SeriesDataFrame4

脚注

  1. map 当传入一个字典/序列时,将根据该字典/序列中的键进行映射。缺失值将在输出中记录为NaN。

  2. applymap 在较新版本中已经针对某些操作进行了优化。在某些情况下,你会发现applymapapply稍微快一些。我的建议是测试两者,并使用效果更好的那个。

  3. map 针对逐元素映射和转换进行了优化。涉及字典或序列的操作将使pandas能够使用更快的代码路径以获得更好的性能。

  4. Series.apply 对于聚合操作返回标量,否则返回Series。对于DataFrame.apply也是如此。请注意,当与某些NumPy函数(如meansum等)一起调用时,apply也具有快速路径。


1
在我看来,最好的答案是: - yts61
你为什么说apply是逐元素的?你是否将列和行视为数据框的元素? - wjandrea
pandas 2.1.0 (2023-08-30) 已更名 df.applymap()df.map()。目前前者仍然可用,但会记录一个FutureWarning建议使用更合理的名称。 - undefined

96

快速摘要

  • DataFrame.apply 一次操作整个行或列。

  • DataFrame.applymapSeries.applySeries.map 一次操作一个元素。

Series.applySeries.map相似且通常可互换使用。它们之间的一些细微差别在下面osa的答案中讨论。


45

除了其他回答之外,在 Series 中还有 mapapply

apply 可以将一个 series 转换为 DataFrame;然而,map 只会将一个 series 放入另一个 series 的每个单元格中,这可能不是您想要的。

In [40]: p=pd.Series([1,2,3])
In [41]: p
Out[31]:
0    1
1    2
2    3
dtype: int64

In [42]: p.apply(lambda x: pd.Series([x, x]))
Out[42]: 
   0  1
0  1  1
1  2  2
2  3  3

In [43]: p.map(lambda x: pd.Series([x, x]))
Out[43]: 
0    0    1
1    1
dtype: int64
1    0    2
1    2
dtype: int64
2    0    3
1    3
dtype: int64
dtype: object

如果我有一个带有副作用的函数,比如"连接到web服务器",为了清晰明了起见,我可能会使用apply

series.apply(download_file_for_every_element) 

Map不仅可以使用函数,还可以使用字典或另一个序列。假设您想要操作排列

拿走

1 2 3 4 5
2 1 4 5 3

这个排列的平方是

1 2 3 4 5
1 2 5 3 4
你可以使用map来计算它。不确定是否有记录自我应用,但在0.15.1中它可以工作。
In [39]: p=pd.Series([1,0,3,4,2])

In [40]: p.map(p)
Out[40]: 
0    0
1    1
2    4
3    2
4    3
dtype: int64

5
此外,.apply() 允许在调用函数时传递关键字参数(kwargs),而 .map() 不允许。 - lineil

23

@jeremiahbuddha提到apply适用于行/列,而applymap适用于逐元素操作。但似乎你仍然可以使用apply进行逐元素计算....

frame.apply(np.sqrt)
Out[102]: 
               b         d         e
Utah         NaN  1.435159       NaN
Ohio    1.098164  0.510594  0.729748
Texas        NaN  0.456436  0.697337
Oregon  0.359079       NaN       NaN

frame.applymap(np.sqrt)
Out[103]: 
               b         d         e
Utah         NaN  1.435159       NaN
Ohio    1.098164  0.510594  0.729748
Texas        NaN  0.456436  0.697337
Oregon  0.359079       NaN       NaN

31
做得好,这个方法能够奏效是因为np.sqrt是一个 ufunc,也就是说,如果你给它一个数组,它会将 sqrt 函数广播到数组的每个元素上。所以当apply将 np.sqrt 应用于每列时,np.sqrt 本身会对列中的每个元素进行操作,因此你实际上得到的结果与 applymap 相同。 - jeremiahbuddha

15

解释apply和applymap的区别最简单的方法是:

apply将整个列作为参数,然后将结果赋值给该列。

applymap将各个单元格的值作为参数,并将结果重新赋值给该单元格。

注意:如果apply返回单个值,则在分配之后只会得到该值,最终会得到一个行而不是矩阵。


12

只是想指出,我曾经为此苦苦挣扎一段时间。

def f(x):
    if x < 0:
        x = 0
    elif x > 100000:
        x = 100000
    return x

df.applymap(f)
df.describe()

这不会修改数据框本身,必须重新赋值:

df = df.applymap(f)
df.describe()

1
有时候我会遇到困难,不确定在对 df 进行某些操作后是否需要重新赋值。对我来说,这主要是靠试错来解决的,但我相信它的工作原理一定有其逻辑性(而我可能没有掌握)。 - marillion
3
一般来说,pandas 数据框只能通过重新赋值 df = modified_df 或者设置 inplace=True 标识进行修改。如果将数据框作为引用参数传递到函数中,并且该函数修改了数据框,则数据框也会发生变化。 - muon
1
这并不完全正确,考虑 .ix.where 等。不确定何时需要重新分配,何时不需要的完整解释是什么。 - Thanos

7

根据cs95的回答:

  • map 只能用于 Series
  • applymap 只能用于 DataFrames
  • apply 可以同时用于两者

以下是一些例子:

In [3]: frame = pd.DataFrame(np.random.randn(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])

In [4]: frame
Out[4]:
            b         d         e
Utah    0.129885 -0.475957 -0.207679
Ohio   -2.978331 -1.015918  0.784675
Texas  -0.256689 -0.226366  2.262588
Oregon  2.605526  1.139105 -0.927518

In [5]: myformat=lambda x: f'{x:.2f}'

In [6]: frame.d.map(myformat)
Out[6]:
Utah      -0.48
Ohio      -1.02
Texas     -0.23
Oregon     1.14
Name: d, dtype: object

In [7]: frame.d.apply(myformat)
Out[7]:
Utah      -0.48
Ohio      -1.02
Texas     -0.23
Oregon     1.14
Name: d, dtype: object

In [8]: frame.applymap(myformat)
Out[8]:
            b      d      e
Utah     0.13  -0.48  -0.21
Ohio    -2.98  -1.02   0.78
Texas   -0.26  -0.23   2.26
Oregon   2.61   1.14  -0.93

In [9]: frame.apply(lambda x: x.apply(myformat))
Out[9]:
            b      d      e
Utah     0.13  -0.48  -0.21
Ohio    -2.98  -1.02   0.78
Texas   -0.26  -0.23   2.26
Oregon   2.61   1.14  -0.93


In [10]: myfunc=lambda x: x**2

In [11]: frame.applymap(myfunc)
Out[11]:
            b         d         e
Utah    0.016870  0.226535  0.043131
Ohio    8.870453  1.032089  0.615714
Texas   0.065889  0.051242  5.119305
Oregon  6.788766  1.297560  0.860289

In [12]: frame.apply(myfunc)
Out[12]:
            b         d         e
Utah    0.016870  0.226535  0.043131
Ohio    8.870453  1.032089  0.615714
Texas   0.065889  0.051242  5.119305
Oregon  6.788766  1.297560  0.860289

5

为了更好地理解,这里举一个明确具体的例子来说明区别。

假设您有以下函数。 (此标签函数将根据您提供的阈值(x)任意将值分为“高”和“低”。)

def label(element, x):
    if element > x:
        return 'High'
    else:
        return 'Low'


在这个例子中,我们假设我们的数据框有一列随机数。

Df with one column that has random numbers

如果您尝试使用 map 函数映射标签函数:

df['ColumnName'].map(label, x = 0.8)

您将得到以下错误:

TypeError: map() got an unexpected keyword argument 'x'

现在使用apply函数来运行同样的功能,你会看到它能够正常工作:
df['ColumnName'].apply(label, x=0.8)

Series.apply()可以逐个元素地使用额外的参数,而Series.map()方法会返回错误。

现在,如果您想同时将同一函数应用于数据帧中的多个列,则使用DataFrame.applymap()

df[['ColumnName','ColumnName2','ColumnName3','ColumnName4']].applymap(label)

最后,您也可以在数据框上使用apply()方法,但是DataFrame.apply()方法具有不同的功能。该df.apply()方法不是逐个元素应用函数,而是沿着轴(列或行)应用函数。当我们创建一个要与df.apply()一起使用的函数时,我们将其设置为接受一个序列,通常是一列。
以下是一个示例:
df.apply(pd.value_counts)

当我们将pd.value_counts函数应用于数据帧时,它会计算所有列的值计数。
请注意,这非常重要,当我们使用df.apply()方法转换多个列时。这只有可能是因为pd.value_counts函数在系列上运行。如果我们尝试使用df.apply()方法将逐个元素工作的函数应用于多个列,我们将收到错误消息:
例如:
def label(element):
    if element > 1:
        return 'High'
    else:
        return 'Low'

df[['ColumnName','ColumnName2','ColumnName3','ColumnName4']].apply(label)


这将导致以下错误:
ValueError: ('The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().', u'occurred at index Economy')

一般来说,只有在不存在向量化函数时才应该使用apply()方法。回想一下,pandas使用向量化处理来优化性能,即将操作一次性应用于整个系列。当我们使用apply()方法时,实际上是通过行循环,因此,向量化方法可以比apply()方法更快地执行相同的任务。

apply, applymap, map summarization

以下是一些已经存在的向量化函数示例,您不需要使用任何类型的apply/map方法重新创建它们:
  1. Series.str.split() 将系列中的每个元素拆分
  2. Series.str.strip() 从系列中的每个字符串中去除空格。
  3. Series.str.lower() 将系列中的字符串转换为小写。
  4. Series.str.upper() 将系列中的字符串转换为大写。
  5. Series.str.get() 检索系列中每个元素的第i个元素。
  6. Series.str.replace() 使用另一个字符串替换系列中的正则表达式或字符串
  7. Series.str.cat() 连接系列中的字符串。
  8. Series.str.extract() 从与正则表达式模式匹配的系列中提取子字符串。

3

我的理解:

从功能角度来看:

如果函数中有需要在列/行内进行比较的变量,则使用apply

例如: lambda x: x.max()-x.mean()

如果要将函数应用于每个元素:

1> 如果要对列/行进行操作,请使用apply

2> 如果要将函数应用于整个数据帧,请使用applymap

majority = lambda x : x > 17
df2['legal_drinker'] = df2['age'].apply(majority)

def times10(x):
  if type(x) is int:
    x *= 10 
  return x
df2.applymap(times10)

请提供df2以获得更好的清晰度,以便我们测试您的代码。 - Ashish Anand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接