Spec-Zone.ru › pandas 2

Индексирование и выбор данных

Информация об обозначении осей в объектах pandas служит многим целям:

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

  • Обеспечивает автоматическое и явное выравнивание данных.

  • Позволяет интуитивно получать и устанавливать подмножества набора данных.

В этом разделе мы сосредоточимся на последнем пункте: как нарезать, делить и в целом получать и устанавливать подмножества объектов pandas. Основное внимание будет уделено Series и DataFrame, так как они получили больше внимания в этом аспекте.

Примечание

Операторы индексирования Python и NumPy [] и оператор атрибутов . обеспечивают быстрый и простой доступ к структурам данных pandas в широком диапазоне случаев использования. Это делает интерактивную работу интуитивной, так как для работы с Python словарями и массивами NumPy нужно немного нового узнать. Однако, поскольку тип данных, к которому осуществляется доступ, заранее неизвестен, прямое использование стандартных операторов имеет некоторые ограничения оптимизации. Для производственного кода рекомендуется использовать оптимизированные методы доступа к данным pandas, представленные в этой главе.

Предупреждение

Возвращается ли копия или ссылка при операции установки, может зависеть от контекста. Это иногда называется chained assignment и следует избегать. См. Возвращение представления или копии.

См. Многоуровневый индекс/Расширенное индексирование для MultiIndex и более подробной документации по расширенному индексированию.

См. справочник для некоторых продвинутых стратегий.

Различные варианты индексирования

Выбор объектов получил ряд запросов от пользователей для поддержки более явного индексирования по местоположению. pandas теперь поддерживает три типа многоосевого индексирования.

  • .loc в основном основан на метках, но также может использоваться с булевым массивом. .loc вызовет KeyError при отсутствии элементов. Допустимые входные данные:

    • Одна метка, например 5 или 'a' (Обратите внимание, что 5 интерпретируется как метка индекса. Это использование не является целочисленной позицией вдоль индекса.).

    • Список или массив меток ['a', 'b', 'c'].

    • Объект среза с метками 'a':'f' (Обратите внимание, что в отличие от обычных Python срезов, оба начала и конца включаются, когда они присутствуют в индексе! См. Срез с метками и Концы включены.)

    • Булевой массив (любые NA значения будут обрабатываться как False).

    • Функция callable с одним аргументом (вызываемая Series или DataFrame) и возвращающая допустимый результат для индексирования (один из вышеперечисленных).

    • Кортеж индексов строк (и столбцов), элементы которых являются одним из вышеперечисленных входных данных.

    См. подробности в Выбор по метке.

  • .iloc в основном основан на целочисленной позиции (от 0 до length-1 оси), но также может использоваться с булевым массивом. .iloc вызовет IndexError если запрашиваемый индексатор выходит за пределы границ, за исключением среза индексаторов, которые позволяют индексирование за пределами границ. (Это соответствует семантике Python/NumPy среза). Допустимые входные данные:

    • Целое число, например 5.

    • Список или массив целых чисел [4, 3, 0].

    • Объект среза с целыми числами 1:7.

    • Булевой массив (любые NA значения будут обрабатываться как False).

    • Функция callable с одним аргументом (вызываемая Series или DataFrame) и возвращающая допустимый результат для индексирования (один из вышеперечисленных).

    • Кортеж индексов строк (и столбцов), элементы которых являются одним из вышеперечисленных входных данных.

    См. дополнительные сведения в Выбор по позиции, Расширенное индексирование и Расширенное иерархическое.

  • .loc, .iloc, а также индексирование [] может принимать callable в качестве индексатора. См. дополнительные сведения в Выбор по вызываемому объекту.

    Примечание

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

Получение значений из объекта с многоосевым выбором использует следующий формат (используя .loc в качестве примера, но следующее также относится к .iloc). Любой из аксессоров осей может быть нулевым срезом :. Оси, не указанные в спецификации, предполагаются :, например, p.loc['a'] эквивалентно p.loc['a', :].

Тип объекта

Индексаторы

Series

s.loc[indexer]

DataFrame

df.loc[row_indexer,column_indexer]

Основы

Как упоминалось при представлении структур данных в последнем разделе, основная функция индексирования с помощью [] (также __getitem__ для тех, кто знаком с реализацией поведения класса в Python) заключается в выборе срезов более низкой размерности. В следующей таблице показаны возвращаемые типы значений при индексировании объектов pandas с помощью []:

Тип объекта

Выбор

Тип возвращаемого значения

Series

series[label]

скалярное значение

DataFrame

frame[colname]

Series соответствующий имени столбца

Здесь мы создаем простой временной ряд данных для иллюстрации функциональности индексирования:

In [1]: dates = pd.date_range('1/1/2000', periods=8)

In [2]: df = pd.DataFrame(np.random.randn(8, 4),
   ...:                   index=dates, columns=['A', 'B', 'C', 'D'])
   ...: 

In [3]: df
Out[3]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885

Примечание

Ни одна из функций индексирования не является специфичной для временных рядов, если не указано иное.

Таким образом, как указано выше, мы имеем самое базовое индексирование с помощью []:

In [4]: s = df['A']

In [5]: s[dates[5]]
Out[5]: -0.6736897080883706

Вы можете передать список столбцов в [] для выбора столбцов в указанном порядке. Если столбец не содержится в DataFrame, будет возбуждено исключение. Несколько столбцов также можно задать таким образом:

In [6]: df
Out[6]: 
                   A         B         C         D
2000-01-01  0.469112 -0.282863 -1.509059 -1.135632
2000-01-02  1.212112 -0.173215  0.119209 -1.044236
2000-01-03 -0.861849 -2.104569 -0.494929  1.071804
2000-01-04  0.721555 -0.706771 -1.039575  0.271860
2000-01-05 -0.424972  0.567020  0.276232 -1.087401
2000-01-06 -0.673690  0.113648 -1.478427  0.524988
2000-01-07  0.404705  0.577046 -1.715002 -1.039268
2000-01-08 -0.370647 -1.157892 -1.344312  0.844885

In [7]: df[['B', 'A']] = df[['A', 'B']]

In [8]: df
Out[8]: 
                   A         B         C         D
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804
2000-01-04 -0.706771  0.721555 -1.039575  0.271860
2000-01-05  0.567020 -0.424972  0.276232 -1.087401
2000-01-06  0.113648 -0.673690 -1.478427  0.524988
2000-01-07  0.577046  0.404705 -1.715002 -1.039268
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885

Это может быть полезно для применения преобразования (в том числе и in-place) к подмножеству столбцов.

Предупреждение

pandas выравнивает все ОСИ при установке Series и DataFrame из .loc.

Это не изменит df, поскольку выравнивание столбцов выполняется до присвоения значений.

In [9]: df[['A', 'B']]
Out[9]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

In [10]: df.loc[:, ['B', 'A']] = df[['A', 'B']]

In [11]: df[['A', 'B']]
Out[11]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

Правильный способ обмена значениями столбцов — использование исходных значений:

In [12]: df.loc[:, ['B', 'A']] = df[['A', 'B']].to_numpy()

In [13]: df[['A', 'B']]
Out[13]: 
                   A         B
2000-01-01  0.469112 -0.282863
2000-01-02  1.212112 -0.173215
2000-01-03 -0.861849 -2.104569
2000-01-04  0.721555 -0.706771
2000-01-05 -0.424972  0.567020
2000-01-06 -0.673690  0.113648
2000-01-07  0.404705  0.577046
2000-01-08 -0.370647 -1.157892

Однако, pandas не выравнивает оси при установке Series и DataFrame из .iloc , потому что .iloc работает по позиции.

Это изменит df , так как выравнивание столбцов не выполняется до присвоения значений.

In [14]: df[['A', 'B']]
Out[14]: 
                   A         B
2000-01-01  0.469112 -0.282863
2000-01-02  1.212112 -0.173215
2000-01-03 -0.861849 -2.104569
2000-01-04  0.721555 -0.706771
2000-01-05 -0.424972  0.567020
2000-01-06 -0.673690  0.113648
2000-01-07  0.404705  0.577046
2000-01-08 -0.370647 -1.157892

In [15]: df.iloc[:, [1, 0]] = df[['A', 'B']]

In [16]: df[['A','B']]
Out[16]: 
                   A         B
2000-01-01 -0.282863  0.469112
2000-01-02 -0.173215  1.212112
2000-01-03 -2.104569 -0.861849
2000-01-04 -0.706771  0.721555
2000-01-05  0.567020 -0.424972
2000-01-06  0.113648 -0.673690
2000-01-07  0.577046  0.404705
2000-01-08 -1.157892 -0.370647

Доступ к атрибутам

Вы можете получить доступ к индексу в Series или столбцу в DataFrame напрямую как к атрибуту:

In [17]: sa = pd.Series([1, 2, 3], index=list('abc'))

In [18]: dfa = df.copy()
In [19]: sa.b
Out[19]: 2

In [20]: dfa.A
Out[20]: 
2000-01-01   -0.282863
2000-01-02   -0.173215
2000-01-03   -2.104569
2000-01-04   -0.706771
2000-01-05    0.567020
2000-01-06    0.113648
2000-01-07    0.577046
2000-01-08   -1.157892
Freq: D, Name: A, dtype: float64
In [21]: sa.a = 5

In [22]: sa
Out[22]: 
a    5
b    2
c    3
dtype: int64

In [23]: dfa.A = list(range(len(dfa.index)))  # ok if A already exists

In [24]: dfa
Out[24]: 
            A         B         C         D
2000-01-01  0  0.469112 -1.509059 -1.135632
2000-01-02  1  1.212112  0.119209 -1.044236
2000-01-03  2 -0.861849 -0.494929  1.071804
2000-01-04  3  0.721555 -1.039575  0.271860
2000-01-05  4 -0.424972  0.276232 -1.087401
2000-01-06  5 -0.673690 -1.478427  0.524988
2000-01-07  6  0.404705 -1.715002 -1.039268
2000-01-08  7 -0.370647 -1.344312  0.844885

In [25]: dfa['A'] = list(range(len(dfa.index)))  # use this form to create a new column

In [26]: dfa
Out[26]: 
            A         B         C         D
2000-01-01  0  0.469112 -1.509059 -1.135632
2000-01-02  1  1.212112  0.119209 -1.044236
2000-01-03  2 -0.861849 -0.494929  1.071804
2000-01-04  3  0.721555 -1.039575  0.271860
2000-01-05  4 -0.424972  0.276232 -1.087401
2000-01-06  5 -0.673690 -1.478427  0.524988
2000-01-07  6  0.404705 -1.715002 -1.039268
2000-01-08  7 -0.370647 -1.344312  0.844885

Предупреждение

  • Вы можете использовать этот доступ только если элемент индекса является допустимым идентификатором Python, например, s.1 не разрешено. См. здесь объяснение допустимых идентификаторов.

  • Атрибут не будет доступен, если он конфликтует с существующим именем метода, например, s.min не разрешено, но s['min'] возможно.

  • Аналогично, атрибут не будет доступен, если он конфликтует с любым из следующего списка: index, major_axis, minor_axis, items.

  • В любом из этих случаев стандартная индексация по-прежнему будет работать, например, s['1'], s['min'], и s['index'] будут обращаться к соответствующему элементу или столбцу.

Если вы используете среду IPython, вы также можете использовать автодополнение, чтобы увидеть эти доступные атрибуты.

Вы также можете присвоить dict строке DataFrame:

In [27]: x = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 4, 5]})

In [28]: x.iloc[1] = {'x': 9, 'y': 99}

In [29]: x
Out[29]: 
   x   y
0  1   3
1  9  99
2  3   5

Вы можете использовать доступ к атрибутам для изменения существующего элемента Series или столбца DataFrame, но будьте осторожны; если вы попытаетесь использовать доступ к атрибутам для создания нового столбца, он создаст новый атрибут, а не новый столбец, и это вызовет UserWarning:

In [30]: df_new = pd.DataFrame({'one': [1., 2., 3.]})

In [31]: df_new.two = [4, 5, 6]

In [32]: df_new
Out[32]: 
   one
0  1.0
1  2.0
2  3.0

Диапазоны срезов

Самый надежный и последовательный способ срезов по произвольным осям описан в разделе Выбор по позиции, описывающем метод .iloc. Пока что мы объясняем семантику срезов с использованием оператора [].

Для Series синтаксис работает точно так же, как с ndarray, возвращая срез значений и соответствующих меток:

In [33]: s[:5]
Out[33]: 
2000-01-01    0.469112
2000-01-02    1.212112
2000-01-03   -0.861849
2000-01-04    0.721555
2000-01-05   -0.424972
Freq: D, Name: A, dtype: float64

In [34]: s[::2]
Out[34]: 
2000-01-01    0.469112
2000-01-03   -0.861849
2000-01-05   -0.424972
2000-01-07    0.404705
Freq: 2D, Name: A, dtype: float64

In [35]: s[::-1]
Out[35]: 
2000-01-08   -0.370647
2000-01-07    0.404705
2000-01-06   -0.673690
2000-01-05   -0.424972
2000-01-04    0.721555
2000-01-03   -0.861849
2000-01-02    1.212112
2000-01-01    0.469112
Freq: -1D, Name: A, dtype: float64

Обратите внимание, что присвоение также работает:

