Áp dụng nhiều chức năng cho nhiều cột nhóm


221

Các tài liệu cho thấy cách áp dụng nhiều chức năng trên một đối tượng nhóm tại một thời điểm bằng cách sử dụng một lệnh với các tên cột đầu ra làm các khóa:

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

Tuy nhiên, điều này chỉ hoạt động trên một đối tượng nhóm Series. Và khi một lệnh được chuyển tương tự đến DataFrame nhóm, nó hy vọng các khóa sẽ là tên cột mà hàm sẽ được áp dụng.

Những gì tôi muốn làm là áp dụng nhiều chức năng cho một số cột (nhưng một số cột nhất định sẽ được vận hành nhiều lần). Ngoài ra, một số hàm sẽ phụ thuộc vào các cột khác trong đối tượng nhóm (như các hàm sumif). Giải pháp hiện tại của tôi là đi từng cột và làm một cái gì đó giống như mã ở trên, sử dụng lambdas cho các hàm phụ thuộc vào các hàng khác. Nhưng điều này mất nhiều thời gian, (tôi nghĩ rằng phải mất một thời gian dài để lặp qua một đối tượng nhóm). Tôi sẽ phải thay đổi nó để tôi lặp lại toàn bộ đối tượng nhóm trong một lần chạy, nhưng tôi tự hỏi liệu có một con gấu trúc nào được chế tạo để làm điều này một cách sạch sẽ không.

Ví dụ: tôi đã thử một cái gì đó như

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(), ...)

nhưng như mong đợi, tôi nhận được KeyError (vì các khóa phải là một cột nếu aggđược gọi từ DataFrame).

Có cách nào được xây dựng để làm những gì tôi muốn làm không, hoặc có khả năng chức năng này có thể được thêm vào, hoặc tôi sẽ chỉ cần lặp lại thông qua nhóm theo cách thủ công?

Cảm ơn


2
Nếu bạn đang đến với câu hỏi này trong năm 2017+, vui lòng xem câu trả lời bên dưới để xem cách thành ngữ để tổng hợp nhiều cột với nhau. Câu trả lời hiện được chọn có nhiều khấu hao trong đó, cụ thể là bạn không thể sử dụng từ điển từ điển nữa để đổi tên các cột trong kết quả của một nhóm.
Ted Petrou

Câu trả lời:


282

Nửa sau của câu trả lời hiện được chấp nhận đã lỗi thời và có hai khấu hao. Đầu tiên và quan trọng nhất, bạn không còn có thể chuyển từ điển từ điển sang aggphương pháp nhóm. Thứ hai, không bao giờ sử dụng .ix.

Nếu bạn muốn làm việc với hai cột riêng biệt cùng một lúc, tôi sẽ đề nghị sử dụng applyphương thức ngầm chuyển DataFrame cho hàm được áp dụng. Chúng ta hãy sử dụng một khung dữ liệu tương tự như một từ trên

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

Một từ điển được ánh xạ từ tên cột đến các hàm tổng hợp vẫn là một cách hoàn toàn tốt để thực hiện tổng hợp.

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

Nếu bạn không thích tên cột lambda xấu xí đó, bạn có thể sử dụng hàm bình thường và cung cấp tên tùy chỉnh cho __name__thuộc tính đặc biệt như sau:

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

Sử dụng applyvà trả lại một Series

Bây giờ, nếu bạn có nhiều cột cần tương tác với nhau thì bạn không thể sử dụng agg, điều này hoàn toàn chuyển Sê-ri cho hàm tổng hợp. Khi sử dụng applytoàn bộ nhóm làm DataFrame được truyền vào hàm.

Tôi khuyên bạn nên tạo một hàm tùy chỉnh duy nhất trả về một chuỗi tất cả các tập hợp. Sử dụng chỉ mục Sê-ri làm nhãn cho các cột mới:

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

Nếu bạn đang yêu thích Multi Indexes, bạn vẫn có thể trả về một Series với một như thế này:

    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
Tôi thích mô hình sử dụng một hàm trả về một chuỗi. Rât gọn gang.
Stephen McAteer

2
đây là cách duy nhất tôi tìm thấy để tổng hợp một khung dữ liệu thông qua nhiều đầu vào cột simulatneosly (ví dụ c_d ở trên)
Blake

