DataFrame.apply()
VS DataFrame.map()
;DataFrame.apply()
вызывает пользовательскую функцию func
вдоль оси axis
;Series.apply()
вызывает пользовательскую функцию func
для значений Series
;DataFrameGroupBy.apply()
вызывает пользовательскую функцию func
для объекта группировки;DataFrame.map()
применяет пользовательскую функцию к DataFrame
поэлементно;Series.map()
заменяет каждое значение Series
, значением, которое может быть получено из функции, словаря или Series
. DataFrame.apply()
VS DataFrame.map()
Метод DataFrame.apply()
применяет пользовательскую функцию func
вдоль оси axis
, т. е. работает со всеми значениями столбца или строки. Другими словами, пользовательская функция принимает в качестве обязательного аргумента Series
- сразу все значения столбца или строки (в зависимости от аргумента axis
) и что-то с ними делает.
>>> import pandas as pd >>> import numpy as np >>> df = pd.DataFrame(np.random.randn(4, 3), columns=list('abc')) >>> df # a b c # 0 0.532464 2.241091 0.688753 # 1 -0.586088 0.111004 1.007061 # 2 0.205406 1.580366 0.609771 # 3 0.968165 1.196149 -0.727974 # определяем функцию, которая принимает `Series` >>> fn = lambda ser: ser.max() - ser.min() >>> df.apply(fn) # a 1.554252 # b 2.130087 # c 1.735035 # dtype: float64
При этом метод DataFrame.apply()
может также изменять каждый элемент, если передаваемая функция - является универсальной функцией numpy
:
>>> df = pd.DataFrame([[4, 9]] * 3, columns=['A', 'B']) >>> df # A B # 0 4 9 # 1 4 9 # 2 4 9 # например, `np.sqrt()` >>> df.apply(np.sqrt) # A B # 0 2.0 3.0 # 1 2.0 3.0 # 2 2.0 3.0
Метод DataFrame.map()
тупо применяет пользовательскую функцию к DataFrame
поэлементно. Другими словами, пользовательская функция принимает в качестве обязательного аргумента только один элемент DataFrame
(перебирает DataFrame
поэлементно) и что то с ним делает.
>>> df = pd.DataFrame([[1, 2.12], [3.356, 4.567]]) >>> df # 0 1 # 0 1.000 2.120 # 1 3.356 4.567 >>> df.map(lambda x: len(str(x))) # 0 1 # 0 3 4 # 1 5 5
Если пользовательская функция должна одновременно работать со всеми значениями строки или столбца, то однозначно используем метод DataFrame.apply()
. Например: lambda ser: ser.max() - ser.min()
Если функция должна быть применена к каждому элементу:
DataFrame
, то используем метод DataFrame.apply()
;DataFrame
, то используем метод DataFrame.map()
(ранее назывался DataFrame.applymap()
)DataFrame.apply(func, axis=0, raw=False, result_type=None, args=(), by_row='compat', **kwargs)
:Метод DataFrame.apply()
применяет пользовательскую функцию вдоль оси axis
к каждому столбцу или строке.
Объекты, передаваемые в пользовательскую функцию, являются объектами Series
, индекс которых является либо индексом DataFrame
(axis=0
), либо столбцами DataFrame
(axis=1
). По умолчанию (result_type=None
) окончательный тип возвращаемого значения выводится из типа возвращаемого значения применяемой функции. В противном случае это зависит от аргумента result_type
.
Принимаемые аргументы:
func
: пользовательская функция, применяемая к каждому столбцу или строке. axis
: ось, вдоль которой применяется функция:'index'
: применить функцию к каждому столбцу.'columns'
: применить функцию к каждой строке.raw
: определяет, передается ли строка или столбец как объект Series
или ndarray
:False
: передает в функцию каждую строку или столбец в виде серии.True
: переданная функция получит объекты numpy.ndarray
. Если применяется функция NumPy
, то это позволит добиться гораздо большей производительности.result_type
: действует только тогда, когда axis=1
(столбцы). Поведение по умолчанию (None
) зависит от возвращаемого значения прикладной функции: результаты, подобные списку, будут возвращены в виде их Series
. Однако, если DataFrame.apply()
возвращает серию, они расширяются до столбцов.'expand'
: результаты, подобные списку, будут преобразованы в столбцы.'reduce'
: по возможности возвращает ряд, а не расширяет результаты, подобные списку. Это противоположно 'expand'.'broadcast'
: результаты будут транслироваться в исходную форму фрейма данных, исходный индекс и столбцы будут сохранены.args
: кортеж с позиционными аргументами для передачи в func
в дополнение к Series
.by_row
: имеет эффект только тогда, аргумент func
не является строкой, а является списком или словарем вызываемых объектов. Если by_row='compat'
, то, по возможности, pandas переведет функцию в методы (например, Series().apply(np.sum)
будет переведен в Series().sum()
). Если это не сработает, попробует снова вызвать DataFrame.apply()
с помощью by_row=True
, а если это не удастся, то снова вызовет DataFrame.apply()
с помощью by_row=False
(обратная совместимость). Если значение равно False
, то функции будут переданы всей серии сразу.**kwargs
: дополнительные ключевые аргументы для передачи в func
.DataFrame.apply()
:Пользовательские функции, изменяющие переданный объект, могут приводить к непредвиденному поведению или ошибкам и не поддерживаются.
>>> import pandas as pd >>> import numpy as np >>> df = pd.DataFrame([[4, 9]] * 3, columns=['A', 'B']) >>> df # A B # 0 4 9 # 1 4 9 # 2 4 9
Используем универсальную функцию numpy
:
>>> df.apply(np.sqrt) # A B # 0 2.0 3.0 # 1 2.0 3.0 # 2 2.0 3.0
Использование групповой функции по осям axis
:
# суммирование значений столбца >>> df.apply(np.sum, axis=0) # A 12 # B 27 # dtype: int64 # суммирование значений строк столбцов >>> df.apply(np.sum, axis=1) # 0 13 # 1 13 # 2 13 # dtype: int64
Если пользовательская функция возвращает список, то это приведет к Series
>>> df.apply(lambda x: [1, 2], axis=1) # 0 [1, 2] # 1 [1, 2] # 2 [1, 2] # dtype: object
Передача result_type='expand'
расширит результаты в виде списка до столбцов DataFrame
>>> df.apply(lambda x: [1, 2], axis=1, result_type='expand') # 0 1 # 0 1 2 # 1 1 2 # 2 1 2
Внутри пользовательской функции можно возвращать Series
, что будет аналогично передаче result_type='expand'
. Результирующие имена столбцов будут индексом ряда.
>>> df.apply(lambda x: pd.Series([1, 2], index=['foo', 'bar']), axis=1) # foo bar # 0 1 2 # 1 1 2 # 2 1 2
Передача result_type='broadcast'
обеспечит один и тот же результат, независимо от того, будет ли функция возвращать список или скаляр, и транслировать его вдоль оси. Результирующие имена столбцов будут исходными.
>>> df.apply(lambda x: [1, 2], axis=1, result_type='broadcast') # A B # 0 1 2 # 1 1 2 # 2 1 2
Series.apply(func, convert_dtype=_NoDefault.no_default, args=(), *, by_row='compat', **kwargs)
:Метод Series.apply()
вызывает пользовательскую функцию func
для значений Series
. Если func
возвращает объект Series
, то результатом будет DataFrame
.
Принимаемые аргументы:
func
: функция может быть функцией |NumPy
|, которая применяется ко всему Series
или функция Python, которая работает только с одиночными значениями. convert_dtype
: находит лучший dtype
для результатов поэлементных функций. Если False
, то оставляет dtype=object
. Аргумент устарел, начиная с версии 2.1.0. Если необходимо применить convert_dtype=False
, то нужно выполнить ser.astype(object).apply()
.args
: кортеж с позиционными аргументами для передачи в func
после значений Series
.by_row
: имеет эффект только тогда, аргумент func
не является строкой, а является списком или словарем вызываемых объектов. Если by_row='compat'
, то, по возможности, pandas переведет функцию в методы (например, Series().apply(np.sum)
будет переведен в Series().sum()
). Если это не сработает, попробует снова вызвать Series.apply()
с помощью by_row=True
, а если это не удастся, то снова вызовет Series.apply()
с помощью by_row=False
(обратная совместимость). Если значение равно False
, то функции будут переданы всей серии сразу.**kwargs
: дополнительные ключевые аргументы для передачи в func
.Series.apply()
:Пользовательские функции, изменяющие переданный объект, могут приводить к непредвиденному поведению или ошибкам и не поддерживаются.
Создадим серию с летними температурами для городов.
>>> import pandas as pd >>> s = pd.Series([20, 21, 12], ... index=['London', 'New York', 'Helsinki']) >>> s # London 20 # New York 21 # Helsinki 12 # dtype: int64
Возведем значения в квадрат, определив пользовательскую функцию и передав ее в качестве аргумента.
>>> def square(x): ... return x ** 2 >>> s.apply(square) # London 400 # New York 441 # Helsinki 144 # dtype: int64
Возвести значения в квадрат, можно анонимной функцией lambda
.
>>> s.apply(lambda x: x ** 2) # London 400 # New York 441 # Helsinki 144 # dtype: int64
Определим пользовательскую функцию, которой требуются дополнительные позиционные аргументы, и передадим эти дополнительные аргументы с помощью ключевого аргумента args
.
>>> def subtract_custom_value(x, custom_value): ... return x - custom_value >>> s.apply(subtract_custom_value, args=(5,)) # London 15 # New York 16 # Helsinki 7 # dtype: int64
Определим пользовательскую функцию, которая принимает ключевые аргументы и передает эти аргументы для применения.
>>> def add_custom_values(x, **kwargs): ... for month in kwargs: ... x += kwargs[month] ... return x >>> s.apply(add_custom_values, june=30, july=20, august=25) # London 95 # New York 96 # Helsinki 87 # dtype: int64
Используем функцию из библиотеки |NumPy
|.
>>> s.apply(np.log) # London 2.995732 # New York 3.044522 # Helsinki 2.484907 # dtype: float64
DataFrameGroupBy.apply(func, *args, **kwargs)
:Метод DataFrameGroupBy.apply()
применяет функцию func
по группам и объединяет результаты.
Функция, передаваемая в DataFrameGroupBy.apply()
, должна принимать в качестве первого аргумента DataFrame
и возвращать DataFrame
, Series
или скаляр. Затем DataFrameGroupBy.apply()
позаботится об объединении результатов в один DataFrame
или Series
. Кроме того, вызываемый объект может принимать позиционные *args
и ключевые **kwargs
аргументы.
Таким образом, apply
является очень гибким методом группировки.
Хотя DataFrameGroupBy.apply()
- очень гибкий метод, недостатком является то, что его использование может быть немного медленнее, чем использование более конкретных методов, таких как .agg()
или .transform()
. Pandas предлагает широкий спектр методов, которые будут намного быстрее, чем использование .apply()
.
DataFrameGroupBy.apply()
:>>> import pandas as pd df = pd.DataFrame({'A': 'a a b'.split(), 'B': [1,2,3], 'C': [4,6,5]}) >>> g = df.groupby('A', group_keys=False)
Пример 1. Функция принимает DataFrame
в качестве аргумента и возвращает DataFrame
. Метод apply
объединяет результаты для каждой группы в новый DataFrame
:
>>> g[['B', 'C']].apply(lambda x: x / x.sum()) # B C # 0 0.333333 0.4 # 1 0.666667 0.6 # 2 1.000000 1.0
Пример 2. Функция принимает DataFrame
в качестве аргумента и возвращает Series
. Метод apply
объединяет результаты для каждой группы в новый DataFrame
.
>>> g[['B', 'C']].apply(lambda x: x.astype(float).max() - x.min()) # B C # A # a 1.0 2.0 # b 0.0 0.0
Пример 3. Функция принимает DataFrame
в качестве аргумента и возвращает скаляр. Метод apply
объединяет результаты для каждой группы в Series
, включая соответствующую установку индекса:
>>> g.apply(lambda x: x.C.max() - x.B.min()) # A # a 5 # b 2 # dtype: int64
DataFrame.map(func, na_action=None, **kwargs)
:Метод DataFrame.map()
применяет пользовательскую функцию к DataFrame
поэлементно. Этот метод применяет функцию func
, которая принимает и возвращает скаляр для каждого элемента DataFrame
.
Принимаемые аргументы:
func
- функция Python, которая принимает и возвращает скаляр для каждого элемента DataFrame
na_action=None
- если na_action='ignore'
, то распространяет значения NaN
, не передавая их в func
.**kwargs
- дополнительные ключевые аргументы для передачи в func
.DataFrame.map()
:>>> df = pd.DataFrame([[1, 2.12], [3.356, 4.567]]) >>> df # 0 1 # 0 1.000 2.120 # 1 3.356 4.567 >>> df.map(lambda x: len(str(x))) # 0 1 # 0 3 4 # 1 5 5
Как и в Series.map()
, значения NA
можно игнорировать:
>>> df_copy = df.copy() >>> df_copy.iloc[0, 0] = pd.NA >>> df_copy.map(lambda x: len(str(x)), na_action='ignore') # 0 1 # 0 NaN 4 # 1 5.0 5
Обратите внимание, что часто существует векторизованная версия функции, которая работает намного быстрее. Например, возведение каждого число в квадрат поэлементно.
# в этом случае лучше избегать `map`. >>> df.map(lambda x: x**2) # 0 1 # 0 1.000000 4.494400 # 1 11.262736 20.857489 # векторизованная версия функции >>> df ** 2 # 0 1 # 0 1.000000 4.494400 # 1 11.262736 20.857489
Series.map(arg, na_action=None)
:Метод Series.map()
используется для замены каждого значения в Series
другим значением, которое может быть получено из функции, словаря dict
или Series
.
Принимаемые аргументы:
arg
- функция, словарь dict
или Series
.na_action=None
- если na_action='ignore'
, то распространяет значения NaN
, не передавая их в func
.Внимание. Когда
arg
является словарем, то значения в последовательностях, которых нет в словаре (как ключей), преобразуются вNaN
. НО, если словарь является подклассомdict
, который определяет__missing__
(предоставляет метод для значений по умолчанию), то используется это значение по умолчанию, а неNaN
.
DataFrame.map()
:>>> s = pd.Series(['cat', 'dog', np.nan, 'rabbit']) >>> s # 0 cat # 1 dog # 2 NaN # 3 rabbit # dtype: object
Метод Series.map()
принимает dict
или Series
. Значения, которые не найдены в словаре, преобразуются в NaN
, если словарь не имеет значения по умолчанию (например, collections.defaultdict
):
>>> s.map({'cat': 'kitten', 'dog': 'puppy'}) # 0 kitten # 1 puppy # 2 NaN # 3 NaN # dtype: object
Аргумент arg
также принимает функцию:
>>> s.map('I am a {}'.format) # 0 I am a cat # 1 I am a dog # 2 I am a nan # 3 I am a rabbit # dtype: object
Чтобы избежать применения функции к отсутствующим значениям (и сохранить их как NaN
), можно использовать na_action='ignore'
.
>>> s.map('I am a {}'.format, na_action='ignore') # 0 I am a cat # 1 I am a dog # 2 NaN # 3 I am a rabbit # dtype: object