In [36]: s2 = s.copy()

In [37]: s2[:5] = 0

In [38]: s2
Out[38]: 
2000-01-01    0.000000
2000-01-02    0.000000
2000-01-03    0.000000
2000-01-04    0.000000
2000-01-05    0.000000
2000-01-06   -0.673690
2000-01-07    0.404705
2000-01-08   -0.370647
Freq: D, Name: A, dtype: float64

Для DataFrame срез внутри [] вырезает строки. Это в основном предоставляется для удобства, поскольку это такая распространенная операция.

In [39]: df[:3]
Out[39]: 
                   A         B         C         D
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804

In [40]: df[::-1]
Out[40]: 
                   A         B         C         D
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885
2000-01-07  0.577046  0.404705 -1.715002 -1.039268
2000-01-06  0.113648 -0.673690 -1.478427  0.524988
2000-01-05  0.567020 -0.424972  0.276232 -1.087401
2000-01-04 -0.706771  0.721555 -1.039575  0.271860
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804
2000-01-02 -0.173215  1.212112  0.119209 -1.044236
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632

Выбор по метке

Предупреждение

Возвращается ли копия или ссылка при операции присвоения, может зависеть от контекста. Это иногда называется chained assignment и следует избегать. См. Возвращение представления или копии.

Предупреждение

.loc строго относится к слайсерам, которые несовместимы (или не могут быть преобразованы) с типом индекса. Например, при использовании целых чисел в DatetimeIndex. Это вызовет TypeError.

In [41]: dfl = pd.DataFrame(np.random.randn(5, 4),
   ....:                    columns=list('ABCD'),
   ....:                    index=pd.date_range('20130101', periods=5))
   ....: 

In [42]: dfl
Out[42]: 
                   A         B         C         D
2013-01-01  1.075770 -0.109050  1.643563 -1.469388
2013-01-02  0.357021 -0.674600 -1.776904 -0.968914
2013-01-03 -1.294524  0.413738  0.276662 -0.472035
2013-01-04 -0.013960 -0.362543 -0.006154 -0.923061
2013-01-05  0.895717  0.805244 -1.206412  2.565646

In [43]: dfl.loc[2:3]
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[43], line 1
----> 1 dfl.loc[2:3]

File ~/work/pandas/pandas/pandas/core/indexing.py:1191, in _LocationIndexer.__getitem__(self, key)
   1189 maybe_callable = com.apply_if_callable(key, self.obj)
   1190 maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable)
-> 1191 return self._getitem_axis(maybe_callable, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexing.py:1411, in _LocIndexer._getitem_axis(self, key, axis)
   1409 if isinstance(key, slice):
   1410     self._validate_key(key, axis)
-> 1411     return self._get_slice_axis(key, axis=axis)
   1412 elif com.is_bool_indexer(key):
   1413     return self._getbool_axis(key, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexing.py:1443, in _LocIndexer._get_slice_axis(self, slice_obj, axis)
   1440     return obj.copy(deep=False)
   1442 labels = obj._get_axis(axis)
-> 1443 indexer = labels.slice_indexer(slice_obj.start, slice_obj.stop, slice_obj.step)
   1445 if isinstance(indexer, slice):
   1446     return self.obj._slice(indexer, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:682, in DatetimeIndex.slice_indexer(self, start, end, step)
    674 # GH#33146 if start and end are combinations of str and None and Index is not
    675 # monotonic, we can not use Index.slice_indexer because it does not honor the
    676 # actual elements, is only searching for start and end
    677 if (
    678     check_str_or_none(start)
    679     or check_str_or_none(end)
    680     or self.is_monotonic_increasing
    681 ):
--> 682     return Index.slice_indexer(self, start, end, step)
    684 mask = np.array(True)
    685 in_index = True

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6662, in Index.slice_indexer(self, start, end, step)
   6618 def slice_indexer(
   6619     self,
   6620     start: Hashable | None = None,
   6621     end: Hashable | None = None,
   6622     step: int | None = None,
   6623 ) -> slice:
   6624     """
   6625     Compute the slice indexer for input labels and step.
   6626 
   (...)
   6660     slice(1, 3, None)
   6661     """
-> 6662     start_slice, end_slice = self.slice_locs(start, end, step=step)
   6664     # return a slice
   6665     if not is_scalar(start_slice):

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6879, in Index.slice_locs(self, start, end, step)
   6877 start_slice = None
   6878 if start is not None:
-> 6879     start_slice = self.get_slice_bound(start, "left")
   6880 if start_slice is None:
   6881     start_slice = 0

File ~/work/pandas/pandas/pandas/core/indexes/base.py:6794, in Index.get_slice_bound(self, label, side)
   6790 original_label = label
   6792 # For datetime indices label may be a string that has to be converted
   6793 # to datetime boundary according to its resolution.
-> 6794 label = self._maybe_cast_slice_bound(label, side)
   6796 # we need to look up the label
   6797 try:

File ~/work/pandas/pandas/pandas/core/indexes/datetimes.py:642, in DatetimeIndex._maybe_cast_slice_bound(self, label, side)
    637 if isinstance(label, dt.date) and not isinstance(label, dt.datetime):
    638     # Pandas supports slicing with dates, treated as datetimes at midnight.
    639     # https://github.com/pandas-dev/pandas/issues/31501
    640     label = Timestamp(label).to_pydatetime()
--> 642 label = super()._maybe_cast_slice_bound(label, side)
    643 self._data._assert_tzawareness_compat(label)
    644 return Timestamp(label)

File ~/work/pandas/pandas/pandas/core/indexes/datetimelike.py:378, in DatetimeIndexOpsMixin._maybe_cast_slice_bound(self, label, side)
    376     return lower if side == "left" else upper
    377 elif not isinstance(label, self._data._recognized_scalars):
--> 378     self._raise_invalid_indexer("slice", label)
    380 return label

File ~/work/pandas/pandas/pandas/core/indexes/base.py:4301, in Index._raise_invalid_indexer(self, form, key, reraise)
   4299 if reraise is not lib.no_default:
   4300     raise TypeError(msg) from reraise
-> 4301 raise TypeError(msg)

TypeError: cannot do slice indexing on DatetimeIndex with these indexers [2] of type int

Строки в срезе могут быть преобразованы в тип индекса и привести к естественному срезу.

In [44]: dfl.loc['20130102':'20130104']
Out[44]: 
                   A         B         C         D
2013-01-02  0.357021 -0.674600 -1.776904 -0.968914
2013-01-03 -1.294524  0.413738  0.276662 -0.472035
2013-01-04 -0.013960 -0.362543 -0.006154 -0.923061

pandas предоставляет набор методов для чисто метко-ориентированной индексации. Это протокол строгого включения. Каждая запрашиваемая метка должна быть в индексе, в противном случае будет поднято KeyError. При срезе оба граничные значения И конечное граничное значение включаются, если они присутствуют в индексе. Целые числа являются допустимыми метками, но они ссылаются на метку, а не на позицию.

Атрибут .loc является основным методом доступа. Допустимыми входными данными являются:

  • Одна метка, например, 5 или 'a' (Обратите внимание, что 5 интерпретируется как метка индекса. Это использование не является целочисленной позицией вдоль индекса.).

  • Список или массив меток ['a', 'b', 'c'].

  • Объект среза с метками 'a':'f' (Обратите внимание, что в отличие от обычных Python-срезов, обе начальная и конечная точки включаются, если они присутствуют в индексе! См. Срез по меткам.

  • Булевый массив.

  • callable, см. Выбор по вызываемому объекту.

In [45]: s1 = pd.Series(np.random.randn(6), index=list('abcdef'))

In [46]: s1
Out[46]: 
a    1.431256
b    1.340309
c   -1.170299
d   -0.226169
e    0.410835
f    0.813850
dtype: float64

In [47]: s1.loc['c':]
Out[47]: 
c   -1.170299
d   -0.226169
e    0.410835
f    0.813850
dtype: float64

In [48]: s1.loc['b']
Out[48]: 1.3403088497993827

Обратите внимание, что присвоение также работает:

In [49]: s1.loc['c':] = 0

In [50]: s1
Out[50]: 
a    1.431256
b    1.340309
c    0.000000
d    0.000000
e    0.000000
f    0.000000
dtype: float64

Для DataFrame:

In [51]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list('abcdef'),
   ....:                    columns=list('ABCD'))
   ....: 

In [52]: df1
Out[52]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
b  1.130127 -1.436737 -1.413681  1.607920
c  1.024180  0.569605  0.875906 -2.211372
d  0.974466 -2.006747 -0.410001 -0.078638
e  0.545952 -1.219217 -1.226825  0.769804
f -1.281247 -0.727707 -0.121306 -0.097883

In [53]: df1.loc[['a', 'b', 'd'], :]
Out[53]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
b  1.130127 -1.436737 -1.413681  1.607920
d  0.974466 -2.006747 -0.410001 -0.078638

Доступ через срез меток:

In [54]: df1.loc['d':, 'A':'C']
Out[54]: 
          A         B         C
d  0.974466 -2.006747 -0.410001
e  0.545952 -1.219217 -1.226825
f -1.281247 -0.727707 -0.121306

Для получения поперечного сечения по метке (эквивалентно df.xs('a')):

In [55]: df1.loc['a']
Out[55]: 
A    0.132003
B   -0.827317
C   -0.076467
D   -1.187678
Name: a, dtype: float64

Для получения значений с помощью булевого массива:

In [56]: df1.loc['a'] > 0
Out[56]: 
A     True
B    False
C    False
D    False
Name: a, dtype: bool

In [57]: df1.loc[:, df1.loc['a'] > 0]
Out[57]: 
          A
a  0.132003
b  1.130127
c  1.024180
d  0.974466
e  0.545952
f -1.281247

Значения NA в булевом массиве распространяются как False:

In [58]: mask = pd.array([True, False, True, False, pd.NA, False], dtype="boolean")

In [59]: mask
Out[59]: 
<BooleanArray>
[True, False, True, False, <NA>, False]
Length: 6, dtype: boolean

In [60]: df1[mask]
Out[60]: 
          A         B         C         D
a  0.132003 -0.827317 -0.076467 -1.187678
c  1.024180  0.569605  0.875906 -2.211372

Для явного получения значения:

# this is also equivalent to ``df1.at['a','A']``
In [61]: df1.loc['a', 'A']
Out[61]: 0.13200317033032932

Срез по меткам

При использовании .loc со срезами, если обе метки начала и конца присутствуют в индексе, то возвращаются элементы, расположенные между ними (включая их):

In [62]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4])

In [63]: s.loc[3:5]
Out[63]: 
3    b
2    c
5    d
dtype: object

Если хотя бы одна из двух отсутствует, но индекс отсортирован и может быть сравнен с метками начала и конца, тогда срез все равно будет работать как ожидается, выбирая метки, которые ранжируются между ними:

In [64]: s.sort_index()
Out[64]: 
0    a
2    c
3    b
4    e
5    d
dtype: object

In [65]: s.sort_index().loc[1:6]
Out[65]: 
2    c
3    b
4    e
5    d
dtype: object

Однако, если хотя бы одна из двух отсутствует и индекс не отсортирован, будет поднята ошибка (поскольку в противном случае вычисление будет дорогостоящим и потенциально неоднозначным для индексов смешанного типа). Например, в приведенном выше примере s.loc[1:6] вызовет KeyError.

Обоснование этого поведения см. в Конечные точки включены.

In [66]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2])

In [67]: s.loc[3:5]
Out[67]: 
3    b
2    c
5    d
dtype: object

Также, если индекс имеет дублирующие метки и метка начала или конца дублируется, будет поднята ошибка. Например, в приведенном выше примере s.loc[2:5] вызовет KeyError.

Дополнительную информацию о дублирующих метках см. в Дублирующие метки.

Выбор по позиции

Предупреждение

Возвращаемый при операции установки объект — копия или ссылка — может зависеть от контекста. Это иногда называется выбором по позиции и следует избегать. См. Возвращение представления или копии.

pandas предоставляет набор методов для получения индексирования на основе целых чисел. Семантика тесно следует Python и срезам NumPy. Это индексирование по позиции. При срезах начальная граница включается, а верхняя граница исключается. Попытка использовать нецелое число, даже валидный индекс, вызовет ошибку.

Атрибут __getitem__ — основной метод доступа. Допустимыми входными данными являются:

  • Целое число, например, 0.

  • Список или массив целых чисел [0, 1, 2].

  • Объект среза с целыми числами 0:2.

  • Массив булевых значений.

  • Функция, см. Выбор по вызываемому объекту.

  • Кортеж индексов строк (и столбцов), элементы которого являются одним из указанных выше типов.

In [68]: s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2)))

In [69]: s1
Out[69]: 
0    0.695775
2    0.341734
4    0.959726
6   -1.110336
8   -0.619976
dtype: float64

In [70]: s1.iloc[:3]
Out[70]: 
0    0.695775
2    0.341734
4    0.959726
dtype: float64

In [71]: s1.iloc[3]
Out[71]: -1.110336102891167

Обратите внимание, что установка также работает:

In [72]: s1.iloc[:3] = 0

In [73]: s1
Out[73]: 
0    0.000000
2    0.000000
4    0.000000
6   -1.110336
8   -0.619976
dtype: float64

