pandas: как сгруппировать / развернуть, сохранив NaNs? Преобразование float в str и обратно в float работает, но кажется запутанным - PullRequest
2 голосов
/ 11 февраля 2020

Я отслеживаю, в каком месяце произошло определенное событие. Если это не так, поле «месяц» является NaN. Стартовая таблица выглядит так:

+-------+----------+---------+
| Month | Category | Balance |
+-------+----------+---------+
| 1     | a        |     100 |
| nan   | a        |     300 |
| 2     | a        |     200 |
+-------+----------+---------+

Я пытаюсь создать кросс-таблицу следующим образом:

+-------+----------------------------------+
| Month | Category a - cumulative % amount |
+-------+----------------------------------+
|     1 |                             0.16 |
|     2 |                             0.50 |
+-------+----------------------------------+

В 1-м месяце событие произошло за 100/600, ie на 16% Во 2-м месяце событие произошло в совокупности для (100 + 200) / 600 = 50%, где 100 в 1-м месяце и 200 в 2-м.

Моя проблема связана с NaNs , Pandas автоматически удаляет NaN из любой групповой / сводной / кросс-таблицы. Я мог бы преобразовать поле месяца в строку, чтобы при группировке оно не удаляло NaN, но затем pandas сортирует по месяцу, как если бы это была строка, ie сортирует: 10, 48, 5, 6 .

Есть предложения?

Бит ниже работает, но кажется очень запутанным:

  • Я конвертирую 'month' в строку
  • Сделать кросс-таблицу
  • Конвертировать месяц назад в число с плавающей точкой (можно ли сделать это, не перемещая индекс в столбец, а затем столбец обратно в индекс?)
  • Сортировка снова
  • Делать cumsum

Код:

import numpy as np
import pandas as pd

df = pd.DataFrame()
mylen = int(10e3)
df['ix'] = np.arange(0,mylen)
df['amount'] = np.random.uniform(10e3,20e3,mylen)
df['category'] = np.where( df['ix'] <=4000, 'a','b' )
df['month'] = np.random.uniform(3,48,mylen)
df['month'] = np.where( df['ix'] <=1000, np.nan, df['month'] )
df['month rounded'] = np.ceil(df['month'])

ct = pd.crosstab(df['month rounded'].astype(str) , df['category'], \
                 values = df['amount'] ,aggfunc = 'sum', margins = True ,\
                     normalize = 'columns', dropna = False)

# the index is 'month rounded'
ct = ct.reset_index()
ct['month rounded'] = ct['month rounded'].astype('float32')
ct = ct.sort_values('month rounded')
ct = ct.set_index('month rounded')
ct2 = ct.cumsum (axis = 0)

Ответы [ 2 ]

1 голос
/ 11 февраля 2020

Использование:

new_df = df.assign(cumulative=df['Balance'].mask(df['Month'].isna())
                                           .groupby(df['Category'])
                                           .cumsum()
                                           .div(df.groupby('Category')['Balance']
                                                  .transform('sum'))).dropna()
print(new_df)
   Month Category  Balance  cumulative
0    1.0        a      100    0.166667
2    2.0        a      200    0.500000

Если вы хотите создать DataFrame для каждой категории, вы можете создать dict:

df_category = {i:group for i,group in new_df.groupby('Category')}
0 голосов
/ 11 февраля 2020
df['Category a - cumulative % amount'] = (
    df.groupby(by=df.Month.fillna(np.inf))
    .apply(lambda x: x.Balance.cumsum().div(df.Balance.sum()))
    .reset_index(level=0, drop=True)
)

df.dropna()

    Month   Category    Balance Category a - cumulative % amount
0   1       a           100     0.166667
2   2       a           200     0.333333
...