ใช้ฟังก์ชั่นหลายกับคอลัมน์หลายกลุ่มโดย


221

เอกสารแสดงวิธีการใช้ฟังก์ชั่นหลายคนบนวัตถุ GroupBy ในเวลาใช้ Dict ที่มีชื่อคอลัมน์ออกเป็นกุญแจ:

In [563]: grouped['D'].agg({'result1' : np.sum,
   .....:                   'result2' : np.mean})
   .....:
Out[563]: 
      result2   result1
A                      
bar -0.579846 -1.739537
foo -0.280588 -1.402938

อย่างไรก็ตามสิ่งนี้ใช้ได้กับวัตถุ Groupby ซีรีย์เท่านั้น และเมื่อ dict ถูกส่งผ่านไปยัง groupby DataFrame ในทำนองเดียวกันก็คาดว่าคีย์จะเป็นชื่อคอลัมน์ที่ฟังก์ชั่นจะถูกนำไปใช้

สิ่งที่ฉันต้องการทำคือใช้หลายฟังก์ชั่นกับหลายคอลัมน์ (แต่คอลัมน์บางคอลัมน์จะทำงานหลายครั้ง) นอกจากนี้บางฟังก์ชั่นจะขึ้นอยู่กับคอลัมน์อื่น ๆ ในวัตถุ groupby (เช่นฟังก์ชัน sumif) ทางออกปัจจุบันของฉันคือไปทีละคอลัมน์และทำบางอย่างเช่นโค้ดด้านบนโดยใช้ lambdas สำหรับฟังก์ชั่นที่ขึ้นอยู่กับแถวอื่น ๆ แต่มันใช้เวลานาน (ฉันคิดว่ามันต้องใช้เวลานานกว่าจะย้ำผ่านวัตถุกลุ่มโดย) ฉันจะต้องเปลี่ยนมันเพื่อที่ฉันจะวนซ้ำไปตามวัตถุทั้งหมดของกลุ่มในการวิ่งครั้งเดียว แต่ฉันสงสัยว่าถ้ามีแพนด้าในตัวเพื่อทำสิ่งนี้ค่อนข้างเรียบร้อย

ตัวอย่างเช่นฉันได้ลองทำสิ่งที่ชอบ

grouped.agg({'C_sum' : lambda x: x['C'].sum(),
             'C_std': lambda x: x['C'].std(),
             'D_sum' : lambda x: x['D'].sum()},
             'D_sumifC3': lambda x: x['D'][x['C'] == 3].sum(), ...)

แต่ตามที่คาดไว้ฉันได้รับ KeyError (เนื่องจากคีย์ต้องเป็นคอลัมน์หากaggถูกเรียกจาก DataFrame)

มีวิธีใดที่จะทำในสิ่งที่ฉันต้องการหรือความเป็นไปได้ที่อาจมีการเพิ่มฟังก์ชั่นนี้หรือฉันจะต้องทำซ้ำผ่านกลุ่มด้วยตนเองหรือไม่

ขอบคุณ


2
หากคุณกำลังมาที่คำถามนี้ในปี 2560+ โปรดดูคำตอบด้านล่างเพื่อดูวิธีใช้สำนวนเพื่อรวมหลายคอลัมน์เข้าด้วยกัน คำตอบที่เลือกไว้ในปัจจุบันมีการคัดค้านหลายครั้งกล่าวคือคุณไม่สามารถใช้พจนานุกรมของพจนานุกรมอีกต่อไปเพื่อเปลี่ยนชื่อคอลัมน์ในผลลัพธ์ของกลุ่มโดย
Ted Petrou

คำตอบ:


282

ครึ่งหลังของคำตอบที่ยอมรับในปัจจุบันนั้นล้าสมัยและมีการคัดค้านสองครั้ง อย่างแรกและสำคัญที่สุดคุณจะไม่สามารถส่งพจนานุกรมพจนานุกรมไปยังaggวิธีการของกลุ่มได้อีกต่อไป .ixประการที่สองไม่เคยใช้