Для DataFrame:

In [74]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list(range(0, 12, 2)),
   ....:                    columns=list(range(0, 8, 2)))
   ....: 

In [75]: df1
Out[75]: 
           0         2         4         6
0   0.149748 -0.732339  0.687738  0.176444
2   0.403310 -0.154951  0.301624 -2.179861
4  -1.369849 -0.954208  1.462696 -1.743161
6  -0.826591 -0.345352  1.314232  0.690579
8   0.995761  2.396780  0.014871  3.357427
10 -0.317441 -1.236269  0.896171 -0.487602

Выбор через срез с целыми числами:

In [76]: df1.iloc[:3]
Out[76]: 
          0         2         4         6
0  0.149748 -0.732339  0.687738  0.176444
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161

In [77]: df1.iloc[1:5, 2:4]
Out[77]: 
          4         6
2  0.301624 -2.179861
4  1.462696 -1.743161
6  1.314232  0.690579
8  0.014871  3.357427

Выбор через список целых чисел:

In [78]: df1.iloc[[1, 3, 5], [1, 3]]
Out[78]: 
           2         6
2  -0.154951 -2.179861
6  -0.345352  0.690579
10 -1.236269 -0.487602
In [79]: df1.iloc[1:3, :]
Out[79]: 
          0         2         4         6
2  0.403310 -0.154951  0.301624 -2.179861
4 -1.369849 -0.954208  1.462696 -1.743161
In [80]: df1.iloc[:, 1:3]
Out[80]: 
           2         4
0  -0.732339  0.687738
2  -0.154951  0.301624
4  -0.954208  1.462696
6  -0.345352  1.314232
8   2.396780  0.014871
10 -1.236269  0.896171
# this is also equivalent to ``df1.iat[1,1]``
In [81]: df1.iloc[1, 1]
Out[81]: -0.1549507744249032

Для получения поперечного среза с помощью целочисленной позиции (эквивалентно iloc):

In [82]: df1.iloc[1]
Out[82]: 
0    0.403310
2   -0.154951
4    0.301624
6   -2.179861
Name: 2, dtype: float64

Индексы срезов, выходящие за пределы допустимого диапазона, обрабатываются так же, как в Python/NumPy.

# these are allowed in Python/NumPy.
In [83]: x = list('abcdef')

In [84]: x
Out[84]: ['a', 'b', 'c', 'd', 'e', 'f']

In [85]: x[4:10]
Out[85]: ['e', 'f']

In [86]: x[8:10]
Out[86]: []

In [87]: s = pd.Series(x)

In [88]: s
Out[88]: 
0    a
1    b
2    c
3    d
4    e
5    f
dtype: object

In [89]: s.iloc[4:10]
Out[89]: 
4    e
5    f
dtype: object

In [90]: s.iloc[8:10]
Out[90]: Series([], dtype: object)

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

In [91]: dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB'))

In [92]: dfl
Out[92]: 
          A         B
0 -0.082240 -2.182937
1  0.380396  0.084844
2  0.432390  1.519970
3 -0.493662  0.600178
4  0.274230  0.132885

In [93]: dfl.iloc[:, 2:3]
Out[93]: 
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]

In [94]: dfl.iloc[:, 1:3]
Out[94]: 
          B
0 -2.182937
1  0.084844
2  1.519970
3  0.600178
4  0.132885

In [95]: dfl.iloc[4:6]
Out[95]: 
         A         B
4  0.27423  0.132885

Единственный индексатор, выходящий за пределы границ, вызовет ошибку индексирования. Список индексаторов, где любой элемент выходит за пределы границ, вызовет ошибку.

In [96]: dfl.iloc[[4, 5, 6]]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
File ~/work/pandas/pandas/pandas/core/indexing.py:1714, in _iLocIndexer._get_list_axis(self, key, axis)
   1713 try:
-> 1714     return self.obj._take_with_is_copy(key, axis=axis)
   1715 except IndexError as err:
   1716     # re-raise with different error message, e.g. test_getitem_ndarray_3d

File ~/work/pandas/pandas/pandas/core/generic.py:4153, in NDFrame._take_with_is_copy(self, indices, axis)
   4144 """
   4145 Internal version of the `take` method that sets the `_is_copy`
   4146 attribute to keep track of the parent dataframe (using in indexing
   (...)
   4151 See the docstring of `take` for full explanation of the parameters.
   4152 """
-> 4153 result = self.take(indices=indices, axis=axis)
   4154 # Maybe set copy if we didn't actually change the index.

File ~/work/pandas/pandas/pandas/core/generic.py:4133, in NDFrame.take(self, indices, axis, **kwargs)
   4129     indices = np.arange(
   4130         indices.start, indices.stop, indices.step, dtype=np.intp
   4131     )
-> 4133 new_data = self._mgr.take(
   4134     indices,
   4135     axis=self._get_block_manager_axis(axis),
   4136     verify=True,
   4137 )
   4138 return self._constructor_from_mgr(new_data, axes=new_data.axes).__finalize__(
   4139     self, method="take"
   4140 )

File ~/work/pandas/pandas/pandas/core/internals/managers.py:891, in BaseBlockManager.take(self, indexer, axis, verify)
    890 n = self.shape[axis]
--> 891 indexer = maybe_convert_indices(indexer, n, verify=verify)
    893 new_labels = self.axes[axis].take(indexer)

File ~/work/pandas/pandas/pandas/core/indexers/utils.py:282, in maybe_convert_indices(indices, n, verify)
    281     if mask.any():
--> 282         raise IndexError("indices are out-of-bounds")
    283 return indices

IndexError: indices are out-of-bounds

The above exception was the direct cause of the following exception:

IndexError                                Traceback (most recent call last)
Cell In[96], line 1
----> 1 dfl.iloc[[4, 5, 6]]

File ~/work/pandas/pandas/pandas/core/indexing.py:1191, in _LocationIndexer.__getitem__(self, key)
   1189 maybe_callable = com.apply_if_callable(key, self.obj)
   1190 maybe_callable = self._check_deprecated_callable_usage(key, maybe_callable)
-> 1191 return self._getitem_axis(maybe_callable, axis=axis)

File ~/work/pandas/pandas/pandas/core/indexing.py:1743, in _iLocIndexer._getitem_axis(self, key, axis)
   1741 # a list of integers
   1742 elif is_list_like_indexer(key):
-> 1743     return self._get_list_axis(key, axis=axis)
   1745 # a single integer
   1746 else:
   1747     key = item_from_zerodim(key)

File ~/work/pandas/pandas/pandas/core/indexing.py:1717, in _iLocIndexer._get_list_axis(self, key, axis)
   1714     return self.obj._take_with_is_copy(key, axis=axis)
   1715 except IndexError as err:
   1716     # re-raise with different error message, e.g. test_getitem_ndarray_3d
-> 1717     raise IndexError("positional indexers are out-of-bounds") from err

IndexError: positional indexers are out-of-bounds
In [97]: dfl.iloc[:, 4]
---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
Cell In[97], line 1
----> 1 dfl.iloc[:, 4]

File ~/work/pandas/pandas/pandas/core/indexing.py:1184, in _LocationIndexer.__getitem__(self, key)
   1182     if self._is_scalar_access(key):
   1183         return self.obj._get_value(*key, takeable=self._takeable)
-> 1184     return self._getitem_tuple(key)
   1185 else:
   1186     # we by definition only have the 0th axis
   1187     axis = self.axis or 0

File ~/work/pandas/pandas/pandas/core/indexing.py:1690, in _iLocIndexer._getitem_tuple(self, tup)
   1689 def _getitem_tuple(self, tup: tuple):
-> 1690     tup = self._validate_tuple_indexer(tup)
   1691     with suppress(IndexingError):
   1692         return self._getitem_lowerdim(tup)

File ~/work/pandas/pandas/pandas/core/indexing.py:966, in _LocationIndexer._validate_tuple_indexer(self, key)
    964 for i, k in enumerate(key):
    965     try:
--> 966         self._validate_key(k, i)
    967     except ValueError as err:
    968         raise ValueError(
    969             "Location based indexing can only have "
    970             f"[{self._valid_types}] types"
    971         ) from err

File ~/work/pandas/pandas/pandas/core/indexing.py:1592, in _iLocIndexer._validate_key(self, key, axis)
   1590     return
   1591 elif is_integer(key):
-> 1592     self._validate_integer(key, axis)
   1593 elif isinstance(key, tuple):
   1594     # a tuple should already have been caught by this point
   1595     # so don't treat a tuple as a valid indexer
   1596     raise IndexingError("Too many indexers")

File ~/work/pandas/pandas/pandas/core/indexing.py:1685, in _iLocIndexer._validate_integer(self, key, axis)
   1683 len_axis = len(self.obj._get_axis(axis))
   1684 if key >= len_axis or key < -len_axis:
-> 1685     raise IndexError("single positional indexer is out-of-bounds")

IndexError: single positional indexer is out-of-bounds

Выбор по вызываемому объекту

Индексирование по позиции, iloc, и также __getitem__ индексирование могут принимать вызываемый объект в качестве индексатора. Вызываемый объект должен быть функцией с одним аргументом (вызываемый Series или DataFrame), возвращающим допустимый результат для индексирования.

Примечание

Для индексирования по вызываемому объекту не поддерживается возвращение кортежа из вызываемого объекта, так как деструктуризация кортежа для индексов строк и столбцов происходит до применения вызываемых объектов.

In [98]: df1 = pd.DataFrame(np.random.randn(6, 4),
   ....:                    index=list('abcdef'),
   ....:                    columns=list('ABCD'))
   ....: 

In [99]: df1
Out[99]: 
          A         B         C         D
a -0.023688  2.410179  1.450520  0.206053
b -0.251905 -2.213588  1.063327  1.266143
c  0.299368 -0.863838  0.408204 -1.048089
d -0.025747 -0.988387  0.094055  1.262731
e  1.289997  0.082423 -0.055758  0.536580
f -0.489682  0.369374 -0.034571 -2.484478

In [100]: df1.loc[lambda df: df['A'] > 0, :]
Out[100]: 
          A         B         C         D
c  0.299368 -0.863838  0.408204 -1.048089
e  1.289997  0.082423 -0.055758  0.536580

In [101]: df1.loc[:, lambda df: ['A', 'B']]
Out[101]: 
          A         B
a -0.023688  2.410179
b -0.251905 -2.213588
c  0.299368 -0.863838
d -0.025747 -0.988387
e  1.289997  0.082423
f -0.489682  0.369374

In [102]: df1.iloc[:, lambda df: [0, 1]]
Out[102]: 
          A         B
a -0.023688  2.410179
b -0.251905 -2.213588
c  0.299368 -0.863838
d -0.025747 -0.988387
e  1.289997  0.082423
f -0.489682  0.369374

In [103]: df1[lambda df: df.columns[0]]
Out[103]: 
a   -0.023688
b   -0.251905
c    0.299368
d   -0.025747
e    1.289997
f   -0.489682
Name: A, dtype: float64

Можно использовать индексирование по вызываемому объекту в цепочках операций.

In [104]: df1['A'].loc[lambda s: s > 0]
Out[104]: 
c    0.299368
e    1.289997
Name: A, dtype: float64

Используя эти методы/индексаторы, вы можете цепочкой объединять операции выбора данных без использования временной переменной.

In [105]: bb = pd.read_csv('data/baseball.csv', index_col='id')

In [106]: (bb.groupby(['year', 'team']).sum(numeric_only=True)
   .....:    .loc[lambda df: df['r'] > 100])
   .....: 
Out[106]: 
           stint    g    ab    r    h  X2b  ...     so   ibb   hbp    sh    sf  gidp
year team                                   ...                                     
2007 CIN       6  379   745  101  203   35  ...  127.0  14.0   1.0   1.0  15.0  18.0
     DET       5  301  1062  162  283   54  ...  176.0   3.0  10.0   4.0   8.0  28.0
     HOU       4  311   926  109  218   47  ...  212.0   3.0   9.0  16.0   6.0  17.0
     LAN      11  413  1021  153  293   61  ...  141.0   8.0   9.0   3.0   8.0  29.0
     NYN      13  622  1854  240  509  101  ...  310.0  24.0  23.0  18.0  15.0  48.0
     SFN       5  482  1305  198  337   67  ...  188.0  51.0   8.0  16.0   6.0  41.0
     TEX       2  198   729  115  200   40  ...  140.0   4.0   5.0   2.0   8.0  16.0
     TOR       4  459  1408  187  378   96  ...  265.0  16.0  12.0   4.0  16.0  38.0

[8 rows x 18 columns]

Сочетание индексирования по позиции и метке

Если вы хотите получить 0-й и 2-й элементы из индекса в столбце ‘A’, вы можете сделать следующее:

In [107]: dfd = pd.DataFrame({'A': [1, 2, 3],
   .....:                     'B': [4, 5, 6]},
   .....:                    index=list('abc'))
   .....: 

In [108]: dfd
Out[108]: 
   A  B
a  1  4
b  2  5
c  3  6

In [109]: dfd.loc[dfd.index[[0, 2]], 'A']
Out[109]: 
a    1
c    3
Name: A, dtype: int64

Это также можно выразить с помощью индексации, явно получая местоположения в индексаторах и используя индексирование по позиции для выбора элементов.

