Сообщить об ошибке.

Метод .reindex() объектов Series/DataFrame в pandas

Фундаментальный методом выравнивания данных в pandas

Синтаксис:

df = DataFrame.reindex(labels=None, *, index=None, columns=None, 
                       axis=None, method=None, copy=None, level=None, 
                       fill_value=nan, limit=None, tolerance=None)

s = Series.reindex(index=None, *, method=None, copy=None, 
                   level=None, fill_value=None, 
                   limit=None, tolerance=None)

Параметры:

  • labels=None - новые метки/индекс для соответствия оси, заданной axis.

  • index=None - новые метки для индекса. Предпочтительно объект Index, чтобы избежать дублирования данных.

  • columns=None - новые метки для столбцов. Предпочтительно объект Index, чтобы избежать дублирования данных. Объект Series этот аргумент не использует.

  • axis=None - может быть либо именем оси 'index'/'columns', либо числом 0/1. Объект Series этот аргумент не использует.

  • method=None - метод, используемый для заполнения NA в переиндексированном DataFrame/Series. Обратите внимание: метод применим только к монотонно растущим/уменьшающимся индексам.

    • None (по умолчанию): не заполнять пробелы
    • pad/ffill: использовать последнее достоверное наблюдение для заполнения NA
    • backfill/bfill: использовать следующее достоверное наблюдение для заполнения NA.
    • nearest: использовать ближайшие достоверные наблюдения для заполнения NA.

  • copy=None - возвращает новый объект, даже если переданные индексы совпадают.

    Аргумент copy изменит поведение в pandas 3.0. Копирование при записи будет включено по умолчанию, а это означает, что все методы с аргументом copy будут использовать механизм отложенного копирования и игнорировать аргумент copy. Ключевой аргумент copy будет удален в будущей версии pandas. Можно уже сейчас получить будущее поведение и улучшения, включив копирование при записи pd.options.mode.copy_on_write = True

  • level=None - сопоставляет значения индекса на переданном уровне MultiIndex.

  • fill_value=nan - значение, используемое для отсутствующих значений. По умолчанию NaN, но может быть любым "совместимым" значением.

  • limit=None - максимальное количество последовательных элементов для method='ffill' или method='backfill' заполнения.

  • tolerance=None - максимальное расстояние между исходной и новой метками для неточного совпадения. Значения индекса в совпадающих местоположениях удовлетворяют уравнению abs(index[indexer] - target) <= tolerance.

    Значение аргумента tolerance может быть скалярным, которое применяет один и тот же допуск ко всем значениям, или объектом list-like, который применяет переменный допуск к каждому элементу. List-like объект включает список, кортеж, массив, серию и должен быть того же размера, что и индекс, а его dtype должен точно соответствовать типу индекса.

Возвращаемое значение:

  • DataFrame.reindex() возвращает DataFrame с измененным индексом.
  • Series.reindex() возвращает Series с измененным индексом.

Описание метода .reindex() объектов DataFrame/Series

Методы DataFrame.reindex() и Series.reindex() модуля pandas приводит DataFrame/Series соответственно в соответствии с новым индексом с дополнительной логикой заполнения.

Методы размещают NA/NaN в местоположениях, не имеющих значения в предыдущем индексе. Если новый индекс НЕ эквивалентен текущему и если аргумент copy=False, то создается новый объект.

И так, метод .reindex() является фундаментальным методом выравнивания данных в Pandas. Он используется для реализации почти всех других функций, основанных на функциональности выравнивания индексных меток. Повторная индексация означает согласование данных с заданным набором меток вдоль определенной оси. Это позволяет достичь нескольких целей:

  • Изменяет порядок существующих данных в соответствии с новым набором меток
  • Вставляет маркеры отсутствующих значений (NA) в места записей, где не существовало данных для этой метки
  • Если необходимо, заполнит данные для отсутствующих меток с помощью логики (очень важно для работы с данными временных рядов)

Простой пример:

>>> import pandas as pd
>>> import numpy as np
>>> s = pd.Series(np.random.randn(5), index=["a", "b", "c", "d", "e"])
>>> s
# a    0.458235
# b   -0.886856
# c   -1.757924
# d   -0.481370
# e    0.993616
# dtype: float64

