Примітка
Ця публікація буде структурована таким чином:
- Питання, висунуті в ОП, будуть вирішені по черзі
- Для кожного питання буде продемонстровано один або кілька методів, застосовних для вирішення цієї проблеми та отримання очікуваного результату.
Примітка s (приблизно подібна до цієї) буде включена для читачів, зацікавлених дізнатися про додаткові функціональні можливості, деталі реалізації та інші відомості щодо відповідної теми. Ці замітки були складені шляхом прочитання документів та розкриття різних незрозумілих особливостей та з мого власного (правда, обмеженого) досвіду.
Усі зразки коду створені та протестовані на пандах v0.23.4, python3.7 . Якщо щось не зрозуміло, або фактично невірно, або якщо ви не знайшли рішення, застосовне до вашої справи використання, будь ласка, запропонуйте редагувати, запитати роз'яснення в коментарях або відкрити нове запитання .... .
Ось ознайомлення з деякими загальними ідіомами (відтепер їх називаються чотирма ідіомами), які ми будемо часто відвідувати
DataFrame.loc
- загальне рішення для вибору за міткою (+ pd.IndexSlice
для більш складних додатків, що включають фрагменти)
DataFrame.xs
- Витягніть певний поперечний переріз із серії / DataFrame.
DataFrame.query
- Динамічно вказуйте операції зрізання та / або фільтрації (тобто як вираження, яке динамічно оцінюється. Більш застосовне для деяких сценаріїв, ніж інші. Також дивіться цей розділ документів для запитів на MultiIndexes.
Булева індексація за допомогою маски, створеної за допомогою MultiIndex.get_level_values
(часто в поєднанні з Index.isin
, особливо при фільтрації з кількома значеннями). Це також досить корисно за деяких обставин.
Буде корисно подивитися на різні проблеми з нарізанням та фільтруванням з точки зору чотирьох ідіом, щоб краще зрозуміти, що можна застосувати до даної ситуації. Дуже важливо розуміти, що не всі ідіоми працюватимуть однаково добре (якщо взагалі) за будь-яких обставин. Якщо ідіома не вказана як потенційне рішення проблеми нижче, це означає, що ідіома не може бути ефективно застосована до цієї проблеми.
питання 1
Як вибрати рядки, що мають "a" на рівні "one"?
col
one two
a t 0
u 1
v 2
w 3
Ви можете використовувати loc
як загальне рішення, застосовне до більшості ситуацій:
df.loc[['a']]
У цей момент, якщо ви отримаєте
TypeError: Expected tuple, got str
Це означає, що ви використовуєте старішу версію панд. Розгляньте можливість модернізації! В іншому випадку використовуйте df.loc[('a', slice(None)), :]
.
Можна також скористатися xs
тут, оскільки ми витягуємо один поперечний переріз. Зверніть увагу на аргументи levels
та axis
аргументи (тут можна припустити розумні за замовчуванням).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Тут drop_level=False
потрібен аргумент, щоб запобігти xs
падінню рівня "один" в результаті (рівня, який ми нарізали).
Ще один варіант тут використовується query
:
df.query("one == 'a'")
Якщо в індексі не було імені, вам потрібно змінити рядок запиту, щоб бути "ilevel_0 == 'a'"
.
Нарешті, використовуючи get_level_values
:
df[df.index.get_level_values('one') == 'a']
# If your levels are unnamed, or if you need to select by position (not label),
# df[df.index.get_level_values(0) == 'a']
Крім того, як я міг би опустити рівень "один" у виході?
col
two
t 0
u 1
v 2
w 3
Це легко зробити за допомогою будь-якого
df.loc['a'] # Notice the single string argument instead the list.
Або,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Зауважте, що ми можемо опустити drop_level
аргумент (він вважається True
за замовчуванням).
Примітка
Ви можете помітити, що відфільтрований DataFrame все ще може мати всі рівні, навіть якщо вони не відображаються при друкуванні DataFrame. Наприклад,
v = df.loc[['a']]
print(v)
col
one two
a t 0
u 1
v 2
w 3
print(v.index)
MultiIndex(levels=[['a', 'b', 'c', 'd'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Ви можете позбутися цих рівнів за допомогою MultiIndex.remove_unused_levels
:
v.index = v.index.remove_unused_levels()
print(v.index)
MultiIndex(levels=[['a'], ['t', 'u', 'v', 'w']],
labels=[[0, 0, 0, 0], [0, 1, 2, 3]],
names=['one', 'two'])
Питання 1б
Як я розрізаю всі рядки зі значенням "t" на рівні "два"?
col
one two
a t 0
b t 4
t 8
d t 12
Інтуїтивно, ви хочете щось, що стосується slice()
:
df.loc[(slice(None), 't'), :]
Це просто працює! ™ Але це незграбно. Ми можемо полегшити більш природний синтаксис нарізки, використовуючи pd.IndexSlice
тут API.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
Це набагато, набагато чистіше.
Примітка.
Для чого :
потрібен останній зріз у стовпцях? Це тому, що loc
може використовуватися для вибору та зрізу по обох осях ( axis=0
або
axis=1
). Без чіткого пояснення, на якій осі потрібно робити нарізку, операція стає неоднозначною. Дивіться велике червоне поле в документації щодо нарізки .
Якщо ви хочете видалити будь-який відтінок неоднозначності, loc
приймає axis
параметр:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Без axis
параметра (тобто, просто виконуючи df.loc[pd.IndexSlice[:, 't']]
), нарізання вважається стовпчиком, і KeyError
в цій обставині буде піднято а.
Це задокументовано на зрізах . Однак для цієї публікації ми чітко вкажемо всі осі.
З xs
, це так
df.xs('t', axis=0, level=1, drop_level=False)
З query
, це так
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
І нарешті, з get_level_values
, ви можете зробити
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Все з однаковим ефектом.
Питання 2
Як можна вибрати рядки, відповідні пунктам "b" і "d" на рівні "one"?
col
one two
b t 4
u 5
v 6
w 7
t 8
d w 11
t 12
u 13
v 14
w 15
Використовуючи локальний, це робиться аналогічно, вказавши список.
df.loc[['b', 'd']]
Для вирішення вищезазначеної проблеми вибору "b" і "d" ви також можете скористатися query
:
items = ['b', 'd']
df.query("one in @items")
# df.query("one == @items", parser='pandas')
# df.query("one in ['b', 'd']")
# df.query("one == ['b', 'd']", parser='pandas')
Примітка
Так, аналізатор за замовчуванням є 'pandas'
, але важливо виділити, що цей синтаксис не є умовно python. Аналізатор Pandas генерує дещо інше дерево розбору від виразу. Це робиться для того, щоб зробити деякі операції більш інтуїтивно зрозумітими. Для отримання додаткової інформації, будь ласка, прочитайте моє повідомлення про
оцінку динамічної виразності в пандах, використовуючи pd.eval () .
І, з get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Питання 2b
Як я можу отримати всі значення, що відповідають "t" і "w" на рівні "два"?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
З loc
, це можливо лише в поєднанні з pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Першого двокрапки :
в pd.IndexSlice[:, ['t', 'w']]
засіб нарізати поперек першого рівня. У міру збільшення глибини запитуваного рівня вам потрібно буде вказати більше фрагментів, по одному на кожен рівень. Однак вам не потрібно буде вказувати більше рівнів, ніж той, що нарізаний.
З query
, це
items = ['t', 'w']
df.query("two in @items")
# df.query("two == @items", parser='pandas')
# df.query("two in ['t', 'w']")
# df.query("two == ['t', 'w']", parser='pandas')
З get_level_values
і Index.isin
(аналогічно вище):
df[df.index.get_level_values('two').isin(['t', 'w'])]
Питання 3
Як отримати поперечний переріз, тобто один рядок із конкретними значеннями для індексу df
? Зокрема, як отримати поперечний переріз ('c', 'u')
, заданий користувачем
col
one two
c u 9
Використовуйте loc
, вказуючи набір ключів:
df.loc[('c', 'u'), :]
Або,
df.loc[pd.IndexSlice[('c', 'u')]]
Примітка.
На цьому етапі ви можете зіткнутися з PerformanceWarning
таким виглядом:
PerformanceWarning: indexing past lexsort depth may impact performance.
Це просто означає, що ваш індекс не відсортований. панди залежать від сортування індексу (у цьому випадку лексикографічно, оскільки ми маємо справу зі значеннями рядків) для оптимального пошуку та пошуку. Швидке виправлення полягатиме в тому, щоб заздалегідь відсортувати ваш DataFrame DataFrame.sort_index
. Це особливо бажано з точки зору продуктивності, якщо ви плануєте виконувати кілька таких запитів у тандемі:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
Ви також MultiIndex.is_lexsorted()
можете перевірити, сортується чи ні індекс. Ця функція повертається True
або False
відповідно. Ви можете зателефонувати за допомогою цієї функції, щоб визначити, чи потрібен додатковий крок сортування чи ні.
З xs
, це знову-таки просто передавання одного кортежу в якості першого аргументу, а всі інші аргументи встановлені відповідно до стандартних параметрів:
df.xs(('c', 'u'))
З цим query
все стає трохи незграбним:
df.query("one == 'c' and two == 'u'")
Тепер ви бачите, що узагальнити це буде досить важко. Але все одно гаразд з цією конкретною проблемою.
З доступом, що охоплює декілька рівнів, get_level_values
все ще можна використовувати, але не рекомендується:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Питання 4
Як вибрати два ряди, що відповідають ('c', 'u')
, і ('a', 'w')
?
col
one two
c u 9
a w 3
З loc
, це все ще так просто, як:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
З query
, вам потрібно буде динамічно генерувати рядок запиту, повторюючи його перерізи та рівні:
cses = [('c', 'u'), ('a', 'w')]
levels = ['one', 'two']
# This is a useful check to make in advance.
assert all(len(levels) == len(cs) for cs in cses)
query = '(' + ') or ('.join([
' and '.join([f"({l} == {repr(c)})" for l, c in zip(levels, cs)])
for cs in cses
]) + ')'
print(query)
# ((one == 'c') and (two == 'u')) or ((one == 'a') and (two == 'w'))
df.query(query)
100% НЕ РЕКОМЕНДУЙТЕ! Але це можливо.
Питання 5
Як я можу отримати всі рядки, що відповідають рівню "a" на рівні "one" або "t" на рівні "two"?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Це насправді дуже важко зробити loc
, забезпечуючи правильність та зберігаючи чіткість коду. df.loc[pd.IndexSlice['a', 't']]
невірно, інтерпретується як df.loc[pd.IndexSlice[('a', 't')]]
(тобто вибір перерізу). Ви можете придумати рішення pd.concat
щодо обробки кожної етикетки окремо:
pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
col
one two
a t 0
u 1
v 2
w 3
t 0 # Does this look right to you? No, it isn't!
b t 4
t 8
d t 12
Але ви помітите, що один з рядків дублюється. Це тому, що цей ряд задовольняв обидва умови нарізки, і так з’являвся двічі. Натомість вам потрібно буде це зробити
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Але якщо ваш DataFrame по суті містить повторювані індекси (що вам потрібно), то це не збереже їх. Використовуйте з особливою обережністю .
З query
, це дурно просто:
df.query("one == 'a' or two == 't'")
З get_level_values
, це все ще просто, але не так елегантно:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Питання 6
Як я можу нарізати конкретні перерізи? Для "a" і "b" я хотів би виділити всі рядки з підрівень "u" і "v", а для "d" - я хотів би вибрати рядки з підрівнем "w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Це особливий випадок, який я додав, щоб допомогти зрозуміти застосовність чотирьох ідіом - це один випадок, коли жодна з них не буде ефективно працювати, оскільки нарізка дуже специфічна і не відповідає реальній схемі.
Зазвичай для вирізання подібних проблем потрібно буде явно передати список ключів loc
. Один із способів зробити це:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Якщо ви хочете зберегти деякий текст, ви визнаєте, що існує схема для нарізки "a", "b" та її підрівнів, тож ми можемо розділити завдання нарізки на дві частини та concat
результат:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
Специфікація нарізки для "a" і "b" трохи чіткіша, (('a', 'b'), ('u', 'v'))
оскільки однакові підрівні, які індексуються, однакові для кожного рівня.
Питання 7
Як отримати всі рядки, де значення на рівні "два" перевищують 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Це можна зробити за допомогою query
,
df2.query("two > 5")
І get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Примітка
Подібно до цього прикладу, ми можемо фільтрувати на основі будь-якої довільної умови за допомогою цих конструкцій. Загалом, корисно пам’ятати про це loc
та xs
спеціально для індексування на основі міток, в той час як query
і
get_level_values
корисні для побудови загальних умовних масок для фільтрації.
Бонусне питання
Що робити, якщо мені потрібно нарізати MultiIndex
стовпчик ?
Насправді більшість рішень тут застосовні і до стовпців, з незначними змінами. Поміркуйте:
np.random.seed(0)
mux3 = pd.MultiIndex.from_product([
list('ABCD'), list('efgh')
], names=['one','two'])
df3 = pd.DataFrame(np.random.choice(10, (3, len(mux))), columns=mux3)
print(df3)
one A B C D
two e f g h e f g h e f g h e f g h
0 5 0 3 3 7 9 3 5 2 4 7 6 8 8 1 6
1 7 7 8 1 5 9 8 9 4 3 0 3 5 0 2 3
2 8 1 3 3 3 7 0 1 9 9 0 4 7 3 2 7
Це наступні зміни, які вам потрібно буде внести до "Чотири ідіоми", щоб вони працювали зі стовпцями.
Щоб нарізати loc
, використовуйте
df3.loc[:, ....] # Notice how we slice across the index with `:`.
або,
df3.loc[:, pd.IndexSlice[...]]
Щоб скористатись xs
відповідним чином, просто передайте аргумент axis=1
.
Ви можете отримати доступ до значень рівня стовпців безпосередньо, використовуючи df.columns.get_level_values
. Тоді вам потрібно буде зробити щось на кшталт
df.loc[:, {condition}]
Де {condition}
представлена деяка умова, побудована за допомогою columns.get_level_values
.
Для використання query
вашим єдиним варіантом є переміщення, запит на індекс та перенесення знову:
df3.T.query(...).T
Не рекомендується, використовуйте один з інших 3 варіантів.
level
аргумент доIndex.isin
!