In [110]: dfd.iloc[[0, 2], dfd.columns.get_loc('A')]
Out[110]: 
a    1
c    3
Name: A, dtype: int64

Для получения нескольких индексаторов используйте iloc:

In [111]: dfd.iloc[[0, 2], dfd.columns.get_indexer(['A', 'B'])]
Out[111]: 
   A  B
a  1  4
c  3  6

Переиндексация

Стандартный способ достижения выбора потенциально отсутствующих элементов — через reindex. См. также раздел о переиндексации.

In [112]: s = pd.Series([1, 2, 3])

In [113]: s.reindex([1, 2, 3])
Out[113]: 
1    2.0
2    3.0
3    NaN
dtype: float64

В качестве альтернативы, если вы хотите выбрать только валидные ключи, следующее — стандартный и эффективный метод; он гарантирует сохранение типа данных выбранных элементов.

In [114]: labels = [1, 2, 3]

In [115]: s.loc[s.index.intersection(labels)]
Out[115]: 
1    2
2    3
dtype: int64

Если индекс дублируется, будет поднята ошибка ValueError:

In [116]: s = pd.Series(np.arange(4), index=['a', 'a', 'b', 'c'])

In [117]: labels = ['c', 'd']

In [118]: s.reindex(labels)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[118], line 1
----> 1 s.reindex(labels)

File ~/work/pandas/pandas/pandas/core/series.py:5153, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
   5136 @doc(
   5137     NDFrame.reindex,  # type: ignore[has-type]
   5138     klass=_shared_doc_kwargs["klass"],
   (...)
   5151     tolerance=None,
   5152 ) -> Series:
-> 5153     return super().reindex(
   5154         index=index,
   5155         method=method,
   5156         copy=copy,
   5157         level=level,
   5158         fill_value=fill_value,
   5159         limit=limit,
   5160         tolerance=tolerance,
   5161     )

File ~/work/pandas/pandas/pandas/core/generic.py:5610, in NDFrame.reindex(self, labels, index, columns, axis, method, copy, level, fill_value, limit, tolerance)
   5607     return self._reindex_multi(axes, copy, fill_value)
   5609 # perform the reindex on the axes
-> 5610 return self._reindex_axes(
   5611     axes, level, limit, tolerance, method, fill_value, copy
   5612 ).__finalize__(self, method="reindex")

File ~/work/pandas/pandas/pandas/core/generic.py:5633, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
   5630     continue
   5632 ax = self._get_axis(a)
-> 5633 new_index, indexer = ax.reindex(
   5634     labels, level=level, limit=limit, tolerance=tolerance, method=method
   5635 )
   5637 axis = self._get_axis_number(a)
   5638 obj = obj._reindex_with_indexers(
   5639     {axis: [new_index, indexer]},
   5640     fill_value=fill_value,
   5641     copy=copy,
   5642     allow_dups=False,
   5643 )

File ~/work/pandas/pandas/pandas/core/indexes/base.py:4429, in Index.reindex(self, target, method, level, limit, tolerance)
   4426     raise ValueError("cannot handle a non-unique multi-index!")
   4427 elif not self.is_unique:
   4428     # GH#42568
-> 4429     raise ValueError("cannot reindex on an axis with duplicate labels")
   4430 else:
   4431     indexer, _ = self.get_indexer_non_unique(target)

ValueError: cannot reindex on an axis with duplicate labels

В общем случае вы можете пересечь желаемые метки с текущей осью и затем переиндексировать.

In [119]: s.loc[s.index.intersection(labels)].reindex(labels)
Out[119]: 
c    3.0
d    NaN
dtype: float64

Однако, это все еще вызовет ошибку, если полученный индекс дублируется.

In [120]: labels = ['a', 'd']

In [121]: s.loc[s.index.intersection(labels)].reindex(labels)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[121], line 1
----> 1 s.loc[s.index.intersection(labels)].reindex(labels)

File ~/work/pandas/pandas/pandas/core/series.py:5153, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
   5136 @doc(
   5137     NDFrame.reindex,  # type: ignore[has-type]
   5138     klass=_shared_doc_kwargs["klass"],
   (...)
   5151     tolerance=None,
   5152 ) -> Series:
-> 5153     return super().reindex(
   5154         index=index,
   5155         method=method,
   5156         copy=copy,
   5157         level=level,
   5158         fill_value=fill_value,
   5159         limit=limit,
   5160         tolerance=tolerance,
   5161     )

File ~/work/pandas/pandas/pandas/core/generic.py:5610, in NDFrame.reindex(self, labels, index, columns, axis, method, copy, level, fill_value, limit, tolerance)
   5607     return self._reindex_multi(axes, copy, fill_value)
   5609 # perform the reindex on the axes
-> 5610 return self._reindex_axes(
   5611     axes, level, limit, tolerance, method, fill_value, copy
   5612 ).__finalize__(self, method="reindex")

File ~/work/pandas/pandas/pandas/core/generic.py:5633, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
   5630     continue
   5632 ax = self._get_axis(a)
-> 5633 new_index, indexer = ax.reindex(
   5634     labels, level=level, limit=limit, tolerance=tolerance, method=method
   5635 )
   5637 axis = self._get_axis_number(a)
   5638 obj = obj._reindex_with_indexers(
   5639     {axis: [new_index, indexer]},
   5640     fill_value=fill_value,
   5641     copy=copy,
   5642     allow_dups=False,
   5643 )

File ~/work/pandas/pandas/pandas/core/indexes/base.py:4429, in Index.reindex(self, target, method, level, limit, tolerance)
   4426     raise ValueError("cannot handle a non-unique multi-index!")
   4427 elif not self.is_unique:
   4428     # GH#42568
-> 4429     raise ValueError("cannot reindex on an axis with duplicate labels")
   4430 else:
   4431     indexer, _ = self.get_indexer_non_unique(target)

ValueError: cannot reindex on an axis with duplicate labels

Выборка случайных образцов

Случайный выбор строк или столбцов из Series или DataFrame с помощью метода sample. Метод по умолчанию выбирает строки и принимает определенное количество строк/столбцов для возвращения или долю строк.

In [122]: s = pd.Series([0, 1, 2, 3, 4, 5])

# When no arguments are passed, returns 1 row.
In [123]: s.sample()
Out[123]: 
4    4
dtype: int64

# One may specify either a number of rows:
In [124]: s.sample(n=3)
Out[124]: 
0    0
4    4
1    1
dtype: int64

# Or a fraction of the rows:
In [125]: s.sample(frac=0.5)
Out[125]: 
5    5
3    3
1    1
dtype: int64

По умолчанию, sample вернет каждую строку не более одного раза, но также можно выполнять выборку с заменой, используя опцию replace=True:

In [126]: s = pd.Series([0, 1, 2, 3, 4, 5])

# Without replacement (default):
In [127]: s.sample(n=6, replace=False)
Out[127]: 
0    0
1    1
5    5
3    3
2    2
4    4
dtype: int64

# With replacement:
In [128]: s.sample(n=6, replace=True)
Out[128]: 
0    0
4    4
3    3
2    2
4    4
4    4
dtype: int64

По умолчанию каждая строка имеет равную вероятность быть выбранной, но если вы хотите, чтобы у строк были разные вероятности, вы можете передать функцию весов выборки в виде weights. Эти веса могут быть списком, массивом NumPy или Series, но они должны иметь такую же длину, как и объект, из которого вы выбираете. Пропущенные значения будут обрабатываться как вес нуль, а значения inf недопустимы. Если сумма весов не равна 1, они будут перенормированы путем деления всех весов на сумму весов. Например:

In [129]: s = pd.Series([0, 1, 2, 3, 4, 5])

In [130]: example_weights = [0, 0, 0.2, 0.2, 0.2, 0.4]

In [131]: s.sample(n=3, weights=example_weights)
Out[131]: 
5    5
4    4
3    3
dtype: int64

# Weights will be re-normalized automatically
In [132]: example_weights2 = [0.5, 0, 0, 0, 0, 0]

In [133]: s.sample(n=1, weights=example_weights2)
Out[133]: 
0    0
dtype: int64

При применении к DataFrame можно использовать столбец DataFrame в качестве весов выборки (при условии, что вы выбираете строки, а не столбцы), просто передав имя столбца в виде строки.

In [134]: df2 = pd.DataFrame({'col1': [9, 8, 7, 6],
   .....:                     'weight_column': [0.5, 0.4, 0.1, 0]})
   .....: 

In [135]: df2.sample(n=3, weights='weight_column')
Out[135]: 
   col1  weight_column
1     8            0.4
0     9            0.5
2     7            0.1

sample также позволяет пользователям выбирать столбцы вместо строк, используя аргумент axis='columns'.

In [136]: df3 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})

In [137]: df3.sample(n=1, axis=1)
Out[137]: 
   col1
0     1
1     2
2     3

Наконец, можно установить seed для генератора случайных чисел random_state с помощью аргумента random_state, который будет принимать либо целое число (в качестве seed), либо объект NumPy RandomState.

In [138]: df4 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})

# With a given seed, the sample will always draw the same rows.
In [139]: df4.sample(n=2, random_state=2)
Out[139]: 
   col1  col2
2     3     4
1     2     3

In [140]: df4.sample(n=2, random_state=2)
Out[140]: 
   col1  col2
2     3     4
1     2     3

Установка с расширением

Операции установки могут производить расширение при установке несуществующего ключа для данной оси.

В случае DataFrame это фактически операция добавления.

In [141]: se = pd.Series([1, 2, 3])

In [142]: se
Out[142]: 
0    1
1    2
2    3
dtype: int64

In [143]: se[5] = 5.

In [144]: se
Out[144]: 
0    1.0
1    2.0
2    3.0
5    5.0
dtype: float64

Series может быть расширен по любой оси с помощью reindex.

In [145]: dfi = pd.DataFrame(np.arange(6).reshape(3, 2),
   .....:                    columns=['A', 'B'])
   .....: 

In [146]: dfi
Out[146]: 
   A  B
0  0  1
1  2  3
2  4  5

In [147]: dfi.loc[:, 'C'] = dfi.loc[:, 'A']

In [148]: dfi
Out[148]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4

Это похоже на операцию добавления на Series.

In [149]: dfi.loc[3] = 5

In [150]: dfi
Out[150]: 
   A  B  C
0  0  1  0
1  2  3  2
2  4  5  4
3  5  5  5

Быстрый доступ и установка скалярных значений

Поскольку индексирование должно обрабатывать множество случаев (доступ к одному значению по метке, срезы, индексирование по булевым значениям и т. д.), оно имеет некоторую издержки для определения того, что вы хотите получить. Если вы хотите получить только скалярное значение, самым быстрым способом является использование методов at и iat, которые реализованы во всех структурах данных.

Аналогично at, iat предоставляет поиск скалярных значений по меткам, в то время как iat предоставляет поиск по целочисленным значениям, аналогично iloc

In [151]: s.iat[5]
Out[151]: 5

In [152]: df.at[dates[5], 'A']
Out[152]: 0.1136484096888855

In [153]: df.iat[3, 0]
Out[153]: -0.7067711336300845

Вы также можете установить значения с помощью этих же индексаторов.

In [154]: df.at[dates[5], 'E'] = 7

In [155]: df.iat[3, 0] = 7

at и iat могут расширить объект на месте, как описано выше, если индексатор отсутствует.

In [156]: df.at[dates[-1] + pd.Timedelta('1 day'), 0] = 7

In [157]: df
Out[157]: 
                   A         B         C         D    E    0
2000-01-01 -0.282863  0.469112 -1.509059 -1.135632  NaN  NaN
2000-01-02 -0.173215  1.212112  0.119209 -1.044236  NaN  NaN
2000-01-03 -2.104569 -0.861849 -0.494929  1.071804  NaN  NaN
2000-01-04  7.000000  0.721555 -1.039575  0.271860  NaN  NaN
2000-01-05  0.567020 -0.424972  0.276232 -1.087401  NaN  NaN
2000-01-06  0.113648 -0.673690 -1.478427  0.524988  7.0  NaN
2000-01-07  0.577046  0.404705 -1.715002 -1.039268  NaN  NaN
2000-01-08 -1.157892 -0.370647 -1.344312  0.844885  NaN  NaN
2000-01-09       NaN       NaN       NaN       NaN  NaN  7.0
END_OF_DOCUMENT_MARKER

Индексация с булевыми значениями

Ещё одна распространённая операция — использование булевых векторов для фильтрации данных. Операторы: | для or, & для and, и ~ для not. Эти операторы обязательно должны быть сгруппированы с помощью скобок, так как по умолчанию Python будет вычислять выражение, например, df['A'] > 2 & df['B'] < 3 как df['A'] > (2 & df['B']) < 3, в то время как нужный порядок вычислений — (df['A'] > 2) & (df['B'] < 3).

Использование булевого вектора для индексации Series работает точно так же, как и в NumPy ndarray:

In [158]: s = pd.Series(range(-3, 4))

In [159]: s
Out[159]: 
0   -3
1   -2
2   -1
3    0
4    1
5    2
6    3
dtype: int64

In [160]: s[s > 0]
Out[160]: 
4    1
5    2
6    3
dtype: int64

In [161]: s[(s < -1) | (s > 0.5)]
Out[161]: 
0   -3
1   -2
4    1
5    2
6    3
dtype: int64

