Pandas DataFrame中transform和applymap有什么区别?

10

applymap函数对DataFrame操作逐个元素进行处理,而transform函数似乎能够实现相同的功能,但声称返回一个与原DataFrame索引相同的DataFrame。

问题:

  1. 是否存在一种使用情况,其中一个函数适用而另一个不适用?
  2. 它们中哪个的性能更好?
  3. 文档中所述的“与原DataFrame索引相同的DataFrame”是什么意思?
4个回答

16

不同的使用情况。在进行比较时,提及 applyagg 会很有帮助。

设置

np.random.seed([3,1415])
df = pd.DataFrame(np.random.randint(10, size=(6, 4)), columns=list('ABCD'))

df

   A  B  C  D
0  0  2  7  3
1  8  7  0  6
2  8  6  0  2
3  0  4  9  7
4  3  2  4  3
5  3  6  7  7

pd.DataFrame.applymap
该函数接受一个函数作为参数,将该函数应用于每个单元格中的值,并用结果替换单元格的值,最终返回一个新的数据帧。

df.applymap(lambda x: str(x) * x)

          A        B          C        D
0                 22    7777777      333
1  88888888  7777777              666666
2  88888888   666666                  22
3               4444  999999999  7777777
4       333       22       4444      333
5       333   666666    7777777  7777777
pd.DataFrame.agg
接受一个或多个函数。每个函数都应该是一个聚合函数。这意味着每个函数都将应用于每个列,并且预计返回替换整个列的单个值。示例可以是'mean''max'。这两个函数都会从一组数据中获取一个标量并返回。
df.agg('mean')

A    3.666667
B    4.500000
C    4.500000
D    4.666667
dtype: float64

或者

df.agg(['mean', 'std', 'first', 'min'])

             A         B         C         D
mean  3.666667  4.500000  4.500000  4.666667
std   3.614784  2.167948  3.834058  2.250926
min   0.000000  2.000000  0.000000  2.000000

pd.DataFrame.transform
该函数需要传入一个应用于列并返回相同大小列的函数。


df.transform(lambda x: x / x.std())

          A         B         C         D
0  0.000000  0.922531  1.825742  1.332785
1  2.213133  3.228859  0.000000  2.665570
2  2.213133  2.767594  0.000000  0.888523
3  0.000000  1.845062  2.347382  3.109832
4  0.829925  0.922531  1.043281  1.332785
5  0.829925  2.767594  1.825742  3.109832

pd.DataFrame.apply
Pandas试图判断apply操作是否在减少所操作的列的维度(即聚合)或者是否将该列转换为大小相等的另一列。当它确定了这一点后,会将其余的操作作为聚合或转换过程来执行。

df.apply('mean')

A    3.666667
B    4.500000
C    4.500000
D    4.666667
dtype: float64

或者

df.apply(lambda x: (x - x.mean()) / x.std())

          A         B         C         D
0 -1.014353 -1.153164  0.652051 -0.740436
1  1.198781  1.153164 -1.173691  0.592349
2  1.198781  0.691898 -1.173691 -1.184698
3 -1.014353 -0.230633  1.173691  1.036611
4 -0.184428 -1.153164 -0.130410 -0.740436
5 -0.184428  0.691898  0.652051  1.036611

7
谢谢,但您没有回答我的问题。 - darcyy
@darcyq applymap通常较慢。尽管在最近几个版本中有所改善。请参考我的示例,并尝试找出其他问题的答案。 - piRSquared
5
这位用户花费了很多时间回答你的问题。实际上,你的问题应该因为太过宽泛而被“关闭”。对于你所有的问题,答案显然是“这取决于情况。” 这取决于数据、情境、版本、你的系统,“一切”。你不能期望在这类问题中获得一个适用于所有情况的答案。更何况,你还投票反对了这个答案。你为什么要这样做呢? - cs95
1
@cᴏʟᴅsᴘᴇᴇᴅ A) 我很欣赏他的回答,虽然是一篇长篇回答,但基本上与 https://pandas.pydata.org/pandas-docs/stable/basics.html#aggregation-api 相同。 B) 更重要的是,它没有回答我的问题。 C) 我正在询问两个 pandas 函数之间的区别(正如主题所说)。一个人可以深入挖掘源代码并找到答案,就像我提到的那样。我真的不认为这是一个广泛的问题。 - darcyy
6
你真的需要知道源代码是如何编写的才能理解这两个函数的区别吗?这个答案完美地解释了这两个函数的工作方式。一个处理系列,另一个处理单个单元格。如果你真的想看源代码,它可以在GitHub上免费获得,所以尽管去看吧。点击此处 - cs95
显示剩余3条评论

6

“文档中提到的 .transform() 返回一个与输入长度相同的 DataFrame,使索引标签保持一致。”

这意味着 .transform() 将一个函数应用于 DataFrame 中的每个值(或组,在使用groupby前)并返回另一个与输入长度相同的 DataFrame。强调一下:它在输出中保留了输入的索引标签。

是否存在其中一个方法能用而另一个方法不能用的情况?

当然。以下是一些例子:

1) applymap 与 transform 的区别

applymap 对 DataFrame 中的所有元素执行操作,因此不能对 Series 执行 applymap