หากคุณต้องการทำงานกับสองคอลัมน์แยกกันในเวลาเดียวกันฉันขอแนะนำให้ใช้applyวิธีการที่ส่งผ่าน DataFrame ไปยังฟังก์ชันที่ใช้โดยนัย ลองใช้ dataframe ที่คล้ายกันจากด้านบน

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.418500  0.030955  0.874869  0.145641      0
1  0.446069  0.901153  0.095052  0.487040      0
2  0.843026  0.936169  0.926090  0.041722      1
3  0.635846  0.439175  0.828787  0.714123      1

พจนานุกรมที่แมปจากชื่อคอลัมน์ไปยังฟังก์ชันการรวมยังคงเป็นวิธีที่ดีอย่างสมบูรณ์ในการดำเนินการรวม

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': lambda x: x.max() - x.min()})

              a                   b         c         d
            sum       max      mean       sum  <lambda>
group                                                  
0      0.864569  0.446069  0.466054  0.969921  0.341399
1      1.478872  0.843026  0.687672  1.754877  0.672401

หากคุณไม่ชอบชื่อคอลัมน์แลมบ์ดาที่น่าเกลียดคุณสามารถใช้ฟังก์ชั่นปกติและระบุชื่อที่กำหนดเองให้กับ__name__แอตทริบิวต์พิเศษเช่นนี้:

def max_min(x):
    return x.max() - x.min()

max_min.__name__ = 'Max minus Min'

df.groupby('group').agg({'a':['sum', 'max'], 
                         'b':'mean', 
                         'c':'sum', 
                         'd': max_min})

              a                   b         c             d
            sum       max      mean       sum Max minus Min
group                                                      
0      0.864569  0.446069  0.466054  0.969921      0.341399
1      1.478872  0.843026  0.687672  1.754877      0.672401

การใช้applyและส่งคืนซีรี่ส์

ตอนนี้ถ้าคุณมีหลายคอลัมน์ที่จำเป็นต้องมีการโต้ตอบกันคุณจะไม่สามารถใช้งานได้aggซึ่งส่งผ่านซีรีส์ไปยังฟังก์ชันการรวม เมื่อใช้applyทั้งกลุ่มเป็น DataFrame จะถูกส่งผ่านไปยังฟังก์ชัน

ฉันขอแนะนำให้สร้างฟังก์ชั่นที่กำหนดเองเพียงตัวเดียวที่คืนค่าการรวมตัวทั้งหมด ใช้ดัชนีซีรี่ส์เป็นป้ายกำกับสำหรับคอลัมน์ใหม่:

def f(x):
    d = {}
    d['a_sum'] = x['a'].sum()
    d['a_max'] = x['a'].max()
    d['b_mean'] = x['b'].mean()
    d['c_d_prodsum'] = (x['c'] * x['d']).sum()
    return pd.Series(d, index=['a_sum', 'a_max', 'b_mean', 'c_d_prodsum'])

df.groupby('group').apply(f)

         a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.864569  0.446069  0.466054     0.173711
1      1.478872  0.843026  0.687672     0.630494

หากคุณตกหลุมรัก MultiIndexes คุณยังสามารถกลับมาใช้ซีรี่ส์ได้อีกเช่น:

    def f_mi(x):
        d = []
        d.append(x['a'].sum())
        d.append(x['a'].max())
        d.append(x['b'].mean())
        d.append((x['c'] * x['d']).sum())
        return pd.Series(d, index=[['a', 'a', 'b', 'c_d'], 
                                   ['sum', 'max', 'mean', 'prodsum']])

df.groupby('group').apply(f_mi)

              a                   b       c_d
            sum       max      mean   prodsum
group                                        
0      0.864569  0.446069  0.466054  0.173711
1      1.478872  0.843026  0.687672  0.630494

3
ฉันชอบรูปแบบการใช้ฟังก์ชั่นที่ส่งคืนซีรี่ส์ เรียบร้อยมาก
Stephen McAteer

2
นี้เป็นวิธีเดียวที่ฉันได้พบการรวม dataframe ปัจจัยการผลิตผ่านทางคอลัมน์หลาย simulatneosly (ตัวอย่าง c_d ด้านบน)
เบลค