In [162]: s[~(s < 0)]
Out[162]: 
3    0
4    1
5    2
6    3
dtype: int64

Вы можете выбрать строки из DataFrame, используя булевой вектор длиной, равной индексу DataFrame (например, что-то, полученное из одного из столбцов DataFrame):

In [163]: df[df['A'] > 0]
Out[163]: 
                   A         B         C         D    E   0
2000-01-04  7.000000  0.721555 -1.039575  0.271860  NaN NaN
2000-01-05  0.567020 -0.424972  0.276232 -1.087401  NaN NaN
2000-01-06  0.113648 -0.673690 -1.478427  0.524988  7.0 NaN
2000-01-07  0.577046  0.404705 -1.715002 -1.039268  NaN NaN

Для создания более сложных критериев также можно использовать списковые включения и метод map для Series:

In [164]: df2 = pd.DataFrame({'a': ['one', 'one', 'two', 'three', 'two', 'one', 'six'],
   .....:                     'b': ['x', 'y', 'y', 'x', 'y', 'x', 'x'],
   .....:                     'c': np.random.randn(7)})
   .....: 

# only want 'two' or 'three'
In [165]: criterion = df2['a'].map(lambda x: x.startswith('t'))

In [166]: df2[criterion]
Out[166]: 
       a  b         c
2    two  y  0.041290
3  three  x  0.361719
4    two  y -0.238075

# equivalent but slower
In [167]: df2[[x.startswith('t') for x in df2['a']]]
Out[167]: 
       a  b         c
2    two  y  0.041290
3  three  x  0.361719
4    two  y -0.238075

# Multiple criteria
In [168]: df2[criterion & (df2['b'] == 'x')]
Out[168]: 
       a  b         c
3  three  x  0.361719

С помощью методов выбора Выбор по меткам, Выбор по позиции и Расширенная индексация вы можете выбирать по нескольким осям, используя булевые векторы в сочетании с другими выражениями индексации.

In [169]: df2.loc[criterion & (df2['b'] == 'x'), 'b':'c']
Out[169]: 
   b         c
3  x  0.361719

Предупреждение

iloc поддерживает два типа булевой индексации. Если индексатор является булевым Series, будет поднято исключение. Например, в следующем примере df.iloc[s.values, 1] нормально. Булевой индексатор является массивом. Но df.iloc[s, 1] будет вызывать ValueError.

In [170]: df = pd.DataFrame([[1, 2], [3, 4], [5, 6]],
   .....:                   index=list('abc'),
   .....:                   columns=['A', 'B'])
   .....: 

In [171]: s = (df['A'] > 2)

In [172]: s
Out[172]: 
a    False
b     True
c     True
Name: A, dtype: bool

In [173]: df.loc[s, 'B']
Out[173]: 
b    4
c    6
Name: B, dtype: int64

In [174]: df.iloc[s.values, 1]
Out[174]: 
b    4
c    6
Name: B, dtype: int64

Индексация с isin

Рассмотрим метод isin() для Series, который возвращает булевой вектор, равный True, где элементы Series присутствуют в переданном списке. Это позволяет выбирать строки, в которых один или несколько столбцов имеют нужные значения:

In [175]: s = pd.Series(np.arange(5), index=np.arange(5)[::-1], dtype='int64')

In [176]: s
Out[176]: 
4    0
3    1
2    2
1    3
0    4
dtype: int64

In [177]: s.isin([2, 4, 6])
Out[177]: 
4    False
3    False
2     True
1    False
0     True
dtype: bool

In [178]: s[s.isin([2, 4, 6])]
Out[178]: 
2    2
0    4
dtype: int64

Этот же метод доступен для объектов Index и полезен в тех случаях, когда вы не знаете, какие из искомых меток фактически присутствуют:

In [179]: s[s.index.isin([2, 4, 6])]
Out[179]: 
4    0
2    2
dtype: int64

# compare it to the following
In [180]: s.reindex([2, 4, 6])
Out[180]: 
2    2.0
4    0.0
6    NaN
dtype: float64

Кроме того, MultiIndex позволяет выбрать отдельный уровень для использования в проверке принадлежности:

In [181]: s_mi = pd.Series(np.arange(6),
   .....:                  index=pd.MultiIndex.from_product([[0, 1], ['a', 'b', 'c']]))
   .....: 

In [182]: s_mi
Out[182]: 
0  a    0
   b    1
   c    2
1  a    3
   b    4
   c    5
dtype: int64

In [183]: s_mi.iloc[s_mi.index.isin([(1, 'a'), (2, 'b'), (0, 'c')])]
Out[183]: 
0  c    2
1  a    3
dtype: int64

In [184]: s_mi.iloc[s_mi.index.isin(['a', 'c', 'e'], level=1)]
Out[184]: 
0  a    0
   c    2
1  a    3
   c    5
dtype: int64

DataFrame также имеет метод isin(). При вызове isin, передайте множество значений в виде массива или словаря. Если values является массивом, isin возвращает DataFrame булевых значений, имеющий такую же форму, что и исходный DataFrame, с True там, где элемент присутствует в последовательности значений.

In [185]: df = pd.DataFrame({'vals': [1, 2, 3, 4], 'ids': ['a', 'b', 'f', 'n'],
   .....:                    'ids2': ['a', 'n', 'c', 'n']})
   .....: 

In [186]: values = ['a', 'b', 1, 3]

In [187]: df.isin(values)
Out[187]: 
    vals    ids   ids2
0   True   True   True
1  False   True  False
2   True  False  False
3  False  False  False

Зачастую вам нужно сопоставить определённые значения с определёнными столбцами. Просто сделайте values словарем, где ключ — столбец, а значение — список элементов, которые вы хотите проверить.

In [188]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}

In [189]: df.isin(values)
Out[189]: 
    vals    ids   ids2
0   True   True  False
1  False   True  False
2   True  False  False
3  False  False  False

Чтобы вернуть DataFrame булевых значений, где значения не присутствуют в исходном DataFrame, используйте оператор ~:

In [190]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}

In [191]: ~df.isin(values)
Out[191]: 
    vals    ids  ids2
0  False  False  True
1   True  False  True
2  False   True  True
3   True   True  True

Комбинируйте метод isin DataFrame с методами any() и all() для быстрого выбора подмножеств данных, которые удовлетворяют заданному критерию. Чтобы выбрать строку, где каждый столбец соответствует своему критерию:

In [192]: values = {'ids': ['a', 'b'], 'ids2': ['a', 'c'], 'vals': [1, 3]}

In [193]: row_mask = df.isin(values).all(1)

In [194]: df[row_mask]
Out[194]: 
   vals ids ids2
0     1   a    a

Метод where() и маскирование

Выбор значений из Series с булевым вектором обычно возвращает подмножество данных. Чтобы гарантировать, что результат выбора имеет ту же форму, что и исходные данные, можно использовать метод where в Series и DataFrame.

Чтобы вернуть только выбранные строки:

In [195]: s[s > 0]
Out[195]: 
3    1
2    2
1    3
0    4
dtype: int64

Чтобы вернуть Series той же формы, что и исходная:

In [196]: s.where(s > 0)
Out[196]: 
4    NaN
3    1.0
2    2.0
1    3.0
0    4.0
dtype: float64

Выбор значений из DataFrame с булевым критерием теперь также сохраняет форму входных данных. where используется в реализации по умолчанию. Код ниже эквивалентен df.where(df < 0).

In [197]: dates = pd.date_range('1/1/2000', periods=8)

In [198]: df = pd.DataFrame(np.random.randn(8, 4),
   .....:                   index=dates, columns=['A', 'B', 'C', 'D'])
   .....: 

In [199]: df[df < 0]
Out[199]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525       NaN       NaN
2000-01-02 -0.352480       NaN -1.192319       NaN
2000-01-03 -0.864883       NaN -0.227870       NaN
2000-01-04       NaN -1.222082       NaN -1.233203
2000-01-05       NaN -0.605656 -1.169184       NaN
2000-01-06       NaN -0.948458       NaN -0.684718
2000-01-07 -2.670153 -0.114722       NaN -0.048048
2000-01-08       NaN       NaN -0.048788 -0.808838

Кроме того, where принимает необязательный аргумент other для замены значений, где условие равно False, в возвращаемой копии.

In [200]: df.where(df < 0, -df)
Out[200]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525 -0.485855 -0.245166
2000-01-02 -0.352480 -0.390389 -1.192319 -1.655824
2000-01-03 -0.864883 -0.299674 -0.227870 -0.281059
2000-01-04 -0.846958 -1.222082 -0.600705 -1.233203
2000-01-05 -0.669692 -0.605656 -1.169184 -0.342416
2000-01-06 -0.868584 -0.948458 -2.297780 -0.684718
2000-01-07 -2.670153 -0.114722 -0.168904 -0.048048
2000-01-08 -0.801196 -1.392071 -0.048788 -0.808838

Вам может понадобиться установить значения на основе некоторых булевых критериев. Это можно сделать интуитивно, как показано ниже:

In [201]: s2 = s.copy()

In [202]: s2[s2 < 0] = 0

In [203]: s2
Out[203]: 
4    0
3    1
2    2
1    3
0    4
dtype: int64

In [204]: df2 = df.copy()

In [205]: df2[df2 < 0] = 0

In [206]: df2
Out[206]: 
                   A         B         C         D
2000-01-01  0.000000  0.000000  0.485855  0.245166
2000-01-02  0.000000  0.390389  0.000000  1.655824
2000-01-03  0.000000  0.299674  0.000000  0.281059
2000-01-04  0.846958  0.000000  0.600705  0.000000
2000-01-05  0.669692  0.000000  0.000000  0.342416
2000-01-06  0.868584  0.000000  2.297780  0.000000
2000-01-07  0.000000  0.000000  0.168904  0.000000
2000-01-08  0.801196  1.392071  0.000000  0.000000

where возвращает изменённую копию данных.

Примечание

Подпись для DataFrame.where() отличается от numpy.where(). Примерно df1.where(m, df2) эквивалентно np.where(m, df1, df2).

In [207]: df.where(df < 0, -df) == np.where(df < 0, df, -df)
Out[207]: 
               A     B     C     D
2000-01-01  True  True  True  True
2000-01-02  True  True  True  True
2000-01-03  True  True  True  True
2000-01-04  True  True  True  True
2000-01-05  True  True  True  True
2000-01-06  True  True  True  True
2000-01-07  True  True  True  True
2000-01-08  True  True  True  True

Выравнивание

Кроме того, where выравнивает входное булевое условие (ndarray или DataFrame), чтобы сделать частичный выбор с установкой возможным. Это аналогично частичной установке через .loc (но по содержанию, а не по меткам осей).

In [208]: df2 = df.copy()

In [209]: df2[df2[1:4] > 0] = 3

In [210]: df2
Out[210]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525  0.485855  0.245166
2000-01-02 -0.352480  3.000000 -1.192319  3.000000
2000-01-03 -0.864883  3.000000 -0.227870  3.000000
2000-01-04  3.000000 -1.222082  3.000000 -1.233203
2000-01-05  0.669692 -0.605656 -1.169184  0.342416
2000-01-06  0.868584 -0.948458  2.297780 -0.684718
2000-01-07 -2.670153 -0.114722  0.168904 -0.048048
2000-01-08  0.801196  1.392071 -0.048788 -0.808838

Где также принимает параметры axis и level для выравнивания входных данных при выполнении where.

In [211]: df2 = df.copy()

In [212]: df2.where(df2 > 0, df2['A'], axis='index')
Out[212]: 
                   A         B         C         D
2000-01-01 -2.104139 -2.104139  0.485855  0.245166
2000-01-02 -0.352480  0.390389 -0.352480  1.655824
2000-01-03 -0.864883  0.299674 -0.864883  0.281059
2000-01-04  0.846958  0.846958  0.600705  0.846958
2000-01-05  0.669692  0.669692  0.669692  0.342416
2000-01-06  0.868584  0.868584  2.297780  0.868584
2000-01-07 -2.670153 -2.670153  0.168904 -2.670153
2000-01-08  0.801196  1.392071  0.801196  0.801196

Это эквивалентно (но быстрее), чем следующее.

In [213]: df2 = df.copy()

In [214]: df.apply(lambda x, y: x.where(x > 0, y), y=df['A'])
Out[214]: 
                   A         B         C         D
2000-01-01 -2.104139 -2.104139  0.485855  0.245166
2000-01-02 -0.352480  0.390389 -0.352480  1.655824
2000-01-03 -0.864883  0.299674 -0.864883  0.281059
2000-01-04  0.846958  0.846958  0.600705  0.846958
2000-01-05  0.669692  0.669692  0.669692  0.342416
2000-01-06  0.868584  0.868584  2.297780  0.868584
2000-01-07 -2.670153 -2.670153  0.168904 -2.670153
2000-01-08  0.801196  1.392071  0.801196  0.801196

where может принимать вызываемую функцию в качестве условия и аргументов other. Функция должна иметь один аргумент (вызываемую Series или DataFrame) и возвращать допустимый результат в качестве условия и аргумента other.

where

Маска

mask() — это инверсная булевая операция к where.

In [217]: s.mask(s >= 0)
Out[217]: 
4   NaN
3   NaN
2   NaN
1   NaN
0   NaN
dtype: float64

