Два основных различия между applyиtransform
Есть два основных различия между transformи applyгрупповыми методами.
- Входные данные:
applyнеявно передает все столбцы для каждой группы как DataFrame пользовательской функции.
- В то время как
transformкаждый столбец для каждой группы индивидуально в виде серии в пользовательскую функцию.
- Вывод:
applyПереданная пользовательская функция может возвращать скаляр или Series или DataFrame (или массив numpy или даже список) .
- Переданная пользовательская функция
transformдолжна возвращать последовательность (одномерный ряд, массив или список) той же длины, что и группа .
Таким образом, transformработает только с одной серией за раз и applyработает со всем DataFrame одновременно.
Проверка пользовательской функции
Это может помочь немного проверить входные данные для вашей пользовательской функции, переданной applyили transform.
Примеры
Давайте создадим пример данных и проверим группы, чтобы вы могли понять, о чем я говорю:
import pandas as pd
import numpy as np
df = pd.DataFrame({'State':['Texas', 'Texas', 'Florida', 'Florida'],
'a':[4,5,1,3], 'b':[6,10,3,11]})
State a b
0 Texas 4 6
1 Texas 5 10
2 Florida 1 3
3 Florida 3 11
Давайте создадим простую пользовательскую функцию, которая распечатывает тип неявно переданного объекта, а затем выдает ошибку, чтобы выполнение могло быть остановлено.
def inspect(x):
print(type(x))
raise
Теперь давайте передадим эту функцию как groupby, так applyи transformметодам, чтобы увидеть, какой объект ей передан:
df.groupby('State').apply(inspect)
<class 'pandas.core.frame.DataFrame'>
<class 'pandas.core.frame.DataFrame'>
RuntimeError
Как видите, DataFrame передается в inspectфункцию. Вы можете быть удивлены, почему тип DataFrame был напечатан дважды. Панды управляют первой группой дважды. Это делает это, чтобы определить, есть ли быстрый способ завершить вычисление или нет. Это небольшая деталь, о которой вам не стоит беспокоиться.
Теперь давайте сделаем то же самое с transform
df.groupby('State').transform(inspect)
<class 'pandas.core.series.Series'>
<class 'pandas.core.series.Series'>
RuntimeError
Передано Серию - совершенно другой объект Панд.
Таким образом, transformразрешено работать только с одной серией одновременно. Для него не является невозможным воздействовать на две колонки одновременно. Таким образом, если мы попытаемся вычесть столбец aиз bнашей пользовательской функции, мы получим ошибку transform. Увидеть ниже:
def subtract_two(x):
return x['a'] - x['b']
df.groupby('State').transform(subtract_two)
KeyError: ('a', 'occurred at index a')
Мы получаем KeyError, поскольку pandas пытается найти индекс Series, aкоторый не существует. Вы можете выполнить эту операцию, так applyкак она содержит весь DataFrame:
df.groupby('State').apply(subtract_two)
State
Florida 2 -2
3 -8
Texas 0 -2
1 -5
dtype: int64
Вывод представляет собой серию и немного сбивает с толку, поскольку исходный индекс сохраняется, но у нас есть доступ ко всем столбцам.
Отображение переданного объекта панды
Это может помочь еще больше отобразить весь объект pandas в пользовательской функции, чтобы вы могли точно видеть, с чем вы работаете. Вы можете использовать printоператоры, которые мне нравятся, чтобы использовать displayфункцию из IPython.displayмодуля, чтобы DataFrames красиво выводились в HTML в блокноте jupyter:
from IPython.display import display
def subtract_two(x):
display(x)
return x['a'] - x['b']
Скриншот:

Преобразование должно возвращать одномерную последовательность того же размера, что и группа
Другое отличие состоит в том, что transformдолжна возвращать одномерную последовательность того же размера, что и группа. В этом конкретном случае каждая группа имеет две строки, поэтому transformдолжна возвращать последовательность из двух строк. Если это не так, возникает ошибка:
def return_three(x):
return np.array([1, 2, 3])
df.groupby('State').transform(return_three)
ValueError: transform must return a scalar value for each group
Сообщение об ошибке на самом деле не описывает проблему. Вы должны вернуть последовательность той же длины, что и группа. Итак, такая функция будет работать:
def rand_group_len(x):
return np.random.rand(len(x))
df.groupby('State').transform(rand_group_len)
a b
0 0.962070 0.151440
1 0.440956 0.782176
2 0.642218 0.483257
3 0.056047 0.238208
Возвращение одного скалярного объекта также работает для transform
Если вы вернете только один скаляр из своей пользовательской функции, то transformбудете использовать его для каждой строки в группе:
def group_sum(x):
return x.sum()
df.groupby('State').transform(group_sum)
a b
0 9 16
1 9 16
2 4 14
3 4 14
transformдолжна возвращать число, строку или ту же форму, что и аргумент. если это число, то число будет установлено для всех элементов в группе, если это строка, она будет транслироваться на все строки в группе. В вашем коде лямбда-функция возвращает столбец, который нельзя передать группе.