Индексация и выбор данных
Информация о метках осей в объектах pandas служит нескольким целям:
Идентифицирует данные (то есть предоставляет метаданные) с помощью известных индикаторов, важных для анализа, визуализации и интерактивного отображения в консоли.
Обеспечивает автоматическое и явное выравнивание данных.
Позволяет интуитивно получать и задавать подмножества набора данных.
В этом разделе мы сосредоточимся на последнем пункте: рассмотрим, как нарезать данные, выбирать их части и в целом получать и задавать подмножества объектов pandas. Основное внимание будет уделено Series и DataFrame, поскольку в этой области они получили больше внимания при разработке.
Примечание
Операторы индексации Python и NumPy [] и оператор доступа к атрибутам . обеспечивают быстрый и удобный доступ к структурам данных pandas в широком спектре случаев. Благодаря этому интерактивная работа становится интуитивно понятной: если вы уже знаете, как работать со словарями Python и массивами NumPy, вам не придётся осваивать много нового. Однако, поскольку тип данных, к которому выполняется доступ, заранее неизвестен, непосредственное использование стандартных операторов имеет некоторые ограничения в плане оптимизации. В производственном коде мы рекомендуем использовать оптимизированные методы доступа к данным pandas, описанные в этой главе.
Предупреждение
При операции присваивания возврат копии или ссылки может зависеть от контекста. Это иногда называют цепочечным присваиванием; его следует избегать. См. Возврат представления или копии.
Документацию по MultiIndex и более сложной индексации см. в разделе 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', :].
In [1]: ser = pd.Series(range(5), index=list("abcde"))
In [2]: ser.loc[["a", "c", "e"]]
Out[2]:
a 0
c 2
e 4
dtype: int64
In [3]: df = pd.DataFrame(np.arange(25).reshape(5, 5), index=list("abcde"), columns=list("abcde"))
In [4]: df.loc[["a", "c", "e"], ["b", "d"]]
Out[4]:
b d
a 1 3
c 11 13
e 21 23
Основы
Как упоминалось при знакомстве со структурами данных в предыдущем разделе, основная функция индексации с помощью [] (то есть __getitem__, если вы знакомы с реализацией поведения классов в Python) — выбор срезов меньшей размерности. В следующей таблице показаны типы возвращаемых значений при индексации объектов pandas с помощью []:
Тип объекта | Выборка | Тип возвращаемого значения |
|---|---|---|
Series |
| скалярное значение |
DataFrame |
|
|
Для иллюстрации возможностей индексации создадим простой набор данных временного ряда:
In [5]: dates = pd.date_range('1/1/2000', periods=8)
In [6]: df = pd.DataFrame(np.random.randn(8, 4),
...: index=dates, columns=['A', 'B', 'C', 'D'])
...:
In [7]: df
Out[7]:
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 [8]: s = df['A']
In [9]: s[dates[5]]
Out[9]: -0.6736897080883706
В [] можно передать список столбцов, чтобы выбрать их в указанном порядке. Если столбца нет в DataFrame, будет вызвано исключение. Таким же способом можно задавать значения сразу для нескольких столбцов:
In [10]: df
Out[10]:
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 [11]: df[['B', 'A']] = df[['A', 'B']]
In [12]: df
Out[12]:
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
Это может быть полезно, если нужно применить преобразование (на месте) к подмножеству столбцов.
Предупреждение
При присваивании Series и DataFrame из .loc pandas выравнивает все ОСИ.
Это не изменит df, поскольку выравнивание столбцов выполняется до присваивания значений.
In [13]: df[['A', 'B']]
Out[13]:
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 [14]: df.loc[:, ['B', 'A']] = df[['A', 'B']]
In [15]: df[['A', 'B']]
Out[15]:
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 [16]: df.loc[:, ['B', 'A']] = df[['A', 'B']].to_numpy()
In [17]: df[['A', 'B']]
Out[17]:
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 [18]: df[['A', 'B']]
Out[18]:
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 [19]: df.iloc[:, [1, 0]] = df[['A', 'B']]
In [20]: df[['A','B']]
Out[20]:
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 [21]: sa = pd.Series([1, 2, 3], index=list('abc'))
In [22]: dfa = df.copy()
In [23]: sa.b
Out[23]: 2
In [24]: dfa.A
Out[24]:
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 [25]: sa.a = 5
In [26]: sa
Out[26]:
a 5
b 2
c 3
dtype: int64
In [27]: dfa.A = list(range(len(dfa.index))) # ok if A already exists
In [28]: dfa
Out[28]:
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 [29]: dfa['A'] = list(range(len(dfa.index))) # use this form to create a new column
In [30]: dfa
Out[30]:
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, можно также использовать автодополнение по клавише Tab, чтобы увидеть доступные атрибуты.
Кроме того, строке DataFrame можно присвоить dict:
In [31]: x = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 4, 5]})
In [32]: x.iloc[1] = {'x': 9, 'y': 99}
In [33]: x
Out[33]:
x y
0 1 3
1 9 99
2 3 5
С помощью доступа к атрибутам можно изменить существующий элемент Series или столбец DataFrame, но будьте осторожны: если попытаться создать новый столбец таким способом, будет создан новый атрибут, а не столбец, и это вызовет UserWarning:
In [34]: df_new = pd.DataFrame({'one': [1., 2., 3.]})
In [35]: df_new.two = [4, 5, 6]
In [36]: df_new
Out[36]:
one
0 1.0
1 2.0
2 3.0
Срезы диапазонов
Наиболее надёжный и согласованный способ создавать срезы диапазонов по произвольным осям описан в разделе Выбор по позиции, посвящённом методу .iloc. Пока же мы объясним семантику срезов с помощью оператора [].
Для Series синтаксис работает так же, как и с ndarray, возвращая срез значений и соответствующие метки:
In [37]: s[:5]
Out[37]:
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 [38]: s[::2]
Out[38]:
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 [39]: s[::-1]
Out[39]:
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 [40]: s2 = s.copy()
In [41]: s2[:5] = 0
In [42]: s2
Out[42]:
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 [43]: df[:3]
Out[43]:
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 [44]: df[::-1]
Out[44]:
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 [45]: dfl = pd.DataFrame(np.random.randn(5, 4), ....: columns=list('ABCD'), ....: index=pd.date_range('20130101', periods=5)) ....: In [46]: dfl Out[46]: 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 [47]: dfl.loc[2:3] --------------------------------------------------------------------------- TypeError Traceback (most recent call last) Cell In[47], 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:6678, in Index.slice_indexer(self, start, end, step) 6634 def slice_indexer( 6635 self, 6636 start: Hashable | None = None, 6637 end: Hashable | None = None, 6638 step: int | None = None, 6639 ) -> slice: 6640 """ 6641 Compute the slice indexer for input labels and step. 6642 (...) 6676 slice(1, 3, None) 6677 """ -> 6678 start_slice, end_slice = self.slice_locs(start, end, step=step) 6680 # return a slice 6681 if not is_scalar(start_slice): File ~/work/pandas/pandas/pandas/core/indexes/base.py:6904, in Index.slice_locs(self, start, end, step) 6902 start_slice = None 6903 if start is not None: -> 6904 start_slice = self.get_slice_bound(start, "left") 6905 if start_slice is None: 6906 start_slice = 0 File ~/work/pandas/pandas/pandas/core/indexes/base.py:6819, in Index.get_slice_bound(self, label, side) 6815 original_label = label 6817 # For datetime indices label may be a string that has to be converted 6818 # to datetime boundary according to its resolution. -> 6819 label = self._maybe_cast_slice_bound(label, side) 6821 # we need to look up the label 6822 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:4308, in Index._raise_invalid_indexer(self, form, key, reraise) 4306 if reraise is not lib.no_default: 4307 raise TypeError(msg) from reraise -> 4308 raise TypeError(msg) TypeError: cannot do slice indexing on DatetimeIndex with these indexers [2] of type int
Строковые значения в срезах могут быть приведены к типу индекса, что позволяет выполнять естественную нарезку.
In [48]: dfl.loc['20130102':'20130104']
Out[48]:
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 [49]: s1 = pd.Series(np.random.randn(6), index=list('abcdef'))
In [50]: s1
Out[50]:
a 1.431256
b 1.340309
c -1.170299
d -0.226169
e 0.410835
f 0.813850
dtype: float64
In [51]: s1.loc['c':]
Out[51]:
c -1.170299
d -0.226169
e 0.410835
f 0.813850
dtype: float64
In [52]: s1.loc['b']
Out[52]: 1.3403088497993827
Обратите внимание, что присваивание тоже работает:
In [53]: s1.loc['c':] = 0
In [54]: s1
Out[54]:
a 1.431256
b 1.340309
c 0.000000
d 0.000000
e 0.000000
f 0.000000
dtype: float64
Для DataFrame:
In [55]: df1 = pd.DataFrame(np.random.randn(6, 4),
....: index=list('abcdef'),
....: columns=list('ABCD'))
....:
In [56]: df1
Out[56]:
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 [57]: df1.loc[['a', 'b', 'd'], :]
Out[57]:
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 [58]: df1.loc['d':, 'A':'C']
Out[58]:
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 [59]: df1.loc['a']
Out[59]:
A 0.132003
B -0.827317
C -0.076467
D -1.187678
Name: a, dtype: float64
Получение значений с помощью булева массива:
In [60]: df1.loc['a'] > 0
Out[60]:
A True
B False
C False
D False
Name: a, dtype: bool
In [61]: df1.loc[:, df1.loc['a'] > 0]
Out[61]:
A
a 0.132003
b 1.130127
c 1.024180
d 0.974466
e 0.545952
f -1.281247
Значения NA в булевом массиве распространяются как False:
In [62]: mask = pd.array([True, False, True, False, pd.NA, False], dtype="boolean")
In [63]: mask
Out[63]:
<BooleanArray>
[True, False, True, False, <NA>, False]
Length: 6, dtype: boolean
In [64]: df1[mask]
Out[64]:
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 [65]: df1.loc['a', 'A']
Out[65]: 0.13200317033032932
Срезы по меткам
При использовании .loc со срезами, если начальная и конечная метки присутствуют в индексе, возвращаются элементы, расположенные между ними (включая сами эти метки):
In [66]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4])
In [67]: s.loc[3:5]
Out[67]:
3 b
2 c
5 d
dtype: object
Если хотя бы одна из двух меток отсутствует, но индекс отсортирован и допускает сравнение с начальной и конечной метками, срез всё равно будет работать ожидаемым образом: будут выбраны метки, занимающие положение между этими двумя:
In [68]: s.sort_index()
Out[68]:
0 a
2 c
3 b
4 e
5 d
dtype: object
In [69]: s.sort_index().loc[1:6]
Out[69]:
2 c
3 b
4 e
5 d
dtype: object
Однако если хотя бы одна из двух меток отсутствует и индекс не отсортирован, будет вызвана ошибка (поскольку в противном случае операция была бы вычислительно затратной, а для индексов со смешанными типами — потенциально неоднозначной). Например, в приведённом выше примере s.loc[1:6] вызовет KeyError.
Обоснование такого поведения см. в разделе Конечные точки включаются.
In [70]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2])
In [71]: s.loc[3:5]
Out[71]:
3 b
2 c
5 d
dtype: object
Кроме того, если индекс содержит дублирующиеся метки и начальная или конечная метка дублируется, будет вызвана ошибка. Например, в приведённом выше примере s.loc[2:5] вызовет KeyError.
Дополнительную информацию о дублирующихся метках см. в разделе Дублирующиеся метки.
Выбор по позиции
Предупреждение
При операции присваивания возврат копии или ссылки может зависеть от контекста. Это иногда называют chained assignment; его следует избегать. См. Возврат представления или копии.
В pandas предусмотрен набор методов для индексации исключительно по целым числам. Семантика близка к нарезке в Python и NumPy. Эти методы выполняют индексацию 0-based. При создании среза начальная граница включается, а верхняя граница не включается. Попытка использовать нецелое число, даже допустимую метку, вызовет IndexError.
Основным способом доступа является атрибут .iloc. Допустимы следующие входные данные:
Целое число, например
5.Список или массив целых чисел
[4, 3, 0].Объект среза с целыми числами
1:7.Булев массив.
Объект
callable; см. раздел Выбор с помощью вызываемого объекта.Кортеж индексов строк (и столбцов), элементы которого относятся к одному из перечисленных выше типов.
In [72]: s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2)))
In [73]: s1
Out[73]:
0 0.695775
2 0.341734
4 0.959726
6 -1.110336
8 -0.619976
dtype: float64
In [74]: s1.iloc[:3]
Out[74]:
0 0.695775
2 0.341734
4 0.959726
dtype: float64
In [75]: s1.iloc[3]
Out[75]: -1.110336102891167
Обратите внимание, что присваивание тоже работает:
In [76]: s1.iloc[:3] = 0
In [77]: s1
Out[77]:
0 0.000000
2 0.000000
4 0.000000
6 -1.110336
8 -0.619976
dtype: float64
Для DataFrame:
In [78]: df1 = pd.DataFrame(np.random.randn(6, 4),
....: index=list(range(0, 12, 2)),
....: columns=list(range(0, 8, 2)))
....:
In [79]: df1
Out[79]:
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 [80]: df1.iloc[:3]
Out[80]:
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 [81]: df1.iloc[1:5, 2:4]
Out[81]:
4 6
2 0.301624 -2.179861
4 1.462696 -1.743161
6 1.314232 0.690579
8 0.014871 3.357427
Выбор с помощью списка целых чисел:
In [82]: df1.iloc[[1, 3, 5], [1, 3]]
Out[82]:
2 6
2 -0.154951 -2.179861
6 -0.345352 0.690579
10 -1.236269 -0.487602
In [83]: df1.iloc[1:3, :]
Out[83]:
0 2 4 6
2 0.403310 -0.154951 0.301624 -2.179861
4 -1.369849 -0.954208 1.462696 -1.743161
In [84]: df1.iloc[:, 1:3]
Out[84]:
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 [85]: df1.iloc[1, 1]
Out[85]: -0.1549507744249032
Получение среза по целочисленной позиции (эквивалентно df.xs(1)):
In [86]: df1.iloc[1]
Out[86]:
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 [87]: x = list('abcdef')
In [88]: x
Out[88]: ['a', 'b', 'c', 'd', 'e', 'f']
In [89]: x[4:10]
Out[89]: ['e', 'f']
In [90]: x[8:10]
Out[90]: []
In [91]: s = pd.Series(x)
In [92]: s
Out[92]:
0 a
1 b
2 c
3 d
4 e
5 f
dtype: object
In [93]: s.iloc[4:10]
Out[93]:
4 e
5 f
dtype: object
In [94]: s.iloc[8:10]
Out[94]: Series([], dtype: object)
Обратите внимание: срезы, выходящие за границы, могут привести к пустой оси (например, будет возвращён пустой DataFrame).
In [95]: dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB'))
In [96]: dfl
Out[96]:
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 [97]: dfl.iloc[:, 2:3]
Out[97]:
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]
In [98]: dfl.iloc[:, 1:3]
Out[98]:
B
0 -2.182937
1 0.084844
2 1.519970
3 0.600178
4 0.132885
In [99]: dfl.iloc[4:6]
Out[99]:
A B
4 0.27423 0.132885
Если один индексатор выходит за границы, будет вызван IndexError. Если хотя бы один элемент списка индексаторов выходит за границы, будет вызван IndexError.
In [100]: 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:4172, in NDFrame._take_with_is_copy(self, indices, axis)
4163 """
4164 Internal version of the `take` method that sets the `_is_copy`
4165 attribute to keep track of the parent dataframe (using in indexing
(...)
4170 See the docstring of `take` for full explanation of the parameters.
4171 """
-> 4172 result = self.take(indices=indices, axis=axis)
4173 # Maybe set copy if we didn't actually change the index.
File ~/work/pandas/pandas/pandas/core/generic.py:4152, in NDFrame.take(self, indices, axis, **kwargs)
4148 indices = np.arange(
4149 indices.start, indices.stop, indices.step, dtype=np.intp
4150 )
-> 4152 new_data = self._mgr.take(
4153 indices,
4154 axis=self._get_block_manager_axis(axis),
4155 verify=True,
4156 )
4157 return self._constructor_from_mgr(new_data, axes=new_data.axes).__finalize__(
4158 self, method="take"
4159 )
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[100], 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 [101]: dfl.iloc[:, 4]
---------------------------------------------------------------------------
IndexError Traceback (most recent call last)
Cell In[101], 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
Выбор с помощью вызываемого объекта
Для индексации с помощью .loc, .iloc, а также [] в качестве индексатора можно использовать callable. callable должна быть функцией с одним аргументом (вызываемый объект Series или DataFrame), возвращающей допустимый для индексации результат.
Примечание
Для индексации с помощью .iloc возврат кортежа из вызываемого объекта не поддерживается, поскольку распаковка кортежа в индексы строк и столбцов выполняется до применения вызываемых объектов.
In [102]: df1 = pd.DataFrame(np.random.randn(6, 4),
.....: index=list('abcdef'),
.....: columns=list('ABCD'))
.....:
In [103]: df1
Out[103]:
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 [104]: df1.loc[lambda df: df['A'] > 0, :]
Out[104]:
A B C D
c 0.299368 -0.863838 0.408204 -1.048089
e 1.289997 0.082423 -0.055758 0.536580
In [105]: df1.loc[:, lambda df: ['A', 'B']]
Out[105]:
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 [106]: df1.iloc[:, lambda df: [0, 1]]
Out[106]:
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 [107]: df1[lambda df: df.columns[0]]
Out[107]:
a -0.023688
b -0.251905
c 0.299368
d -0.025747
e 1.289997
f -0.489682
Name: A, dtype: float64
Индексацию с помощью вызываемого объекта можно использовать в Series.
In [108]: df1['A'].loc[lambda s: s > 0]
Out[108]:
c 0.299368
e 1.289997
Name: A, dtype: float64
С помощью этих методов и индексаторов можно объединять операции выбора данных в цепочки, не используя временную переменную.
In [109]: bb = pd.read_csv('data/baseball.csv', index_col='id')
In [110]: (bb.groupby(['year', 'team']).sum(numeric_only=True)
.....: .loc[lambda df: df['r'] > 100])
.....:
Out[110]:
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 [111]: dfd = pd.DataFrame({'A': [1, 2, 3],
.....: 'B': [4, 5, 6]},
.....: index=list('abc'))
.....:
In [112]: dfd
Out[112]:
A B
a 1 4
b 2 5
c 3 6
In [113]: dfd.loc[dfd.index[[0, 2]], 'A']
Out[113]:
a 1
c 3
Name: A, dtype: int64
Это также можно выразить с помощью .iloc: явно получить позиции в индексаторах, а затем выбрать элементы с помощью индексации по позициям.
In [114]: dfd.iloc[[0, 2], dfd.columns.get_loc('A')]
Out[114]:
a 1
c 3
Name: A, dtype: int64
Чтобы получить несколько индексаторов, используйте .get_indexer:
In [115]: dfd.iloc[[0, 2], dfd.columns.get_indexer(['A', 'B'])]
Out[115]:
A B
a 1 4
c 3 6
Переиндексация
Идиоматичный способ выбрать элементы, которые могут отсутствовать, — использовать .reindex(). См. также раздел о переиндексации.
In [116]: s = pd.Series([1, 2, 3])
In [117]: s.reindex([1, 2, 3])
Out[117]:
1 2.0
2 3.0
3 NaN
dtype: float64
В качестве альтернативы, если нужно выбрать только допустимые ключи, следующий способ является идиоматичным и эффективным; он гарантированно сохраняет dtype выборки.
In [118]: labels = [1, 2, 3]
In [119]: s.loc[s.index.intersection(labels)]
Out[119]:
1 2
2 3
dtype: int64
Дублирующийся индекс вызовет ошибку при использовании .reindex():
In [120]: s = pd.Series(np.arange(4), index=['a', 'a', 'b', 'c'])
In [121]: labels = ['c', 'd']
In [122]: s.reindex(labels)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[122], line 1
----> 1 s.reindex(labels)
File ~/work/pandas/pandas/pandas/core/series.py:5164, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
5147 @doc(
5148 NDFrame.reindex, # type: ignore[has-type]
5149 klass=_shared_doc_kwargs["klass"],
(...)
5162 tolerance=None,
5163 ) -> Series:
-> 5164 return super().reindex(
5165 index=index,
5166 method=method,
5167 copy=copy,
5168 level=level,
5169 fill_value=fill_value,
5170 limit=limit,
5171 tolerance=tolerance,
5172 )
File ~/work/pandas/pandas/pandas/core/generic.py:5629, in NDFrame.reindex(self, labels, index, columns, axis, method, copy, level, fill_value, limit, tolerance)
5626 return self._reindex_multi(axes, copy, fill_value)
5628 # perform the reindex on the axes
-> 5629 return self._reindex_axes(
5630 axes, level, limit, tolerance, method, fill_value, copy
5631 ).__finalize__(self, method="reindex")
File ~/work/pandas/pandas/pandas/core/generic.py:5652, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
5649 continue
5651 ax = self._get_axis(a)
-> 5652 new_index, indexer = ax.reindex(
5653 labels, level=level, limit=limit, tolerance=tolerance, method=method
5654 )
5656 axis = self._get_axis_number(a)
5657 obj = obj._reindex_with_indexers(
5658 {axis: [new_index, indexer]},
5659 fill_value=fill_value,
5660 copy=copy,
5661 allow_dups=False,
5662 )
File ~/work/pandas/pandas/pandas/core/indexes/base.py:4436, in Index.reindex(self, target, method, level, limit, tolerance)
4433 raise ValueError("cannot handle a non-unique multi-index!")
4434 elif not self.is_unique:
4435 # GH#42568
-> 4436 raise ValueError("cannot reindex on an axis with duplicate labels")
4437 else:
4438 indexer, _ = self.get_indexer_non_unique(target)
ValueError: cannot reindex on an axis with duplicate labels
Как правило, можно пересечь нужные метки с текущей осью, а затем выполнить переиндексацию.
In [123]: s.loc[s.index.intersection(labels)].reindex(labels)
Out[123]:
c 3.0
d NaN
dtype: float64
Однако это всё равно вызовет ошибку, если полученный индекс окажется дублирующимся.
In [124]: labels = ['a', 'd']
In [125]: s.loc[s.index.intersection(labels)].reindex(labels)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[125], line 1
----> 1 s.loc[s.index.intersection(labels)].reindex(labels)
File ~/work/pandas/pandas/pandas/core/series.py:5164, in Series.reindex(self, index, axis, method, copy, level, fill_value, limit, tolerance)
5147 @doc(
5148 NDFrame.reindex, # type: ignore[has-type]
5149 klass=_shared_doc_kwargs["klass"],
(...)
5162 tolerance=None,
5163 ) -> Series:
-> 5164 return super().reindex(
5165 index=index,
5166 method=method,
5167 copy=copy,
5168 level=level,
5169 fill_value=fill_value,
5170 limit=limit,
5171 tolerance=tolerance,
5172 )
File ~/work/pandas/pandas/pandas/core/generic.py:5629, in NDFrame.reindex(self, labels, index, columns, axis, method, copy, level, fill_value, limit, tolerance)
5626 return self._reindex_multi(axes, copy, fill_value)
5628 # perform the reindex on the axes
-> 5629 return self._reindex_axes(
5630 axes, level, limit, tolerance, method, fill_value, copy
5631 ).__finalize__(self, method="reindex")
File ~/work/pandas/pandas/pandas/core/generic.py:5652, in NDFrame._reindex_axes(self, axes, level, limit, tolerance, method, fill_value, copy)
5649 continue
5651 ax = self._get_axis(a)
-> 5652 new_index, indexer = ax.reindex(
5653 labels, level=level, limit=limit, tolerance=tolerance, method=method
5654 )
5656 axis = self._get_axis_number(a)
5657 obj = obj._reindex_with_indexers(
5658 {axis: [new_index, indexer]},
5659 fill_value=fill_value,
5660 copy=copy,
5661 allow_dups=False,
5662 )
File ~/work/pandas/pandas/pandas/core/indexes/base.py:4436, in Index.reindex(self, target, method, level, limit, tolerance)
4433 raise ValueError("cannot handle a non-unique multi-index!")
4434 elif not self.is_unique:
4435 # GH#42568
-> 4436 raise ValueError("cannot reindex on an axis with duplicate labels")
4437 else:
4438 indexer, _ = self.get_indexer_non_unique(target)
ValueError: cannot reindex on an axis with duplicate labels
Выбор случайных выборок
Случайную выборку строк или столбцов из Series или DataFrame можно получить с помощью метода sample(). По умолчанию метод выбирает строки; он принимает конкретное число строк или столбцов для возврата либо долю строк.
In [126]: s = pd.Series([0, 1, 2, 3, 4, 5])
# When no arguments are passed, returns 1 row.
In [127]: s.sample()
Out[127]:
4 4
dtype: int64
# One may specify either a number of rows:
In [128]: s.sample(n=3)
Out[128]:
0 0
4 4
1 1
dtype: int64
# Or a fraction of the rows:
In [129]: s.sample(frac=0.5)
Out[129]:
5 5
3 3
1 1
dtype: int64
По умолчанию sample возвращает каждую строку не более одного раза, однако можно выполнять выборку с возвращением, используя параметр replace:
In [130]: s = pd.Series([0, 1, 2, 3, 4, 5])
# Without replacement (default):
In [131]: s.sample(n=6, replace=False)
Out[131]:
0 0
1 1
5 5
3 3
2 2
4 4
dtype: int64
# With replacement:
In [132]: s.sample(n=6, replace=True)
Out[132]:
0 0
4 4
3 3
2 2
4 4
4 4
dtype: int64
По умолчанию вероятность выбора каждой строки одинакова. Если нужны разные вероятности для разных строк, можно передать веса выборки функции sample в виде weights. Веса могут быть списком, массивом NumPy или Series, но их длина должна совпадать с длиной объекта, из которого выполняется выборка. Пропущенные значения будут считаться весом, равным нулю, а бесконечные значения не допускаются. Если сумма весов не равна 1, они будут нормализованы повторно путём деления каждого веса на сумму весов. Например:
In [133]: s = pd.Series([0, 1, 2, 3, 4, 5])
In [134]: example_weights = [0, 0, 0.2, 0.2, 0.2, 0.4]
In [135]: s.sample(n=3, weights=example_weights)
Out[135]:
5 5
4 4
3 3
dtype: int64
# Weights will be re-normalized automatically
In [136]: example_weights2 = [0.5, 0, 0, 0, 0, 0]
In [137]: s.sample(n=1, weights=example_weights2)
Out[137]:
0 0
dtype: int64
При применении к DataFrame в качестве весов выборки можно использовать столбец DataFrame (если выбираются строки, а не столбцы), просто передав имя столбца в виде строки.
In [138]: df2 = pd.DataFrame({'col1': [9, 8, 7, 6],
.....: 'weight_column': [0.5, 0.4, 0.1, 0]})
.....:
In [139]: df2.sample(n=3, weights='weight_column')
Out[139]:
col1 weight_column
1 8 0.4
0 9 0.5
2 7 0.1
Кроме того, sample позволяет выбирать столбцы, а не строки, с помощью аргумента axis.
In [140]: df3 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})
In [141]: df3.sample(n=1, axis=1)
Out[141]:
col1
0 1
1 2
2 3
Наконец, можно задать начальное значение для генератора случайных чисел sample с помощью аргумента random_state, принимающего целое число (в качестве начального значения) или объект RandomState из NumPy.
In [142]: df4 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})
# With a given seed, the sample will always draw the same rows.
In [143]: df4.sample(n=2, random_state=2)
Out[143]:
col1 col2
2 3 4
1 2 3
In [144]: df4.sample(n=2, random_state=2)
Out[144]:
col1 col2
2 3 4
1 2 3
Присваивание с расширением
Операции .loc/[] могут расширять объект, если при присваивании для этой оси указан несуществующий ключ.
В случае Series это фактически операция добавления.
In [145]: se = pd.Series([1, 2, 3])
In [146]: se
Out[146]:
0 1
1 2
2 3
dtype: int64
In [147]: se[5] = 5.
In [148]: se
Out[148]:
0 1.0
1 2.0
2 3.0
5 5.0
dtype: float64
Объект DataFrame можно расширить по любой из осей с помощью .loc.
In [149]: dfi = pd.DataFrame(np.arange(6).reshape(3, 2),
.....: columns=['A', 'B'])
.....:
In [150]: dfi
Out[150]:
A B
0 0 1
1 2 3
2 4 5
In [151]: dfi.loc[:, 'C'] = dfi.loc[:, 'A']
In [152]: dfi
Out[152]:
A B C
0 0 1 0
1 2 3 2
2 4 5 4
Это похоже на операцию append для DataFrame.
In [153]: dfi.loc[3] = 5
In [154]: dfi
Out[154]:
A B C
0 0 1 0
1 2 3 2
2 4 5 4
3 5 5 5
Быстрое получение и присваивание скалярных значений
Индексация с помощью [] должна обрабатывать множество случаев (доступ по одной метке, срезы, булева индексация и т. д.), поэтому ей требуется время на определение того, что именно вы запрашиваете. Если нужен доступ только к скалярному значению, быстрее всего использовать методы at и iat, реализованные для всех структур данных.
Подобно loc, at обеспечивает скалярный доступ по метке, тогда как iat обеспечивает доступ по целочисленной позиции, аналогично iloc.
In [155]: s.iat[5]
Out[155]: 5
In [156]: df.at[dates[5], 'A']
Out[156]: 0.1136484096888855
In [157]: df.iat[3, 0]
Out[157]: -0.7067711336300845
Эти же индексаторы можно использовать и для присваивания.
In [158]: df.at[dates[5], 'E'] = 7
In [159]: df.iat[3, 0] = 7
Как и в примере выше, at может расширить объект на месте, если индексатор отсутствует.
In [160]: df.at[dates[-1] + pd.Timedelta('1 day'), 0] = 7
In [161]: df
Out[161]:
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 [162]: s = pd.Series(range(-3, 4))
In [163]: s
Out[163]:
0 -3
1 -2
2 -1
3 0
4 1
5 2
6 3
dtype: int64
In [164]: s[s > 0]
Out[164]:
4 1
5 2
6 3
dtype: int64
In [165]: s[(s < -1) | (s > 0.5)]
Out[165]:
0 -3
1 -2
4 1
5 2
6 3
dtype: int64
In [166]: s[~(s < 0)]
Out[166]:
3 0
4 1
5 2
6 3
dtype: int64
Вы можете выбирать строки из DataFrame с помощью булева вектора той же длины, что и индекс DataFrame (например, вектора, полученного из одного из столбцов DataFrame):
In [167]: df[df['A'] > 0]
Out[167]:
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 [168]: 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 [169]: criterion = df2['a'].map(lambda x: x.startswith('t'))
In [170]: df2[criterion]
Out[170]:
a b c
2 two y 0.041290
3 three x 0.361719
4 two y -0.238075
# equivalent but slower
In [171]: df2[[x.startswith('t') for x in df2['a']]]
Out[171]:
a b c
2 two y 0.041290
3 three x 0.361719
4 two y -0.238075
# Multiple criteria
In [172]: df2[criterion & (df2['b'] == 'x')]
Out[172]:
a b c
3 three x 0.361719
С помощью методов выбора Выбор по метке, Выбор по позиции и Расширенная индексация можно выбирать данные по нескольким осям, используя булевы векторы в сочетании с другими выражениями индексации.
In [173]: df2.loc[criterion & (df2['b'] == 'x'), 'b':'c']
Out[173]:
b c
3 x 0.361719
Предупреждение
iloc поддерживает два вида булевой индексации. Если индексатор — булев Series, будет вызвана ошибка. Например, в следующем примере df.iloc[s.values, 1] допустимо. Булев индексатор представляет собой массив. Однако df.iloc[s, 1] вызовет ValueError.
In [174]: df = pd.DataFrame([[1, 2], [3, 4], [5, 6]],
.....: index=list('abc'),
.....: columns=['A', 'B'])
.....:
In [175]: s = (df['A'] > 2)
In [176]: s
Out[176]:
a False
b True
c True
Name: A, dtype: bool
In [177]: df.loc[s, 'B']
Out[177]:
b 4
c 6
Name: B, dtype: int64
In [178]: df.iloc[s.values, 1]
Out[178]:
b 4
c 6
Name: B, dtype: int64
Индексация с помощью isin
Рассмотрим метод isin() класса Series, который возвращает булев вектор со значением True для элементов Series, присутствующих в переданном списке. Это позволяет выбирать строки, в которых один или несколько столбцов содержат нужные значения:
In [179]: s = pd.Series(np.arange(5), index=np.arange(5)[::-1], dtype='int64')
In [180]: s
Out[180]:
4 0
3 1
2 2
1 3
0 4
dtype: int64
In [181]: s.isin([2, 4, 6])
Out[181]:
4 False
3 False
2 True
1 False
0 True
dtype: bool
In [182]: s[s.isin([2, 4, 6])]
Out[182]:
2 2
0 4
dtype: int64
Этот же метод доступен для объектов Index и полезен в случаях, когда неизвестно, какие из искомых меток действительно присутствуют:
In [183]: s[s.index.isin([2, 4, 6])]
Out[183]:
4 0
2 2
dtype: int64
# compare it to the following
In [184]: s.reindex([2, 4, 6])
Out[184]:
2 2.0
4 0.0
6 NaN
dtype: float64
Кроме того, MultiIndex позволяет выбрать отдельный уровень для проверки принадлежности:
In [185]: s_mi = pd.Series(np.arange(6),
.....: index=pd.MultiIndex.from_product([[0, 1], ['a', 'b', 'c']]))
.....:
In [186]: s_mi
Out[186]:
0 a 0
b 1
c 2
1 a 3
b 4
c 5
dtype: int64
In [187]: s_mi.iloc[s_mi.index.isin([(1, 'a'), (2, 'b'), (0, 'c')])]
Out[187]:
0 c 2
1 a 3
dtype: int64
In [188]: s_mi.iloc[s_mi.index.isin(['a', 'c', 'e'], level=1)]
Out[188]:
0 a 0
c 2
1 a 3
c 5
dtype: int64
У DataFrame также есть метод isin(). При вызове isin передайте набор значений в виде массива или словаря. Если values — это массив, isin возвращает DataFrame булевых значений той же формы, что и исходный DataFrame, со значением True там, где элемент входит в последовательность значений.
In [189]: df = pd.DataFrame({'vals': [1, 2, 3, 4], 'ids': ['a', 'b', 'f', 'n'],
.....: 'ids2': ['a', 'n', 'c', 'n']})
.....:
In [190]: values = ['a', 'b', 1, 3]
In [191]: df.isin(values)
Out[191]:
vals ids ids2
0 True True True
1 False True False
2 True False False
3 False False False
Часто требуется сопоставить определённые значения с определёнными столбцами. Для этого достаточно задать values как dict, где ключом является столбец, а значением — список элементов для проверки.
In [192]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}
In [193]: df.isin(values)
Out[193]:
vals ids ids2
0 True True False
1 False True False
2 True False False
3 False False False
Чтобы вернуть DataFrame из булевых значений, в котором значения не входят в исходный DataFrame, используйте оператор ~:
In [194]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}
In [195]: ~df.isin(values)
Out[195]:
vals ids ids2
0 False False True
1 True False True
2 False True True
3 True True True
Сочетайте isin DataFrame с методами any() и all(), чтобы быстро выбирать подмножества данных, удовлетворяющие заданному критерию. Чтобы выбрать строку, в которой каждый столбец удовлетворяет собственному критерию:
In [196]: values = {'ids': ['a', 'b'], 'ids2': ['a', 'c'], 'vals': [1, 3]}
In [197]: row_mask = df.isin(values).all(1)
In [198]: df[row_mask]
Out[198]:
vals ids ids2
0 1 a a
Метод where() и маскирование
Выбор значений из Series с помощью булева вектора обычно возвращает подмножество данных. Чтобы гарантировать, что результат выборки будет иметь ту же форму, что и исходные данные, можно использовать метод where в Series и DataFrame.
Чтобы вернуть только выбранные строки:
In [199]: s[s > 0]
Out[199]:
3 1
2 2
1 3
0 4
dtype: int64
Чтобы вернуть Series той же формы, что и исходная:
In [200]: s.where(s > 0)
Out[200]:
4 NaN
3 1.0
2 2.0
1 3.0
0 4.0
dtype: float64
При выборе значений из DataFrame с помощью булева критерия исходная форма данных также сохраняется. Внутренне для реализации используется where. Приведённый ниже код эквивалентен df.where(df < 0).
In [201]: dates = pd.date_range('1/1/2000', periods=8)
In [202]: df = pd.DataFrame(np.random.randn(8, 4),
.....: index=dates, columns=['A', 'B', 'C', 'D'])
.....:
In [203]: df[df < 0]
Out[203]:
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 для замены значений, для которых условие ложно, в возвращаемой копии.
In [204]: df.where(df < 0, -df)
Out[204]:
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 [205]: s2 = s.copy()
In [206]: s2[s2 < 0] = 0
In [207]: s2
Out[207]:
4 0
3 1
2 2
1 3
0 4
dtype: int64
In [208]: df2 = df.copy()
In [209]: df2[df2 < 0] = 0
In [210]: df2
Out[210]:
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 [211]: df.where(df < 0, -df) == np.where(df < 0, df, -df)
Out[211]:
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 [212]: df2 = df.copy()
In [213]: df2[df2[1:4] > 0] = 3
In [214]: df2
Out[214]:
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
При выполнении where метод where также принимает параметры axis и level для выравнивания входных данных.
In [215]: df2 = df.copy()
In [216]: df2.where(df2 > 0, df2['A'], axis='index')
Out[216]:
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 [217]: df2 = df.copy()
In [218]: df.apply(lambda x, y: x.where(x > 0, y), y=df['A'])
Out[218]:
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
В качестве условия и аргументов other метод where может принимать вызываемый объект. Функция должна принимать один аргумент (вызывающий её объект Series или DataFrame) и возвращать допустимый результат для условия и аргумента other.
In [219]: df3 = pd.DataFrame({'A': [1, 2, 3],
.....: 'B': [4, 5, 6],
.....: 'C': [7, 8, 9]})
.....:
In [220]: df3.where(lambda x: x > 4, lambda x: x + 10)
Out[220]:
A B C
0 11 14 7
1 12 5 8
2 13 6 9
Маска
mask() выполняет обратную булеву операцию по отношению к where.
In [221]: s.mask(s >= 0)
Out[221]:
4 NaN
3 NaN
2 NaN
1 NaN
0 NaN
dtype: float64
In [222]: df.mask(df >= 0)
Out[222]:
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 [223]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})
In [224]: df['color'] = np.where(df['col2'] == 'Z', 'green', 'red')
In [225]: df
Out[225]:
col1 col2 color
0 A Z green
1 B Z green
2 B X red
3 C Y red
Если условий несколько, для этого можно использовать numpy.select(). Предположим, что трём условиям соответствуют три варианта цвета, а четвёртый цвет используется по умолчанию. Тогда можно сделать следующее:
In [226]: conditions = [
.....: (df['col2'] == 'Z') & (df['col1'] == 'A'),
.....: (df['col2'] == 'Z') & (df['col1'] == 'B'),
.....: (df['col1'] == 'B')
.....: ]
.....:
In [227]: choices = ['yellow', 'blue', 'purple']
In [228]: df['color'] = np.select(conditions, choices, default='black')
In [229]: df
Out[229]:
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 [230]: n = 10
In [231]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [232]: df
Out[232]:
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 [233]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[233]:
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 [234]: df.query('(a < b) & (b < c)')
Out[234]:
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 [235]: df = pd.DataFrame(np.random.randint(n / 2, size=(n, 2)), columns=list('bc'))
In [236]: df.index.name = 'a'
In [237]: df
Out[237]:
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 [238]: df.query('a < b and b < c')
Out[238]:
b c
a
2 3 4
Если вы не хотите или не можете задать имя индекса, используйте в выражении запроса имя index:
In [239]: df = pd.DataFrame(np.random.randint(n, size=(n, 2)), columns=list('bc'))
In [240]: df
Out[240]:
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 [241]: df.query('index < b < c')
Out[241]:
b c
2 5 6
Примечание
Если имя индекса совпадает с именем столбца, приоритет отдаётся имени столбца. Например:
In [242]: df = pd.DataFrame({'a': np.random.randint(5, size=5)})
In [243]: df.index.name = 'a'
In [244]: df.query('a > 2') # uses the column 'a', not the index
Out[244]:
a
a
1 3
3 3
Тем не менее, индекс можно использовать в выражении запроса, указав специальный идентификатор ‘index’:
In [245]: df.query('index > 2')
Out[245]:
a
a
3 3
4 2
Если по какой-либо причине у вас есть столбец с именем index, индекс также можно указать как ilevel_0, но в этом случае стоит переименовать столбцы, чтобы избежать неоднозначности.
Синтаксис MultiIndex для query()
Уровни DataFrame также можно использовать вместе с MultiIndex, как если бы они были столбцами фрейма:
In [246]: n = 10
In [247]: colors = np.random.choice(['red', 'green'], size=n)
In [248]: foods = np.random.choice(['eggs', 'ham'], size=n)
In [249]: colors
Out[249]:
array(['red', 'red', 'red', 'green', 'green', 'green', 'green', 'green',
'green', 'green'], dtype='<U5')
In [250]: foods
Out[250]:
array(['ham', 'ham', 'eggs', 'eggs', 'eggs', 'ham', 'ham', 'eggs', 'eggs',
'eggs'], dtype='<U4')
In [251]: index = pd.MultiIndex.from_arrays([colors, foods], names=['color', 'food'])
In [252]: df = pd.DataFrame(np.random.randn(n, 2), index=index)
In [253]: df
Out[253]:
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 [254]: df.query('color == "red"')
Out[254]:
0 1
color food
red ham 0.194889 -0.381994
ham 0.318587 2.089075
eggs -0.728293 -0.090255
Если уровни MultiIndex не имеют имён, на них можно ссылаться с помощью специальных имён:
In [255]: df.index.names = [None, None]
In [256]: df
Out[256]:
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 [257]: df.query('ilevel_0 == "red"')
Out[257]:
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 [258]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [259]: df
Out[259]:
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 [260]: df2 = pd.DataFrame(np.random.rand(n + 2, 3), columns=df.columns)
In [261]: df2
Out[261]:
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 [262]: expr = '0.0 <= a <= c <= 0.5'
In [263]: map(lambda frame: frame.query(expr), [df, df2])
Out[263]: <map at 0x7f946de71e40>
Сравнение синтаксиса query() в Python и pandas
Полный синтаксис в стиле NumPy:
In [264]: df = pd.DataFrame(np.random.randint(n, size=(n, 3)), columns=list('abc'))
In [265]: df
Out[265]:
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 [266]: df.query('(a < b) & (b < c)')
Out[266]:
a b c
0 7 8 9
In [267]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[267]:
a b c
0 7 8 9
Немного удобнее без скобок (операторы сравнения имеют более высокий приоритет, чем & и |):
In [268]: df.query('a < b & b < c')
Out[268]:
a b c
0 7 8 9
Использование слов вместо символов:
In [269]: df.query('a < b and b < c')
Out[269]:
a b c
0 7 8 9
Почти так же, как это можно было бы записать на бумаге:
In [270]: df.query('a < b < c')
Out[270]:
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 [271]: df = pd.DataFrame({'a': list('aabbccddeeff'), 'b': list('aaaabbbbcccc'),
.....: 'c': np.random.randint(5, size=12),
.....: 'd': np.random.randint(9, size=12)})
.....:
In [272]: df
Out[272]:
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 [273]: df.query('a in b')
Out[273]:
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 [274]: df[df['a'].isin(df['b'])]
Out[274]:
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 [275]: df.query('a not in b')
Out[275]:
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 [276]: df[~df['a'].isin(df['b'])]
Out[276]:
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 [277]: df.query('a in b and c < d')
Out[277]:
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 [278]: df[df['b'].isin(df['a']) & (df['c'] < df['d'])]
Out[278]:
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 нет аналога этой операции. Однако в обычном Python вычисляется только само выражение in/not in. Например, в выражении
df.query('a in b + c + d')
(b + c + d) вычисляется с помощью numexpr, а затем операция in вычисляется в обычном Python. В общем случае любые операции, которые можно вычислить с помощью numexpr, будут вычислены именно так.
Особое использование оператора == с объектами list
Сравнение list значений со столбцом с помощью ==/!= работает аналогично in/not in.
In [279]: df.query('b == ["a", "b", "c"]')
Out[279]:
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 [280]: df[df['b'].isin(["a", "b", "c"])]
Out[280]:
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 [281]: df.query('c == [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
In [282]: df.query('c != [1, 2]')
Out[282]:
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 [283]: df.query('[1, 2] in c')
Out[283]:
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 [284]: df.query('[1, 2] not in c')
Out[284]:
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 [285]: df[df['c'].isin([1, 2])]
Out[285]:
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 [286]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [287]: df['bools'] = np.random.rand(len(df)) > 0.5
In [288]: df.query('~bools')
Out[288]:
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 [289]: df.query('not bools')
Out[289]:
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 [290]: df.query('not bools') == df[~df['bools']]
Out[290]:
a b c bools
2 True True True True
7 True True True True
8 True True True True
Разумеется, выражения могут быть сколь угодно сложными:
# short query syntax
In [291]: shorter = df.query('a < b < c and (not bools) or bools > 2')
# equivalent in pure Python
In [292]: longer = df[(df['a'] < df['b'])
.....: & (df['b'] < df['c'])
.....: & (~df['bools'])
.....: | (df['bools'] > 2)]
.....:
In [293]: shorter
Out[293]:
a b c bools
7 0.275396 0.691034 0.826619 False
In [294]: longer
Out[294]:
a b c bools
7 0.275396 0.691034 0.826619 False
In [295]: shorter == longer
Out[295]:
a b c bools
7 True True True True
Производительность query()
DataFrame.query() с использованием numexpr работает немного быстрее Python на больших фреймах.
Преимущество в производительности при использовании движка numexpr с DataFrame.query() заметно только в том случае, если фрейм содержит более 100 000 строк.
Этот график построен на основе DataFrame с тремя столбцами, каждый из которых содержит значения с плавающей точкой, созданные с помощью numpy.random.randn().
In [296]: df = pd.DataFrame(np.random.randn(8, 4),
.....: index=dates, columns=['A', 'B', 'C', 'D'])
.....:
In [297]: df2 = df.copy()
Дублирующиеся данные
Чтобы найти и удалить дублирующиеся строки в DataFrame, можно воспользоваться двумя методами: duplicated и drop_duplicates. Каждый из них принимает в качестве аргумента столбцы, используемые для определения дублирующихся строк.
duplicatedвозвращает булев вектор длиной, равной числу строк, который показывает, является ли строка дубликатом.drop_duplicatesудаляет дублирующиеся строки.
По умолчанию первая встреченная строка из группы дубликатов считается уникальной, но каждый метод имеет параметр keep, позволяющий указать, какие строки следует сохранить.
keep='first'(по умолчанию): пометить / удалить дубликаты, кроме первого вхождения.keep='last': пометить / удалить дубликаты, кроме последнего вхождения.keep=False: пометить / удалить все дубликаты.
In [298]: 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 [299]: df2
Out[299]:
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 [300]: df2.duplicated('a')
Out[300]:
0 False
1 True
2 False
3 True
4 True
5 False
6 False
dtype: bool
In [301]: df2.duplicated('a', keep='last')
Out[301]:
0 True
1 False
2 True
3 True
4 False
5 False
6 False
dtype: bool
In [302]: df2.duplicated('a', keep=False)
Out[302]:
0 True
1 True
2 True
3 True
4 True
5 False
6 False
dtype: bool
In [303]: df2.drop_duplicates('a')
Out[303]:
a b c
0 one x -1.067137
2 two x -0.211056
5 three x -1.964475
6 four x 1.298329
In [304]: df2.drop_duplicates('a', keep='last')
Out[304]:
a b c
1 one y 0.309500
4 two x -0.390820
5 three x -1.964475
6 four x 1.298329
In [305]: df2.drop_duplicates('a', keep=False)
Out[305]:
a b c
5 three x -1.964475
6 four x 1.298329
Для поиска дубликатов также можно передать список столбцов.
In [306]: df2.duplicated(['a', 'b'])
Out[306]:
0 False
1 False
2 False
3 False
4 True
5 False
6 False
dtype: bool
In [307]: df2.drop_duplicates(['a', 'b'])
Out[307]:
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 [308]: df3 = pd.DataFrame({'a': np.arange(6),
.....: 'b': np.random.randn(6)},
.....: index=['a', 'a', 'b', 'c', 'b', 'a'])
.....:
In [309]: df3
Out[309]:
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 [310]: df3.index.duplicated()
Out[310]: array([False, True, False, False, True, True])
In [311]: df3[~df3.index.duplicated()]
Out[311]:
a b
a 0 1.440455
b 2 1.038402
c 3 -0.894409
In [312]: df3[~df3.index.duplicated(keep='last')]
Out[312]:
a b
c 3 -0.894409
b 4 0.683536
a 5 3.082764
In [313]: df3[~df3.index.duplicated(keep=False)]
Out[313]:
a b
c 3 -0.894409
Метод get(), похожий на метод словаря
У объектов Series и DataFrame есть метод get, который может возвращать значение по умолчанию.
In [314]: s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
In [315]: s.get('a') # equivalent to s['a']
Out[315]: 1
In [316]: s.get('x', default=-1)
Out[316]: -1
Поиск значений по меткам индекса/столбцов
Иногда требуется извлечь набор значений по последовательностям меток строк и столбцов. Это можно сделать с помощью pandas.factorize и индексации NumPy. Например:
In [317]: df = pd.DataFrame({'col': ["A", "A", "B", "B"],
.....: 'A': [80, 23, np.nan, 22],
.....: 'B': [80, 55, 76, 67]})
.....:
In [318]: df
Out[318]:
col A B
0 A 80.0 80
1 A 23.0 55
2 B NaN 76
3 B 22.0 67
In [319]: idx, cols = pd.factorize(df['col'])
In [320]: df.reindex(cols, axis=1).to_numpy()[np.arange(len(df)), idx]
Out[320]: array([80., 23., 76., 67.])
Ранее это можно было сделать с помощью специального метода DataFrame.lookup, который был объявлен устаревшим в версии 1.2.0 и удалён в версии 2.0.0.
Объекты Index
Класс pandas Index и его подклассы можно рассматривать как реализацию упорядоченного мультимножества. Дубликаты допускаются.
Index также предоставляет инфраструктуру, необходимую для поиска, выравнивания данных и повторной индексации. Проще всего создать Index напрямую, передав list или другую последовательность в Index:
In [321]: index = pd.Index(['e', 'd', 'a', 'b'])
In [322]: index
Out[322]: Index(['e', 'd', 'a', 'b'], dtype='object')
In [323]: 'd' in index
Out[323]: True
или используя числа:
In [324]: index = pd.Index([1, 5, 12])
In [325]: index
Out[325]: Index([1, 5, 12], dtype='int64')
In [326]: 5 in index
Out[326]: True
Если dtype не указан, Index пытается определить его по данным. При создании экземпляра Index также можно указать dtype явно:
In [327]: index = pd.Index(['e', 'd', 'a', 'b'], dtype="string")
In [328]: index
Out[328]: Index(['e', 'd', 'a', 'b'], dtype='string')
In [329]: index = pd.Index([1, 5, 12], dtype="int8")
In [330]: index
Out[330]: Index([1, 5, 12], dtype='int8')
In [331]: index = pd.Index([1, 5, 12], dtype="float32")
In [332]: index
Out[332]: Index([1.0, 5.0, 12.0], dtype='float32')
В индекс также можно передать name:
In [333]: index = pd.Index(['e', 'd', 'a', 'b'], name='something')
In [334]: index.name
Out[334]: 'something'
Если имя задано, оно отображается в консоли:
In [335]: index = pd.Index(list(range(5)), name='rows')
In [336]: columns = pd.Index(['A', 'B', 'C'], name='cols')
In [337]: df = pd.DataFrame(np.random.randn(5, 3), index=index, columns=columns)
In [338]: df
Out[338]:
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 [339]: df['A']
Out[339]:
rows
0 1.295989
1 -2.366110
2 0.433306
3 2.154730
4 1.126818
Name: A, dtype: float64
Задание метаданных
Индексы «в основном неизменяемы», но их атрибут name можно задавать и изменять. Для непосредственного задания этих атрибутов можно использовать rename, set_names; по умолчанию они возвращают копию.
Информацию об использовании MultiIndex см. в разделе Расширенная индексация.
In [340]: ind = pd.Index([1, 2, 3])
In [341]: ind.rename("apple")
Out[341]: Index([1, 2, 3], dtype='int64', name='apple')
In [342]: ind
Out[342]: Index([1, 2, 3], dtype='int64')
In [343]: ind = ind.set_names(["apple"])
In [344]: ind.name = "bob"
In [345]: ind
Out[345]: Index([1, 2, 3], dtype='int64', name='bob')
set_names, set_levels и set_codes также принимают необязательный аргумент level.
In [346]: index = pd.MultiIndex.from_product([range(3), ['one', 'two']], names=['first', 'second'])
In [347]: index
Out[347]:
MultiIndex([(0, 'one'),
(0, 'two'),
(1, 'one'),
(1, 'two'),
(2, 'one'),
(2, 'two')],
names=['first', 'second'])
In [348]: index.levels[1]
Out[348]: Index(['one', 'two'], dtype='object', name='second')
In [349]: index.set_levels(["a", "b"], level=1)
Out[349]:
MultiIndex([(0, 'a'),
(0, 'b'),
(1, 'a'),
(1, 'b'),
(2, 'a'),
(2, 'b')],
names=['first', 'second'])
Операции над множествами для объектов Index
Две основные операции — union и intersection. Разность вычисляется с помощью метода .difference().
In [350]: a = pd.Index(['c', 'b', 'a'])
In [351]: b = pd.Index(['c', 'e', 'd'])
In [352]: a.difference(b)
Out[352]: Index(['a', 'b'], dtype='object')
Также доступна операция symmetric_difference, возвращающая элементы, которые встречаются в idx1 или idx2, но не в обоих индексах одновременно. Она эквивалентна индексу, созданному с помощью idx1.difference(idx2).union(idx2.difference(idx1)), с удалёнными дубликатами.
In [353]: idx1 = pd.Index([1, 2, 3, 4])
In [354]: idx2 = pd.Index([2, 3, 4, 5])
In [355]: idx1.symmetric_difference(idx2)
Out[355]: Index([1, 5], dtype='int64')
Примечание
Результирующий индекс после операции над множествами будет отсортирован по возрастанию.
При выполнении Index.union() для индексов с разными типами данных их необходимо привести к общему типу. Обычно, хотя и не всегда, это тип object. Исключение составляет объединение целочисленных данных и данных с плавающей точкой. В этом случае целочисленные значения преобразуются в числа с плавающей точкой.
In [356]: idx1 = pd.Index([0, 1, 2])
In [357]: idx2 = pd.Index([0.5, 1.5])
In [358]: idx1.union(idx2)
Out[358]: Index([0.0, 0.5, 1.0, 1.5, 2.0], dtype='float64')
Отсутствующие значения
Важно
Хотя Index может содержать отсутствующие значения (NaN), этого следует избегать, если вы не хотите столкнуться с неожиданными результатами. Например, некоторые операции неявно исключают отсутствующие значения.
Index.fillna заполняет отсутствующие значения указанным скалярным значением.
In [359]: idx1 = pd.Index([1, np.nan, 3, 4])
In [360]: idx1
Out[360]: Index([1.0, nan, 3.0, 4.0], dtype='float64')
In [361]: idx1.fillna(2)
Out[361]: Index([1.0, 2.0, 3.0, 4.0], dtype='float64')
In [362]: idx2 = pd.DatetimeIndex([pd.Timestamp('2011-01-01'),
.....: pd.NaT,
.....: pd.Timestamp('2011-01-03')])
.....:
In [363]: idx2
Out[363]: DatetimeIndex(['2011-01-01', 'NaT', '2011-01-03'], dtype='datetime64[ns]', freq=None)
In [364]: idx2.fillna(pd.Timestamp('2011-01-02'))
Out[364]: DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], dtype='datetime64[ns]', freq=None)
Задание / сброс индекса
Иногда набор данных загружается или создаётся в DataFrame, и индекс требуется добавить уже после этого. Для этого есть несколько способов.
Задание индекса
У DataFrame есть метод set_index(), который принимает имя столбца (для обычного Index) или список имён столбцов (для MultiIndex). Чтобы создать новый DataFrame с переиндексированными данными:
In [365]: data = pd.DataFrame({'a': ['bar', 'bar', 'foo', 'foo'],
.....: 'b': ['one', 'two', 'one', 'two'],
.....: 'c': ['z', 'y', 'x', 'w'],
.....: 'd': [1., 2., 3, 4]})
.....:
In [366]: data
Out[366]:
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 [367]: indexed1 = data.set_index('c')
In [368]: indexed1
Out[368]:
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 [369]: indexed2 = data.set_index(['a', 'b'])
In [370]: indexed2
Out[370]:
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 [371]: frame = data.set_index('c', drop=False)
In [372]: frame = frame.set_index(['a', 'b'], append=True)
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
Другие параметры метода set_index позволяют не удалять столбцы индекса.
In [374]: data.set_index('c', drop=False)
Out[374]:
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 [375]: data
Out[375]:
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 [376]: data.reset_index()
Out[376]:
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 [377]: frame
Out[377]:
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 [378]: frame.reset_index(level=1)
Out[378]:
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, который при значении True просто отбрасывает индекс, а не помещает его значения в столбцы DataFrame.
Добавление временного индекса
Пользовательский индекс можно присвоить атрибуту index:
In [379]: df_idx = pd.DataFrame(range(4))
In [380]: df_idx.index = pd.Index([10, 20, 30, 40], name="a")
In [381]: df_idx
Out[381]:
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 [382]: dfmi = pd.DataFrame([list('abcd'),
.....: list('efgh'),
.....: list('ijkl'),
.....: list('mnop')],
.....: columns=pd.MultiIndex.from_product([['one', 'two'],
.....: ['first', 'second']]))
.....:
In [383]: dfmi
Out[383]:
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 [384]: dfmi['one']['second']
Out[384]:
0 b
1 f
2 j
3 n
Name: second, dtype: object
In [385]: dfmi.loc[:, ('one', 'second')]
Out[385]:
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 [386]: 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 [387]: dfb['c'][dfb['a'].str.startswith('o')] = 42
Однако это выполняется над копией и работать не будет.
In [388]: with pd.option_context('mode.chained_assignment','warn'):
.....: dfb[dfb['a'].str.startswith('o')]['c'] = 42
.....:
Цепочечное присваивание может возникнуть и при установке значений в фрейме со смешанными типами данных.
Примечание
Эти правила установки значений применяются ко всем .loc/.iloc.
Ниже приведён рекомендуемый способ доступа: используйте .loc для нескольких элементов (с помощью mask) и фиксированный индекс для одного элемента:
In [389]: dfc = pd.DataFrame({'a': ['one', 'one', 'two',
.....: 'three', 'two', 'one', 'six'],
.....: 'c': np.arange(7)})
.....:
In [390]: dfd = dfc.copy()
# Setting multiple items using a mask
In [391]: mask = dfd['a'].str.startswith('o')
In [392]: dfd.loc[mask, 'c'] = 42
In [393]: dfd
Out[393]:
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 [394]: dfd = dfc.copy()
In [395]: dfd.loc[2, 'a'] = 11
In [396]: dfd
Out[396]:
a c
0 one 0
1 one 1
2 11 2
3 three 3
4 two 4
5 one 5
6 six 6
Следующий вариант иногда может работать, но это не гарантируется, поэтому его следует избегать:
In [397]: dfd = dfc.copy()
In [398]: dfd['a'][2] = 111
In [399]: dfd
Out[399]:
a c
0 one 0
1 one 1
2 111 2
3 three 3
4 two 4
5 one 5
6 six 6
Наконец, следующий пример совсем не будет работать, поэтому его следует избегать:
In [400]: with pd.option_context('mode.chained_assignment','raise'):
.....: dfd.loc[0]['a'] = 1111
.....:
---------------------------------------------------------------------------
SettingWithCopyError Traceback (most recent call last)
<ipython-input-400-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)
1293 )
1294
1295 check_dict_or_set_indexers(key)
1296 key = com.apply_if_callable(key, self)
-> 1297 cacher_needs_updating = self._check_is_chained_assignment_possible()
1298
1299 if key is Ellipsis:
1300 key = slice(None)
~/work/pandas/pandas/pandas/core/series.py in ?(self)
1498 ref = self._get_cacher()
1499 if ref is not None and ref._is_mixed_type:
1500 self._check_setitem_copy(t="referent", force=True)
1501 return True
-> 1502 return super()._check_is_chained_assignment_possible()
~/work/pandas/pandas/pandas/core/generic.py in ?(self)
4414 single-dtype meaning that the cacher should be updated following
4415 setting.
4416 """
4417 if self._is_copy:
-> 4418 self._check_setitem_copy(t="referent")
4419 return False
~/work/pandas/pandas/pandas/core/generic.py in ?(self, t, force)
4488 "indexing.html#returning-a-view-versus-a-copy"
4489 )
4490
4491 if value == "raise":
-> 4492 raise SettingWithCopyError(t)
4493 if value == "warn":
4494 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–2011, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
© 2011–2025, Open source contributors
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.3.0/user_guide/indexing.html