In [218]: df.mask(df >= 0)
Out[218]: 
                   A         B         C         D
2000-01-01 -2.104139 -1.309525       NaN       NaN
2000-01-02 -0.352480       NaN -1.192319       NaN
2000-01-03 -0.864883       NaN -0.227870       NaN
2000-01-04       NaN -1.222082       NaN -1.233203
2000-01-05       NaN -0.605656 -1.169184       NaN
2000-01-06       NaN -0.948458       NaN -0.684718
2000-01-07 -2.670153 -0.114722       NaN -0.048048
2000-01-08       NaN       NaN -0.048788 -0.808838

Установление с расширением условно с использованием numpy()

Альтернативой where() является использование numpy.where(). В сочетании с добавлением нового столбца его можно использовать для расширения DataFrame, где значения определяются условно.

Предположим, у вас есть два варианта выбора в следующем DataFrame. И вы хотите установить новый столбец color в ‘green’, когда во втором столбце есть ‘Z’. Вы можете сделать следующее:

In [219]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})

In [220]: df['color'] = np.where(df['col2'] == 'Z', 'green', 'red')

In [221]: df
Out[221]: 
  col1 col2  color
0    A    Z  green
1    B    Z  green
2    B    X    red
3    C    Y    red

Если у вас несколько условий, вы можете использовать numpy.select() для достижения этого. Допустим, трём условиям соответствуют три варианта цветов, а четвёртый цвет — резервный. Вы можете сделать следующее.

In [222]: conditions = [
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'A'),
   .....:     (df['col2'] == 'Z') & (df['col1'] == 'B'),
   .....:     (df['col1'] == 'B')
   .....: ]
   .....: 

In [223]: choices = ['yellow', 'blue', 'purple']

In [224]: df['color'] = np.select(conditions, choices, default='black')

In [225]: df
Out[225]: 
  col1 col2   color
0    A    Z  yellow
1    B    Z    blue
2    B    X  purple
3    C    Y   black

Метод query()

DataFrame объекты имеют метод query(), который позволяет осуществлять выборку с помощью выражения.

Вы можете получить значение фрейма, где столбец b имеет значения между значениями столбцов a и c. Например:

In [226]: n = 10

In [227]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [228]: df
Out[228]: 
          a         b         c
0  0.438921  0.118680  0.863670
1  0.138138  0.577363  0.686602
2  0.595307  0.564592  0.520630
3  0.913052  0.926075  0.616184
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
6  0.792342  0.216974  0.564056
7  0.397890  0.454131  0.915716
8  0.074315  0.437913  0.019794
9  0.559209  0.502065  0.026437

# pure python
In [229]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[229]: 
          a         b         c
1  0.138138  0.577363  0.686602
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
7  0.397890  0.454131  0.915716

# query
In [230]: df.query('(a < b) & (b < c)')
Out[230]: 
          a         b         c
1  0.138138  0.577363  0.686602
4  0.078718  0.854477  0.898725
5  0.076404  0.523211  0.591538
7  0.397890  0.454131  0.915716

Сделайте то же самое, но используйте именованный индекс, если столбец с именем a отсутствует.

In [231]: df = pd.DataFrame(np.random.randint(n / 2, size=(n, 2)), columns=list('bc'))

In [232]: df.index.name = 'a'

In [233]: df
Out[233]: 
   b  c
a      
0  0  4
1  0  1
2  3  4
3  4  3
4  1  4
5  0  3
6  0  1
7  3  4
8  2  3
9  1  1

In [234]: df.query('a < b and b < c')
Out[234]: 
   b  c
a      
2  3  4

Если вы не хотите или не можете назвать свой индекс, вы можете использовать имя index в выражении запроса:

In [235]: df = pd.DataFrame(np.random.randint(n, size=(n, 2)), columns=list('bc'))

In [236]: df
Out[236]: 
   b  c
0  3  1
1  3  0
2  5  6
3  5  2
4  7  4
5  0  1
6  2  5
7  0  1
8  6  0
9  7  9

In [237]: df.query('index < b < c')
Out[237]: 
   b  c
2  5  6

Примечание

Если имя вашего индекса совпадает с именем столбца, имя столбца имеет приоритет. Например,

In [238]: df = pd.DataFrame({'a': np.random.randint(5, size=5)})

In [239]: df.index.name = 'a'

In [240]: df.query('a > 2')  # uses the column 'a', not the index
Out[240]: 
   a
a   
1  3
3  3

Вы по-прежнему можете использовать индекс в выражении запроса, используя специальный идентификатор «index»:

In [241]: df.query('index > 2')
Out[241]: 
   a
a   
3  3
4  2

Если по какой-то причине у вас есть столбец с именем index, вы можете обратиться к индексу как к ilevel_0, но в этом случае стоит переименовать ваши столбцы на что-то менее неоднозначное.

Синтаксис запроса для MultiIndex query()

Вы также можете использовать уровни MultiIndex объекта DataFrame так, как будто они являются столбцами в фрейме:

In [242]: n = 10

In [243]: colors = np.random.choice(['red', 'green'], size=n)

In [244]: foods = np.random.choice(['eggs', 'ham'], size=n)

In [245]: colors
Out[245]: 
array(['red', 'red', 'red', 'green', 'green', 'green', 'green', 'green',
       'green', 'green'], dtype='<U5')

In [246]: foods
Out[246]: 
array(['ham', 'ham', 'eggs', 'eggs', 'eggs', 'ham', 'ham', 'eggs', 'eggs',
       'eggs'], dtype='<U4')

In [247]: index = pd.MultiIndex.from_arrays([colors, foods], names=['color', 'food'])

In [248]: df = pd.DataFrame(np.random.randn(n, 2), index=index)

In [249]: df
Out[249]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255
green eggs -0.748199  1.318931
      eggs -2.029766  0.792652
      ham   0.461007 -0.542749
      ham  -0.305384 -0.479195
      eggs  0.095031 -0.270099
      eggs -0.707140 -0.773882
      eggs  0.229453  0.304418

In [250]: df.query('color == "red"')
Out[250]: 
                   0         1
color food                    
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255

Если уровни MultiIndex не имеют имен, вы можете обратиться к ним, используя специальные имена:

In [251]: df.index.names = [None, None]

In [252]: df
Out[252]: 
                   0         1
red   ham   0.194889 -0.381994
      ham   0.318587  2.089075
      eggs -0.728293 -0.090255
green eggs -0.748199  1.318931
      eggs -2.029766  0.792652
      ham   0.461007 -0.542749
      ham  -0.305384 -0.479195
      eggs  0.095031 -0.270099
      eggs -0.707140 -0.773882
      eggs  0.229453  0.304418

In [253]: df.query('ilevel_0 == "red"')
Out[253]: 
                 0         1
red ham   0.194889 -0.381994
    ham   0.318587  2.089075
    eggs -0.728293 -0.090255

Конвенция — ilevel_0, что означает «уровень индекса 0» для нулевого уровня index.

Примеры использования query()

Пример использования метода query() — когда у вас есть набор объектов DataFrame, у которых есть общая подмножество имен столбцов (или уровней/имен индексов). Вы можете передать один и тот же запрос обоим фреймам без указания, какой фрейм вы хотите запросить.

In [254]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [255]: df
Out[255]: 
          a         b         c
0  0.224283  0.736107  0.139168
1  0.302827  0.657803  0.713897
2  0.611185  0.136624  0.984960
3  0.195246  0.123436  0.627712
4  0.618673  0.371660  0.047902
5  0.480088  0.062993  0.185760
6  0.568018  0.483467  0.445289
7  0.309040  0.274580  0.587101
8  0.258993  0.477769  0.370255
9  0.550459  0.840870  0.304611

In [256]: df2 = pd.DataFrame(np.random.rand(n + 2, 3), columns=df.columns)

In [257]: df2
Out[257]: 
           a         b         c
0   0.357579  0.229800  0.596001
1   0.309059  0.957923  0.965663
2   0.123102  0.336914  0.318616
3   0.526506  0.323321  0.860813
4   0.518736  0.486514  0.384724
5   0.190804  0.505723  0.614533
6   0.891939  0.623977  0.676639
7   0.480559  0.378528  0.460858
8   0.420223  0.136404  0.141295
9   0.732206  0.419540  0.604675
10  0.604466  0.848974  0.896165
11  0.589168  0.920046  0.732716

In [258]: expr = '0.0 <= a <= c <= 0.5'

In [259]: map(lambda frame: frame.query(expr), [df, df2])
Out[259]: <map at 0x7ff2e57db2e0>

Сравнение синтаксиса Python и pandas для query()

Полный синтаксис в стиле numpy:

In [260]: df = pd.DataFrame(np.random.randint(n, size=(n, 3)), columns=list('abc'))

In [261]: df
Out[261]: 
   a  b  c
0  7  8  9
1  1  0  7
2  2  7  2
3  6  2  2
4  2  6  3
5  3  8  2
6  1  7  2
7  5  1  5
8  9  8  0
9  1  5  0

In [262]: df.query('(a < b) & (b < c)')
Out[262]: 
   a  b  c
0  7  8  9

In [263]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[263]: 
   a  b  c
0  7  8  9

Немного улучшенный за счет удаления скобок (операторы сравнения связываются сильнее, чем & и |):

In [264]: df.query('a < b & b < c')
Out[264]: 
   a  b  c
0  7  8  9

Использование слов вместо символов:

In [265]: df.query('a < b and b < c')
Out[265]: 
   a  b  c
0  7  8  9

Очень похоже на то, как вы можете написать это на бумаге:

In [266]: df.query('a < b < c')
Out[266]: 
   a  b  c
0  7  8  9

Операторы in и not in

query() также поддерживает специальное использование операторов сравнения Python in и not in, предоставляя лаконичный синтаксис для вызова метода isin объекта Series или DataFrame.

# get all rows where columns "a" and "b" have overlapping values
In [267]: df = pd.DataFrame({'a': list('aabbccddeeff'), 'b': list('aaaabbbbcccc'),
   .....:                    'c': np.random.randint(5, size=12),
   .....:                    'd': np.random.randint(9, size=12)})
   .....: 

In [268]: df
Out[268]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

In [269]: df.query('a in b')
Out[269]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
3  b  a  2  1
4  c  b  3  6
5  c  b  0  2

# How you'd do it in pure Python
In [270]: df[df['a'].isin(df['b'])]
Out[270]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
3  b  a  2  1
4  c  b  3  6
5  c  b  0  2

In [271]: df.query('a not in b')
Out[271]: 
    a  b  c  d
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

# pure Python
In [272]: df[~df['a'].isin(df['b'])]
Out[272]: 
    a  b  c  d
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

Вы можете комбинировать это с другими выражениями для очень лаконичных запросов:

# rows where cols a and b have overlapping values
# and col c's values are less than col d's
In [273]: df.query('a in b and c < d')
Out[273]: 
   a  b  c  d
0  a  a  2  6
1  a  a  4  7
2  b  a  1  6
4  c  b  3  6
5  c  b  0  2

# pure Python
In [274]: df[df['b'].isin(df['a']) & (df['c'] < df['d'])]
Out[274]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
4   c  b  3  6
5   c  b  0  2
10  f  c  0  6
11  f  c  1  2

Примечание

Обратите внимание, что in и not in вычисляются в Python, так как в numexpr нет эквивалента этой операции. Однако, только выражение in/not in вычисляется в обычном Python. Например, в выражении

df.query('a in b + c + d')

(b + c + d) вычисляется методом numexpr и затем операция in вычисляется в обычном Python. В общем случае любые операции, которые можно вычислить с помощью numexpr будут.

Особое использование оператора == с объектами list

Сравнение списка значений со столбцом с помощью ==/!= работает аналогично in/not in.

In [275]: df.query('b == ["a", "b", "c"]')
Out[275]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

# pure Python
In [276]: df[df['b'].isin(["a", "b", "c"])]
Out[276]: 
    a  b  c  d
0   a  a  2  6
1   a  a  4  7
2   b  a  1  6
3   b  a  2  1
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
7   d  b  2  1
8   e  c  4  3
9   e  c  2  0
10  f  c  0  6
11  f  c  1  2

In [277]: df.query('c == [1, 2]')
Out[277]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

In [278]: df.query('c != [1, 2]')
Out[278]: 
    a  b  c  d
1   a  a  4  7
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
8   e  c  4  3
10  f  c  0  6

# using in/not in
In [279]: df.query('[1, 2] in c')
Out[279]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

In [280]: df.query('[1, 2] not in c')
Out[280]: 
    a  b  c  d
1   a  a  4  7
4   c  b  3  6
5   c  b  0  2
6   d  b  3  3
8   e  c  4  3
10  f  c  0  6

# pure Python
In [281]: df[df['c'].isin([1, 2])]
Out[281]: 
    a  b  c  d
0   a  a  2  6
2   b  a  1  6
3   b  a  2  1
7   d  b  2  1
9   e  c  2  0
11  f  c  1  2

Булевы операторы

Вы можете отрицать булевы выражения, используя слово not или оператор ~.

In [282]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))

In [283]: df['bools'] = np.random.rand(len(df)) > 0.5

In [284]: df.query('~bools')
Out[284]: 
          a         b         c  bools