2
ฉันสับสนกับผลลัพธ์การสรุปaภายในกลุ่ม0นี้ไม่ควรเป็น0.418500 + 0.446069 = 0.864569อย่างไร สิ่งเดียวกันนี้ถือเป็นจริงสำหรับเซลล์อื่น ๆ ตัวเลขไม่ปรากฏขึ้น เป็นไปได้ไหมว่าอาจใช้ดาต้าเบสพื้นฐานที่ต่างกันเล็กน้อยในตัวอย่างต่อไป
slackline

ฉันใช้. size () กับ groupby เป็นประจำเพื่อดูจำนวนเรคคอร์ด มีวิธีทำเช่นนี้หรือไม่โดยใช้วิธี agg: dict ฉันเข้าใจว่าฉันสามารถนับฟิลด์ใดฟิลด์หนึ่งได้ แต่การตั้งค่าของฉันจะทำให้การนับเป็นฟิลด์อิสระ
Chris Decker

1
@slackline ใช่ ฉันเพิ่งทดสอบและใช้งานได้ดี เท็ดจะต้องสร้างเฟรมเพียงไม่กี่ครั้งและเนื่องจากมันถูกสร้างขึ้นผ่านการสร้างตัวเลขสุ่มข้อมูล df ที่จะสร้างข้อมูลนั้นแตกต่างจากที่ใช้ในการคำนวณในที่สุด
Lucas H

166

ในส่วนแรกคุณสามารถส่งคำสั่งชื่อคอลัมน์สำหรับคีย์และรายการฟังก์ชันสำหรับค่าต่างๆ:

In [28]: df
Out[28]:
          A         B         C         D         E  GRP
0  0.395670  0.219560  0.600644  0.613445  0.242893    0
1  0.323911  0.464584  0.107215  0.204072  0.927325    0
2  0.321358  0.076037  0.166946  0.439661  0.914612    1
3  0.133466  0.447946  0.014815  0.130781  0.268290    1

In [26]: f = {'A':['sum','mean'], 'B':['prod']}

In [27]: df.groupby('GRP').agg(f)
Out[27]:
            A                   B
          sum      mean      prod
GRP
0    0.719580  0.359790  0.102004
1    0.454824  0.227412  0.034060

อัปเดต 1:

เนื่องจากฟังก์ชันการรวมทำงานบน Series การอ้างอิงไปยังชื่อคอลัมน์อื่นจะหายไป ในการหลีกเลี่ยงปัญหานี้คุณสามารถอ้างอิงดาต้าเฟรมทั้งหมดและจัดทำดัชนีโดยใช้ดัชนีกลุ่มภายในฟังก์ชั่นแลมบ์ดา

นี่คือวิธีแก้ปัญหาแฮ็ค:

In [67]: f = {'A':['sum','mean'], 'B':['prod'], 'D': lambda g: df.loc[g.index].E.sum()}

In [69]: df.groupby('GRP').agg(f)
Out[69]:
            A                   B         D
          sum      mean      prod  <lambda>
GRP
0    0.719580  0.359790  0.102004  1.170219
1    0.454824  0.227412  0.034060  1.182901

ที่นี่คอลัมน์ 'D' ผลลัพธ์ประกอบด้วยค่าสรุป 'E'

อัปเดต 2:

นี่คือวิธีที่ฉันคิดว่าจะทำทุกสิ่งที่คุณถาม ก่อนอื่นให้สร้างฟังก์ชั่นแลมบ์ดาที่กำหนดเอง ด้านล่าง g อ้างอิงกลุ่ม เมื่อรวมกัน g จะเป็นซีรี่ส์ การส่งผ่านg.indexเพื่อdf.ix[]เลือกกลุ่มปัจจุบันจาก df ฉันจะทดสอบว่าคอลัมน์ C น้อยกว่า 0.5 หรือไม่ ชุดบูลีนที่ส่งคืนถูกส่งผ่านไปg[]ซึ่งเลือกเฉพาะแถวเหล่านั้นที่ตรงกับเกณฑ์

