Таблицы частот в pandas (например, plyr в R)

Моя проблема заключается в том, как рассчитать частоты для нескольких переменных в pandas. Я из этого блока данных:

d1 = pd.DataFrame( {'StudentID': ["x1", "x10", "x2","x3", "x4", "x5", "x6",   "x7",     "x8", "x9"],
                       'StudentGender' : ['F', 'M', 'F', 'M', 'F', 'M', 'F', 'M', 'M', 'M'],
                 'ExamenYear': ['2007','2007','2007','2008','2008','2008','2008','2009','2009','2009'],
                 'Exam': ['algebra', 'stats', 'bio', 'algebra', 'algebra', 'stats', 'stats', 'algebra', 'bio', 'bio'],
                 'Participated': ['no','yes','yes','yes','no','yes','yes','yes','yes','yes'],
                  'Passed': ['no','yes','yes','yes','no','yes','yes','yes','no','yes']},
                  columns = ['StudentID', 'StudentGender', 'ExamenYear', 'Exam', 'Participated', 'Passed'])

К следующему результату

             Participated  OfWhichpassed
 ExamenYear                             
2007                   3              2
2008                   4              3
2009                   3              2

(1) Одна из возможностей, которую я пробовал, - вычислить два кадра данных и связать их

t1 = d1.pivot_table(values = 'StudentID', rows=['ExamenYear'], cols = ['Participated'], aggfunc = len)
t2 = d1.pivot_table(values = 'StudentID', rows=['ExamenYear'], cols = ['Passed'], aggfunc = len)
tx = pd.concat([t1, t2] , axis = 1)

Res1 = tx['yes']

(2) Вторая возможность заключается в использовании функции агрегации.

import collections
dg = d1.groupby('ExamenYear')
Res2 = dg.agg({'Participated': len,'Passed': lambda x : collections.Counter(x == 'yes')[True]})

 Res2.columns = ['Participated', 'OfWhichpassed']

Оба способа наименее верны. Как это сделано правильно в pandas?

P.S: Я также попробовал value_counts вместо collection.Counter, но не смог заставить его работать

Для справки: Несколько месяцев назад я задал аналогичный вопрос для R здесь и plyr мог помочь

---- UPDATE ------

пользователь DSM прав. ошибка в желаемом результате таблицы.

(1) Код для опции 1 -

 t1 = d1.pivot_table(values = 'StudentID', rows=['ExamenYear'], aggfunc = len)
 t2 = d1.pivot_table(values = 'StudentID', rows=['ExamenYear'], cols = ['Participated'], aggfunc = len)
 t3 = d1.pivot_table(values = 'StudentID', rows=['ExamenYear'], cols = ['Passed'], aggfunc = len)

 Res1 = pd.DataFrame( {'All': t1,
                       'OfWhichParticipated': t2['yes'],
                     'OfWhichPassed': t3['yes']})

Он даст результат

             All  OfWhichParticipated  OfWhichPassed
ExamenYear                                         
2007          3                    2              2
2008          4                    3              3
2009          3                    3              2

(2) Для варианта 2, благодаря пользователю herrfz, я понял, как использовать value_count, и код будет

Res2 = d1.groupby('ExamenYear').agg({'StudentID': len,
                                 'Participated': lambda x: x.value_counts()['yes'],
                                 'Passed': lambda x: x.value_counts()['yes']})

Res2.columns = ['All', 'OfWgichParticipated', 'OfWhichPassed']

который даст тот же результат, что и Res1

Мой вопрос остается, хотя:

Используя вариант 2, можно ли использовать одну и ту же переменную дважды (для другой операции?), можно ли передать настраиваемое имя для результирующей переменной?

---- НОВОЕ ОБНОВЛЕНИЕ ----

Наконец-то я решил использовать apply, который, как я понимаю, более гибкий.

Ответ 1

Это:

d1.groupby('ExamenYear').agg({'Participated': len, 
                              'Passed': lambda x: sum(x == 'yes')})

не выглядит более неудобным, чем решение R, IMHO.

