Панди Python Як призначити результати групової операції назад до стовпців у батьківському фреймі даних?


83

У мене є такий фрейм даних в IPython, де кожен рядок є одним запасом:

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)

Я хочу застосувати групову операцію, яка обчислює середньозважену дохідну величину по всьому, за кожну дату в стовпці "yearmonth".

Це працює як слід:

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

Але тоді я хочу сортувати ці значення назад до індексів у вихідному кадрі даних та зберегти їх як постійні стовпці, де дати збігаються.

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

Я розумію, що це наївне призначення не повинно працювати. Але яка "правильна" ідіома Pandas для присвоєння результату групової операції новому стовпцю на батьківському фреймі даних?

Врешті-решт, я хочу стовпець під назвою "MarketReturn", ніж буде повторюване постійне значення для всіх індексів, що мають дату збігу з результатом операції groupby.

Одним із хак для досягнення цього було б наступне:

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]

Але це повільно, погано і непітонічно.


Ви призначаєте назад згрупованому об’єкту замість початкового кадру.
Wouter Overmeire

2
Я це знаю, і я сказав це прямо під помилкою, де сказав: "Я усвідомлюю, що це наївне призначення не повинно працювати. Але яка" правильна "ідіома Панди для присвоєння результату групової операції новому стовпцю на батьківському фрейм даних? " Виконання призначення за моїм оригінальним фреймом даних на LHS також не працює, і ще менш інтуїтивно зрозуміло, що додавання стовпця на рівні об’єкта GroupBy.
Елі

Відповіді:


74
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

Це все ще вимагає від мене збереження групових обчислень, а не призначення прямо на LHS на лінії, де я виконую групову операцію. Застосування може бути трохи кращим за цикл у моєму руйнуванні внизу запитання, але в основному це одна і та ж ідея.
ely

Приєднання може це зробити, але вам потрібно буде перейменувати доданий стовпець. У цьому випадку A_r - new_col.
Wouter Overmeire

Приклад об’єднання внизу працює, але він не представлений чітко. Якщо ви хочете видалити першу частину відповіді та зробити останню частину більш чіткою, я, окрім прийняття, підтримую.
ely,

12
Я прибрав перший підхід. Чесно кажучи, я відчуваю, що код говорить сам за себе, сміливо редагуйте, якщо ви хочете додати пояснення або посилання на документи. Я не дуже в системі голосування, тому тут, щоб трохи підтримати панд.
Wouter Overmeire

1
Я довго шукав цю відповідь, трохи повідомлення про некро, але спасибі! +1
Ден Картер

52

Поки я все ще вивчаю всі неймовірно розумні способи, що applyоб'єднують дані, що передаються, ось ще один спосіб додати новий стовпець у батьківському після групової операції.

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

1
Ви також можете зробити це, не визначаючи функції за допомогою лямбда- df.groupby('yearmonth').apply(lambda grp: grp.assign(mkt_return=grp['return'].sum()))
сигналу,

32

Як загальне правило при використанні groupby (), якщо ви використовуєте функцію .transform (), панди повернуть таблицю з такою ж довжиною, що і оригінал. Коли ви використовуєте інші функції, такі як .sum () або .first (), тоді панди повернуть таблицю, де кожен рядок є групою.

Я не впевнений, як це працює із застосуванням, але реалізація складних лямбда-функцій із перетворенням може бути досить складною, тому стратегія, яку я вважаю найбільш корисною, полягає у створенні потрібних мені змінних, розміщенні їх у вихідному наборі даних, а потім виконую там свої операції.

Якщо я розумію, що ви намагаєтеся зробити правильно спочатку, ви можете розрахувати загальну ринкову капіталізацію для кожної групи:

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

Це додасть стовпець "group_MarketCap" до вихідних даних, який міститиме суму ринкових обмежень для кожної групи. Тоді ви можете обчислити зважені значення безпосередньо:

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

І нарешті, ви розрахуєте середньозважене середнє для кожної групи, використовуючи ту саму функцію перетворення:

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

Я схильний будувати свої змінні таким чином. Іноді ви можете зробити все, вкладаючи все в одну команду, але це не завжди працює з groupby (), оскільки більшість випадків пандам потрібно створити екземпляр нового об'єкта, щоб працювати з ним у повному масштабі набору даних (тобто ви додати два стовпці разом, якщо один ще не існує).

Сподіваюся, це допомагає :)


24

Чи можу я запропонувати transformметод (замість сукупного)? Якщо ви використовуєте його у своєму оригінальному прикладі, він повинен робити те, що ви хочете (трансляція).


Я зрозумів, що перетворення створює об’єкт, схожий на той, який йому передано. Отже, якщо ви перетворюєте DataFrame, ви не просто повертаєте стовпець, а повертаєте DataFrame. Тоді як у моєму випадку я хочу додати новий результат до вихідного кадру даних. Або ви хочете сказати, що я повинен написати окрему функцію, яка бере кадр даних, обчислює новий стовпець і додає новий стовпець, а потім трансформує за допомогою цієї функції?
ely,

2
Я згоден, трансформація - кращий вибір, df ['A-month-sum'] = df.groupby ('month') ['A'].
Transform

Але чому це було б краще? Це робить те саме, ні? Це швидше?
K.-Michael Aye

1
ІМХО, transform виглядає чистішим. У мене немає даних EMS, щоб підтвердити це, але це може спрацювати (хоча лямбда-функцію, можливо, доведеться змінити):bdata['mkt_return'] = bdata.groupby("yearmonth").transform(lambda x: (x["PriceReturn"]*x["MarketCap"]/x["MarketCap"].sum()).sum())
cd98

1
виправте мене, якщо я помиляюся, transform не дозволяє оперувати кількома стовпцями після groupby, наприклад, df.groupby('col_3')[['col_1','col_2']].transform(lambda x: ((1-x.col_1.mean()) - x.col_2.std()))видасть помилку із скаргою на те, що "немає атрибута XXX"
Джейсон Гол

0

Я не знайшов способу призначити вихідний фрейм даних. Тому я просто зберігаю результати з груп і об’єдную їх. Потім ми сортуємо об’єднаний фрейм даних за індексом, щоб отримати вихідний порядок як вхідний фрейм даних. Ось зразок коду:

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

Цей метод досить швидкий і розширюваний. Ви можете отримати будь-яку функцію тут.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.