Индексирование и выбор данных
Информация об обозначении осей в объектах 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 |
|
DataFrame |
|
Основы
Как упоминалось при представлении структур данных в последнем разделе, основная функция индексирования с помощью [] (также __getitem__ для тех, кто знаком с реализацией поведения класса в Python) заключается в выборе срезов более низкой размерности. В следующей таблице показаны возвращаемые типы значений при индексировании объектов pandas с помощью []:
Тип объекта | Выбор | Тип возвращаемого значения |
|---|---|---|
Series |
| скалярное значение |
DataFrame |
|
|
Здесь мы создаем простой временной ряд данных для иллюстрации функциональности индексирования:
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
Индексация с булевыми значениями
Ещё одна распространённая операция — использование булевых векторов для фильтрации данных. Операторы: | для 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 для больших фреймов.
Вы заметите преимущества использования движка 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.
Индексные объекты
Класс 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