Рассмотрим следующий кадр данных:
A B C D
0 foo one 0.162003 0.087469
1 bar one -1.156319 -1.526272
2 foo two 0.833892 -1.666304
3 bar three -2.026673 -0.322057
4 foo two 0.411452 -0.954371
5 bar two 0.765878 -0.095968
6 foo one -0.654890 0.678091
7 foo three -1.789842 -1.130922
Работают следующие команды:
> df.groupby('A').apply(lambda x: (x['C'] - x['D']))
> df.groupby('A').apply(lambda x: (x['C'] - x['D']).mean())
но ни одна из следующих работ:
> df.groupby('A').transform(lambda x: (x['C'] - x['D']))
ValueError: could not broadcast input array from shape (5) into shape (5,3)
> df.groupby('A').transform(lambda x: (x['C'] - x['D']).mean())
TypeError: cannot concatenate a non-NDFrame object
Зачем? Пример в документации, кажется, предполагает, что вызов transform
группы позволяет выполнять построчную обработку операций:
# Note that the following suggests row-wise operation (x.mean is the column mean)
zscore = lambda x: (x - x.mean()) / x.std()
transformed = ts.groupby(key).transform(zscore)
Другими словами, я думал, что преобразование - это, по сути, специфический тип применения (тот, который не агрегирует). Где я не прав?
Для справки ниже приведена конструкция исходного кадра данных выше:
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
'foo', 'bar', 'foo', 'foo'],
'B' : ['one', 'one', 'two', 'three',
'two', 'two', 'one', 'three'],
'C' : randn(8), 'D' : randn(8)})
transform
должна возвращать число, строку или ту же форму, что и аргумент. если это число, то число будет установлено для всех элементов в группе, если это строка, она будет транслироваться на все строки в группе. В вашем коде лямбда-функция возвращает столбец, который нельзя передать группе.zscore
),transform
получит лямбда-функцию, которая предполагает, что каждыйx
является элементом внутриgroup
, и также возвращает значение для элемента в группе. Чего мне не хватает?apply
проходит весь df, ноtransform
передает каждый столбец индивидуально как Серию. 2)apply
может возвращать любой вывод формы (скаляр / серия / массив данных / массив / список ...), тогда какtransform
должен возвращать последовательность (серия 1D / массив / список) той же длины, что и группа. Вот почему ОПapply()
не нужноtransform()
. Это хороший вопрос, так как документ не объяснил оба различия четко. (сродниapply/map/applymap
Ответы:
Два основных различия между
apply
иtransform
Есть два основных различия между
transform
иapply
групповыми методами.apply
неявно передает все столбцы для каждой группы как DataFrame пользовательской функции.transform
каждый столбец для каждой группы индивидуально в виде серии в пользовательскую функцию.apply
Переданная пользовательская функция может возвращать скаляр или Series или DataFrame (или массив numpy или даже список) .transform
должна возвращать последовательность (одномерный ряд, массив или список) той же длины, что и группа .Таким образом,
transform
работает только с одной серией за раз иapply
работает со всем DataFrame одновременно.Проверка пользовательской функции
Это может помочь немного проверить входные данные для вашей пользовательской функции, переданной
apply
илиtransform
.Примеры
Давайте создадим пример данных и проверим группы, чтобы вы могли понять, о чем я говорю:
Давайте создадим простую пользовательскую функцию, которая распечатывает тип неявно переданного объекта, а затем выдает ошибку, чтобы выполнение могло быть остановлено.
Теперь давайте передадим эту функцию как groupby, так
apply
иtransform
методам, чтобы увидеть, какой объект ей передан:Как видите, DataFrame передается в
inspect
функцию. Вы можете быть удивлены, почему тип DataFrame был напечатан дважды. Панды управляют первой группой дважды. Это делает это, чтобы определить, есть ли быстрый способ завершить вычисление или нет. Это небольшая деталь, о которой вам не стоит беспокоиться.Теперь давайте сделаем то же самое с
transform
Передано Серию - совершенно другой объект Панд.
Таким образом,
transform
разрешено работать только с одной серией одновременно. Для него не является невозможным воздействовать на две колонки одновременно. Таким образом, если мы попытаемся вычесть столбецa
изb
нашей пользовательской функции, мы получим ошибкуtransform
. Увидеть ниже:Мы получаем KeyError, поскольку pandas пытается найти индекс Series,
a
который не существует. Вы можете выполнить эту операцию, такapply
как она содержит весь DataFrame:Вывод представляет собой серию и немного сбивает с толку, поскольку исходный индекс сохраняется, но у нас есть доступ ко всем столбцам.
Отображение переданного объекта панды
Это может помочь еще больше отобразить весь объект pandas в пользовательской функции, чтобы вы могли точно видеть, с чем вы работаете. Вы можете использовать
print
операторы, которые мне нравятся, чтобы использоватьdisplay
функцию изIPython.display
модуля, чтобы DataFrames красиво выводились в HTML в блокноте jupyter:Скриншот:
Преобразование должно возвращать одномерную последовательность того же размера, что и группа
Другое отличие состоит в том, что
transform
должна возвращать одномерную последовательность того же размера, что и группа. В этом конкретном случае каждая группа имеет две строки, поэтомуtransform
должна возвращать последовательность из двух строк. Если это не так, возникает ошибка:Сообщение об ошибке на самом деле не описывает проблему. Вы должны вернуть последовательность той же длины, что и группа. Итак, такая функция будет работать:
Возвращение одного скалярного объекта также работает для
transform
Если вы вернете только один скаляр из своей пользовательской функции, то
transform
будете использовать его для каждой строки в группе:источник
np
не определен. Я предполагаю, что новички будут благодарны, если вы включитеimport numpy as np
в свой ответ.Поскольку я чувствовал себя так же запутанным с
.transform
операцией против,.apply
я нашел несколько ответов, проливающих некоторый свет на проблему. Этот ответ, например, был очень полезным.Мой вывод пока что
.transform
будет работать (или иметь дело) сSeries
(столбцами) изолированно друг от друга . Это означает, что в ваших последних двух звонках:Вы попросили
.transform
взять значения из двух столбцов, и «оно» фактически не «видит» их обоих одновременно (так сказать).transform
рассмотрим столбцы данных один за другим и вернут обратно серию (или группу рядов), «сделанную» из скаляров, которые повторяютсяlen(input_column)
.Таким образом, этот скаляр, который должен использоваться
.transform
для создания,Series
является результатом некоторой функции сокращения, применяемой к входуSeries
(и только к ОДНОЙ серии / столбцу за раз).Рассмотрим этот пример (на вашем фрейме данных):
даст:
Что точно так же, как если бы вы использовали его только для одного столбца за раз:
получая:
Обратите внимание, что
.apply
в последнем примере (df.groupby('A')['C'].apply(zscore)
) будет работать точно так же, но он потерпит неудачу, если вы попытаетесь использовать его на фрейме данных:выдает ошибку:
Так где еще
.transform
полезно? В простейшем случае попытка присвоить результаты функции сокращения обратно исходному кадру данных.получая:
Попытка же с
.apply
дастNaNs
вsum_C
. Потому.apply
что вернул бы уменьшенныйSeries
, который он не знает, как транслировать обратно:давая:
Есть также случаи, когда
.transform
используется для фильтрации данных:Я надеюсь, что это добавляет немного большей ясности.
источник
.transform()
может также использоваться для заполнения пропущенных значений. Особенно, если вы хотите транслировать среднее значение группы или групповую статистику дляNaN
значений в этой группе. К сожалению, документация панд также не помогла мне..groupby().filter()
делает то же самое. Спасибо за ваше объяснение,.apply()
и.transform()
меня это тоже сильно смущает.df.groupby().transform()
не может работать для подгруппы df, я всегда получаю сообщение об ошибке,ValueError: transform must return a scalar value for each group
потому чтоtransform
видит столбцы один за другимЯ собираюсь использовать очень простой фрагмент, чтобы проиллюстрировать разницу:
DataFrame выглядит следующим образом:
В этой таблице 3 идентификатора клиента, каждый из которых совершил три транзакции и заплатил 1,2,3 доллара каждый раз.
Теперь я хочу найти минимальный платеж для каждого клиента. Есть два способа сделать это:
Использование
apply
:grouping.min ()
Возвращение выглядит так:
Использование
transform
:grouping.transform (мин)
Возвращение выглядит так:
Оба метода возвращают
Series
объект, ноlength
первый из них равен 3, аlength
второй - 9.Если вы хотите ответить
What is the minimum price paid by each customer
, тоapply
метод является более подходящим для выбора.Если вы хотите ответить
What is the difference between the amount paid for each transaction vs the minimum payment
, то вы хотите использоватьtransform
, потому что:Apply
здесь не работает просто потому, что возвращает серию размера 3, но длина исходного df равна 9. Вы не можете легко интегрировать его обратно в исходный df.источник
как
или
источник