df['Quantity'].transform(lambda x: x+10) # successful
df['Quantity'].apply(lambda x: x+10) # successful
df['Quantity'].applymap(lambda x: x+10) # gives AttributeError: 'Series' object has no attribute 'applymap'

# unless you cast it to DataFrame:
pd.DataFrame(df['Quantity']).applymap(lambda x: x+10) # successful

另一个重要的区别是,尽管.applymap()进行逐元素操作,.transform()可以执行分组操作,这在下一部分中提到。

此外,applymap()不能由groupby()前置执行。

2) apply与transform的区别

applytransform可以相互替换,只要它们作用于DataFrame的列(s)。这里有一个简单的例子:

# imagine the following DataFrame
df = pd.DataFrame({'Label': ['A', 'B', 'C', 'A', 'C'],
                   'Values': [0,1,2,3,4],
                   'Quantity': [5,6,7,8,9]}, index = list('VWXYZ'))


    Label   Quantity   Values
---------------------------------
V    A         5         0
W    B         6         1
X    C         7         2
Y    A         8         3
Z    C         9         4


df.loc[:, ['Quantity', 'Values']].apply(lambda x: x+10)
df.loc[:, ['Quantity', 'Values']].transform(lambda x: x+10)
# both of them give the following same result:

    Quantity   Values
-------------------------
V    15          10
W    16          11
X    17          12
Y    18          13
Z    19          14

主要的区别出现在它们执行 groupby 操作时。例如:
label_grouping = df.groupby('Label')
label_grouping.apply(lambda x: x.mean())
# output:

      Quantity   Values
Label
-----------------------
A       6.5       1.5
B       6.0       1.0
C       8.0       3.0

label_grouping.transform(lambda x: x.mean())
# see how `transform` could manage to keeps the input index labels in the output
# output:

    Quantity   Values
------------------------
V     6.5       1.5
W     6.0       1.0
X     8.0       3.0
Y     6.5       1.5
Z     8.0       3.0

上面的例子清楚地展示了 transform 如何保留输入 DataFrame 的索引;因此为了更好地利用这一独特的功能,下面的简短示例尝试说明如何通过计算每个产品代表的订单总额的百分比来从输入和输出之间的索引对齐中受益:
df_sales = pd.DataFrame({'OrderID': [1001,1001,1001,1002,1002],
                         'Product': ['p1','p2','p3','p1','p4'],
                         'Quantity': [30,20,70,160,40]})


    OrderID   Product   Quantity
-----------------------------------
0    1001       p1        30
1    1001       p2        20
2    1001       p3        70
3    1002       p1        160
4    1002       p4        40


df_sales['total_per_order'] = df_sales.groupby(['OrderID'])['Quantity'].transform(lambda x: x.sum()) 
df_sales['pct_of_order'] = df_sales['Quantity'] / df_sales['total_per_order']


    OrderID   Product   Quantity   total_per_order   pct_of_order
----------------------------------------------------------------------
0    1001       p1        30           120             0.250000
1    1001       p2        20           120             0.166667
2    1001       p3        70           120             0.583333
3    1002       p1        160          200             0.800000
4    1002       p4        40           200             0.200000

强烈建议您访问以下链接以获取更详细的示例:https://pbpython.com/pandas_transform.html

许多聚合函数已直接内置到groupby对象中,以减少您的输入。具体而言,一些常见函数(以gb为前缀)可供使用:

  • gb.apply
  • gb.transform
  • gb.filter
  • gb.agg
  • gb.count
  • gb.comsum
  • gb.fillna
  • ...

希望这可以帮助您 :)


3
我是新手 Pandas,一直在尝试找到同样问题的答案。我在 Pandas 网站上找到了这个(https://pandas.pydata.org/pandas-docs/stable/basics.html
引用: “由于并非所有函数都可以矢量化(接受 NumPy 数组并返回另一个数组或值),因此 DataFrame 上的 applymap() 方法和 Series 上的 map() 方法类似地接受任何 Python 函数,该函数接受单个值并返回单个值。”
我理解为传递给 transform 的函数应该是矢量化的,并且整个系列将作为参数传递给这些函数。传递给 map(和 applymap)的函数不需要矢量化,而是在 map 迭代系列时将每个元素传递给函数。

0
> df = pd.DataFrame([['europe', 'france', 68],
>                    ['europe', 'russia', 144],
>                    ['asia', 'china', 1398]],
>                   columns=['continent', 'country', 'population'])
> df

  continent country  population
0    europe  france          68
1    europe  russia         144
2      asia   china        1398

applymap

> df[['country', 'population']].set_index('country')
  .applymap(lambda x: 'big' if x > 100 else 'small')

country           
france       small
russia         big
china          big

> df.groupby(['continent'], sort=False)['population'].applymap(max)

AttributeError: 'SeriesGroupBy' object has no attribute 'applymap'

转换

> df[['country', 'population']].set_index('country')
  .transform(lambda x: 'big' if x > 100 else 'small')

ValueError: The truth value of a Series is ambiguous.

> df.groupby(['continent'], sort=False)['population'].transform(max)

0     144
1     144
2    1398
Name: population, dtype: int64

> df[df.groupby(['continent'], sort=False)['population'].transform(max)
     == df['population']]

  continent country  population
1    europe  russia         144
2      asia   china        1398

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