2
Tôi bối rối trước kết quả, việc tổng kết atrong nhóm 0có nên không 0.418500 + 0.446069 = 0.864569? Điều tương tự cũng đúng với các ô khác, các số không xuất hiện để cộng lại. Nó có thể là một khung dữ liệu cơ bản hơi khác được sử dụng trong các ví dụ tiếp theo không?
slackline

Tôi thường xuyên sử dụng .size () với một nhóm để xem số lượng hồ sơ. Có cách nào để làm điều này bằng phương pháp agg: dict không? Tôi hiểu rằng tôi có thể đếm một lĩnh vực cụ thể, nhưng sở thích của tôi sẽ là tính độc lập với trường.
Chris Decker

1
@slackline có. tôi vừa thử nó và nó hoạt động tốt. Ted phải tạo khung một vài lần khác nhau và vì nó được tạo thông qua việc tạo số ngẫu nhiên, dữ liệu df để tạo dữ liệu thực sự khác với dữ liệu cuối cùng được sử dụng trong các phép tính
Lucas H

166

Đối với phần đầu tiên, bạn có thể truyền một lệnh tên cột cho các khóa và danh sách các hàm cho các giá trị:

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

CẬP NHẬT 1:

Vì hàm tổng hợp hoạt động trên Sê-ri, tham chiếu đến các tên cột khác bị mất. Để giải quyết vấn đề này, bạn có thể tham chiếu toàn bộ khung dữ liệu và lập chỉ mục cho nó bằng cách sử dụng các chỉ mục nhóm trong hàm lambda.

Đây là một cách giải quyết khó khăn:

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

Ở đây, cột 'D' kết quả được tạo thành từ các giá trị 'E' tổng hợp.

CẬP NHẬT 2:

Đây là một phương pháp mà tôi nghĩ sẽ làm mọi thứ bạn yêu cầu. Đầu tiên thực hiện một chức năng lambda tùy chỉnh. Dưới đây, g tham khảo nhóm. Khi tổng hợp, g sẽ là một Series. Chuyển qua g.indexđể df.ix[]chọn nhóm hiện tại từ df. Sau đó tôi kiểm tra nếu cột C nhỏ hơn 0,5. Chuỗi boolean được trả về được chuyển đến g[]chỉ chọn những hàng đáp ứng tiêu chí.

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

Thật thú vị, tôi cũng có thể chuyển một mệnh lệnh {funcname: func}là giá trị thay vì danh sách để giữ tên tùy chỉnh của mình. Nhưng trong cả hai trường hợp, tôi không thể vượt qua một lambdacột sử dụng các cột khác (như lambda x: x['D'][x['C'] < 3].sum()trên: "KeyError: 'D'"). Bất cứ ý tưởng nếu điều đó có thể?
râu

Tôi đã cố gắng thực hiện chính xác điều đó và tôi đã nhận được lỗiKeyError: 'D'
Zelazny7

Thật tuyệt, tôi đã có nó để làm việc với df['A'].ix[g.index][df['C'] < 0].sum(). Tuy nhiên, điều này bắt đầu trở nên khá lộn xộn - Tôi nghĩ rằng việc lặp thủ công dễ đọc có thể thích hợp hơn, cộng với tôi không chắc chắn có cách nào để đặt cho nó tên ưa thích của tôi trong aggđối số (thay vì <lambda>). Tôi sẽ hy vọng rằng ai đó có thể biết một cách đơn giản hơn ...
râu

3
Bạn có thể truyền một lệnh cho giá trị cột {'D': {'my name':lambda function}}và nó sẽ biến khóa chính bên trong thành tên cột.
Zelazny7

1
Tôi tin rằng gấu trúc bây giờ hỗ trợ nhiều chức năng áp dụng cho một nhóm-by dataframe: pandas.pydata.org/pandas-docs/stable/...
IANS

22

Thay thế (chủ yếu là về thẩm mỹ) cho câu trả lời của Ted Petrou, tôi thấy tôi thích một danh sách nhỏ gọn hơn một chút. Xin đừng cân nhắc việc chấp nhận nó, đó chỉ là một nhận xét chi tiết hơn nhiều về câu trả lời của Ted, cộng với mã / dữ liệu. Python / gấu trúc không phải là đầu tiên / tốt nhất của tôi, nhưng tôi thấy điều này để đọc tốt:

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