Ответ 2

Наконец, я решил использовать применить.

Я публикую то, что я придумал, надеясь, что это может быть полезно для других.

Из того, что я понимаю из книги Уэса "Анализ Python для данных"

  • apply более гибкий, чем agg и transform, потому что вы можете определить свою собственную функцию.
  • единственным требованием является то, что функции возвращают объект pandas или скалярное значение.
  • внутренняя механика: функция вызывается для каждой части сгруппированного объекта. Результаты abd склеиваются с помощью pandas.concat
  • Нужно создать структуру "жесткого кода" в конце.

Вот что я придумал

def ZahlOccurence_0(x):
      return pd.Series({'All': len(x['StudentID']),
                       'Part': sum(x['Participated'] == 'yes'),
                       'Pass' :  sum(x['Passed'] == 'yes')})

когда я запустил его:

 d1.groupby('ExamenYear').apply(ZahlOccurence_0)

Я получаю правильные результаты

            All  Part  Pass
ExamenYear                 
2007          3     2     2
2008          4     3     3
2009          3     3     2

Этот подход также позволит мне комбинировать частоты с другими статистическими данными

import numpy as np
d1['testValue'] = np.random.randn(len(d1))

def ZahlOccurence_1(x):
    return pd.Series({'All': len(x['StudentID']),
        'Part': sum(x['Participated'] == 'yes'),
        'Pass' :  sum(x['Passed'] == 'yes'),
        'test' : x['testValue'].mean()})


d1.groupby('ExamenYear').apply(ZahlOccurence_1)


            All  Part  Pass      test
ExamenYear                           
2007          3     2     2  0.358702
2008          4     3     3  1.004504
2009          3     3     2  0.521511

Я надеюсь, что кто-то еще найдет это полезное

Ответ 3

Вы можете использовать функцию pandas crosstab, которая по умолчанию вычисляет частотную таблицу из двух или более переменных. Например,

> import pandas as pd
> pd.crosstab(d1['ExamenYear'], d1['Passed'])
Passed      no  yes
ExamenYear         
2007         1    2
2008         1    3
2009         1    2

Используйте параметр margins=True, если вы также хотите увидеть промежуточный итог каждой строки и столбца.

> pd.crosstab(d1['ExamenYear'], d1['Participated'], margins=True)
Participated  no  yes  All
ExamenYear                
2007           1    2    3
2008           1    3    4
2009           0    3    3
All            2    8   10

Ответ 4

Существует другой подход, который мне нравится использовать для подобных задач, он использует groupby и unstack:

d1 = pd.DataFrame({'StudentID': ["x1", "x10", "x2","x3", "x4", "x5", "x6",   "x7",     "x8", "x9"],
                   'StudentGender' : ['F', 'M', 'F', 'M', 'F', 'M', 'F', 'M', 'M', 'M'],
                   'ExamenYear': ['2007','2007','2007','2008','2008','2008','2008','2009','2009','2009'],
                   'Exam': ['algebra', 'stats', 'bio', 'algebra', 'algebra', 'stats', 'stats', 'algebra', 'bio', 'bio'],
                   'Participated': ['no','yes','yes','yes','no','yes','yes','yes','yes','yes'],
                   'Passed': ['no','yes','yes','yes','no','yes','yes','yes','no','yes']},
                  columns = ['StudentID', 'StudentGender', 'ExamenYear', 'Exam', 'Participated', 'Passed'])

(это только исходные данные сверху)

d2 = d1.groupby("ExamenYear").Participated.value_counts().unstack(fill_value=0)['yes']
d3 = d1.groupby("ExamenYear").Passed.value_counts().unstack(fill_value=0)['yes']
d2.name = "Participated"
d3.name = "Passed"

pd.DataFrame(data=[d2,d3]).T
            Participated  Passed
ExamenYear                      
2007                   2       2
2008                   3       3
2009                   3       2

Это решение немного более громоздко, чем предыдущее, применяя, но это легче понять и расширить, я чувствую.