In [95]: cust = lambda g: g[df.loc[g.index]['C'] < 0.5].sum()

In [96]: f = {'A':['sum','mean'], 'B':['prod'], 'D': {'my name': cust}}

In [97]: df.groupby('GRP').agg(f)
Out[97]:
            A                   B         D
          sum      mean      prod   my name
GRP
0    0.719580  0.359790  0.102004  0.204072
1    0.454824  0.227412  0.034060  0.570441

ที่น่าสนใจฉันยังสามารถส่งผ่านการกำหนด{funcname: func}เป็นค่าแทนที่จะเป็นรายการเพื่อรักษาชื่อที่กำหนดเองของฉัน แต่ไม่ว่าในกรณีใดฉันไม่สามารถlambdaใช้คอลัมน์ที่ใช้คอลัมน์อื่น (เช่นlambda x: x['D'][x['C'] < 3].sum()ด้านบน: "KeyError: 'D'") มีความคิดอะไรบ้างถ้าเป็นไปได้?
beardc

ฉันพยายามที่จะทำอย่างนั้นและฉันได้รับข้อผิดพลาดKeyError: 'D'
Zelazny7

df['A'].ix[g.index][df['C'] < 0].sum()เย็นผมได้รับมันในการทำงานด้วย นี่เป็นจุดเริ่มต้นที่ค่อนข้างยุ่งเหยิง - ฉันคิดว่าการวนซ้ำแบบแมนนวลอาจจะดีกว่าและฉันไม่แน่ใจว่ามีวิธีที่จะให้ชื่อที่ฉันต้องการในการaggโต้แย้ง (แทน<lambda>) ฉันจะถือเอาความหวังว่าใครบางคนอาจจะรู้วิธีที่ตรงไปตรงมามากขึ้น ...
beardc

3
คุณสามารถส่ง dict สำหรับค่าคอลัมน์{'D': {'my name':lambda function}}และจะทำให้คีย์ dict ภายในเป็นชื่อคอลัมน์
Zelazny7

1
ผมเชื่อว่าหมีแพนด้าในขณะนี้สนับสนุนฟังก์ชั่นหลาย ๆ นำไปใช้กับการจัดกลุ่มโดย dataframe: pandas.pydata.org/pandas-docs/stable/...
IANS

22

ในฐานะที่เป็นทางเลือก (ส่วนใหญ่เกี่ยวกับสุนทรียศาสตร์) สำหรับคำตอบของ Ted Petrou ฉันพบว่าฉันชอบรายการที่มีขนาดกะทัดรัดกว่าเล็กน้อย โปรดอย่าพิจารณายอมรับมันเป็นเพียงความคิดเห็นที่มีรายละเอียดมากขึ้นเกี่ยวกับคำตอบของ Ted รวมถึงรหัส / ข้อมูล Python / pandas ไม่ใช่คนแรก / ดีที่สุดของฉัน แต่ฉันพบสิ่งนี้ให้อ่านอย่างดี:

df.groupby('group') \
  .apply(lambda x: pd.Series({
      'a_sum'       : x['a'].sum(),
      'a_max'       : x['a'].max(),
      'b_mean'      : x['b'].mean(),
      'c_d_prodsum' : (x['c'] * x['d']).sum()
  })
)

          a_sum     a_max    b_mean  c_d_prodsum
group                                           
0      0.530559  0.374540  0.553354     0.488525
1      1.433558  0.832443  0.460206     0.053313

ฉันพบว่ามันชวนให้นึกถึงdplyrท่อและdata.tableคำสั่งที่ถูกล่ามโซ่ อย่าบอกว่าพวกเขาเก่งกว่าฉันคุ้นเคยมากขึ้น (แน่นอนฉันรู้จักพลังและสำหรับหลาย ๆ คนความชอบในการใช้defฟังก์ชั่นที่เป็นทางการมากขึ้นสำหรับการดำเนินการประเภทนี้เป็นเพียงทางเลือกไม่จำเป็นต้องดีกว่า)


ฉันสร้างข้อมูลในลักษณะเดียวกับ Ted ฉันจะเพิ่ม seed สำหรับการทำซ้ำ

