Индексирование и выбор данных
Информация о маркировке осей в объектах 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
Вы можете найти это полезным для применения преобразования (вместо) к подмножеству столбцов.
Предупреждение
pandas выравнивает все оси при установке Series и DataFrame из .loc, а также .iloc.
Это не изменит 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
Доступ к атрибутам
Вы можете получить доступ к индексу в Series или столбцу в DataFrame напрямую как к атрибуту:
In [14]: sa = pd.Series([1, 2, 3], index=list('abc'))
In [15]: dfa = df.copy()
In [16]: sa.b
Out[16]: 2
In [17]: dfa.A
Out[17]:
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
2000-01-06 -0.673690
2000-01-07 0.404705
2000-01-08 -0.370647
Freq: D, Name: A, dtype: float64
In [18]: sa.a = 5
In [19]: sa
Out[19]:
a 5
b 2
c 3
dtype: int64
In [20]: dfa.A = list(range(len(dfa.index))) # ok if A already exists
In [21]: dfa
Out[21]:
A B C D
2000-01-01 0 -0.282863 -1.509059 -1.135632
2000-01-02 1 -0.173215 0.119209 -1.044236
2000-01-03 2 -2.104569 -0.494929 1.071804
2000-01-04 3 -0.706771 -1.039575 0.271860
2000-01-05 4 0.567020 0.276232 -1.087401
2000-01-06 5 0.113648 -1.478427 0.524988
2000-01-07 6 0.577046 -1.715002 -1.039268
2000-01-08 7 -1.157892 -1.344312 0.844885
In [22]: dfa['A'] = list(range(len(dfa.index))) # use this form to create a new column
In [23]: dfa
Out[23]:
A B C D
2000-01-01 0 -0.282863 -1.509059 -1.135632
2000-01-02 1 -0.173215 0.119209 -1.044236
2000-01-03 2 -2.104569 -0.494929 1.071804
2000-01-04 3 -0.706771 -1.039575 0.271860
2000-01-05 4 0.567020 0.276232 -1.087401
2000-01-06 5 0.113648 -1.478427 0.524988
2000-01-07 6 0.577046 -1.715002 -1.039268
2000-01-08 7 -1.157892 -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 [24]: x = pd.DataFrame({'x': [1, 2, 3], 'y': [3, 4, 5]})
In [25]: x.iloc[1] = {'x': 9, 'y': 99}
In [26]: x
Out[26]:
x y
0 1 3
1 9 99
2 3 5
Вы можете использовать доступ к атрибутам для изменения существующего элемента Series или столбца DataFrame, но будьте осторожны; если вы попытаетесь использовать доступ к атрибутам для создания нового столбца, он создаст новый атрибут, а не новый столбец. В 0.21.0 и более поздних версиях это вызовет UserWarning:
In [1]: df = pd.DataFrame({'one': [1., 2., 3.]})
In [2]: df.two = [4, 5, 6]
UserWarning: Pandas doesn't allow Series to be assigned into nonexistent columns - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute_access
In [3]: df
Out[3]:
one
0 1.0
1 2.0
2 3.0
Диапазоны срезов
Наиболее надежный и последовательный способ среза диапазонов по произвольным осям описан в разделе Выбор по позиции, подробно описывающем метод .iloc. Пока мы объясним семантику срезов с использованием оператора [].
С объектами Series синтаксис работает точно так же, как с ndarray, возвращая срез значений и соответствующих меток:
In [27]: s[:5]
Out[27]:
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 [28]: s[::2]
Out[28]:
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 [29]: s[::-1]
Out[29]:
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 [30]: s2 = s.copy()
In [31]: s2[:5] = 0
In [32]: s2
Out[32]:
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 [33]: df[:3]
Out[33]:
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
In [34]: df[::-1]
Out[34]:
A B C D
2000-01-08 -0.370647 -1.157892 -1.344312 0.844885
2000-01-07 0.404705 0.577046 -1.715002 -1.039268
2000-01-06 -0.673690 0.113648 -1.478427 0.524988
2000-01-05 -0.424972 0.567020 0.276232 -1.087401
2000-01-04 0.721555 -0.706771 -1.039575 0.271860
2000-01-03 -0.861849 -2.104569 -0.494929 1.071804
2000-01-02 1.212112 -0.173215 0.119209 -1.044236
2000-01-01 0.469112 -0.282863 -1.509059 -1.135632
Выбор по метке
Предупреждение
Возвращается ли копия или ссылка при операции установки, может зависеть от контекста. Это иногда называется chained assignment и следует избегать. См. Возвращение представления или копии.
Предупреждение
.locстрого относится к слайсерам, которые несовместимы (или не могут быть преобразованы) с типом индекса. Например, использование целых чисел вDatetimeIndex. Это приведет к ошибкеTypeError.
In [35]: dfl = pd.DataFrame(np.random.randn(5, 4),
....: columns=list('ABCD'),
....: index=pd.date_range('20130101', periods=5))
....:
In [36]: dfl
Out[36]:
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 [4]: dfl.loc[2:3]
TypeError: cannot do slice indexing on <class 'pandas.tseries.index.DatetimeIndex'> with these indexers [2] of <type 'int'>
Строки при срезе могут быть преобразованы к типу индекса и привести к естественному срезу.
In [37]: dfl.loc['20130102':'20130104']
Out[37]:
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
Предупреждение
Изменено в версии 1.0.0.
pandas вызовет ошибку KeyError, если индексирование выполняется со списком с отсутствующими метками. См. Список-подобные Используя loc с отсутствующими ключами в списке устарело.
pandas предоставляет набор методов для индексирования только на основе меток. Это протокол строгого включения. Каждая запрашиваемая метка должна быть в индексе, в противном случае будет вызвана ошибка KeyError. При срезе и начало И конец диапазона включаются, если они присутствуют в индексе. Целые числа являются допустимыми метками, но они ссылаются на метку, а не на позицию.
Атрибут .loc является основным методом доступа. Следующие являются допустимыми входами:
Одна метка, например
5или'a'(Обратите внимание, что5интерпретируется как метка индекса. Это использование не является целочисленной позицией вдоль индекса).Список или массив меток
['a', 'b', 'c'].Объект среза с метками
'a':'f'(Обратите внимание, что в отличие от обычных Python срезов, оба начало и конец включаются, если они присутствуют в индексе! См. Срез с метками.Булевый массив.
callable, см. Выбор по вызываемому объекту.
In [38]: s1 = pd.Series(np.random.randn(6), index=list('abcdef'))
In [39]: s1
Out[39]:
a 1.431256
b 1.340309
c -1.170299
d -0.226169
e 0.410835
f 0.813850
dtype: float64
In [40]: s1.loc['c':]
Out[40]:
c -1.170299
d -0.226169
e 0.410835
f 0.813850
dtype: float64
In [41]: s1.loc['b']
Out[41]: 1.3403088497993827
Обратите внимание, что установка также работает:
In [42]: s1.loc['c':] = 0
In [43]: s1
Out[43]:
a 1.431256
b 1.340309
c 0.000000
d 0.000000
e 0.000000
f 0.000000
dtype: float64
С DataFrame:
In [44]: df1 = pd.DataFrame(np.random.randn(6, 4),
....: index=list('abcdef'),
....: columns=list('ABCD'))
....:
In [45]: df1
Out[45]:
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 [46]: df1.loc[['a', 'b', 'd'], :]
Out[46]:
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 [47]: df1.loc['d':, 'A':'C']
Out[47]:
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 [48]: df1.loc['a']
Out[48]:
A 0.132003
B -0.827317
C -0.076467
D -1.187678
Name: a, dtype: float64
Для получения значений с помощью булевого массива:
In [49]: df1.loc['a'] > 0
Out[49]:
A True
B False
C False
D False
Name: a, dtype: bool
In [50]: df1.loc[:, df1.loc['a'] > 0]
Out[50]:
A
a 0.132003
b 1.130127
c 1.024180
d 0.974466
e 0.545952
f -1.281247
Значения NA в булевом массиве распространяются как False:
Изменено в версии 1.0.2.
In [51]: mask = pd.array([True, False, True, False, pd.NA, False], dtype="boolean")
In [52]: mask
Out[52]:
<BooleanArray>
[True, False, True, False, <NA>, False]
Length: 6, dtype: boolean
In [53]: df1[mask]
Out[53]:
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 [54]: df1.loc['a', 'A']
Out[54]: 0.13200317033032932
Срез с метками
При использовании .loc со слайсами, если метки начала и конца присутствуют в индексе, то элементы, расположенные между ними (включая их), возвращаются:
In [55]: s = pd.Series(list('abcde'), index=[0, 3, 2, 5, 4])
In [56]: s.loc[3:5]
Out[56]:
3 b
2 c
5 d
dtype: object
Если хотя бы одна из двух отсутствует, но индекс отсортирован и может быть сравнен с метками начала и конца, то срез все равно будет работать как ожидается, выбирая метки, которые ранжируются между двумя:
In [57]: s.sort_index()
Out[57]:
0 a
2 c
3 b
4 e
5 d
dtype: object
In [58]: s.sort_index().loc[1:6]
Out[58]:
2 c
3 b
4 e
5 d
dtype: object
Однако, если хотя бы одна из двух отсутствует и индекс не отсортирован, будет вызвана ошибка (так как в противном случае это будет вычислительно дорого, а также потенциально неоднозначно для индексов смешанного типа). Например, в приведенном выше примере s.loc[1:6] вызовет KeyError.
Для обоснования этого поведения см. Концы включены.
In [59]: s = pd.Series(list('abcdef'), index=[0, 3, 2, 5, 4, 2])
In [60]: s.loc[3:5]
Out[60]:
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 [61]: s1 = pd.Series(np.random.randn(5), index=list(range(0, 10, 2)))
In [62]: s1
Out[62]:
0 0.695775
2 0.341734
4 0.959726
6 -1.110336
8 -0.619976
dtype: float64
In [63]: s1.iloc[:3]
Out[63]:
0 0.695775
2 0.341734
4 0.959726
dtype: float64
In [64]: s1.iloc[3]
Out[64]: -1.110336102891167
Обратите внимание, что установка также работает:
In [65]: s1.iloc[:3] = 0
In [66]: s1
Out[66]:
0 0.000000
2 0.000000
4 0.000000
6 -1.110336
8 -0.619976
dtype: float64
С DataFrame:
In [67]: df1 = pd.DataFrame(np.random.randn(6, 4),
....: index=list(range(0, 12, 2)),
....: columns=list(range(0, 8, 2)))
....:
In [68]: df1
Out[68]:
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 [69]: df1.iloc[:3]
Out[69]:
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 [70]: df1.iloc[1:5, 2:4]
Out[70]:
4 6
2 0.301624 -2.179861
4 1.462696 -1.743161
6 1.314232 0.690579
8 0.014871 3.357427
Выбор через целочисленный список:
In [71]: df1.iloc[[1, 3, 5], [1, 3]]
Out[71]:
2 6
2 -0.154951 -2.179861
6 -0.345352 0.690579
10 -1.236269 -0.487602
In [72]: df1.iloc[1:3, :]
Out[72]:
0 2 4 6
2 0.403310 -0.154951 0.301624 -2.179861
4 -1.369849 -0.954208 1.462696 -1.743161
In [73]: df1.iloc[:, 1:3]
Out[73]:
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 [74]: df1.iloc[1, 1]
Out[74]: -0.1549507744249032
Для получения поперечного среза с использованием целочисленной позиции (эквивалентно df.xs(1)):
In [75]: df1.iloc[1]
Out[75]:
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 [76]: x = list('abcdef')
In [77]: x
Out[77]: ['a', 'b', 'c', 'd', 'e', 'f']
In [78]: x[4:10]
Out[78]: ['e', 'f']
In [79]: x[8:10]
Out[79]: []
In [80]: s = pd.Series(x)
In [81]: s
Out[81]:
0 a
1 b
2 c
3 d
4 e
5 f
dtype: object
In [82]: s.iloc[4:10]
Out[82]:
4 e
5 f
dtype: object
In [83]: s.iloc[8:10]
Out[83]: Series([], dtype: object)
Обратите внимание, что использование срезов, выходящих за пределы, может привести к пустой оси (например, возвращению пустого DataFrame).
In [84]: dfl = pd.DataFrame(np.random.randn(5, 2), columns=list('AB'))
In [85]: dfl
Out[85]:
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 [86]: dfl.iloc[:, 2:3]
Out[86]:
Empty DataFrame
Columns: []
Index: [0, 1, 2, 3, 4]
In [87]: dfl.iloc[:, 1:3]
Out[87]:
B
0 -2.182937
1 0.084844
2 1.519970
3 0.600178
4 0.132885
In [88]: dfl.iloc[4:6]
Out[88]:
A B
4 0.27423 0.132885
Один индексатор, выходящий за пределы, вызовет ошибку IndexError. Список индексаторов, где любой элемент выходит за пределы, вызовет ошибку IndexError.
>>> dfl.iloc[[4, 5, 6]]
IndexError: positional indexers are out-of-bounds
>>> dfl.iloc[:, 4]
IndexError: single positional indexer is out-of-bounds
Выбор по вызываемому объекту
.loc, .iloc, и также [] индексирование может принимать callable как индексатор. callable должен быть функцией с одним аргументом (вызываемым Series или DataFrame), которая возвращает допустимый результат для индексирования.
In [89]: df1 = pd.DataFrame(np.random.randn(6, 4),
....: index=list('abcdef'),
....: columns=list('ABCD'))
....:
In [90]: df1
Out[90]:
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 [91]: df1.loc[lambda df: df['A'] > 0, :]
Out[91]:
A B C D
c 0.299368 -0.863838 0.408204 -1.048089
e 1.289997 0.082423 -0.055758 0.536580
In [92]: df1.loc[:, lambda df: ['A', 'B']]
Out[92]:
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 [93]: df1.iloc[:, lambda df: [0, 1]]
Out[93]:
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 [94]: df1[lambda df: df.columns[0]]
Out[94]:
a -0.023688
b -0.251905
c 0.299368
d -0.025747
e 1.289997
f -0.489682
Name: A, dtype: float64
Вы можете использовать вызываемое индексирование в Series.
In [95]: df1['A'].loc[lambda s: s > 0]
Out[95]:
c 0.299368
e 1.289997
Name: A, dtype: float64
Используя эти методы/индексаторы, вы можете объединять операции выбора данных без использования временной переменной.
In [96]: bb = pd.read_csv('data/baseball.csv', index_col='id')
In [97]: (bb.groupby(['year', 'team']).sum(numeric_only=True)
....: .loc[lambda df: df['r'] > 100])
....:
Out[97]:
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 [98]: dfd = pd.DataFrame({'A': [1, 2, 3],
....: 'B': [4, 5, 6]},
....: index=list('abc'))
....:
In [99]: dfd
Out[99]:
A B
a 1 4
b 2 5
c 3 6
In [100]: dfd.loc[dfd.index[[0, 2]], 'A']
Out[100]:
a 1
c 3
Name: A, dtype: int64
Это также можно выразить, используя .iloc, явно получая местоположения в индексаторах и используя позиционное индексирование для выбора элементов.
In [101]: dfd.iloc[[0, 2], dfd.columns.get_loc('A')]
Out[101]:
a 1
c 3
Name: A, dtype: int64
Для получения нескольких индексаторов, используйте .get_indexer:
In [102]: dfd.iloc[[0, 2], dfd.columns.get_indexer(['A', 'B'])]
Out[102]:
A B
a 1 4
c 3 6
Индексирование списком с отсутствующими метками устарело
Предупреждение
Изменено в версии 1.0.0.
Использование .loc или [] со списком с одной или несколькими отсутствующими метками больше не будет переиндексировать, в пользу .reindex.
В предыдущих версиях использование .loc[list-of-labels] работало, пока хотя бы 1 из ключей находилось (иначе это вызывало бы KeyError). Это поведение было изменено, и теперь будет вызываться KeyError если хотя бы одна метка отсутствует. Рекомендуемая альтернатива - использовать .reindex().
Например.
In [103]: s = pd.Series([1, 2, 3])
In [104]: s
Out[104]:
0 1
1 2
2 3
dtype: int64
Выбор со всеми найденными ключами не изменился.
In [105]: s.loc[[1, 2]]
Out[105]:
1 2
2 3
dtype: int64
Предыдущее поведение
In [4]: s.loc[[1, 2, 3]]
Out[4]:
1 2.0
2 3.0
3 NaN
dtype: float64
Текущее поведение
In [4]: s.loc[[1, 2, 3]]
Passing list-likes to .loc with any non-matching elements will raise
KeyError in the future, you can use .reindex() as an alternative.
See the documentation here:
https://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike
Out[4]:
1 2.0
2 3.0
3 NaN
dtype: float64
Переиндексирование
Идиоматичный способ достижения выбора потенциально отсутствующих элементов - через .reindex(). См. также раздел о переиндексировании.
In [106]: s.reindex([1, 2, 3])
Out[106]:
1 2.0
2 3.0
3 NaN
dtype: float64
В качестве альтернативы, если вы хотите выбрать только действительные ключи, следующее является идиоматичным и эффективным; гарантируется сохранение типа данных выборки.
In [107]: labels = [1, 2, 3]
In [108]: s.loc[s.index.intersection(labels)]
Out[108]:
1 2
2 3
dtype: int64
Наличие дублирующегося индекса вызовет .reindex():
In [109]: s = pd.Series(np.arange(4), index=['a', 'a', 'b', 'c'])
In [110]: labels = ['c', 'd']
In [17]: s.reindex(labels)
ValueError: cannot reindex on an axis with duplicate labels
В общем случае вы можете пересечь желаемые метки с текущей осью, а затем переиндексировать.
In [111]: s.loc[s.index.intersection(labels)].reindex(labels)
Out[111]:
c 3.0
d NaN
dtype: float64
Однако это все равно вызовет ошибку, если ваш результирующий индекс дублируется.
In [41]: labels = ['a', 'd']
In [42]: s.loc[s.index.intersection(labels)].reindex(labels)
ValueError: cannot reindex on an axis with duplicate labels
Выбор случайных выборок
Случайный выбор строк или столбцов из Series или DataFrame с помощью метода sample(). По умолчанию метод выбирает строки и принимает определенное количество строк/столбцов для возврата или долю строк.
In [112]: s = pd.Series([0, 1, 2, 3, 4, 5])
# When no arguments are passed, returns 1 row.
In [113]: s.sample()
Out[113]:
4 4
dtype: int64
# One may specify either a number of rows:
In [114]: s.sample(n=3)
Out[114]:
0 0
4 4
1 1
dtype: int64
# Or a fraction of the rows:
In [115]: s.sample(frac=0.5)
Out[115]:
5 5
3 3
1 1
dtype: int64
По умолчанию, sample вернёт каждую строку не более одного раза, но также можно сделать выборку с заменой, используя опцию replace:
In [116]: s = pd.Series([0, 1, 2, 3, 4, 5])
# Without replacement (default):
In [117]: s.sample(n=6, replace=False)
Out[117]:
0 0
1 1
5 5
3 3
2 2
4 4
dtype: int64
# With replacement:
In [118]: s.sample(n=6, replace=True)
Out[118]:
0 0
4 4
3 3
2 2
4 4
4 4
dtype: int64
По умолчанию каждая строка имеет равную вероятность выбора, но если вы хотите, чтобы строки имели разные вероятности, вы можете передать функцию sample весов выборки как weights. Эти веса могут быть списком, массивом NumPy или Series, но они должны иметь такую же длину, как и объект, из которого происходит выборка. Пропущенные значения будут рассматриваться как вес 0, а значения inf недопустимы. Если веса не суммируются до 1, они будут перенормированы путём деления всех весов на сумму весов. Например:
In [119]: s = pd.Series([0, 1, 2, 3, 4, 5])
In [120]: example_weights = [0, 0, 0.2, 0.2, 0.2, 0.4]
In [121]: s.sample(n=3, weights=example_weights)
Out[121]:
5 5
4 4
3 3
dtype: int64
# Weights will be re-normalized automatically
In [122]: example_weights2 = [0.5, 0, 0, 0, 0, 0]
In [123]: s.sample(n=1, weights=example_weights2)
Out[123]:
0 0
dtype: int64
При применении к DataFrame вы можете использовать столбец DataFrame в качестве весов выборки (при условии, что вы выбираете строки, а не столбцы), просто передав имя столбца как строку.
In [124]: df2 = pd.DataFrame({'col1': [9, 8, 7, 6],
.....: 'weight_column': [0.5, 0.4, 0.1, 0]})
.....:
In [125]: df2.sample(n=3, weights='weight_column')
Out[125]:
col1 weight_column
1 8 0.4
0 9 0.5
2 7 0.1
sample также позволяет пользователям выбирать столбцы вместо строк, используя аргумент axis.
In [126]: df3 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})
In [127]: df3.sample(n=1, axis=1)
Out[127]:
col1
0 1
1 2
2 3
Наконец, можно установить seed для генератора случайных чисел sample с помощью аргумента random_state, который будет принимать либо целое число (в качестве seed), либо объект NumPy RandomState.
In [128]: df4 = pd.DataFrame({'col1': [1, 2, 3], 'col2': [2, 3, 4]})
# With a given seed, the sample will always draw the same rows.
In [129]: df4.sample(n=2, random_state=2)
Out[129]:
col1 col2
2 3 4
1 2 3
In [130]: df4.sample(n=2, random_state=2)
Out[130]:
col1 col2
2 3 4
1 2 3
Установка с расширением
Операции .loc/[] могут выполнять расширение при установке несуществующего ключа для этой оси.
В случае Series это фактически операция добавления.
In [131]: se = pd.Series([1, 2, 3])
In [132]: se
Out[132]:
0 1
1 2
2 3
dtype: int64
In [133]: se[5] = 5.
In [134]: se
Out[134]:
0 1.0
1 2.0
2 3.0
5 5.0
dtype: float64
DataFrame может быть расширена на любой оси через .loc.
In [135]: dfi = pd.DataFrame(np.arange(6).reshape(3, 2),
.....: columns=['A', 'B'])
.....:
In [136]: dfi
Out[136]:
A B
0 0 1
1 2 3
2 4 5
In [137]: dfi.loc[:, 'C'] = dfi.loc[:, 'A']
In [138]: dfi
Out[138]:
A B C
0 0 1 0
1 2 3 2
2 4 5 4
Это подобно операции append над DataFrame.
In [139]: dfi.loc[3] = 5
In [140]: dfi
Out[140]:
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 [141]: s.iat[5]
Out[141]: 5
In [142]: df.at[dates[5], 'A']
Out[142]: -0.6736897080883706
In [143]: df.iat[3, 0]
Out[143]: 0.7215551622443669
Вы также можете установить значения с помощью этих же индексаторов.
In [144]: df.at[dates[5], 'E'] = 7
In [145]: df.iat[3, 0] = 7
at может расширять объект на месте, как выше, если индексатор отсутствует.
In [146]: df.at[dates[-1] + pd.Timedelta('1 day'), 0] = 7
In [147]: df
Out[147]:
A B C D E 0
2000-01-01 0.469112 -0.282863 -1.509059 -1.135632 NaN NaN
2000-01-02 1.212112 -0.173215 0.119209 -1.044236 NaN NaN
2000-01-03 -0.861849 -2.104569 -0.494929 1.071804 NaN NaN
2000-01-04 7.000000 -0.706771 -1.039575 0.271860 NaN NaN
2000-01-05 -0.424972 0.567020 0.276232 -1.087401 NaN NaN
2000-01-06 -0.673690 0.113648 -1.478427 0.524988 7.0 NaN
2000-01-07 0.404705 0.577046 -1.715002 -1.039268 NaN NaN
2000-01-08 -0.370647 -1.157892 -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 [148]: s = pd.Series(range(-3, 4))
In [149]: s
Out[149]:
0 -3
1 -2
2 -1
3 0
4 1
5 2
6 3
dtype: int64
In [150]: s[s > 0]
Out[150]:
4 1
5 2
6 3
dtype: int64
In [151]: s[(s < -1) | (s > 0.5)]
Out[151]:
0 -3
1 -2
4 1
5 2
6 3
dtype: int64
In [152]: s[~(s < 0)]
Out[152]:
3 0
4 1
5 2
6 3
dtype: int64
Вы можете выбрать строки из DataFrame, используя логический вектор длиной, равной индексу DataFrame (например, что-то, полученное из одного из столбцов DataFrame):
In [153]: df[df['A'] > 0]
Out[153]:
A B C D E 0
2000-01-01 0.469112 -0.282863 -1.509059 -1.135632 NaN NaN
2000-01-02 1.212112 -0.173215 0.119209 -1.044236 NaN NaN
2000-01-04 7.000000 -0.706771 -1.039575 0.271860 NaN NaN
2000-01-07 0.404705 0.577046 -1.715002 -1.039268 NaN NaN
Для создания более сложных условий также можно использовать списковые включения и метод map Series:
In [154]: 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 [155]: criterion = df2['a'].map(lambda x: x.startswith('t'))
In [156]: df2[criterion]
Out[156]:
a b c
2 two y 0.041290
3 three x 0.361719
4 two y -0.238075
# equivalent but slower
In [157]: df2[[x.startswith('t') for x in df2['a']]]
Out[157]:
a b c
2 two y 0.041290
3 three x 0.361719
4 two y -0.238075
# Multiple criteria
In [158]: df2[criterion & (df2['b'] == 'x')]
Out[158]:
a b c
3 three x 0.361719
С помощью методов выбора Выбор по метке, Выбор по позиции и Расширенное индексирование можно выбрать по нескольким осям, используя логические векторы в сочетании с другими выражениями индексирования.
In [159]: df2.loc[criterion & (df2['b'] == 'x'), 'b':'c']
Out[159]:
b c
3 x 0.361719
Предупреждение
iloc поддерживает два вида логического индексирования. Если индексатор является логическим Series, будет поднято исключение. Например, в следующем примере df.iloc[s.values, 1] подходит. Логический индексатор является массивом. Но df.iloc[s, 1] вызовет ValueError.
In [160]: df = pd.DataFrame([[1, 2], [3, 4], [5, 6]],
.....: index=list('abc'),
.....: columns=['A', 'B'])
.....:
In [161]: s = (df['A'] > 2)
In [162]: s
Out[162]:
a False
b True
c True
Name: A, dtype: bool
In [163]: df.loc[s, 'B']
Out[163]:
b 4
c 6
Name: B, dtype: int64
In [164]: df.iloc[s.values, 1]
Out[164]:
b 4
c 6
Name: B, dtype: int64
Индексирование с помощью isin
Рассмотрим метод isin() объекта Series, который возвращает булевый вектор, равный True в тех местах, где элементы Series присутствуют в переданном списке. Это позволяет вам выбирать строки, где один или несколько столбцов имеют значения, которые вы хотите:
In [165]: s = pd.Series(np.arange(5), index=np.arange(5)[::-1], dtype='int64')
In [166]: s
Out[166]:
4 0
3 1
2 2
1 3
0 4
dtype: int64
In [167]: s.isin([2, 4, 6])
Out[167]:
4 False
3 False
2 True
1 False
0 True
dtype: bool
In [168]: s[s.isin([2, 4, 6])]
Out[168]:
2 2
0 4
dtype: int64
Тот же метод доступен для объектов Index и полезен в тех случаях, когда вы не знаете, какие из искомых меток фактически присутствуют:
In [169]: s[s.index.isin([2, 4, 6])]
Out[169]:
4 0
2 2
dtype: int64
# compare it to the following
In [170]: s.reindex([2, 4, 6])
Out[170]:
2 2.0
4 0.0
6 NaN
dtype: float64
Кроме того, MultiIndex позволяет выбрать отдельный уровень для использования в проверке членства:
In [171]: s_mi = pd.Series(np.arange(6),
.....: index=pd.MultiIndex.from_product([[0, 1], ['a', 'b', 'c']]))
.....:
In [172]: s_mi
Out[172]:
0 a 0
b 1
c 2
1 a 3
b 4
c 5
dtype: int64
In [173]: s_mi.iloc[s_mi.index.isin([(1, 'a'), (2, 'b'), (0, 'c')])]
Out[173]:
0 c 2
1 a 3
dtype: int64
In [174]: s_mi.iloc[s_mi.index.isin(['a', 'c', 'e'], level=1)]
Out[174]:
0 a 0
c 2
1 a 3
c 5
dtype: int64
DataFrame также имеет метод isin(). При вызове isin, передайте набор значений в виде массива или словаря. Если values — массив, то isin возвращает DataFrame булевых значений, имеющий ту же форму, что и исходный DataFrame, со значением True там, где элемент присутствует в последовательности значений.
In [175]: df = pd.DataFrame({'vals': [1, 2, 3, 4], 'ids': ['a', 'b', 'f', 'n'],
.....: 'ids2': ['a', 'n', 'c', 'n']})
.....:
In [176]: values = ['a', 'b', 1, 3]
In [177]: df.isin(values)
Out[177]:
vals ids ids2
0 True True True
1 False True False
2 True False False
3 False False False
Часто вам нужно сопоставить определенные значения с определенными столбцами. Просто сделайте values словарем dict, где ключ — столбец, а значение — список элементов, которые вы хотите проверить.
In [178]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}
In [179]: df.isin(values)
Out[179]:
vals ids ids2
0 True True False
1 False True False
2 True False False
3 False False False
Чтобы вернуть DataFrame булевых значений, где значения не находятся в исходном DataFrame, используйте оператор ~:
In [180]: values = {'ids': ['a', 'b'], 'vals': [1, 3]}
In [181]: ~df.isin(values)
Out[181]:
vals ids ids2
0 False False True
1 True False True
2 False True True
3 True True True
Объедините DataFrame’s isin с методами any() и all() для быстрого выбора подмножеств ваших данных, удовлетворяющих заданному критерию. Чтобы выбрать строку, где каждый столбец удовлетворяет своему собственному критерию:
In [182]: values = {'ids': ['a', 'b'], 'ids2': ['a', 'c'], 'vals': [1, 3]}
In [183]: row_mask = df.isin(values).all(1)
In [184]: df[row_mask]
Out[184]:
vals ids ids2
0 1 a a
Метод where() и маскирование
Выбор значений из Series с помощью булевого вектора обычно возвращает подмножество данных. Чтобы гарантировать, что результат выбора имеет ту же форму, что и исходные данные, вы можете использовать метод where в Series и DataFrame.
Чтобы вернуть только выбранные строки:
In [185]: s[s > 0]
Out[185]:
3 1
2 2
1 3
0 4
dtype: int64
Чтобы вернуть Series той же формы, что и исходная:
In [186]: s.where(s > 0)
Out[186]:
4 NaN
3 1.0
2 2.0
1 3.0
0 4.0
dtype: float64
Выбор значений из DataFrame с помощью булевого критерия теперь также сохраняет форму входных данных. where используется в реализации по умолчанию. Приведенный ниже код эквивалентен df.where(df < 0).
In [187]: df[df < 0]
Out[187]:
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 [188]: df.where(df < 0, -df)
Out[188]:
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 [189]: s2 = s.copy()
In [190]: s2[s2 < 0] = 0
In [191]: s2
Out[191]:
4 0
3 1
2 2
1 3
0 4
dtype: int64
In [192]: df2 = df.copy()
In [193]: df2[df2 < 0] = 0
In [194]: df2
Out[194]:
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 возвращает измененную копию данных. Существует необязательный параметр inplace, чтобы исходные данные могли быть изменены без создания копии:
In [195]: df_orig = df.copy()
In [196]: df_orig.where(df > 0, -df, inplace=True)
In [197]: df_orig
Out[197]:
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
Примечание
Подпись для DataFrame.where() отличается от numpy.where(). Примерно, df1.where(m, df2) эквивалентно np.where(m, df1, df2).
In [198]: df.where(df < 0, -df) == np.where(df < 0, df, -df)
Out[198]:
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 [199]: df2 = df.copy()
In [200]: df2[df2[1:4] > 0] = 3
In [201]: df2
Out[201]:
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 и other для выравнивания ввода при выполнении where.
In [202]: df2 = df.copy()
In [203]: df2.where(df2 > 0, df2['A'], axis='index')
Out[203]:
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 [204]: df2 = df.copy()
In [205]: df.apply(lambda x, y: x.where(x > 0, y), y=df['A'])
Out[205]:
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.
In [206]: df3 = pd.DataFrame({'A': [1, 2, 3],
.....: 'B': [4, 5, 6],
.....: 'C': [7, 8, 9]})
.....:
In [207]: df3.where(lambda x: x > 4, lambda x: x + 10)
Out[207]:
A B C
0 11 14 7
1 12 5 8
2 13 6 9
Маска
mask() является обратной булевой операцией для where.
In [208]: s.mask(s >= 0)
Out[208]:
4 NaN
3 NaN
2 NaN
1 NaN
0 NaN
dtype: float64
In [209]: df.mask(df >= 0)
Out[209]:
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 в ‘зеленый’, когда второй столбец имеет ‘Z’. Вы можете сделать следующее:
In [210]: df = pd.DataFrame({'col1': list('ABBC'), 'col2': list('ZZXY')})
In [211]: df['color'] = np.where(df['col2'] == 'Z', 'green', 'red')
In [212]: df
Out[212]:
col1 col2 color
0 A Z green
1 B Z green
2 B X red
3 C Y red
Если у вас есть несколько условий, вы можете использовать numpy.select() для достижения этого. Допустим, что трем условиям соответствуют три варианта цветов, а четвертый цвет используется по умолчанию. Вы можете сделать следующее.
In [213]: conditions = [
.....: (df['col2'] == 'Z') & (df['col1'] == 'A'),
.....: (df['col2'] == 'Z') & (df['col1'] == 'B'),
.....: (df['col1'] == 'B')
.....: ]
.....:
In [214]: choices = ['yellow', 'blue', 'purple']
In [215]: df['color'] = np.select(conditions, choices, default='black')
In [216]: df
Out[216]:
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 [217]: n = 10
In [218]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [219]: df
Out[219]:
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 [220]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[220]:
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 [221]: df.query('(a < b) & (b < c)')
Out[221]:
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 [222]: df = pd.DataFrame(np.random.randint(n / 2, size=(n, 2)), columns=list('bc'))
In [223]: df.index.name = 'a'
In [224]: df
Out[224]:
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 [225]: df.query('a < b and b < c')
Out[225]:
b c
a
2 3 4
Если вы не хотите или не можете назвать свой индекс, вы можете использовать имя index в выражении запроса:
In [226]: df = pd.DataFrame(np.random.randint(n, size=(n, 2)), columns=list('bc'))
In [227]: df
Out[227]:
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 [228]: df.query('index < b < c')
Out[228]:
b c
2 5 6
Примечание
Если имя вашего индекса совпадает с именем столбца, имя столбца имеет приоритет. Например,
In [229]: df = pd.DataFrame({'a': np.random.randint(5, size=5)})
In [230]: df.index.name = 'a'
In [231]: df.query('a > 2') # uses the column 'a', not the index
Out[231]:
a
a
1 3
3 3
Вы по-прежнему можете использовать индекс в выражении запроса, используя специальный идентификатор «индекс»:
In [232]: df.query('index > 2')
Out[232]:
a
a
3 3
4 2
Если по какой-то причине у вас есть столбец с именем index, то вы также можете сослаться на индекс как на ilevel_0, но на этом этапе стоит рассмотреть возможность переименования столбцов на что-то менее неоднозначное.
Синтаксис запроса MultiIndex query()
Вы также можете использовать уровни MultiIndex объекта DataFrame как если бы они были столбцами во фрейме:
In [233]: n = 10
In [234]: colors = np.random.choice(['red', 'green'], size=n)
In [235]: foods = np.random.choice(['eggs', 'ham'], size=n)
In [236]: colors
Out[236]:
array(['red', 'red', 'red', 'green', 'green', 'green', 'green', 'green',
'green', 'green'], dtype='<U5')
In [237]: foods
Out[237]:
array(['ham', 'ham', 'eggs', 'eggs', 'eggs', 'ham', 'ham', 'eggs', 'eggs',
'eggs'], dtype='<U4')
In [238]: index = pd.MultiIndex.from_arrays([colors, foods], names=['color', 'food'])
In [239]: df = pd.DataFrame(np.random.randn(n, 2), index=index)
In [240]: df
Out[240]:
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 [241]: df.query('color == "red"')
Out[241]:
0 1
color food
red ham 0.194889 -0.381994
ham 0.318587 2.089075
eggs -0.728293 -0.090255
Если уровни MultiIndex не имеют имен, вы можете ссылаться на них, используя специальные имена:
In [242]: df.index.names = [None, None]
In [243]: df
Out[243]:
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 [244]: df.query('ilevel_0 == "red"')
Out[244]:
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 [245]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [246]: df
Out[246]:
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 [247]: df2 = pd.DataFrame(np.random.rand(n + 2, 3), columns=df.columns)
In [248]: df2
Out[248]:
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 [249]: expr = '0.0 <= a <= c <= 0.5'
In [250]: map(lambda frame: frame.query(expr), [df, df2])
Out[250]: <map at 0x7f2809d937f0>
Сравнение синтаксиса Python и pandas для query()
Полный синтаксис наподобие numpy:
In [251]: df = pd.DataFrame(np.random.randint(n, size=(n, 3)), columns=list('abc'))
In [252]: df
Out[252]:
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 [253]: df.query('(a < b) & (b < c)')
Out[253]:
a b c
0 7 8 9
In [254]: df[(df['a'] < df['b']) & (df['b'] < df['c'])]
Out[254]:
a b c
0 7 8 9
Чуть лучше, удаляя скобки (операторы сравнения связываются сильнее, чем & и |):
In [255]: df.query('a < b & b < c')
Out[255]:
a b c
0 7 8 9
Используйте слова вместо символов:
In [256]: df.query('a < b and b < c')
Out[256]:
a b c
0 7 8 9
Довольно близко к тому, как вы можете написать это на бумаге:
In [257]: df.query('a < b < c')
Out[257]:
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 [258]: df = pd.DataFrame({'a': list('aabbccddeeff'), 'b': list('aaaabbbbcccc'),
.....: 'c': np.random.randint(5, size=12),
.....: 'd': np.random.randint(9, size=12)})
.....:
In [259]: df
Out[259]:
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 [260]: df.query('a in b')
Out[260]:
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 [261]: df[df['a'].isin(df['b'])]
Out[261]:
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 [262]: df.query('a not in b')
Out[262]:
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 [263]: df[~df['a'].isin(df['b'])]
Out[263]:
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 [264]: df.query('a in b and c < d')
Out[264]:
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 [265]: df[df['b'].isin(df['a']) & (df['c'] < df['d'])]
Out[265]:
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 [266]: df.query('b == ["a", "b", "c"]')
Out[266]:
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 [267]: df[df['b'].isin(["a", "b", "c"])]
Out[267]:
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 [268]: df.query('c == [1, 2]')
Out[268]:
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 [269]: df.query('c != [1, 2]')
Out[269]:
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 [270]: df.query('[1, 2] in c')
Out[270]:
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 [271]: df.query('[1, 2] not in c')
Out[271]:
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 [272]: df[df['c'].isin([1, 2])]
Out[272]:
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 [273]: df = pd.DataFrame(np.random.rand(n, 3), columns=list('abc'))
In [274]: df['bools'] = np.random.rand(len(df)) > 0.5
In [275]: df.query('~bools')
Out[275]:
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 [276]: df.query('not bools')
Out[276]:
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 [277]: df.query('not bools') == df[~df['bools']]
Out[277]:
a b c bools
2 True True True True
7 True True True True
8 True True True True
Конечно, выражения также могут быть произвольно сложными:
# short query syntax
In [278]: shorter = df.query('a < b < c and (not bools) or bools > 2')
# equivalent in pure Python
In [279]: longer = df[(df['a'] < df['b'])
.....: & (df['b'] < df['c'])
.....: & (~df['bools'])
.....: | (df['bools'] > 2)]
.....:
In [280]: shorter
Out[280]:
a b c bools
7 0.275396 0.691034 0.826619 False
In [281]: longer
Out[281]:
a b c bools
7 0.275396 0.691034 0.826619 False
In [282]: shorter == longer
Out[282]:
a b c bools
7 True True True True
Производительность метода query()
DataFrame.query() с использованием numexpr немного быстрее, чем Python, для больших фреймов.
Примечание
Вы увидите преимущества использования движка numexpr с DataFrame.query() только если ваш фрейм содержит более приблизительно 200 000 строк.
Этот график был создан с помощью DataFrame с 3 столбцами, каждый из которых содержит значения с плавающей запятой, сгенерированные с помощью numpy.random.randn().
Дублирующиеся данные
Если вы хотите определить и удалить дубликаты строк в DataFrame, есть два метода, которые вам помогут: duplicated и drop_duplicates. Каждый принимает в качестве аргумента столбцы, которые будут использоваться для определения дублирующих строк.
duplicatedвозвращает булевый вектор, длина которого равна количеству строк, и который указывает, является ли строка дубликатом.drop_duplicatesудаляет дубликаты строк.
По умолчанию первая наблюдаемая строка набора дубликатов считается уникальной, но каждый метод имеет параметр keep для указания целевых строк, которые должны быть сохранены.
keep='first'(по умолчанию): отмечать/удалять дубликаты, за исключением первого вхождения.keep='last': отмечать/удалять дубликаты, за исключением последнего вхождения.keep=False: отмечать/удалять все дубликаты.
In [283]: 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 [284]: df2
Out[284]:
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 [285]: df2.duplicated('a')
Out[285]:
0 False
1 True
2 False
3 True
4 True
5 False
6 False
dtype: bool
In [286]: df2.duplicated('a', keep='last')
Out[286]:
0 True
1 False
2 True
3 True
4 False
5 False
6 False
dtype: bool
In [287]: df2.duplicated('a', keep=False)
Out[287]:
0 True
1 True
2 True
3 True
4 True
5 False
6 False
dtype: bool
In [288]: df2.drop_duplicates('a')
Out[288]:
a b c
0 one x -1.067137
2 two x -0.211056
5 three x -1.964475
6 four x 1.298329
In [289]: df2.drop_duplicates('a', keep='last')
Out[289]:
a b c
1 one y 0.309500
4 two x -0.390820
5 three x -1.964475
6 four x 1.298329
In [290]: df2.drop_duplicates('a', keep=False)
Out[290]:
a b c
5 three x -1.964475
6 four x 1.298329
Также вы можете передать список столбцов для определения дубликатов.
In [291]: df2.duplicated(['a', 'b'])
Out[291]:
0 False
1 False
2 False
3 False
4 True
5 False
6 False
dtype: bool
In [292]: df2.drop_duplicates(['a', 'b'])
Out[292]:
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 [293]: df3 = pd.DataFrame({'a': np.arange(6),
.....: 'b': np.random.randn(6)},
.....: index=['a', 'a', 'b', 'c', 'b', 'a'])
.....:
In [294]: df3
Out[294]:
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 [295]: df3.index.duplicated()
Out[295]: array([False, True, False, False, True, True])
In [296]: df3[~df3.index.duplicated()]
Out[296]:
a b
a 0 1.440455
b 2 1.038402
c 3 -0.894409
In [297]: df3[~df3.index.duplicated(keep='last')]
Out[297]:
a b
c 3 -0.894409
b 4 0.683536
a 5 3.082764
In [298]: df3[~df3.index.duplicated(keep=False)]
Out[298]:
a b
c 3 -0.894409
Метод get() по типу словаря
Каждый из объектов Series или DataFrame имеет метод get, который может возвращать значение по умолчанию.
In [299]: s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
In [300]: s.get('a') # equivalent to s['a']
Out[300]: 1
In [301]: s.get('x', default=-1)
Out[301]: -1
Поиск значений по меткам индекса/столбцов
Иногда вам нужно извлечь набор значений, заданных последовательностью меток строк и столбцов. Это можно сделать с помощью pandas.factorize и индексирования NumPy. Например:
In [302]: df = pd.DataFrame({'col': ["A", "A", "B", "B"],
.....: 'A': [80, 23, np.nan, 22],
.....: 'B': [80, 55, 76, 67]})
.....:
In [303]: df
Out[303]:
col A B
0 A 80.0 80
1 A 23.0 55
2 B NaN 76
3 B 22.0 67
In [304]: idx, cols = pd.factorize(df['col'])
In [305]: df.reindex(cols, axis=1).to_numpy()[np.arange(len(df)), idx]
Out[305]: array([80., 23., 76., 67.])
Раньше это можно было сделать с помощью специального метода DataFrame.lookup, который был устаревшим с версии 1.2.0.
Индексы объектов
Класс pandas Index и его подклассы можно рассматривать как реализацию упорядоченного мультимножества. Повторения допускаются. Однако, если вы попытаетесь преобразовать объект Index с повторяющимися значениями в set, будет возбуждено исключение.
Index также предоставляет инфраструктуру для поиска, выравнивания данных и переиндексации. Самый простой способ создать Index напрямую — передать list или другую последовательность в Index:
In [306]: index = pd.Index(['e', 'd', 'a', 'b'])
In [307]: index
Out[307]: Index(['e', 'd', 'a', 'b'], dtype='object')
In [308]: 'd' in index
Out[308]: True
Вы также можете передать name для хранения в индексе:
In [309]: index = pd.Index(['e', 'd', 'a', 'b'], name='something')
In [310]: index.name
Out[310]: 'something'
Имя, если оно задано, будет отображено в консоли:
In [311]: index = pd.Index(list(range(5)), name='rows')
In [312]: columns = pd.Index(['A', 'B', 'C'], name='cols')
In [313]: df = pd.DataFrame(np.random.randn(5, 3), index=index, columns=columns)
In [314]: df
Out[314]:
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 [315]: df['A']
Out[315]:
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 [316]: ind = pd.Index([1, 2, 3])
In [317]: ind.rename("apple")
Out[317]: Int64Index([1, 2, 3], dtype='int64', name='apple')
In [318]: ind
Out[318]: Int64Index([1, 2, 3], dtype='int64')
In [319]: ind.set_names(["apple"], inplace=True)
In [320]: ind.name = "bob"
In [321]: ind
Out[321]: Int64Index([1, 2, 3], dtype='int64', name='bob')
set_names, set_levels, и set_codes также принимают необязательный аргумент level
In [322]: index = pd.MultiIndex.from_product([range(3), ['one', 'two']], names=['first', 'second'])
In [323]: index
Out[323]:
MultiIndex([(0, 'one'),
(0, 'two'),
(1, 'one'),
(1, 'two'),
(2, 'one'),
(2, 'two')],
names=['first', 'second'])
In [324]: index.levels[1]
Out[324]: Index(['one', 'two'], dtype='object', name='second')
In [325]: index.set_levels(["a", "b"], level=1)
Out[325]:
MultiIndex([(0, 'a'),
(0, 'b'),
(1, 'a'),
(1, 'b'),
(2, 'a'),
(2, 'b')],
names=['first', 'second'])
Операции над множествами с объектами индексов
Две основные операции — union и intersection. Разность предоставляется методом .difference().
In [326]: a = pd.Index(['c', 'b', 'a'])
In [327]: b = pd.Index(['c', 'e', 'd'])
In [328]: a.difference(b)
Out[328]: Index(['a', 'b'], dtype='object')
Также доступна операция symmetric_difference, которая возвращает элементы, присутствующие либо в idx1, либо в idx2, но не в обоих. Это эквивалентно индексу, созданному с помощью idx1.difference(idx2).union(idx2.difference(idx1)), с удалением дубликатов.
In [329]: idx1 = pd.Index([1, 2, 3, 4])
In [330]: idx2 = pd.Index([2, 3, 4, 5])
In [331]: idx1.symmetric_difference(idx2)
Out[331]: Int64Index([1, 5], dtype='int64')
Примечание
Результат операции над множествами будет отсортирован по возрастанию.
При выполнении Index.union() над индексами с различными типами данных, индексы должны быть приведены к общему типу. Обычно, хотя и не всегда, это тип «объект». Исключением является объединение целочисленных и числовых данных. В этом случае целочисленные значения преобразуются в числа с плавающей точкой.
In [332]: idx1 = pd.Index([0, 1, 2])
In [333]: idx2 = pd.Index([0.5, 1.5])
In [334]: idx1.union(idx2)
Out[334]: Float64Index([0.0, 0.5, 1.0, 1.5, 2.0], dtype='float64')
Пропущенные значения
Важно
Несмотря на то, что Index может содержать пропущенные значения (NaN), следует избегать их использования, если вы не хотите получить неожиданные результаты. Например, некоторые операции неявно исключают пропущенные значения.
Index.fillna заполняет пропущенные значения заданным скалярным значением.
In [335]: idx1 = pd.Index([1, np.nan, 3, 4])
In [336]: idx1
Out[336]: Float64Index([1.0, nan, 3.0, 4.0], dtype='float64')
In [337]: idx1.fillna(2)
Out[337]: Float64Index([1.0, 2.0, 3.0, 4.0], dtype='float64')
In [338]: idx2 = pd.DatetimeIndex([pd.Timestamp('2011-01-01'),
.....: pd.NaT,
.....: pd.Timestamp('2011-01-03')])
.....:
In [339]: idx2
Out[339]: DatetimeIndex(['2011-01-01', 'NaT', '2011-01-03'], dtype='datetime64[ns]', freq=None)
In [340]: idx2.fillna(pd.Timestamp('2011-01-02'))
Out[340]: DatetimeIndex(['2011-01-01', '2011-01-02', '2011-01-03'], dtype='datetime64[ns]', freq=None)
Установка/сброс индекса
Иногда вы загружаете или создаете набор данных в DataFrame и хотите добавить индекс после того, как это уже сделано. Существует несколько способов.
Установка индекса
DataFrame имеет метод set_index(), который принимает имя столбца (для обычного Index) или список имён столбцов (для MultiIndex). Чтобы создать новый, переиндексированный DataFrame:
In [341]: data
Out[341]:
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 [342]: indexed1 = data.set_index('c')
In [343]: indexed1
Out[343]:
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 [344]: indexed2 = data.set_index(['a', 'b'])
In [345]: indexed2
Out[345]:
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 [346]: frame = data.set_index('c', drop=False)
In [347]: frame = frame.set_index(['a', 'b'], append=True)
In [348]: frame
Out[348]:
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 [349]: data.set_index('c', drop=False)
Out[349]:
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
In [350]: data.set_index(['a', 'b'], inplace=True)
In [351]: data
Out[351]:
c d
a b
bar one z 1.0
two y 2.0
foo one x 3.0
two w 4.0
Сброс индекса
Для удобства существует новая функция в DataFrame под названием reset_index(), которая переносит значения индекса в столбцы DataFrame и устанавливает простой целочисленный индекс. Это обратная операция к set_index().
In [352]: data
Out[352]:
c d
a b
bar one z 1.0
two y 2.0
foo one x 3.0
two w 4.0
In [353]: data.reset_index()
Out[353]:
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
Вывод более похож на таблицу SQL или массив записей. Имена столбцов, полученных из индекса, — это те, что хранятся в атрибуте names.
Можно использовать ключевое слово level для удаления только части индекса:
In [354]: frame
Out[354]:
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 [355]: frame.reset_index(level=1)
Out[355]:
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:
data.index = index
Возвращение представления или копии
При настройке значений в объекте pandas необходимо быть осторожным, чтобы избежать того, что называется chained indexing. Вот пример.
In [356]: dfmi = pd.DataFrame([list('abcd'),
.....: list('efgh'),
.....: list('ijkl'),
.....: list('mnop')],
.....: columns=pd.MultiIndex.from_product([['one', 'two'],
.....: ['first', 'second']]))
.....:
In [357]: dfmi
Out[357]:
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 [358]: dfmi['one']['second']
Out[358]:
0 b
1 f
2 j
3 n
Name: second, dtype: object
In [359]: dfmi.loc[:, ('one', 'second')]
Out[359]:
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 обрабатывать это как единое целое. Кроме того, этот порядок операций может быть значительно быстрее и позволяет индексировать обе оси, если это необходимо.
Почему присваивание не выполняется при использовании цепочечной индексации?
Проблема в предыдущем разделе — это просто проблема производительности. В чем дело с предупреждением 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
Ужас!
Порядок вычислений имеет значение
При использовании цепочечной индексации порядок и тип операции индексирования частично определяют, является ли результат срезом исходного объекта или копией этого среза.
pandas имеет SettingWithCopyWarning потому что присваивание копии среза часто не является преднамеренным, а является ошибкой, вызванной тем, что цепочечная индексация возвращает копию там, где ожидался срез.
Если вы хотите, чтобы pandas был более или менее уверен в присваивании выражению с цепочечной индексацией, вы можете установить опцию mode.chained_assignment на одно из этих значений:
'warn', значение по умолчанию, означает, что выводитсяSettingWithCopyWarning.'raise'означает, что pandas выведетSettingWithCopyError, с которым нужно будет разобраться.Noneполностью подавит предупреждения.
In [360]: 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 [361]: dfb['c'][dfb['a'].str.startswith('o')] = 42
Однако это работает с копией и не сработает.
>>> pd.set_option('mode.chained_assignment','warn')
>>> dfb[dfb['a'].str.startswith('o')]['c'] = 42
Traceback (most recent call last)
...
SettingWithCopyWarning:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
Цепочечная операция присваивания также может возникать при установлении в фрейме смешанного типа.
Примечание
Эти правила присвоения применяются ко всем .loc/.iloc.
Следующий метод доступа рекомендуется использовать для нескольких элементов с использованием .loc (с помощью mask) и одного элемента с использованием фиксированного индекса:
In [362]: dfc = pd.DataFrame({'a': ['one', 'one', 'two',
.....: 'three', 'two', 'one', 'six'],
.....: 'c': np.arange(7)})
.....:
In [363]: dfd = dfc.copy()
# Setting multiple items using a mask
In [364]: mask = dfd['a'].str.startswith('o')
In [365]: dfd.loc[mask, 'c'] = 42
In [366]: dfd
Out[366]:
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 [367]: dfd = dfc.copy()
In [368]: dfd.loc[2, 'a'] = 11
In [369]: dfd
Out[369]:
a c
0 one 0
1 one 1
2 11 2
3 three 3
4 two 4
5 one 5
6 six 6
Следующее может работать иногда, но это не гарантируется, поэтому его следует избегать:
In [370]: dfd = dfc.copy()
In [371]: dfd['a'][2] = 111
In [372]: dfd
Out[372]:
a c
0 one 0
1 one 1
2 111 2
3 three 3
4 two 4
5 one 5
6 six 6
Наконец, следующий пример не сработает, поэтому его следует избегать:
>>> pd.set_option('mode.chained_assignment','raise')
>>> dfd.loc[0]['a'] = 1111
Traceback (most recent call last)
...
SettingWithCopyError:
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_index,col_indexer] = value instead
Предупреждение
Предупреждения/исключения при цепочечном присваивании предназначены для информирования пользователя о возможном недопустимом присваивании. Возможно возникновение ложных срабатываний; ситуации, когда цепочечное присваивание ошибочно распознается.
© 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/1.5.0/user_guide/indexing.html