>>> s.reindex(["e", "b", "f", "d"])
# e    0.993616
# b   -0.886856
# f         NaN
# d   -0.481370
# dtype: float64

Здесь метка f не содержалась в исходной Series и, следовательно, в результате заполнилась NaN.

С помощью DataFrame можно одновременно переиндексировать индекс и столбцы:

df = pd.DataFrame(
    {
        "one": pd.Series(np.random.randn(3), index=["a", "b", "c"]),
        "two": pd.Series(np.random.randn(4), index=["a", "b", "c", "d"]),
        "three": pd.Series(np.random.randn(3), index=["b", "c", "d"]),
    }
)
>>> df
#         one       two     three
# a  0.782362 -0.318496       NaN
# b -1.332115  0.363552 -0.481942
# c -0.241576 -1.004899  1.792289
# d       NaN  0.113503  0.648034

>>> df.reindex(index=["c", "f", "b"], columns=["three", "two", "one"])
#       three       two       one
# c  1.792289 -1.004899 -0.241576
# f       NaN       NaN       NaN
# b -0.481942  0.363552 -1.332115

Обратите внимание, что объекты Index, содержащие фактические метки осей, могут быть общими для всех объектов. Таким образом, если есть Series и DataFrame, то можно сделать следующее:

>>> rs = s.reindex(df.index)
>>> rs
# a    0.458235
# b   -0.886856
# c   -1.757924
# d   -0.481370
# dtype: float64

>>> rs.index is df.index
# True

Это означает, что индекс переиндексированного Series является тем же объектом Python, что и индекс DataFrame.

DataFrame.reindex() также поддерживает соглашение о вызовах в стиле axis, где указывается один аргумент labels и ось axis, к которой он относится.

>>> df.reindex(["c", "f", "b"], axis="index")
#         one       two     three
# c -0.241576 -1.004899  1.792289
# f       NaN       NaN       NaN
# b -1.332115  0.363552 -0.481942

>>> df.reindex(["three", "two", "one"], axis="columns")
#       three       two       one
# a       NaN -0.318496  0.782362
# b -0.481942  0.363552 -1.332115
# c  1.792289 -1.004899 -0.241576
# d  0.648034  0.113503       NaN

Заметка. При написании кода, чувствительного к производительности, следует учитывать, что многие операции выполняются быстрее с предварительно выровненными данными. При добавлении двух невыровненных DataFrames внутренне запускается шаг переиндексации. Для исследовательского анализа разница вряд ли будет заметна (т.к. переиндексация была сильно оптимизирована), но когда циклы ЦП имеют значение, то несколько явных вызовов повторной индексации здесь и там могут оказать влияние.

Переиндексация для выравнивания по другому объекту

Можно взять объект и переиндексировать его оси так, чтобы они были помечены так же, как и другой объект. Несмотря на то, что синтаксис для этого прост, хотя и многословен, это достаточно распространенная операция, поэтому для упрощения этой операции доступен метод .reindex_like() :

>>> df2 = df.reindex(["a", "b", "c"], columns=["one", "two"])
>>> df.reindex_like(df2)
#         one       two
# a  0.782362 -0.318496
# b -1.332115  0.363552
# c -0.241576 -1.004899

Заполнение значений при расширении индекса

Метод .reindex() принимает необязательный аргумент method, который является методом заполнения:

Принимаемые значения:

  • pad/ffill - заполнение значений вперед
  • bfill/backfill - заполнение значений в обратном направлении
  • nearest - заполнение от ближайшего значения индекса

Эти методы требуют, чтобы индексы были упорядочены в порядке увеличения или уменьшения.

Смотрим поведение этих методы заполнения на Series:

>>> rng = pd.date_range("1/3/2000", periods=8)
>>> ts = pd.Series(np.random.randn(8), index=rng)
>>> ts2 = ts.iloc[[0, 3, 6]]

>>> ts
# 2000-01-03   -0.553267
# 2000-01-04    0.715464
# 2000-01-05    0.020540
# 2000-01-06   -0.309617
# 2000-01-07    1.163681
# 2000-01-08   -0.963053
# 2000-01-09    0.883835
# 2000-01-10   -2.165508
# Freq: D, dtype: float64

>>> ts2
# 2000-01-03   -0.553267
# 2000-01-06   -0.309617
# 2000-01-09    0.883835
# Freq: 3D, dtype: float64

