Catatan
Posting ini akan disusun dengan cara berikut:
- Pertanyaan-pertanyaan yang diajukan dalam OP akan dibahas satu per satu
- Untuk setiap pertanyaan, satu atau lebih metode yang dapat diterapkan untuk memecahkan masalah ini dan mendapatkan hasil yang diharapkan akan didemonstrasikan.
Catatan (seperti ini) akan disertakan bagi pembaca yang tertarik mempelajari tentang fungsionalitas tambahan, detail implementasi, dan info sepintas lainnya untuk topik yang sedang dibahas. Catatan ini telah disusun dengan menjelajahi dokumen dan mengungkap berbagai fitur yang tidak jelas, dan dari pengalaman saya sendiri (yang memang terbatas).
Semua contoh kode telah dibuat dan diuji pada pandas v0.23.4, python3.7 . Jika ada sesuatu yang tidak jelas, atau tidak benar secara faktual, atau jika Anda tidak menemukan solusi yang sesuai untuk kasus penggunaan Anda, jangan ragu untuk menyarankan pengeditan, meminta klarifikasi di komentar, atau membuka pertanyaan baru, .... sebagaimana berlaku .
Berikut adalah pengantar beberapa idiom umum (untuk selanjutnya disebut sebagai Empat Idiom) yang akan sering kita kunjungi kembali
DataFrame.loc
- Solusi umum untuk pemilihan berdasarkan label (+ pd.IndexSlice
untuk aplikasi yang lebih kompleks yang melibatkan irisan)
DataFrame.xs
- Ekstrak penampang tertentu dari Seri / DataFrame.
DataFrame.query
- Tentukan operasi pemotongan dan / atau pemfilteran secara dinamis (misalnya, sebagai ekspresi yang dievaluasi secara dinamis. Lebih dapat diterapkan pada beberapa skenario daripada yang lain. Lihat juga bagian dokumen ini untuk melakukan kueri di MultiIndex.
Pengindeksan Boolean dengan mask yang dibuat menggunakan MultiIndex.get_level_values
(sering kali berhubungan dengan Index.isin
, terutama saat memfilter dengan beberapa nilai). Ini juga cukup berguna dalam beberapa situasi.
Akan bermanfaat untuk melihat berbagai masalah pemotongan dan pemfilteran dalam kaitannya dengan Empat Idiom untuk mendapatkan pemahaman yang lebih baik tentang apa yang dapat diterapkan pada situasi tertentu. Sangat penting untuk dipahami bahwa tidak semua idiom akan bekerja sama dengan baik (jika ada) dalam setiap keadaan. Jika idiom belum dicantumkan sebagai solusi potensial untuk masalah di bawah ini, itu berarti idiom tidak dapat diterapkan pada masalah itu secara efektif.
pertanyaan 1
Bagaimana cara memilih baris yang memiliki "a" di tingkat "satu"?
col
one two
a t 0
u 1
v 2
w 3
Anda dapat menggunakan loc
, sebagai solusi tujuan umum yang berlaku untuk sebagian besar situasi:
df.loc[['a']]
Pada titik ini, jika Anda mendapatkan
TypeError: Expected tuple, got str
Itu berarti Anda menggunakan panda versi lama. Pertimbangkan untuk meningkatkan! Jika tidak, gunakan df.loc[('a', slice(None)), :]
.
Alternatifnya, Anda dapat menggunakan di xs
sini, karena kami mengekstraksi satu penampang lintang. Perhatikan argumen levels
dan axis
(default yang masuk akal dapat diasumsikan di sini).
df.xs('a', level=0, axis=0, drop_level=False)
# df.xs('a', drop_level=False)
Di sini, drop_level=False
argumen diperlukan untuk mencegah xs
penurunan level "satu" dalam hasil (level yang kita potong).
Namun opsi lain di sini adalah menggunakan query
:
df.query("one == 'a'")
Jika indeks tidak memiliki nama, Anda perlu mengubah string kueri Anda menjadi "ilevel_0 == 'a'"
.
Terakhir, menggunakan 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']
Selain itu, bagaimana saya bisa menurunkan level "satu" pada output?
col
two
t 0
u 1
v 2
w 3
Ini dapat dengan mudah dilakukan dengan menggunakan keduanya
df.loc['a'] # Notice the single string argument instead the list.
Atau,
df.xs('a', level=0, axis=0, drop_level=True)
# df.xs('a')
Perhatikan bahwa kita dapat menghilangkan drop_level
argumen (diasumsikan secara True
default).
Catatan
Anda mungkin memperhatikan bahwa DataFrame yang difilter mungkin masih memiliki semua level, bahkan jika mereka tidak muncul saat mencetak DataFrame. Sebagai contoh,
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'])
Anda dapat menghilangkan level ini menggunakan 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'])
Pertanyaan 1b
Bagaimana cara saya memotong semua baris dengan nilai "t" pada level "dua"?
col
one two
a t 0
b t 4
t 8
d t 12
Secara intuitif, Anda menginginkan sesuatu yang melibatkan slice()
:
df.loc[(slice(None), 't'), :]
It Just Works! ™ Tapi itu kikuk. Kami dapat memfasilitasi sintaks pemotongan yang lebih alami menggunakan pd.IndexSlice
API di sini.
idx = pd.IndexSlice
df.loc[idx[:, 't'], :]
Ini jauh lebih bersih.
Catatan
Mengapa potongan trailing :
melintasi kolom diperlukan? Ini karena, loc
dapat digunakan untuk memilih dan mengiris sepanjang kedua sumbu ( axis=0
atau
axis=1
). Tanpa secara eksplisit menjelaskan pada sumbu mana pemotongan harus dilakukan, operasi menjadi ambigu. Lihat kotak merah besar di dokumentasi tentang mengiris .
Jika Anda ingin menghilangkan bayangan ambiguitas, loc
terima axis
parameter:
df.loc(axis=0)[pd.IndexSlice[:, 't']]
Tanpa axis
parameter (yaitu, hanya dengan melakukan df.loc[pd.IndexSlice[:, 't']]
), pemotongan diasumsikan berada pada kolom, dan a KeyError
akan dimunculkan dalam keadaan ini.
Ini didokumentasikan di pemotong . Untuk tujuan posting ini, bagaimanapun, kami akan secara eksplisit menentukan semua sumbu.
Dengan xs
, itu
df.xs('t', axis=0, level=1, drop_level=False)
Dengan query
, itu
df.query("two == 't'")
# Or, if the first level has no name,
# df.query("ilevel_1 == 't'")
Dan akhirnya, dengan get_level_values
, Anda dapat melakukannya
df[df.index.get_level_values('two') == 't']
# Or, to perform selection by position/integer,
# df[df.index.get_level_values(1) == 't']
Semua untuk efek yang sama.
Pertanyaan 2
Bagaimana cara memilih baris yang sesuai dengan item "b" dan "d" di level "satu"?
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
Menggunakan loc, ini dilakukan dengan cara yang sama dengan menentukan daftar.
df.loc[['b', 'd']]
Untuk mengatasi masalah memilih "b" dan "d" di atas, Anda juga dapat menggunakan 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')
Catatan
Ya, parser default adalah 'pandas'
, tetapi penting untuk menyoroti sintaks ini bukan python konvensional. Parser Pandas menghasilkan pohon parse yang sedikit berbeda dari ekspresi. Ini dilakukan untuk membuat beberapa operasi lebih intuitif untuk ditentukan. Untuk informasi lebih lanjut, silakan baca posting saya tentang
Evaluasi Ekspresi Dinamis di pandas menggunakan pd.eval () .
Dan, dengan get_level_values
+ Index.isin
:
df[df.index.get_level_values("one").isin(['b', 'd'])]
Pertanyaan 2b
Bagaimana saya mendapatkan semua nilai yang sesuai dengan "t" dan "w" di level "dua"?
col
one two
a t 0
w 3
b t 4
w 7
t 8
d w 11
t 12
w 15
Dengan loc
, ini hanya mungkin dalam hubungannya dengan pd.IndexSlice
.
df.loc[pd.IndexSlice[:, ['t', 'w']], :]
Usus besar pertama :
di pd.IndexSlice[:, ['t', 'w']]
sarana untuk mengiris di tingkat pertama. Saat kedalaman level yang ditanyakan meningkat, Anda perlu menentukan lebih banyak irisan, satu per level yang dipotong. Namun, Anda tidak perlu menentukan lebih banyak level selain yang sedang diiris.
Dengan query
, ini
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')
Dengan get_level_values
dan Index.isin
(mirip dengan di atas):
df[df.index.get_level_values('two').isin(['t', 'w'])]
Pertanyaan 3
Bagaimana cara mengambil penampang, yaitu, satu baris yang memiliki nilai tertentu untuk indeks df
? Secara khusus, bagaimana cara mengambil penampang ('c', 'u')
, yang diberikan oleh
col
one two
c u 9
Gunakan loc
dengan menentukan tuple kunci:
df.loc[('c', 'u'), :]
Atau,
df.loc[pd.IndexSlice[('c', 'u')]]
Catatan
Pada titik ini, Anda mungkin mengalami PerformanceWarning
yang terlihat seperti ini:
PerformanceWarning: indexing past lexsort depth may impact performance.
Ini hanya berarti indeks Anda tidak diurutkan. panda bergantung pada indeks yang diurutkan (dalam hal ini, secara leksikografis, karena kita berurusan dengan nilai string) untuk pencarian dan pengambilan yang optimal. Perbaikan cepat adalah mengurutkan DataFrame Anda terlebih dahulu menggunakan DataFrame.sort_index
. Ini terutama diinginkan dari sudut pandang kinerja jika Anda berencana melakukan beberapa kueri seperti itu secara bersamaan:
df_sort = df.sort_index()
df_sort.loc[('c', 'u')]
Anda juga dapat menggunakan MultiIndex.is_lexsorted()
untuk memeriksa apakah indeks diurutkan atau tidak. Fungsi ini mengembalikan True
atau False
sesuai. Anda dapat memanggil fungsi ini untuk menentukan apakah langkah pengurutan tambahan diperlukan atau tidak.
Dengan xs
, ini lagi-lagi hanya meneruskan satu tupel sebagai argumen pertama, dengan semua argumen lain disetel ke default yang sesuai:
df.xs(('c', 'u'))
Dengan query
, hal-hal menjadi agak kikuk:
df.query("one == 'c' and two == 'u'")
Anda sekarang dapat melihat bahwa ini akan relatif sulit untuk digeneralisasikan. Tapi masih oke untuk masalah khusus ini.
Dengan akses yang mencakup beberapa level, get_level_values
masih dapat digunakan, tetapi tidak disarankan:
m1 = (df.index.get_level_values('one') == 'c')
m2 = (df.index.get_level_values('two') == 'u')
df[m1 & m2]
Pertanyaan 4
Bagaimana cara memilih dua baris yang sesuai dengan ('c', 'u')
, dan ('a', 'w')
?
col
one two
c u 9
a w 3
Dengan loc
, ini masih sesederhana:
df.loc[[('c', 'u'), ('a', 'w')]]
# df.loc[pd.IndexSlice[[('c', 'u'), ('a', 'w')]]]
Dengan query
, Anda perlu membuat string kueri secara dinamis dengan melakukan iterasi pada lintas bagian dan level Anda:
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% JANGAN MEREKOMENDASIKAN! Tapi itu mungkin.
Pertanyaan 5
Bagaimana saya bisa mengambil semua baris yang sesuai dengan "a" di tingkat "satu" atau "t" di tingkat "dua"?
col
one two
a t 0
u 1
v 2
w 3
b t 4
t 8
d t 12
Ini sebenarnya sangat sulit dilakukan dengan loc
tetap memastikan kebenaran dan tetap menjaga kejelasan kode. df.loc[pd.IndexSlice['a', 't']]
tidak benar, itu diartikan sebagai df.loc[pd.IndexSlice[('a', 't')]]
(yaitu, memilih penampang lintang). Anda mungkin memikirkan solusi pd.concat
untuk menangani setiap label secara terpisah:
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
Tapi Anda akan melihat salah satu baris digandakan. Ini karena baris tersebut memenuhi kedua kondisi pemotongan, dan muncul dua kali. Anda harus melakukannya
v = pd.concat([
df.loc[['a'],:], df.loc[pd.IndexSlice[:, 't'],:]
])
v[~v.index.duplicated()]
Tetapi jika DataFrame Anda secara inheren berisi indeks duplikat (yang Anda inginkan), ini tidak akan mempertahankannya. Gunakan dengan sangat hati-hati .
Dengan query
, ini sangat sederhana:
df.query("one == 'a' or two == 't'")
Dengan get_level_values
, ini masih sederhana, tetapi tidak seelegan:
m1 = (df.index.get_level_values('one') == 'a')
m2 = (df.index.get_level_values('two') == 't')
df[m1 | m2]
Pertanyaan 6
Bagaimana cara saya memotong penampang melintang tertentu? Untuk "a" dan "b", saya ingin memilih semua baris dengan sub-level "u" dan "v", dan untuk "d", saya ingin memilih baris dengan sub-level "w".
col
one two
a u 1
v 2
b u 5
v 6
d w 11
w 15
Ini adalah kasus khusus yang saya tambahkan untuk membantu memahami penerapan Empat Idiom — ini adalah satu kasus di mana tidak satupun dari mereka akan bekerja secara efektif, karena pemotongannya sangat spesifik, dan tidak mengikuti pola nyata apa pun.
Biasanya, masalah pemotongan seperti ini memerlukan pengalihan daftar kunci secara eksplisit ke loc
. Salah satu cara untuk melakukannya adalah dengan:
keys = [('a', 'u'), ('a', 'v'), ('b', 'u'), ('b', 'v'), ('d', 'w')]
df.loc[keys, :]
Jika Anda ingin menyimpan beberapa pengetikan, Anda akan mengenali bahwa terdapat pola pemotongan "a", "b" dan sublevelnya, sehingga kita dapat memisahkan tugas pemotongan menjadi dua bagian dan concat
hasilnya:
pd.concat([
df.loc[(('a', 'b'), ('u', 'v')), :],
df.loc[('d', 'w'), :]
], axis=0)
Spesifikasi pemotongan untuk "a" dan "b" sedikit lebih bersih (('a', 'b'), ('u', 'v'))
karena sub-level yang sama yang diindeks juga sama untuk setiap level.
Pertanyaan 7
Bagaimana cara mendapatkan semua baris yang nilai pada level "dua" lebih besar dari 5?
col
one two
b 7 4
9 5
c 7 10
d 6 11
8 12
8 13
6 15
Ini bisa dilakukan dengan query
,
df2.query("two > 5")
Dan get_level_values
.
df2[df2.index.get_level_values('two') > 5]
Catatan
Mirip dengan contoh ini, kita dapat memfilter berdasarkan kondisi sembarang apa pun menggunakan konstruksi ini. Secara umum, penting untuk diingat bahwa loc
dan xs
secara khusus untuk pengindeksan berbasis label, sementara query
dan
get_level_values
berguna untuk membangun masker bersyarat umum untuk pemfilteran.
Pertanyaan Bonus
Bagaimana jika saya perlu memotong MultiIndex
kolom ?
Sebenarnya, sebagian besar solusi di sini juga berlaku untuk kolom, dengan sedikit perubahan. Mempertimbangkan:
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
Ini adalah perubahan berikut yang perlu Anda lakukan pada Empat Idiom agar dapat bekerja dengan kolom.
Untuk mengiris loc
, gunakan
df3.loc[:, ....] # Notice how we slice across the index with `:`.
atau,
df3.loc[:, pd.IndexSlice[...]]
Untuk menggunakan yang xs
sesuai, cukup berikan argumen axis=1
.
Anda dapat mengakses nilai level kolom secara langsung menggunakan df.columns.get_level_values
. Anda kemudian perlu melakukan sesuatu seperti
df.loc[:, {condition}]
Dimana {condition}
mewakili beberapa kondisi yang dibangun dengan menggunakan columns.get_level_values
.
Untuk menggunakan query
, satu-satunya pilihan Anda adalah mengubah urutan, kueri pada indeks, dan mengubah urutan lagi:
df3.T.query(...).T
Tidak disarankan, gunakan salah satu dari 3 opsi lainnya.
level
argumen untukIndex.isin
!