import numpy as np
np.random.seed(42)
df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]
df

          a         b         c         d  group
0  0.374540  0.950714  0.731994  0.598658      0
1  0.156019  0.155995  0.058084  0.866176      0
2  0.601115  0.708073  0.020584  0.969910      1
3  0.832443  0.212339  0.181825  0.183405      1

2
ฉันชอบคำตอบนี้มากที่สุด คล้ายกับท่อ dplyr ใน R.
Renhuai

18

Pandas >= 0.25.0การรวมชื่อ

ตั้งแต่รุ่นหมีแพนด้า0.25.0หรือสูงกว่าที่เราจะย้ายออกจากการรวมพจนานุกรมพื้นฐานและการเปลี่ยนชื่อและย้ายไปสู่การรวมชื่อtupleที่ยอมรับ ตอนนี้เราสามารถรวม + เปลี่ยนชื่อเป็นชื่อคอลัมน์ที่ให้ข้อมูลเพิ่มเติมพร้อมกัน:

ตัวอย่าง :

df = pd.DataFrame(np.random.rand(4,4), columns=list('abcd'))
df['group'] = [0, 0, 1, 1]

          a         b         c         d  group
0  0.521279  0.914988  0.054057  0.125668      0
1  0.426058  0.828890  0.784093  0.446211      0
2  0.363136  0.843751  0.184967  0.467351      1
3  0.241012  0.470053  0.358018  0.525032      1

ใช้GroupBy.aggกับการรวมชื่อ:

df.groupby('group').agg(
             a_sum=('a', 'sum'),
             a_mean=('a', 'mean'),
             b_mean=('b', 'mean'),
             c_sum=('c', 'sum'),
             d_range=('d', lambda x: x.max() - x.min())
)

          a_sum    a_mean    b_mean     c_sum   d_range
group                                                  
0      0.947337  0.473668  0.871939  0.838150  0.320543
1      0.604149  0.302074  0.656902  0.542985  0.057681

ฉันชอบการรวมที่มีชื่อเหล่านี้ แต่ฉันไม่เห็นว่าเราควรใช้กับคอลัมน์หลายคอลัมน์อย่างไร
Simon Woodhead

เป็นคำถามที่ดีไม่สามารถเข้าใจได้สงสัยว่าเป็นไปได้ (ยัง) ฉันเปิดตั๋วสำหรับสิ่งนี้ จะทำให้คำถามของฉันและคุณอัปเดต ขอบคุณสำหรับการชี้ให้เห็น @SimonWoodhead
Erfan

4

ใหม่ในเวอร์ชัน 0.25.0

เพื่อสนับสนุนการรวมคอลัมน์เฉพาะที่มีการควบคุมชื่อคอลัมน์เอาต์พุตนุ่นยอมรับไวยากรณ์พิเศษในGroupBy.agg ()หรือที่รู้จักในชื่อ“ การรวมชื่อที่มีชื่อ”โดยที่

  • คำหลักคือชื่อคอลัมน์ผลลัพธ์
  • ค่าเป็นสิ่งอันดับที่องค์ประกอบแรกคือคอลัมน์ที่จะเลือกและองค์ประกอบที่สองคือการรวมเพื่อนำไปใช้กับคอลัมน์นั้น Pandas ให้ pandas.NamedAgg ตั้งชื่อว่า tuple พร้อมกับฟิลด์ ['column', 'aggfunc'] เพื่อให้ชัดเจนว่าอาร์กิวเมนต์คืออะไร ตามปกติการรวมสามารถเรียกได้หรือนามแฝงสตริง
    In [79]: animals = pd.DataFrame({'kind': ['cat', 'dog', 'cat', 'dog'],
       ....:                         'height': [9.1, 6.0, 9.5, 34.0],
       ....:                         'weight': [7.9, 7.5, 9.9, 198.0]})
       ....: 

    In [80]: animals
    Out[80]: 
      kind  height  weight
    0  cat     9.1     7.9
    1  dog     6.0     7.5
    2  cat     9.5     9.9
    3  dog    34.0   198.0

    In [81]: animals.groupby("kind").agg(
       ....:     min_height=pd.NamedAgg(column='height', aggfunc='min'),
       ....:     max_height=pd.NamedAgg(column='height', aggfunc='max'),
       ....:     average_weight=pd.NamedAgg(column='weight', aggfunc=np.mean),
       ....: )
       ....: 
    Out[81]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