>>> ts2.reindex(ts.index)
# 2000-01-03   -0.553267
# 2000-01-04         NaN
# 2000-01-05         NaN
# 2000-01-06   -0.309617
# 2000-01-07         NaN
# 2000-01-08         NaN
# 2000-01-09    0.883835
# 2000-01-10         NaN
# Freq: D, dtype: float64

>>> ts2.reindex(ts.index, method="ffill")
# 2000-01-03   -0.553267
# 2000-01-04   -0.553267
# 2000-01-05   -0.553267
# 2000-01-06   -0.309617
# 2000-01-07   -0.309617
# 2000-01-08   -0.309617
# 2000-01-09    0.883835
# 2000-01-10    0.883835
# Freq: D, dtype: float64

>>> ts2.reindex(ts.index, method="bfill")
# 2000-01-03   -0.553267
# 2000-01-04   -0.309617
# 2000-01-05   -0.309617
# 2000-01-06   -0.309617
# 2000-01-07    0.883835
# 2000-01-08    0.883835
# 2000-01-09    0.883835
# 2000-01-10         NaN
# Freq: D, dtype: float64

>>> ts2.reindex(ts.index, method="nearest")
# 2000-01-03   -0.553267
# 2000-01-04   -0.553267
# 2000-01-05   -0.309617
# 2000-01-06   -0.309617
# 2000-01-07   -0.309617
# 2000-01-08    0.883835
# 2000-01-09    0.883835
# 2000-01-10    0.883835
# Freq: D, dtype: float64

Обратите внимание, что тот же результат можно было бы получить с помощью метода .ffill() (за исключением method='nearest') или интерполяции:

>>> ts2.reindex(ts.index).ffill()
# 2000-01-03   -0.553267
# 2000-01-04   -0.553267
# 2000-01-05   -0.553267
# 2000-01-06   -0.309617
# 2000-01-07   -0.309617
# 2000-01-08   -0.309617
# 2000-01-09    0.883835
# 2000-01-10    0.883835
# Freq: D, dtype: float64

Лимиты на заполнение при переиндексации

Аргументы limit и tolerance обеспечивают дополнительный контроль над заполнением при повторной индексации. Лимит определяет максимальное количество последовательных совпадений:

>>> ts2.reindex(ts.index, method="ffill", limit=1)
# 2000-01-03   -0.553267
# 2000-01-04   -0.553267
# 2000-01-05         NaN
# 2000-01-06   -0.309617
# 2000-01-07   -0.309617
# 2000-01-08         NaN
# 2000-01-09    0.883835
# 2000-01-10    0.883835
# Freq: D, dtype: float64

Напротив, аргумент tolerance задает максимальное расстояние между значениями индекса и индексатора:

>>> ts2.reindex(ts.index, method="ffill", tolerance="1 day")
# 2000-01-03   -0.553267
# 2000-01-04   -0.553267
# 2000-01-05         NaN
# 2000-01-06   -0.309617
# 2000-01-07   -0.309617
# 2000-01-08         NaN
# 2000-01-09    0.883835
# 2000-01-10    0.883835
# Freq: D, dtype: float64

Обратите внимание, что при использовании для pandas.DatetimeIndex, pandas.TimedeltaIndex или pandas.PeriodIndex аргумент tolerance будет приведен к pandas.Timedelta, если это возможно. Это позволяет указать допуск с соответствующими строками.

Примеры использования метода DataFrame.reindex()

Метод DataFrame.reindex() поддерживает два соглашения о вызовах:

  • DataFrame.reindex(index=index_labels, columns=column_labels, ...);
  • DataFrame.reindex(labels, axis={'index', 'columns'}, ...).

Настоятельно рекомендуется использовать ключевые аргументы, для ясности намерений.

Для большинства примеров будем использовать фрейм данных следующего вида:

>>> import pandas as pd
>>> index = ['Firefox', 'Chrome', 'Safari', 'IE10', 'Konqueror']
df = pd.DataFrame({'http_status': [200, 200, 404, 404, 301],
                  'response_time': [0.04, 0.02, 0.07, 0.08, 1.0]},
                  index=index)
