После объединения столбца фрейма данных, как создать новый фрейм данных для подсчета количества элементов в каждом бине?

Скажем, у меня есть кадр данных, df:

>>> df

Age    Score
19     1
20     2
24     3
19     2
24     3
24     1
24     3
20     1
19     1
20     3
22     2
22     1

Я хочу создать новый фрейм данных, который содержит Age и сохраняет общее количество элементов в каждом из бинов в разных столбцах Score:

Age       Score 1   Score 2     Score 3
19-21     2         4           3
22-24     2         2           9

Это мой способ сделать это, который я считаю очень запутанным (это означает, что это не должно быть так сложно):

import numpy as np
import pandas as pd

data = pd.DataFrame(columns=['Age', 'Score'])
data['Age'] = [19,20,24,19,24,24,24,20,19,20,22,22]
data['Score'] = [1,2,3,2,3,1,3,1,1,3,2,1]

_, bins = np.histogram(data['Age'], 2)

labels = ['{}-{}'.format(i + 1, j) for i, j in zip(bins[:-1], bins[1:])] #dynamically create labels
labels[0] = '{}-{}'.format(bins[0], bins[1])

df = pd.DataFrame(columns=['Score', labels[0], labels[1]])
df['Score'] = data.Score.unique()
for i in labels:
    df[i] = np.zeros(3)


for i in range(len(data)):
    for j in range(len(labels)):
        m1, m2 = labels[j].split('-') # lower & upper bounds of the age interval
        if ((float(data['Age'][i])>float(m1)) & (float(data['Age'][i])<float(m2))): # find the age group in which each age lies
            if data['Score'][i]==1:
                index = 0
            elif data['Score'][i]==2:
                index = 1
            elif data['Score'][i]==3:
                index = 2

            df[labels[j]][index] += 1

df.sort_values('Score', inplace=True)
df.set_index('Score', inplace=True)
print(df)

Это производит

             19.0-21.5      22.5-24.0
Score                      
1            2.0            2.0
2            4.0            2.0
3            3.0            9.0

Есть ли лучший, более чистый, более эффективный способ достижения этого?


person Kristada673    schedule 08.08.2018    source источник
comment
Вероятно, вы можете начать с: pd.crosstab(pd.cut(df.Age, [19, 21, 24]), df.Score) ...   -  person Jon Clements♦    schedule 08.08.2018


Ответы (3)


IIUC, я думаю, вы можете попробовать один из них:

1.Если вы уже знаете бины:

df['Age'] = np.where(df['Age']<=21,'19-21','22-24')
df.groupby(['Age'])['Score'].value_counts().unstack()

2. Если вы знаете, сколько контейнеров вам нужно:

df.Age = pd.cut(df.Age, bins=2,include_lowest=True)
df.groupby(['Age'])['Score'].value_counts().unstack()

3. Джон Клементс Идея из комментариев:

pd.crosstab(pd.cut(df.Age, [19, 21, 24],include_lowest=True), df.Score)

Все три производят следующий вывод:

Score           1   2   3
Age         
(18.999, 21.0]  3   2   1
(21.0, 24.0]    2   1   3
person Space Impact    schedule 08.08.2018
comment
Не могли бы вы также упомянуть, как сделать так, чтобы бин ограничивал целые числа вместо чисел с плавающей запятой, используя второй метод? Спасибо. - person Kristada673; 09.08.2018
comment
@Kristada673 используйте precision=0 в pd.cut. - person Space Impact; 09.08.2018

cats = ['1', '2', '3']
bins = [0, 1, 2, 3]
data = data[['Age']].join(pd.get_dummies(pd.cut(data.Score, bins, labels=cats)))
data['bins'] = pd.cut(data['Age'], bins=[19,21,24], include_lowest=True)
data.groupby('bins').sum() 

                Age  1  2  3
bins
(18.999, 21.0]  117  3  2  1
(21.0, 24.0]    140  2  1  3

Вы можете удалить/переименовать корзины и серии Age, и это потребует некоторой настройки, чтобы правильно включить включения.

person Alex    schedule 08.08.2018

Я не совсем уверен, какой результат вы хотите (вы умножаете количество очков на счет...?), но это может помочь:

>>> data['age_binned'] = pd.cut(data['Age'], [18,21,24])
>>> data.groupby(['age_binned', 'Score'])['Age'].nunique().unstack()

Score       1  2  3
age_binned         
(18, 21]    2  2  1
(21, 24]    2  1  1

Я предположил, что вам нужно количество уникальных элементов, если вы просто хотите, чтобы общее количество элементов использовало .count() вместо .nunique()

person Dan    schedule 08.08.2018