2  0.697753  0.212799  0.329209  False
7  0.275396  0.691034  0.826619  False
8  0.190649  0.558748  0.262467  False

In [285]: df.query('not bools')
Out[285]: 
          a         b         c  bools
2  0.697753  0.212799  0.329209  False
7  0.275396  0.691034  0.826619  False
8  0.190649  0.558748  0.262467  False

In [286]: df.query('not bools') == df[~df['bools']]
Out[286]: 
      a     b     c  bools
2  True  True  True   True
7  True  True  True   True
8  True  True  True   True

Конечно, выражения могут быть сколь угодно сложными:

# short query syntax
In [287]: shorter = df.query('a < b < c and (not bools) or bools > 2')

# equivalent in pure Python
In [288]: longer = df[(df['a'] < df['b'])
   .....:             & (df['b'] < df['c'])
   .....:             & (~df['bools'])
   .....:             | (df['bools'] > 2)]
   .....: 

In [289]: shorter
Out[289]: 
          a         b         c  bools
7  0.275396  0.691034  0.826619  False

In [290]: longer
Out[290]: 
          a         b         c  bools
7  0.275396  0.691034  0.826619  False

In [291]: shorter == longer
Out[291]: 
      a     b     c  bools
7  True  True  True   True

Производительность метода query()

DataFrame.query() с использованием numexpr немного быстрее, чем Python для больших фреймов.

../_images/query-perf.png

Вы заметите преимущества использования движка numexpr с DataFrame.query() только если ваш фрейм содержит более примерно 100 000 строк.

Этот график был создан с помощью фрейма DataFrame с 3 столбцами, каждый из которых содержал значения с плавающей точкой, сгенерированные с помощью numpy.random.randn().

In [292]: df = pd.DataFrame(np.random.randn(8, 4),
   .....:                   index=dates, columns=['A', 'B', 'C', 'D'])
   .....: 

In [293]: df2 = df.copy()

Дубликаты данных

Если вы хотите идентифицировать и удалить дубликаты строк в DataFrame, вам помогут два метода: duplicated и drop_duplicates. Каждый из них принимает в качестве аргумента столбцы, которые будут использоваться для идентификации дубликатов строк.

  • duplicated возвращает булевый вектор, длина которого равна количеству строк, и который указывает, является ли строка дубликатом.

  • drop_duplicates удаляет дубликаты строк.

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

  • keep='first' (по умолчанию): отмечает/удаляет дубликаты, кроме первого вхождения.

  • keep='last': отмечает/удаляет дубликаты, кроме последнего вхождения.

  • keep=False: отмечает/удаляет все дубликаты.

In [294]: df2 = pd.DataFrame({'a': ['one', 'one', 'two', 'two', 'two', 'three', 'four'],
   .....:                     'b': ['x', 'y', 'x', 'y', 'x', 'x', 'x'],
   .....:                     'c': np.random.randn(7)})
   .....: 

In [295]: df2
Out[295]: 
       a  b         c
0    one  x -1.067137
1    one  y  0.309500
2    two  x -0.211056
3    two  y -1.842023
4    two  x -0.390820
5  three  x -1.964475
6   four  x  1.298329

In [296]: df2.duplicated('a')
Out[296]: 
0    False
1     True
2    False
3     True
4     True
5    False
6    False
dtype: bool

In [297]: df2.duplicated('a', keep='last')
Out[297]: 
0     True
1    False
2     True
3     True
4    False
5    False
6    False
dtype: bool

In [298]: df2.duplicated('a', keep=False)
Out[298]: 
0     True
1     True
2     True
3     True
4     True
5    False
6    False
dtype: bool

In [299]: df2.drop_duplicates('a')
Out[299]: 
       a  b         c
0    one  x -1.067137
2    two  x -0.211056
5  three  x -1.964475
6   four  x  1.298329

In [300]: df2.drop_duplicates('a', keep='last')
Out[300]: 
       a  b         c
1    one  y  0.309500
4    two  x -0.390820
5  three  x -1.964475
6   four  x  1.298329

In [301]: df2.drop_duplicates('a', keep=False)
Out[301]: 
       a  b         c
5  three  x -1.964475
6   four  x  1.298329

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

In [302]: df2.duplicated(['a', 'b'])
Out[302]: 
0    False
1    False
2    False
3    False
4     True
5    False
6    False
dtype: bool

In [303]: df2.drop_duplicates(['a', 'b'])
Out[303]: 
       a  b         c
0    one  x -1.067137
1    one  y  0.309500
2    two  x -0.211056
3    two  y -1.842023
5  three  x -1.964475
6   four  x  1.298329

Чтобы удалить дубликаты по значению индекса, используйте Index.duplicated, а затем выполните срезы. Для параметра keep доступен тот же набор вариантов.

In [304]: df3 = pd.DataFrame({'a': np.arange(6),
   .....:                     'b': np.random.randn(6)},
   .....:                    index=['a', 'a', 'b', 'c', 'b', 'a'])
   .....: 

In [305]: df3
Out[305]: 
   a         b
a  0  1.440455
a  1  2.456086
b  2  1.038402
c  3 -0.894409
b  4  0.683536
a  5  3.082764

In [306]: df3.index.duplicated()
Out[306]: array([False,  True, False, False,  True,  True])

In [307]: df3[~df3.index.duplicated()]
Out[307]: 
   a         b
a  0  1.440455
b  2  1.038402
c  3 -0.894409

In [308]: df3[~df3.index.duplicated(keep='last')]
Out[308]: 
   a         b
c  3 -0.894409
b  4  0.683536
a  5  3.082764

In [309]: df3[~df3.index.duplicated(keep=False)]
Out[309]: 
   a         b
c  3 -0.894409

Метод get() в стиле словаря

Каждый из объектов Series или DataFrame имеет метод get, который может вернуть значение по умолчанию.

In [310]: s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])

In [311]: s.get('a')  # equivalent to s['a']
Out[311]: 1

In [312]: s.get('x', default=-1)
Out[312]: -1

Поиск значений по меткам индексов/столбцов

Иногда вам нужно извлечь набор значений по последовательности меток строк и столбцов. Это можно сделать с помощью индексации pandas.factorize и NumPy. Например:

In [313]: df = pd.DataFrame({'col': ["A", "A", "B", "B"],
   .....:                    'A': [80, 23, np.nan, 22],
   .....:                    'B': [80, 55, 76, 67]})
   .....: 

In [314]: df
Out[314]: 
  col     A   B
0   A  80.0  80
1   A  23.0  55
2   B   NaN  76
3   B  22.0  67

In [315]: idx, cols = pd.factorize(df['col'])

In [316]: df.reindex(cols, axis=1).to_numpy()[np.arange(len(df)), idx]
Out[316]: array([80., 23., 76., 67.])

Ранее это можно было сделать с помощью специального метода DataFrame.lookup, который был устаревшим в версии 1.2.0 и удалён в версии 2.0.0.

END_OF_DOCUMENT_MARKER

Индексные объекты

Класс pandas Index и его подклассы можно рассматривать как реализацию упорядоченного мультимножества. Повторения допускаются.

Index также предоставляет инфраструктуру, необходимую для поиска, выравнивания данных и повторной индексации. Самый простой способ создать Index напрямую — передать list или другую последовательность в Index:

In [317]: index = pd.Index(['e', 'd', 'a', 'b'])

In [318]: index
Out[318]: Index(['e', 'd', 'a', 'b'], dtype='object')

In [319]: 'd' in index
Out[319]: True

или используя числа:

In [320]: index = pd.Index([1, 5, 12])

In [321]: index
Out[321]: Index([1, 5, 12], dtype='int64')

In [322]: 5 in index
Out[322]: True

Если тип данных (dtype) не указан, Index пытается определить его по данным. Также можно явно указать тип данных при создании Index:

In [323]: index = pd.Index(['e', 'd', 'a', 'b'], dtype="string")

In [324]: index
Out[324]: Index(['e', 'd', 'a', 'b'], dtype='string')

In [325]: index = pd.Index([1, 5, 12], dtype="int8")

In [326]: index
Out[326]: Index([1, 5, 12], dtype='int8')

In [327]: index = pd.Index([1, 5, 12], dtype="float32")

In [328]: index
Out[328]: Index([1.0, 5.0, 12.0], dtype='float32')

Вы также можете передать name для хранения в индексе:

In [329]: index = pd.Index(['e', 'd', 'a', 'b'], name='something')

In [330]: index.name
Out[330]: 'something'

Имя, если оно установлено, будет отображаться в консоли:

In [331]: index = pd.Index(list(range(5)), name='rows')

In [332]: columns = pd.Index(['A', 'B', 'C'], name='cols')

In [333]: df = pd.DataFrame(np.random.randn(5, 3), index=index, columns=columns)

In [334]: df
Out[334]: 
cols         A         B         C
rows                              
0     1.295989 -1.051694  1.340429
1    -2.366110  0.428241  0.387275
2     0.433306  0.929548  0.278094
3     2.154730 -0.315628  0.264223
4     1.126818  1.132290 -0.353310

In [335]: df['A']
Out[335]: 
rows
0    1.295989
1   -2.366110
2    0.433306
3    2.154730
4    1.126818
Name: A, dtype: float64

Установка метаданных

Индексы «в основном неизменяемы», но можно установить и изменить их атрибут name. Можно использовать rename, set_names для непосредственного задания этих атрибутов, и по умолчанию они возвращают копию.

См. Расширенная индексация для использования MultiIndexes.

In [336]: ind = pd.Index([1, 2, 3])

In [337]: ind.rename("apple")
Out[337]: Index([1, 2, 3], dtype='int64', name='apple')

In [338]: ind
Out[338]: Index([1, 2, 3], dtype='int64')

In [339]: ind = ind.set_names(["apple"])

In [340]: ind.name = "bob"

In [341]: ind
Out[341]: Index([1, 2, 3], dtype='int64', name='bob')

set_names, set_levels, и set_codes также принимают необязательный аргумент level

In [342]: index = pd.MultiIndex.from_product([range(3), ['one', 'two']], names=['first', 'second'])

In [343]: index
Out[343]: 
MultiIndex([(0, 'one'),
            (0, 'two'),
            (1, 'one'),
            (1, 'two'),
            (2, 'one'),
            (2, 'two')],
           names=['first', 'second'])

In [344]: index.levels[1]
Out[344]: Index(['one', 'two'], dtype='object', name='second')

In [345]: index.set_levels(["a", "b"], level=1)
Out[345]: 
MultiIndex([(0, 'a'),
            (0, 'b'),
            (1, 'a'),
            (1, 'b'),
            (2, 'a'),
            (2, 'b')],
           names=['first', 'second'])

Операции над множествами с объектами Index

Две основные операции — union и intersection. Разность предоставляется методом .difference().

In [346]: a = pd.Index(['c', 'b', 'a'])

In [347]: b = pd.Index(['c', 'e', 'd'])

In [348]: a.difference(b)
Out[348]: Index(['a', 'b'], dtype='object')

Также доступна операция symmetric_difference, которая возвращает элементы, присутствующие либо в idx1, либо в idx2, но не в обоих. Это эквивалентно индексу, созданному с помощью idx1.difference(idx2).union(idx2.difference(idx1)), с удалением дубликатов.

In [349]: idx1 = pd.Index([1, 2, 3, 4])

In [350]: idx2 = pd.Index([2, 3, 4, 5])

In [351]: idx1.symmetric_difference(idx2)
Out[351]: Index([1, 5], dtype='int64')

Примечание

Полученный индекс из операции над множествами будет отсортирован по возрастанию.

При выполнении Index.union() между индексами с разными типами данных, индексы должны быть приведены к общему типу. Обычно, хотя и не всегда, это тип object. Исключением является объединение целочисленных и плавающей точкой данных. В этом случае целые значения преобразуются в вещественные.

In [352]: idx1 = pd.Index([0, 1, 2])

In [353]: idx2 = pd.Index([0.5, 1.5])

In [354]: idx1.union(idx2)
Out[354]: Index([0.0, 0.5, 1.0, 1.5, 2.0], dtype='float64')

Пропущенные значения

Важно

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

Index.fillna заполняет пропущенные значения указанным скалярным значением.

In [355]: idx1 = pd.Index([1, np.nan, 3, 4])

In [356]: idx1
Out[356]: Index([1.0, nan, 3.0, 4.0], dtype='float64')

In [357]: idx1.fillna(2)
Out[357]: Index([1.0, 2.0, 3.0, 4.0], dtype='float64')

In [358]: idx2 = pd.DatetimeIndex([pd.Timestamp('2011-01-01'),
   .....:                          pd.NaT,
   .....:                          pd.Timestamp('2011-01-03')])
   .....: 

In [359]: idx2
Out[359]: DatetimeIndex(['2011-01-01', 'NaT', '2011-01-03'], dtype='datetime64[ns]', freq=None)

In [360]: idx2.fillna(pd.Timestamp('2011-01-02'))
Out[360]: DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], dtype='datetime64[ns]', freq=None)

Установка/сброс индекса

Иногда вы загружаете или создаёте набор данных в DataFrame и хотите добавить индекс после того, как это уже сделали. Есть несколько способов.

Установка индекса

DataFrame имеет метод set_index(), который принимает имя столбца (для обычного Index) или список имён столбцов (для MultiIndex). Для создания нового, повторно индексированного DataFrame:

