Python Pandas Làm cách nào để gán kết quả hoạt động theo nhóm trở lại các cột trong khung dữ liệu mẹ?


81

Tôi có khung dữ liệu sau trong IPython, trong đó mỗi hàng là một kho duy nhất:

In [261]: bdata
Out[261]:
<class 'pandas.core.frame.DataFrame'>
Int64Index: 21210 entries, 0 to 21209
Data columns:
BloombergTicker      21206  non-null values
Company              21210  non-null values
Country              21210  non-null values
MarketCap            21210  non-null values
PriceReturn          21210  non-null values
SEDOL                21210  non-null values
yearmonth            21210  non-null values
dtypes: float64(2), int64(1), object(4)

Tôi muốn áp dụng một phép toán theo nhóm để tính toán lợi tức trung bình có trọng số giới hạn trên mọi thứ, mỗi ngày trong cột "tháng".

Điều này hoạt động như mong đợi:

In [262]: bdata.groupby("yearmonth").apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
Out[262]:
yearmonth
201204      -0.109444
201205      -0.290546

Nhưng sau đó tôi muốn sắp xếp "phát sóng" các giá trị này trở lại các chỉ số trong khung dữ liệu ban đầu và lưu chúng dưới dạng cột không đổi nơi khớp ngày tháng.

In [263]: dateGrps = bdata.groupby("yearmonth")

In [264]: dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/mnt/bos-devrnd04/usr6/home/espears/ws/Research/Projects/python-util/src/util/<ipython-input-264-4a68c8782426> in <module>()
----> 1 dateGrps["MarketReturn"] = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())

TypeError: 'DataFrameGroupBy' object does not support item assignment

Tôi nhận ra rằng nhiệm vụ ngây thơ này không nên làm việc. Nhưng thành ngữ Pandas "đúng" để gán kết quả của một phép toán theo nhóm vào một cột mới trên khung dữ liệu mẹ là gì?

Cuối cùng, tôi muốn một cột có tên "MarketReturn" sẽ là một giá trị không đổi lặp lại cho tất cả các chỉ số có ngày khớp với đầu ra của hoạt động theo nhóm.

Một mẹo để đạt được điều này sẽ như sau:

marketRetsByDate  = dateGrps.apply(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())

bdata["MarketReturn"] = np.repeat(np.NaN, len(bdata))

for elem in marketRetsByDate.index.values:
    bdata["MarketReturn"][bdata["yearmonth"]==elem] = marketRetsByDate.ix[elem]

Nhưng điều này là chậm, xấu và không đẹp.


Bạn đang gán lại cho đối tượng được nhóm của mình thay vì khung ban đầu.
Wouter Overmeire,

2
Tôi biết điều đó và tôi đã nói như vậy ngay bên dưới lỗi, nơi tôi nói: "Tôi nhận ra rằng phép gán ngây thơ này không hiệu quả. Nhưng thành ngữ Pandas" đúng "là gì khi gán kết quả của một phép toán theo nhóm vào một cột mới trên cột mẹ. khung dữ liệu?" Thực hiện bài tập với khung dữ liệu ban đầu của tôi trên LHS cũng không hoạt động và thậm chí còn kém trực quan hơn khi thêm cột ở cấp GroupBy-object.
ely

Câu trả lời:


73
In [97]: df = pandas.DataFrame({'month': np.random.randint(0,11, 100), 'A': np.random.randn(100), 'B': np.random.randn(100)})

In [98]: df.join(df.groupby('month')['A'].sum(), on='month', rsuffix='_r')
Out[98]:
           A         B  month       A_r
0  -0.040710  0.182269      0 -0.331816
1  -0.004867  0.642243      1  2.448232
2  -0.162191  0.442338      4  2.045909
3  -0.979875  1.367018      5 -2.736399
4  -1.126198  0.338946      5 -2.736399
5  -0.992209 -1.343258      1  2.448232
6  -1.450310  0.021290      0 -0.331816
7  -0.675345 -1.359915      9  2.722156

