หมายเหตุ
โพสต์นี้จะมีโครงสร้างในลักษณะต่อไปนี้:
- คำถามที่ใส่ไว้ใน OP จะได้รับการตอบทีละคำถาม
- สำหรับคำถามแต่ละข้อจะมีการสาธิตวิธีการอย่างน้อยหนึ่งวิธีที่ใช้ในการแก้ปัญหานี้และการได้รับผลลัพธ์ที่คาดหวัง
บันทึก (เช่นเดียวกับนี้) จะรวมไว้สำหรับผู้อ่านที่สนใจเรียนรู้เกี่ยวกับฟังก์ชันเพิ่มเติมรายละเอียดการใช้งานและข้อมูลอื่น ๆ ตามหัวข้อที่ต้องการ บันทึกเหล่านี้ได้รับการรวบรวมผ่านการกำจัดเอกสารและการเปิดเผยคุณสมบัติที่คลุมเครือต่างๆและจากประสบการณ์ของฉันเอง (ที่ยอมรับได้ในวง จำกัด )
ตัวอย่างโค้ดทั้งหมดได้สร้างและทดสอบในv0.23.4 หมีแพนด้า python3.7 หากมีบางอย่างไม่ชัดเจนหรือไม่ถูกต้องตามความเป็นจริงหรือหากคุณไม่พบวิธีแก้ไขที่เกี่ยวข้องกับกรณีการใช้งานของคุณโปรดแนะนำการแก้ไขขอคำชี้แจงในความคิดเห็นหรือเปิดคำถามใหม่ .... ตามความเหมาะสม .
นี่คือคำแนะนำเกี่ยวกับสำนวนทั่วไปบางส่วน (ต่อจากนี้ไปจะเรียกว่าสี่สำนวน) เราจะกลับมาเยี่ยมชมบ่อยๆ
DataFrame.loc
- โซลูชันทั่วไปสำหรับการเลือกตามฉลาก (+ pd.IndexSlice
สำหรับการใช้งานที่ซับซ้อนมากขึ้นเกี่ยวกับชิ้นส่วน)
DataFrame.xs
- แยกส่วนตัดขวางเฉพาะจาก Series / DataFrame
DataFrame.query
- ระบุการดำเนินการแบ่งส่วนและ / หรือการกรองแบบไดนามิก (กล่าวคือเป็นนิพจน์ที่ได้รับการประเมินแบบไดนามิกสามารถใช้ได้กับบางสถานการณ์มากกว่าสถานการณ์อื่น ๆ ดูส่วนนี้ของเอกสารสำหรับการสืบค้นใน MultiIndexes
การสร้างดัชนีบูลีนด้วยมาสก์ที่สร้างขึ้นโดยใช้MultiIndex.get_level_values
(มักใช้ร่วมกับIndex.isin
โดยเฉพาะอย่างยิ่งเมื่อกรองด้วยหลายค่า) นอกจากนี้ยังมีประโยชน์มากในบางสถานการณ์
การพิจารณาปัญหาการแบ่งส่วนและการกรองต่างๆในแง่ของสำนวนทั้งสี่จะเป็นประโยชน์เพื่อให้เข้าใจถึงสิ่งที่สามารถนำไปใช้กับสถานการณ์ที่กำหนดได้ดีขึ้น เป็นสิ่งสำคัญมากที่จะต้องเข้าใจว่าไม่ใช่ทุกสำนวนที่จะทำงานได้ดีเท่ากัน (ถ้าเป็นเช่นนั้น) ในทุกสถานการณ์ หากสำนวนไม่ได้รับการระบุว่าเป็นวิธีแก้ปัญหาด้านล่างนั่นหมายความว่าสำนวนนั้นไม่สามารถใช้กับปัญหานั้นได้อย่างมีประสิทธิภาพ
คำถามที่ 1
ฉันจะเลือกแถวที่มี "a" ในระดับ "หนึ่ง" ได้อย่างไร
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'])
คำถาม 1b
ฉันจะแบ่งแถวทั้งหมดที่มีค่า "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']]
) การแบ่งส่วนจะถือว่าอยู่บนคอลัมน์และ a 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" ในระดับ "หนึ่ง" ได้อย่างไร
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
การใช้ loc ทำได้ในลักษณะเดียวกันโดยระบุรายการ
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'])]
คำถาม 2 ข
ฉันจะรับค่าทั้งหมดที่สอดคล้องกับ "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" ในระดับ "หนึ่ง" หรือ "t" ในระดับ "สอง" ได้อย่างไร
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
!