df = DataFrame.transform(func, axis=0, *args, **kwargs) df = Series.transform(func, *args, **kwargs) df = DataFrameGroupBy.transform(func, *args, engine=None, engine_kwargs=None, **kwargs)
func
- функция для преобразования данных. Принимает в качестве аргумента Series
(столбец/строку, в зависимости от axis
), а результат функции не должен менять исходную форму.
Допустимые комбинации:
[np.exp, 'sqrt']
;Если func
одновременно похож на список и на словарь, то поведение, подобное словарю, имеет приоритет.
axis=0
- если 0 или 'index'
: функция применяется к каждому столбцу. Если 1 или 'columns'
: функция применяется к каждой строке.
Для
Series.transform()
этот аргумент не используется.
*args
- позиционные аргументы для передачи в func
.
**kwargs
- ключевые аргументы для передачи в func
.
DataFrame.transform()
Методы DataFrame.transform()
и Series.transform()
модуля pandas
вызывают функцию func
для самостоятельного создания DataFrame
/Series
с той же формой оси, что и у исходного объекта.
Этот метод в основном используется с групповыми операциями (смотрите ниже)
Если возвращаемый DataFrame
имеет длину, отличную от исходного DataFrame
/Series
, то поднимается исключение ValueError
.
С обычным DataFrame
пользовательская функция func
метода DataFrame.transform()
принимает на вход столбец или строку (в зависимости от axis
) в виде Series
, и может работать со всеми элементами Series
, но не должна изменять форму DataFrame
.
>>> import pandas as pd >>> df = pd.DataFrame({'A': range(3), 'B': range(1, 4)}) >>> df # A B # 0 0 1 # 1 1 2 # 2 2 3 # принимает `Series` и может работать # со всеми элементами `Series` (`x.max()`) >>> df.transform(lambda x: x + x.max()) # A B # 0 2 4 # 1 3 5 # 2 4 6 # но не должна изменять форму `DataFrame` >>> df.transform(lambda x: x.max()) # Traceback (most recent call last): # ... # ValueError: Function did not transform # метод `.apply()` может менять форму `DataFrame` >>> df.apply(lambda x: x.max()) # A 2 # B 3 # dtype: int64
Несмотря на то, что результирующий DataFrame
должен иметь ту же длину, что и исходный, можно передать несколько функций:
>>> import numpy as np >>> s = pd.Series(range(3)) >>> s # 0 0 # 1 1 # 2 2 # dtype: int64 >>> s.transform([np.sqrt, np.exp]) # sqrt exp # 0 0.000000 1.000000 # 1 1.000000 2.718282 # 2 1.414214 7.389056
DataFrameGroupBy.transform()
Метод .transform()
также имеют объекты DataFrameGroupBy
и Resampler
. Здесь и проявляются главные отличия от метода .apply()
.
Здесь аргумент func
может быть:
Numba
с указанием engine='numba'
. (Для 'numba'
, функцию должна быть определена пользователем со значениями и индексом в качестве первого и второго аргументов соответственно. Индекс каждой группы будет передан пользовательской функции и опционально доступен для использования).Два основных различия между DataFrameGroupBy.apply()
и DataFrameGroupBy.transform()
в групповых операциях:
Входные данные для функции:
DataFrameGroupBy.apply()
неявно передает func
ВСЕ столбцы для каждой группы в виде DataFrame
,DataFrameGroupBy.transform()
неявно передает func
КАЖДЫЙ столбец для каждой группы по отдельности в виде Series
.Выходные данные функции:
DataFrameGroupBy.apply()
, может возвращать скаляр, или Series
, или DataFrame
(или массив numpy
, или даже список).DataFrameGroupBy.transform()
, должна возвращать последовательность (Series
, массив или список) той же длины, что и группа.Таким образом, функция DataFrameGroupBy.transform()
работает только с одной Series
за раз, а DataFrameGroupBy.apply()
работает сразу со всем DataFrame
.
Несколько примеров, чтобы понять о чем речь:
>>> import pandas as pd df = pd.DataFrame({'str':['one', 'one', 'two', 'two'], 'a':[4,5,1,3], 'b':[6,10,3,11]}) >>> df # str a b # 0 one 4 6 # 1 one 5 10 # 2 two 1 3 # 3 two 3 11
Создадим функцию, которая выводит тип неявно переданного объекта, а затем вызывает исключение, чтобы остановить выполнение.
def inspect(x): print(type(x)) raise
Теперь передадим функцию inspect()
соответствующим методам операции .groupby()
, и посмотрим, какой объект ей передается:
>>> df.groupby('str').apply(inspect, include_groups=False) # <class 'pandas.core.frame.DataFrame'> # Traceback (most recent call last): # ... >>> df.groupby('str').transform(inspect) # <class 'pandas.core.series.Series'> # Traceback (most recent call last): # ...
Таким образом, DataFrameGroupBy.transform()
разрешено работать только с одной Series
одновременно. Невозможно, чтобы DataFrameGroupBy.transform()
воздействовал на два столбца одновременно. Итак, если попытаться вычесть столбец a
из b
внутри пользовательской функции, то при преобразовании получим ошибку:
def subtract_two(x): return x['a'] - x['b'] >>> df.groupby('str').transform(subtract_two) # Traceback (most recent call last): # ... # KeyError: 'a'
Получаем ошибку KeyError
, т.к. pandas
пытается найти индекс a
, которого не существует. Эту операцию будет выполнена с помощью DataFrameGroupBy.apply()
, т.к. она работает со всем DataFrame
:
>>> df.groupby('str').apply(subtract_two, include_groups=False) # str # one 0 -2 # 1 -5 # two 2 -2 # 3 -8 # dtype: int64
DataFrameGroupBy.transform()
должен возвращать последовательность того же размера, что и группа.Другое отличие состоит в том, что .transform()
должен возвращать одномерную последовательность того же размера, что и группа. В данном конкретном случае каждая группа имеет две строки, поэтому преобразование должно возвращать последовательность из двух строк. Если этого не происходит, то выдается ошибка:
>>> import numpy as np def return_three(x): return np.array([1, 2, 3]) >>> df.groupby('str').transform(return_three) # Traceback (most recent call last): # ... # ValueError: Length mismatch: Expected axis has 7 elements, # new values have 4 elements # такая функция будет работать def rand_group_len(x): return np.random.rand(len(x)) >>> df.groupby('str').transform(rand_group_len) # a b # 0 0.113465 0.311376 # 1 0.917720 0.068328 # 2 0.180872 0.871791 # 3 0.163254 0.703114
DataFrameGroupBy.transform()
Если из пользовательская функция возвращает только один скаляр, то transform
будет использовать его для каждой строки в группе:
>>> df.groupby('str').transform(group_sum) # a b # 0 9 16 # 1 9 16 # 2 4 14 # 3 4 14 # по сравнения с groupby().apply() >>> df.groupby('str').apply(group_sum, include_groups=False) # a b # str # one 9 16 # two 4 14
Благодаря такому поведению можно посчитать количество элементов в каждой группе
df = pd.DataFrame({ "c": [1, 1, 1, 2, 2, 2, 2], "type": ["m", "n", "o", "m", "m", "n", "n"] }) >>> df # c type # 0 1 m # 1 1 n # 2 1 o # 3 2 m # 4 2 m # 5 2 n # 6 2 n >>> df['size'] = df.groupby('c')['type'].transform(len) >>> df # c type size # 0 1 m 3 # 1 1 n 3 # 2 1 o 3 # 3 2 m 4 # 4 2 m 4 # 5 2 n 4 # 6 2 n 4
Несмотря на то, что результирующий DataFrame
должен иметь ту же длину, что и входной DataFrame
, можно предоставить несколько входных функций:
>>> import numpy as np >>> s = pd.Series(range(3)) >>> s # 0 0 # 1 1 # 2 2 # dtype: int64 >>> s.transform([np.sqrt, np.exp]) # sqrt exp # 0 0.000000 1.000000 # 1 1.000000 2.718282 # 2 1.414214 7.389056
.transform()
Дополнительно смотрите примеры с
groupby.transform()
в материале "Примеры групповых операций вpandas
соStackOverflow
"
DataFrame
/Series
>>> import pandas as pd >>> df = pd.DataFrame({'A': range(3), 'B': range(1, 4)}) >>> df # A B # 0 0 1 # 1 1 2 # 2 2 3 >>> df.transform(lambda x: x + 1) # A B # 0 1 2 # 1 2 3 # 2 3 4
Несмотря на то, что результирующий DataFrame
должен иметь ту же длину, что и входной DataFrame
, можно предоставить несколько входных функций:
>>> import numpy as np >>> s = pd.Series(range(3)) >>> s # 0 0 # 1 1 # 2 2 # dtype: int64 >>> s.transform([np.sqrt, np.exp]) # sqrt exp # 0 0.000000 1.000000 # 1 1.000000 2.718282 # 2 1.414214 7.389056
DataFrameGroupBy
df = pd.DataFrame({ "Date": [ "2015-05-08", "2015-05-07", "2015-05-06", "2015-05-05", "2015-05-08", "2015-05-07", "2015-05-06", "2015-05-05"], "Data": [5, 8, 6, 1, 50, 100, 60, 120], }) >>> df # Date Data # 0 2015-05-08 5 # 1 2015-05-07 8 # 2 2015-05-06 6 # 3 2015-05-05 1 # 4 2015-05-08 50 # 5 2015-05-07 100 # 6 2015-05-06 60 # 7 2015-05-05 120 >>> df.groupby('Date')['Data'].transform('sum') # 0 55 # 1 108 # 2 66 # 3 121 # 4 55 # 5 108 # 6 66 # 7 121 # Name: Data, dtype: int64
df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar', 'foo', 'bar'], 'B' : ['one', 'one', 'two', 'three', 'two', 'two'], 'C' : [1, 5, 5, 2, 5, 5], 'D' : [2.0, 5., 8., 1., 2., 9.]}) >>> grouped = df.groupby('A')[['C', 'D']] >>> grouped.transform(lambda x: (x - x.mean()) / x.std()) # C D # 0 -1.154701 -0.577350 # 1 0.577350 0.000000 # 2 0.577350 1.154701 # 3 -1.154701 -1.000000 # 4 0.577350 -0.577350 # 5 0.577350 1.000000
Трансляция результата трансформации
>>> grouped.transform(lambda x: x.max() - x.min()) # C D # 0 4.0 6.0 # 1 3.0 8.0 # 2 4.0 6.0 # 3 3.0 8.0 # 4 4.0 6.0 # 5 3.0 8.0 >>> grouped.transform("mean") # C D # 0 3.666667 4.0 # 1 4.000000 5.0 # 2 3.666667 4.0 # 3 4.000000 5.0 # 4 3.666667 4.0 # 5 4.000000 5.0
Resampler
s = pd.Series([1, 2], index=pd.date_range('20180101', periods=2, freq='1h')) >>> s # 2018-01-01 00:00:00 1 # 2018-01-01 01:00:00 2 # Freq: h, dtype: int64 >>> resampled = s.resample('15min') >>> resampled.transform(lambda x: (x - x.mean()) / x.std()) # 2018-01-01 00:00:00 NaN # 2018-01-01 01:00:00 NaN # Freq: h, dtype: float64