Điều này vẫn yêu cầu tôi phải lưu tính toán theo nhóm, thay vì phải thực hiện nhiệm vụ trực tiếp trên LHS trên đường dây mà tôi thực hiện thao tác theo nhóm. Áp dụng có thể tốt hơn một chút so với vòng lặp trong bản hack của tôi ở cuối câu hỏi, nhưng về cơ bản chúng có cùng ý tưởng.
ely

Tham gia có thể làm điều này, nhưng bạn sẽ cần phải đổi tên cột đã thêm. Trong trường hợp này A_r là new_col.
Wouter Overmeire

Ví dụ tham gia ở dưới cùng hoạt động, nhưng nó không được trình bày rõ ràng. Nếu bạn muốn xóa phần đầu tiên của câu trả lời và làm cho phần sau rõ ràng hơn một chút, tôi sẽ ủng hộ ngoài việc chấp nhận.
ely

12
Tôi đã loại bỏ cách tiếp cận đầu tiên. Thành thật mà nói, tôi cảm thấy như mã tự nói lên điều đó, vui lòng chỉnh sửa nếu bạn muốn thêm một số giải thích hoặc tài liệu tham khảo vào tài liệu. Tôi thực sự không thích hệ thống bỏ phiếu như vậy, chỉ ở đây để ủng hộ gấu trúc một chút.
Wouter Overmeire

1
Tôi đã dành một thời gian dài để tìm kiếm câu trả lời này, một chút của một bài viết chưa thực sự nhưng cảm ơn! +1
Dan Carter

50

Trong khi tôi vẫn đang khám phá tất cả các cách cực kỳ thông minh để applynối các phần mà nó đưa ra, đây là một cách khác để thêm một cột mới trong cột gốc sau một thao tác theo nhóm.

In [236]: df
Out[236]: 
  yearmonth    return
0    201202  0.922132
1    201202  0.220270
2    201202  0.228856
3    201203  0.277170
4    201203  0.747347

In [237]: def add_mkt_return(grp):
   .....:     grp['mkt_return'] = grp['return'].sum()
   .....:     return grp
   .....: 

In [238]: df.groupby('yearmonth').apply(add_mkt_return)
Out[238]: 
  yearmonth    return  mkt_return
0    201202  0.922132    1.371258
1    201202  0.220270    1.371258
2    201202  0.228856    1.371258
3    201203  0.277170    1.024516
4    201203  0.747347    1.024516

Bạn cũng có thể thực hiện việc này mà không cần xác định hàm bằng lambda và gán:df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
krassowski

30

Theo nguyên tắc chung khi sử dụng groupby (), nếu bạn sử dụng hàm .transform (), gấu trúc sẽ trả về một bảng có cùng độ dài với ban đầu của bạn. Khi bạn sử dụng các hàm khác như .sum () hoặc .first () thì pandas sẽ trả về một bảng trong đó mỗi hàng là một nhóm.

Tôi không chắc điều này hoạt động như thế nào với ứng dụng nhưng việc triển khai các hàm lambda phức tạp với biến đổi có thể khá phức tạp vì vậy chiến lược mà tôi thấy hữu ích nhất là tạo các biến tôi cần, đặt chúng vào tập dữ liệu ban đầu và sau đó thực hiện các thao tác của tôi ở đó.

Nếu tôi hiểu những gì bạn đang cố gắng làm một cách chính xác, trước tiên, bạn có thể tính tổng vốn hóa thị trường cho từng nhóm:

bdata['group_MarketCap'] = bdata.groupby('yearmonth')['MarketCap'].transform('sum')

Thao tác này sẽ thêm một cột có tên "group_MarketCap" vào dữ liệu ban đầu của bạn, cột này sẽ chứa tổng giá trị vốn hóa thị trường cho mỗi nhóm. Sau đó, bạn có thể tính toán các giá trị có trọng số trực tiếp:

bdata['weighted_P'] = bdata['PriceReturn'] * (bdata['MarketCap']/bdata['group_MarketCap'])