Tôi thấy nó gợi nhớ nhiều hơn về các dplyrđường ống và data.tablecác lệnh xích. Không phải nói họ tốt hơn, chỉ quen thuộc hơn với tôi. (Tôi chắc chắn nhận ra sức mạnh và, đối với nhiều người, ưu tiên sử dụng các defchức năng chính thức hơn cho các loại hoạt động này. Đây chỉ là một giải pháp thay thế, không nhất thiết phải tốt hơn.)


Tôi đã tạo dữ liệu theo cách tương tự như Ted, tôi sẽ thêm một hạt giống để tái tạo.

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
Tôi thích câu trả lời này nhất. Điều này tương tự với các ống dplyr trong R.
Renhuai

18

Pandas >= 0.25.0, tập hợp được đặt tên

Kể từ phiên bản gấu trúc 0.25.0trở lên, chúng tôi đang rời khỏi tập hợp và đổi tên dựa trên từ điển, và chuyển sang tập hợp được đặt tên chấp nhận a tuple. Bây giờ chúng ta có thể đồng thời tổng hợp + đổi tên thành một tên cột nhiều thông tin hơn:

Ví dụ :

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

Áp dụng GroupBy.aggvới tổng hợp được đặt tên:

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

Tôi thích các tập hợp được đặt tên này nhưng tôi không thể thấy chúng ta phải sử dụng chúng với nhiều cột như thế nào?
Simon Woodhead

Câu hỏi hay, không thể hiểu điều này, nghi ngờ điều này là có thể (chưa). Tôi đã mở một cho việc này. Sẽ giữ câu hỏi của tôi và bạn cập nhật. Cảm ơn bạn đã chỉ ra @SimonWoodhead
Erfan

4

Mới trong phiên bản 0.25.0.

Để hỗ trợ tập hợp theo cột cụ thể với quyền kiểm soát các tên cột đầu ra, gấu trúc chấp nhận cú pháp đặc biệt trong GroupBy.agg () , được gọi là tập hợp có tên là tập hợp tên , trong đó

  • Các từ khóa là tên cột đầu ra
  • Các giá trị là các bộ có phần tử đầu tiên là cột cần chọn và phần tử thứ hai là tập hợp để áp dụng cho cột đó. Pandas cung cấp cho gấu trúc.NamedAgg được đặt tên với các trường ['cột', 'aggfunc'] để làm rõ hơn các đối số là gì. Như thường lệ, tập hợp có thể là một bí danh có thể gọi hoặc chuỗi.
    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

gấu trúc.NamedAgg chỉ là một tên được đặt tên. Đồng bằng tuples cũng được cho phép.

    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

Đối số từ khóa bổ sung không được chuyển qua các hàm tổng hợp. Chỉ các cặp (cột, aggfunc) mới được truyền dưới dạng ** kwargs. Nếu các hàm tổng hợp của bạn yêu cầu các đối số bổ sung, hãy áp dụng một phần chúng với funcools.partial ().

Tập hợp được đặt tên cũng có giá trị cho tập hợp nhóm. Trong trường hợp này không có lựa chọn cột, vì vậy các giá trị chỉ là các hàm.

    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

Câu trả lời của Ted thật tuyệt vời. Tôi đã kết thúc bằng một phiên bản nhỏ hơn trong trường hợp có ai quan tâm. Hữu ích khi bạn đang tìm kiếm một tập hợp phụ thuộc vào các giá trị từ nhiều cột:

tạo một khung dữ liệu

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

nhóm và tổng hợp với áp dụng (sử dụng nhiều cột)

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

phân nhóm và tổng hợp với tổng hợp (sử dụng nhiều cột)

Tôi thích cách tiếp cận này vì tôi vẫn có thể sử dụng tổng hợp. Có lẽ mọi người sẽ cho tôi biết lý do tại sao áp dụng là cần thiết để có được ở nhiều cột khi thực hiện tổng hợp trên các nhóm.

Bây giờ có vẻ hiển nhiên, nhưng miễn là bạn không chọn cột quan tâm trực tiếp sau khi nhóm , bạn sẽ có quyền truy cập vào tất cả các cột của khung dữ liệu từ trong hàm tổng hợp của bạn.

chỉ truy cập vào cột đã chọn

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

truy cập vào tất cả các cột kể từ khi lựa chọn là sau tất cả các phép thuật

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

hoặc tương tự

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

Tôi hi vọng cái này giúp được.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.