pandas.NamedAgg เป็นเพียง tuple อนุญาตให้ใช้ tuples ธรรมดาได้เช่นกัน

    In [82]: animals.groupby("kind").agg(
       ....:     min_height=('height', 'min'),
       ....:     max_height=('height', 'max'),
       ....:     average_weight=('weight', np.mean),
       ....: )
       ....: 
    Out[82]: 
          min_height  max_height  average_weight
    kind                                        
    cat          9.1         9.5            8.90
    dog          6.0        34.0          102.75

อาร์กิวเมนต์คำหลักเพิ่มเติมจะไม่ถูกส่งผ่านไปยังฟังก์ชันการรวม ควรส่งผ่านคู่ของ (คอลัมน์ aggfunc) เป็น ** kwargs หากฟังก์ชันการรวมของคุณต้องการอาร์กิวเมนต์เพิ่มเติมให้ใช้ฟังก์ชัน functools.partial () บางส่วน

การรวมชื่อนั้นใช้ได้สำหรับการรวมกลุ่มโดย Series ในกรณีนี้ไม่มีการเลือกคอลัมน์ดังนั้นค่าจึงเป็นเพียงฟังก์ชัน

    In [84]: animals.groupby("kind").height.agg(
       ....:     min_height='min',
       ....:     max_height='max',
       ....: )
       ....: 
    Out[84]: 
          min_height  max_height
    kind                        
    cat          9.1         9.5
    dog          6.0        34.0

3

คำตอบของ Ted นั้นวิเศษมาก ฉันลงเอยด้วยการใช้เวอร์ชั่นที่เล็กกว่านี้ในกรณีที่ใคร ๆ ก็สนใจ มีประโยชน์เมื่อคุณกำลังมองหาการรวมหนึ่งที่ขึ้นอยู่กับค่าจากหลายคอลัมน์:

สร้าง dataframe

df=pd.DataFrame({'a': [1,2,3,4,5,6], 'b': [1,1,0,1,1,0], 'c': ['x','x','y','y','z','z']})


   a  b  c
0  1  1  x
1  2  1  x
2  3  0  y
3  4  1  y
4  5  1  z
5  6  0  z

การจัดกลุ่มและการรวมกับการใช้ (ใช้หลายคอลัมน์)

df.groupby('c').apply(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

c
x    2.0
y    4.0
z    5.0

การจัดกลุ่มและการรวมกับการรวม (ใช้หลายคอลัมน์)

ฉันชอบวิธีการนี้เนื่องจากฉันยังสามารถใช้การรวมได้ บางทีผู้คนจะแจ้งให้เราทราบว่าเหตุใดจึงจำเป็นต้องมีการสมัครหลายคอลัมน์เมื่อทำการรวมกลุ่ม

ดูเหมือนชัดเจนตอนนี้ แต่ตราบใดที่คุณไม่ได้เลือกคอลัมน์ที่สนใจหลังจากกลุ่มโดยคุณจะสามารถเข้าถึงคอลัมน์ทั้งหมดของ dataframe จากภายในฟังก์ชั่นการรวมของคุณ

เข้าถึงคอลัมน์ที่เลือกเท่านั้น

df.groupby('c')['a'].aggregate(lambda x: x[x>1].mean())

เข้าถึงทุกคอลัมน์เนื่องจากการเลือกเป็นสิ่งมหัศจรรย์

df.groupby('c').aggregate(lambda x: x[(x['a']>1) & (x['b']==1)].mean())['a']

หรือในทำนองเดียวกัน

df.groupby('c').aggregate(lambda x: x['a'][(x['a']>1) & (x['b']==1)].mean())

ฉันหวังว่านี่จะช่วยได้.

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.