Và cuối cùng, bạn sẽ tính toán trung bình có trọng số cho mỗi nhóm bằng cách sử dụng cùng một hàm biến đổi:

bdata['MarketReturn'] = bdata.groupby('yearmonth')['weighted_P'].transform('sum')

Tôi có xu hướng xây dựng các biến của mình theo cách này. Đôi khi bạn có thể thực hiện tất cả trong một lệnh duy nhất nhưng điều đó không phải lúc nào cũng hoạt động với groupby () vì hầu hết thời gian gấu trúc cần khởi tạo đối tượng mới để hoạt động trên nó ở quy mô tập dữ liệu đầy đủ (tức là bạn không thể thêm hai cột với nhau nếu một cột chưa tồn tại).

Hi vọng điêu nay co ich :)


23

Tôi có thể đề xuất transformphương pháp (thay vì tổng hợp) không? Nếu bạn sử dụng nó trong ví dụ ban đầu, nó sẽ làm những gì bạn muốn (phát sóng).


Sự hiểu biết của tôi là phép biến đổi tạo ra một đối tượng trông giống như đối tượng mà nó đã được truyền qua. Vì vậy, nếu bạn chuyển đổi một DataFrame, bạn không chỉ lấy lại một cột mà còn lấy lại một DataFrame. Trong khi trong trường hợp của tôi, tôi muốn nối một kết quả mới vào khung dữ liệu ban đầu. Hay bạn đang nói rằng tôi nên viết một hàm riêng biệt lấy một khung dữ liệu, tính toán cột mới và nối cột mới, sau đó biến đổi với hàm đó?
ely

2
Tôi đồng ý, chuyển đổi là một lựa chọn tốt hơn, df [ 'A-tháng-sum'] = df.groupby ( 'tháng') [ 'A'] transform (tổng hợp).
Wouter Overmeire

Nhưng tại sao nó sẽ tốt hơn? Nó không giống nhau, không? Nó có nhanh hơn không?
K.-Michael Aye

1
IMHO, transformtrông sạch sẽ hơn. Tôi không có dữ liệu EMS để xác nhận điều này, nhưng công việc sức mạnh này (mặc dù chức năng lambda có thể phải được sửa đổi):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
cd98

1
đúng cho tôi nếu tôi là sai, transformkhông cho phép một để hoạt động trên nhiều cột sau groupby, ví dụ như df.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))sẽ ném ra một lỗi phàn nàn rằng 'không có thuộc tính XXX'
Jason Goal

0

Tôi không tìm thấy cách nào để gán cho khung dữ liệu ban đầu. Vì vậy, tôi chỉ lưu trữ kết quả từ các nhóm và nối chúng. Sau đó, chúng tôi sắp xếp khung dữ liệu nối theo chỉ mục để lấy thứ tự ban đầu làm khung dữ liệu đầu vào. Đây là một mã mẫu:

In [10]: df = pd.DataFrame({'month': np.random.randint(0,11, 100), 'A': np.random.randn(100), 'B': np.random.randn(100)})

In [11]: df.head()
Out[11]:
   month         A         B
0      4 -0.029106 -0.904648
1      2 -2.724073  0.492751
2      7  0.732403  0.689530
3      2  0.487685 -1.017337
4      1  1.160858 -0.025232

In [12]: res = []

In [13]: for month, group in df.groupby('month'):
    ...:     new_df = pd.DataFrame({
    ...:         'A^2+B': group.A ** 2 + group.B,
    ...:         'A+B^2': group.A + group.B**2
    ...:     })
    ...:     res.append(new_df)
    ...:

In [14]: res = pd.concat(res).sort_index()

In [15]: res.head()
Out[15]:
      A^2+B     A+B^2
0 -0.903801  0.789282
1  7.913327 -2.481270
2  1.225944  1.207855
3 -0.779501  1.522660
4  1.322360  1.161495

Phương pháp này khá nhanh và có thể mở rộng. Bạn có thể lấy bất kỳ tính năng nào ở đây.

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.