In [361]: data = pd.DataFrame({'a': ['bar', 'bar', 'foo', 'foo'],
   .....:                      'b': ['one', 'two', 'one', 'two'],
   .....:                      'c': ['z', 'y', 'x', 'w'],
   .....:                      'd': [1., 2., 3, 4]})
   .....: 

In [362]: data
Out[362]: 
     a    b  c    d
0  bar  one  z  1.0
1  bar  two  y  2.0
2  foo  one  x  3.0
3  foo  two  w  4.0

In [363]: indexed1 = data.set_index('c')

In [364]: indexed1
Out[364]: 
     a    b    d
c               
z  bar  one  1.0
y  bar  two  2.0
x  foo  one  3.0
w  foo  two  4.0

In [365]: indexed2 = data.set_index(['a', 'b'])

In [366]: indexed2
Out[366]: 
         c    d
a   b          
bar one  z  1.0
    two  y  2.0
foo one  x  3.0
    two  w  4.0

Необязательный параметр append позволяет сохранить существующий индекс и добавить заданные столбцы в MultiIndex:

In [367]: frame = data.set_index('c', drop=False)

In [368]: frame = frame.set_index(['a', 'b'], append=True)

In [369]: frame
Out[369]: 
           c    d
c a   b          
z bar one  z  1.0
y bar two  y  2.0
x foo one  x  3.0
w foo two  w  4.0

Другие параметры в set_index позволяют не удалять столбцы индекса.

In [370]: data.set_index('c', drop=False)
Out[370]: 
     a    b  c    d
c                  
z  bar  one  z  1.0
y  bar  two  y  2.0
x  foo  one  x  3.0
w  foo  two  w  4.0

Сброс индекса

Для удобства есть новая функция в DataFrame, называемая reset_index(), которая переносит значения индекса в столбцы DataFrame и задаёт простой целочисленный индекс. Это обратная операция к set_index().

In [371]: data
Out[371]: 
     a    b  c    d
0  bar  one  z  1.0
1  bar  two  y  2.0
2  foo  one  x  3.0
3  foo  two  w  4.0

In [372]: data.reset_index()
Out[372]: 
   index    a    b  c    d
0      0  bar  one  z  1.0
1      1  bar  two  y  2.0
2      2  foo  one  x  3.0
3      3  foo  two  w  4.0

Вывод более похож на таблицу SQL или массив записей. Имена столбцов, полученных из индекса, — это те, что хранятся в атрибуте names.

Можно использовать параметр level чтобы удалить только часть индекса:

In [373]: frame
Out[373]: 
           c    d
c a   b          
z bar one  z  1.0
y bar two  y  2.0
x foo one  x  3.0
w foo two  w  4.0

In [374]: frame.reset_index(level=1)
Out[374]: 
         a  c    d
c b               
z one  bar  z  1.0
y two  bar  y  2.0
x one  foo  x  3.0
w two  foo  w  4.0

reset_index принимает необязательный параметр drop, который, если он истинный, просто отбрасывает индекс вместо того, чтобы помещать значения индекса в столбцы DataFrame.

Добавление временного индекса

Вы можете назначить пользовательский индекс атрибуту index:

In [375]: df_idx = pd.DataFrame(range(4))

In [376]: df_idx.index = pd.Index([10, 20, 30, 40], name="a")

In [377]: df_idx
Out[377]: 
    0
a    
10  0
20  1
30  2
40  3

Возвращение представления по сравнению с копией

Предупреждение

Copy-on-Write станет новым значением по умолчанию в pandas 3.0. Это означает, что цепная индексация больше никогда не будет работать. Вследствие этого, SettingWithCopyWarning больше не потребуется. Смотрите эту секцию для более подробного контекста. Рекомендуется включить Copy-on-Write, чтобы воспользоваться преимуществами

` pd.options.mode.copy_on_write = True `

даже до выхода pandas 3.0.

При настройке значений в объекте pandas необходимо быть внимательным, чтобы избежать того, что называется chained indexing. Вот пример.

In [378]: dfmi = pd.DataFrame([list('abcd'),
   .....:                      list('efgh'),
   .....:                      list('ijkl'),
   .....:                      list('mnop')],
   .....:                     columns=pd.MultiIndex.from_product([['one', 'two'],
   .....:                                                         ['first', 'second']]))
   .....: 

In [379]: dfmi
Out[379]: 
    one          two       
  first second first second
0     a      b     c      d
1     e      f     g      h
2     i      j     k      l
3     m      n     o      p

Сравните эти два метода доступа:

In [380]: dfmi['one']['second']
Out[380]: 
0    b
1    f
2    j
3    n
Name: second, dtype: object
In [381]: dfmi.loc[:, ('one', 'second')]
Out[381]: 
0    b
1    f
2    j
3    n
Name: (one, second), dtype: object

Оба результата одинаковые, так какой же использовать? Полезно понять порядок операций и почему метод 2 (.loc) предпочтительнее метода 1 (цепной []).

dfmi['one'] выбирает первый уровень столбцов и возвращает DataFrame с одноуровневой индексацией. Затем другая операция Python dfmi_with_one['second'] выбирает серию, индексированную по 'second'. Это показано переменной dfmi_with_one, потому что pandas рассматривает эти операции как отдельные события. Например, отдельные вызовы __getitem__, поэтому он должен обрабатывать их как линейные операции, которые происходят одна за другой.

В отличие от df.loc[:,('one','second')], которая передает вложенную кортеж из (slice(None),('one','second')) в один вызов __getitem__. Это позволяет pandas обрабатывать это как единое целое. Кроме того, этот порядок операций может быть значительно быстрее и позволяет индексировать оба осей, если это необходимо.

Почему присвоение не выполняется при использовании цепной индексации?

Предупреждение

Copy-on-Write станет новым значением по умолчанию в pandas 3.0. Это означает, что цепная индексация больше никогда не будет работать. Вследствие этого, SettingWithCopyWarning больше не потребуется. Смотрите эту секцию для более подробного контекста. Рекомендуется включить Copy-on-Write, чтобы воспользоваться преимуществами

` pd.options.mode.copy_on_write = True `

даже до выхода pandas 3.0.

Проблема в предыдущем разделе — это просто проблема производительности. Что с SettingWithCopy предупреждением? Мы обычно не выводим предупреждения, когда вы делаете что-то, что может стоить несколько дополнительных миллисекунд!

Но оказывается, что присваивание результату цепной индексации имеет неопределенные результаты. Чтобы увидеть это, подумайте, как интерпретатор Python выполняет этот код:

dfmi.loc[:, ('one', 'second')] = value
# becomes
dfmi.loc.__setitem__((slice(None), ('one', 'second')), value)

Но этот код обрабатывается по-другому:

dfmi['one']['second'] = value
# becomes
dfmi.__getitem__('one').__setitem__('second', value)

Вы видите __getitem__? За пределами простых случаев очень сложно предсказать, вернет ли он представление или копию (это зависит от структуры памяти массива, о чем pandas не гарантирует), и, следовательно, изменит ли __setitem__ dfmi или временный объект, который выбрасывается сразу после этого. Это то, о чем предупреждает SettingWithCopy!

Примечание

Вы, возможно, задаетесь вопросом, должны ли мы беспокоиться о свойстве loc в первом примере. Но dfmi.loc гарантированно является dfmi с измененным поведением индексации, поэтому dfmi.loc.__getitem__ / dfmi.loc.__setitem__ работают непосредственно с dfmi. Конечно, dfmi.loc.__getitem__(idx) может быть представлением или копией dfmi.

Иногда возникает предупреждение SettingWithCopy в ситуациях, когда очевидной цепной индексации нет. Вот те ошибки, которые SettingWithCopy предназначены для обнаружения! pandas, вероятно, пытается предупредить вас, что вы сделали это:

def do_something(df):
    foo = df[['bar', 'baz']]  # Is foo a view? A copy? Nobody knows!
    # ... many lines here ...
    # We don't know whether this will modify df or not!
    foo['quux'] = value
    return foo

Ужас!

Порядок вычисления имеет значение

Предупреждение

Copy-on-Write станет новым значением по умолчанию в pandas 3.0. Это означает, что цепная индексация больше никогда не будет работать. Вследствие этого, SettingWithCopyWarning больше не потребуется. Смотрите эту секцию для более подробного контекста. Рекомендуется включить Copy-on-Write, чтобы воспользоваться преимуществами

` pd.options.mode.copy_on_write = True `

даже до выхода pandas 3.0.

При использовании цепной индексации порядок и тип операции индексации частично определяют, является ли результат срезом исходного объекта или копией среза.

pandas имеет SettingWithCopyWarning, потому что присваивание копии среза часто не является намеренным, а ошибкой, вызванной тем, что цепная индексация возвращает копию там, где ожидался срез.

Если вы хотите, чтобы pandas был более или менее уверенным в присваивании выражению с цепной индексацией, вы можете установить параметр параметра mode.chained_assignment на одно из этих значений:

  • 'warn', значение по умолчанию, означает, что будет выведено SettingWithCopyWarning.

  • 'raise' означает, что pandas будет поднимать SettingWithCopyError, с которой вам нужно будет разобраться.

  • None полностью подавляет предупреждения.

In [382]: dfb = pd.DataFrame({'a': ['one', 'one', 'two',
   .....:                           'three', 'two', 'one', 'six'],
   .....:                     'c': np.arange(7)})
   .....: 

# This will show the SettingWithCopyWarning
# but the frame values will be set
In [383]: dfb['c'][dfb['a'].str.startswith('o')] = 42

Однако это работает с копией и не будет работать.

In [384]: with pd.option_context('mode.chained_assignment','warn'):
   .....:     dfb[dfb['a'].str.startswith('o')]['c'] = 42
   .....: 

Цепное присваивание также может возникать при настройке в фрейме смешанного типа.

Примечание

Эти правила настройки применяются ко всем .loc/.iloc.

Рекомендуемый метод доступа с использованием .loc для нескольких элементов (используя mask) и одного элемента с фиксированным индексом:

In [385]: dfc = pd.DataFrame({'a': ['one', 'one', 'two',
   .....:                           'three', 'two', 'one', 'six'],
   .....:                     'c': np.arange(7)})
   .....: 

In [386]: dfd = dfc.copy()

# Setting multiple items using a mask
In [387]: mask = dfd['a'].str.startswith('o')

In [388]: dfd.loc[mask, 'c'] = 42

In [389]: dfd
Out[389]: 
       a   c
0    one  42
1    one  42
2    two   2
3  three   3
4    two   4
5    one  42
6    six   6

# Setting a single item
In [390]: dfd = dfc.copy()

In [391]: dfd.loc[2, 'a'] = 11

In [392]: dfd
Out[392]: 
       a  c
0    one  0
1    one  1
2     11  2
3  three  3
4    two  4
5    one  5
6    six  6

Следующее может работать иногда, но это не гарантировано, и поэтому следует избегать:

In [393]: dfd = dfc.copy()

In [394]: dfd['a'][2] = 111

In [395]: dfd
Out[395]: 
       a  c
0    one  0
1    one  1
2    111  2
3  three  3
4    two  4
5    one  5
6    six  6

Наконец, последующий пример не будет работать вообще и поэтому следует избегать:

In [396]: with pd.option_context('mode.chained_assignment','raise'):
   .....:     dfd.loc[0]['a'] = 1111
   .....: 
---------------------------------------------------------------------------
SettingWithCopyError                      Traceback (most recent call last)
<ipython-input-396-32ce785aaa5b> in ?()
      1 with pd.option_context('mode.chained_assignment','raise'):
----> 2     dfd.loc[0]['a'] = 1111

~/work/pandas/pandas/pandas/core/series.py in ?(self, key, value)
   1284                 )
   1285 
   1286         check_dict_or_set_indexers(key)
   1287         key = com.apply_if_callable(key, self)
-> 1288         cacher_needs_updating = self._check_is_chained_assignment_possible()
   1289 
   1290         if key is Ellipsis:
   1291             key = slice(None)

~/work/pandas/pandas/pandas/core/series.py in ?(self)
   1489             ref = self._get_cacher()
   1490             if ref is not None and ref._is_mixed_type:
   1491                 self._check_setitem_copy(t="referent", force=True)
   1492             return True
-> 1493         return super()._check_is_chained_assignment_possible()

~/work/pandas/pandas/pandas/core/generic.py in ?(self)
   4395         single-dtype meaning that the cacher should be updated following
   4396         setting.
   4397         """
   4398         if self._is_copy:
-> 4399             self._check_setitem_copy(t="referent")
   4400         return False

~/work/pandas/pandas/pandas/core/generic.py in ?(self, t, force)
   4469                 "indexing.html#returning-a-view-versus-a-copy"
   4470             )
   4471 
   4472         if value == "raise":
-> 4473             raise SettingWithCopyError(t)
   4474         if value == "warn":
   4475             warnings.warn(t, SettingWithCopyWarning, stacklevel=find_stack_level())

SettingWithCopyError: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

Предупреждение

Предупреждения/исключения при цепном присваивании предназначены для информирования пользователя о возможном некорректном присваивании. Могут быть ложноположительные результаты; ситуации, когда цепное присваивание ошибочно сообщается.

© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/indexing.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API