>>> df
#            http_status  response_time
# Firefox            200           0.04
# Chrome             200           0.02
# Safari             404           0.07
# IE10               404           0.08
# Konqueror          301           1.00

Создаем новый индекс и переиндексируем DataFrame. По умолчанию, значениям в новом индексе, не имеющим соответствующих записей в кадре данных, присваивается значение NaN.

>>> new_index = ['Safari', 'Iceweasel', 'Comodo Dragon', 'IE10', 'Chrome']
>>> df.reindex(new_index)
#                http_status  response_time
# Safari               404.0           0.07
# Iceweasel              NaN            NaN
# Comodo Dragon          NaN            NaN
# IE10                 404.0           0.08
# Chrome               200.0           0.02

Можно восполнить пропущенные значения, передав значение ключевому аргументу fill_value. Так как индекс не увеличивается и не уменьшается, следовательно нельзя использовать ключевой аргумент method для заполнения значений NaN.

>>> df.reindex(new_index, fill_value=0)
#                http_status  response_time
# Safari                 404           0.07
# Iceweasel                0           0.00
# Comodo Dragon            0           0.00
# IE10                   404           0.08
# Chrome                 200           0.02

>>> df.reindex(new_index, fill_value='missing')
#               http_status response_time
# Safari                404          0.07
# Iceweasel         missing       missing
# Comodo Dragon     missing       missing
# IE10                  404          0.08
# Chrome                200          0.02

Также можно переиндексировать столбцы.

>>> df.reindex(columns=['http_status', 'user_agent'])
#            http_status  user_agent
# Firefox            200         NaN
# Chrome             200         NaN
# Safari             404         NaN
# IE10               404         NaN
# Konqueror          301         NaN

Или можно использовать ключевой аргумент axis="columns".

>>> df.reindex(['http_status', 'user_agent'], axis="columns")
#            http_status  user_agent
# Firefox            200         NaN
# Chrome             200         NaN
# Safari             404         NaN
# IE10               404         NaN
# Konqueror          301         NaN

Чтобы дополнительно показать функциональность заполнения отсутствующих значений в переиндексации, создадим DataFrame с монотонно увеличивающимся индексом (например, последовательность дат).

>>> date_index = pd.date_range('1/1/2010', periods=6, freq='D')
df2 = pd.DataFrame({"prices": [100, 101, np.nan, 100, 89, 88]},
                   index=date_index)
>>> df2
#             prices
# 2024-01-01   100.0
# 2024-01-02   101.0
# 2024-01-03     NaN
# 2024-01-04   100.0
# 2024-01-05    89.0
# 2024-01-06    88.0

Предположим, что нужно расширить фрейм данных, чтобы охватить более широкий диапазон дат.

# создадим новый индекс
>>> date_index2 = pd.date_range('12/29/2009', periods=10, freq='D')
# переиндексируем
>>> df2.reindex(date_index2)
#             prices
# 2023-12-29     NaN
# 2023-12-30     NaN
# 2023-12-31     NaN
# 2024-01-01   100.0
# 2024-01-02   101.0
# 2024-01-03     NaN
# 2024-01-04   100.0
# 2024-01-05    89.0
# 2024-01-06    88.0
# 2024-01-07     NaN

Записи индекса, которые не имели значения в исходном фрейме данных (например, '2023-12-29'), по умолчанию заполняются NaN. При желании можно заполнить недостающие значения, используя один из нескольких вариантов.

Например, чтобы распространить последнее допустимое значение для заполнения значений NaN, используем аргумент method со значением bfill.

>>> df2.reindex(date_index2, method='bfill')
#             prices
# 2009-12-29   100.0
# 2009-12-30   100.0
# 2009-12-31   100.0
# 2010-01-01   100.0
# 2010-01-02   101.0
# 2010-01-03     NaN
# 2010-01-04   100.0
# 2010-01-05    89.0
# 2010-01-06    88.0
# 2010-01-07     NaN

Обратите внимание, что значение NaN, присутствующее в исходном DataFrame (при значении индекса 2024-01-03), не будет заполнено ни одной из схем распространения значений. Это связано с тем, что заполнение при повторной индексации не рассматривает значения исходного DataFrame, а только сравнивает исходный и требуемый индексы. Если необходимо заполнить значения NaN, присутствующие в исходном DataFrame, необходимо использовать метод DataFrame.fillna().